summaryrefslogtreecommitdiff
path: root/chromium/services/preferences
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2017-07-12 14:07:37 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2017-07-17 10:29:26 +0000
commitec02ee4181c49b61fce1c8fb99292dbb8139cc90 (patch)
tree25cde714b2b71eb639d1cd53f5a22e9ba76e14ef /chromium/services/preferences
parentbb09965444b5bb20b096a291445170876225268d (diff)
downloadqtwebengine-chromium-ec02ee4181c49b61fce1c8fb99292dbb8139cc90.tar.gz
BASELINE: Update Chromium to 59.0.3071.134
Change-Id: Id02ef6fb2204c5fd21668a1c3e6911c83b17585a Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/services/preferences')
-rw-r--r--chromium/services/preferences/BUILD.gn63
-rw-r--r--chromium/services/preferences/DEPS3
-rw-r--r--chromium/services/preferences/manifest.json19
-rw-r--r--chromium/services/preferences/persistent_pref_store_factory.cc51
-rw-r--r--chromium/services/preferences/persistent_pref_store_factory.h28
-rw-r--r--chromium/services/preferences/persistent_pref_store_impl.cc161
-rw-r--r--chromium/services/preferences/persistent_pref_store_impl.h64
-rw-r--r--chromium/services/preferences/persistent_pref_store_impl_unittest.cc336
-rw-r--r--chromium/services/preferences/pref_service_factory_unittest.cc269
-rw-r--r--chromium/services/preferences/pref_store_manager_impl.cc280
-rw-r--r--chromium/services/preferences/pref_store_manager_impl.h136
-rw-r--r--chromium/services/preferences/public/cpp/BUILD.gn33
-rw-r--r--chromium/services/preferences/public/cpp/DEPS7
-rw-r--r--chromium/services/preferences/public/cpp/OWNERS4
-rw-r--r--chromium/services/preferences/public/cpp/persistent_pref_store_client.cc184
-rw-r--r--chromium/services/preferences/public/cpp/persistent_pref_store_client.h89
-rw-r--r--chromium/services/preferences/public/cpp/pref_client_store.cc104
-rw-r--r--chromium/services/preferences/public/cpp/pref_client_store.h82
-rw-r--r--chromium/services/preferences/public/cpp/pref_registry_serializer.cc21
-rw-r--r--chromium/services/preferences/public/cpp/pref_registry_serializer.h18
-rw-r--r--chromium/services/preferences/public/cpp/pref_service_factory.cc116
-rw-r--r--chromium/services/preferences/public/cpp/pref_service_factory.h48
-rw-r--r--chromium/services/preferences/public/cpp/pref_service_main.cc20
-rw-r--r--chromium/services/preferences/public/cpp/pref_service_main.h30
-rw-r--r--chromium/services/preferences/public/cpp/pref_store_adapter.cc40
-rw-r--r--chromium/services/preferences/public/cpp/pref_store_adapter.h44
-rw-r--r--chromium/services/preferences/public/cpp/pref_store_client.cc18
-rw-r--r--chromium/services/preferences/public/cpp/pref_store_client.h34
-rw-r--r--chromium/services/preferences/public/cpp/pref_store_client_mixin.cc129
-rw-r--r--chromium/services/preferences/public/cpp/pref_store_client_mixin.h82
-rw-r--r--chromium/services/preferences/public/cpp/pref_store_impl.cc113
-rw-r--r--chromium/services/preferences/public/cpp/pref_store_impl.h63
-rw-r--r--chromium/services/preferences/public/cpp/preferences.typemap22
-rw-r--r--chromium/services/preferences/public/cpp/preferences_struct_traits.cc136
-rw-r--r--chromium/services/preferences/public/cpp/preferences_struct_traits.h38
-rw-r--r--chromium/services/preferences/public/cpp/tests/BUILD.gn16
-rw-r--r--chromium/services/preferences/public/cpp/tracked/BUILD.gn33
-rw-r--r--chromium/services/preferences/public/cpp/tracked/OWNERS2
-rw-r--r--chromium/services/preferences/public/cpp/tracked/configuration.cc25
-rw-r--r--chromium/services/preferences/public/cpp/tracked/configuration.h37
-rw-r--r--chromium/services/preferences/public/cpp/tracked/mock_validation_delegate.cc84
-rw-r--r--chromium/services/preferences/public/cpp/tracked/mock_validation_delegate.h124
-rw-r--r--chromium/services/preferences/public/cpp/tracked/pref_names.cc13
-rw-r--r--chromium/services/preferences/public/cpp/tracked/pref_names.h14
-rw-r--r--chromium/services/preferences/public/cpp/tracked/tracked_preference_histogram_names.cc31
-rw-r--r--chromium/services/preferences/public/cpp/tracked/tracked_preference_histogram_names.h26
-rw-r--r--chromium/services/preferences/public/cpp/typemaps.gni5
-rw-r--r--chromium/services/preferences/public/interfaces/BUILD.gn2
-rw-r--r--chromium/services/preferences/public/interfaces/preferences.mojom140
-rw-r--r--chromium/services/preferences/public/interfaces/preferences_configuration.mojom62
-rw-r--r--chromium/services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom56
-rw-r--r--chromium/services/preferences/tracked/BUILD.gn86
-rw-r--r--chromium/services/preferences/tracked/DEPS4
-rw-r--r--chromium/services/preferences/tracked/OWNERS2
-rw-r--r--chromium/services/preferences/tracked/device_id.h23
-rw-r--r--chromium/services/preferences/tracked/device_id_mac.cc32
-rw-r--r--chromium/services/preferences/tracked/device_id_stub.cc11
-rw-r--r--chromium/services/preferences/tracked/device_id_unittest.cc35
-rw-r--r--chromium/services/preferences/tracked/device_id_win.cc73
-rw-r--r--chromium/services/preferences/tracked/dictionary_hash_store_contents.cc133
-rw-r--r--chromium/services/preferences/tracked/dictionary_hash_store_contents.h62
-rw-r--r--chromium/services/preferences/tracked/hash_store_contents.h90
-rw-r--r--chromium/services/preferences/tracked/interceptable_pref_filter.cc39
-rw-r--r--chromium/services/preferences/tracked/interceptable_pref_filter.h68
-rw-r--r--chromium/services/preferences/tracked/interceptable_pref_filter_unittest.cc55
-rw-r--r--chromium/services/preferences/tracked/pref_hash_calculator.cc112
-rw-r--r--chromium/services/preferences/tracked/pref_hash_calculator.h55
-rw-r--r--chromium/services/preferences/tracked/pref_hash_calculator_unittest.cc196
-rw-r--r--chromium/services/preferences/tracked/pref_hash_filter.cc366
-rw-r--r--chromium/services/preferences/tracked/pref_hash_filter.h157
-rw-r--r--chromium/services/preferences/tracked/pref_hash_filter_unittest.cc1355
-rw-r--r--chromium/services/preferences/tracked/pref_hash_store.h48
-rw-r--r--chromium/services/preferences/tracked/pref_hash_store_impl.cc321
-rw-r--r--chromium/services/preferences/tracked/pref_hash_store_impl.h61
-rw-r--r--chromium/services/preferences/tracked/pref_hash_store_impl_unittest.cc507
-rw-r--r--chromium/services/preferences/tracked/pref_hash_store_transaction.h80
-rw-r--r--chromium/services/preferences/tracked/registry_hash_store_contents_win.cc182
-rw-r--r--chromium/services/preferences/tracked/registry_hash_store_contents_win.h49
-rw-r--r--chromium/services/preferences/tracked/registry_hash_store_contents_win_unittest.cc120
-rw-r--r--chromium/services/preferences/tracked/segregated_pref_store.cc195
-rw-r--r--chromium/services/preferences/tracked/segregated_pref_store.h122
-rw-r--r--chromium/services/preferences/tracked/segregated_pref_store_unittest.cc304
-rw-r--r--chromium/services/preferences/tracked/tracked_atomic_preference.cc89
-rw-r--r--chromium/services/preferences/tracked/tracked_atomic_preference.h55
-rw-r--r--chromium/services/preferences/tracked/tracked_persistent_pref_store_factory.cc133
-rw-r--r--chromium/services/preferences/tracked/tracked_persistent_pref_store_factory.h27
-rw-r--r--chromium/services/preferences/tracked/tracked_preference.h44
-rw-r--r--chromium/services/preferences/tracked/tracked_preference_helper.cc139
-rw-r--r--chromium/services/preferences/tracked/tracked_preference_helper.h75
-rw-r--r--chromium/services/preferences/tracked/tracked_preferences_migration.cc333
-rw-r--r--chromium/services/preferences/tracked/tracked_preferences_migration.h45
-rw-r--r--chromium/services/preferences/tracked/tracked_preferences_migration_unittest.cc645
-rw-r--r--chromium/services/preferences/tracked/tracked_split_preference.cc120
-rw-r--r--chromium/services/preferences/tracked/tracked_split_preference.h58
-rw-r--r--chromium/services/preferences/unittest_manifest.json16
95 files changed, 10060 insertions, 217 deletions
diff --git a/chromium/services/preferences/BUILD.gn b/chromium/services/preferences/BUILD.gn
index 8640a9194ee..12b41820c89 100644
--- a/chromium/services/preferences/BUILD.gn
+++ b/chromium/services/preferences/BUILD.gn
@@ -2,9 +2,70 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-group("tests") {
+import("//services/catalog/public/tools/catalog.gni")
+import("//services/service_manager/public/service_manifest.gni")
+import("//testing/test.gni")
+
+service_manifest("manifest") {
+ name = "preferences"
+ source = "manifest.json"
+}
+
+source_set("preferences") {
+ visibility = [
+ ":*",
+ "//services/preferences/public/cpp:service_main",
+ ]
+ deps = [
+ "//components/prefs",
+ "//services/preferences/public/cpp",
+ "//services/preferences/public/interfaces",
+ "//services/preferences/tracked",
+ "//services/service_manager/public/cpp",
+ ]
+ sources = [
+ "persistent_pref_store_factory.cc",
+ "persistent_pref_store_factory.h",
+ "persistent_pref_store_impl.cc",
+ "persistent_pref_store_impl.h",
+ "pref_store_manager_impl.cc",
+ "pref_store_manager_impl.h",
+ ]
+}
+
+source_set("tests") {
testonly = true
deps = [
+ ":preferences",
+ "//base",
+ "//base/test:test_support",
+ "//components/prefs:test_support",
+ "//mojo/public/cpp/bindings:bindings",
+ "//services/preferences/public/cpp",
+ "//services/preferences/public/cpp:service_main",
"//services/preferences/public/cpp/tests",
+ "//services/preferences/public/interfaces",
+ "//services/preferences/tracked:unit_tests",
+ "//services/service_manager/public/cpp",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+ sources = [
+ "persistent_pref_store_impl_unittest.cc",
]
+ if (!is_ios) {
+ sources += [ "pref_service_factory_unittest.cc" ]
+ deps += [ "//services/service_manager/public/cpp:service_test_support" ]
+ }
+}
+
+service_manifest("unittest_manifest") {
+ name = "prefs_unittests"
+ source = "unittest_manifest.json"
+ packaged_services = [ ":manifest" ]
+}
+
+catalog("tests_catalog") {
+ testonly = true
+ embedded_services = [ ":unittest_manifest" ]
}
diff --git a/chromium/services/preferences/DEPS b/chromium/services/preferences/DEPS
new file mode 100644
index 00000000000..eac076192dc
--- /dev/null
+++ b/chromium/services/preferences/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+components/prefs",
+]
diff --git a/chromium/services/preferences/manifest.json b/chromium/services/preferences/manifest.json
new file mode 100644
index 00000000000..40473465595
--- /dev/null
+++ b/chromium/services/preferences/manifest.json
@@ -0,0 +1,19 @@
+{
+ "name": "preferences",
+ "display_name": "Preferences",
+ "interface_provider_specs": {
+ "service_manager:connector": {
+ "provides": {
+ "pref_client": [
+ "prefs::mojom::PrefStoreConnector",
+ "prefs::mojom::PrefStoreRegistry"
+ ],
+ "pref_control": [
+ "prefs::mojom::PrefServiceControl"
+ ]
+ },
+ "requires": {
+ }
+ }
+ }
+}
diff --git a/chromium/services/preferences/persistent_pref_store_factory.cc b/chromium/services/preferences/persistent_pref_store_factory.cc
new file mode 100644
index 00000000000..587ac96d182
--- /dev/null
+++ b/chromium/services/preferences/persistent_pref_store_factory.cc
@@ -0,0 +1,51 @@
+// Copyright 2017 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 "services/preferences/persistent_pref_store_factory.h"
+
+#include <memory>
+#include <utility>
+
+#include "components/prefs/json_pref_store.h"
+#include "components/prefs/pref_filter.h"
+#include "services/preferences/persistent_pref_store_impl.h"
+#include "services/preferences/tracked/tracked_persistent_pref_store_factory.h"
+
+namespace prefs {
+namespace {
+
+std::unique_ptr<PersistentPrefStoreImpl> CreateSimplePersistentPrefStore(
+ mojom::SimplePersistentPrefStoreConfigurationPtr config,
+ base::SequencedWorkerPool* worker_pool,
+ base::Closure on_initialized) {
+ return base::MakeUnique<PersistentPrefStoreImpl>(
+ new JsonPrefStore(config->pref_filename,
+ JsonPrefStore::GetTaskRunnerForFile(
+ config->pref_filename.DirName(), worker_pool),
+ nullptr),
+ std::move(on_initialized));
+}
+
+} // namespace
+
+std::unique_ptr<PersistentPrefStoreImpl> CreatePersistentPrefStore(
+ mojom::PersistentPrefStoreConfigurationPtr configuration,
+ base::SequencedWorkerPool* worker_pool,
+ base::Closure on_initialized) {
+ if (configuration->is_simple_configuration()) {
+ return CreateSimplePersistentPrefStore(
+ std::move(configuration->get_simple_configuration()), worker_pool,
+ std::move(on_initialized));
+ }
+ if (configuration->is_tracked_configuration()) {
+ return base::MakeUnique<PersistentPrefStoreImpl>(
+ CreateTrackedPersistentPrefStore(
+ std::move(configuration->get_tracked_configuration()), worker_pool),
+ std::move(on_initialized));
+ }
+ NOTREACHED();
+ return nullptr;
+}
+
+} // namespace prefs
diff --git a/chromium/services/preferences/persistent_pref_store_factory.h b/chromium/services/preferences/persistent_pref_store_factory.h
new file mode 100644
index 00000000000..06cf8f701b0
--- /dev/null
+++ b/chromium/services/preferences/persistent_pref_store_factory.h
@@ -0,0 +1,28 @@
+// Copyright 2017 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 SERVICES_PREFERENCES_PERSISTENT_PREF_STORE_FACTORY_H_
+#define SERVICES_PREFERENCES_PERSISTENT_PREF_STORE_FACTORY_H_
+
+#include <memory>
+
+#include "services/preferences/public/interfaces/preferences.mojom.h"
+
+namespace base {
+class SequencedWorkerPool;
+}
+
+namespace prefs {
+class PersistentPrefStoreImpl;
+
+// Create a new mojom::PersistentPrefStore impl that runs on |worker_pool|
+// configured by |configuration|.
+std::unique_ptr<PersistentPrefStoreImpl> CreatePersistentPrefStore(
+ mojom::PersistentPrefStoreConfigurationPtr configuration,
+ base::SequencedWorkerPool* worker_pool,
+ base::Closure on_initialized);
+
+} // namespace prefs
+
+#endif // SERVICES_PREFERENCES_PERSISTENT_PREF_STORE_FACTORY_H_
diff --git a/chromium/services/preferences/persistent_pref_store_impl.cc b/chromium/services/preferences/persistent_pref_store_impl.cc
new file mode 100644
index 00000000000..95930387654
--- /dev/null
+++ b/chromium/services/preferences/persistent_pref_store_impl.cc
@@ -0,0 +1,161 @@
+// Copyright 2017 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 "services/preferences/persistent_pref_store_impl.h"
+
+#include <utility>
+
+#include "base/auto_reset.h"
+#include "base/memory/ptr_util.h"
+#include "base/stl_util.h"
+#include "base/values.h"
+#include "components/prefs/persistent_pref_store.h"
+#include "mojo/public/cpp/bindings/binding.h"
+
+namespace prefs {
+
+class PersistentPrefStoreImpl::Connection : public mojom::PersistentPrefStore {
+ public:
+ Connection(PersistentPrefStoreImpl* pref_store,
+ mojom::PersistentPrefStoreRequest request,
+ mojom::PrefStoreObserverPtr observer,
+ ObservedPrefs observed_keys)
+ : pref_store_(pref_store),
+ binding_(this, std::move(request)),
+ observer_(std::move(observer)),
+ observed_keys_(std::move(observed_keys)) {
+ auto error_callback =
+ base::Bind(&PersistentPrefStoreImpl::Connection::OnConnectionError,
+ base::Unretained(this));
+ binding_.set_connection_error_handler(error_callback);
+ observer_.set_connection_error_handler(error_callback);
+ }
+
+ ~Connection() override = default;
+
+ void OnPrefValuesChanged(const std::vector<mojom::PrefUpdatePtr>& updates) {
+ if (write_in_progress_)
+ return;
+
+ std::vector<mojom::PrefUpdatePtr> filtered_updates;
+ for (const auto& update : updates) {
+ if (base::ContainsKey(observed_keys_, update->key)) {
+ filtered_updates.push_back(mojom::PrefUpdate::New(
+ update->key,
+ update->value ? update->value->CreateDeepCopy() : nullptr, 0));
+ }
+ }
+ if (!filtered_updates.empty())
+ observer_->OnPrefsChanged(std::move(filtered_updates));
+ }
+
+ private:
+ // mojom::PersistentPrefStore:
+ void SetValues(std::vector<mojom::PrefUpdatePtr> updates) override {
+ base::AutoReset<bool> scoped_call_in_progress(&write_in_progress_, true);
+ pref_store_->SetValues(std::move(updates));
+ }
+
+ void CommitPendingWrite() override { pref_store_->CommitPendingWrite(); }
+ void SchedulePendingLossyWrites() override {
+ pref_store_->SchedulePendingLossyWrites();
+ }
+ void ClearMutableValues() override { pref_store_->ClearMutableValues(); }
+
+ void OnConnectionError() { pref_store_->OnConnectionError(this); }
+
+ // Owns |this|.
+ PersistentPrefStoreImpl* const pref_store_;
+
+ mojo::Binding<mojom::PersistentPrefStore> binding_;
+ mojom::PrefStoreObserverPtr observer_;
+ const ObservedPrefs observed_keys_;
+
+ // If true then a write is in progress and any update notifications should be
+ // ignored, as those updates would originate from ourselves.
+ bool write_in_progress_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(Connection);
+};
+
+PersistentPrefStoreImpl::PersistentPrefStoreImpl(
+ scoped_refptr<PersistentPrefStore> backing_pref_store,
+ base::OnceClosure on_initialized)
+ : backing_pref_store_(backing_pref_store) {
+ if (!backing_pref_store_->IsInitializationComplete()) {
+ backing_pref_store_->AddObserver(this);
+ on_initialized_ = std::move(on_initialized);
+ initializing_ = true;
+ backing_pref_store_->ReadPrefsAsync(nullptr);
+ }
+}
+
+PersistentPrefStoreImpl::~PersistentPrefStoreImpl() = default;
+
+mojom::PersistentPrefStoreConnectionPtr
+PersistentPrefStoreImpl::CreateConnection(ObservedPrefs observed_prefs) {
+ DCHECK(!initializing_);
+ if (!backing_pref_store_->IsInitializationComplete()) {
+ // |backing_pref_store_| initialization failed.
+ return mojom::PersistentPrefStoreConnection::New(
+ nullptr, nullptr, backing_pref_store_->GetReadError(),
+ backing_pref_store_->ReadOnly());
+ }
+ mojom::PersistentPrefStorePtr pref_store_ptr;
+ mojom::PrefStoreObserverPtr observer;
+ mojom::PrefStoreObserverRequest observer_request =
+ mojo::MakeRequest(&observer);
+ auto connection = base::MakeUnique<Connection>(
+ this, mojo::MakeRequest(&pref_store_ptr), std::move(observer),
+ std::move(observed_prefs));
+ auto* connection_ptr = connection.get();
+ connections_.insert(std::make_pair(connection_ptr, std::move(connection)));
+ return mojom::PersistentPrefStoreConnection::New(
+ mojom::PrefStoreConnection::New(std::move(observer_request),
+ backing_pref_store_->GetValues(), true),
+ std::move(pref_store_ptr), backing_pref_store_->GetReadError(),
+ backing_pref_store_->ReadOnly());
+}
+
+void PersistentPrefStoreImpl::OnPrefValueChanged(const std::string& key) {}
+
+void PersistentPrefStoreImpl::OnInitializationCompleted(bool succeeded) {
+ DCHECK(initializing_);
+ backing_pref_store_->RemoveObserver(this);
+ initializing_ = false;
+ std::move(on_initialized_).Run();
+}
+
+void PersistentPrefStoreImpl::SetValues(
+ std::vector<mojom::PrefUpdatePtr> updates) {
+ for (auto& entry : connections_)
+ entry.first->OnPrefValuesChanged(updates);
+
+ for (auto& update : updates) {
+ if (update->value) {
+ backing_pref_store_->SetValue(update->key, std::move(update->value),
+ update->flags);
+ } else {
+ backing_pref_store_->RemoveValue(update->key, update->flags);
+ }
+ }
+}
+
+void PersistentPrefStoreImpl::CommitPendingWrite() {
+ backing_pref_store_->CommitPendingWrite();
+}
+
+void PersistentPrefStoreImpl::SchedulePendingLossyWrites() {
+ backing_pref_store_->SchedulePendingLossyWrites();
+}
+
+void PersistentPrefStoreImpl::ClearMutableValues() {
+ backing_pref_store_->ClearMutableValues();
+}
+
+void PersistentPrefStoreImpl::OnConnectionError(Connection* connection) {
+ connections_.erase(connection);
+}
+
+} // namespace prefs
diff --git a/chromium/services/preferences/persistent_pref_store_impl.h b/chromium/services/preferences/persistent_pref_store_impl.h
new file mode 100644
index 00000000000..5111ed7f4e2
--- /dev/null
+++ b/chromium/services/preferences/persistent_pref_store_impl.h
@@ -0,0 +1,64 @@
+// Copyright 2017 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 SERVICES_PREFERENCES_PERSISTENT_PREF_STORE_IMPL_H_
+#define SERVICES_PREFERENCES_PERSISTENT_PREF_STORE_IMPL_H_
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "base/macros.h"
+#include "services/preferences/public/interfaces/preferences.mojom.h"
+#include "services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom.h"
+
+namespace prefs {
+
+class PersistentPrefStoreImpl : public PrefStore::Observer {
+ public:
+ using ObservedPrefs = std::set<std::string>;
+
+ // If |initialized()| is false after construction, |on_initialized| will be
+ // called when it becomes true.
+ PersistentPrefStoreImpl(
+ scoped_refptr<PersistentPrefStore> backing_pref_store,
+ base::OnceClosure on_initialized);
+
+ ~PersistentPrefStoreImpl() override;
+
+ mojom::PersistentPrefStoreConnectionPtr CreateConnection(
+ ObservedPrefs observed_prefs);
+
+ bool initialized() { return !initializing_; }
+
+ private:
+ class Connection;
+
+ void SetValues(std::vector<mojom::PrefUpdatePtr> updates);
+
+ void CommitPendingWrite();
+ void SchedulePendingLossyWrites();
+ void ClearMutableValues();
+
+ // PrefStore::Observer:
+ void OnPrefValueChanged(const std::string& key) override;
+ void OnInitializationCompleted(bool succeeded) override;
+
+ void OnConnectionError(Connection* connection);
+
+ scoped_refptr<PersistentPrefStore> backing_pref_store_;
+
+ bool initializing_ = false;
+
+ std::unordered_map<Connection*, std::unique_ptr<Connection>> connections_;
+
+ base::OnceClosure on_initialized_;
+
+ DISALLOW_COPY_AND_ASSIGN(PersistentPrefStoreImpl);
+};
+
+} // namespace prefs
+
+#endif // SERVICES_PREFERENCES_PERSISTENT_PREF_STORE_IMPL_H_
diff --git a/chromium/services/preferences/persistent_pref_store_impl_unittest.cc b/chromium/services/preferences/persistent_pref_store_impl_unittest.cc
new file mode 100644
index 00000000000..7a326276c0a
--- /dev/null
+++ b/chromium/services/preferences/persistent_pref_store_impl_unittest.cc
@@ -0,0 +1,336 @@
+// Copyright 2017 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 "services/preferences/persistent_pref_store_impl.h"
+
+#include <utility>
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/values.h"
+#include "components/prefs/in_memory_pref_store.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+#include "services/preferences/public/cpp/persistent_pref_store_client.h"
+#include "services/preferences/public/interfaces/preferences.mojom.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::Invoke;
+using testing::WithoutArgs;
+
+namespace prefs {
+namespace {
+
+class PrefStoreObserverMock : public PrefStore::Observer {
+ public:
+ MOCK_METHOD1(OnPrefValueChanged, void(const std::string&));
+ MOCK_METHOD1(OnInitializationCompleted, void(bool));
+};
+
+class PersistentPrefStoreMock : public InMemoryPrefStore {
+ public:
+ MOCK_METHOD0(CommitPendingWrite, void());
+ MOCK_METHOD0(SchedulePendingLossyWrites, void());
+ MOCK_METHOD0(ClearMutableValues, void());
+
+ private:
+ ~PersistentPrefStoreMock() override = default;
+};
+
+void ExpectPrefChange(PrefStore* pref_store, base::StringPiece key) {
+ PrefStoreObserverMock observer;
+ pref_store->AddObserver(&observer);
+ base::RunLoop run_loop;
+ EXPECT_CALL(observer, OnPrefValueChanged(key.as_string()))
+ .WillOnce(WithoutArgs(Invoke([&run_loop]() { run_loop.Quit(); })));
+ run_loop.Run();
+ pref_store->RemoveObserver(&observer);
+}
+
+class InitializationMockPersistentPrefStore : public InMemoryPrefStore {
+ public:
+ InitializationMockPersistentPrefStore(
+ bool success,
+ PersistentPrefStore::PrefReadError error,
+ bool read_only)
+ : success_(success), read_error_(error), read_only_(read_only) {}
+
+ bool IsInitializationComplete() const override {
+ return initialized_ && success_;
+ }
+
+ void AddObserver(PrefStore::Observer* observer) override {
+ observers_.AddObserver(observer);
+ }
+
+ void RemoveObserver(PrefStore::Observer* observer) override {
+ observers_.RemoveObserver(observer);
+ }
+
+ void ReadPrefsAsync(ReadErrorDelegate* error_delegate) override {
+ DCHECK(!error_delegate);
+ DCHECK(!initialized_);
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&InitializationMockPersistentPrefStore::CompleteRead, this));
+ }
+
+ void CompleteRead() {
+ initialized_ = true;
+ for (auto& observer : observers_) {
+ observer.OnInitializationCompleted(success_);
+ }
+ }
+
+ PersistentPrefStore::PrefReadError GetReadError() const override {
+ return read_error_;
+ }
+ bool ReadOnly() const override { return read_only_; }
+
+ private:
+ ~InitializationMockPersistentPrefStore() override = default;
+
+ bool initialized_ = false;
+ bool success_;
+ PersistentPrefStore::PrefReadError read_error_;
+ bool read_only_;
+ base::ObserverList<PrefStore::Observer, true> observers_;
+};
+
+constexpr char kKey[] = "path.to.key";
+constexpr char kOtherKey[] = "path.to.other_key";
+
+class PersistentPrefStoreImplTest : public testing::Test {
+ public:
+ PersistentPrefStoreImplTest() = default;
+
+ // testing::Test:
+ void TearDown() override {
+ pref_store_ = nullptr;
+ base::RunLoop().RunUntilIdle();
+ impl_.reset();
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void CreateImpl(scoped_refptr<PersistentPrefStore> backing_pref_store) {
+ base::RunLoop run_loop;
+ bool initialized = backing_pref_store->IsInitializationComplete();
+ impl_ = base::MakeUnique<PersistentPrefStoreImpl>(
+ std::move(backing_pref_store), run_loop.QuitClosure());
+ if (!initialized)
+ run_loop.Run();
+ pref_store_ = CreateConnection();
+ }
+
+ scoped_refptr<PersistentPrefStore> CreateConnection(
+ PersistentPrefStoreImpl::ObservedPrefs observed_prefs =
+ PersistentPrefStoreImpl::ObservedPrefs()) {
+ if (observed_prefs.empty())
+ observed_prefs.insert({kKey, kOtherKey});
+ return make_scoped_refptr(new PersistentPrefStoreClient(
+ impl_->CreateConnection(std::move(observed_prefs))));
+ }
+
+ PersistentPrefStore* pref_store() { return pref_store_.get(); }
+
+ private:
+ base::MessageLoop message_loop_;
+
+ std::unique_ptr<PersistentPrefStoreImpl> impl_;
+
+ scoped_refptr<PersistentPrefStore> pref_store_;
+
+ DISALLOW_COPY_AND_ASSIGN(PersistentPrefStoreImplTest);
+};
+
+TEST_F(PersistentPrefStoreImplTest, InitializationSuccess) {
+ auto backing_pref_store =
+ make_scoped_refptr(new InitializationMockPersistentPrefStore(
+ true, PersistentPrefStore::PREF_READ_ERROR_NONE, false));
+ CreateImpl(backing_pref_store);
+ EXPECT_TRUE(pref_store()->IsInitializationComplete());
+ EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE,
+ pref_store()->GetReadError());
+ EXPECT_FALSE(pref_store()->ReadOnly());
+}
+
+TEST_F(PersistentPrefStoreImplTest, InitializationFailure) {
+ auto backing_pref_store =
+ make_scoped_refptr(new InitializationMockPersistentPrefStore(
+ false, PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE, true));
+ CreateImpl(backing_pref_store);
+ EXPECT_FALSE(pref_store()->IsInitializationComplete());
+ EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE,
+ pref_store()->GetReadError());
+ EXPECT_TRUE(pref_store()->ReadOnly());
+}
+
+TEST_F(PersistentPrefStoreImplTest, InitialValue) {
+ auto backing_pref_store = make_scoped_refptr(new InMemoryPrefStore());
+ const base::Value value("value");
+ backing_pref_store->SetValue(kKey, value.CreateDeepCopy(), 0);
+ CreateImpl(backing_pref_store);
+ EXPECT_TRUE(pref_store()->IsInitializationComplete());
+ const base::Value* output = nullptr;
+ ASSERT_TRUE(pref_store()->GetValue(kKey, &output));
+ EXPECT_TRUE(value.Equals(output));
+}
+
+TEST_F(PersistentPrefStoreImplTest, InitialValueWithoutPathExpansion) {
+ auto backing_pref_store = make_scoped_refptr(new InMemoryPrefStore());
+ base::DictionaryValue dict;
+ dict.SetStringWithoutPathExpansion(kKey, "value");
+ backing_pref_store->SetValue(kKey, dict.CreateDeepCopy(), 0);
+ CreateImpl(backing_pref_store);
+ EXPECT_TRUE(pref_store()->IsInitializationComplete());
+ const base::Value* output = nullptr;
+ ASSERT_TRUE(pref_store()->GetValue(kKey, &output));
+ EXPECT_TRUE(dict.Equals(output));
+}
+
+TEST_F(PersistentPrefStoreImplTest, WriteObservedByOtherClient) {
+ auto backing_pref_store = make_scoped_refptr(new InMemoryPrefStore());
+ CreateImpl(backing_pref_store);
+ EXPECT_TRUE(pref_store()->IsInitializationComplete());
+
+ auto other_pref_store = CreateConnection();
+ EXPECT_TRUE(other_pref_store->IsInitializationComplete());
+
+ const base::Value value("value");
+ pref_store()->SetValueSilently(kKey, value.CreateDeepCopy(), 0);
+
+ ExpectPrefChange(other_pref_store.get(), kKey);
+ const base::Value* output = nullptr;
+ ASSERT_TRUE(other_pref_store->GetValue(kKey, &output));
+ EXPECT_TRUE(value.Equals(output));
+}
+
+TEST_F(PersistentPrefStoreImplTest, UnregisteredPrefNotObservedByOtherClient) {
+ auto backing_pref_store = make_scoped_refptr(new InMemoryPrefStore());
+ CreateImpl(backing_pref_store);
+ EXPECT_TRUE(pref_store()->IsInitializationComplete());
+
+ PersistentPrefStoreImpl::ObservedPrefs observed_prefs;
+ observed_prefs.insert(kKey);
+
+ auto other_pref_store = CreateConnection(std::move(observed_prefs));
+ EXPECT_TRUE(other_pref_store->IsInitializationComplete());
+
+ pref_store()->SetValue(kOtherKey, base::MakeUnique<base::Value>(123), 0);
+ pref_store()->SetValue(kKey, base::MakeUnique<base::Value>("value"), 0);
+
+ ExpectPrefChange(other_pref_store.get(), kKey);
+ EXPECT_FALSE(other_pref_store->GetValue(kOtherKey, nullptr));
+}
+
+TEST_F(PersistentPrefStoreImplTest,
+ WriteWithoutPathExpansionObservedByOtherClient) {
+ auto backing_pref_store = make_scoped_refptr(new InMemoryPrefStore());
+ CreateImpl(backing_pref_store);
+ EXPECT_TRUE(pref_store()->IsInitializationComplete());
+
+ auto other_pref_store = CreateConnection();
+ EXPECT_TRUE(other_pref_store->IsInitializationComplete());
+
+ base::DictionaryValue dict;
+ dict.SetStringWithoutPathExpansion(kKey, "value");
+ pref_store()->SetValue(kKey, dict.CreateDeepCopy(), 0);
+
+ ExpectPrefChange(other_pref_store.get(), kKey);
+ const base::Value* output = nullptr;
+ ASSERT_TRUE(other_pref_store->GetValue(kKey, &output));
+ EXPECT_TRUE(dict.Equals(output));
+}
+
+TEST_F(PersistentPrefStoreImplTest, RemoveObservedByOtherClient) {
+ auto backing_pref_store = make_scoped_refptr(new InMemoryPrefStore());
+ const base::Value value("value");
+ backing_pref_store->SetValue(kKey, value.CreateDeepCopy(), 0);
+ CreateImpl(backing_pref_store);
+ EXPECT_TRUE(pref_store()->IsInitializationComplete());
+
+ auto other_pref_store = CreateConnection();
+ EXPECT_TRUE(other_pref_store->IsInitializationComplete());
+
+ const base::Value* output = nullptr;
+ ASSERT_TRUE(other_pref_store->GetValue(kKey, &output));
+ EXPECT_TRUE(value.Equals(output));
+ pref_store()->RemoveValue(kKey, 0);
+
+ // This should be a no-op and shouldn't trigger a notification for the other
+ // client.
+ pref_store()->RemoveValue(kKey, 0);
+
+ ExpectPrefChange(other_pref_store.get(), kKey);
+ EXPECT_FALSE(other_pref_store->GetValue(kKey, &output));
+}
+
+TEST_F(PersistentPrefStoreImplTest,
+ RemoveWithoutPathExpansionObservedByOtherClient) {
+ auto backing_pref_store = make_scoped_refptr(new InMemoryPrefStore());
+ base::DictionaryValue dict;
+ dict.SetStringWithoutPathExpansion(kKey, "value");
+ backing_pref_store->SetValue(kKey, dict.CreateDeepCopy(), 0);
+ CreateImpl(backing_pref_store);
+ EXPECT_TRUE(pref_store()->IsInitializationComplete());
+
+ auto other_pref_store = CreateConnection();
+ EXPECT_TRUE(other_pref_store->IsInitializationComplete());
+
+ const base::Value* output = nullptr;
+ ASSERT_TRUE(other_pref_store->GetValue(kKey, &output));
+ EXPECT_TRUE(dict.Equals(output));
+
+ base::Value* mutable_value = nullptr;
+ dict.SetStringWithoutPathExpansion(kKey, "value");
+ ASSERT_TRUE(pref_store()->GetMutableValue(kKey, &mutable_value));
+ base::DictionaryValue* mutable_dict = nullptr;
+ ASSERT_TRUE(mutable_value->GetAsDictionary(&mutable_dict));
+ mutable_dict->RemoveWithoutPathExpansion(kKey, nullptr);
+ pref_store()->ReportValueChanged(kKey, 0);
+
+ ExpectPrefChange(other_pref_store.get(), kKey);
+ ASSERT_TRUE(other_pref_store->GetValue(kKey, &output));
+ const base::DictionaryValue* dict_value = nullptr;
+ ASSERT_TRUE(output->GetAsDictionary(&dict_value));
+ EXPECT_TRUE(dict_value->empty());
+}
+
+TEST_F(PersistentPrefStoreImplTest, CommitPendingWrite) {
+ auto backing_store = make_scoped_refptr(new PersistentPrefStoreMock);
+ CreateImpl(backing_store);
+ base::RunLoop run_loop;
+ EXPECT_CALL(*backing_store, CommitPendingWrite())
+ .Times(2)
+ .WillOnce(WithoutArgs(Invoke([&run_loop]() { run_loop.Quit(); })));
+ pref_store()->CommitPendingWrite();
+ run_loop.Run();
+}
+
+TEST_F(PersistentPrefStoreImplTest, SchedulePendingLossyWrites) {
+ auto backing_store = make_scoped_refptr(new PersistentPrefStoreMock);
+ CreateImpl(backing_store);
+ base::RunLoop run_loop;
+ EXPECT_CALL(*backing_store, SchedulePendingLossyWrites())
+ .WillOnce(WithoutArgs(Invoke([&run_loop]() { run_loop.Quit(); })));
+ EXPECT_CALL(*backing_store, CommitPendingWrite()).Times(1);
+ pref_store()->SchedulePendingLossyWrites();
+ run_loop.Run();
+}
+
+TEST_F(PersistentPrefStoreImplTest, ClearMutableValues) {
+ auto backing_store = make_scoped_refptr(new PersistentPrefStoreMock);
+ CreateImpl(backing_store);
+ base::RunLoop run_loop;
+ EXPECT_CALL(*backing_store, ClearMutableValues())
+ .WillOnce(WithoutArgs(Invoke([&run_loop]() { run_loop.Quit(); })));
+ EXPECT_CALL(*backing_store, CommitPendingWrite()).Times(1);
+ pref_store()->ClearMutableValues();
+ run_loop.Run();
+}
+
+} // namespace
+} // namespace prefs
diff --git a/chromium/services/preferences/pref_service_factory_unittest.cc b/chromium/services/preferences/pref_service_factory_unittest.cc
new file mode 100644
index 00000000000..21094e823ad
--- /dev/null
+++ b/chromium/services/preferences/pref_service_factory_unittest.cc
@@ -0,0 +1,269 @@
+// Copyright (c) 2017 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 "services/preferences/public/cpp/pref_service_factory.h"
+
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/test/sequenced_worker_pool_owner.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/value_map_pref_store.h"
+#include "components/prefs/writeable_pref_store.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+#include "services/preferences/public/cpp/pref_service_main.h"
+#include "services/preferences/public/cpp/pref_store_impl.h"
+#include "services/preferences/public/interfaces/preferences.mojom.h"
+#include "services/service_manager/public/cpp/binder_registry.h"
+#include "services/service_manager/public/cpp/interface_factory.h"
+#include "services/service_manager/public/cpp/service_context.h"
+#include "services/service_manager/public/cpp/service_test.h"
+#include "services/service_manager/public/interfaces/service_factory.mojom.h"
+
+namespace prefs {
+namespace {
+
+class ServiceTestClient : public service_manager::test::ServiceTestClient,
+ public service_manager::mojom::ServiceFactory,
+ public service_manager::InterfaceFactory<
+ service_manager::mojom::ServiceFactory> {
+ public:
+ ServiceTestClient(service_manager::test::ServiceTest* test,
+ scoped_refptr<base::SequencedWorkerPool> worker_pool)
+ : service_manager::test::ServiceTestClient(test),
+ worker_pool_(std::move(worker_pool)) {
+ registry_.AddInterface<service_manager::mojom::ServiceFactory>(this);
+ }
+
+ protected:
+ void OnBindInterface(const service_manager::ServiceInfo& source_info,
+ const std::string& interface_name,
+ mojo::ScopedMessagePipeHandle interface_pipe) override {
+ registry_.BindInterface(source_info.identity, interface_name,
+ std::move(interface_pipe));
+ }
+
+ void CreateService(service_manager::mojom::ServiceRequest request,
+ const std::string& name) override {
+ if (name == prefs::mojom::kServiceName) {
+ pref_service_context_.reset(new service_manager::ServiceContext(
+ CreatePrefService(
+ {PrefValueStore::COMMAND_LINE_STORE,
+ PrefValueStore::RECOMMENDED_STORE, PrefValueStore::USER_STORE,
+ PrefValueStore::DEFAULT_STORE},
+ worker_pool_),
+ std::move(request)));
+ }
+ }
+
+ void Create(const service_manager::Identity& remote_identity,
+ service_manager::mojom::ServiceFactoryRequest request) override {
+ service_factory_bindings_.AddBinding(this, std::move(request));
+ }
+
+ private:
+ scoped_refptr<base::SequencedWorkerPool> worker_pool_;
+ service_manager::BinderRegistry registry_;
+ mojo::BindingSet<service_manager::mojom::ServiceFactory>
+ service_factory_bindings_;
+ std::unique_ptr<service_manager::ServiceContext> pref_service_context_;
+};
+
+constexpr int kInitialValue = 1;
+constexpr int kUpdatedValue = 2;
+constexpr char kKey[] = "some_key";
+constexpr char kOtherKey[] = "some_other_key";
+
+class PrefServiceFactoryTest : public base::MessageLoop::DestructionObserver,
+ public service_manager::test::ServiceTest {
+ public:
+ PrefServiceFactoryTest() : ServiceTest("prefs_unittests", false) {}
+
+ protected:
+ void SetUp() override {
+ ServiceTest::SetUp();
+ ASSERT_TRUE(profile_dir_.CreateUniqueTempDir());
+
+ // Init the pref service (in production Chrome startup would do this.)
+ mojom::PrefServiceControlPtr control;
+ connector()->BindInterface(mojom::kServiceName, &control);
+ auto config = mojom::PersistentPrefStoreConfiguration::New();
+ config->set_simple_configuration(
+ mojom::SimplePersistentPrefStoreConfiguration::New(
+ profile_dir_.GetPath().AppendASCII("Preferences")));
+ control->Init(std::move(config));
+ above_user_prefs_pref_store_ = new ValueMapPrefStore();
+ below_user_prefs_pref_store_ = new ValueMapPrefStore();
+ mojom::PrefStoreRegistryPtr registry;
+ connector()->BindInterface(mojom::kServiceName, &registry);
+ above_user_prefs_impl_ =
+ PrefStoreImpl::Create(registry.get(), above_user_prefs_pref_store_,
+ PrefValueStore::COMMAND_LINE_STORE);
+ below_user_prefs_impl_ =
+ PrefStoreImpl::Create(registry.get(), below_user_prefs_pref_store_,
+ PrefValueStore::RECOMMENDED_STORE);
+ }
+
+ // service_manager::test::ServiceTest:
+ std::unique_ptr<service_manager::Service> CreateService() override {
+ return base::MakeUnique<ServiceTestClient>(this,
+ worker_pool_owner_->pool());
+ }
+
+ std::unique_ptr<base::MessageLoop> CreateMessageLoop() override {
+ auto loop = ServiceTest::CreateMessageLoop();
+ worker_pool_owner_ = base::MakeUnique<base::SequencedWorkerPoolOwner>(
+ 2, "PrefServiceFactoryTest");
+ loop->AddDestructionObserver(this);
+ return loop;
+ }
+
+ // base::MessageLoop::DestructionObserver
+ void WillDestroyCurrentMessageLoop() override { worker_pool_owner_.reset(); }
+
+ // Create a fully initialized PrefService synchronously.
+ std::unique_ptr<PrefService> Create() {
+ std::unique_ptr<PrefService> pref_service;
+ base::RunLoop run_loop;
+ auto pref_registry = make_scoped_refptr(new PrefRegistrySimple());
+ pref_registry->RegisterIntegerPref(kKey, kInitialValue);
+ pref_registry->RegisterIntegerPref(kOtherKey, kInitialValue);
+ ConnectToPrefService(connector(), pref_registry,
+ std::vector<PrefValueStore::PrefStoreType>(),
+ base::Bind(&PrefServiceFactoryTest::OnCreate,
+ run_loop.QuitClosure(), &pref_service));
+ run_loop.Run();
+ return pref_service;
+ }
+
+ // Wait until first update of the pref |key| in |pref_service| synchronously.
+ void WaitForPrefChange(PrefService* pref_service, const std::string& key) {
+ PrefChangeRegistrar registrar;
+ registrar.Init(pref_service);
+ base::RunLoop run_loop;
+ registrar.Add(key, base::Bind(&OnPrefChanged, run_loop.QuitClosure(), key));
+ run_loop.Run();
+ }
+
+ WriteablePrefStore* above_user_prefs_pref_store() {
+ return above_user_prefs_pref_store_.get();
+ }
+ WriteablePrefStore* below_user_prefs_pref_store() {
+ return below_user_prefs_pref_store_.get();
+ }
+
+ private:
+ // Called when the PrefService has been initialized.
+ static void OnInit(const base::Closure& quit_closure, bool success) {
+ quit_closure.Run();
+ }
+
+ // Called when the PrefService has been created.
+ static void OnCreate(const base::Closure& quit_closure,
+ std::unique_ptr<PrefService>* out,
+ std::unique_ptr<PrefService> pref_service) {
+ DCHECK(pref_service);
+ *out = std::move(pref_service);
+ if ((*out)->GetInitializationStatus() ==
+ PrefService::INITIALIZATION_STATUS_WAITING) {
+ (*out)->AddPrefInitObserver(
+ base::Bind(PrefServiceFactoryTest::OnInit, quit_closure));
+ return;
+ }
+ quit_closure.Run();
+ }
+
+ static void OnPrefChanged(const base::Closure& quit_closure,
+ const std::string& expected_path,
+ const std::string& path) {
+ if (path == expected_path)
+ quit_closure.Run();
+ }
+
+ base::ScopedTempDir profile_dir_;
+ std::unique_ptr<base::SequencedWorkerPoolOwner> worker_pool_owner_;
+ scoped_refptr<WriteablePrefStore> above_user_prefs_pref_store_;
+ std::unique_ptr<PrefStoreImpl> above_user_prefs_impl_;
+ scoped_refptr<WriteablePrefStore> below_user_prefs_pref_store_;
+ std::unique_ptr<PrefStoreImpl> below_user_prefs_impl_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrefServiceFactoryTest);
+};
+
+// Check that a single client can set and read back values.
+TEST_F(PrefServiceFactoryTest, Basic) {
+ auto pref_service = Create();
+
+ EXPECT_EQ(kInitialValue, pref_service->GetInteger(kKey));
+ pref_service->SetInteger(kKey, kUpdatedValue);
+ EXPECT_EQ(kUpdatedValue, pref_service->GetInteger(kKey));
+}
+
+// Check that updates in one client eventually propagates to the other.
+TEST_F(PrefServiceFactoryTest, MultipleClients) {
+ auto pref_service = Create();
+ auto pref_service2 = Create();
+
+ EXPECT_EQ(kInitialValue, pref_service->GetInteger(kKey));
+ EXPECT_EQ(kInitialValue, pref_service2->GetInteger(kKey));
+ pref_service->SetInteger(kKey, kUpdatedValue);
+ WaitForPrefChange(pref_service2.get(), kKey);
+ EXPECT_EQ(kUpdatedValue, pref_service2->GetInteger(kKey));
+}
+
+// Check that read-only pref store changes are observed.
+TEST_F(PrefServiceFactoryTest, ReadOnlyPrefStore) {
+ auto pref_service = Create();
+
+ EXPECT_EQ(kInitialValue, pref_service->GetInteger(kKey));
+
+ below_user_prefs_pref_store()->SetValue(
+ kKey, base::MakeUnique<base::Value>(kUpdatedValue), 0);
+ WaitForPrefChange(pref_service.get(), kKey);
+ EXPECT_EQ(kUpdatedValue, pref_service->GetInteger(kKey));
+ pref_service->SetInteger(kKey, 3);
+ EXPECT_EQ(3, pref_service->GetInteger(kKey));
+ above_user_prefs_pref_store()->SetValue(kKey,
+ base::MakeUnique<base::Value>(4), 0);
+ WaitForPrefChange(pref_service.get(), kKey);
+ EXPECT_EQ(4, pref_service->GetInteger(kKey));
+}
+
+// Check that updates to read-only pref stores are correctly layered.
+TEST_F(PrefServiceFactoryTest, ReadOnlyPrefStore_Layering) {
+ auto pref_service = Create();
+
+ above_user_prefs_pref_store()->SetValue(
+ kKey, base::MakeUnique<base::Value>(kInitialValue), 0);
+ WaitForPrefChange(pref_service.get(), kKey);
+ EXPECT_EQ(kInitialValue, pref_service->GetInteger(kKey));
+
+ below_user_prefs_pref_store()->SetValue(
+ kKey, base::MakeUnique<base::Value>(kUpdatedValue), 0);
+ // This update is needed to check that the change to kKey has propagated even
+ // though we will not observe it change.
+ below_user_prefs_pref_store()->SetValue(
+ kOtherKey, base::MakeUnique<base::Value>(kUpdatedValue), 0);
+ WaitForPrefChange(pref_service.get(), kOtherKey);
+ EXPECT_EQ(kInitialValue, pref_service->GetInteger(kKey));
+}
+
+// Check that writes to user prefs are correctly layered with read-only
+// pref stores.
+TEST_F(PrefServiceFactoryTest, ReadOnlyPrefStore_UserPrefStoreLayering) {
+ auto pref_service = Create();
+
+ above_user_prefs_pref_store()->SetValue(kKey,
+ base::MakeUnique<base::Value>(2), 0);
+ WaitForPrefChange(pref_service.get(), kKey);
+ EXPECT_EQ(2, pref_service->GetInteger(kKey));
+
+ pref_service->SetInteger(kKey, 3);
+ EXPECT_EQ(2, pref_service->GetInteger(kKey));
+}
+
+} // namespace
+} // namespace prefs
diff --git a/chromium/services/preferences/pref_store_manager_impl.cc b/chromium/services/preferences/pref_store_manager_impl.cc
new file mode 100644
index 00000000000..f418792f29e
--- /dev/null
+++ b/chromium/services/preferences/pref_store_manager_impl.cc
@@ -0,0 +1,280 @@
+// Copyright 2017 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 "services/preferences/pref_store_manager_impl.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/memory/ref_counted.h"
+#include "base/stl_util.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "components/prefs/default_pref_store.h"
+#include "components/prefs/pref_value_store.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "services/preferences/persistent_pref_store_factory.h"
+#include "services/preferences/persistent_pref_store_impl.h"
+#include "services/preferences/public/cpp/pref_store_impl.h"
+#include "services/service_manager/public/cpp/service_info.h"
+
+namespace prefs {
+namespace {
+
+using ConnectCallback = mojom::PrefStoreConnector::ConnectCallback;
+using PrefStorePtrs =
+ std::unordered_map<PrefValueStore::PrefStoreType, mojom::PrefStore*>;
+
+// Used to make sure all pref stores have been registered before replying to any
+// Connect calls.
+class ConnectionBarrier : public base::RefCounted<ConnectionBarrier> {
+ public:
+ static void Create(
+ const PrefStorePtrs& pref_store_ptrs,
+ mojom::PersistentPrefStoreConnectionPtr persistent_pref_store_connection,
+ const std::vector<std::string>& observed_prefs,
+ const ConnectCallback& callback);
+
+ void Init(const PrefStorePtrs& pref_store_ptrs,
+ const std::vector<std::string>& observed_prefs);
+
+ private:
+ friend class base::RefCounted<ConnectionBarrier>;
+ ConnectionBarrier(
+ const PrefStorePtrs& pref_store_ptrs,
+ mojom::PersistentPrefStoreConnectionPtr persistent_pref_store_connection,
+ const ConnectCallback& callback);
+ ~ConnectionBarrier() = default;
+
+ void OnConnect(PrefValueStore::PrefStoreType type,
+ mojom::PrefStoreConnectionPtr connection_ptr);
+
+ ConnectCallback callback_;
+
+ std::unordered_map<PrefValueStore::PrefStoreType,
+ mojom::PrefStoreConnectionPtr>
+ connections_;
+
+ const size_t expected_connections_;
+
+ mojom::PersistentPrefStoreConnectionPtr persistent_pref_store_connection_;
+
+ DISALLOW_COPY_AND_ASSIGN(ConnectionBarrier);
+};
+
+// static
+void ConnectionBarrier::Create(
+ const PrefStorePtrs& pref_store_ptrs,
+ mojom::PersistentPrefStoreConnectionPtr persistent_pref_store_connection,
+ const std::vector<std::string>& observed_prefs,
+ const ConnectCallback& callback) {
+ make_scoped_refptr(new ConnectionBarrier(
+ pref_store_ptrs,
+ std::move(persistent_pref_store_connection), callback))
+ ->Init(pref_store_ptrs, observed_prefs);
+}
+
+void ConnectionBarrier::Init(const PrefStorePtrs& pref_store_ptrs,
+ const std::vector<std::string>& observed_prefs) {
+ for (const auto& ptr : pref_store_ptrs) {
+ ptr.second->AddObserver(
+ observed_prefs,
+ base::Bind(&ConnectionBarrier::OnConnect, this, ptr.first));
+ }
+}
+
+ConnectionBarrier::ConnectionBarrier(
+ const PrefStorePtrs& pref_store_ptrs,
+ mojom::PersistentPrefStoreConnectionPtr persistent_pref_store_connection,
+ const ConnectCallback& callback)
+ : callback_(callback),
+ expected_connections_(pref_store_ptrs.size()),
+ persistent_pref_store_connection_(
+ std::move(persistent_pref_store_connection)) {}
+
+void ConnectionBarrier::OnConnect(
+ PrefValueStore::PrefStoreType type,
+ mojom::PrefStoreConnectionPtr connection_ptr) {
+ connections_.insert(std::make_pair(type, std::move(connection_ptr)));
+ if (connections_.size() == expected_connections_) {
+ // After this method exits |this| will get destroyed so it's safe to move
+ // out the members.
+ callback_.Run(std::move(persistent_pref_store_connection_),
+ std::move(connections_));
+ }
+}
+
+} // namespace
+
+PrefStoreManagerImpl::PrefStoreManagerImpl(
+ std::set<PrefValueStore::PrefStoreType> expected_pref_stores,
+ scoped_refptr<base::SequencedWorkerPool> worker_pool)
+ : expected_pref_stores_(std::move(expected_pref_stores)),
+ init_binding_(this),
+ defaults_(new DefaultPrefStore),
+ defaults_wrapper_(base::MakeUnique<PrefStoreImpl>(
+ defaults_,
+ mojo::MakeRequest(&pref_store_ptrs_[PrefValueStore::DEFAULT_STORE]))),
+ worker_pool_(std::move(worker_pool)) {
+ DCHECK(
+ base::ContainsValue(expected_pref_stores_, PrefValueStore::USER_STORE) &&
+ base::ContainsValue(expected_pref_stores_, PrefValueStore::DEFAULT_STORE))
+ << "expected_pref_stores must always include PrefValueStore::USER_STORE "
+ "and PrefValueStore::DEFAULT_STORE.";
+ // The user store is not actually connected to in the implementation, but
+ // accessed directly.
+ expected_pref_stores_.erase(PrefValueStore::USER_STORE);
+ registry_.AddInterface<prefs::mojom::PrefStoreConnector>(this);
+ registry_.AddInterface<prefs::mojom::PrefStoreRegistry>(this);
+ registry_.AddInterface<prefs::mojom::PrefServiceControl>(this);
+}
+
+PrefStoreManagerImpl::~PrefStoreManagerImpl() = default;
+
+struct PrefStoreManagerImpl::PendingConnect {
+ mojom::PrefRegistryPtr pref_registry;
+ // Pref stores the caller already is connected to (and hence we won't
+ // connect to these).
+ std::vector<PrefValueStore::PrefStoreType> already_connected_types;
+ ConnectCallback callback;
+};
+
+void PrefStoreManagerImpl::Register(PrefValueStore::PrefStoreType type,
+ mojom::PrefStorePtr pref_store_ptr) {
+ if (expected_pref_stores_.count(type) == 0) {
+ LOG(WARNING) << "Not registering unexpected pref store: " << type;
+ return;
+ }
+ DVLOG(1) << "Registering pref store: " << type;
+ pref_store_ptr.set_connection_error_handler(
+ base::Bind(&PrefStoreManagerImpl::OnPrefStoreDisconnect,
+ base::Unretained(this), type));
+ const bool success =
+ pref_store_ptrs_.insert(std::make_pair(type, std::move(pref_store_ptr)))
+ .second;
+ DCHECK(success) << "The same pref store registered twice: " << type;
+ if (AllConnected()) {
+ DVLOG(1) << "All pref stores registered.";
+ ProcessPendingConnects();
+ }
+}
+
+void PrefStoreManagerImpl::Connect(
+ mojom::PrefRegistryPtr pref_registry,
+ const std::vector<PrefValueStore::PrefStoreType>& already_connected_types,
+ const ConnectCallback& callback) {
+ if (AllConnected()) {
+ ConnectImpl(std::move(pref_registry), already_connected_types, callback);
+ } else {
+ pending_connects_.push_back(
+ {std::move(pref_registry), already_connected_types, callback});
+ }
+}
+
+void PrefStoreManagerImpl::Create(
+ const service_manager::Identity& remote_identity,
+ prefs::mojom::PrefStoreConnectorRequest request) {
+ connector_bindings_.AddBinding(this, std::move(request));
+}
+
+void PrefStoreManagerImpl::Create(
+ const service_manager::Identity& remote_identity,
+ prefs::mojom::PrefStoreRegistryRequest request) {
+ registry_bindings_.AddBinding(this, std::move(request));
+}
+
+void PrefStoreManagerImpl::Create(
+ const service_manager::Identity& remote_identity,
+ prefs::mojom::PrefServiceControlRequest request) {
+ if (init_binding_.is_bound()) {
+ LOG(ERROR)
+ << "Pref service received unexpected control interface connection from "
+ << remote_identity.name();
+ return;
+ }
+
+ init_binding_.Bind(std::move(request));
+}
+
+void PrefStoreManagerImpl::Init(
+ mojom::PersistentPrefStoreConfigurationPtr configuration) {
+ DCHECK(!persistent_pref_store_);
+
+ persistent_pref_store_ = CreatePersistentPrefStore(
+ std::move(configuration), worker_pool_.get(),
+ base::Bind(&PrefStoreManagerImpl::OnPersistentPrefStoreReady,
+ base::Unretained(this)));
+ DCHECK(persistent_pref_store_);
+ if (AllConnected())
+ ProcessPendingConnects();
+}
+
+void PrefStoreManagerImpl::OnStart() {}
+
+void PrefStoreManagerImpl::OnBindInterface(
+ const service_manager::ServiceInfo& source_info,
+ const std::string& interface_name,
+ mojo::ScopedMessagePipeHandle interface_pipe) {
+ registry_.BindInterface(source_info.identity, interface_name,
+ std::move(interface_pipe));
+}
+
+void PrefStoreManagerImpl::OnPrefStoreDisconnect(
+ PrefValueStore::PrefStoreType type) {
+ DVLOG(1) << "Deregistering pref store: " << type;
+ pref_store_ptrs_.erase(type);
+}
+
+bool PrefStoreManagerImpl::AllConnected() const {
+ return pref_store_ptrs_.size() == expected_pref_stores_.size() &&
+ persistent_pref_store_ && persistent_pref_store_->initialized();
+}
+
+void PrefStoreManagerImpl::ProcessPendingConnects() {
+ for (auto& connect : pending_connects_)
+ ConnectImpl(std::move(connect.pref_registry),
+ connect.already_connected_types, connect.callback);
+ pending_connects_.clear();
+}
+
+void PrefStoreManagerImpl::ConnectImpl(
+ mojom::PrefRegistryPtr pref_registry,
+ const std::vector<PrefValueStore::PrefStoreType>& already_connected_types,
+ const ConnectCallback& callback) {
+ std::vector<std::string> observed_prefs;
+ for (auto& registration : pref_registry->registrations) {
+ observed_prefs.push_back(registration.first);
+ const auto& key = registration.first;
+ auto& default_value = registration.second->default_value;
+ const base::Value* old_default = nullptr;
+ // TODO(sammc): Once non-owning registrations are supported, disallow
+ // multiple owners instead of just checking for consistent defaults.
+ if (defaults_->GetValue(key, &old_default))
+ DCHECK(old_default->Equals(default_value.get()));
+ else
+ defaults_->SetDefaultValue(key, std::move(default_value));
+ }
+
+ // Only connect to pref stores the client isn't already connected to.
+ PrefStorePtrs ptrs;
+ for (const auto& entry : pref_store_ptrs_) {
+ if (!base::ContainsValue(already_connected_types, entry.first)) {
+ ptrs.insert(std::make_pair(entry.first, entry.second.get()));
+ }
+ }
+ ConnectionBarrier::Create(
+ ptrs,
+ persistent_pref_store_->CreateConnection(
+ PersistentPrefStoreImpl::ObservedPrefs(observed_prefs.begin(),
+ observed_prefs.end())),
+ observed_prefs, callback);
+}
+
+void PrefStoreManagerImpl::OnPersistentPrefStoreReady() {
+ DVLOG(1) << "PersistentPrefStore ready";
+ if (AllConnected()) {
+ ProcessPendingConnects();
+ }
+}
+
+} // namespace prefs
diff --git a/chromium/services/preferences/pref_store_manager_impl.h b/chromium/services/preferences/pref_store_manager_impl.h
new file mode 100644
index 00000000000..bc7eb1e6c45
--- /dev/null
+++ b/chromium/services/preferences/pref_store_manager_impl.h
@@ -0,0 +1,136 @@
+// Copyright 2017 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 SERVICES_PREFERENCES_PREF_STORE_MANAGER_IMPL_H_
+#define SERVICES_PREFERENCES_PREF_STORE_MANAGER_IMPL_H_
+
+#include <memory>
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/prefs/pref_value_store.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+#include "services/preferences/public/interfaces/preferences.mojom.h"
+#include "services/service_manager/public/cpp/binder_registry.h"
+#include "services/service_manager/public/cpp/interface_factory.h"
+#include "services/service_manager/public/cpp/service.h"
+
+class DefaultPrefStore;
+
+namespace base {
+class SequencedWorkerPool;
+}
+
+namespace prefs {
+class PersistentPrefStoreImpl;
+class PrefStoreImpl;
+
+// This class mediates the connection of clients who wants to read preferences
+// and the pref stores that store those preferences. Pref stores use the
+// |PrefStoreRegistry| interface to register themselves with the manager and
+// clients use the |PrefStoreConnector| interface to connect to these stores.
+class PrefStoreManagerImpl
+ : public mojom::PrefStoreRegistry,
+ public mojom::PrefStoreConnector,
+ public service_manager::InterfaceFactory<mojom::PrefStoreConnector>,
+ public service_manager::InterfaceFactory<mojom::PrefStoreRegistry>,
+ public mojom::PrefServiceControl,
+ public service_manager::InterfaceFactory<mojom::PrefServiceControl>,
+ public service_manager::Service {
+ public:
+ // Only replies to Connect calls when all |expected_pref_stores| have
+ // registered. |expected_pref_stores| must contain
+ // PrefValueStore::DEFAULT_STORE and PrefValueStore::USER_STORE for
+ // consistency, as the service always registers these
+ // internally. |worker_pool| is used for any I/O performed by the service.
+ PrefStoreManagerImpl(
+ std::set<PrefValueStore::PrefStoreType> expected_pref_stores,
+ scoped_refptr<base::SequencedWorkerPool> worker_pool);
+ ~PrefStoreManagerImpl() override;
+
+ private:
+ struct PendingConnect;
+
+ // mojom::PrefStoreRegistry:
+ void Register(PrefValueStore::PrefStoreType type,
+ mojom::PrefStorePtr pref_store_ptr) override;
+
+ // mojom::PrefStoreConnector: |already_connected_types| must not include
+ // PrefValueStore::DEFAULT_STORE and PrefValueStore::USER_STORE as these must
+ // always be accessed through the service.
+ void Connect(
+ mojom::PrefRegistryPtr pref_registry,
+ const std::vector<PrefValueStore::PrefStoreType>& already_connected_types,
+ const ConnectCallback& callback) override;
+
+ // service_manager::InterfaceFactory<PrefStoreConnector>:
+ void Create(const service_manager::Identity& remote_identity,
+ prefs::mojom::PrefStoreConnectorRequest request) override;
+
+ // service_manager::InterfaceFactory<PrefStoreRegistry>:
+ void Create(const service_manager::Identity& remote_identity,
+ prefs::mojom::PrefStoreRegistryRequest request) override;
+
+ // service_manager::InterfaceFactory<PrefServiceControl>:
+ void Create(const service_manager::Identity& remote_identity,
+ prefs::mojom::PrefServiceControlRequest request) override;
+
+ // PrefServiceControl:
+ void Init(mojom::PersistentPrefStoreConfigurationPtr configuration) override;
+
+ // service_manager::Service:
+ void OnStart() override;
+ void OnBindInterface(const service_manager::ServiceInfo& source_info,
+ const std::string& interface_name,
+ mojo::ScopedMessagePipeHandle interface_pipe) override;
+
+ // Called when a PrefStore previously registered using |Register| disconnects.
+ void OnPrefStoreDisconnect(PrefValueStore::PrefStoreType type);
+
+ // Have all the expected PrefStores connected?
+ bool AllConnected() const;
+
+ void ProcessPendingConnects();
+
+ void ConnectImpl(
+ mojom::PrefRegistryPtr pref_registry,
+ const std::vector<PrefValueStore::PrefStoreType>& already_connected_types,
+ const ConnectCallback& callback);
+
+ void OnPersistentPrefStoreReady();
+
+ // PrefStores that need to register before replying to any Connect calls. This
+ // does not include the PersistentPrefStore, which is handled separately.
+ std::set<PrefValueStore::PrefStoreType> expected_pref_stores_;
+
+ // Registered pref stores.
+ std::unordered_map<PrefValueStore::PrefStoreType, mojom::PrefStorePtr>
+ pref_store_ptrs_;
+
+ // We hold on to the connection request callbacks until all expected
+ // PrefStores have registered.
+ std::vector<PendingConnect> pending_connects_;
+
+ mojo::BindingSet<mojom::PrefStoreConnector> connector_bindings_;
+ mojo::BindingSet<mojom::PrefStoreRegistry> registry_bindings_;
+ std::unique_ptr<PersistentPrefStoreImpl> persistent_pref_store_;
+ mojo::Binding<mojom::PrefServiceControl> init_binding_;
+
+ const scoped_refptr<DefaultPrefStore> defaults_;
+ const std::unique_ptr<PrefStoreImpl> defaults_wrapper_;
+
+ const scoped_refptr<base::SequencedWorkerPool> worker_pool_;
+
+ service_manager::BinderRegistry registry_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrefStoreManagerImpl);
+};
+
+} // namespace prefs
+
+#endif // SERVICES_PREFERENCES_PREF_STORE_MANAGER_IMPL_H_
diff --git a/chromium/services/preferences/public/cpp/BUILD.gn b/chromium/services/preferences/public/cpp/BUILD.gn
index 727169326a4..1499131617c 100644
--- a/chromium/services/preferences/public/cpp/BUILD.gn
+++ b/chromium/services/preferences/public/cpp/BUILD.gn
@@ -4,18 +4,43 @@
source_set("cpp") {
sources = [
- "pref_client_store.cc",
- "pref_client_store.h",
+ "persistent_pref_store_client.cc",
+ "persistent_pref_store_client.h",
+ "pref_registry_serializer.cc",
+ "pref_registry_serializer.h",
+ "pref_service_factory.cc",
+ "pref_service_factory.h",
+ "pref_store_adapter.cc",
+ "pref_store_adapter.h",
+ "pref_store_client.cc",
+ "pref_store_client.h",
+ "pref_store_client_mixin.cc",
+ "pref_store_client_mixin.h",
+ "pref_store_impl.cc",
+ "pref_store_impl.h",
]
public_deps = [
"//base",
- "//components/prefs:prefs",
+ "//components/prefs",
"//services/preferences/public/interfaces",
"//services/service_manager/public/cpp",
]
deps = [
- "//mojo/public/cpp/bindings:bindings",
+ "//mojo/public/cpp/bindings",
+ ]
+}
+
+source_set("service_main") {
+ deps = [
+ "//base",
+ "//components/prefs",
+ "//services/preferences",
+ "//services/service_manager/public/cpp",
+ ]
+ sources = [
+ "pref_service_main.cc",
+ "pref_service_main.h",
]
}
diff --git a/chromium/services/preferences/public/cpp/DEPS b/chromium/services/preferences/public/cpp/DEPS
index eac076192dc..45e227dd6d0 100644
--- a/chromium/services/preferences/public/cpp/DEPS
+++ b/chromium/services/preferences/public/cpp/DEPS
@@ -1,3 +1,10 @@
include_rules = [
"+components/prefs",
+ "-services/preferences",
+ "+services/preferences/public",
]
+specific_include_rules = {
+ "pref_service_main\.cc": [
+ "+services/preferences",
+ ],
+}
diff --git a/chromium/services/preferences/public/cpp/OWNERS b/chromium/services/preferences/public/cpp/OWNERS
new file mode 100644
index 00000000000..4df0c71cc7d
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/OWNERS
@@ -0,0 +1,4 @@
+per-file *_struct_traits*.*=set noparent
+per-file *_struct_traits*.*=file://ipc/SECURITY_OWNERS
+per-file *.typemap=set noparent
+per-file *.typemap=file://ipc/SECURITY_OWNERS
diff --git a/chromium/services/preferences/public/cpp/persistent_pref_store_client.cc b/chromium/services/preferences/public/cpp/persistent_pref_store_client.cc
new file mode 100644
index 00000000000..9bcb92fb1b6
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/persistent_pref_store_client.cc
@@ -0,0 +1,184 @@
+// Copyright 2017 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 "services/preferences/public/cpp/persistent_pref_store_client.h"
+
+#include <utility>
+
+#include "base/values.h"
+#include "components/prefs/pref_registry.h"
+#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
+#include "services/preferences/public/cpp/pref_registry_serializer.h"
+
+namespace prefs {
+
+PersistentPrefStoreClient::PersistentPrefStoreClient(
+ mojom::PrefStoreConnectorPtr connector,
+ scoped_refptr<PrefRegistry> pref_registry,
+ std::vector<PrefValueStore::PrefStoreType> already_connected_types)
+ : connector_(std::move(connector)),
+ pref_registry_(std::move(pref_registry)),
+ already_connected_types_(std::move(already_connected_types)),
+ weak_factory_(this) {
+ DCHECK(connector_);
+}
+
+PersistentPrefStoreClient::PersistentPrefStoreClient(
+ mojom::PersistentPrefStoreConnectionPtr connection)
+ : weak_factory_(this) {
+ OnConnect(std::move(connection),
+ std::unordered_map<PrefValueStore::PrefStoreType,
+ prefs::mojom::PrefStoreConnectionPtr>());
+}
+
+void PersistentPrefStoreClient::SetValue(const std::string& key,
+ std::unique_ptr<base::Value> value,
+ uint32_t flags) {
+ base::Value* old_value = nullptr;
+ GetMutableValues().Get(key, &old_value);
+ if (!old_value || !value->Equals(old_value)) {
+ GetMutableValues().Set(key, std::move(value));
+ ReportValueChanged(key, flags);
+ }
+}
+
+void PersistentPrefStoreClient::RemoveValue(const std::string& key,
+ uint32_t flags) {
+ if (GetMutableValues().RemovePath(key, nullptr))
+ ReportValueChanged(key, flags);
+}
+
+bool PersistentPrefStoreClient::GetMutableValue(const std::string& key,
+ base::Value** result) {
+ return GetMutableValues().Get(key, result);
+}
+
+void PersistentPrefStoreClient::ReportValueChanged(const std::string& key,
+ uint32_t flags) {
+ DCHECK(pref_store_);
+ const base::Value* local_value = nullptr;
+ GetMutableValues().Get(key, &local_value);
+
+ QueueWrite(key, flags);
+ ReportPrefValueChanged(key);
+}
+
+void PersistentPrefStoreClient::SetValueSilently(
+ const std::string& key,
+ std::unique_ptr<base::Value> value,
+ uint32_t flags) {
+ DCHECK(pref_store_);
+ QueueWrite(key, flags);
+ GetMutableValues().Set(key, std::move(value));
+}
+
+bool PersistentPrefStoreClient::ReadOnly() const {
+ return read_only_;
+}
+
+PersistentPrefStore::PrefReadError PersistentPrefStoreClient::GetReadError()
+ const {
+ return read_error_;
+}
+
+PersistentPrefStore::PrefReadError PersistentPrefStoreClient::ReadPrefs() {
+ mojom::PersistentPrefStoreConnectionPtr connection;
+ std::unordered_map<PrefValueStore::PrefStoreType,
+ prefs::mojom::PrefStoreConnectionPtr>
+ other_pref_stores;
+ mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_calls;
+ if (!connector_->Connect(SerializePrefRegistry(*pref_registry_),
+ already_connected_types_, &connection,
+ &other_pref_stores)) {
+ NOTREACHED();
+ }
+ pref_registry_ = nullptr;
+ OnConnect(std::move(connection), std::move(other_pref_stores));
+ return read_error_;
+}
+
+void PersistentPrefStoreClient::ReadPrefsAsync(
+ ReadErrorDelegate* error_delegate) {
+ error_delegate_.reset(error_delegate);
+ connector_->Connect(SerializePrefRegistry(*pref_registry_),
+ already_connected_types_,
+ base::Bind(&PersistentPrefStoreClient::OnConnect,
+ base::Unretained(this)));
+ pref_registry_ = nullptr;
+}
+
+void PersistentPrefStoreClient::CommitPendingWrite() {
+ DCHECK(pref_store_);
+ if (!pending_writes_.empty())
+ FlushPendingWrites();
+ pref_store_->CommitPendingWrite();
+}
+
+void PersistentPrefStoreClient::SchedulePendingLossyWrites() {
+ DCHECK(pref_store_);
+ return pref_store_->SchedulePendingLossyWrites();
+}
+
+void PersistentPrefStoreClient::ClearMutableValues() {
+ DCHECK(pref_store_);
+ return pref_store_->ClearMutableValues();
+}
+
+PersistentPrefStoreClient::~PersistentPrefStoreClient() {
+ if (!pref_store_)
+ return;
+
+ CommitPendingWrite();
+}
+
+void PersistentPrefStoreClient::OnConnect(
+ mojom::PersistentPrefStoreConnectionPtr connection,
+ std::unordered_map<PrefValueStore::PrefStoreType,
+ prefs::mojom::PrefStoreConnectionPtr>
+ other_pref_stores) {
+ connector_.reset();
+ read_error_ = connection->read_error;
+ read_only_ = connection->read_only;
+ pref_store_ = std::move(connection->pref_store);
+ if (error_delegate_ && read_error_ != PREF_READ_ERROR_NONE)
+ error_delegate_->OnError(read_error_);
+ error_delegate_.reset();
+
+ if (connection->pref_store_connection) {
+ Init(std::move(connection->pref_store_connection->initial_prefs), true,
+ std::move(connection->pref_store_connection->observer));
+ } else {
+ Init(nullptr, false, nullptr);
+ }
+}
+
+void PersistentPrefStoreClient::QueueWrite(const std::string& key,
+ uint32_t flags) {
+ if (pending_writes_.empty()) {
+ // Use a weak pointer since a pending write should not prolong the life of
+ // |this|. Instead, the destruction of |this| will flush any pending writes.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&PersistentPrefStoreClient::FlushPendingWrites,
+ weak_factory_.GetWeakPtr()));
+ }
+ pending_writes_.insert(std::make_pair(key, flags));
+}
+
+void PersistentPrefStoreClient::FlushPendingWrites() {
+ std::vector<mojom::PrefUpdatePtr> updates;
+ for (const auto& pref : pending_writes_) {
+ const base::Value* value = nullptr;
+ if (GetValue(pref.first, &value)) {
+ updates.push_back(mojom::PrefUpdate::New(
+ pref.first, value->CreateDeepCopy(), pref.second));
+ } else {
+ updates.push_back(
+ mojom::PrefUpdate::New(pref.first, nullptr, pref.second));
+ }
+ }
+ pref_store_->SetValues(std::move(updates));
+ pending_writes_.clear();
+}
+
+} // namespace prefs
diff --git a/chromium/services/preferences/public/cpp/persistent_pref_store_client.h b/chromium/services/preferences/public/cpp/persistent_pref_store_client.h
new file mode 100644
index 00000000000..e317ec9f163
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/persistent_pref_store_client.h
@@ -0,0 +1,89 @@
+// Copyright 2017 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 SERVICES_PREFERENCES_PUBLIC_CPP_PERSISTENT_PREF_STORE_CLIENT_H_
+#define SERVICES_PREFERENCES_PUBLIC_CPP_PERSISTENT_PREF_STORE_CLIENT_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "components/prefs/persistent_pref_store.h"
+#include "components/prefs/pref_value_store.h"
+#include "services/preferences/public/cpp/pref_store_client_mixin.h"
+#include "services/preferences/public/interfaces/preferences.mojom.h"
+
+namespace base {
+class Value;
+}
+
+class PrefRegistry;
+
+namespace prefs {
+
+// An implementation of PersistentPrefStore backed by a
+// mojom::PersistentPrefStore and a mojom::PrefStoreObserver.
+class PersistentPrefStoreClient
+ : public PrefStoreClientMixin<PersistentPrefStore> {
+ public:
+ PersistentPrefStoreClient(
+ mojom::PrefStoreConnectorPtr connector,
+ scoped_refptr<PrefRegistry> pref_registry,
+ std::vector<PrefValueStore::PrefStoreType> already_connected_types);
+
+ explicit PersistentPrefStoreClient(
+ mojom::PersistentPrefStoreConnectionPtr connection);
+
+ // WriteablePrefStore:
+ void SetValue(const std::string& key,
+ std::unique_ptr<base::Value> value,
+ uint32_t flags) override;
+ void RemoveValue(const std::string& key, uint32_t flags) override;
+ bool GetMutableValue(const std::string& key, base::Value** result) override;
+ void ReportValueChanged(const std::string& key, uint32_t flags) override;
+ void SetValueSilently(const std::string& key,
+ std::unique_ptr<base::Value> value,
+ uint32_t flags) override;
+
+ // PersistentPrefStore:
+ bool ReadOnly() const override;
+ PrefReadError GetReadError() const override;
+ PrefReadError ReadPrefs() override;
+ void ReadPrefsAsync(ReadErrorDelegate* error_delegate) override;
+ void CommitPendingWrite() override;
+ void SchedulePendingLossyWrites() override;
+ void ClearMutableValues() override;
+
+ protected:
+ // base::RefCounted<PrefStore>:
+ ~PersistentPrefStoreClient() override;
+
+ private:
+ void OnConnect(mojom::PersistentPrefStoreConnectionPtr connection,
+ std::unordered_map<PrefValueStore::PrefStoreType,
+ prefs::mojom::PrefStoreConnectionPtr>
+ other_pref_stores);
+
+ void QueueWrite(const std::string& key, uint32_t flags);
+ void FlushPendingWrites();
+
+ mojom::PrefStoreConnectorPtr connector_;
+ scoped_refptr<PrefRegistry> pref_registry_;
+ bool read_only_ = false;
+ PrefReadError read_error_ = PersistentPrefStore::PREF_READ_ERROR_NONE;
+ mojom::PersistentPrefStorePtr pref_store_;
+ std::map<std::string, uint32_t> pending_writes_;
+
+ std::unique_ptr<ReadErrorDelegate> error_delegate_;
+ std::vector<PrefValueStore::PrefStoreType> already_connected_types_;
+
+ base::WeakPtrFactory<PersistentPrefStoreClient> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(PersistentPrefStoreClient);
+};
+
+} // namespace prefs
+
+#endif // SERVICES_PREFERENCES_PUBLIC_CPP_PERSISTENT_PREF_STORE_CLIENT_H_
diff --git a/chromium/services/preferences/public/cpp/pref_client_store.cc b/chromium/services/preferences/public/cpp/pref_client_store.cc
deleted file mode 100644
index 289aaaadd20..00000000000
--- a/chromium/services/preferences/public/cpp/pref_client_store.cc
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2016 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 "services/preferences/public/cpp/pref_client_store.h"
-
-#include "base/memory/ptr_util.h"
-#include "base/values.h"
-#include "services/service_manager/public/cpp/connector.h"
-
-namespace preferences {
-
-PrefClientStore::PrefClientStore(
- prefs::mojom::PreferencesServiceFactoryPtr pref_factory_ptr)
- : prefs_binding_(this),
- pref_factory_ptr_(std::move(pref_factory_ptr)),
- initialized_(false) {
- pref_factory_ptr_->Create(prefs_binding_.CreateInterfacePtrAndBind(),
- mojo::MakeRequest(&prefs_service_ptr_));
-}
-
-void PrefClientStore::Subscribe(const std::set<std::string>& keys) {
- keys_.insert(keys.begin(), keys.end());
-
- std::vector<std::string> pref_array;
- std::copy(keys_.begin(), keys_.end(), std::back_inserter(pref_array));
- prefs_service_ptr_->Subscribe(pref_array);
-}
-
-bool PrefClientStore::GetValue(const std::string& key,
- const base::Value** value) const {
- DCHECK(initialized_);
- DCHECK(keys_.find(key) != keys_.end());
-
- return ValueMapPrefStore::GetValue(key, value);
-}
-
-void PrefClientStore::SetValue(const std::string& key,
- std::unique_ptr<base::Value> value,
- uint32_t flags) {
- DCHECK(keys_.find(key) != keys_.end());
-
- // TODO(jonross): only notify the server if the value changed.
- SetValueOnPreferenceManager(key, *value);
- ValueMapPrefStore::SetValue(key, std::move(value), flags);
-}
-
-void PrefClientStore::RemoveValue(const std::string& key, uint32_t flags) {
- // TODO(jonross): add preference removal to preferences.mojom
- NOTIMPLEMENTED();
-}
-
-bool PrefClientStore::GetMutableValue(const std::string& key,
- base::Value** value) {
- DCHECK(initialized_);
- DCHECK(keys_.find(key) != keys_.end());
-
- return ValueMapPrefStore::GetMutableValue(key, value);
-}
-
-void PrefClientStore::ReportValueChanged(const std::string& key,
- uint32_t flags) {
- DCHECK(keys_.find(key) != keys_.end());
- const base::Value* value = nullptr;
- ValueMapPrefStore::GetValue(key, &value);
- SetValueOnPreferenceManager(key, *value);
- ValueMapPrefStore::ReportValueChanged(key, flags);
-}
-
-void PrefClientStore::SetValueSilently(const std::string& key,
- std::unique_ptr<base::Value> value,
- uint32_t flags) {
- SetValueOnPreferenceManager(key, *value);
- ValueMapPrefStore::SetValueSilently(key, std::move(value), flags);
-}
-
-PrefClientStore::~PrefClientStore() {}
-
-void PrefClientStore::SetValueOnPreferenceManager(const std::string& key,
- const base::Value& value) {
- if (keys_.find(key) == keys_.end())
- return;
-
- auto prefs = base::MakeUnique<base::DictionaryValue>();
- prefs->SetWithoutPathExpansion(key, value.CreateDeepCopy());
- prefs_service_ptr_->SetPreferences(std::move(prefs));
-}
-
-void PrefClientStore::OnPreferencesChanged(
- std::unique_ptr<base::DictionaryValue> preferences) {
- if (!initialized_) {
- initialized_ = true;
- NotifyInitializationCompleted();
- }
-
- for (base::DictionaryValue::Iterator it(*preferences); !it.IsAtEnd();
- it.Advance()) {
- if (keys_.find(it.key()) == keys_.end())
- continue;
- ValueMapPrefStore::SetValue(it.key(), it.value().CreateDeepCopy(), 0);
- }
-}
-
-} // namespace preferences
diff --git a/chromium/services/preferences/public/cpp/pref_client_store.h b/chromium/services/preferences/public/cpp/pref_client_store.h
deleted file mode 100644
index c2ac032366c..00000000000
--- a/chromium/services/preferences/public/cpp/pref_client_store.h
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2016 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 SERVICES_PREFERENCES_PUBLIC_CPP_PREFS_CLIENT_STORE_H_
-#define SERVICES_PREFERENCES_PUBLIC_CPP_PREFS_CLIENT_STORE_H_
-
-#include <set>
-
-#include "base/macros.h"
-#include "components/prefs/value_map_pref_store.h"
-#include "mojo/public/cpp/bindings/binding.h"
-#include "services/preferences/public/interfaces/preferences.mojom.h"
-
-namespace preferences {
-
-class PrefClientStoreTest;
-
-// An implementation of PrefStore which uses prefs::mojom::PreferenceManager as
-// the backing of the preferences.
-//
-// PrefClientStore caches the values locally to provide synchronous access.
-// The cache is empty until the first notification of OnPreferencesChanged is
-// received from the prefs::mojom::PreferenceManager. Upon recieving an initial
-// OnPreferencesChanged initialization will be considered as completed, and any
-// PrefStore::Observer will be notified of OnInitializationCompleted.
-//
-// Currently this does not support RemoveValue.
-class PrefClientStore : public ValueMapPrefStore,
- public prefs::mojom::PreferencesServiceClient {
- public:
- explicit PrefClientStore(
- prefs::mojom::PreferencesServiceFactoryPtr pref_factory_ptr);
-
- // Adds a set of |keys| which PrefClientStore will handle. Begins listening
- // for changes to these from |prefs_service_|.
- void Subscribe(const std::set<std::string>& keys);
-
- // PrefStore:
- bool GetValue(const std::string& key,
- const base::Value** value) const override;
-
- // ValueMapPrefStore:
- void SetValue(const std::string& key,
- std::unique_ptr<base::Value> value,
- uint32_t flags) override;
- void RemoveValue(const std::string& key, uint32_t flags) override;
- bool GetMutableValue(const std::string& key, base::Value** value) override;
- void ReportValueChanged(const std::string& key, uint32_t flags) override;
- void SetValueSilently(const std::string& key,
- std::unique_ptr<base::Value> value,
- uint32_t flags) override;
-
- protected:
- // base::RefCounted<PrefStore>:
- ~PrefClientStore() override;
-
- private:
- friend class PrefClientStoreTest;
-
- // Notifies |prefs_service_| of the change in |key| - |value| pair.
- void SetValueOnPreferenceManager(const std::string& key,
- base::Value const& value);
-
- // prefs::mojom::PreferencesServiceClient:
- void OnPreferencesChanged(
- std::unique_ptr<base::DictionaryValue> preferences) override;
-
- mojo::Binding<prefs::mojom::PreferencesServiceClient> prefs_binding_;
- prefs::mojom::PreferencesServiceFactoryPtr pref_factory_ptr_;
- prefs::mojom::PreferencesServicePtr prefs_service_ptr_;
-
- std::set<std::string> keys_;
-
- // True upon the first OnPreferencesChanged received after Init.
- bool initialized_;
-
- DISALLOW_COPY_AND_ASSIGN(PrefClientStore);
-};
-
-} // namespace preferences
-#endif // SERVICES_PREFERENCES_PUBLIC_CPP_PREFS_CLIENT_STORE_H_
diff --git a/chromium/services/preferences/public/cpp/pref_registry_serializer.cc b/chromium/services/preferences/public/cpp/pref_registry_serializer.cc
new file mode 100644
index 00000000000..b64e2c787d4
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/pref_registry_serializer.cc
@@ -0,0 +1,21 @@
+// Copyright 2017 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 "services/preferences/public/cpp/pref_registry_serializer.h"
+
+#include "components/prefs/pref_registry.h"
+
+namespace prefs {
+
+mojom::PrefRegistryPtr SerializePrefRegistry(PrefRegistry& pref_registry) {
+ auto registry = mojom::PrefRegistry::New();
+ for (auto& pref : pref_registry) {
+ registry->registrations[pref.first] = mojom::PrefRegistration::New(
+ pref.second->CreateDeepCopy(),
+ pref_registry.GetRegistrationFlags(pref.first));
+ }
+ return registry;
+}
+
+} // namespace prefs
diff --git a/chromium/services/preferences/public/cpp/pref_registry_serializer.h b/chromium/services/preferences/public/cpp/pref_registry_serializer.h
new file mode 100644
index 00000000000..4e016ce9519
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/pref_registry_serializer.h
@@ -0,0 +1,18 @@
+// Copyright 2017 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 SERVICES_PREFERENCES_PUBLIC_CPP_PREF_REGISTRY_SERIALIZER_H_
+#define SERVICES_PREFERENCES_PUBLIC_CPP_PREF_REGISTRY_SERIALIZER_H_
+
+#include "services/preferences/public/interfaces/preferences.mojom.h"
+
+class PrefRegistry;
+
+namespace prefs {
+
+mojom::PrefRegistryPtr SerializePrefRegistry(PrefRegistry& pref_registry);
+
+} // namespace prefs
+
+#endif // SERVICES_PREFERENCES_PUBLIC_CPP_PREF_REGISTRY_SERIALIZER_H_
diff --git a/chromium/services/preferences/public/cpp/pref_service_factory.cc b/chromium/services/preferences/public/cpp/pref_service_factory.cc
new file mode 100644
index 00000000000..58ff031c081
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/pref_service_factory.cc
@@ -0,0 +1,116 @@
+// Copyright 2017 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 "services/preferences/public/cpp/pref_service_factory.h"
+
+#include "base/callback_helpers.h"
+#include "components/prefs/persistent_pref_store.h"
+#include "components/prefs/pref_notifier_impl.h"
+#include "components/prefs/pref_registry.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/pref_value_store.h"
+#include "services/preferences/public/cpp/persistent_pref_store_client.h"
+#include "services/preferences/public/cpp/pref_registry_serializer.h"
+#include "services/preferences/public/cpp/pref_store_client.h"
+#include "services/preferences/public/interfaces/preferences.mojom.h"
+#include "services/service_manager/public/cpp/connector.h"
+
+namespace prefs {
+namespace {
+
+// Used to implement a "fire and forget" pattern where we call an interface
+// method, with an attached error handler, but don't care to hold on to the
+// InterfacePtr after.
+template <typename Interface>
+class RefCountedInterfacePtr
+ : public base::RefCounted<RefCountedInterfacePtr<Interface>> {
+ public:
+ mojo::InterfacePtr<Interface>& get() { return ptr_; }
+ void reset() { ptr_.reset(); }
+
+ private:
+ friend class base::RefCounted<RefCountedInterfacePtr<Interface>>;
+ ~RefCountedInterfacePtr() = default;
+
+ mojo::InterfacePtr<Interface> ptr_;
+};
+
+void DoNothingHandleReadError(PersistentPrefStore::PrefReadError error) {}
+
+scoped_refptr<PrefStore> CreatePrefStore(
+ PrefValueStore::PrefStoreType store_type,
+ std::unordered_map<PrefValueStore::PrefStoreType,
+ mojom::PrefStoreConnectionPtr>* connections) {
+ auto pref_store_it = connections->find(store_type);
+ if (pref_store_it != connections->end()) {
+ return make_scoped_refptr(
+ new PrefStoreClient(std::move(pref_store_it->second)));
+ } else {
+ return nullptr;
+ }
+}
+
+void OnConnect(
+ scoped_refptr<RefCountedInterfacePtr<mojom::PrefStoreConnector>>
+ connector_ptr,
+ scoped_refptr<PrefRegistry> pref_registry,
+ ConnectCallback callback,
+ mojom::PersistentPrefStoreConnectionPtr persistent_pref_store_connection,
+ std::unordered_map<PrefValueStore::PrefStoreType,
+ mojom::PrefStoreConnectionPtr> connections) {
+ scoped_refptr<PrefStore> managed_prefs =
+ CreatePrefStore(PrefValueStore::MANAGED_STORE, &connections);
+ scoped_refptr<PrefStore> supervised_user_prefs =
+ CreatePrefStore(PrefValueStore::SUPERVISED_USER_STORE, &connections);
+ scoped_refptr<PrefStore> extension_prefs =
+ CreatePrefStore(PrefValueStore::EXTENSION_STORE, &connections);
+ scoped_refptr<PrefStore> command_line_prefs =
+ CreatePrefStore(PrefValueStore::COMMAND_LINE_STORE, &connections);
+ scoped_refptr<PrefStore> recommended_prefs =
+ CreatePrefStore(PrefValueStore::RECOMMENDED_STORE, &connections);
+ scoped_refptr<PrefStore> default_prefs =
+ CreatePrefStore(PrefValueStore::DEFAULT_STORE, &connections);
+ scoped_refptr<PersistentPrefStore> persistent_pref_store(
+ new PersistentPrefStoreClient(
+ std::move(persistent_pref_store_connection)));
+ PrefNotifierImpl* pref_notifier = new PrefNotifierImpl();
+ auto* pref_value_store = new PrefValueStore(
+ managed_prefs.get(), supervised_user_prefs.get(), extension_prefs.get(),
+ command_line_prefs.get(), persistent_pref_store.get(),
+ recommended_prefs.get(), default_prefs.get(), pref_notifier);
+ callback.Run(base::MakeUnique<::PrefService>(
+ pref_notifier, pref_value_store, persistent_pref_store.get(),
+ pref_registry.get(), base::Bind(&DoNothingHandleReadError), true));
+ connector_ptr->reset();
+}
+
+void OnConnectError(
+ scoped_refptr<RefCountedInterfacePtr<mojom::PrefStoreConnector>>
+ connector_ptr,
+ ConnectCallback callback) {
+ callback.Run(nullptr);
+ connector_ptr->reset();
+}
+
+} // namespace
+
+void ConnectToPrefService(
+ service_manager::Connector* connector,
+ scoped_refptr<PrefRegistry> pref_registry,
+ const std::vector<PrefValueStore::PrefStoreType>& already_connected_types,
+ ConnectCallback callback,
+ base::StringPiece service_name) {
+ auto connector_ptr = make_scoped_refptr(
+ new RefCountedInterfacePtr<mojom::PrefStoreConnector>());
+ connector->BindInterface(service_name.as_string(), &connector_ptr->get());
+ connector_ptr->get().set_connection_error_handler(base::Bind(
+ &OnConnectError, connector_ptr, base::Passed(ConnectCallback{callback})));
+ auto serialized_pref_registry = SerializePrefRegistry(*pref_registry);
+ connector_ptr->get()->Connect(
+ std::move(serialized_pref_registry), already_connected_types,
+ base::Bind(&OnConnect, connector_ptr, base::Passed(&pref_registry),
+ base::Passed(&callback)));
+}
+
+} // namespace prefs
diff --git a/chromium/services/preferences/public/cpp/pref_service_factory.h b/chromium/services/preferences/public/cpp/pref_service_factory.h
new file mode 100644
index 00000000000..d138ad3583d
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/pref_service_factory.h
@@ -0,0 +1,48 @@
+// Copyright 2017 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.
+
+// This provides a way for any service to connect to the pref service to access
+// the application's current preferences.
+
+// Access is provided through a synchronous interface, exposed using the
+// |PrefService| class.
+
+#ifndef SERVICES_PREFERENCES_PUBLIC_CPP_PREF_SERVICE_FACTORY_H_
+#define SERVICES_PREFERENCES_PUBLIC_CPP_PREF_SERVICE_FACTORY_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "components/prefs/pref_value_store.h"
+#include "services/preferences/public/interfaces/preferences.mojom.h"
+
+class PrefRegistry;
+class PrefService;
+
+namespace service_manager {
+class Connector;
+}
+
+namespace prefs {
+
+// Note that |PrefService| might not be fully initialized yet and thus you need
+// to call |AddPrefInitObserver| on it before using it. Passed |nullptr| on
+// failure.
+using ConnectCallback = base::Callback<void(std::unique_ptr<::PrefService>)>;
+
+// Create a |PrefService| object acting as a client library for the pref
+// service, by connecting to the service using |connector|. Connecting is
+// asynchronous and |callback| will be called when it has been established. All
+// preferences that will be accessed need to be registered in |pref_registry|
+// first.
+void ConnectToPrefService(
+ service_manager::Connector* connector,
+ scoped_refptr<PrefRegistry> pref_registry,
+ const std::vector<PrefValueStore::PrefStoreType>& already_connected_types,
+ ConnectCallback callback,
+ base::StringPiece service_name = mojom::kServiceName);
+
+} // namespace prefs
+
+#endif // SERVICES_PREFERENCES_PUBLIC_CPP_PREF_SERVICE_FACTORY_H_
diff --git a/chromium/services/preferences/public/cpp/pref_service_main.cc b/chromium/services/preferences/public/cpp/pref_service_main.cc
new file mode 100644
index 00000000000..b4c92c53060
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/pref_service_main.cc
@@ -0,0 +1,20 @@
+// Copyright 2017 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 "services/preferences/public/cpp/pref_service_main.h"
+
+#include "base/threading/sequenced_worker_pool.h"
+#include "services/preferences/pref_store_manager_impl.h"
+#include "services/service_manager/public/cpp/service.h"
+
+namespace prefs {
+
+std::unique_ptr<service_manager::Service> CreatePrefService(
+ std::set<PrefValueStore::PrefStoreType> expected_pref_stores,
+ scoped_refptr<base::SequencedWorkerPool> worker_pool) {
+ return base::MakeUnique<PrefStoreManagerImpl>(expected_pref_stores,
+ std::move(worker_pool));
+}
+
+} // namespace prefs
diff --git a/chromium/services/preferences/public/cpp/pref_service_main.h b/chromium/services/preferences/public/cpp/pref_service_main.h
new file mode 100644
index 00000000000..6b51efaf3c1
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/pref_service_main.h
@@ -0,0 +1,30 @@
+// Copyright 2017 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 SERVICES_PREFERENCES_PUBLIC_CPP_PREF_SERVICE_MAIN_H_
+#define SERVICES_PREFERENCES_PUBLIC_CPP_PREF_SERVICE_MAIN_H_
+
+#include <memory>
+#include <set>
+
+#include "base/memory/ref_counted.h"
+#include "components/prefs/pref_value_store.h"
+
+namespace base {
+class SequencedWorkerPool;
+}
+
+namespace service_manager {
+class Service;
+}
+
+namespace prefs {
+
+std::unique_ptr<service_manager::Service> CreatePrefService(
+ std::set<PrefValueStore::PrefStoreType> expected_pref_stores,
+ scoped_refptr<base::SequencedWorkerPool> worker_pool);
+
+} // namespace prefs
+
+#endif // SERVICES_PREFERENCES_PUBLIC_CPP_PREF_SERVICE_MAIN_H_
diff --git a/chromium/services/preferences/public/cpp/pref_store_adapter.cc b/chromium/services/preferences/public/cpp/pref_store_adapter.cc
new file mode 100644
index 00000000000..2cfe85a9167
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/pref_store_adapter.cc
@@ -0,0 +1,40 @@
+// Copyright 2017 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 "services/preferences/public/cpp/pref_store_adapter.h"
+
+namespace prefs {
+
+PrefStoreAdapter::PrefStoreAdapter(scoped_refptr<PrefStore> pref_store,
+ std::unique_ptr<PrefStoreImpl> impl)
+ : pref_store_(std::move(pref_store)), impl_(std::move(impl)) {}
+
+void PrefStoreAdapter::AddObserver(PrefStore::Observer* observer) {
+ pref_store_->AddObserver(observer);
+}
+
+void PrefStoreAdapter::RemoveObserver(PrefStore::Observer* observer) {
+ pref_store_->RemoveObserver(observer);
+}
+
+bool PrefStoreAdapter::HasObservers() const {
+ return pref_store_->HasObservers();
+}
+
+bool PrefStoreAdapter::IsInitializationComplete() const {
+ return pref_store_->IsInitializationComplete();
+}
+
+bool PrefStoreAdapter::GetValue(const std::string& key,
+ const base::Value** result) const {
+ return pref_store_->GetValue(key, result);
+}
+
+std::unique_ptr<base::DictionaryValue> PrefStoreAdapter::GetValues() const {
+ return pref_store_->GetValues();
+}
+
+PrefStoreAdapter::~PrefStoreAdapter() = default;
+
+} // namespace prefs
diff --git a/chromium/services/preferences/public/cpp/pref_store_adapter.h b/chromium/services/preferences/public/cpp/pref_store_adapter.h
new file mode 100644
index 00000000000..c734606344e
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/pref_store_adapter.h
@@ -0,0 +1,44 @@
+// Copyright 2017 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 SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_ADAPTER_H_
+#define SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_ADAPTER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/values.h"
+#include "components/prefs/pref_store.h"
+#include "services/preferences/public/cpp/pref_store_impl.h"
+
+namespace prefs {
+
+// Ties the lifetime of a |PrefStoreImpl| to a |PrefStore|. Otherwise acts as a
+// |PrefStore|, forwarding all calls to the wrapped |PrefStore|.
+class PrefStoreAdapter : public PrefStore {
+ public:
+ PrefStoreAdapter(scoped_refptr<PrefStore> pref_store,
+ std::unique_ptr<PrefStoreImpl> impl);
+
+ // PrefStore:
+ void AddObserver(PrefStore::Observer* observer) override;
+ void RemoveObserver(PrefStore::Observer* observer) override;
+ bool HasObservers() const override;
+ bool IsInitializationComplete() const override;
+ bool GetValue(const std::string& key,
+ const base::Value** result) const override;
+ std::unique_ptr<base::DictionaryValue> GetValues() const override;
+
+ private:
+ ~PrefStoreAdapter() override;
+
+ scoped_refptr<PrefStore> pref_store_;
+ std::unique_ptr<PrefStoreImpl> impl_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrefStoreAdapter);
+};
+
+} // namespace prefs
+
+#endif // SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_ADAPTER_H_
diff --git a/chromium/services/preferences/public/cpp/pref_store_client.cc b/chromium/services/preferences/public/cpp/pref_store_client.cc
new file mode 100644
index 00000000000..441e8154589
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/pref_store_client.cc
@@ -0,0 +1,18 @@
+// Copyright 2017 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 "services/preferences/public/cpp/pref_store_client.h"
+
+#include <utility>
+
+namespace prefs {
+
+PrefStoreClient::PrefStoreClient(mojom::PrefStoreConnectionPtr connection) {
+ Init(std::move(connection->initial_prefs), connection->is_initialized,
+ std::move(connection->observer));
+}
+
+PrefStoreClient::~PrefStoreClient() = default;
+
+} // namespace prefs
diff --git a/chromium/services/preferences/public/cpp/pref_store_client.h b/chromium/services/preferences/public/cpp/pref_store_client.h
new file mode 100644
index 00000000000..b4d82451100
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/pref_store_client.h
@@ -0,0 +1,34 @@
+// Copyright 2017 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 SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_CLIENT_H_
+#define SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_CLIENT_H_
+
+#include "base/macros.h"
+#include "components/prefs/pref_store.h"
+#include "services/preferences/public/cpp/pref_store_client_mixin.h"
+#include "services/preferences/public/interfaces/preferences.mojom.h"
+
+namespace prefs {
+
+// TODO(tibell): Make PrefObserverStore use PrefStoreClient as a base class.
+
+// An implementation of PrefStore which uses prefs::mojom::PrefStore as
+// the backing store of the preferences.
+//
+// PrefStoreClient provides synchronous access to the preferences stored by the
+// backing store by caching them locally.
+class PrefStoreClient : public PrefStoreClientMixin<::PrefStore> {
+ public:
+ explicit PrefStoreClient(mojom::PrefStoreConnectionPtr connection);
+
+ private:
+ ~PrefStoreClient() override;
+
+ DISALLOW_COPY_AND_ASSIGN(PrefStoreClient);
+};
+
+} // namespace prefs
+
+#endif // SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_CLIENT_H_
diff --git a/chromium/services/preferences/public/cpp/pref_store_client_mixin.cc b/chromium/services/preferences/public/cpp/pref_store_client_mixin.cc
new file mode 100644
index 00000000000..0387883265f
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/pref_store_client_mixin.cc
@@ -0,0 +1,129 @@
+// Copyright 2017 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 "services/preferences/public/cpp/pref_store_client_mixin.h"
+
+#include <utility>
+
+#include "base/values.h"
+#include "services/preferences/public/cpp/pref_store_client.h"
+
+namespace prefs {
+
+template <typename BasePrefStore>
+PrefStoreClientMixin<BasePrefStore>::PrefStoreClientMixin()
+ : observer_binding_(this) {}
+
+template <typename BasePrefStore>
+void PrefStoreClientMixin<BasePrefStore>::AddObserver(
+ PrefStore::Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+template <typename BasePrefStore>
+void PrefStoreClientMixin<BasePrefStore>::RemoveObserver(
+ PrefStore::Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+template <typename BasePrefStore>
+bool PrefStoreClientMixin<BasePrefStore>::HasObservers() const {
+ return observers_.might_have_observers();
+}
+
+template <typename BasePrefStore>
+bool PrefStoreClientMixin<BasePrefStore>::IsInitializationComplete() const {
+ return initialized_ && static_cast<bool>(cached_prefs_);
+}
+
+template <typename BasePrefStore>
+bool PrefStoreClientMixin<BasePrefStore>::GetValue(
+ const std::string& key,
+ const base::Value** result) const {
+ DCHECK(initialized_);
+ DCHECK(cached_prefs_);
+ return cached_prefs_->Get(key, result);
+}
+
+template <typename BasePrefStore>
+std::unique_ptr<base::DictionaryValue>
+PrefStoreClientMixin<BasePrefStore>::GetValues() const {
+ DCHECK(initialized_);
+ DCHECK(cached_prefs_);
+ return cached_prefs_->CreateDeepCopy();
+}
+
+template <typename BasePrefStore>
+PrefStoreClientMixin<BasePrefStore>::~PrefStoreClientMixin() = default;
+
+template <typename BasePrefStore>
+void PrefStoreClientMixin<BasePrefStore>::Init(
+ std::unique_ptr<base::DictionaryValue> initial_prefs,
+ bool initialized,
+ mojom::PrefStoreObserverRequest observer_request) {
+ cached_prefs_ = std::move(initial_prefs);
+ observer_binding_.Bind(std::move(observer_request));
+ if (initialized)
+ OnInitializationCompleted(static_cast<bool>(cached_prefs_));
+}
+
+template <typename BasePrefStore>
+base::DictionaryValue& PrefStoreClientMixin<BasePrefStore>::GetMutableValues() {
+ DCHECK(cached_prefs_);
+ return *cached_prefs_;
+}
+
+template <typename BasePrefStore>
+void PrefStoreClientMixin<BasePrefStore>::ReportPrefValueChanged(
+ const std::string& key) {
+ for (auto& observer : observers_)
+ observer.OnPrefValueChanged(key);
+}
+
+template <typename BasePrefStore>
+void PrefStoreClientMixin<BasePrefStore>::OnPrefsChanged(
+ std::vector<mojom::PrefUpdatePtr> updates) {
+ for (const auto& update : updates)
+ OnPrefChanged(update->key, std::move(update->value));
+}
+
+template <typename BasePrefStore>
+void PrefStoreClientMixin<BasePrefStore>::OnInitializationCompleted(
+ bool succeeded) {
+ if (!initialized_) {
+ initialized_ = true;
+ for (auto& observer : observers_)
+ observer.OnInitializationCompleted(succeeded);
+ }
+}
+
+template <typename BasePrefStore>
+void PrefStoreClientMixin<BasePrefStore>::OnPrefChanged(
+ const std::string& key,
+ std::unique_ptr<base::Value> value) {
+ DCHECK(cached_prefs_);
+ bool changed = false;
+ if (!value) { // Delete
+ if (cached_prefs_->RemovePath(key, nullptr))
+ changed = true;
+ } else {
+ const base::Value* prev;
+ if (cached_prefs_->Get(key, &prev)) {
+ if (!prev->Equals(value.get())) {
+ cached_prefs_->Set(key, std::move(value));
+ changed = true;
+ }
+ } else {
+ cached_prefs_->Set(key, std::move(value));
+ changed = true;
+ }
+ }
+ if (changed && initialized_)
+ ReportPrefValueChanged(key);
+}
+
+template class PrefStoreClientMixin<::PrefStore>;
+template class PrefStoreClientMixin<::PersistentPrefStore>;
+
+} // namespace prefs
diff --git a/chromium/services/preferences/public/cpp/pref_store_client_mixin.h b/chromium/services/preferences/public/cpp/pref_store_client_mixin.h
new file mode 100644
index 00000000000..19cf250c586
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/pref_store_client_mixin.h
@@ -0,0 +1,82 @@
+// Copyright 2017 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 SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_CLIENT_MIXIN_H_
+#define SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_CLIENT_MIXIN_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "services/preferences/public/interfaces/preferences.mojom.h"
+
+namespace base {
+class DictionaryValue;
+class Value;
+}
+
+namespace prefs {
+
+// An mixin implementation of PrefStore which uses prefs::mojom::PrefStore as
+// the backing store of the preferences.
+//
+// PrefStoreClientMixin provides synchronous access to the preferences stored by
+// the backing store by caching them locally.
+template <typename BasePrefStore>
+class PrefStoreClientMixin : public BasePrefStore,
+ public mojom::PrefStoreObserver {
+ public:
+ PrefStoreClientMixin();
+
+ // BasePrefStore:
+ void AddObserver(PrefStore::Observer* observer) override;
+ void RemoveObserver(PrefStore::Observer* observer) override;
+ bool HasObservers() const override;
+ bool IsInitializationComplete() const override;
+ bool GetValue(const std::string& key,
+ const base::Value** result) const override;
+ std::unique_ptr<base::DictionaryValue> GetValues() const override;
+
+ protected:
+ ~PrefStoreClientMixin() override;
+
+ // Initializes |this|. This must be called before any of the other public or
+ // protected member functions.
+ void Init(std::unique_ptr<base::DictionaryValue> initial_prefs,
+ bool initialized,
+ mojom::PrefStoreObserverRequest observer_request);
+
+ base::DictionaryValue& GetMutableValues();
+ void ReportPrefValueChanged(const std::string& key);
+
+ private:
+ // prefs::mojom::PreferenceObserver:
+ void OnPrefsChanged(std::vector<mojom::PrefUpdatePtr> updates) override;
+ void OnInitializationCompleted(bool succeeded) override;
+
+ void OnPrefChanged(const std::string& key,
+ std::unique_ptr<base::Value> value);
+
+ // Cached preferences.
+ // If null, indicates that initialization failed.
+ std::unique_ptr<base::DictionaryValue> cached_prefs_;
+
+ base::ObserverList<PrefStore::Observer, true> observers_;
+
+ // Has the PrefStore we're observing been initialized?
+ bool initialized_ = false;
+
+ mojo::Binding<mojom::PrefStoreObserver> observer_binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrefStoreClientMixin);
+};
+
+extern template class PrefStoreClientMixin<::PrefStore>;
+extern template class PrefStoreClientMixin<::PersistentPrefStore>;
+
+} // namespace prefs
+
+#endif // SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_CLIENT_MIXIN_H_
diff --git a/chromium/services/preferences/public/cpp/pref_store_impl.cc b/chromium/services/preferences/public/cpp/pref_store_impl.cc
new file mode 100644
index 00000000000..c2289621e7e
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/pref_store_impl.cc
@@ -0,0 +1,113 @@
+// Copyright 2017 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 "services/preferences/public/cpp/pref_store_impl.h"
+
+#include <memory>
+#include <unordered_set>
+
+#include "base/stl_util.h"
+#include "base/values.h"
+
+namespace prefs {
+
+class PrefStoreImpl::Observer {
+ public:
+ Observer(mojom::PrefStoreObserverPtr observer,
+ std::unordered_set<std::string> prefs)
+ : observer_(std::move(observer)), prefs_(std::move(prefs)) {}
+
+ void OnInitializationCompleted(bool succeeded) {
+ observer_->OnInitializationCompleted(succeeded);
+ }
+
+ void OnPrefChanged(const std::string& key, const base::Value& value) const {
+ if (!base::ContainsKey(prefs_, key))
+ return;
+
+ std::vector<mojom::PrefUpdatePtr> updates;
+ updates.push_back(mojom::PrefUpdate::New(key, value.CreateDeepCopy(), 0));
+ observer_->OnPrefsChanged(std::move(updates));
+ }
+
+ void OnPrefRemoved(const std::string& key) const {
+ if (!base::ContainsKey(prefs_, key))
+ return;
+
+ std::vector<mojom::PrefUpdatePtr> updates;
+ updates.push_back(mojom::PrefUpdate::New(key, nullptr, 0));
+ observer_->OnPrefsChanged(std::move(updates));
+ }
+
+ private:
+ mojom::PrefStoreObserverPtr observer_;
+ const std::unordered_set<std::string> prefs_;
+
+ DISALLOW_COPY_AND_ASSIGN(Observer);
+};
+
+PrefStoreImpl::PrefStoreImpl(scoped_refptr<::PrefStore> pref_store,
+ mojom::PrefStoreRequest request)
+ : backing_pref_store_(std::move(pref_store)),
+ backing_pref_store_initialized_(false),
+ binding_(this, std::move(request)) {
+ DCHECK(backing_pref_store_);
+ if (backing_pref_store_->IsInitializationComplete())
+ OnInitializationCompleted(true);
+ backing_pref_store_->AddObserver(this);
+}
+
+PrefStoreImpl::~PrefStoreImpl() {
+ backing_pref_store_->RemoveObserver(this);
+}
+
+// static
+std::unique_ptr<PrefStoreImpl> PrefStoreImpl::Create(
+ mojom::PrefStoreRegistry* registry_ptr,
+ scoped_refptr<::PrefStore> pref_store,
+ PrefValueStore::PrefStoreType type) {
+ mojom::PrefStorePtr ptr;
+ auto impl = base::MakeUnique<PrefStoreImpl>(std::move(pref_store),
+ mojo::MakeRequest(&ptr));
+ registry_ptr->Register(type, std::move(ptr));
+ return impl;
+}
+
+void PrefStoreImpl::OnPrefValueChanged(const std::string& key) {
+ auto dictionary = base::MakeUnique<base::DictionaryValue>();
+ const base::Value* value = nullptr;
+ if (backing_pref_store_->GetValue(key, &value)) {
+ for (const auto& observer : observers_)
+ observer->OnPrefChanged(key, *value);
+ } else {
+ for (const auto& observer : observers_)
+ observer->OnPrefRemoved(key);
+ }
+}
+
+void PrefStoreImpl::OnInitializationCompleted(bool succeeded) {
+ // Some pref stores call this more than once. We just ignore all calls after
+ // the first.
+ if (backing_pref_store_initialized_)
+ DCHECK(succeeded);
+ backing_pref_store_initialized_ = succeeded;
+ for (const auto& observer : observers_)
+ observer->OnInitializationCompleted(succeeded);
+}
+
+void PrefStoreImpl::AddObserver(
+ const std::vector<std::string>& prefs_to_observe,
+ const AddObserverCallback& callback) {
+ mojom::PrefStoreObserverPtr observer_ptr;
+ auto request = mojo::MakeRequest(&observer_ptr);
+ observers_.push_back(base::MakeUnique<Observer>(
+ std::move(observer_ptr),
+ std::unordered_set<std::string>(prefs_to_observe.begin(),
+ prefs_to_observe.end())));
+ callback.Run(mojom::PrefStoreConnection::New(
+ std::move(request), backing_pref_store_->GetValues(),
+ backing_pref_store_->IsInitializationComplete()));
+}
+
+} // namespace prefs
diff --git a/chromium/services/preferences/public/cpp/pref_store_impl.h b/chromium/services/preferences/public/cpp/pref_store_impl.h
new file mode 100644
index 00000000000..62146a648c7
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/pref_store_impl.h
@@ -0,0 +1,63 @@
+// Copyright 2017 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 SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_IMPL_H_
+#define SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_IMPL_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/prefs/pref_store.h"
+#include "components/prefs/pref_value_store.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "services/preferences/public/interfaces/preferences.mojom.h"
+
+namespace prefs {
+
+// Wraps an actual PrefStore implementation and exposes it as a
+// mojom::PrefStore interface.
+class PrefStoreImpl : public ::PrefStore::Observer, public mojom::PrefStore {
+ public:
+ PrefStoreImpl(scoped_refptr<::PrefStore> pref_store,
+ mojom::PrefStoreRequest request);
+ ~PrefStoreImpl() override;
+
+ // The created instance is registered in and owned by the
+ // |mojom::PrefStoreRegistry|.
+ static std::unique_ptr<PrefStoreImpl> Create(
+ mojom::PrefStoreRegistry* registry_ptr,
+ scoped_refptr<::PrefStore> pref_store,
+ PrefValueStore::PrefStoreType type);
+
+ private:
+ class Observer;
+
+ // PrefStore::Observer:
+ void OnPrefValueChanged(const std::string& key) override;
+ void OnInitializationCompleted(bool succeeded) override;
+
+ // prefs::mojom::PrefStore:
+ void AddObserver(const std::vector<std::string>& prefs_to_observe,
+ const AddObserverCallback& callback) override;
+
+ // The backing store we observer for changes.
+ scoped_refptr<::PrefStore> backing_pref_store_;
+
+ // Observers we notify when |backing_pref_store_| changes.
+ std::vector<std::unique_ptr<Observer>> observers_;
+
+ // True when the |backing_pref_store_| is initialized, either because it was
+ // passed already initialized in the constructor or after
+ // OnInitializationCompleted was called.
+ bool backing_pref_store_initialized_;
+
+ mojo::Binding<mojom::PrefStore> binding_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrefStoreImpl);
+};
+
+} // namespace prefs
+
+#endif // SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_IMPL_H_
diff --git a/chromium/services/preferences/public/cpp/preferences.typemap b/chromium/services/preferences/public/cpp/preferences.typemap
new file mode 100644
index 00000000000..d438bedb829
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/preferences.typemap
@@ -0,0 +1,22 @@
+# Copyright 2017 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.
+
+mojom = "//services/preferences/public/interfaces/preferences.mojom"
+public_headers = [
+ "//components/prefs/persistent_pref_store.h",
+ "//components/prefs/pref_value_store.h",
+]
+traits_headers =
+ [ "//services/preferences/public/cpp/preferences_struct_traits.h" ]
+sources = [
+ "//services/preferences/public/cpp/preferences_struct_traits.cc",
+]
+deps = [
+ "//components/prefs",
+]
+
+type_mappings = [
+ "prefs.mojom.PersistentPrefStoreConnection.ReadError=::PersistentPrefStore::PrefReadError",
+ "prefs.mojom.PrefStoreType=::PrefValueStore::PrefStoreType",
+]
diff --git a/chromium/services/preferences/public/cpp/preferences_struct_traits.cc b/chromium/services/preferences/public/cpp/preferences_struct_traits.cc
new file mode 100644
index 00000000000..4d22db121f9
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/preferences_struct_traits.cc
@@ -0,0 +1,136 @@
+// Copyright 2017 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 "services/preferences/public/cpp/preferences_struct_traits.h"
+
+namespace mojo {
+
+using PrefStoreType = prefs::mojom::PrefStoreType;
+
+PrefStoreType EnumTraits<PrefStoreType, PrefValueStore::PrefStoreType>::ToMojom(
+ PrefValueStore::PrefStoreType input) {
+ switch (input) {
+ case PrefValueStore::INVALID_STORE:
+ break;
+ case PrefValueStore::MANAGED_STORE:
+ return PrefStoreType::MANAGED;
+ case PrefValueStore::SUPERVISED_USER_STORE:
+ return PrefStoreType::SUPERVISED_USER;
+ case PrefValueStore::EXTENSION_STORE:
+ return PrefStoreType::EXTENSION;
+ case PrefValueStore::COMMAND_LINE_STORE:
+ return PrefStoreType::COMMAND_LINE;
+ case PrefValueStore::USER_STORE:
+ return PrefStoreType::USER;
+ case PrefValueStore::RECOMMENDED_STORE:
+ return PrefStoreType::RECOMMENDED;
+ case PrefValueStore::DEFAULT_STORE:
+ return PrefStoreType::DEFAULT;
+ }
+ NOTREACHED();
+ return {};
+}
+
+bool EnumTraits<PrefStoreType, PrefValueStore::PrefStoreType>::FromMojom(
+ PrefStoreType input,
+ PrefValueStore::PrefStoreType* output) {
+ switch (input) {
+ case PrefStoreType::MANAGED:
+ *output = PrefValueStore::MANAGED_STORE;
+ return true;
+ case PrefStoreType::SUPERVISED_USER:
+ *output = PrefValueStore::SUPERVISED_USER_STORE;
+ return true;
+ case PrefStoreType::EXTENSION:
+ *output = PrefValueStore::EXTENSION_STORE;
+ return true;
+ case PrefStoreType::COMMAND_LINE:
+ *output = PrefValueStore::COMMAND_LINE_STORE;
+ return true;
+ case PrefStoreType::USER:
+ *output = PrefValueStore::USER_STORE;
+ return true;
+ case PrefStoreType::RECOMMENDED:
+ *output = PrefValueStore::RECOMMENDED_STORE;
+ return true;
+ case PrefStoreType::DEFAULT:
+ *output = PrefValueStore::DEFAULT_STORE;
+ return true;
+ }
+ return false;
+}
+
+using MojomReadError = prefs::mojom::PersistentPrefStoreConnection_ReadError;
+
+MojomReadError
+EnumTraits<MojomReadError, PersistentPrefStore::PrefReadError>::ToMojom(
+ PersistentPrefStore::PrefReadError input) {
+ switch (input) {
+ case PersistentPrefStore::PREF_READ_ERROR_NONE:
+ return MojomReadError::NONE;
+ case PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE:
+ return MojomReadError::JSON_PARSE;
+ case PersistentPrefStore::PREF_READ_ERROR_JSON_TYPE:
+ return MojomReadError::JSON_TYPE;
+ case PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED:
+ return MojomReadError::ACCESS_DENIED;
+ case PersistentPrefStore::PREF_READ_ERROR_FILE_OTHER:
+ return MojomReadError::FILE_OTHER;
+ case PersistentPrefStore::PREF_READ_ERROR_FILE_LOCKED:
+ return MojomReadError::FILE_LOCKED;
+ case PersistentPrefStore::PREF_READ_ERROR_NO_FILE:
+ return MojomReadError::NO_FILE;
+ case PersistentPrefStore::PREF_READ_ERROR_JSON_REPEAT:
+ return MojomReadError::JSON_REPEAT;
+ case PersistentPrefStore::PREF_READ_ERROR_FILE_NOT_SPECIFIED:
+ return MojomReadError::FILE_NOT_SPECIFIED;
+ case PersistentPrefStore::PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE:
+ return MojomReadError::ASYNCHRONOUS_TASK_INCOMPLETE;
+ case PersistentPrefStore::PREF_READ_ERROR_MAX_ENUM:
+ break;
+ }
+ NOTREACHED();
+ return {};
+}
+
+bool EnumTraits<MojomReadError, PersistentPrefStore::PrefReadError>::FromMojom(
+ MojomReadError input,
+ PersistentPrefStore::PrefReadError* output) {
+ switch (input) {
+ case MojomReadError::NONE:
+ *output = PersistentPrefStore::PREF_READ_ERROR_NONE;
+ return true;
+ case MojomReadError::JSON_PARSE:
+ *output = PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE;
+ return true;
+ case MojomReadError::JSON_TYPE:
+ *output = PersistentPrefStore::PREF_READ_ERROR_JSON_TYPE;
+ return true;
+ case MojomReadError::ACCESS_DENIED:
+ *output = PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED;
+ return true;
+ case MojomReadError::FILE_OTHER:
+ *output = PersistentPrefStore::PREF_READ_ERROR_FILE_OTHER;
+ return true;
+ case MojomReadError::FILE_LOCKED:
+ *output = PersistentPrefStore::PREF_READ_ERROR_FILE_LOCKED;
+ return true;
+ case MojomReadError::NO_FILE:
+ *output = PersistentPrefStore::PREF_READ_ERROR_NO_FILE;
+ return true;
+ case MojomReadError::JSON_REPEAT:
+ *output = PersistentPrefStore::PREF_READ_ERROR_JSON_REPEAT;
+ return true;
+ case MojomReadError::FILE_NOT_SPECIFIED:
+ *output = PersistentPrefStore::PREF_READ_ERROR_FILE_NOT_SPECIFIED;
+ return true;
+ case MojomReadError::ASYNCHRONOUS_TASK_INCOMPLETE:
+ *output =
+ PersistentPrefStore::PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE;
+ return true;
+ }
+ return false;
+}
+
+} // namespace mojo
diff --git a/chromium/services/preferences/public/cpp/preferences_struct_traits.h b/chromium/services/preferences/public/cpp/preferences_struct_traits.h
new file mode 100644
index 00000000000..194d2748b24
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/preferences_struct_traits.h
@@ -0,0 +1,38 @@
+// Copyright 2017 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 SERVICES_PREFERENCES_PUBLIC_CPP_PREFERENCES_STRUCT_TRAITS_H_
+#define SERVICES_PREFERENCES_PUBLIC_CPP_PREFERENCES_STRUCT_TRAITS_H_
+
+#include "components/prefs/persistent_pref_store.h"
+#include "components/prefs/pref_value_store.h"
+#include "mojo/public/cpp/bindings/enum_traits.h"
+#include "services/preferences/public/interfaces/preferences.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct EnumTraits<::prefs::mojom::PrefStoreType,
+ ::PrefValueStore::PrefStoreType> {
+ static prefs::mojom::PrefStoreType ToMojom(
+ PrefValueStore::PrefStoreType input);
+
+ static bool FromMojom(prefs::mojom::PrefStoreType input,
+ PrefValueStore::PrefStoreType* output);
+};
+
+template <>
+struct EnumTraits<::prefs::mojom::PersistentPrefStoreConnection_ReadError,
+ ::PersistentPrefStore::PrefReadError> {
+ static prefs::mojom::PersistentPrefStoreConnection_ReadError ToMojom(
+ PersistentPrefStore::PrefReadError input);
+
+ static bool FromMojom(
+ prefs::mojom::PersistentPrefStoreConnection_ReadError input,
+ PersistentPrefStore::PrefReadError* output);
+};
+
+} // namespace mojo
+
+#endif // SERVICES_PREFERENCES_PUBLIC_CPP_PREFERENCES_STRUCT_TRAITS_H_
diff --git a/chromium/services/preferences/public/cpp/tests/BUILD.gn b/chromium/services/preferences/public/cpp/tests/BUILD.gn
index 1c818cbff4d..bbe7e02055a 100644
--- a/chromium/services/preferences/public/cpp/tests/BUILD.gn
+++ b/chromium/services/preferences/public/cpp/tests/BUILD.gn
@@ -2,28 +2,20 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-import("//build/config/ui.gni")
-import("//testing/test.gni")
-
-group("tests") {
+source_set("tests") {
testonly = true
- deps = [
- ":preferences_unittests",
- ]
-}
-
-test("preferences_unittests") {
sources = [
- "pref_client_store_unittest.cc",
+ "pref_store_client_unittest.cc",
+ "pref_store_impl_unittest.cc",
]
deps = [
"//base",
"//base/test:test_support",
"//components/prefs:test_support",
- "//mojo/edk/test:run_all_unittests",
"//mojo/public/cpp/bindings:bindings",
"//services/preferences/public/cpp",
"//services/preferences/public/interfaces",
+ "//testing/gmock",
"//testing/gtest",
]
}
diff --git a/chromium/services/preferences/public/cpp/tracked/BUILD.gn b/chromium/services/preferences/public/cpp/tracked/BUILD.gn
new file mode 100644
index 00000000000..ddcc14d0e40
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/tracked/BUILD.gn
@@ -0,0 +1,33 @@
+# 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.
+
+static_library("tracked") {
+ sources = [
+ "configuration.cc",
+ "configuration.h",
+ "pref_names.cc",
+ "pref_names.h",
+ "tracked_preference_histogram_names.cc",
+ "tracked_preference_histogram_names.h",
+ ]
+ public_deps = [
+ "//services/preferences/public/interfaces",
+ ]
+ deps = [
+ "//base",
+ ]
+}
+
+static_library("test_support") {
+ testonly = true
+ sources = [
+ "mock_validation_delegate.cc",
+ "mock_validation_delegate.h",
+ ]
+
+ deps = [
+ "//base",
+ "//services/preferences/public/interfaces",
+ ]
+}
diff --git a/chromium/services/preferences/public/cpp/tracked/OWNERS b/chromium/services/preferences/public/cpp/tracked/OWNERS
new file mode 100644
index 00000000000..2845b0f366d
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/tracked/OWNERS
@@ -0,0 +1,2 @@
+bauerb@chromium.org
+gab@chromium.org
diff --git a/chromium/services/preferences/public/cpp/tracked/configuration.cc b/chromium/services/preferences/public/cpp/tracked/configuration.cc
new file mode 100644
index 00000000000..f49adeab640
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/tracked/configuration.cc
@@ -0,0 +1,25 @@
+// Copyright 2017 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 "services/preferences/public/cpp/tracked/configuration.h"
+
+namespace prefs {
+
+mojom::TrackedPreferenceMetadataPtr ConstructTrackedMetadata(
+ const TrackedPreferenceMetadata& metadata) {
+ return mojom::TrackedPreferenceMetadata::New(
+ metadata.reporting_id, metadata.name, metadata.enforcement_level,
+ metadata.strategy, metadata.value_type);
+}
+
+std::vector<mojom::TrackedPreferenceMetadataPtr> CloneTrackedConfiguration(
+ const std::vector<mojom::TrackedPreferenceMetadataPtr>& configuration) {
+ std::vector<mojom::TrackedPreferenceMetadataPtr> result;
+ for (const auto& metadata : configuration) {
+ result.push_back(metadata.Clone());
+ }
+ return result;
+}
+
+} // namespace prefs
diff --git a/chromium/services/preferences/public/cpp/tracked/configuration.h b/chromium/services/preferences/public/cpp/tracked/configuration.h
new file mode 100644
index 00000000000..bbb0ebff4ca
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/tracked/configuration.h
@@ -0,0 +1,37 @@
+// Copyright 2017 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 SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_CONFIGURATION_H_
+#define SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_CONFIGURATION_H_
+
+#include "services/preferences/public/interfaces/preferences_configuration.mojom.h"
+
+namespace prefs {
+
+struct TrackedPreferenceMetadata {
+ size_t reporting_id;
+ const char* name;
+ mojom::TrackedPreferenceMetadata::EnforcementLevel enforcement_level;
+ mojom::TrackedPreferenceMetadata::PrefTrackingStrategy strategy;
+ mojom::TrackedPreferenceMetadata::ValueType value_type;
+};
+
+mojom::TrackedPreferenceMetadataPtr ConstructTrackedMetadata(
+ const TrackedPreferenceMetadata& metadata);
+
+template <typename ConfigurationContainer>
+std::vector<mojom::TrackedPreferenceMetadataPtr> ConstructTrackedConfiguration(
+ const ConfigurationContainer& configuration) {
+ std::vector<mojom::TrackedPreferenceMetadataPtr> result;
+ for (auto metadata : configuration) {
+ result.push_back(ConstructTrackedMetadata(metadata));
+ }
+ return result;
+}
+
+std::vector<mojom::TrackedPreferenceMetadataPtr> CloneTrackedConfiguration(
+ const std::vector<mojom::TrackedPreferenceMetadataPtr>& configuration);
+
+} // namespace prefs
+#endif // SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_CONFIGURATION_H_
diff --git a/chromium/services/preferences/public/cpp/tracked/mock_validation_delegate.cc b/chromium/services/preferences/public/cpp/tracked/mock_validation_delegate.cc
new file mode 100644
index 00000000000..f26ff4f001f
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/tracked/mock_validation_delegate.cc
@@ -0,0 +1,84 @@
+// 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 "services/preferences/public/cpp/tracked/mock_validation_delegate.h"
+
+MockValidationDelegateRecord::MockValidationDelegateRecord() = default;
+
+MockValidationDelegateRecord::~MockValidationDelegateRecord() = default;
+
+size_t MockValidationDelegateRecord::CountValidationsOfState(
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state)
+ const {
+ size_t count = 0;
+ for (size_t i = 0; i < validations_.size(); ++i) {
+ if (validations_[i].value_state == value_state)
+ ++count;
+ }
+ return count;
+}
+
+size_t MockValidationDelegateRecord::CountExternalValidationsOfState(
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state)
+ const {
+ size_t count = 0;
+ for (size_t i = 0; i < validations_.size(); ++i) {
+ if (validations_[i].external_validation_value_state == value_state)
+ ++count;
+ }
+ return count;
+}
+
+const MockValidationDelegateRecord::ValidationEvent*
+MockValidationDelegateRecord::GetEventForPath(
+ const std::string& pref_path) const {
+ for (size_t i = 0; i < validations_.size(); ++i) {
+ if (validations_[i].pref_path == pref_path)
+ return &validations_[i];
+ }
+ return NULL;
+}
+
+void MockValidationDelegateRecord::RecordValidation(
+ const std::string& pref_path,
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state,
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState
+ external_validation_value_state,
+ bool is_personal,
+ prefs::mojom::TrackedPreferenceMetadata::PrefTrackingStrategy strategy) {
+ validations_.push_back(ValidationEvent(pref_path, value_state,
+ external_validation_value_state,
+ is_personal, strategy));
+}
+
+MockValidationDelegate::MockValidationDelegate(
+ scoped_refptr<MockValidationDelegateRecord> record)
+ : record_(std::move(record)) {}
+
+MockValidationDelegate::~MockValidationDelegate() = default;
+
+void MockValidationDelegate::OnAtomicPreferenceValidation(
+ const std::string& pref_path,
+ std::unique_ptr<base::Value> value,
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state,
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState
+ external_validation_value_state,
+ bool is_personal) {
+ record_->RecordValidation(
+ pref_path, value_state, external_validation_value_state, is_personal,
+ prefs::mojom::TrackedPreferenceMetadata::PrefTrackingStrategy::ATOMIC);
+}
+
+void MockValidationDelegate::OnSplitPreferenceValidation(
+ const std::string& pref_path,
+ const std::vector<std::string>& invalid_keys,
+ const std::vector<std::string>& external_validation_invalid_keys,
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state,
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState
+ external_validation_value_state,
+ bool is_personal) {
+ record_->RecordValidation(
+ pref_path, value_state, external_validation_value_state, is_personal,
+ prefs::mojom::TrackedPreferenceMetadata::PrefTrackingStrategy::SPLIT);
+}
diff --git a/chromium/services/preferences/public/cpp/tracked/mock_validation_delegate.h b/chromium/services/preferences/public/cpp/tracked/mock_validation_delegate.h
new file mode 100644
index 00000000000..5538c69301f
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/tracked/mock_validation_delegate.h
@@ -0,0 +1,124 @@
+// 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.
+
+#ifndef SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_MOCK_VALIDATION_DELEGATE_H_
+#define SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_MOCK_VALIDATION_DELEGATE_H_
+
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "services/preferences/public/interfaces/preferences_configuration.mojom.h"
+#include "services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom.h"
+
+class MockValidationDelegate;
+
+// A mock tracked preference validation delegate for use by tests.
+class MockValidationDelegateRecord
+ : public base::RefCounted<MockValidationDelegateRecord> {
+ public:
+ struct ValidationEvent {
+ ValidationEvent(
+ const std::string& path,
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState state,
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState
+ external_validation_state,
+ bool is_personal,
+ prefs::mojom::TrackedPreferenceMetadata::PrefTrackingStrategy
+ tracking_strategy)
+ : pref_path(path),
+ value_state(state),
+ external_validation_value_state(external_validation_state),
+ is_personal(is_personal),
+ strategy(tracking_strategy) {}
+
+ std::string pref_path;
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state;
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState
+ external_validation_value_state;
+ bool is_personal;
+ prefs::mojom::TrackedPreferenceMetadata::PrefTrackingStrategy strategy;
+ };
+
+ MockValidationDelegateRecord();
+
+ // Returns the number of recorded validations.
+ size_t recorded_validations_count() const { return validations_.size(); }
+
+ // Returns the number of validations of a given value state.
+ size_t CountValidationsOfState(
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state)
+ const;
+
+ // Returns the number of external validations of a given value state.
+ size_t CountExternalValidationsOfState(
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state)
+ const;
+
+ // Returns the event for the preference with a given path.
+ const ValidationEvent* GetEventForPath(const std::string& pref_path) const;
+
+ private:
+ friend class MockValidationDelegate;
+ friend class base::RefCounted<MockValidationDelegateRecord>;
+
+ ~MockValidationDelegateRecord();
+
+ // Adds a new validation event.
+ void RecordValidation(
+ const std::string& pref_path,
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state,
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState
+ external_validation_value_state,
+ bool is_personal,
+ prefs::mojom::TrackedPreferenceMetadata::PrefTrackingStrategy strategy);
+
+ std::vector<ValidationEvent> validations_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockValidationDelegateRecord);
+};
+
+class MockValidationDelegate
+ : public prefs::mojom::TrackedPreferenceValidationDelegate {
+ public:
+ explicit MockValidationDelegate(
+ scoped_refptr<MockValidationDelegateRecord> record);
+ ~MockValidationDelegate() override;
+
+ // TrackedPreferenceValidationDelegate implementation.
+ void OnAtomicPreferenceValidation(
+ const std::string& pref_path,
+ std::unique_ptr<base::Value> value,
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state,
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState
+ external_validation_value_state,
+ bool is_personal) override;
+ void OnSplitPreferenceValidation(
+ const std::string& pref_path,
+ const std::vector<std::string>& invalid_keys,
+ const std::vector<std::string>& external_validation_invalid_keys,
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state,
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState
+ external_validation_value_state,
+ bool is_personal) override;
+
+ private:
+ // Adds a new validation event.
+ void RecordValidation(
+ const std::string& pref_path,
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state,
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState
+ external_validation_value_state,
+ bool is_personal,
+ prefs::mojom::TrackedPreferenceMetadata::PrefTrackingStrategy strategy);
+
+ scoped_refptr<MockValidationDelegateRecord> record_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockValidationDelegate);
+};
+
+#endif // SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_MOCK_VALIDATION_DELEGATE_H_
diff --git a/chromium/services/preferences/public/cpp/tracked/pref_names.cc b/chromium/services/preferences/public/cpp/tracked/pref_names.cc
new file mode 100644
index 00000000000..59446c8a3f5
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/tracked/pref_names.cc
@@ -0,0 +1,13 @@
+// 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.
+
+#include "services/preferences/public/cpp/tracked/pref_names.h"
+
+namespace user_prefs {
+
+// A timestamp (stored in base::Time::ToInternalValue format) of the last time
+// a preference was reset.
+const char kPreferenceResetTime[] = "prefs.preference_reset_time";
+
+} // namespace user_prefs
diff --git a/chromium/services/preferences/public/cpp/tracked/pref_names.h b/chromium/services/preferences/public/cpp/tracked/pref_names.h
new file mode 100644
index 00000000000..2f594f5059f
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/tracked/pref_names.h
@@ -0,0 +1,14 @@
+// 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 SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_PREF_NAMES_H_
+#define SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_PREF_NAMES_H_
+
+namespace user_prefs {
+
+extern const char kPreferenceResetTime[];
+
+} // namespace user_prefs
+
+#endif // SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_PREF_NAMES_H_
diff --git a/chromium/services/preferences/public/cpp/tracked/tracked_preference_histogram_names.cc b/chromium/services/preferences/public/cpp/tracked/tracked_preference_histogram_names.cc
new file mode 100644
index 00000000000..7478db56b7f
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/tracked/tracked_preference_histogram_names.cc
@@ -0,0 +1,31 @@
+// 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.
+
+#include "services/preferences/public/cpp/tracked/tracked_preference_histogram_names.h"
+
+namespace user_prefs {
+namespace tracked {
+
+// Tracked pref histogram names.
+const char kTrackedPrefHistogramUnchanged[] =
+ "Settings.TrackedPreferenceUnchanged";
+const char kTrackedPrefHistogramCleared[] = "Settings.TrackedPreferenceCleared";
+const char kTrackedPrefHistogramMigratedLegacyDeviceId[] =
+ "Settings.TrackedPreferenceMigratedLegacyDeviceId";
+const char kTrackedPrefHistogramChanged[] = "Settings.TrackedPreferenceChanged";
+const char kTrackedPrefHistogramInitialized[] =
+ "Settings.TrackedPreferenceInitialized";
+const char kTrackedPrefHistogramTrustedInitialized[] =
+ "Settings.TrackedPreferenceTrustedInitialized";
+const char kTrackedPrefHistogramNullInitialized[] =
+ "Settings.TrackedPreferenceNullInitialized";
+const char kTrackedPrefHistogramWantedReset[] =
+ "Settings.TrackedPreferenceWantedReset";
+const char kTrackedPrefHistogramReset[] = "Settings.TrackedPreferenceReset";
+const char kTrackedSplitPrefHistogramChanged[] =
+ "Settings.TrackedSplitPreferenceChanged.";
+const char kTrackedPrefRegistryValidationSuffix[] = "FromRegistry";
+
+} // namespace tracked
+} // namespace user_prefs
diff --git a/chromium/services/preferences/public/cpp/tracked/tracked_preference_histogram_names.h b/chromium/services/preferences/public/cpp/tracked/tracked_preference_histogram_names.h
new file mode 100644
index 00000000000..ec0a219db58
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/tracked/tracked_preference_histogram_names.h
@@ -0,0 +1,26 @@
+// 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 SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_TRACKED_PREFERENCE_HISTOGRAM_NAMES_H_
+#define SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_TRACKED_PREFERENCE_HISTOGRAM_NAMES_H_
+
+namespace user_prefs {
+namespace tracked {
+
+extern const char kTrackedPrefHistogramUnchanged[];
+extern const char kTrackedPrefHistogramCleared[];
+extern const char kTrackedPrefHistogramMigratedLegacyDeviceId[];
+extern const char kTrackedPrefHistogramChanged[];
+extern const char kTrackedPrefHistogramInitialized[];
+extern const char kTrackedPrefHistogramTrustedInitialized[];
+extern const char kTrackedPrefHistogramNullInitialized[];
+extern const char kTrackedPrefHistogramWantedReset[];
+extern const char kTrackedPrefHistogramReset[];
+extern const char kTrackedSplitPrefHistogramChanged[];
+extern const char kTrackedPrefRegistryValidationSuffix[];
+
+} // namespace tracked
+} // namespace user_prefs
+
+#endif // SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_TRACKED_PREFERENCE_HISTOGRAM_NAMES_H_
diff --git a/chromium/services/preferences/public/cpp/typemaps.gni b/chromium/services/preferences/public/cpp/typemaps.gni
new file mode 100644
index 00000000000..2238fb6e9e7
--- /dev/null
+++ b/chromium/services/preferences/public/cpp/typemaps.gni
@@ -0,0 +1,5 @@
+# Copyright 2017 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.
+
+typemaps = [ "//services/preferences/public/cpp/preferences.typemap" ]
diff --git a/chromium/services/preferences/public/interfaces/BUILD.gn b/chromium/services/preferences/public/interfaces/BUILD.gn
index c7e9091f046..8bc3749fe60 100644
--- a/chromium/services/preferences/public/interfaces/BUILD.gn
+++ b/chromium/services/preferences/public/interfaces/BUILD.gn
@@ -7,6 +7,8 @@ import("//mojo/public/tools/bindings/mojom.gni")
mojom("interfaces") {
sources = [
"preferences.mojom",
+ "preferences_configuration.mojom",
+ "tracked_preference_validation_delegate.mojom",
]
public_deps = [
"//mojo/common:common_custom_types",
diff --git a/chromium/services/preferences/public/interfaces/preferences.mojom b/chromium/services/preferences/public/interfaces/preferences.mojom
index 1978e4b671f..47a79dc5386 100644
--- a/chromium/services/preferences/public/interfaces/preferences.mojom
+++ b/chromium/services/preferences/public/interfaces/preferences.mojom
@@ -5,25 +5,137 @@
module prefs.mojom;
import "mojo/common/values.mojom";
+import "services/preferences/public/interfaces/preferences_configuration.mojom";
const string kServiceName = "preferences";
+const string kForwarderServiceName = "preferences_forwarder";
-// Used for the creation of a PreferencesService and to ensure that the
-// PreferencesServiceClient is bound at creation time.
-interface PreferencesServiceFactory {
- // Creates a PreferencesService bound to the provided |observer|.
- Create(PreferencesServiceClient observer, PreferencesService& service);
+// The know pref store types.
+//
+// Should be kept in sync with PrefValueStore::PrefStoreType.
+enum PrefStoreType {
+ MANAGED,
+ SUPERVISED_USER,
+ EXTENSION,
+ COMMAND_LINE,
+ USER,
+ RECOMMENDED,
+ DEFAULT,
};
-// Used to subscribe to preference changes within PreferenceManager. After
-// requesting to observe, the current values for all requested keys are sent.
-interface PreferencesServiceClient {
- OnPreferencesChanged(mojo.common.mojom.DictionaryValue preferences);
+// Allows observing changes to prefs stored in a |PrefStore|.
+interface PrefStoreObserver {
+ // Preferences have been changed.
+ OnPrefsChanged(array<PrefUpdate> updates);
+
+ // The PrefStore has been initialized (asynchronously).
+ OnInitializationCompleted(bool succeeded);
+};
+
+// Captures the connections to a PrefStore by supplying the initial state of the
+// store and a handle to receive notifications on.
+struct PrefStoreConnection {
+ // Handle to receive updates on.
+ PrefStoreObserver& observer;
+
+ // Initial values of the PrefStore. These will not be communicated through
+ // OnPrefChanged.
+ mojo.common.mojom.DictionaryValue initial_prefs;
+
+ // Is the PrefStore initialized? If not it should not be used before
+ // OnInitializationCompleted has been called.
+ bool is_initialized;
+};
+
+struct PersistentPrefStoreConnection {
+ enum ReadError {
+ NONE = 0,
+ JSON_PARSE = 1,
+ JSON_TYPE = 2,
+ ACCESS_DENIED = 3,
+ FILE_OTHER = 4,
+ FILE_LOCKED = 5,
+ NO_FILE = 6,
+ JSON_REPEAT = 7,
+ // OTHER = 8, // Deprecated.
+ FILE_NOT_SPECIFIED = 9,
+ ASYNCHRONOUS_TASK_INCOMPLETE = 10,
+ };
+
+ PrefStoreConnection? pref_store_connection;
+ PersistentPrefStore? pref_store;
+ ReadError read_error;
+ bool read_only;
+};
+
+// Manages actual read of preference data. Accepts observers who subscribe to
+// preferences, notifying them of changes.
+interface PrefStore {
+ // Add an observer of changes to prefs contained in |prefs_to_observe|. This
+ // current values of all prefs will not be communicated through a call to
+ // |observer| but instead be returned in |initial_prefs|.
+ AddObserver(array<string> prefs_to_observe) => (
+ PrefStoreConnection connection);
+};
+
+// Manages a registry of all pref stores. Registered pref stores can be
+// connected to through the |PrefStoreConnector| interface.
+interface PrefStoreRegistry {
+ // Register a pref store.
+ Register(PrefStoreType type, PrefStore pref_store);
+};
+
+// Allows connections to pref stores registered with |PrefStoreRegistry|.
+interface PrefStoreConnector {
+ // Connect to all registered pref stores, retrieving the current values of all
+ // prefs in each store and an |observer| interfaces through which updates can
+ // be received. The client asserts that it is already connected to the
+ // |already_connected_types| pref stores through some other means, so the
+ // Connect call will not connect to those.
+ [Sync]
+ Connect(PrefRegistry pref_registry,
+ array<PrefStoreType> already_connected_types) =>
+ (PersistentPrefStoreConnection connection,
+ map<PrefStoreType, PrefStoreConnection> connections);
+};
+
+// An update to a pref.
+struct PrefUpdate {
+ // The key of the pref being updated.
+ string key;
+ // The new value; a null |value| indicates a delete.
+ mojo.common.mojom.Value? value;
+ //|flags| is a bitmask of WritablePrefStore::PrefWriteFlags.
+ uint32 flags;
+};
+
+// An interface providing mutation access to a PersistentPrefStore.
+interface PersistentPrefStore {
+ // Sets the values for prefs.
+ SetValues(array<PrefUpdate> updates);
+
+ // These mirror the C++ PersistentPrefStore methods.
+ CommitPendingWrite();
+ SchedulePendingLossyWrites();
+ ClearMutableValues();
+};
+
+// A registry of all prefs registered by a single client.
+struct PrefRegistry {
+ map<string, PrefRegistration> registrations;
+};
+
+struct PrefRegistration {
+ mojo.common.mojom.Value default_value;
+
+ // A bitfield of flags. Flag values are defined in
+ // PrefRegistry::PrefRegistrationFlags and
+ // PrefRegistrySyncable::PrefRegistrationFlags.
+ uint32 flags;
};
-// Manages actual read/write of preference data. Accepts observers who subscribe
-// to preferences, notifying them of changes.
-interface PreferencesService {
- SetPreferences(mojo.common.mojom.DictionaryValue preferences);
- Subscribe(array<string> preferences);
+interface PrefServiceControl {
+ // Initializes the pref service. This must be called before the service can
+ // be used.
+ Init(PersistentPrefStoreConfiguration configuration);
};
diff --git a/chromium/services/preferences/public/interfaces/preferences_configuration.mojom b/chromium/services/preferences/public/interfaces/preferences_configuration.mojom
new file mode 100644
index 00000000000..76f7785ae06
--- /dev/null
+++ b/chromium/services/preferences/public/interfaces/preferences_configuration.mojom
@@ -0,0 +1,62 @@
+// Copyright 2017 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.
+
+module prefs.mojom;
+
+import "mojo/common/file_path.mojom";
+import "mojo/common/string16.mojom";
+import "services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom";
+
+union PersistentPrefStoreConfiguration {
+ SimplePersistentPrefStoreConfiguration simple_configuration;
+ TrackedPersistentPrefStoreConfiguration tracked_configuration;
+};
+
+struct SimplePersistentPrefStoreConfiguration {
+ mojo.common.mojom.FilePath pref_filename;
+};
+
+// These parameters are passed to prefs::CreateTrackedPersistentPrefStore() in
+// services/preferences/persistent_pref_store_factory.cc.
+struct TrackedPersistentPrefStoreConfiguration {
+ mojo.common.mojom.FilePath unprotected_pref_filename;
+ mojo.common.mojom.FilePath protected_pref_filename;
+ array<TrackedPreferenceMetadata> tracking_configuration;
+ uint64 reporting_ids_count;
+ string seed;
+ string legacy_device_id;
+ string registry_seed;
+ mojo.common.mojom.String16 registry_path;
+ TrackedPreferenceValidationDelegate? validation_delegate;
+ ResetOnLoadObserver? reset_on_load_observer;
+};
+
+struct TrackedPreferenceMetadata {
+ enum EnforcementLevel { NO_ENFORCEMENT, ENFORCE_ON_LOAD };
+
+ enum PrefTrackingStrategy {
+ // Atomic preferences are tracked as a whole.
+ ATOMIC,
+ // Split preferences are dictionaries for which each top-level entry is
+ // tracked independently. Note: preferences using this strategy must be kept
+ // in sync with TrackedSplitPreferences in histograms.xml.
+ SPLIT,
+ };
+
+ enum ValueType {
+ IMPERSONAL,
+ // The preference value may contain personal information.
+ PERSONAL,
+ };
+
+ uint64 reporting_id;
+ string name;
+ EnforcementLevel enforcement_level;
+ PrefTrackingStrategy strategy;
+ ValueType value_type;
+};
+
+interface ResetOnLoadObserver {
+ OnResetOnLoad();
+};
diff --git a/chromium/services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom b/chromium/services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom
new file mode 100644
index 00000000000..370f95f75d9
--- /dev/null
+++ b/chromium/services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom
@@ -0,0 +1,56 @@
+// Copyright 2017 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.
+
+module prefs.mojom;
+
+import "mojo/common/values.mojom";
+
+// A TrackedPreferenceValidationDelegate is notified of the results of each
+// tracked preference validation event.
+interface TrackedPreferenceValidationDelegate {
+ enum ValueState {
+ // The preference value corresponds to its stored hash.
+ UNCHANGED,
+ // The preference has been cleared since the last hash.
+ CLEARED,
+ // The preference value corresponds to its stored hash, but the hash was
+ // calculated using a deprecated hash algorithm which is just as safe as
+ // the current one.
+ SECURE_LEGACY,
+ // The preference value has been changed since the last hash.
+ CHANGED,
+ // No stored hash exists for the preference value.
+ UNTRUSTED_UNKNOWN_VALUE,
+ // No stored hash exists for the preference value, but the current set of
+ // hashes stored is trusted and thus this value can safely be seeded. This
+ // happens when all hashes are already properly seeded and a newly
+ // tracked value needs to be seeded).
+ TRUSTED_UNKNOWN_VALUE,
+ // Null values are inherently trusted.
+ TRUSTED_NULL_VALUE,
+ // This transaction's store type is not supported.
+ UNSUPPORTED,
+ };
+
+ // Notifies observes of the result (|value_state|) of checking the atomic
+ // |value| (which may be null) at |pref_path|. |is_personal| indicates whether
+ // or not the value may contain personal information.
+ OnAtomicPreferenceValidation(
+ string pref_path,
+ mojo.common.mojom.Value? value,
+ ValueState value_state,
+ ValueState external_validation_value_state,
+ bool is_personal);
+
+ // Notifies observes of the result (|value_state|) of checking the split
+ // value at |pref_path|. |is_personal| indicates whether or not the value may
+ // contain personal information.
+ OnSplitPreferenceValidation(
+ string pref_path,
+ array<string> invalid_keys,
+ array<string> external_validation_invalid_keys,
+ ValueState value_state,
+ ValueState external_validation_value_state,
+ bool is_personal);
+};
diff --git a/chromium/services/preferences/tracked/BUILD.gn b/chromium/services/preferences/tracked/BUILD.gn
new file mode 100644
index 00000000000..16be8a2d696
--- /dev/null
+++ b/chromium/services/preferences/tracked/BUILD.gn
@@ -0,0 +1,86 @@
+# 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.
+
+source_set("tracked") {
+ sources = [
+ "device_id.h",
+ "device_id_mac.cc",
+ "device_id_stub.cc",
+ "device_id_win.cc",
+ "dictionary_hash_store_contents.cc",
+ "dictionary_hash_store_contents.h",
+ "hash_store_contents.h",
+ "interceptable_pref_filter.cc",
+ "interceptable_pref_filter.h",
+ "pref_hash_calculator.cc",
+ "pref_hash_calculator.h",
+ "pref_hash_filter.cc",
+ "pref_hash_filter.h",
+ "pref_hash_store.h",
+ "pref_hash_store_impl.cc",
+ "pref_hash_store_impl.h",
+ "pref_hash_store_transaction.h",
+ "registry_hash_store_contents_win.cc",
+ "registry_hash_store_contents_win.h",
+ "segregated_pref_store.cc",
+ "segregated_pref_store.h",
+ "tracked_atomic_preference.cc",
+ "tracked_atomic_preference.h",
+ "tracked_persistent_pref_store_factory.cc",
+ "tracked_persistent_pref_store_factory.h",
+ "tracked_preference.h",
+ "tracked_preference_helper.cc",
+ "tracked_preference_helper.h",
+ "tracked_preferences_migration.cc",
+ "tracked_preferences_migration.h",
+ "tracked_split_preference.cc",
+ "tracked_split_preference.h",
+ ]
+
+ if (is_win || is_mac) {
+ sources -= [ "device_id_stub.cc" ]
+ }
+
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ configs += [ "//build/config/compiler:no_size_t_to_int_warning" ]
+
+ public_deps = [
+ "//services/preferences/public/cpp/tracked",
+ "//services/preferences/public/interfaces",
+ ]
+
+ deps = [
+ "//base",
+ "//components/pref_registry",
+ "//components/prefs",
+ "//crypto",
+ ]
+
+ if (is_mac) {
+ libs = [ "IOKit.framework" ]
+ }
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "device_id_unittest.cc",
+ "interceptable_pref_filter_unittest.cc",
+ "pref_hash_calculator_unittest.cc",
+ "pref_hash_filter_unittest.cc",
+ "pref_hash_store_impl_unittest.cc",
+ "registry_hash_store_contents_win_unittest.cc",
+ "segregated_pref_store_unittest.cc",
+ "tracked_preferences_migration_unittest.cc",
+ ]
+
+ deps = [
+ ":tracked",
+ "//base",
+ "//base/test:test_support",
+ "//components/prefs:test_support",
+ "//services/preferences/public/cpp/tracked:test_support",
+ "//testing/gtest",
+ ]
+}
diff --git a/chromium/services/preferences/tracked/DEPS b/chromium/services/preferences/tracked/DEPS
new file mode 100644
index 00000000000..6ccb05e9fab
--- /dev/null
+++ b/chromium/services/preferences/tracked/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+components/pref_registry",
+ "+crypto/hmac.h",
+]
diff --git a/chromium/services/preferences/tracked/OWNERS b/chromium/services/preferences/tracked/OWNERS
new file mode 100644
index 00000000000..2845b0f366d
--- /dev/null
+++ b/chromium/services/preferences/tracked/OWNERS
@@ -0,0 +1,2 @@
+bauerb@chromium.org
+gab@chromium.org
diff --git a/chromium/services/preferences/tracked/device_id.h b/chromium/services/preferences/tracked/device_id.h
new file mode 100644
index 00000000000..04355628c7d
--- /dev/null
+++ b/chromium/services/preferences/tracked/device_id.h
@@ -0,0 +1,23 @@
+// 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 SERVICES_PREFERENCES_TRACKED_DEVICE_ID_H_
+#define SERVICES_PREFERENCES_TRACKED_DEVICE_ID_H_
+
+#include <string>
+
+enum class MachineIdStatus {
+ SUCCESS = 0,
+ FAILURE, // Returned if attempt to obtain a machine-specific ID fails.
+ NOT_IMPLEMENTED // Returned if the method for obtaining a machine-specific ID
+ // is not implemented for the system.
+};
+
+// Populates |machine_id| with a deterministic ID for this machine. |machine_id|
+// must not be null. Returns |FAILURE| if a machine ID cannot be obtained or
+// |NOT_IMPLEMENTED| on systems for which this feature is not supported (in both
+// cases |machine_id| is left untouched).
+MachineIdStatus GetDeterministicMachineSpecificId(std::string* machine_id);
+
+#endif // SERVICES_PREFERENCES_TRACKED_DEVICE_ID_H_
diff --git a/chromium/services/preferences/tracked/device_id_mac.cc b/chromium/services/preferences/tracked/device_id_mac.cc
new file mode 100644
index 00000000000..38dfb340c7e
--- /dev/null
+++ b/chromium/services/preferences/tracked/device_id_mac.cc
@@ -0,0 +1,32 @@
+// 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.
+
+#include "services/preferences/tracked/device_id.h"
+
+#include <IOKit/IOKitLib.h>
+
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/mac/scoped_ioobject.h"
+#include "base/strings/sys_string_conversions.h"
+
+MachineIdStatus GetDeterministicMachineSpecificId(std::string* machine_id) {
+ base::mac::ScopedIOObject<io_service_t> platform_expert(
+ IOServiceGetMatchingService(kIOMasterPortDefault,
+ IOServiceMatching("IOPlatformExpertDevice")));
+ if (!platform_expert.get())
+ return MachineIdStatus::FAILURE;
+
+ base::ScopedCFTypeRef<CFTypeRef> uuid(IORegistryEntryCreateCFProperty(
+ platform_expert, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0));
+ if (!uuid.get())
+ return MachineIdStatus::FAILURE;
+
+ CFStringRef uuid_string = base::mac::CFCast<CFStringRef>(uuid);
+ if (!uuid_string)
+ return MachineIdStatus::FAILURE;
+
+ *machine_id = base::SysCFStringRefToUTF8(uuid_string);
+ return MachineIdStatus::SUCCESS;
+}
diff --git a/chromium/services/preferences/tracked/device_id_stub.cc b/chromium/services/preferences/tracked/device_id_stub.cc
new file mode 100644
index 00000000000..9db4d503863
--- /dev/null
+++ b/chromium/services/preferences/tracked/device_id_stub.cc
@@ -0,0 +1,11 @@
+// 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.
+
+#include "base/logging.h"
+#include "services/preferences/tracked/device_id.h"
+
+MachineIdStatus GetDeterministicMachineSpecificId(std::string* machine_id) {
+ DCHECK(machine_id);
+ return MachineIdStatus::NOT_IMPLEMENTED;
+}
diff --git a/chromium/services/preferences/tracked/device_id_unittest.cc b/chromium/services/preferences/tracked/device_id_unittest.cc
new file mode 100644
index 00000000000..297cf42e618
--- /dev/null
+++ b/chromium/services/preferences/tracked/device_id_unittest.cc
@@ -0,0 +1,35 @@
+// 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.
+
+#include "services/preferences/tracked/device_id.h"
+
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(GetDeterministicMachineSpecificIdTest, IsDeterministic) {
+ std::string first_machine_id;
+ std::string second_machine_id;
+
+ const MachineIdStatus kExpectedStatus =
+#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
+ MachineIdStatus::SUCCESS;
+#else
+ MachineIdStatus::NOT_IMPLEMENTED;
+#endif
+
+ ASSERT_EQ(kExpectedStatus,
+ GetDeterministicMachineSpecificId(&first_machine_id));
+ ASSERT_EQ(kExpectedStatus,
+ GetDeterministicMachineSpecificId(&second_machine_id));
+
+ // The reason for using |EXPECT_TRUE| with one argument instead of |EXPECT_EQ|
+ // with two arguments is a compiler bug in gcc that results in a "converting
+ // 'false' to pointer type" error when the first argument to |EXPECT_EQ| is a
+ // compile-time const false value. See also the following bug reports:
+ // https://code.google.com/p/googletest/issues/detail?id=322
+ // https://code.google.com/p/googletest/issues/detail?id=458
+ EXPECT_TRUE((kExpectedStatus == MachineIdStatus::SUCCESS) ==
+ !first_machine_id.empty());
+ EXPECT_EQ(first_machine_id, second_machine_id);
+}
diff --git a/chromium/services/preferences/tracked/device_id_win.cc b/chromium/services/preferences/tracked/device_id_win.cc
new file mode 100644
index 00000000000..9dbd110674a
--- /dev/null
+++ b/chromium/services/preferences/tracked/device_id_win.cc
@@ -0,0 +1,73 @@
+// 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.
+
+#include "services/preferences/tracked/device_id.h"
+
+#include <windows.h>
+
+#include <sddl.h> // For ConvertSidToStringSidA.
+
+#include <memory>
+
+#include "base/logging.h"
+#include "base/macros.h"
+
+MachineIdStatus GetDeterministicMachineSpecificId(std::string* machine_id) {
+ DCHECK(machine_id);
+
+ wchar_t computer_name[MAX_COMPUTERNAME_LENGTH + 1] = {};
+ DWORD computer_name_size = arraysize(computer_name);
+
+ if (!::GetComputerNameW(computer_name, &computer_name_size))
+ return MachineIdStatus::FAILURE;
+
+ DWORD sid_size = SECURITY_MAX_SID_SIZE;
+ char sid_buffer[SECURITY_MAX_SID_SIZE];
+ SID* sid = reinterpret_cast<SID*>(sid_buffer);
+ DWORD domain_size = 128; // Will expand below if needed.
+ std::unique_ptr<wchar_t[]> domain_buffer(new wchar_t[domain_size]);
+ SID_NAME_USE sid_name_use;
+
+ // Although the fifth argument to |LookupAccountNameW()|,
+ // |ReferencedDomainName|, is annotated as |_Out_opt_|, if a null
+ // value is passed in, zero is returned and |GetLastError()| will
+ // return |ERROR_INSUFFICIENT_BUFFER| (assuming that nothing else went
+ // wrong). In order to ensure that the call to |LookupAccountNameW()|
+ // has succeeded, it is necessary to include the following logic and
+ // obtain the domain name.
+ if (!::LookupAccountNameW(nullptr, computer_name, sid, &sid_size,
+ domain_buffer.get(), &domain_size, &sid_name_use)) {
+ // If the initial size of |domain_buffer| was too small, the
+ // required size is now found in |domain_size|. Resize and try
+ // again.
+ if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+ return MachineIdStatus::FAILURE;
+
+ domain_buffer.reset(new wchar_t[domain_size]);
+ if (!::LookupAccountNameW(nullptr, computer_name, sid, &sid_size,
+ domain_buffer.get(), &domain_size,
+ &sid_name_use)) {
+ return MachineIdStatus::FAILURE;
+ }
+ }
+
+ // Ensure that the correct type of SID was obtained. The
+ // |LookupAccountNameW()| function seems to always return
+ // |SidTypeDomain| instead of |SidTypeComputer| when the computer name
+ // is passed in as its second argument and therefore both enum values
+ // will be considered acceptable. If the computer name and user name
+ // coincide, |LookupAccountNameW()| seems to always return the machine
+ // SID and set the returned enum to |SidTypeDomain|.
+ DCHECK(sid_name_use == SID_NAME_USE::SidTypeComputer ||
+ sid_name_use == SID_NAME_USE::SidTypeDomain);
+
+ char* sid_string = nullptr;
+ if (!::ConvertSidToStringSidA(sid, &sid_string))
+ return MachineIdStatus::FAILURE;
+
+ *machine_id = sid_string;
+ ::LocalFree(sid_string);
+
+ return MachineIdStatus::SUCCESS;
+}
diff --git a/chromium/services/preferences/tracked/dictionary_hash_store_contents.cc b/chromium/services/preferences/tracked/dictionary_hash_store_contents.cc
new file mode 100644
index 00000000000..3cef69b05a1
--- /dev/null
+++ b/chromium/services/preferences/tracked/dictionary_hash_store_contents.cc
@@ -0,0 +1,133 @@
+// Copyright (c) 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 "services/preferences/tracked/dictionary_hash_store_contents.h"
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/values.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/persistent_pref_store.h"
+
+namespace {
+const char kPreferenceMACs[] = "protection.macs";
+const char kSuperMACPref[] = "protection.super_mac";
+}
+
+DictionaryHashStoreContents::DictionaryHashStoreContents(
+ base::DictionaryValue* storage)
+ : storage_(storage) {}
+
+// static
+void DictionaryHashStoreContents::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) {
+ registry->RegisterDictionaryPref(kPreferenceMACs);
+ registry->RegisterStringPref(kSuperMACPref, std::string());
+}
+
+bool DictionaryHashStoreContents::IsCopyable() const {
+ return false;
+}
+
+std::unique_ptr<HashStoreContents> DictionaryHashStoreContents::MakeCopy()
+ const {
+ NOTREACHED() << "DictionaryHashStoreContents does not support MakeCopy";
+ return nullptr;
+}
+
+base::StringPiece DictionaryHashStoreContents::GetUMASuffix() const {
+ // To stay consistent with existing reported data, do not append a suffix
+ // when reporting UMA stats for this content.
+ return base::StringPiece();
+}
+
+void DictionaryHashStoreContents::Reset() {
+ storage_->Remove(kPreferenceMACs, NULL);
+}
+
+bool DictionaryHashStoreContents::GetMac(const std::string& path,
+ std::string* out_value) {
+ const base::DictionaryValue* macs_dict = GetContents();
+ if (macs_dict)
+ return macs_dict->GetString(path, out_value);
+
+ return false;
+}
+
+bool DictionaryHashStoreContents::GetSplitMacs(
+ const std::string& path,
+ std::map<std::string, std::string>* split_macs) {
+ DCHECK(split_macs);
+ DCHECK(split_macs->empty());
+
+ const base::DictionaryValue* macs_dict = GetContents();
+ const base::DictionaryValue* split_macs_dict = NULL;
+ if (!macs_dict || !macs_dict->GetDictionary(path, &split_macs_dict))
+ return false;
+ for (base::DictionaryValue::Iterator it(*split_macs_dict); !it.IsAtEnd();
+ it.Advance()) {
+ std::string mac_string;
+ if (!it.value().GetAsString(&mac_string)) {
+ NOTREACHED();
+ continue;
+ }
+ split_macs->insert(make_pair(it.key(), mac_string));
+ }
+ return true;
+}
+
+void DictionaryHashStoreContents::SetMac(const std::string& path,
+ const std::string& value) {
+ base::DictionaryValue* macs_dict = GetMutableContents(true);
+ macs_dict->SetString(path, value);
+}
+
+void DictionaryHashStoreContents::SetSplitMac(const std::string& path,
+ const std::string& split_path,
+ const std::string& value) {
+ // DictionaryValue handles a '.' delimiter.
+ SetMac(path + '.' + split_path, value);
+}
+
+void DictionaryHashStoreContents::ImportEntry(const std::string& path,
+ const base::Value* in_value) {
+ base::DictionaryValue* macs_dict = GetMutableContents(true);
+ macs_dict->Set(path, in_value->DeepCopy());
+}
+
+bool DictionaryHashStoreContents::RemoveEntry(const std::string& path) {
+ base::DictionaryValue* macs_dict = GetMutableContents(false);
+ if (macs_dict)
+ return macs_dict->RemovePath(path, NULL);
+
+ return false;
+}
+
+std::string DictionaryHashStoreContents::GetSuperMac() const {
+ std::string super_mac_string;
+ storage_->GetString(kSuperMACPref, &super_mac_string);
+ return super_mac_string;
+}
+
+void DictionaryHashStoreContents::SetSuperMac(const std::string& super_mac) {
+ storage_->SetString(kSuperMACPref, super_mac);
+}
+
+const base::DictionaryValue* DictionaryHashStoreContents::GetContents() const {
+ const base::DictionaryValue* macs_dict = NULL;
+ storage_->GetDictionary(kPreferenceMACs, &macs_dict);
+ return macs_dict;
+}
+
+base::DictionaryValue* DictionaryHashStoreContents::GetMutableContents(
+ bool create_if_null) {
+ base::DictionaryValue* macs_dict = NULL;
+ storage_->GetDictionary(kPreferenceMACs, &macs_dict);
+ if (!macs_dict && create_if_null) {
+ macs_dict = new base::DictionaryValue;
+ storage_->Set(kPreferenceMACs, macs_dict);
+ }
+ return macs_dict;
+}
diff --git a/chromium/services/preferences/tracked/dictionary_hash_store_contents.h b/chromium/services/preferences/tracked/dictionary_hash_store_contents.h
new file mode 100644
index 00000000000..16fe628ad81
--- /dev/null
+++ b/chromium/services/preferences/tracked/dictionary_hash_store_contents.h
@@ -0,0 +1,62 @@
+// 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.
+
+#ifndef SERVICES_PREFERENCES_TRACKED_DICTIONARY_HASH_STORE_CONTENTS_H_
+#define SERVICES_PREFERENCES_TRACKED_DICTIONARY_HASH_STORE_CONTENTS_H_
+
+#include "base/macros.h"
+#include "services/preferences/tracked/hash_store_contents.h"
+
+namespace base {
+class DictionaryValue;
+class Value;
+} // namespace base
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+} // namespace user_prefs
+
+// Implements HashStoreContents by storing MACs in a DictionaryValue. The
+// DictionaryValue is presumed to be the contents of a PrefStore.
+// RegisterProfilePrefs() may be used to register all of the preferences used by
+// this object.
+class DictionaryHashStoreContents : public HashStoreContents {
+ public:
+ // Constructs a DictionaryHashStoreContents that reads from and writes to
+ // |storage|.
+ explicit DictionaryHashStoreContents(base::DictionaryValue* storage);
+
+ // Registers required preferences.
+ static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+ // HashStoreContents implementation
+ bool IsCopyable() const override;
+ std::unique_ptr<HashStoreContents> MakeCopy() const override;
+ base::StringPiece GetUMASuffix() const override;
+ void Reset() override;
+ bool GetMac(const std::string& path, std::string* out_value) override;
+ bool GetSplitMacs(const std::string& path,
+ std::map<std::string, std::string>* split_macs) override;
+ void SetMac(const std::string& path, const std::string& value) override;
+ void SetSplitMac(const std::string& path,
+ const std::string& split_path,
+ const std::string& value) override;
+ void ImportEntry(const std::string& path,
+ const base::Value* in_value) override;
+ bool RemoveEntry(const std::string& path) override;
+ const base::DictionaryValue* GetContents() const override;
+ std::string GetSuperMac() const override;
+ void SetSuperMac(const std::string& super_mac) override;
+
+ private:
+ base::DictionaryValue* storage_;
+
+ // Helper function to get a mutable version of the macs from |storage_|,
+ // creating it if needed and |create_if_null| is true.
+ base::DictionaryValue* GetMutableContents(bool create_if_null);
+
+ DISALLOW_COPY_AND_ASSIGN(DictionaryHashStoreContents);
+};
+
+#endif // SERVICES_PREFERENCES_TRACKED_DICTIONARY_HASH_STORE_CONTENTS_H_
diff --git a/chromium/services/preferences/tracked/hash_store_contents.h b/chromium/services/preferences/tracked/hash_store_contents.h
new file mode 100644
index 00000000000..5486514bf23
--- /dev/null
+++ b/chromium/services/preferences/tracked/hash_store_contents.h
@@ -0,0 +1,90 @@
+// 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.
+
+#ifndef SERVICES_PREFERENCES_TRACKED_HASH_STORE_CONTENTS_H_
+#define SERVICES_PREFERENCES_TRACKED_HASH_STORE_CONTENTS_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/strings/string_piece.h"
+
+namespace base {
+class DictionaryValue;
+class Value;
+} // namespace base
+
+// Provides access to the contents of a preference hash store. The store
+// contains the following data:
+// Contents: a client-defined dictionary that should map preference names to
+// MACs.
+// Version: a client-defined version number for the format of Contents.
+// Super MAC: a MAC that authenticates the entirety of Contents.
+class HashStoreContents {
+ public:
+ virtual ~HashStoreContents() {}
+
+ // Returns true if this implementation of HashStoreContents can be copied via
+ // MakeCopy().
+ virtual bool IsCopyable() const = 0;
+
+ // Returns a copy of this HashStoreContents. Must only be called on
+ // lightweight implementations (which return true from IsCopyable()) and only
+ // in scenarios where a copy cannot be avoided.
+ virtual std::unique_ptr<HashStoreContents> MakeCopy() const = 0;
+
+ // Returns the suffix to be appended to UMA histograms for this store type.
+ // The returned value must either be an empty string or one of the values in
+ // histograms.xml's TrackedPreferencesExternalValidators.
+ virtual base::StringPiece GetUMASuffix() const = 0;
+
+ // Discards all data related to this hash store.
+ virtual void Reset() = 0;
+
+ // Outputs the MAC validating the preference at path. Returns true if a MAC
+ // was successfully read and false otherwise.
+ virtual bool GetMac(const std::string& path, std::string* out_value) = 0;
+
+ // Outputs the MACS validating the split preference at path. Returns true if
+ // MACS were successfully read and false otherwise.
+ virtual bool GetSplitMacs(const std::string& path,
+ std::map<std::string, std::string>* out_value) = 0;
+
+ // Set the MAC validating the preference at path.
+ virtual void SetMac(const std::string& path, const std::string& value) = 0;
+
+ // Set the MAC validating the split preference at path and split_path.
+ // For example, |path| is 'extension' and |split_path| is some extenson id.
+ virtual void SetSplitMac(const std::string& path,
+ const std::string& split_path,
+ const std::string& value) = 0;
+
+ // Sets the MAC for the preference at |path|.
+ // If |path| is a split preference |in_value| must be a DictionaryValue whose
+ // keys are keys in the split preference and whose values are MACs of the
+ // corresponding values in the split preference.
+ // If |path| is an atomic preference |in_value| must be a StringValue
+ // containing a MAC of the preference value.
+ virtual void ImportEntry(const std::string& path,
+ const base::Value* in_value) = 0;
+
+ // Removes the MAC (for atomic preferences) or MACs (for split preferences)
+ // at |path|. Returns true if there was an entry at |path| which was
+ // successfully removed.
+ virtual bool RemoveEntry(const std::string& path) = 0;
+
+ // Only needed if this store supports super MACs.
+ virtual const base::DictionaryValue* GetContents() const = 0;
+
+ // Retrieves the super MAC value previously stored by SetSuperMac. May be
+ // empty if no super MAC has been stored or if this store does not support
+ // super MACs.
+ virtual std::string GetSuperMac() const = 0;
+
+ // Stores a super MAC value for this hash store.
+ virtual void SetSuperMac(const std::string& super_mac) = 0;
+};
+
+#endif // SERVICES_PREFERENCES_TRACKED_HASH_STORE_CONTENTS_H_
diff --git a/chromium/services/preferences/tracked/interceptable_pref_filter.cc b/chromium/services/preferences/tracked/interceptable_pref_filter.cc
new file mode 100644
index 00000000000..81f2783558e
--- /dev/null
+++ b/chromium/services/preferences/tracked/interceptable_pref_filter.cc
@@ -0,0 +1,39 @@
+// 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 "services/preferences/tracked/interceptable_pref_filter.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+
+InterceptablePrefFilter::InterceptablePrefFilter() {}
+InterceptablePrefFilter::~InterceptablePrefFilter() {}
+
+void InterceptablePrefFilter::FilterOnLoad(
+ const PostFilterOnLoadCallback& post_filter_on_load_callback,
+ std::unique_ptr<base::DictionaryValue> pref_store_contents) {
+ if (filter_on_load_interceptor_.is_null()) {
+ FinalizeFilterOnLoad(post_filter_on_load_callback,
+ std::move(pref_store_contents), false);
+ } else {
+ // Note, in practice (in the implementation as it was in May 2014) it would
+ // be okay to pass an unretained |this| pointer below, but in order to avoid
+ // having to augment the API everywhere to explicitly enforce the ownership
+ // model as it happens to currently be: make the relationship simpler by
+ // weakly binding the FinalizeFilterOnLoadCallback below to |this|.
+ const FinalizeFilterOnLoadCallback finalize_filter_on_load(
+ base::Bind(&InterceptablePrefFilter::FinalizeFilterOnLoad, AsWeakPtr(),
+ post_filter_on_load_callback));
+ base::ResetAndReturn(&filter_on_load_interceptor_)
+ .Run(finalize_filter_on_load, std::move(pref_store_contents));
+ }
+}
+
+void InterceptablePrefFilter::InterceptNextFilterOnLoad(
+ const FilterOnLoadInterceptor& filter_on_load_interceptor) {
+ DCHECK(filter_on_load_interceptor_.is_null());
+ filter_on_load_interceptor_ = filter_on_load_interceptor;
+}
diff --git a/chromium/services/preferences/tracked/interceptable_pref_filter.h b/chromium/services/preferences/tracked/interceptable_pref_filter.h
new file mode 100644
index 00000000000..7971c8da8f1
--- /dev/null
+++ b/chromium/services/preferences/tracked/interceptable_pref_filter.h
@@ -0,0 +1,68 @@
+// 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.
+
+#ifndef SERVICES_PREFERENCES_TRACKED_INTERCEPTABLE_PREF_FILTER_H_
+#define SERVICES_PREFERENCES_TRACKED_INTERCEPTABLE_PREF_FILTER_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "components/prefs/pref_filter.h"
+
+// A partial implementation of a PrefFilter whose FilterOnLoad call may be
+// intercepted by a FilterOnLoadInterceptor. Implementations of
+// InterceptablePrefFilter are expected to override FinalizeFilterOnLoad rather
+// than re-overriding FilterOnLoad.
+class InterceptablePrefFilter
+ : public PrefFilter,
+ public base::SupportsWeakPtr<InterceptablePrefFilter> {
+ public:
+ // A callback to be invoked by a FilterOnLoadInterceptor when its ready to
+ // hand back the |prefs| it was handed for early filtering. |prefs_altered|
+ // indicates whether the |prefs| were actually altered by the
+ // FilterOnLoadInterceptor before being handed back.
+ typedef base::Callback<void(std::unique_ptr<base::DictionaryValue> prefs,
+ bool prefs_altered)>
+ FinalizeFilterOnLoadCallback;
+
+ // A callback to be invoked from FilterOnLoad. It takes ownership of prefs
+ // and may modify them before handing them back to this
+ // InterceptablePrefFilter via |finalize_filter_on_load|.
+ typedef base::Callback<void(
+ const FinalizeFilterOnLoadCallback& finalize_filter_on_load,
+ std::unique_ptr<base::DictionaryValue> prefs)>
+ FilterOnLoadInterceptor;
+
+ InterceptablePrefFilter();
+ ~InterceptablePrefFilter() override;
+
+ // PrefFilter partial implementation.
+ void FilterOnLoad(
+ const PostFilterOnLoadCallback& post_filter_on_load_callback,
+ std::unique_ptr<base::DictionaryValue> pref_store_contents) override;
+
+ // Registers |filter_on_load_interceptor| to intercept the next FilterOnLoad
+ // event. At most one FilterOnLoadInterceptor should be registered per
+ // PrefFilter.
+ void InterceptNextFilterOnLoad(
+ const FilterOnLoadInterceptor& filter_on_load_interceptor);
+
+ private:
+ // Does any extra filtering required by the implementation of this
+ // InterceptablePrefFilter and hands back the |pref_store_contents| to the
+ // initial caller of FilterOnLoad.
+ virtual void FinalizeFilterOnLoad(
+ const PostFilterOnLoadCallback& post_filter_on_load_callback,
+ std::unique_ptr<base::DictionaryValue> pref_store_contents,
+ bool prefs_altered) = 0;
+
+ // Callback to be invoked only once (and subsequently reset) on the next
+ // FilterOnLoad event. It will be allowed to modify the |prefs| handed to
+ // FilterOnLoad before handing them back to this PrefHashFilter.
+ FilterOnLoadInterceptor filter_on_load_interceptor_;
+};
+
+#endif // SERVICES_PREFERENCES_TRACKED_INTERCEPTABLE_PREF_FILTER_H_
diff --git a/chromium/services/preferences/tracked/interceptable_pref_filter_unittest.cc b/chromium/services/preferences/tracked/interceptable_pref_filter_unittest.cc
new file mode 100644
index 00000000000..cdaaadb37d4
--- /dev/null
+++ b/chromium/services/preferences/tracked/interceptable_pref_filter_unittest.cc
@@ -0,0 +1,55 @@
+// Copyright 2017 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 "services/preferences/tracked/interceptable_pref_filter.h"
+
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class TestInterceptablePrefFilter : public InterceptablePrefFilter {
+ public:
+ void FinalizeFilterOnLoad(
+ const PostFilterOnLoadCallback& post_filter_on_load_callback,
+ std::unique_ptr<base::DictionaryValue> pref_store_contents,
+ bool prefs_altered) override {
+ post_filter_on_load_callback.Run(std::move(pref_store_contents),
+ prefs_altered);
+ }
+
+ void FilterUpdate(const std::string& path) override {}
+
+ OnWriteCallbackPair FilterSerializeData(
+ base::DictionaryValue* pref_store_contents) override {
+ return {};
+ }
+};
+
+void NoOpIntercept(const InterceptablePrefFilter::FinalizeFilterOnLoadCallback&
+ finalize_filter_on_load,
+ std::unique_ptr<base::DictionaryValue> prefs) {
+ finalize_filter_on_load.Run(std::move(prefs), false);
+}
+
+void DeleteFilter(std::unique_ptr<TestInterceptablePrefFilter>* filter,
+ std::unique_ptr<base::DictionaryValue> prefs,
+ bool schedule_write) {
+ filter->reset();
+}
+
+TEST(InterceptablePrefFilterTest, CallbackDeletes) {
+ auto filter = base::MakeUnique<TestInterceptablePrefFilter>();
+ filter->InterceptNextFilterOnLoad(base::Bind(&NoOpIntercept));
+ filter->FilterOnLoad(base::Bind(&DeleteFilter, &filter),
+ base::MakeUnique<base::DictionaryValue>());
+ EXPECT_FALSE(filter);
+}
+
+} // namespace
diff --git a/chromium/services/preferences/tracked/pref_hash_calculator.cc b/chromium/services/preferences/tracked/pref_hash_calculator.cc
new file mode 100644
index 00000000000..207221c5688
--- /dev/null
+++ b/chromium/services/preferences/tracked/pref_hash_calculator.cc
@@ -0,0 +1,112 @@
+// 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 "services/preferences/tracked/pref_hash_calculator.h"
+
+#include <stdint.h>
+
+#include <memory>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "crypto/hmac.h"
+
+namespace {
+
+// Calculates an HMAC of |message| using |key|, encoded as a hexadecimal string.
+std::string GetDigestString(const std::string& key,
+ const std::string& message) {
+ crypto::HMAC hmac(crypto::HMAC::SHA256);
+ std::vector<uint8_t> digest(hmac.DigestLength());
+ if (!hmac.Init(key) || !hmac.Sign(message, &digest[0], digest.size())) {
+ NOTREACHED();
+ return std::string();
+ }
+ return base::HexEncode(&digest[0], digest.size());
+}
+
+// Verifies that |digest_string| is a valid HMAC of |message| using |key|.
+// |digest_string| must be encoded as a hexadecimal string.
+bool VerifyDigestString(const std::string& key,
+ const std::string& message,
+ const std::string& digest_string) {
+ crypto::HMAC hmac(crypto::HMAC::SHA256);
+ std::vector<uint8_t> digest;
+ return base::HexStringToBytes(digest_string, &digest) && hmac.Init(key) &&
+ hmac.Verify(message,
+ base::StringPiece(reinterpret_cast<char*>(&digest[0]),
+ digest.size()));
+}
+
+// Renders |value| as a string. |value| may be NULL, in which case the result
+// is an empty string. This method can be expensive and its result should be
+// re-used rather than recomputed where possible.
+std::string ValueAsString(const base::Value* value) {
+ // Dictionary values may contain empty lists and sub-dictionaries. Make a
+ // deep copy with those removed to make the hash more stable.
+ const base::DictionaryValue* dict_value;
+ std::unique_ptr<base::DictionaryValue> canonical_dict_value;
+ if (value && value->GetAsDictionary(&dict_value)) {
+ canonical_dict_value = dict_value->DeepCopyWithoutEmptyChildren();
+ value = canonical_dict_value.get();
+ }
+
+ std::string value_as_string;
+ if (value) {
+ JSONStringValueSerializer serializer(&value_as_string);
+ serializer.Serialize(*value);
+ }
+
+ return value_as_string;
+}
+
+// Concatenates |device_id|, |path|, and |value_as_string| to give the hash
+// input.
+std::string GetMessage(const std::string& device_id,
+ const std::string& path,
+ const std::string& value_as_string) {
+ std::string message;
+ message.reserve(device_id.size() + path.size() + value_as_string.size());
+ message.append(device_id);
+ message.append(path);
+ message.append(value_as_string);
+ return message;
+}
+
+} // namespace
+
+PrefHashCalculator::PrefHashCalculator(const std::string& seed,
+ const std::string& device_id,
+ const std::string& legacy_device_id)
+ : seed_(seed), device_id_(device_id), legacy_device_id_(legacy_device_id) {}
+
+PrefHashCalculator::~PrefHashCalculator() {}
+
+std::string PrefHashCalculator::Calculate(const std::string& path,
+ const base::Value* value) const {
+ return GetDigestString(seed_,
+ GetMessage(device_id_, path, ValueAsString(value)));
+}
+
+PrefHashCalculator::ValidationResult PrefHashCalculator::Validate(
+ const std::string& path,
+ const base::Value* value,
+ const std::string& digest_string) const {
+ const std::string value_as_string(ValueAsString(value));
+ if (VerifyDigestString(seed_, GetMessage(device_id_, path, value_as_string),
+ digest_string)) {
+ return VALID;
+ }
+ if (VerifyDigestString(seed_,
+ GetMessage(legacy_device_id_, path, value_as_string),
+ digest_string)) {
+ return VALID_SECURE_LEGACY;
+ }
+ return INVALID;
+}
diff --git a/chromium/services/preferences/tracked/pref_hash_calculator.h b/chromium/services/preferences/tracked/pref_hash_calculator.h
new file mode 100644
index 00000000000..1fc14a180aa
--- /dev/null
+++ b/chromium/services/preferences/tracked/pref_hash_calculator.h
@@ -0,0 +1,55 @@
+// 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 SERVICES_PREFERENCES_TRACKED_PREF_HASH_CALCULATOR_H_
+#define SERVICES_PREFERENCES_TRACKED_PREF_HASH_CALCULATOR_H_
+
+#include <string>
+
+#include "base/macros.h"
+
+namespace base {
+class Value;
+} // namespace base
+
+// Calculates and validates preference value hashes.
+class PrefHashCalculator {
+ public:
+ enum ValidationResult {
+ INVALID,
+ VALID,
+ // Valid under a deprecated but as secure algorithm.
+ VALID_SECURE_LEGACY,
+ };
+
+ // Constructs a PrefHashCalculator using |seed|, |device_id| and
+ // |legacy_device_id|. The same parameters must be used in order to
+ // successfully validate generated hashes. |_device_id| or |legacy_device_id|
+ // may be empty.
+ PrefHashCalculator(const std::string& seed,
+ const std::string& device_id,
+ const std::string& legacy_device_id);
+
+ ~PrefHashCalculator();
+
+ // Calculates a hash value for the supplied preference |path| and |value|.
+ // |value| may be null if the preference has no value.
+ std::string Calculate(const std::string& path,
+ const base::Value* value) const;
+
+ // Validates the provided preference hash using current and legacy hashing
+ // algorithms.
+ ValidationResult Validate(const std::string& path,
+ const base::Value* value,
+ const std::string& hash) const;
+
+ private:
+ const std::string seed_;
+ const std::string device_id_;
+ const std::string legacy_device_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrefHashCalculator);
+};
+
+#endif // SERVICES_PREFERENCES_TRACKED_PREF_HASH_CALCULATOR_H_
diff --git a/chromium/services/preferences/tracked/pref_hash_calculator_unittest.cc b/chromium/services/preferences/tracked/pref_hash_calculator_unittest.cc
new file mode 100644
index 00000000000..df3746a09e0
--- /dev/null
+++ b/chromium/services/preferences/tracked/pref_hash_calculator_unittest.cc
@@ -0,0 +1,196 @@
+// 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 "services/preferences/tracked/pref_hash_calculator.h"
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(PrefHashCalculatorTest, TestCurrentAlgorithm) {
+ base::Value string_value_1("string value 1");
+ base::Value string_value_2("string value 2");
+ base::DictionaryValue dictionary_value_1;
+ dictionary_value_1.SetInteger("int value", 1);
+ dictionary_value_1.Set("nested empty map", new base::DictionaryValue);
+ base::DictionaryValue dictionary_value_1_equivalent;
+ dictionary_value_1_equivalent.SetInteger("int value", 1);
+ base::DictionaryValue dictionary_value_2;
+ dictionary_value_2.SetInteger("int value", 2);
+
+ PrefHashCalculator calc1("seed1", "deviceid", "legacydeviceid");
+ PrefHashCalculator calc1_dup("seed1", "deviceid", "legacydeviceid");
+ PrefHashCalculator calc2("seed2", "deviceid", "legacydeviceid");
+ PrefHashCalculator calc3("seed1", "deviceid2", "legacydeviceid");
+
+ // Two calculators with same seed produce same hash.
+ ASSERT_EQ(calc1.Calculate("pref_path", &string_value_1),
+ calc1_dup.Calculate("pref_path", &string_value_1));
+ ASSERT_EQ(PrefHashCalculator::VALID,
+ calc1_dup.Validate("pref_path", &string_value_1,
+ calc1.Calculate("pref_path", &string_value_1)));
+
+ // Different seeds, different hashes.
+ ASSERT_NE(calc1.Calculate("pref_path", &string_value_1),
+ calc2.Calculate("pref_path", &string_value_1));
+ ASSERT_EQ(PrefHashCalculator::INVALID,
+ calc2.Validate("pref_path", &string_value_1,
+ calc1.Calculate("pref_path", &string_value_1)));
+
+ // Different device IDs, different hashes.
+ ASSERT_NE(calc1.Calculate("pref_path", &string_value_1),
+ calc3.Calculate("pref_path", &string_value_1));
+
+ // Different values, different hashes.
+ ASSERT_NE(calc1.Calculate("pref_path", &string_value_1),
+ calc1.Calculate("pref_path", &string_value_2));
+
+ // Different paths, different hashes.
+ ASSERT_NE(calc1.Calculate("pref_path", &string_value_1),
+ calc1.Calculate("pref_path_2", &string_value_1));
+
+ // Works for dictionaries.
+ ASSERT_EQ(calc1.Calculate("pref_path", &dictionary_value_1),
+ calc1.Calculate("pref_path", &dictionary_value_1));
+ ASSERT_NE(calc1.Calculate("pref_path", &dictionary_value_1),
+ calc1.Calculate("pref_path", &dictionary_value_2));
+
+ // Empty dictionary children are pruned.
+ ASSERT_EQ(calc1.Calculate("pref_path", &dictionary_value_1),
+ calc1.Calculate("pref_path", &dictionary_value_1_equivalent));
+
+ // NULL value is supported.
+ ASSERT_FALSE(calc1.Calculate("pref_path", NULL).empty());
+}
+
+// Tests the output against a known value to catch unexpected algorithm changes.
+// The test hashes below must NEVER be updated, the serialization algorithm used
+// must always be able to generate data that will produce these exact hashes.
+TEST(PrefHashCalculatorTest, CatchHashChanges) {
+ static const char kSeed[] = "0123456789ABCDEF0123456789ABCDEF";
+ static const char kDeviceId[] = "test_device_id1";
+
+ auto null_value = base::MakeUnique<base::Value>();
+ std::unique_ptr<base::Value> bool_value(new base::Value(false));
+ std::unique_ptr<base::Value> int_value(new base::Value(1234567890));
+ std::unique_ptr<base::Value> double_value(new base::Value(123.0987654321));
+ std::unique_ptr<base::Value> string_value(
+ new base::Value("testing with special chars:\n<>{}:^^@#$\\/"));
+
+ // For legacy reasons, we have to support pruning of empty lists/dictionaries
+ // and nested empty ists/dicts in the hash generation algorithm.
+ std::unique_ptr<base::DictionaryValue> nested_empty_dict(
+ new base::DictionaryValue);
+ nested_empty_dict->Set("a", new base::DictionaryValue);
+ nested_empty_dict->Set("b", new base::ListValue);
+ std::unique_ptr<base::ListValue> nested_empty_list(new base::ListValue);
+ nested_empty_list->Append(base::MakeUnique<base::DictionaryValue>());
+ nested_empty_list->Append(base::MakeUnique<base::ListValue>());
+ nested_empty_list->Append(nested_empty_dict->CreateDeepCopy());
+
+ // A dictionary with an empty dictionary, an empty list, and nested empty
+ // dictionaries/lists in it.
+ std::unique_ptr<base::DictionaryValue> dict_value(new base::DictionaryValue);
+ dict_value->Set("a", new base::Value("foo"));
+ dict_value->Set("d", new base::ListValue);
+ dict_value->Set("b", new base::DictionaryValue);
+ dict_value->Set("c", new base::Value("baz"));
+ dict_value->Set("e", nested_empty_dict.release());
+ dict_value->Set("f", nested_empty_list.release());
+
+ std::unique_ptr<base::ListValue> list_value(new base::ListValue);
+ list_value->AppendBoolean(true);
+ list_value->AppendInteger(100);
+ list_value->AppendDouble(1.0);
+
+ ASSERT_EQ(base::Value::Type::NONE, null_value->GetType());
+ ASSERT_EQ(base::Value::Type::BOOLEAN, bool_value->GetType());
+ ASSERT_EQ(base::Value::Type::INTEGER, int_value->GetType());
+ ASSERT_EQ(base::Value::Type::DOUBLE, double_value->GetType());
+ ASSERT_EQ(base::Value::Type::STRING, string_value->GetType());
+ ASSERT_EQ(base::Value::Type::DICTIONARY, dict_value->GetType());
+ ASSERT_EQ(base::Value::Type::LIST, list_value->GetType());
+
+ // Test every value type independently. Intentionally omits Type::BINARY which
+ // isn't even allowed in JSONWriter's input.
+ static const char kExpectedNullValue[] =
+ "82A9F3BBC7F9FF84C76B033C854E79EEB162783FA7B3E99FF9372FA8E12C44F7";
+ EXPECT_EQ(PrefHashCalculator::VALID,
+ PrefHashCalculator(kSeed, kDeviceId, "legacydeviceid")
+ .Validate("pref.path", null_value.get(), kExpectedNullValue));
+
+ static const char kExpectedBooleanValue[] =
+ "A520D8F43EA307B0063736DC9358C330539D0A29417580514C8B9862632C4CCC";
+ EXPECT_EQ(
+ PrefHashCalculator::VALID,
+ PrefHashCalculator(kSeed, kDeviceId, "legacydeviceid")
+ .Validate("pref.path", bool_value.get(), kExpectedBooleanValue));
+
+ static const char kExpectedIntegerValue[] =
+ "8D60DA1F10BF5AA29819D2D66D7CCEF9AABC5DA93C11A0D2BD21078D63D83682";
+ EXPECT_EQ(PrefHashCalculator::VALID,
+ PrefHashCalculator(kSeed, kDeviceId, "legacydeviceid")
+ .Validate("pref.path", int_value.get(), kExpectedIntegerValue));
+
+ static const char kExpectedDoubleValue[] =
+ "C9D94772516125BEEDAE68C109D44BC529E719EE020614E894CC7FB4098C545D";
+ EXPECT_EQ(
+ PrefHashCalculator::VALID,
+ PrefHashCalculator(kSeed, kDeviceId, "legacydeviceid")
+ .Validate("pref.path", double_value.get(), kExpectedDoubleValue));
+
+ static const char kExpectedStringValue[] =
+ "05ACCBD3B05C45C36CD06190F63EC577112311929D8380E26E5F13182EB68318";
+ EXPECT_EQ(
+ PrefHashCalculator::VALID,
+ PrefHashCalculator(kSeed, kDeviceId, "legacydeviceid")
+ .Validate("pref.path", string_value.get(), kExpectedStringValue));
+
+ static const char kExpectedDictValue[] =
+ "7A84DCC710D796C771F789A4DA82C952095AA956B6F1667EE42D0A19ECAA3C4A";
+ EXPECT_EQ(PrefHashCalculator::VALID,
+ PrefHashCalculator(kSeed, kDeviceId, "legacydeviceid")
+ .Validate("pref.path", dict_value.get(), kExpectedDictValue));
+
+ static const char kExpectedListValue[] =
+ "8D5A25972DF5AE20D041C780E7CA54E40F614AD53513A0724EE8D62D4F992740";
+ EXPECT_EQ(PrefHashCalculator::VALID,
+ PrefHashCalculator(kSeed, kDeviceId, "legacydeviceid")
+ .Validate("pref.path", list_value.get(), kExpectedListValue));
+
+ // Also test every value type together in the same dictionary.
+ base::DictionaryValue everything;
+ everything.Set("null", null_value.release());
+ everything.Set("bool", bool_value.release());
+ everything.Set("int", int_value.release());
+ everything.Set("double", double_value.release());
+ everything.Set("string", string_value.release());
+ everything.Set("list", list_value.release());
+ everything.Set("dict", dict_value.release());
+ static const char kExpectedEverythingValue[] =
+ "B97D09BE7005693574DCBDD03D8D9E44FB51F4008B73FB56A49A9FA671A1999B";
+ EXPECT_EQ(PrefHashCalculator::VALID,
+ PrefHashCalculator(kSeed, kDeviceId, "legacydeviceid")
+ .Validate("pref.path", &everything, kExpectedEverythingValue));
+}
+
+TEST(PrefHashCalculatorTest, TestCompatibilityWithLegacyDeviceId) {
+ static const char kSeed[] = "0123456789ABCDEF0123456789ABCDEF";
+ static const char kNewDeviceId[] = "new_test_device_id1";
+ static const char kLegacyDeviceId[] = "test_device_id1";
+
+ // As in PrefHashCalculatorTest.CatchHashChanges.
+ const base::Value string_value("testing with special chars:\n<>{}:^^@#$\\/");
+ static const char kExpectedValue[] =
+ "05ACCBD3B05C45C36CD06190F63EC577112311929D8380E26E5F13182EB68318";
+
+ EXPECT_EQ(PrefHashCalculator::VALID_SECURE_LEGACY,
+ PrefHashCalculator(kSeed, kNewDeviceId, kLegacyDeviceId)
+ .Validate("pref.path", &string_value, kExpectedValue));
+}
diff --git a/chromium/services/preferences/tracked/pref_hash_filter.cc b/chromium/services/preferences/tracked/pref_hash_filter.cc
new file mode 100644
index 00000000000..29d6b9ab830
--- /dev/null
+++ b/chromium/services/preferences/tracked/pref_hash_filter.cc
@@ -0,0 +1,366 @@
+// 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 "services/preferences/tracked/pref_hash_filter.h"
+
+#include <stdint.h>
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/pref_store.h"
+#include "services/preferences/public/cpp/tracked/pref_names.h"
+#include "services/preferences/tracked/dictionary_hash_store_contents.h"
+#include "services/preferences/tracked/pref_hash_store.h"
+#include "services/preferences/tracked/pref_hash_store_transaction.h"
+#include "services/preferences/tracked/tracked_atomic_preference.h"
+#include "services/preferences/tracked/tracked_split_preference.h"
+
+namespace {
+
+void CleanupDeprecatedTrackedPreferences(
+ base::DictionaryValue* pref_store_contents,
+ PrefHashStoreTransaction* hash_store_transaction) {
+ // Add deprecated previously tracked preferences below for them to be cleaned
+ // up from both the pref files and the hash store.
+ static const char* const kDeprecatedTrackedPreferences[] = {
+ // TODO(a-v-y): Remove in M60+,
+ "default_search_provider.search_url", "default_search_provider.name",
+ "default_search_provider.keyword"};
+
+ for (size_t i = 0; i < arraysize(kDeprecatedTrackedPreferences); ++i) {
+ const char* key = kDeprecatedTrackedPreferences[i];
+ pref_store_contents->Remove(key, NULL);
+ hash_store_transaction->ClearHash(key);
+ }
+}
+
+} // namespace
+
+using PrefTrackingStrategy =
+ prefs::mojom::TrackedPreferenceMetadata::PrefTrackingStrategy;
+
+PrefHashFilter::PrefHashFilter(
+ std::unique_ptr<PrefHashStore> pref_hash_store,
+ StoreContentsPair external_validation_hash_store_pair,
+ const std::vector<prefs::mojom::TrackedPreferenceMetadataPtr>&
+ tracked_preferences,
+ prefs::mojom::ResetOnLoadObserverPtr reset_on_load_observer,
+ prefs::mojom::TrackedPreferenceValidationDelegate* delegate,
+ size_t reporting_ids_count,
+ bool report_super_mac_validity)
+ : pref_hash_store_(std::move(pref_hash_store)),
+ external_validation_hash_store_pair_(
+ external_validation_hash_store_pair.first
+ ? base::make_optional(
+ std::move(external_validation_hash_store_pair))
+ : base::nullopt),
+ reset_on_load_observer_(std::move(reset_on_load_observer)),
+ report_super_mac_validity_(report_super_mac_validity) {
+ DCHECK(pref_hash_store_);
+ DCHECK_GE(reporting_ids_count, tracked_preferences.size());
+ // Verify that, if |external_validation_hash_store_pair_| is present, both its
+ // items are non-null.
+ DCHECK(!external_validation_hash_store_pair_.has_value() ||
+ (external_validation_hash_store_pair_->first &&
+ external_validation_hash_store_pair_->second));
+
+ for (size_t i = 0; i < tracked_preferences.size(); ++i) {
+ const prefs::mojom::TrackedPreferenceMetadata& metadata =
+ *tracked_preferences[i];
+
+ std::unique_ptr<TrackedPreference> tracked_preference;
+ switch (metadata.strategy) {
+ case PrefTrackingStrategy::ATOMIC:
+ tracked_preference.reset(new TrackedAtomicPreference(
+ metadata.name, metadata.reporting_id, reporting_ids_count,
+ metadata.enforcement_level, metadata.value_type, delegate));
+ break;
+ case PrefTrackingStrategy::SPLIT:
+ tracked_preference.reset(new TrackedSplitPreference(
+ metadata.name, metadata.reporting_id, reporting_ids_count,
+ metadata.enforcement_level, metadata.value_type, delegate));
+ break;
+ }
+ DCHECK(tracked_preference);
+
+ bool is_new = tracked_paths_
+ .insert(std::make_pair(metadata.name,
+ std::move(tracked_preference)))
+ .second;
+ DCHECK(is_new);
+ }
+}
+
+PrefHashFilter::~PrefHashFilter() {
+ // Ensure new values for all |changed_paths_| have been flushed to
+ // |pref_hash_store_| already.
+ DCHECK(changed_paths_.empty());
+}
+
+// static
+void PrefHashFilter::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) {
+ // See GetResetTime for why this is a StringPref and not Int64Pref.
+ registry->RegisterStringPref(
+ user_prefs::kPreferenceResetTime,
+ base::Int64ToString(base::Time().ToInternalValue()));
+}
+
+// static
+base::Time PrefHashFilter::GetResetTime(PrefService* user_prefs) {
+ // Provide our own implementation (identical to the PrefService::GetInt64) in
+ // order to ensure it remains consistent with the way we store this value
+ // (which we do via a PrefStore, preventing us from reusing
+ // PrefService::SetInt64).
+ int64_t internal_value = base::Time().ToInternalValue();
+ if (!base::StringToInt64(
+ user_prefs->GetString(user_prefs::kPreferenceResetTime),
+ &internal_value)) {
+ // Somehow the value stored on disk is not a valid int64_t.
+ NOTREACHED();
+ return base::Time();
+ }
+ return base::Time::FromInternalValue(internal_value);
+}
+
+// static
+void PrefHashFilter::ClearResetTime(PrefService* user_prefs) {
+ user_prefs->ClearPref(user_prefs::kPreferenceResetTime);
+}
+
+void PrefHashFilter::Initialize(base::DictionaryValue* pref_store_contents) {
+ DictionaryHashStoreContents dictionary_contents(pref_store_contents);
+ std::unique_ptr<PrefHashStoreTransaction> hash_store_transaction(
+ pref_hash_store_->BeginTransaction(&dictionary_contents));
+ for (auto it = tracked_paths_.begin(); it != tracked_paths_.end(); ++it) {
+ const std::string& initialized_path = it->first;
+ const TrackedPreference* initialized_preference = it->second.get();
+ const base::Value* value = nullptr;
+ pref_store_contents->Get(initialized_path, &value);
+ initialized_preference->OnNewValue(value, hash_store_transaction.get());
+ }
+}
+
+// Marks |path| has having changed if it is part of |tracked_paths_|. A new hash
+// will be stored for it the next time FilterSerializeData() is invoked.
+void PrefHashFilter::FilterUpdate(const std::string& path) {
+ auto it = tracked_paths_.find(path);
+ if (it != tracked_paths_.end())
+ changed_paths_.insert(std::make_pair(path, it->second.get()));
+}
+
+// Updates the stored hashes for |changed_paths_| before serializing data to
+// disk. This is required as storing the hash everytime a pref's value changes
+// is too expensive (see perf regression @ http://crbug.com/331273).
+PrefFilter::OnWriteCallbackPair PrefHashFilter::FilterSerializeData(
+ base::DictionaryValue* pref_store_contents) {
+ // Generate the callback pair before clearing |changed_paths_|.
+ PrefFilter::OnWriteCallbackPair callback_pair =
+ GetOnWriteSynchronousCallbacks(pref_store_contents);
+
+ if (!changed_paths_.empty()) {
+ base::TimeTicks checkpoint = base::TimeTicks::Now();
+ {
+ DictionaryHashStoreContents dictionary_contents(pref_store_contents);
+ std::unique_ptr<PrefHashStoreTransaction> hash_store_transaction(
+ pref_hash_store_->BeginTransaction(&dictionary_contents));
+
+ std::unique_ptr<PrefHashStoreTransaction>
+ external_validation_hash_store_transaction;
+ if (external_validation_hash_store_pair_) {
+ external_validation_hash_store_transaction =
+ external_validation_hash_store_pair_->first->BeginTransaction(
+ external_validation_hash_store_pair_->second.get());
+ }
+
+ for (ChangedPathsMap::const_iterator it = changed_paths_.begin();
+ it != changed_paths_.end(); ++it) {
+ const std::string& changed_path = it->first;
+ const TrackedPreference* changed_preference = it->second;
+ const base::Value* value = nullptr;
+ pref_store_contents->Get(changed_path, &value);
+ changed_preference->OnNewValue(value, hash_store_transaction.get());
+ }
+ changed_paths_.clear();
+ }
+ UMA_HISTOGRAM_TIMES("Settings.FilterSerializeDataTime",
+ base::TimeTicks::Now() - checkpoint);
+ }
+
+ return callback_pair;
+}
+
+void PrefHashFilter::FinalizeFilterOnLoad(
+ const PostFilterOnLoadCallback& post_filter_on_load_callback,
+ std::unique_ptr<base::DictionaryValue> pref_store_contents,
+ bool prefs_altered) {
+ DCHECK(pref_store_contents);
+ base::TimeTicks checkpoint = base::TimeTicks::Now();
+
+ bool did_reset = false;
+ {
+ DictionaryHashStoreContents dictionary_contents(pref_store_contents.get());
+ std::unique_ptr<PrefHashStoreTransaction> hash_store_transaction(
+ pref_hash_store_->BeginTransaction(&dictionary_contents));
+
+ std::unique_ptr<PrefHashStoreTransaction>
+ external_validation_hash_store_transaction;
+ if (external_validation_hash_store_pair_) {
+ external_validation_hash_store_transaction =
+ external_validation_hash_store_pair_->first->BeginTransaction(
+ external_validation_hash_store_pair_->second.get());
+ }
+
+ CleanupDeprecatedTrackedPreferences(pref_store_contents.get(),
+ hash_store_transaction.get());
+
+ if (report_super_mac_validity_) {
+ UMA_HISTOGRAM_BOOLEAN("Settings.HashesDictionaryTrusted",
+ hash_store_transaction->IsSuperMACValid());
+ }
+
+ for (auto it = tracked_paths_.begin(); it != tracked_paths_.end(); ++it) {
+ if (it->second->EnforceAndReport(
+ pref_store_contents.get(), hash_store_transaction.get(),
+ external_validation_hash_store_transaction.get())) {
+ did_reset = true;
+ prefs_altered = true;
+ }
+ }
+ if (hash_store_transaction->StampSuperMac())
+ prefs_altered = true;
+ }
+
+ if (did_reset) {
+ pref_store_contents->Set(user_prefs::kPreferenceResetTime,
+ new base::Value(base::Int64ToString(
+ base::Time::Now().ToInternalValue())));
+ FilterUpdate(user_prefs::kPreferenceResetTime);
+
+ if (reset_on_load_observer_)
+ reset_on_load_observer_->OnResetOnLoad();
+ }
+ reset_on_load_observer_.reset();
+
+ UMA_HISTOGRAM_TIMES("Settings.FilterOnLoadTime",
+ base::TimeTicks::Now() - checkpoint);
+
+ post_filter_on_load_callback.Run(std::move(pref_store_contents),
+ prefs_altered);
+}
+
+// static
+void PrefHashFilter::ClearFromExternalStore(
+ HashStoreContents* external_validation_hash_store_contents,
+ const base::DictionaryValue* changed_paths_and_macs) {
+ DCHECK(!changed_paths_and_macs->empty());
+
+ for (base::DictionaryValue::Iterator it(*changed_paths_and_macs);
+ !it.IsAtEnd(); it.Advance()) {
+ external_validation_hash_store_contents->RemoveEntry(it.key());
+ }
+}
+
+// static
+void PrefHashFilter::FlushToExternalStore(
+ std::unique_ptr<HashStoreContents> external_validation_hash_store_contents,
+ std::unique_ptr<base::DictionaryValue> changed_paths_and_macs,
+ bool write_success) {
+ DCHECK(!changed_paths_and_macs->empty());
+ DCHECK(external_validation_hash_store_contents);
+ if (!write_success)
+ return;
+
+ for (base::DictionaryValue::Iterator it(*changed_paths_and_macs);
+ !it.IsAtEnd(); it.Advance()) {
+ const std::string& changed_path = it.key();
+
+ const base::DictionaryValue* split_values = nullptr;
+ if (it.value().GetAsDictionary(&split_values)) {
+ for (base::DictionaryValue::Iterator inner_it(*split_values);
+ !inner_it.IsAtEnd(); inner_it.Advance()) {
+ std::string mac;
+ bool is_string = inner_it.value().GetAsString(&mac);
+ DCHECK(is_string);
+
+ external_validation_hash_store_contents->SetSplitMac(
+ changed_path, inner_it.key(), mac);
+ }
+ } else {
+ const base::Value* value_as_string;
+ bool is_string = it.value().GetAsString(&value_as_string);
+ DCHECK(is_string);
+
+ external_validation_hash_store_contents->SetMac(
+ changed_path, value_as_string->GetString());
+ }
+ }
+}
+
+PrefFilter::OnWriteCallbackPair PrefHashFilter::GetOnWriteSynchronousCallbacks(
+ base::DictionaryValue* pref_store_contents) {
+ if (changed_paths_.empty() || !external_validation_hash_store_pair_) {
+ return std::make_pair(base::Closure(),
+ base::Callback<void(bool success)>());
+ }
+
+ std::unique_ptr<base::DictionaryValue> changed_paths_macs =
+ base::MakeUnique<base::DictionaryValue>();
+
+ for (ChangedPathsMap::const_iterator it = changed_paths_.begin();
+ it != changed_paths_.end(); ++it) {
+ const std::string& changed_path = it->first;
+ const TrackedPreference* changed_preference = it->second;
+
+ switch (changed_preference->GetType()) {
+ case TrackedPreferenceType::ATOMIC: {
+ const base::Value* new_value = nullptr;
+ pref_store_contents->Get(changed_path, &new_value);
+ changed_paths_macs->SetStringWithoutPathExpansion(
+ changed_path,
+ external_validation_hash_store_pair_->first->ComputeMac(
+ changed_path, new_value));
+ break;
+ }
+ case TrackedPreferenceType::SPLIT: {
+ const base::DictionaryValue* dict_value = nullptr;
+ pref_store_contents->GetDictionary(changed_path, &dict_value);
+ changed_paths_macs->SetWithoutPathExpansion(
+ changed_path,
+ external_validation_hash_store_pair_->first->ComputeSplitMacs(
+ changed_path, dict_value));
+ break;
+ }
+ }
+ }
+
+ DCHECK(external_validation_hash_store_pair_->second->IsCopyable())
+ << "External HashStoreContents must be copyable as it needs to be used "
+ "off-thread";
+
+ std::unique_ptr<HashStoreContents> hash_store_contents_copy =
+ external_validation_hash_store_pair_->second->MakeCopy();
+
+ // We can use raw pointers for the first callback instead of making more
+ // copies as it will be executed in sequence before the second callback,
+ // which owns the pointers.
+ HashStoreContents* raw_contents = hash_store_contents_copy.get();
+ base::DictionaryValue* raw_changed_paths_macs = changed_paths_macs.get();
+
+ return std::make_pair(
+ base::Bind(&ClearFromExternalStore, base::Unretained(raw_contents),
+ base::Unretained(raw_changed_paths_macs)),
+ base::Bind(&FlushToExternalStore, base::Passed(&hash_store_contents_copy),
+ base::Passed(&changed_paths_macs)));
+}
diff --git a/chromium/services/preferences/tracked/pref_hash_filter.h b/chromium/services/preferences/tracked/pref_hash_filter.h
new file mode 100644
index 00000000000..4c3cdaa4996
--- /dev/null
+++ b/chromium/services/preferences/tracked/pref_hash_filter.h
@@ -0,0 +1,157 @@
+// 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 SERVICES_PREFERENCES_TRACKED_PREF_HASH_FILTER_H_
+#define SERVICES_PREFERENCES_TRACKED_PREF_HASH_FILTER_H_
+
+#include <stddef.h>
+
+#include <map>
+#include <memory>
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "services/preferences/public/interfaces/preferences_configuration.mojom.h"
+#include "services/preferences/tracked/hash_store_contents.h"
+#include "services/preferences/tracked/interceptable_pref_filter.h"
+#include "services/preferences/tracked/tracked_preference.h"
+
+class PrefHashStore;
+class PrefService;
+
+namespace base {
+class DictionaryValue;
+class Time;
+} // namespace base
+
+namespace prefs {
+namespace mojom {
+class TrackedPreferenceValidationDelegate;
+}
+}
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+} // namespace user_prefs
+
+// Intercepts preference values as they are loaded from disk and verifies them
+// using a PrefHashStore. Keeps the PrefHashStore contents up to date as values
+// are changed.
+class PrefHashFilter : public InterceptablePrefFilter {
+ public:
+ using StoreContentsPair = std::pair<std::unique_ptr<PrefHashStore>,
+ std::unique_ptr<HashStoreContents>>;
+
+ // Constructs a PrefHashFilter tracking the specified |tracked_preferences|
+ // using |pref_hash_store| to check/store hashes. An optional |delegate| is
+ // notified of the status of each preference as it is checked.
+ // If |reset_on_load_observer| is provided, it will be notified if a reset
+ // occurs in FilterOnLoad.
+ // |reporting_ids_count| is the count of all possible IDs (possibly greater
+ // than |tracked_preferences.size()|). If |report_super_mac_validity| is true,
+ // the state of the super MAC will be reported via UMA during
+ // FinalizeFilterOnLoad.
+ // |external_validation_hash_store_pair_| will be used (if non-null) to
+ // perform extra validations without triggering resets.
+ PrefHashFilter(std::unique_ptr<PrefHashStore> pref_hash_store,
+ StoreContentsPair external_validation_hash_store_pair_,
+ const std::vector<prefs::mojom::TrackedPreferenceMetadataPtr>&
+ tracked_preferences,
+ prefs::mojom::ResetOnLoadObserverPtr reset_on_load_observer,
+ prefs::mojom::TrackedPreferenceValidationDelegate* delegate,
+ size_t reporting_ids_count,
+ bool report_super_mac_validity);
+
+ ~PrefHashFilter() override;
+
+ // Registers required user preferences.
+ static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+ // Retrieves the time of the last reset event, if any, for the provided user
+ // preferences. If no reset has occurred, Returns a null |Time|.
+ static base::Time GetResetTime(PrefService* user_prefs);
+
+ // Clears the time of the last reset event, if any, for the provided user
+ // preferences.
+ static void ClearResetTime(PrefService* user_prefs);
+
+ // Initializes the PrefHashStore with hashes of the tracked preferences in
+ // |pref_store_contents|. |pref_store_contents| will be the |storage| passed
+ // to PrefHashStore::BeginTransaction().
+ void Initialize(base::DictionaryValue* pref_store_contents);
+
+ // PrefFilter remaining implementation.
+ void FilterUpdate(const std::string& path) override;
+ OnWriteCallbackPair FilterSerializeData(
+ base::DictionaryValue* pref_store_contents) override;
+
+ private:
+ // InterceptablePrefFilter implementation.
+ void FinalizeFilterOnLoad(
+ const PostFilterOnLoadCallback& post_filter_on_load_callback,
+ std::unique_ptr<base::DictionaryValue> pref_store_contents,
+ bool prefs_altered) override;
+
+ // Helper function to generate FilterSerializeData()'s pre-write and
+ // post-write callbacks. The returned callbacks are thread-safe.
+ OnWriteCallbackPair GetOnWriteSynchronousCallbacks(
+ base::DictionaryValue* pref_store_contents);
+
+ // Clears the MACs contained in |external_validation_hash_store_contents|
+ // which are present in |paths_to_clear|.
+ static void ClearFromExternalStore(
+ HashStoreContents* external_validation_hash_store_contents,
+ const base::DictionaryValue* changed_paths_and_macs);
+
+ // Flushes the MACs contained in |changed_paths_and_mac| to
+ // external_hash_store_contents if |write_success|, otherwise discards the
+ // changes.
+ static void FlushToExternalStore(
+ std::unique_ptr<HashStoreContents> external_hash_store_contents,
+ std::unique_ptr<base::DictionaryValue> changed_paths_and_macs,
+ bool write_success);
+
+ // Callback to be invoked only once (and subsequently reset) on the next
+ // FilterOnLoad event. It will be allowed to modify the |prefs| handed to
+ // FilterOnLoad before handing them back to this PrefHashFilter.
+ FilterOnLoadInterceptor filter_on_load_interceptor_;
+
+ // A map of paths to TrackedPreferences; this map owns this individual
+ // TrackedPreference objects.
+ using TrackedPreferencesMap =
+ std::unordered_map<std::string, std::unique_ptr<TrackedPreference>>;
+
+ // A map from changed paths to their corresponding TrackedPreferences (which
+ // aren't owned by this map).
+ using ChangedPathsMap = std::map<std::string, const TrackedPreference*>;
+
+ std::unique_ptr<PrefHashStore> pref_hash_store_;
+
+ // A store and contents on which to perform extra validations without
+ // triggering resets.
+ // Will be null if the platform does not support external validation.
+ const base::Optional<StoreContentsPair> external_validation_hash_store_pair_;
+
+ // Notified if a reset occurs in a call to FilterOnLoad.
+ prefs::mojom::ResetOnLoadObserverPtr reset_on_load_observer_;
+
+ TrackedPreferencesMap tracked_paths_;
+
+ // The set of all paths whose value has changed since the last call to
+ // FilterSerializeData.
+ ChangedPathsMap changed_paths_;
+
+ // Whether to report the validity of the super MAC at load time (via UMA).
+ bool report_super_mac_validity_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrefHashFilter);
+};
+
+#endif // SERVICES_PREFERENCES_TRACKED_PREF_HASH_FILTER_H_
diff --git a/chromium/services/preferences/tracked/pref_hash_filter_unittest.cc b/chromium/services/preferences/tracked/pref_hash_filter_unittest.cc
new file mode 100644
index 00000000000..5c288dcc77d
--- /dev/null
+++ b/chromium/services/preferences/tracked/pref_hash_filter_unittest.cc
@@ -0,0 +1,1355 @@
+// 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 "services/preferences/tracked/pref_hash_filter.h"
+
+#include <stddef.h>
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/run_loop.h"
+#include "base/values.h"
+#include "components/prefs/testing_pref_store.h"
+#include "mojo/public/cpp/bindings/binding_set.h"
+#include "services/preferences/public/cpp/tracked/configuration.h"
+#include "services/preferences/public/cpp/tracked/mock_validation_delegate.h"
+#include "services/preferences/public/cpp/tracked/pref_names.h"
+#include "services/preferences/tracked/hash_store_contents.h"
+#include "services/preferences/tracked/pref_hash_store.h"
+#include "services/preferences/tracked/pref_hash_store_transaction.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using EnforcementLevel =
+ prefs::mojom::TrackedPreferenceMetadata::EnforcementLevel;
+using PrefTrackingStrategy =
+ prefs::mojom::TrackedPreferenceMetadata::PrefTrackingStrategy;
+using ValueType = prefs::mojom::TrackedPreferenceMetadata::ValueType;
+using ValueState =
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState;
+
+const char kAtomicPref[] = "atomic_pref";
+const char kAtomicPref2[] = "atomic_pref2";
+const char kAtomicPref3[] = "pref3";
+const char kAtomicPref4[] = "pref4";
+const char kReportOnlyPref[] = "report_only";
+const char kReportOnlySplitPref[] = "report_only_split_pref";
+const char kSplitPref[] = "split_pref";
+
+const prefs::TrackedPreferenceMetadata kTestTrackedPrefs[] = {
+ {0, kAtomicPref, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC, ValueType::PERSONAL},
+ {1, kReportOnlyPref, EnforcementLevel::NO_ENFORCEMENT,
+ PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL},
+ {2, kSplitPref, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::SPLIT, ValueType::IMPERSONAL},
+ {3, kReportOnlySplitPref, EnforcementLevel::NO_ENFORCEMENT,
+ PrefTrackingStrategy::SPLIT, ValueType::IMPERSONAL},
+ {4, kAtomicPref2, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL},
+ {5, kAtomicPref3, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL},
+ {6, kAtomicPref4, EnforcementLevel::ENFORCE_ON_LOAD,
+ PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL},
+};
+
+} // namespace
+
+// A PrefHashStore that allows simulation of CheckValue results and captures
+// checked and stored values.
+class MockPrefHashStore : public PrefHashStore {
+ public:
+ typedef std::pair<const void*, PrefTrackingStrategy> ValuePtrStrategyPair;
+
+ MockPrefHashStore()
+ : stamp_super_mac_result_(false),
+ is_super_mac_valid_result_(false),
+ transactions_performed_(0),
+ transaction_active_(false) {}
+
+ ~MockPrefHashStore() override { EXPECT_FALSE(transaction_active_); }
+
+ // Set the result that will be returned when |path| is passed to
+ // |CheckValue/CheckSplitValue|.
+ void SetCheckResult(const std::string& path, ValueState result);
+
+ // Set the invalid_keys that will be returned when |path| is passed to
+ // |CheckSplitValue|. SetCheckResult should already have been called for
+ // |path| with |result == CHANGED| for this to make any sense.
+ void SetInvalidKeysResult(
+ const std::string& path,
+ const std::vector<std::string>& invalid_keys_result);
+
+ // Sets the value that will be returned from
+ // PrefHashStoreTransaction::StampSuperMAC().
+ void set_stamp_super_mac_result(bool result) {
+ stamp_super_mac_result_ = result;
+ }
+
+ // Sets the value that will be returned from
+ // PrefHashStoreTransaction::IsSuperMACValid().
+ void set_is_super_mac_valid_result(bool result) {
+ is_super_mac_valid_result_ = result;
+ }
+
+ // Returns the number of transactions that were performed.
+ size_t transactions_performed() { return transactions_performed_; }
+
+ // Returns the number of paths checked.
+ size_t checked_paths_count() const { return checked_values_.size(); }
+
+ // Returns the number of paths stored.
+ size_t stored_paths_count() const { return stored_values_.size(); }
+
+ // Returns the pointer value and strategy that was passed to
+ // |CheckHash/CheckSplitHash| for |path|. The returned pointer could since
+ // have been freed and is thus not safe to dereference.
+ ValuePtrStrategyPair checked_value(const std::string& path) const {
+ std::map<std::string, ValuePtrStrategyPair>::const_iterator value =
+ checked_values_.find(path);
+ if (value != checked_values_.end())
+ return value->second;
+ return std::make_pair(reinterpret_cast<void*>(0xBAD),
+ static_cast<PrefTrackingStrategy>(-1));
+ }
+
+ // Returns the pointer value that was passed to |StoreHash/StoreSplitHash| for
+ // |path|. The returned pointer could since have been freed and is thus not
+ // safe to dereference.
+ ValuePtrStrategyPair stored_value(const std::string& path) const {
+ std::map<std::string, ValuePtrStrategyPair>::const_iterator value =
+ stored_values_.find(path);
+ if (value != stored_values_.end())
+ return value->second;
+ return std::make_pair(reinterpret_cast<void*>(0xBAD),
+ static_cast<PrefTrackingStrategy>(-1));
+ }
+
+ // PrefHashStore implementation.
+ std::unique_ptr<PrefHashStoreTransaction> BeginTransaction(
+ HashStoreContents* storage) override;
+ std::string ComputeMac(const std::string& path,
+ const base::Value* new_value) override;
+ std::unique_ptr<base::DictionaryValue> ComputeSplitMacs(
+ const std::string& path,
+ const base::DictionaryValue* split_values) override;
+
+ private:
+ // A MockPrefHashStoreTransaction is handed to the caller on
+ // MockPrefHashStore::BeginTransaction(). It then stores state in its
+ // underlying MockPrefHashStore about calls it receives from that same caller
+ // which can later be verified in tests.
+ class MockPrefHashStoreTransaction : public PrefHashStoreTransaction {
+ public:
+ explicit MockPrefHashStoreTransaction(MockPrefHashStore* outer)
+ : outer_(outer) {}
+
+ ~MockPrefHashStoreTransaction() override {
+ outer_->transaction_active_ = false;
+ ++outer_->transactions_performed_;
+ }
+
+ // PrefHashStoreTransaction implementation.
+ base::StringPiece GetStoreUMASuffix() const override;
+ ValueState CheckValue(const std::string& path,
+ const base::Value* value) const override;
+ void StoreHash(const std::string& path,
+ const base::Value* new_value) override;
+ ValueState CheckSplitValue(
+ const std::string& path,
+ const base::DictionaryValue* initial_split_value,
+ std::vector<std::string>* invalid_keys) const override;
+ void StoreSplitHash(const std::string& path,
+ const base::DictionaryValue* split_value) override;
+ bool HasHash(const std::string& path) const override;
+ void ImportHash(const std::string& path, const base::Value* hash) override;
+ void ClearHash(const std::string& path) override;
+ bool IsSuperMACValid() const override;
+ bool StampSuperMac() override;
+
+ private:
+ MockPrefHashStore* outer_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockPrefHashStoreTransaction);
+ };
+
+ // Records a call to this mock's CheckValue/CheckSplitValue methods.
+ ValueState RecordCheckValue(const std::string& path,
+ const base::Value* value,
+ PrefTrackingStrategy strategy);
+
+ // Records a call to this mock's StoreHash/StoreSplitHash methods.
+ void RecordStoreHash(const std::string& path,
+ const base::Value* new_value,
+ PrefTrackingStrategy strategy);
+
+ std::map<std::string, ValueState> check_results_;
+ std::map<std::string, std::vector<std::string>> invalid_keys_results_;
+
+ bool stamp_super_mac_result_;
+ bool is_super_mac_valid_result_;
+
+ std::map<std::string, ValuePtrStrategyPair> checked_values_;
+ std::map<std::string, ValuePtrStrategyPair> stored_values_;
+
+ // Number of transactions that were performed via this MockPrefHashStore.
+ size_t transactions_performed_;
+
+ // Whether a transaction is currently active (only one transaction should be
+ // active at a time).
+ bool transaction_active_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockPrefHashStore);
+};
+
+void MockPrefHashStore::SetCheckResult(const std::string& path,
+ ValueState result) {
+ check_results_.insert(std::make_pair(path, result));
+}
+
+void MockPrefHashStore::SetInvalidKeysResult(
+ const std::string& path,
+ const std::vector<std::string>& invalid_keys_result) {
+ // Ensure |check_results_| has a CHANGED entry for |path|.
+ std::map<std::string, ValueState>::const_iterator result =
+ check_results_.find(path);
+ ASSERT_TRUE(result != check_results_.end());
+ ASSERT_EQ(ValueState::CHANGED, result->second);
+
+ invalid_keys_results_.insert(std::make_pair(path, invalid_keys_result));
+}
+
+std::unique_ptr<PrefHashStoreTransaction> MockPrefHashStore::BeginTransaction(
+ HashStoreContents* storage) {
+ EXPECT_FALSE(transaction_active_);
+ return std::unique_ptr<PrefHashStoreTransaction>(
+ new MockPrefHashStoreTransaction(this));
+}
+
+std::string MockPrefHashStore::ComputeMac(const std::string& path,
+ const base::Value* new_value) {
+ return "atomic mac for: " + path;
+};
+
+std::unique_ptr<base::DictionaryValue> MockPrefHashStore::ComputeSplitMacs(
+ const std::string& path,
+ const base::DictionaryValue* split_values) {
+ std::unique_ptr<base::DictionaryValue> macs_dict(new base::DictionaryValue);
+ for (base::DictionaryValue::Iterator it(*split_values); !it.IsAtEnd();
+ it.Advance()) {
+ macs_dict->SetStringWithoutPathExpansion(
+ it.key(), "split mac for: " + path + "/" + it.key());
+ }
+ return macs_dict;
+};
+
+ValueState MockPrefHashStore::RecordCheckValue(const std::string& path,
+ const base::Value* value,
+ PrefTrackingStrategy strategy) {
+ // Record that |path| was checked and validate that it wasn't previously
+ // checked.
+ EXPECT_TRUE(checked_values_
+ .insert(std::make_pair(path, std::make_pair(value, strategy)))
+ .second);
+ std::map<std::string, ValueState>::const_iterator result =
+ check_results_.find(path);
+ if (result != check_results_.end())
+ return result->second;
+ return ValueState::UNCHANGED;
+}
+
+void MockPrefHashStore::RecordStoreHash(const std::string& path,
+ const base::Value* new_value,
+ PrefTrackingStrategy strategy) {
+ EXPECT_TRUE(
+ stored_values_
+ .insert(std::make_pair(path, std::make_pair(new_value, strategy)))
+ .second);
+}
+
+base::StringPiece
+MockPrefHashStore::MockPrefHashStoreTransaction ::GetStoreUMASuffix() const {
+ return "unused";
+}
+
+ValueState MockPrefHashStore::MockPrefHashStoreTransaction::CheckValue(
+ const std::string& path,
+ const base::Value* value) const {
+ return outer_->RecordCheckValue(path, value, PrefTrackingStrategy::ATOMIC);
+}
+
+void MockPrefHashStore::MockPrefHashStoreTransaction::StoreHash(
+ const std::string& path,
+ const base::Value* new_value) {
+ outer_->RecordStoreHash(path, new_value, PrefTrackingStrategy::ATOMIC);
+}
+
+ValueState MockPrefHashStore::MockPrefHashStoreTransaction::CheckSplitValue(
+ const std::string& path,
+ const base::DictionaryValue* initial_split_value,
+ std::vector<std::string>* invalid_keys) const {
+ EXPECT_TRUE(invalid_keys && invalid_keys->empty());
+
+ std::map<std::string, std::vector<std::string>>::const_iterator
+ invalid_keys_result = outer_->invalid_keys_results_.find(path);
+ if (invalid_keys_result != outer_->invalid_keys_results_.end()) {
+ invalid_keys->insert(invalid_keys->begin(),
+ invalid_keys_result->second.begin(),
+ invalid_keys_result->second.end());
+ }
+
+ return outer_->RecordCheckValue(path, initial_split_value,
+ PrefTrackingStrategy::SPLIT);
+}
+
+void MockPrefHashStore::MockPrefHashStoreTransaction::StoreSplitHash(
+ const std::string& path,
+ const base::DictionaryValue* new_value) {
+ outer_->RecordStoreHash(path, new_value, PrefTrackingStrategy::SPLIT);
+}
+
+bool MockPrefHashStore::MockPrefHashStoreTransaction::HasHash(
+ const std::string& path) const {
+ ADD_FAILURE() << "Unexpected call.";
+ return false;
+}
+
+void MockPrefHashStore::MockPrefHashStoreTransaction::ImportHash(
+ const std::string& path,
+ const base::Value* hash) {
+ ADD_FAILURE() << "Unexpected call.";
+}
+
+void MockPrefHashStore::MockPrefHashStoreTransaction::ClearHash(
+ const std::string& path) {
+ // Allow this to be called by PrefHashFilter's deprecated tracked prefs
+ // cleanup tasks.
+}
+
+bool MockPrefHashStore::MockPrefHashStoreTransaction::IsSuperMACValid() const {
+ return outer_->is_super_mac_valid_result_;
+}
+
+bool MockPrefHashStore::MockPrefHashStoreTransaction::StampSuperMac() {
+ return outer_->stamp_super_mac_result_;
+}
+
+std::vector<prefs::mojom::TrackedPreferenceMetadataPtr> GetConfiguration(
+ EnforcementLevel max_enforcement_level) {
+ auto configuration = prefs::ConstructTrackedConfiguration(kTestTrackedPrefs);
+ for (const auto& metadata : configuration) {
+ if (metadata->enforcement_level > max_enforcement_level)
+ metadata->enforcement_level = max_enforcement_level;
+ }
+ return configuration;
+}
+
+class MockHashStoreContents : public HashStoreContents {
+ public:
+ MockHashStoreContents(){};
+
+ // Returns the number of hashes stored.
+ size_t stored_hashes_count() const { return dictionary_.size(); }
+
+ // Returns the number of paths cleared.
+ size_t cleared_paths_count() const { return removed_entries_.size(); }
+
+ // Returns the stored MAC for an Atomic preference.
+ std::string GetStoredMac(const std::string& path) const;
+ // Returns the stored MAC for a Split preference.
+ std::string GetStoredSplitMac(const std::string& path,
+ const std::string& split_path) const;
+
+ // HashStoreContents implementation.
+ bool IsCopyable() const override;
+ std::unique_ptr<HashStoreContents> MakeCopy() const override;
+ base::StringPiece GetUMASuffix() const override;
+ void Reset() override;
+ bool GetMac(const std::string& path, std::string* out_value) override;
+ bool GetSplitMacs(const std::string& path,
+ std::map<std::string, std::string>* split_macs) override;
+ void SetMac(const std::string& path, const std::string& value) override;
+ void SetSplitMac(const std::string& path,
+ const std::string& split_path,
+ const std::string& value) override;
+ void ImportEntry(const std::string& path,
+ const base::Value* in_value) override;
+ bool RemoveEntry(const std::string& path) override;
+ const base::DictionaryValue* GetContents() const override;
+ std::string GetSuperMac() const override;
+ void SetSuperMac(const std::string& super_mac) override;
+
+ private:
+ MockHashStoreContents(MockHashStoreContents* origin_mock);
+
+ // Records calls to this mock's SetMac/SetSplitMac methods.
+ void RecordSetMac(const std::string& path, const std::string& mac) {
+ dictionary_.SetStringWithoutPathExpansion(path, mac);
+ }
+ void RecordSetSplitMac(const std::string& path,
+ const std::string& split_path,
+ const std::string& mac) {
+ base::DictionaryValue* mac_dict = nullptr;
+ dictionary_.GetDictionaryWithoutPathExpansion(path, &mac_dict);
+ if (!mac_dict) {
+ mac_dict = new base::DictionaryValue;
+ dictionary_.SetWithoutPathExpansion(path, mac_dict);
+ }
+ mac_dict->SetStringWithoutPathExpansion(split_path, mac);
+ }
+
+ // Records a call to this mock's RemoveEntry method.
+ void RecordRemoveEntry(const std::string& path) {
+ // Don't expect the same pref to be cleared more than once.
+ EXPECT_EQ(removed_entries_.end(), removed_entries_.find(path));
+ removed_entries_.insert(path);
+ }
+
+ base::DictionaryValue dictionary_;
+ std::set<std::string> removed_entries_;
+
+ // The code being tested copies its HashStoreContents for use in a callback
+ // which can be executed during shutdown. To be able to capture the behavior
+ // of the copy, we make it forward calls to the mock it was created from.
+ // Once set, |origin_mock_| must outlive this instance.
+ MockHashStoreContents* origin_mock_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockHashStoreContents);
+};
+
+std::string MockHashStoreContents::GetStoredMac(const std::string& path) const {
+ const base::Value* out_value;
+ if (dictionary_.GetWithoutPathExpansion(path, &out_value)) {
+ const base::Value* value_as_string;
+ EXPECT_TRUE(out_value->GetAsString(&value_as_string));
+
+ return value_as_string->GetString();
+ }
+
+ return std::string();
+}
+
+std::string MockHashStoreContents::GetStoredSplitMac(
+ const std::string& path,
+ const std::string& split_path) const {
+ const base::Value* out_value;
+ if (dictionary_.GetWithoutPathExpansion(path, &out_value)) {
+ const base::DictionaryValue* value_as_dict;
+ EXPECT_TRUE(out_value->GetAsDictionary(&value_as_dict));
+
+ if (value_as_dict->GetWithoutPathExpansion(split_path, &out_value)) {
+ const base::Value* value_as_string;
+ EXPECT_TRUE(out_value->GetAsString(&value_as_string));
+
+ return value_as_string->GetString();
+ }
+ }
+
+ return std::string();
+}
+
+MockHashStoreContents::MockHashStoreContents(MockHashStoreContents* origin_mock)
+ : origin_mock_(origin_mock) {}
+
+bool MockHashStoreContents::IsCopyable() const {
+ return true;
+}
+
+std::unique_ptr<HashStoreContents> MockHashStoreContents::MakeCopy() const {
+ // Return a new MockHashStoreContents which forwards all requests to this
+ // mock instance.
+ return std::unique_ptr<HashStoreContents>(
+ new MockHashStoreContents(const_cast<MockHashStoreContents*>(this)));
+}
+
+base::StringPiece MockHashStoreContents::GetUMASuffix() const {
+ return "Unused";
+}
+
+void MockHashStoreContents::Reset() {
+ ADD_FAILURE() << "Unexpected call.";
+}
+
+bool MockHashStoreContents::GetMac(const std::string& path,
+ std::string* out_value) {
+ ADD_FAILURE() << "Unexpected call.";
+ return false;
+}
+
+bool MockHashStoreContents::GetSplitMacs(
+ const std::string& path,
+ std::map<std::string, std::string>* split_macs) {
+ ADD_FAILURE() << "Unexpected call.";
+ return false;
+}
+
+void MockHashStoreContents::SetMac(const std::string& path,
+ const std::string& value) {
+ if (origin_mock_)
+ origin_mock_->RecordSetMac(path, value);
+ else
+ RecordSetMac(path, value);
+}
+
+void MockHashStoreContents::SetSplitMac(const std::string& path,
+ const std::string& split_path,
+ const std::string& value) {
+ if (origin_mock_)
+ origin_mock_->RecordSetSplitMac(path, split_path, value);
+ else
+ RecordSetSplitMac(path, split_path, value);
+}
+
+void MockHashStoreContents::ImportEntry(const std::string& path,
+ const base::Value* in_value) {
+ ADD_FAILURE() << "Unexpected call.";
+}
+
+bool MockHashStoreContents::RemoveEntry(const std::string& path) {
+ if (origin_mock_)
+ origin_mock_->RecordRemoveEntry(path);
+ else
+ RecordRemoveEntry(path);
+ return true;
+}
+
+const base::DictionaryValue* MockHashStoreContents::GetContents() const {
+ ADD_FAILURE() << "Unexpected call.";
+ return nullptr;
+}
+
+std::string MockHashStoreContents::GetSuperMac() const {
+ ADD_FAILURE() << "Unexpected call.";
+ return std::string();
+}
+
+void MockHashStoreContents::SetSuperMac(const std::string& super_mac) {
+ ADD_FAILURE() << "Unexpected call.";
+}
+
+class PrefHashFilterTest : public testing::TestWithParam<EnforcementLevel>,
+ public prefs::mojom::ResetOnLoadObserver {
+ public:
+ PrefHashFilterTest()
+ : mock_pref_hash_store_(NULL),
+ pref_store_contents_(new base::DictionaryValue),
+ mock_validation_delegate_record_(new MockValidationDelegateRecord),
+ mock_validation_delegate_(mock_validation_delegate_record_),
+ reset_recorded_(false) {}
+
+ void SetUp() override {
+ base::StatisticsRecorder::Initialize();
+ Reset();
+ }
+
+ protected:
+ // Reset the PrefHashFilter instance.
+ void Reset() {
+ // Construct a PrefHashFilter and MockPrefHashStore for the test.
+ InitializePrefHashFilter(GetConfiguration(GetParam()));
+ }
+
+ // Initializes |pref_hash_filter_| with a PrefHashFilter that uses a
+ // MockPrefHashStore. The raw pointer to the MockPrefHashStore (owned by the
+ // PrefHashFilter) is stored in |mock_pref_hash_store_|.
+ void InitializePrefHashFilter(
+ std::vector<prefs::mojom::TrackedPreferenceMetadataPtr> configuration) {
+ std::unique_ptr<MockPrefHashStore> temp_mock_pref_hash_store(
+ new MockPrefHashStore);
+ std::unique_ptr<MockPrefHashStore>
+ temp_mock_external_validation_pref_hash_store(new MockPrefHashStore);
+ std::unique_ptr<MockHashStoreContents>
+ temp_mock_external_validation_hash_store_contents(
+ new MockHashStoreContents);
+ mock_pref_hash_store_ = temp_mock_pref_hash_store.get();
+ mock_external_validation_pref_hash_store_ =
+ temp_mock_external_validation_pref_hash_store.get();
+ mock_external_validation_hash_store_contents_ =
+ temp_mock_external_validation_hash_store_contents.get();
+ pref_hash_filter_.reset(new PrefHashFilter(
+ std::move(temp_mock_pref_hash_store),
+ PrefHashFilter::StoreContentsPair(
+ std::move(temp_mock_external_validation_pref_hash_store),
+ std::move(temp_mock_external_validation_hash_store_contents)),
+ std::move(configuration),
+ reset_on_load_observer_bindings_.CreateInterfacePtrAndBind(this),
+ &mock_validation_delegate_, arraysize(kTestTrackedPrefs), true));
+ }
+
+ // Verifies whether a reset was reported by the PrefHashFiler. Also verifies
+ // that kPreferenceResetTime was set (or not) accordingly.
+ void VerifyRecordedReset(bool reset_expected) {
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(reset_expected, reset_recorded_);
+ EXPECT_EQ(reset_expected, pref_store_contents_->Get(
+ user_prefs::kPreferenceResetTime, NULL));
+ }
+
+ // Calls FilterOnLoad() on |pref_hash_Filter_|. |pref_store_contents_| is
+ // handed off, but should be given back to us synchronously through
+ // GetPrefsBack() as there is no FilterOnLoadInterceptor installed on
+ // |pref_hash_filter_|.
+ void DoFilterOnLoad(bool expect_prefs_modifications) {
+ pref_hash_filter_->FilterOnLoad(
+ base::Bind(&PrefHashFilterTest::GetPrefsBack, base::Unretained(this),
+ expect_prefs_modifications),
+ std::move(pref_store_contents_));
+ }
+
+ MockPrefHashStore* mock_pref_hash_store_;
+ MockPrefHashStore* mock_external_validation_pref_hash_store_;
+ MockHashStoreContents* mock_external_validation_hash_store_contents_;
+ std::unique_ptr<base::DictionaryValue> pref_store_contents_;
+ scoped_refptr<MockValidationDelegateRecord> mock_validation_delegate_record_;
+ std::unique_ptr<PrefHashFilter> pref_hash_filter_;
+
+ private:
+ // Stores |prefs| back in |pref_store_contents| and ensure
+ // |expected_schedule_write| matches the reported |schedule_write|.
+ void GetPrefsBack(bool expected_schedule_write,
+ std::unique_ptr<base::DictionaryValue> prefs,
+ bool schedule_write) {
+ pref_store_contents_ = std::move(prefs);
+ EXPECT_TRUE(pref_store_contents_);
+ EXPECT_EQ(expected_schedule_write, schedule_write);
+ }
+
+ void OnResetOnLoad() override {
+ // As-is |reset_recorded_| is only designed to remember a single reset, make
+ // sure none was previously recorded.
+ EXPECT_FALSE(reset_recorded_);
+ reset_recorded_ = true;
+ }
+
+ base::MessageLoop message_loop_;
+ MockValidationDelegate mock_validation_delegate_;
+ mojo::BindingSet<prefs::mojom::ResetOnLoadObserver>
+ reset_on_load_observer_bindings_;
+ bool reset_recorded_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrefHashFilterTest);
+};
+
+TEST_P(PrefHashFilterTest, EmptyAndUnchanged) {
+ DoFilterOnLoad(false);
+ // All paths checked.
+ ASSERT_EQ(arraysize(kTestTrackedPrefs),
+ mock_pref_hash_store_->checked_paths_count());
+ // No paths stored, since they all return |UNCHANGED|.
+ ASSERT_EQ(0u, mock_pref_hash_store_->stored_paths_count());
+ // Since there was nothing in |pref_store_contents_| the checked value should
+ // have been NULL for all tracked preferences.
+ for (size_t i = 0; i < arraysize(kTestTrackedPrefs); ++i) {
+ ASSERT_EQ(
+ NULL,
+ mock_pref_hash_store_->checked_value(kTestTrackedPrefs[i].name).first);
+ }
+ ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed());
+ VerifyRecordedReset(false);
+
+ // Delegate saw all paths, and all unchanged.
+ ASSERT_EQ(arraysize(kTestTrackedPrefs),
+ mock_validation_delegate_record_->recorded_validations_count());
+ ASSERT_EQ(arraysize(kTestTrackedPrefs),
+ mock_validation_delegate_record_->CountValidationsOfState(
+ ValueState::UNCHANGED));
+}
+
+TEST_P(PrefHashFilterTest, StampSuperMACAltersStore) {
+ mock_pref_hash_store_->set_stamp_super_mac_result(true);
+ DoFilterOnLoad(true);
+ // No paths stored, since they all return |UNCHANGED|. The StampSuperMAC
+ // result is the only reason the prefs were considered altered.
+ ASSERT_EQ(0u, mock_pref_hash_store_->stored_paths_count());
+}
+
+TEST_P(PrefHashFilterTest, FilterTrackedPrefUpdate) {
+ base::DictionaryValue root_dict;
+ // Ownership of |string_value| is transfered to |root_dict|.
+ base::Value* string_value = new base::Value("string value");
+ root_dict.Set(kAtomicPref, string_value);
+
+ // No path should be stored on FilterUpdate.
+ pref_hash_filter_->FilterUpdate(kAtomicPref);
+ ASSERT_EQ(0u, mock_pref_hash_store_->stored_paths_count());
+
+ // One path should be stored on FilterSerializeData.
+ pref_hash_filter_->FilterSerializeData(&root_dict);
+ ASSERT_EQ(1u, mock_pref_hash_store_->stored_paths_count());
+ MockPrefHashStore::ValuePtrStrategyPair stored_value =
+ mock_pref_hash_store_->stored_value(kAtomicPref);
+ ASSERT_EQ(string_value, stored_value.first);
+ ASSERT_EQ(PrefTrackingStrategy::ATOMIC, stored_value.second);
+
+ ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed());
+ VerifyRecordedReset(false);
+}
+
+TEST_P(PrefHashFilterTest, ReportSuperMacValidity) {
+ // Do this once just to force the histogram to be defined.
+ DoFilterOnLoad(false);
+
+ base::HistogramBase* histogram = base::StatisticsRecorder::FindHistogram(
+ "Settings.HashesDictionaryTrusted");
+ ASSERT_TRUE(histogram);
+
+ base::HistogramBase::Count initial_untrusted =
+ histogram->SnapshotSamples()->GetCount(0);
+ base::HistogramBase::Count initial_trusted =
+ histogram->SnapshotSamples()->GetCount(1);
+
+ Reset();
+
+ // Run with an invalid super MAC.
+ mock_pref_hash_store_->set_is_super_mac_valid_result(false);
+
+ DoFilterOnLoad(false);
+
+ // Verify that the invalidity was reported.
+ ASSERT_EQ(initial_untrusted + 1, histogram->SnapshotSamples()->GetCount(0));
+ ASSERT_EQ(initial_trusted, histogram->SnapshotSamples()->GetCount(1));
+
+ Reset();
+
+ // Run with a valid super MAC.
+ mock_pref_hash_store_->set_is_super_mac_valid_result(true);
+
+ DoFilterOnLoad(false);
+
+ // Verify that the validity was reported.
+ ASSERT_EQ(initial_untrusted + 1, histogram->SnapshotSamples()->GetCount(0));
+ ASSERT_EQ(initial_trusted + 1, histogram->SnapshotSamples()->GetCount(1));
+}
+
+TEST_P(PrefHashFilterTest, FilterSplitPrefUpdate) {
+ base::DictionaryValue root_dict;
+ // Ownership of |dict_value| is transfered to |root_dict|.
+ base::DictionaryValue* dict_value = new base::DictionaryValue;
+ dict_value->SetString("a", "foo");
+ dict_value->SetInteger("b", 1234);
+ root_dict.Set(kSplitPref, dict_value);
+
+ // No path should be stored on FilterUpdate.
+ pref_hash_filter_->FilterUpdate(kSplitPref);
+ ASSERT_EQ(0u, mock_pref_hash_store_->stored_paths_count());
+
+ // One path should be stored on FilterSerializeData.
+ pref_hash_filter_->FilterSerializeData(&root_dict);
+ ASSERT_EQ(1u, mock_pref_hash_store_->stored_paths_count());
+ MockPrefHashStore::ValuePtrStrategyPair stored_value =
+ mock_pref_hash_store_->stored_value(kSplitPref);
+ ASSERT_EQ(dict_value, stored_value.first);
+ ASSERT_EQ(PrefTrackingStrategy::SPLIT, stored_value.second);
+
+ ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed());
+ VerifyRecordedReset(false);
+}
+
+TEST_P(PrefHashFilterTest, FilterUntrackedPrefUpdate) {
+ base::DictionaryValue root_dict;
+ root_dict.Set("untracked", new base::Value("some value"));
+ pref_hash_filter_->FilterUpdate("untracked");
+
+ // No paths should be stored on FilterUpdate.
+ ASSERT_EQ(0u, mock_pref_hash_store_->stored_paths_count());
+
+ // Nor on FilterSerializeData.
+ pref_hash_filter_->FilterSerializeData(&root_dict);
+ ASSERT_EQ(0u, mock_pref_hash_store_->stored_paths_count());
+
+ // No transaction should even be started on FilterSerializeData() if there are
+ // no updates to perform.
+ ASSERT_EQ(0u, mock_pref_hash_store_->transactions_performed());
+}
+
+TEST_P(PrefHashFilterTest, MultiplePrefsFilterSerializeData) {
+ base::DictionaryValue root_dict;
+ // Ownership of the following values is transfered to |root_dict|.
+ base::Value* int_value1 = new base::Value(1);
+ base::Value* int_value2 = new base::Value(2);
+ base::Value* int_value3 = new base::Value(3);
+ base::Value* int_value4 = new base::Value(4);
+ base::DictionaryValue* dict_value = new base::DictionaryValue;
+ dict_value->Set("a", new base::Value(true));
+ root_dict.Set(kAtomicPref, int_value1);
+ root_dict.Set(kAtomicPref2, int_value2);
+ root_dict.Set(kAtomicPref3, int_value3);
+ root_dict.Set("untracked", int_value4);
+ root_dict.Set(kSplitPref, dict_value);
+
+ // Only update kAtomicPref, kAtomicPref3, and kSplitPref.
+ pref_hash_filter_->FilterUpdate(kAtomicPref);
+ pref_hash_filter_->FilterUpdate(kAtomicPref3);
+ pref_hash_filter_->FilterUpdate(kSplitPref);
+ ASSERT_EQ(0u, mock_pref_hash_store_->stored_paths_count());
+
+ // Update kAtomicPref3 again, nothing should be stored still.
+ base::Value* int_value5 = new base::Value(5);
+ root_dict.Set(kAtomicPref3, int_value5);
+ ASSERT_EQ(0u, mock_pref_hash_store_->stored_paths_count());
+
+ // On FilterSerializeData, only kAtomicPref, kAtomicPref3, and kSplitPref
+ // should get a new hash.
+ pref_hash_filter_->FilterSerializeData(&root_dict);
+ ASSERT_EQ(3u, mock_pref_hash_store_->stored_paths_count());
+ MockPrefHashStore::ValuePtrStrategyPair stored_value_atomic1 =
+ mock_pref_hash_store_->stored_value(kAtomicPref);
+ ASSERT_EQ(int_value1, stored_value_atomic1.first);
+ ASSERT_EQ(PrefTrackingStrategy::ATOMIC, stored_value_atomic1.second);
+ ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed());
+
+ MockPrefHashStore::ValuePtrStrategyPair stored_value_atomic3 =
+ mock_pref_hash_store_->stored_value(kAtomicPref3);
+ ASSERT_EQ(int_value5, stored_value_atomic3.first);
+ ASSERT_EQ(PrefTrackingStrategy::ATOMIC, stored_value_atomic3.second);
+
+ MockPrefHashStore::ValuePtrStrategyPair stored_value_split =
+ mock_pref_hash_store_->stored_value(kSplitPref);
+ ASSERT_EQ(dict_value, stored_value_split.first);
+ ASSERT_EQ(PrefTrackingStrategy::SPLIT, stored_value_split.second);
+}
+
+TEST_P(PrefHashFilterTest, UnknownNullValue) {
+ ASSERT_FALSE(pref_store_contents_->Get(kAtomicPref, NULL));
+ ASSERT_FALSE(pref_store_contents_->Get(kSplitPref, NULL));
+ // NULL values are always trusted by the PrefHashStore.
+ mock_pref_hash_store_->SetCheckResult(kAtomicPref,
+ ValueState::TRUSTED_NULL_VALUE);
+ mock_pref_hash_store_->SetCheckResult(kSplitPref,
+ ValueState::TRUSTED_NULL_VALUE);
+ DoFilterOnLoad(false);
+ ASSERT_EQ(arraysize(kTestTrackedPrefs),
+ mock_pref_hash_store_->checked_paths_count());
+ ASSERT_EQ(2u, mock_pref_hash_store_->stored_paths_count());
+ ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed());
+
+ MockPrefHashStore::ValuePtrStrategyPair stored_atomic_value =
+ mock_pref_hash_store_->stored_value(kAtomicPref);
+ ASSERT_EQ(NULL, stored_atomic_value.first);
+ ASSERT_EQ(PrefTrackingStrategy::ATOMIC, stored_atomic_value.second);
+
+ MockPrefHashStore::ValuePtrStrategyPair stored_split_value =
+ mock_pref_hash_store_->stored_value(kSplitPref);
+ ASSERT_EQ(NULL, stored_split_value.first);
+ ASSERT_EQ(PrefTrackingStrategy::SPLIT, stored_split_value.second);
+
+ // Delegate saw all prefs, two of which had the expected value_state.
+ ASSERT_EQ(arraysize(kTestTrackedPrefs),
+ mock_validation_delegate_record_->recorded_validations_count());
+ ASSERT_EQ(2u, mock_validation_delegate_record_->CountValidationsOfState(
+ ValueState::TRUSTED_NULL_VALUE));
+ ASSERT_EQ(arraysize(kTestTrackedPrefs) - 2u,
+ mock_validation_delegate_record_->CountValidationsOfState(
+ ValueState::UNCHANGED));
+
+ const MockValidationDelegateRecord::ValidationEvent* validated_split_pref =
+ mock_validation_delegate_record_->GetEventForPath(kSplitPref);
+ ASSERT_EQ(PrefTrackingStrategy::SPLIT, validated_split_pref->strategy);
+ ASSERT_FALSE(validated_split_pref->is_personal);
+ const MockValidationDelegateRecord::ValidationEvent* validated_atomic_pref =
+ mock_validation_delegate_record_->GetEventForPath(kAtomicPref);
+ ASSERT_EQ(PrefTrackingStrategy::ATOMIC, validated_atomic_pref->strategy);
+ ASSERT_TRUE(validated_atomic_pref->is_personal);
+}
+
+TEST_P(PrefHashFilterTest, InitialValueUnknown) {
+ // Ownership of these values is transfered to |pref_store_contents_|.
+ base::Value* string_value = new base::Value("string value");
+ pref_store_contents_->Set(kAtomicPref, string_value);
+
+ base::DictionaryValue* dict_value = new base::DictionaryValue;
+ dict_value->SetString("a", "foo");
+ dict_value->SetInteger("b", 1234);
+ pref_store_contents_->Set(kSplitPref, dict_value);
+
+ ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref, NULL));
+ ASSERT_TRUE(pref_store_contents_->Get(kSplitPref, NULL));
+
+ mock_pref_hash_store_->SetCheckResult(kAtomicPref,
+ ValueState::UNTRUSTED_UNKNOWN_VALUE);
+ mock_pref_hash_store_->SetCheckResult(kSplitPref,
+ ValueState::UNTRUSTED_UNKNOWN_VALUE);
+ // If we are enforcing, expect this to report changes.
+ DoFilterOnLoad(GetParam() >= EnforcementLevel::ENFORCE_ON_LOAD);
+ ASSERT_EQ(arraysize(kTestTrackedPrefs),
+ mock_pref_hash_store_->checked_paths_count());
+ ASSERT_EQ(2u, mock_pref_hash_store_->stored_paths_count());
+ ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed());
+
+ // Delegate saw all prefs, two of which had the expected value_state.
+ ASSERT_EQ(arraysize(kTestTrackedPrefs),
+ mock_validation_delegate_record_->recorded_validations_count());
+ ASSERT_EQ(2u, mock_validation_delegate_record_->CountValidationsOfState(
+ ValueState::UNTRUSTED_UNKNOWN_VALUE));
+ ASSERT_EQ(arraysize(kTestTrackedPrefs) - 2u,
+ mock_validation_delegate_record_->CountValidationsOfState(
+ ValueState::UNCHANGED));
+
+ MockPrefHashStore::ValuePtrStrategyPair stored_atomic_value =
+ mock_pref_hash_store_->stored_value(kAtomicPref);
+ MockPrefHashStore::ValuePtrStrategyPair stored_split_value =
+ mock_pref_hash_store_->stored_value(kSplitPref);
+ ASSERT_EQ(PrefTrackingStrategy::ATOMIC, stored_atomic_value.second);
+ ASSERT_EQ(PrefTrackingStrategy::SPLIT, stored_split_value.second);
+ if (GetParam() == EnforcementLevel::ENFORCE_ON_LOAD) {
+ // Ensure the prefs were cleared and the hashes for NULL were restored if
+ // the current enforcement level denies seeding.
+ ASSERT_FALSE(pref_store_contents_->Get(kAtomicPref, NULL));
+ ASSERT_EQ(NULL, stored_atomic_value.first);
+
+ ASSERT_FALSE(pref_store_contents_->Get(kSplitPref, NULL));
+ ASSERT_EQ(NULL, stored_split_value.first);
+
+ VerifyRecordedReset(true);
+ } else {
+ // Otherwise the values should have remained intact and the hashes should
+ // have been updated to match them.
+ const base::Value* atomic_value_in_store;
+ ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref, &atomic_value_in_store));
+ ASSERT_EQ(string_value, atomic_value_in_store);
+ ASSERT_EQ(string_value, stored_atomic_value.first);
+
+ const base::Value* split_value_in_store;
+ ASSERT_TRUE(pref_store_contents_->Get(kSplitPref, &split_value_in_store));
+ ASSERT_EQ(dict_value, split_value_in_store);
+ ASSERT_EQ(dict_value, stored_split_value.first);
+
+ VerifyRecordedReset(false);
+ }
+}
+
+TEST_P(PrefHashFilterTest, InitialValueTrustedUnknown) {
+ // Ownership of this value is transfered to |pref_store_contents_|.
+ base::Value* string_value = new base::Value("test");
+ pref_store_contents_->Set(kAtomicPref, string_value);
+
+ base::DictionaryValue* dict_value = new base::DictionaryValue;
+ dict_value->SetString("a", "foo");
+ dict_value->SetInteger("b", 1234);
+ pref_store_contents_->Set(kSplitPref, dict_value);
+
+ ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref, NULL));
+ ASSERT_TRUE(pref_store_contents_->Get(kSplitPref, NULL));
+
+ mock_pref_hash_store_->SetCheckResult(kAtomicPref,
+ ValueState::TRUSTED_UNKNOWN_VALUE);
+ mock_pref_hash_store_->SetCheckResult(kSplitPref,
+ ValueState::TRUSTED_UNKNOWN_VALUE);
+ DoFilterOnLoad(false);
+ ASSERT_EQ(arraysize(kTestTrackedPrefs),
+ mock_pref_hash_store_->checked_paths_count());
+ ASSERT_EQ(2u, mock_pref_hash_store_->stored_paths_count());
+ ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed());
+
+ // Delegate saw all prefs, two of which had the expected value_state.
+ ASSERT_EQ(arraysize(kTestTrackedPrefs),
+ mock_validation_delegate_record_->recorded_validations_count());
+ ASSERT_EQ(2u, mock_validation_delegate_record_->CountValidationsOfState(
+ ValueState::TRUSTED_UNKNOWN_VALUE));
+ ASSERT_EQ(arraysize(kTestTrackedPrefs) - 2u,
+ mock_validation_delegate_record_->CountValidationsOfState(
+ ValueState::UNCHANGED));
+
+ // Seeding is always allowed for trusted unknown values.
+ const base::Value* atomic_value_in_store;
+ ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref, &atomic_value_in_store));
+ ASSERT_EQ(string_value, atomic_value_in_store);
+ MockPrefHashStore::ValuePtrStrategyPair stored_atomic_value =
+ mock_pref_hash_store_->stored_value(kAtomicPref);
+ ASSERT_EQ(string_value, stored_atomic_value.first);
+ ASSERT_EQ(PrefTrackingStrategy::ATOMIC, stored_atomic_value.second);
+
+ const base::Value* split_value_in_store;
+ ASSERT_TRUE(pref_store_contents_->Get(kSplitPref, &split_value_in_store));
+ ASSERT_EQ(dict_value, split_value_in_store);
+ MockPrefHashStore::ValuePtrStrategyPair stored_split_value =
+ mock_pref_hash_store_->stored_value(kSplitPref);
+ ASSERT_EQ(dict_value, stored_split_value.first);
+ ASSERT_EQ(PrefTrackingStrategy::SPLIT, stored_split_value.second);
+}
+
+TEST_P(PrefHashFilterTest, InitialValueChanged) {
+ // Ownership of this value is transfered to |pref_store_contents_|.
+ base::Value* int_value = new base::Value(1234);
+ pref_store_contents_->Set(kAtomicPref, int_value);
+
+ base::DictionaryValue* dict_value = new base::DictionaryValue;
+ dict_value->SetString("a", "foo");
+ dict_value->SetInteger("b", 1234);
+ dict_value->SetInteger("c", 56);
+ dict_value->SetBoolean("d", false);
+ pref_store_contents_->Set(kSplitPref, dict_value);
+
+ ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref, NULL));
+ ASSERT_TRUE(pref_store_contents_->Get(kSplitPref, NULL));
+
+ mock_pref_hash_store_->SetCheckResult(kAtomicPref, ValueState::CHANGED);
+ mock_pref_hash_store_->SetCheckResult(kSplitPref, ValueState::CHANGED);
+
+ std::vector<std::string> mock_invalid_keys;
+ mock_invalid_keys.push_back("a");
+ mock_invalid_keys.push_back("c");
+ mock_pref_hash_store_->SetInvalidKeysResult(kSplitPref, mock_invalid_keys);
+
+ DoFilterOnLoad(GetParam() >= EnforcementLevel::ENFORCE_ON_LOAD);
+ ASSERT_EQ(arraysize(kTestTrackedPrefs),
+ mock_pref_hash_store_->checked_paths_count());
+ ASSERT_EQ(2u, mock_pref_hash_store_->stored_paths_count());
+ ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed());
+
+ MockPrefHashStore::ValuePtrStrategyPair stored_atomic_value =
+ mock_pref_hash_store_->stored_value(kAtomicPref);
+ MockPrefHashStore::ValuePtrStrategyPair stored_split_value =
+ mock_pref_hash_store_->stored_value(kSplitPref);
+ ASSERT_EQ(PrefTrackingStrategy::ATOMIC, stored_atomic_value.second);
+ ASSERT_EQ(PrefTrackingStrategy::SPLIT, stored_split_value.second);
+ if (GetParam() == EnforcementLevel::ENFORCE_ON_LOAD) {
+ // Ensure the atomic pref was cleared and the hash for NULL was restored if
+ // the current enforcement level prevents changes.
+ ASSERT_FALSE(pref_store_contents_->Get(kAtomicPref, NULL));
+ ASSERT_EQ(NULL, stored_atomic_value.first);
+
+ // The split pref on the other hand should only have been stripped of its
+ // invalid keys.
+ const base::Value* split_value_in_store;
+ ASSERT_TRUE(pref_store_contents_->Get(kSplitPref, &split_value_in_store));
+ ASSERT_EQ(2U, dict_value->size());
+ ASSERT_FALSE(dict_value->HasKey("a"));
+ ASSERT_TRUE(dict_value->HasKey("b"));
+ ASSERT_FALSE(dict_value->HasKey("c"));
+ ASSERT_TRUE(dict_value->HasKey("d"));
+ ASSERT_EQ(dict_value, stored_split_value.first);
+
+ VerifyRecordedReset(true);
+ } else {
+ // Otherwise the value should have remained intact and the hash should have
+ // been updated to match it.
+ const base::Value* atomic_value_in_store;
+ ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref, &atomic_value_in_store));
+ ASSERT_EQ(int_value, atomic_value_in_store);
+ ASSERT_EQ(int_value, stored_atomic_value.first);
+
+ const base::Value* split_value_in_store;
+ ASSERT_TRUE(pref_store_contents_->Get(kSplitPref, &split_value_in_store));
+ ASSERT_EQ(dict_value, split_value_in_store);
+ ASSERT_EQ(4U, dict_value->size());
+ ASSERT_TRUE(dict_value->HasKey("a"));
+ ASSERT_TRUE(dict_value->HasKey("b"));
+ ASSERT_TRUE(dict_value->HasKey("c"));
+ ASSERT_TRUE(dict_value->HasKey("d"));
+ ASSERT_EQ(dict_value, stored_split_value.first);
+
+ VerifyRecordedReset(false);
+ }
+}
+
+TEST_P(PrefHashFilterTest, EmptyCleared) {
+ ASSERT_FALSE(pref_store_contents_->Get(kAtomicPref, NULL));
+ ASSERT_FALSE(pref_store_contents_->Get(kSplitPref, NULL));
+ mock_pref_hash_store_->SetCheckResult(kAtomicPref, ValueState::CLEARED);
+ mock_pref_hash_store_->SetCheckResult(kSplitPref, ValueState::CLEARED);
+ DoFilterOnLoad(false);
+ ASSERT_EQ(arraysize(kTestTrackedPrefs),
+ mock_pref_hash_store_->checked_paths_count());
+ ASSERT_EQ(2u, mock_pref_hash_store_->stored_paths_count());
+ ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed());
+
+ // Delegate saw all prefs, two of which had the expected value_state.
+ ASSERT_EQ(arraysize(kTestTrackedPrefs),
+ mock_validation_delegate_record_->recorded_validations_count());
+ ASSERT_EQ(2u, mock_validation_delegate_record_->CountValidationsOfState(
+ ValueState::CLEARED));
+ ASSERT_EQ(arraysize(kTestTrackedPrefs) - 2u,
+ mock_validation_delegate_record_->CountValidationsOfState(
+ ValueState::UNCHANGED));
+
+ // Regardless of the enforcement level, the only thing that should be done is
+ // to restore the hash for NULL. The value itself should still be NULL.
+ ASSERT_FALSE(pref_store_contents_->Get(kAtomicPref, NULL));
+ MockPrefHashStore::ValuePtrStrategyPair stored_atomic_value =
+ mock_pref_hash_store_->stored_value(kAtomicPref);
+ ASSERT_EQ(NULL, stored_atomic_value.first);
+ ASSERT_EQ(PrefTrackingStrategy::ATOMIC, stored_atomic_value.second);
+
+ ASSERT_FALSE(pref_store_contents_->Get(kSplitPref, NULL));
+ MockPrefHashStore::ValuePtrStrategyPair stored_split_value =
+ mock_pref_hash_store_->stored_value(kSplitPref);
+ ASSERT_EQ(NULL, stored_split_value.first);
+ ASSERT_EQ(PrefTrackingStrategy::SPLIT, stored_split_value.second);
+}
+
+TEST_P(PrefHashFilterTest, InitialValueUnchangedLegacyId) {
+ // Ownership of these values is transfered to |pref_store_contents_|.
+ base::Value* string_value = new base::Value("string value");
+ pref_store_contents_->Set(kAtomicPref, string_value);
+
+ base::DictionaryValue* dict_value = new base::DictionaryValue;
+ dict_value->SetString("a", "foo");
+ dict_value->SetInteger("b", 1234);
+ pref_store_contents_->Set(kSplitPref, dict_value);
+
+ ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref, NULL));
+ ASSERT_TRUE(pref_store_contents_->Get(kSplitPref, NULL));
+
+ mock_pref_hash_store_->SetCheckResult(kAtomicPref, ValueState::SECURE_LEGACY);
+ mock_pref_hash_store_->SetCheckResult(kSplitPref, ValueState::SECURE_LEGACY);
+ DoFilterOnLoad(false);
+ ASSERT_EQ(arraysize(kTestTrackedPrefs),
+ mock_pref_hash_store_->checked_paths_count());
+ ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed());
+
+ // Delegate saw all prefs, two of which had the expected value_state.
+ ASSERT_EQ(arraysize(kTestTrackedPrefs),
+ mock_validation_delegate_record_->recorded_validations_count());
+ ASSERT_EQ(2u, mock_validation_delegate_record_->CountValidationsOfState(
+ ValueState::SECURE_LEGACY));
+ ASSERT_EQ(arraysize(kTestTrackedPrefs) - 2u,
+ mock_validation_delegate_record_->CountValidationsOfState(
+ ValueState::UNCHANGED));
+
+ // Ensure that both the atomic and split hashes were restored.
+ ASSERT_EQ(2u, mock_pref_hash_store_->stored_paths_count());
+
+ // In all cases, the values should have remained intact and the hashes should
+ // have been updated to match them.
+
+ MockPrefHashStore::ValuePtrStrategyPair stored_atomic_value =
+ mock_pref_hash_store_->stored_value(kAtomicPref);
+ ASSERT_EQ(PrefTrackingStrategy::ATOMIC, stored_atomic_value.second);
+ const base::Value* atomic_value_in_store;
+ ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref, &atomic_value_in_store));
+ ASSERT_EQ(string_value, atomic_value_in_store);
+ ASSERT_EQ(string_value, stored_atomic_value.first);
+
+ MockPrefHashStore::ValuePtrStrategyPair stored_split_value =
+ mock_pref_hash_store_->stored_value(kSplitPref);
+ ASSERT_EQ(PrefTrackingStrategy::SPLIT, stored_split_value.second);
+ const base::Value* split_value_in_store;
+ ASSERT_TRUE(pref_store_contents_->Get(kSplitPref, &split_value_in_store));
+ ASSERT_EQ(dict_value, split_value_in_store);
+ ASSERT_EQ(dict_value, stored_split_value.first);
+
+ VerifyRecordedReset(false);
+}
+
+TEST_P(PrefHashFilterTest, DontResetReportOnly) {
+ // Ownership of these values is transfered to |pref_store_contents_|.
+ base::Value* int_value1 = new base::Value(1);
+ base::Value* int_value2 = new base::Value(2);
+ base::Value* report_only_val = new base::Value(3);
+ base::DictionaryValue* report_only_split_val = new base::DictionaryValue;
+ report_only_split_val->SetInteger("a", 1234);
+ pref_store_contents_->Set(kAtomicPref, int_value1);
+ pref_store_contents_->Set(kAtomicPref2, int_value2);
+ pref_store_contents_->Set(kReportOnlyPref, report_only_val);
+ pref_store_contents_->Set(kReportOnlySplitPref, report_only_split_val);
+
+ ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref, NULL));
+ ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref2, NULL));
+ ASSERT_TRUE(pref_store_contents_->Get(kReportOnlyPref, NULL));
+ ASSERT_TRUE(pref_store_contents_->Get(kReportOnlySplitPref, NULL));
+
+ mock_pref_hash_store_->SetCheckResult(kAtomicPref, ValueState::CHANGED);
+ mock_pref_hash_store_->SetCheckResult(kAtomicPref2, ValueState::CHANGED);
+ mock_pref_hash_store_->SetCheckResult(kReportOnlyPref, ValueState::CHANGED);
+ mock_pref_hash_store_->SetCheckResult(kReportOnlySplitPref,
+ ValueState::CHANGED);
+
+ DoFilterOnLoad(GetParam() >= EnforcementLevel::ENFORCE_ON_LOAD);
+ // All prefs should be checked and a new hash should be stored for each tested
+ // pref.
+ ASSERT_EQ(arraysize(kTestTrackedPrefs),
+ mock_pref_hash_store_->checked_paths_count());
+ ASSERT_EQ(4u, mock_pref_hash_store_->stored_paths_count());
+ ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed());
+
+ // Delegate saw all prefs, four of which had the expected value_state.
+ ASSERT_EQ(arraysize(kTestTrackedPrefs),
+ mock_validation_delegate_record_->recorded_validations_count());
+ ASSERT_EQ(4u, mock_validation_delegate_record_->CountValidationsOfState(
+ ValueState::CHANGED));
+ ASSERT_EQ(arraysize(kTestTrackedPrefs) - 4u,
+ mock_validation_delegate_record_->CountValidationsOfState(
+ ValueState::UNCHANGED));
+
+ // No matter what the enforcement level is, the report only pref should never
+ // be reset.
+ ASSERT_TRUE(pref_store_contents_->Get(kReportOnlyPref, NULL));
+ ASSERT_TRUE(pref_store_contents_->Get(kReportOnlySplitPref, NULL));
+ ASSERT_EQ(report_only_val,
+ mock_pref_hash_store_->stored_value(kReportOnlyPref).first);
+ ASSERT_EQ(report_only_split_val,
+ mock_pref_hash_store_->stored_value(kReportOnlySplitPref).first);
+
+ // All other prefs should have been reset if the enforcement level allows it.
+ if (GetParam() == EnforcementLevel::ENFORCE_ON_LOAD) {
+ ASSERT_FALSE(pref_store_contents_->Get(kAtomicPref, NULL));
+ ASSERT_FALSE(pref_store_contents_->Get(kAtomicPref2, NULL));
+ ASSERT_EQ(NULL, mock_pref_hash_store_->stored_value(kAtomicPref).first);
+ ASSERT_EQ(NULL, mock_pref_hash_store_->stored_value(kAtomicPref2).first);
+
+ VerifyRecordedReset(true);
+ } else {
+ const base::Value* value_in_store;
+ const base::Value* value_in_store2;
+ ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref, &value_in_store));
+ ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref2, &value_in_store2));
+ ASSERT_EQ(int_value1, value_in_store);
+ ASSERT_EQ(int_value1,
+ mock_pref_hash_store_->stored_value(kAtomicPref).first);
+ ASSERT_EQ(int_value2, value_in_store2);
+ ASSERT_EQ(int_value2,
+ mock_pref_hash_store_->stored_value(kAtomicPref2).first);
+
+ VerifyRecordedReset(false);
+ }
+}
+
+TEST_P(PrefHashFilterTest, CallFilterSerializeDataCallbacks) {
+ base::DictionaryValue root_dict;
+ // Ownership of the following values is transfered to |root_dict|.
+ base::Value* int_value1 = new base::Value(1);
+ base::Value* int_value2 = new base::Value(2);
+ base::DictionaryValue* dict_value = new base::DictionaryValue;
+ dict_value->Set("a", new base::Value(true));
+ root_dict.Set(kAtomicPref, int_value1);
+ root_dict.Set(kAtomicPref2, int_value2);
+ root_dict.Set(kSplitPref, dict_value);
+
+ // Skip updating kAtomicPref2.
+ pref_hash_filter_->FilterUpdate(kAtomicPref);
+ pref_hash_filter_->FilterUpdate(kSplitPref);
+
+ PrefHashFilter::OnWriteCallbackPair callbacks =
+ pref_hash_filter_->FilterSerializeData(&root_dict);
+
+ ASSERT_FALSE(callbacks.first.is_null());
+
+ // Prefs should be cleared from the external validation store only once the
+ // before-write callback is run.
+ ASSERT_EQ(
+ 0u, mock_external_validation_hash_store_contents_->cleared_paths_count());
+ callbacks.first.Run();
+ ASSERT_EQ(
+ 2u, mock_external_validation_hash_store_contents_->cleared_paths_count());
+
+ // No pref write should occur before the after-write callback is run.
+ ASSERT_EQ(
+ 0u, mock_external_validation_hash_store_contents_->stored_hashes_count());
+
+ callbacks.second.Run(true);
+
+ ASSERT_EQ(
+ 2u, mock_external_validation_hash_store_contents_->stored_hashes_count());
+ ASSERT_EQ(
+ "atomic mac for: atomic_pref",
+ mock_external_validation_hash_store_contents_->GetStoredMac(kAtomicPref));
+ ASSERT_EQ("split mac for: split_pref/a",
+ mock_external_validation_hash_store_contents_->GetStoredSplitMac(
+ kSplitPref, "a"));
+
+ // The callbacks should write directly to the contents without going through
+ // a pref hash store.
+ ASSERT_EQ(0u,
+ mock_external_validation_pref_hash_store_->stored_paths_count());
+}
+
+TEST_P(PrefHashFilterTest, CallFilterSerializeDataCallbacksWithFailure) {
+ base::DictionaryValue root_dict;
+ // Ownership of the following values is transfered to |root_dict|.
+ base::Value* int_value1 = new base::Value(1);
+ root_dict.Set(kAtomicPref, int_value1);
+
+ // Only update kAtomicPref.
+ pref_hash_filter_->FilterUpdate(kAtomicPref);
+
+ PrefHashFilter::OnWriteCallbackPair callbacks =
+ pref_hash_filter_->FilterSerializeData(&root_dict);
+
+ ASSERT_FALSE(callbacks.first.is_null());
+
+ callbacks.first.Run();
+
+ // The pref should have been cleared from the external validation store.
+ ASSERT_EQ(
+ 1u, mock_external_validation_hash_store_contents_->cleared_paths_count());
+
+ callbacks.second.Run(false);
+
+ // Expect no writes to the external validation hash store contents.
+ ASSERT_EQ(0u,
+ mock_external_validation_pref_hash_store_->stored_paths_count());
+ ASSERT_EQ(
+ 0u, mock_external_validation_hash_store_contents_->stored_hashes_count());
+}
+
+TEST_P(PrefHashFilterTest, ExternalValidationValueChanged) {
+ // Ownership of this value is transfered to |pref_store_contents_|.
+ base::Value* int_value = new base::Value(1234);
+ pref_store_contents_->Set(kAtomicPref, int_value);
+
+ base::DictionaryValue* dict_value = new base::DictionaryValue;
+ dict_value->SetString("a", "foo");
+ dict_value->SetInteger("b", 1234);
+ dict_value->SetInteger("c", 56);
+ dict_value->SetBoolean("d", false);
+ pref_store_contents_->Set(kSplitPref, dict_value);
+
+ mock_external_validation_pref_hash_store_->SetCheckResult(
+ kAtomicPref, ValueState::CHANGED);
+ mock_external_validation_pref_hash_store_->SetCheckResult(
+ kSplitPref, ValueState::CHANGED);
+
+ std::vector<std::string> mock_invalid_keys;
+ mock_invalid_keys.push_back("a");
+ mock_invalid_keys.push_back("c");
+ mock_external_validation_pref_hash_store_->SetInvalidKeysResult(
+ kSplitPref, mock_invalid_keys);
+
+ DoFilterOnLoad(false);
+
+ ASSERT_EQ(arraysize(kTestTrackedPrefs),
+ mock_external_validation_pref_hash_store_->checked_paths_count());
+ ASSERT_EQ(2u,
+ mock_external_validation_pref_hash_store_->stored_paths_count());
+ ASSERT_EQ(
+ 1u, mock_external_validation_pref_hash_store_->transactions_performed());
+
+ ASSERT_EQ(arraysize(kTestTrackedPrefs),
+ mock_validation_delegate_record_->recorded_validations_count());
+
+ // Regular validation should not have any CHANGED prefs.
+ ASSERT_EQ(arraysize(kTestTrackedPrefs),
+ mock_validation_delegate_record_->CountValidationsOfState(
+ ValueState::UNCHANGED));
+
+ // External validation should have two CHANGED prefs (kAtomic and kSplit).
+ ASSERT_EQ(2u,
+ mock_validation_delegate_record_->CountExternalValidationsOfState(
+ ValueState::CHANGED));
+ ASSERT_EQ(arraysize(kTestTrackedPrefs) - 2u,
+ mock_validation_delegate_record_->CountExternalValidationsOfState(
+ ValueState::UNCHANGED));
+}
+
+INSTANTIATE_TEST_CASE_P(PrefHashFilterTestInstance,
+ PrefHashFilterTest,
+ testing::Values(EnforcementLevel::NO_ENFORCEMENT,
+ EnforcementLevel::ENFORCE_ON_LOAD));
diff --git a/chromium/services/preferences/tracked/pref_hash_store.h b/chromium/services/preferences/tracked/pref_hash_store.h
new file mode 100644
index 00000000000..4716834c946
--- /dev/null
+++ b/chromium/services/preferences/tracked/pref_hash_store.h
@@ -0,0 +1,48 @@
+// 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 SERVICES_PREFERENCES_TRACKED_PREF_HASH_STORE_H_
+#define SERVICES_PREFERENCES_TRACKED_PREF_HASH_STORE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/values.h"
+#include "services/preferences/tracked/pref_hash_store_transaction.h"
+
+class HashStoreContents;
+class PrefHashStoreTransaction;
+
+// Holds the configuration and implementation used to calculate and verify
+// preference MACs.
+// TODO(gab): Rename this class as it is no longer a store.
+class PrefHashStore {
+ public:
+ virtual ~PrefHashStore() {}
+
+ // Returns a PrefHashStoreTransaction which can be used to perform a series
+ // of operations on the hash store. |storage| MAY be used as the backing store
+ // depending on the implementation. Therefore the HashStoreContents used for
+ // related transactions should correspond to the same underlying data store.
+ // |storage| must outlive the returned transaction.
+ virtual std::unique_ptr<PrefHashStoreTransaction> BeginTransaction(
+ HashStoreContents* storage) = 0;
+
+ // Computes the MAC to be associated with |path| and |value| in this store.
+ // PrefHashStoreTransaction typically uses this internally but it's also
+ // exposed for users that want to compute MACs ahead of time for asynchronous
+ // operations.
+ virtual std::string ComputeMac(const std::string& path,
+ const base::Value* value) = 0;
+
+ // Computes the MAC to be associated with |path| and |split_values| in this
+ // store. PrefHashStoreTransaction typically uses this internally but it's
+ // also exposed for users that want to compute MACs ahead of time for
+ // asynchronous operations.
+ virtual std::unique_ptr<base::DictionaryValue> ComputeSplitMacs(
+ const std::string& path,
+ const base::DictionaryValue* split_values) = 0;
+};
+
+#endif // SERVICES_PREFERENCES_TRACKED_PREF_HASH_STORE_H_
diff --git a/chromium/services/preferences/tracked/pref_hash_store_impl.cc b/chromium/services/preferences/tracked/pref_hash_store_impl.cc
new file mode 100644
index 00000000000..522b013c5bc
--- /dev/null
+++ b/chromium/services/preferences/tracked/pref_hash_store_impl.cc
@@ -0,0 +1,321 @@
+// 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 "services/preferences/tracked/pref_hash_store_impl.h"
+
+#include <stddef.h>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/metrics/histogram_macros.h"
+#include "services/preferences/tracked/device_id.h"
+#include "services/preferences/tracked/hash_store_contents.h"
+
+namespace {
+
+using ValueState =
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState;
+
+// Returns a deterministic ID for this machine.
+std::string GenerateDeviceId() {
+ static std::string cached_device_id;
+ if (!cached_device_id.empty())
+ return cached_device_id;
+
+ std::string device_id;
+ MachineIdStatus status = GetDeterministicMachineSpecificId(&device_id);
+ if (status != MachineIdStatus::NOT_IMPLEMENTED) {
+ // TODO(proberge): Remove this histogram once we validate that machine id
+ // generation is not flaky and consider adding a CHECK or DCHECK.
+ UMA_HISTOGRAM_BOOLEAN("Settings.MachineIdGenerationSuccess",
+ status == MachineIdStatus::SUCCESS);
+ }
+
+ if (status == MachineIdStatus::SUCCESS) {
+ cached_device_id = device_id;
+ return device_id;
+ }
+
+ return std::string();
+}
+
+} // namespace
+
+class PrefHashStoreImpl::PrefHashStoreTransactionImpl
+ : public PrefHashStoreTransaction {
+ public:
+ // Constructs a PrefHashStoreTransactionImpl which can use the private
+ // members of its |outer| PrefHashStoreImpl.
+ PrefHashStoreTransactionImpl(PrefHashStoreImpl* outer,
+ HashStoreContents* storage);
+ ~PrefHashStoreTransactionImpl() override;
+
+ // PrefHashStoreTransaction implementation.
+ base::StringPiece GetStoreUMASuffix() const override;
+ ValueState CheckValue(const std::string& path,
+ const base::Value* value) const override;
+ void StoreHash(const std::string& path, const base::Value* value) override;
+ ValueState CheckSplitValue(
+ const std::string& path,
+ const base::DictionaryValue* initial_split_value,
+ std::vector<std::string>* invalid_keys) const override;
+ void StoreSplitHash(const std::string& path,
+ const base::DictionaryValue* split_value) override;
+ bool HasHash(const std::string& path) const override;
+ void ImportHash(const std::string& path, const base::Value* hash) override;
+ void ClearHash(const std::string& path) override;
+ bool IsSuperMACValid() const override;
+ bool StampSuperMac() override;
+
+ private:
+ PrefHashStoreImpl* outer_;
+ HashStoreContents* contents_;
+
+ bool super_mac_valid_;
+ bool super_mac_dirty_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrefHashStoreTransactionImpl);
+};
+
+PrefHashStoreImpl::PrefHashStoreImpl(const std::string& seed,
+ const std::string& legacy_device_id,
+ bool use_super_mac)
+ : pref_hash_calculator_(seed, GenerateDeviceId(), legacy_device_id),
+ use_super_mac_(use_super_mac) {}
+
+PrefHashStoreImpl::~PrefHashStoreImpl() {}
+
+std::unique_ptr<PrefHashStoreTransaction> PrefHashStoreImpl::BeginTransaction(
+ HashStoreContents* storage) {
+ return std::unique_ptr<PrefHashStoreTransaction>(
+ new PrefHashStoreTransactionImpl(this, std::move(storage)));
+}
+
+std::string PrefHashStoreImpl::ComputeMac(const std::string& path,
+ const base::Value* value) {
+ return pref_hash_calculator_.Calculate(path, value);
+}
+
+std::unique_ptr<base::DictionaryValue> PrefHashStoreImpl::ComputeSplitMacs(
+ const std::string& path,
+ const base::DictionaryValue* split_values) {
+ DCHECK(split_values);
+
+ std::string keyed_path(path);
+ keyed_path.push_back('.');
+ const size_t common_part_length = keyed_path.length();
+
+ std::unique_ptr<base::DictionaryValue> split_macs(new base::DictionaryValue);
+
+ for (base::DictionaryValue::Iterator it(*split_values); !it.IsAtEnd();
+ it.Advance()) {
+ // Keep the common part from the old |keyed_path| and replace the key to
+ // get the new |keyed_path|.
+ keyed_path.replace(common_part_length, std::string::npos, it.key());
+
+ split_macs->SetStringWithoutPathExpansion(
+ it.key(), ComputeMac(keyed_path, &it.value()));
+ }
+
+ return split_macs;
+}
+
+PrefHashStoreImpl::PrefHashStoreTransactionImpl::PrefHashStoreTransactionImpl(
+ PrefHashStoreImpl* outer,
+ HashStoreContents* storage)
+ : outer_(outer),
+ contents_(std::move(storage)),
+ super_mac_valid_(false),
+ super_mac_dirty_(false) {
+ if (!outer_->use_super_mac_)
+ return;
+
+ // The store must have a valid super MAC to be trusted.
+ std::string super_mac = contents_->GetSuperMac();
+ if (super_mac.empty())
+ return;
+
+ super_mac_valid_ =
+ outer_->pref_hash_calculator_.Validate(
+ "", contents_->GetContents(), super_mac) == PrefHashCalculator::VALID;
+}
+
+PrefHashStoreImpl::PrefHashStoreTransactionImpl::
+ ~PrefHashStoreTransactionImpl() {
+ if (super_mac_dirty_ && outer_->use_super_mac_) {
+ // Get the dictionary of hashes (or NULL if it doesn't exist).
+ const base::DictionaryValue* hashes_dict = contents_->GetContents();
+ contents_->SetSuperMac(outer_->ComputeMac("", hashes_dict));
+ }
+}
+
+base::StringPiece
+PrefHashStoreImpl::PrefHashStoreTransactionImpl::GetStoreUMASuffix() const {
+ return contents_->GetUMASuffix();
+}
+
+ValueState PrefHashStoreImpl::PrefHashStoreTransactionImpl::CheckValue(
+ const std::string& path,
+ const base::Value* initial_value) const {
+ std::string last_hash;
+ contents_->GetMac(path, &last_hash);
+
+ if (last_hash.empty()) {
+ // In the absence of a hash for this pref, always trust a NULL value, but
+ // only trust an existing value if the initial hashes dictionary is trusted.
+ if (!initial_value)
+ return ValueState::TRUSTED_NULL_VALUE;
+ else if (super_mac_valid_)
+ return ValueState::TRUSTED_UNKNOWN_VALUE;
+ else
+ return ValueState::UNTRUSTED_UNKNOWN_VALUE;
+ }
+
+ PrefHashCalculator::ValidationResult validation_result =
+ outer_->pref_hash_calculator_.Validate(path, initial_value, last_hash);
+ switch (validation_result) {
+ case PrefHashCalculator::VALID:
+ return ValueState::UNCHANGED;
+ case PrefHashCalculator::VALID_SECURE_LEGACY:
+ return ValueState::SECURE_LEGACY;
+ case PrefHashCalculator::INVALID:
+ return initial_value ? ValueState::CHANGED : ValueState::CLEARED;
+ }
+ NOTREACHED() << "Unexpected PrefHashCalculator::ValidationResult: "
+ << validation_result;
+ return ValueState::UNTRUSTED_UNKNOWN_VALUE;
+}
+
+void PrefHashStoreImpl::PrefHashStoreTransactionImpl::StoreHash(
+ const std::string& path,
+ const base::Value* new_value) {
+ const std::string mac = outer_->ComputeMac(path, new_value);
+ contents_->SetMac(path, mac);
+ super_mac_dirty_ = true;
+}
+
+ValueState PrefHashStoreImpl::PrefHashStoreTransactionImpl::CheckSplitValue(
+ const std::string& path,
+ const base::DictionaryValue* initial_split_value,
+ std::vector<std::string>* invalid_keys) const {
+ DCHECK(invalid_keys && invalid_keys->empty());
+
+ std::map<std::string, std::string> split_macs;
+ const bool has_hashes = contents_->GetSplitMacs(path, &split_macs);
+
+ // Treat NULL and empty the same; otherwise we would need to store a hash for
+ // the entire dictionary (or some other special beacon) to differentiate these
+ // two cases which are really the same for dictionaries.
+ if (!initial_split_value || initial_split_value->empty())
+ return has_hashes ? ValueState::CLEARED : ValueState::UNCHANGED;
+
+ if (!has_hashes)
+ return super_mac_valid_ ? ValueState::TRUSTED_UNKNOWN_VALUE
+ : ValueState::UNTRUSTED_UNKNOWN_VALUE;
+
+ bool has_secure_legacy_id_hashes = false;
+ std::string keyed_path(path);
+ keyed_path.push_back('.');
+ const size_t common_part_length = keyed_path.length();
+ for (base::DictionaryValue::Iterator it(*initial_split_value); !it.IsAtEnd();
+ it.Advance()) {
+ std::map<std::string, std::string>::iterator entry =
+ split_macs.find(it.key());
+ if (entry == split_macs.end()) {
+ invalid_keys->push_back(it.key());
+ } else {
+ // Keep the common part from the old |keyed_path| and replace the key to
+ // get the new |keyed_path|.
+ keyed_path.replace(common_part_length, std::string::npos, it.key());
+ switch (outer_->pref_hash_calculator_.Validate(keyed_path, &it.value(),
+ entry->second)) {
+ case PrefHashCalculator::VALID:
+ break;
+ case PrefHashCalculator::VALID_SECURE_LEGACY:
+ // Secure legacy device IDs based hashes are still accepted, but we
+ // should make sure to notify the caller for them to update the legacy
+ // hashes.
+ has_secure_legacy_id_hashes = true;
+ break;
+ case PrefHashCalculator::INVALID:
+ invalid_keys->push_back(it.key());
+ break;
+ }
+ // Remove processed MACs, remaining MACs at the end will also be
+ // considered invalid.
+ split_macs.erase(entry);
+ }
+ }
+
+ // Anything left in the map is missing from the data.
+ for (std::map<std::string, std::string>::const_iterator it =
+ split_macs.begin();
+ it != split_macs.end(); ++it) {
+ invalid_keys->push_back(it->first);
+ }
+
+ return invalid_keys->empty()
+ ? (has_secure_legacy_id_hashes ? ValueState::SECURE_LEGACY
+ : ValueState::UNCHANGED)
+ : ValueState::CHANGED;
+}
+
+void PrefHashStoreImpl::PrefHashStoreTransactionImpl::StoreSplitHash(
+ const std::string& path,
+ const base::DictionaryValue* split_value) {
+ contents_->RemoveEntry(path);
+
+ if (split_value) {
+ std::unique_ptr<base::DictionaryValue> split_macs =
+ outer_->ComputeSplitMacs(path, split_value);
+
+ for (base::DictionaryValue::Iterator it(*split_macs); !it.IsAtEnd();
+ it.Advance()) {
+ const base::Value* value_as_string;
+ bool is_string = it.value().GetAsString(&value_as_string);
+ DCHECK(is_string);
+
+ contents_->SetSplitMac(path, it.key(), value_as_string->GetString());
+ }
+ }
+ super_mac_dirty_ = true;
+}
+
+bool PrefHashStoreImpl::PrefHashStoreTransactionImpl::HasHash(
+ const std::string& path) const {
+ std::string out_value;
+ std::map<std::string, std::string> out_values;
+ return contents_->GetMac(path, &out_value) ||
+ contents_->GetSplitMacs(path, &out_values);
+}
+
+void PrefHashStoreImpl::PrefHashStoreTransactionImpl::ImportHash(
+ const std::string& path,
+ const base::Value* hash) {
+ DCHECK(hash);
+
+ contents_->ImportEntry(path, hash);
+
+ if (super_mac_valid_)
+ super_mac_dirty_ = true;
+}
+
+void PrefHashStoreImpl::PrefHashStoreTransactionImpl::ClearHash(
+ const std::string& path) {
+ if (contents_->RemoveEntry(path) && super_mac_valid_) {
+ super_mac_dirty_ = true;
+ }
+}
+
+bool PrefHashStoreImpl::PrefHashStoreTransactionImpl::IsSuperMACValid() const {
+ return super_mac_valid_;
+}
+
+bool PrefHashStoreImpl::PrefHashStoreTransactionImpl::StampSuperMac() {
+ if (!outer_->use_super_mac_ || super_mac_valid_)
+ return false;
+ super_mac_dirty_ = true;
+ return true;
+}
diff --git a/chromium/services/preferences/tracked/pref_hash_store_impl.h b/chromium/services/preferences/tracked/pref_hash_store_impl.h
new file mode 100644
index 00000000000..8e7ae8a796c
--- /dev/null
+++ b/chromium/services/preferences/tracked/pref_hash_store_impl.h
@@ -0,0 +1,61 @@
+// 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 SERVICES_PREFERENCES_TRACKED_PREF_HASH_STORE_IMPL_H_
+#define SERVICES_PREFERENCES_TRACKED_PREF_HASH_STORE_IMPL_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "services/preferences/tracked/pref_hash_calculator.h"
+#include "services/preferences/tracked/pref_hash_store.h"
+
+// Implements PrefHashStoreImpl by storing preference hashes in a
+// HashStoreContents.
+class PrefHashStoreImpl : public PrefHashStore {
+ public:
+ enum StoreVersion {
+ // No hashes have been stored in this PrefHashStore yet.
+ VERSION_UNINITIALIZED = 0,
+ // The hashes in this PrefHashStore were stored before the introduction
+ // of a version number and should be re-initialized.
+ VERSION_PRE_MIGRATION = 1,
+ // The hashes in this PrefHashStore were stored using the latest algorithm.
+ VERSION_LATEST = 2,
+ };
+
+ // Constructs a PrefHashStoreImpl that calculates hashes using
+ // |seed| and |legacy_device_id| and stores them in |contents|.
+ //
+ // The same |seed| and |legacy_device_id| must be used to load and validate
+ // previously stored hashes in |contents|.
+ PrefHashStoreImpl(const std::string& seed,
+ const std::string& legacy_device_id,
+ bool use_super_mac);
+
+ ~PrefHashStoreImpl() override;
+
+ // Clears the contents of this PrefHashStore. |IsInitialized()| will return
+ // false after this call.
+ void Reset();
+
+ // PrefHashStore implementation.
+ std::unique_ptr<PrefHashStoreTransaction> BeginTransaction(
+ HashStoreContents* storage) override;
+
+ std::string ComputeMac(const std::string& path,
+ const base::Value* new_value) override;
+ std::unique_ptr<base::DictionaryValue> ComputeSplitMacs(
+ const std::string& path,
+ const base::DictionaryValue* split_values) override;
+
+ private:
+ class PrefHashStoreTransactionImpl;
+
+ const PrefHashCalculator pref_hash_calculator_;
+ bool use_super_mac_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrefHashStoreImpl);
+};
+
+#endif // SERVICES_PREFERENCES_TRACKED_PREF_HASH_STORE_IMPL_H_
diff --git a/chromium/services/preferences/tracked/pref_hash_store_impl_unittest.cc b/chromium/services/preferences/tracked/pref_hash_store_impl_unittest.cc
new file mode 100644
index 00000000000..3c6ca13126f
--- /dev/null
+++ b/chromium/services/preferences/tracked/pref_hash_store_impl_unittest.cc
@@ -0,0 +1,507 @@
+// 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 "services/preferences/tracked/pref_hash_store_impl.h"
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/values.h"
+#include "services/preferences/tracked/dictionary_hash_store_contents.h"
+#include "services/preferences/tracked/hash_store_contents.h"
+#include "services/preferences/tracked/pref_hash_store_transaction.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ValueState =
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState;
+
+class PrefHashStoreImplTest : public testing::Test {
+ public:
+ PrefHashStoreImplTest() : contents_(&pref_store_contents_) {}
+
+ protected:
+ HashStoreContents* GetHashStoreContents() { return &contents_; }
+
+ private:
+ base::DictionaryValue pref_store_contents_;
+ // Must be declared after |pref_store_contents_| as it needs to be outlived
+ // by it.
+ DictionaryHashStoreContents contents_;
+
+ DISALLOW_COPY_AND_ASSIGN(PrefHashStoreImplTest);
+};
+
+TEST_F(PrefHashStoreImplTest, ComputeMac) {
+ base::Value string_1("string1");
+ base::Value string_2("string2");
+ PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
+
+ std::string computed_mac_1 = pref_hash_store.ComputeMac("path1", &string_1);
+ std::string computed_mac_2 = pref_hash_store.ComputeMac("path1", &string_2);
+ std::string computed_mac_3 = pref_hash_store.ComputeMac("path2", &string_1);
+
+ // Quick sanity checks here, see pref_hash_calculator_unittest.cc for more
+ // complete tests.
+ EXPECT_EQ(computed_mac_1, pref_hash_store.ComputeMac("path1", &string_1));
+ EXPECT_NE(computed_mac_1, computed_mac_2);
+ EXPECT_NE(computed_mac_1, computed_mac_3);
+ EXPECT_EQ(64U, computed_mac_1.size());
+}
+
+TEST_F(PrefHashStoreImplTest, ComputeSplitMacs) {
+ base::DictionaryValue dict;
+ dict.Set("a", new base::Value("string1"));
+ dict.Set("b", new base::Value("string2"));
+ PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
+
+ std::unique_ptr<base::DictionaryValue> computed_macs =
+ pref_hash_store.ComputeSplitMacs("foo.bar", &dict);
+
+ std::string mac_1;
+ std::string mac_2;
+ ASSERT_TRUE(computed_macs->GetString("a", &mac_1));
+ ASSERT_TRUE(computed_macs->GetString("b", &mac_2));
+
+ EXPECT_EQ(2U, computed_macs->size());
+
+ base::Value string_1("string1");
+ base::Value string_2("string2");
+ EXPECT_EQ(pref_hash_store.ComputeMac("foo.bar.a", &string_1), mac_1);
+ EXPECT_EQ(pref_hash_store.ComputeMac("foo.bar.b", &string_2), mac_2);
+}
+
+TEST_F(PrefHashStoreImplTest, AtomicHashStoreAndCheck) {
+ base::Value string_1("string1");
+ base::Value string_2("string2");
+
+ {
+ // 32 NULL bytes is the seed that was used to generate the legacy hash.
+ PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store.BeginTransaction(GetHashStoreContents()));
+
+ // Only NULL should be trusted in the absence of a hash.
+ EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE,
+ transaction->CheckValue("path1", &string_1));
+ EXPECT_EQ(ValueState::TRUSTED_NULL_VALUE,
+ transaction->CheckValue("path1", NULL));
+
+ transaction->StoreHash("path1", &string_1);
+ EXPECT_EQ(ValueState::UNCHANGED,
+ transaction->CheckValue("path1", &string_1));
+ EXPECT_EQ(ValueState::CLEARED, transaction->CheckValue("path1", NULL));
+ transaction->StoreHash("path1", NULL);
+ EXPECT_EQ(ValueState::UNCHANGED, transaction->CheckValue("path1", NULL));
+ EXPECT_EQ(ValueState::CHANGED, transaction->CheckValue("path1", &string_2));
+
+ base::DictionaryValue dict;
+ dict.Set("a", new base::Value("foo"));
+ dict.Set("d", new base::Value("bad"));
+ dict.Set("b", new base::Value("bar"));
+ dict.Set("c", new base::Value("baz"));
+
+ transaction->StoreHash("path1", &dict);
+ EXPECT_EQ(ValueState::UNCHANGED, transaction->CheckValue("path1", &dict));
+ }
+
+ ASSERT_FALSE(GetHashStoreContents()->GetSuperMac().empty());
+
+ {
+ // |pref_hash_store2| should trust its initial hashes dictionary and thus
+ // trust new unknown values.
+ PrefHashStoreImpl pref_hash_store2(std::string(32, 0), "device_id", true);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store2.BeginTransaction(GetHashStoreContents()));
+ EXPECT_EQ(ValueState::TRUSTED_UNKNOWN_VALUE,
+ transaction->CheckValue("new_path", &string_1));
+ EXPECT_EQ(ValueState::TRUSTED_UNKNOWN_VALUE,
+ transaction->CheckValue("new_path", &string_2));
+ EXPECT_EQ(ValueState::TRUSTED_NULL_VALUE,
+ transaction->CheckValue("new_path", NULL));
+ }
+
+ // Manually corrupt the super MAC.
+ GetHashStoreContents()->SetSuperMac(std::string(64, 'A'));
+
+ {
+ // |pref_hash_store3| should no longer trust its initial hashes dictionary
+ // and thus shouldn't trust non-NULL unknown values.
+ PrefHashStoreImpl pref_hash_store3(std::string(32, 0), "device_id", true);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store3.BeginTransaction(GetHashStoreContents()));
+ EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE,
+ transaction->CheckValue("new_path", &string_1));
+ EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE,
+ transaction->CheckValue("new_path", &string_2));
+ EXPECT_EQ(ValueState::TRUSTED_NULL_VALUE,
+ transaction->CheckValue("new_path", NULL));
+ }
+}
+
+TEST_F(PrefHashStoreImplTest, ImportExportOperations) {
+ base::Value string_1("string1");
+ base::Value string_2("string2");
+
+ // Initial state: no super MAC.
+ {
+ PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store.BeginTransaction(GetHashStoreContents()));
+ ASSERT_FALSE(transaction->IsSuperMACValid());
+
+ ASSERT_FALSE(transaction->HasHash("path1"));
+
+ // Storing a hash will stamp the super MAC.
+ transaction->StoreHash("path1", &string_1);
+
+ ASSERT_TRUE(transaction->HasHash("path1"));
+ EXPECT_EQ(ValueState::UNCHANGED,
+ transaction->CheckValue("path1", &string_1));
+ EXPECT_EQ(ValueState::CHANGED, transaction->CheckValue("path1", &string_2));
+ }
+
+ // Make a copy of the stored hash for future use.
+ const base::Value* hash = NULL;
+ ASSERT_TRUE(GetHashStoreContents()->GetContents()->Get("path1", &hash));
+ std::unique_ptr<base::Value> path_1_string_1_hash_copy(hash->DeepCopy());
+ hash = NULL;
+
+ // Verify that the super MAC was stamped.
+ {
+ PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store.BeginTransaction(GetHashStoreContents()));
+ ASSERT_TRUE(transaction->IsSuperMACValid());
+ ASSERT_TRUE(transaction->HasHash("path1"));
+
+ // Clearing the hash should preserve validity.
+ transaction->ClearHash("path1");
+
+ // The effects of the clear should be immediately visible.
+ ASSERT_FALSE(transaction->HasHash("path1"));
+ EXPECT_EQ(ValueState::TRUSTED_NULL_VALUE,
+ transaction->CheckValue("path1", NULL));
+ EXPECT_EQ(ValueState::TRUSTED_UNKNOWN_VALUE,
+ transaction->CheckValue("path1", &string_1));
+ }
+
+ // Verify that validity was preserved and that the clear took effect.
+ {
+ PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store.BeginTransaction(GetHashStoreContents()));
+ ASSERT_TRUE(transaction->IsSuperMACValid());
+ ASSERT_FALSE(transaction->HasHash("path1"));
+ }
+
+ // Invalidate the super MAC.
+ GetHashStoreContents()->SetSuperMac(std::string());
+
+ {
+ PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store.BeginTransaction(GetHashStoreContents()));
+ ASSERT_FALSE(transaction->IsSuperMACValid());
+ ASSERT_FALSE(transaction->HasHash("path1"));
+
+ // An import should preserve invalidity.
+ transaction->ImportHash("path1", path_1_string_1_hash_copy.get());
+
+ ASSERT_TRUE(transaction->HasHash("path1"));
+
+ // The imported hash should be usable for validating the original value.
+ EXPECT_EQ(ValueState::UNCHANGED,
+ transaction->CheckValue("path1", &string_1));
+ }
+
+ // Verify that invalidity was preserved and that the import took effect.
+ {
+ PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store.BeginTransaction(GetHashStoreContents()));
+ ASSERT_FALSE(transaction->IsSuperMACValid());
+ ASSERT_TRUE(transaction->HasHash("path1"));
+ EXPECT_EQ(ValueState::UNCHANGED,
+ transaction->CheckValue("path1", &string_1));
+
+ // After clearing the hash, non-null values are UNTRUSTED_UNKNOWN.
+ transaction->ClearHash("path1");
+
+ EXPECT_EQ(ValueState::TRUSTED_NULL_VALUE,
+ transaction->CheckValue("path1", NULL));
+ EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE,
+ transaction->CheckValue("path1", &string_1));
+ }
+
+ {
+ PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store.BeginTransaction(GetHashStoreContents()));
+ ASSERT_FALSE(transaction->IsSuperMACValid());
+
+ // Test StampSuperMac.
+ transaction->StampSuperMac();
+ }
+
+ // Verify that the store is now valid.
+ {
+ PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store.BeginTransaction(GetHashStoreContents()));
+ ASSERT_TRUE(transaction->IsSuperMACValid());
+
+ // Store the hash of a different value to test an "over-import".
+ transaction->StoreHash("path1", &string_2);
+ EXPECT_EQ(ValueState::CHANGED, transaction->CheckValue("path1", &string_1));
+ EXPECT_EQ(ValueState::UNCHANGED,
+ transaction->CheckValue("path1", &string_2));
+ }
+
+ {
+ PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store.BeginTransaction(GetHashStoreContents()));
+ ASSERT_TRUE(transaction->IsSuperMACValid());
+
+ // "Over-import". An import should preserve validity.
+ transaction->ImportHash("path1", path_1_string_1_hash_copy.get());
+ EXPECT_EQ(ValueState::UNCHANGED,
+ transaction->CheckValue("path1", &string_1));
+ EXPECT_EQ(ValueState::CHANGED, transaction->CheckValue("path1", &string_2));
+ }
+
+ // Verify that validity was preserved and the "over-import" took effect.
+ {
+ PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store.BeginTransaction(GetHashStoreContents()));
+ ASSERT_TRUE(transaction->IsSuperMACValid());
+ EXPECT_EQ(ValueState::UNCHANGED,
+ transaction->CheckValue("path1", &string_1));
+ EXPECT_EQ(ValueState::CHANGED, transaction->CheckValue("path1", &string_2));
+ }
+}
+
+TEST_F(PrefHashStoreImplTest, SuperMACDisabled) {
+ base::Value string_1("string1");
+ base::Value string_2("string2");
+
+ {
+ // Pass |use_super_mac| => false.
+ PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", false);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store.BeginTransaction(GetHashStoreContents()));
+
+ transaction->StoreHash("path1", &string_2);
+ EXPECT_EQ(ValueState::UNCHANGED,
+ transaction->CheckValue("path1", &string_2));
+ }
+
+ ASSERT_TRUE(GetHashStoreContents()->GetSuperMac().empty());
+
+ {
+ PrefHashStoreImpl pref_hash_store2(std::string(32, 0), "device_id", false);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store2.BeginTransaction(GetHashStoreContents()));
+ EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE,
+ transaction->CheckValue("new_path", &string_1));
+ }
+}
+
+TEST_F(PrefHashStoreImplTest, SplitHashStoreAndCheck) {
+ base::DictionaryValue dict;
+ dict.Set("a", new base::Value("to be replaced"));
+ dict.Set("b", new base::Value("same"));
+ dict.Set("o", new base::Value("old"));
+
+ base::DictionaryValue modified_dict;
+ modified_dict.Set("a", new base::Value("replaced"));
+ modified_dict.Set("b", new base::Value("same"));
+ modified_dict.Set("c", new base::Value("new"));
+
+ base::DictionaryValue empty_dict;
+
+ std::vector<std::string> invalid_keys;
+
+ {
+ PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store.BeginTransaction(GetHashStoreContents()));
+
+ // No hashes stored yet and hashes dictionary is empty (and thus not
+ // trusted).
+ EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE,
+ transaction->CheckSplitValue("path1", &dict, &invalid_keys));
+ EXPECT_TRUE(invalid_keys.empty());
+
+ transaction->StoreSplitHash("path1", &dict);
+
+ // Verify match post storage.
+ EXPECT_EQ(ValueState::UNCHANGED,
+ transaction->CheckSplitValue("path1", &dict, &invalid_keys));
+ EXPECT_TRUE(invalid_keys.empty());
+
+ // Verify new path is still unknown.
+ EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE,
+ transaction->CheckSplitValue("path2", &dict, &invalid_keys));
+ EXPECT_TRUE(invalid_keys.empty());
+
+ // Verify NULL or empty dicts are declared as having been cleared.
+ EXPECT_EQ(ValueState::CLEARED,
+ transaction->CheckSplitValue("path1", NULL, &invalid_keys));
+ EXPECT_TRUE(invalid_keys.empty());
+ EXPECT_EQ(ValueState::CLEARED, transaction->CheckSplitValue(
+ "path1", &empty_dict, &invalid_keys));
+ EXPECT_TRUE(invalid_keys.empty());
+
+ // Verify changes are properly detected.
+ EXPECT_EQ(ValueState::CHANGED, transaction->CheckSplitValue(
+ "path1", &modified_dict, &invalid_keys));
+ std::vector<std::string> expected_invalid_keys1;
+ expected_invalid_keys1.push_back("a");
+ expected_invalid_keys1.push_back("c");
+ expected_invalid_keys1.push_back("o");
+ EXPECT_EQ(expected_invalid_keys1, invalid_keys);
+ invalid_keys.clear();
+
+ // Verify |dict| still matches post check.
+ EXPECT_EQ(ValueState::UNCHANGED,
+ transaction->CheckSplitValue("path1", &dict, &invalid_keys));
+ EXPECT_TRUE(invalid_keys.empty());
+
+ // Store hash for |modified_dict|.
+ transaction->StoreSplitHash("path1", &modified_dict);
+
+ // Verify |modified_dict| is now the one that verifies correctly.
+ EXPECT_EQ(
+ ValueState::UNCHANGED,
+ transaction->CheckSplitValue("path1", &modified_dict, &invalid_keys));
+ EXPECT_TRUE(invalid_keys.empty());
+
+ // Verify old dict no longer matches.
+ EXPECT_EQ(ValueState::CHANGED,
+ transaction->CheckSplitValue("path1", &dict, &invalid_keys));
+ std::vector<std::string> expected_invalid_keys2;
+ expected_invalid_keys2.push_back("a");
+ expected_invalid_keys2.push_back("o");
+ expected_invalid_keys2.push_back("c");
+ EXPECT_EQ(expected_invalid_keys2, invalid_keys);
+ invalid_keys.clear();
+ }
+
+ {
+ // |pref_hash_store2| should trust its initial hashes dictionary and thus
+ // trust new unknown values.
+ PrefHashStoreImpl pref_hash_store2(std::string(32, 0), "device_id", true);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store2.BeginTransaction(GetHashStoreContents()));
+ EXPECT_EQ(ValueState::TRUSTED_UNKNOWN_VALUE,
+ transaction->CheckSplitValue("new_path", &dict, &invalid_keys));
+ EXPECT_TRUE(invalid_keys.empty());
+ }
+
+ // Manually corrupt the super MAC.
+ GetHashStoreContents()->SetSuperMac(std::string(64, 'A'));
+
+ {
+ // |pref_hash_store3| should no longer trust its initial hashes dictionary
+ // and thus shouldn't trust unknown values.
+ PrefHashStoreImpl pref_hash_store3(std::string(32, 0), "device_id", true);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store3.BeginTransaction(GetHashStoreContents()));
+ EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE,
+ transaction->CheckSplitValue("new_path", &dict, &invalid_keys));
+ EXPECT_TRUE(invalid_keys.empty());
+ }
+}
+
+TEST_F(PrefHashStoreImplTest, EmptyAndNULLSplitDict) {
+ base::DictionaryValue empty_dict;
+
+ std::vector<std::string> invalid_keys;
+
+ {
+ PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store.BeginTransaction(GetHashStoreContents()));
+
+ // Store hashes for a random dict to be overwritten below.
+ base::DictionaryValue initial_dict;
+ initial_dict.Set("a", new base::Value("foo"));
+ transaction->StoreSplitHash("path1", &initial_dict);
+
+ // Verify stored empty dictionary matches NULL and empty dictionary back.
+ transaction->StoreSplitHash("path1", &empty_dict);
+ EXPECT_EQ(ValueState::UNCHANGED,
+ transaction->CheckSplitValue("path1", NULL, &invalid_keys));
+ EXPECT_TRUE(invalid_keys.empty());
+ EXPECT_EQ(ValueState::UNCHANGED, transaction->CheckSplitValue(
+ "path1", &empty_dict, &invalid_keys));
+ EXPECT_TRUE(invalid_keys.empty());
+
+ // Same when storing NULL directly.
+ transaction->StoreSplitHash("path1", NULL);
+ EXPECT_EQ(ValueState::UNCHANGED,
+ transaction->CheckSplitValue("path1", NULL, &invalid_keys));
+ EXPECT_TRUE(invalid_keys.empty());
+ EXPECT_EQ(ValueState::UNCHANGED, transaction->CheckSplitValue(
+ "path1", &empty_dict, &invalid_keys));
+ EXPECT_TRUE(invalid_keys.empty());
+ }
+
+ {
+ // |pref_hash_store2| should trust its initial hashes dictionary (and thus
+ // trust new unknown values) even though the last action done was to clear
+ // the hashes for path1 by setting its value to NULL (this is a regression
+ // test ensuring that the internal action of clearing some hashes does
+ // update the stored hash of hashes).
+ PrefHashStoreImpl pref_hash_store2(std::string(32, 0), "device_id", true);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store2.BeginTransaction(GetHashStoreContents()));
+
+ base::DictionaryValue tested_dict;
+ tested_dict.Set("a", new base::Value("foo"));
+ tested_dict.Set("b", new base::Value("bar"));
+ EXPECT_EQ(
+ ValueState::TRUSTED_UNKNOWN_VALUE,
+ transaction->CheckSplitValue("new_path", &tested_dict, &invalid_keys));
+ EXPECT_TRUE(invalid_keys.empty());
+ }
+}
+
+// Test that the PrefHashStore returns TRUSTED_UNKNOWN_VALUE when checking for
+// a split preference even if there is an existing atomic preference's hash
+// stored. There is no point providing a migration path for preferences
+// switching strategies after their initial release as split preferences are
+// turned into split preferences specifically because the atomic hash isn't
+// considered useful.
+TEST_F(PrefHashStoreImplTest, TrustedUnknownSplitValueFromExistingAtomic) {
+ base::Value string("string1");
+
+ base::DictionaryValue dict;
+ dict.Set("a", new base::Value("foo"));
+ dict.Set("d", new base::Value("bad"));
+ dict.Set("b", new base::Value("bar"));
+ dict.Set("c", new base::Value("baz"));
+
+ {
+ PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store.BeginTransaction(GetHashStoreContents()));
+
+ transaction->StoreHash("path1", &string);
+ EXPECT_EQ(ValueState::UNCHANGED, transaction->CheckValue("path1", &string));
+ }
+
+ {
+ // Load a new |pref_hash_store2| in which the hashes dictionary is trusted.
+ PrefHashStoreImpl pref_hash_store2(std::string(32, 0), "device_id", true);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ pref_hash_store2.BeginTransaction(GetHashStoreContents()));
+ std::vector<std::string> invalid_keys;
+ EXPECT_EQ(ValueState::TRUSTED_UNKNOWN_VALUE,
+ transaction->CheckSplitValue("path1", &dict, &invalid_keys));
+ EXPECT_TRUE(invalid_keys.empty());
+ }
+}
diff --git a/chromium/services/preferences/tracked/pref_hash_store_transaction.h b/chromium/services/preferences/tracked/pref_hash_store_transaction.h
new file mode 100644
index 00000000000..2e888859ee8
--- /dev/null
+++ b/chromium/services/preferences/tracked/pref_hash_store_transaction.h
@@ -0,0 +1,80 @@
+// 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.
+
+#ifndef SERVICES_PREFERENCES_TRACKED_PREF_HASH_STORE_TRANSACTION_H_
+#define SERVICES_PREFERENCES_TRACKED_PREF_HASH_STORE_TRANSACTION_H_
+
+#include <string>
+#include <vector>
+
+#include "base/strings/string_piece.h"
+#include "services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom.h"
+
+namespace base {
+class DictionaryValue;
+class Value;
+} // namespace base
+
+// Used to perform a series of checks/transformations on a PrefHashStore.
+class PrefHashStoreTransaction {
+ public:
+ // Finalizes any remaining work after the transaction has been performed.
+ virtual ~PrefHashStoreTransaction() {}
+
+ // Returns the suffix to be appended to UMA histograms for the store contained
+ // in this transaction.
+ virtual base::StringPiece GetStoreUMASuffix() const = 0;
+
+ // Checks |initial_value| against the existing stored value hash.
+ virtual prefs::mojom::TrackedPreferenceValidationDelegate::ValueState
+ CheckValue(const std::string& path,
+ const base::Value* initial_value) const = 0;
+
+ // Stores a hash of the current |value| of the preference at |path|.
+ virtual void StoreHash(const std::string& path, const base::Value* value) = 0;
+
+ // Checks |initial_value| against the existing stored hashes for the split
+ // preference at |path|. |initial_split_value| being an empty dictionary or
+ // NULL is equivalent. |invalid_keys| must initially be empty. |invalid_keys|
+ // will not be modified unless the return value is CHANGED, in which case it
+ // will be filled with the keys that are considered invalid (unknown or
+ // changed).
+ virtual prefs::mojom::TrackedPreferenceValidationDelegate::ValueState
+ CheckSplitValue(const std::string& path,
+ const base::DictionaryValue* initial_split_value,
+ std::vector<std::string>* invalid_keys) const = 0;
+
+ // Stores hashes for the |value| of the split preference at |path|.
+ // |split_value| being an empty dictionary or NULL is equivalent.
+ virtual void StoreSplitHash(const std::string& path,
+ const base::DictionaryValue* split_value) = 0;
+
+ // Indicates whether the store contains a hash for the preference at |path|.
+ virtual bool HasHash(const std::string& path) const = 0;
+
+ // Sets the hash for the preference at |path|.
+ // If |path| is a split preference |hash| must be a DictionaryValue whose
+ // keys are keys in the split preference and whose values are MACs of the
+ // corresponding values in the split preference.
+ // If |path| is an atomic preference |hash| must be a StringValue
+ // containing a MAC of the preference value.
+ // |hash| should originate from a PrefHashStore sharing the same MAC
+ // parameters as this transaction's store.
+ // The (in)validity of the super MAC will be maintained by this call.
+ virtual void ImportHash(const std::string& path, const base::Value* hash) = 0;
+
+ // Removes the hash stored at |path|. The (in)validity of the super MAC will
+ // be maintained by this call.
+ virtual void ClearHash(const std::string& path) = 0;
+
+ // Indicates whether the super MAC was successfully verified at the beginning
+ // of this transaction.
+ virtual bool IsSuperMACValid() const = 0;
+
+ // Forces a valid super MAC to be stored when this transaction terminates.
+ // Returns true if this results in a change to the store contents.
+ virtual bool StampSuperMac() = 0;
+};
+
+#endif // SERVICES_PREFERENCES_TRACKED_PREF_HASH_STORE_TRANSACTION_H_
diff --git a/chromium/services/preferences/tracked/registry_hash_store_contents_win.cc b/chromium/services/preferences/tracked/registry_hash_store_contents_win.cc
new file mode 100644
index 00000000000..ab1c2c56478
--- /dev/null
+++ b/chromium/services/preferences/tracked/registry_hash_store_contents_win.cc
@@ -0,0 +1,182 @@
+// Copyright (c) 2016 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 "services/preferences/tracked/registry_hash_store_contents_win.h"
+
+#include <windows.h>
+
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/values.h"
+#include "base/win/registry.h"
+#include "services/preferences/public/cpp/tracked/tracked_preference_histogram_names.h"
+
+using base::win::RegistryValueIterator;
+
+namespace {
+
+constexpr size_t kMacSize = 64;
+
+base::string16 GetSplitPrefKeyName(const base::string16& reg_key_name,
+ const std::string& split_key_name) {
+ return reg_key_name + L"\\" + base::UTF8ToUTF16(split_key_name);
+}
+
+bool ReadMacFromRegistry(const base::win::RegKey& key,
+ const std::string& value_name,
+ std::string* out_mac) {
+ base::string16 string_value;
+ if (key.ReadValue(base::UTF8ToUTF16(value_name).c_str(), &string_value) ==
+ ERROR_SUCCESS &&
+ string_value.size() == kMacSize) {
+ out_mac->assign(base::UTF16ToUTF8(string_value));
+ return true;
+ }
+ return false;
+}
+
+// Removes |value_name| under |reg_key_name|. Returns true if found and
+// successfully removed.
+bool ClearAtomicMac(const base::string16& reg_key_name,
+ const std::string& value_name) {
+ base::win::RegKey key;
+ if (key.Open(HKEY_CURRENT_USER, reg_key_name.c_str(),
+ KEY_SET_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS) {
+ return key.DeleteValue(base::UTF8ToUTF16(value_name).c_str()) ==
+ ERROR_SUCCESS;
+ }
+ return false;
+}
+
+// Deletes |split_key_name| under |reg_key_name|. Returns true if found and
+// successfully removed.
+bool ClearSplitMac(const base::string16& reg_key_name,
+ const std::string& split_key_name) {
+ base::win::RegKey key;
+ if (key.Open(HKEY_CURRENT_USER,
+ GetSplitPrefKeyName(reg_key_name, split_key_name).c_str(),
+ KEY_SET_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS) {
+ return key.DeleteKey(L"") == ERROR_SUCCESS;
+ }
+ return false;
+}
+
+} // namespace
+
+RegistryHashStoreContentsWin::RegistryHashStoreContentsWin(
+ const base::string16& registry_path,
+ const base::string16& store_key)
+ : preference_key_name_(registry_path + L"\\PreferenceMACs\\" + store_key) {}
+
+RegistryHashStoreContentsWin::RegistryHashStoreContentsWin(
+ const RegistryHashStoreContentsWin& other) = default;
+
+bool RegistryHashStoreContentsWin::IsCopyable() const {
+ return true;
+}
+
+std::unique_ptr<HashStoreContents> RegistryHashStoreContentsWin::MakeCopy()
+ const {
+ return base::WrapUnique(new RegistryHashStoreContentsWin(*this));
+}
+
+base::StringPiece RegistryHashStoreContentsWin::GetUMASuffix() const {
+ return user_prefs::tracked::kTrackedPrefRegistryValidationSuffix;
+}
+
+void RegistryHashStoreContentsWin::Reset() {
+ base::win::RegKey key;
+ if (key.Open(HKEY_CURRENT_USER, preference_key_name_.c_str(),
+ KEY_SET_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS) {
+ LONG result = key.DeleteKey(L"");
+ DCHECK(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND) << result;
+ }
+}
+
+bool RegistryHashStoreContentsWin::GetMac(const std::string& path,
+ std::string* out_value) {
+ base::win::RegKey key;
+ if (key.Open(HKEY_CURRENT_USER, preference_key_name_.c_str(),
+ KEY_QUERY_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS) {
+ return ReadMacFromRegistry(key, path, out_value);
+ }
+
+ return false;
+}
+
+bool RegistryHashStoreContentsWin::GetSplitMacs(
+ const std::string& path,
+ std::map<std::string, std::string>* split_macs) {
+ DCHECK(split_macs);
+ DCHECK(split_macs->empty());
+
+ RegistryValueIterator iter_key(
+ HKEY_CURRENT_USER,
+ GetSplitPrefKeyName(preference_key_name_, path).c_str());
+
+ for (; iter_key.Valid(); ++iter_key) {
+ split_macs->insert(make_pair(base::UTF16ToUTF8(iter_key.Name()),
+ base::UTF16ToUTF8(iter_key.Value())));
+ }
+
+ return !split_macs->empty();
+}
+
+void RegistryHashStoreContentsWin::SetMac(const std::string& path,
+ const std::string& value) {
+ base::win::RegKey key;
+ DCHECK_EQ(kMacSize, value.size());
+
+ if (key.Create(HKEY_CURRENT_USER, preference_key_name_.c_str(),
+ KEY_SET_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS) {
+ key.WriteValue(base::UTF8ToUTF16(path).c_str(),
+ base::UTF8ToUTF16(value).c_str());
+ }
+}
+
+void RegistryHashStoreContentsWin::SetSplitMac(const std::string& path,
+ const std::string& split_path,
+ const std::string& value) {
+ base::win::RegKey key;
+ DCHECK_EQ(kMacSize, value.size());
+
+ if (key.Create(HKEY_CURRENT_USER,
+ GetSplitPrefKeyName(preference_key_name_, path).c_str(),
+ KEY_SET_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS) {
+ key.WriteValue(base::UTF8ToUTF16(split_path).c_str(),
+ base::UTF8ToUTF16(value).c_str());
+ }
+}
+
+bool RegistryHashStoreContentsWin::RemoveEntry(const std::string& path) {
+ return ClearAtomicMac(preference_key_name_, path) ||
+ ClearSplitMac(preference_key_name_, path);
+}
+
+void RegistryHashStoreContentsWin::ImportEntry(const std::string& path,
+ const base::Value* in_value) {
+ NOTREACHED()
+ << "RegistryHashStoreContents does not support the ImportEntry operation";
+}
+
+const base::DictionaryValue* RegistryHashStoreContentsWin::GetContents() const {
+ NOTREACHED()
+ << "RegistryHashStoreContents does not support the GetContents operation";
+ return NULL;
+}
+
+std::string RegistryHashStoreContentsWin::GetSuperMac() const {
+ NOTREACHED()
+ << "RegistryHashStoreContents does not support the GetSuperMac operation";
+ return NULL;
+}
+
+void RegistryHashStoreContentsWin::SetSuperMac(const std::string& super_mac) {
+ NOTREACHED()
+ << "RegistryHashStoreContents does not support the SetSuperMac operation";
+}
diff --git a/chromium/services/preferences/tracked/registry_hash_store_contents_win.h b/chromium/services/preferences/tracked/registry_hash_store_contents_win.h
new file mode 100644
index 00000000000..34496f97cec
--- /dev/null
+++ b/chromium/services/preferences/tracked/registry_hash_store_contents_win.h
@@ -0,0 +1,49 @@
+// Copyright 2016 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 SERVICES_PREFERENCES_TRACKED_REGISTRY_HASH_STORE_CONTENTS_WIN_H_
+#define SERVICES_PREFERENCES_TRACKED_REGISTRY_HASH_STORE_CONTENTS_WIN_H_
+
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "services/preferences/tracked/hash_store_contents.h"
+
+// Implements HashStoreContents by storing MACs in the Windows registry.
+class RegistryHashStoreContentsWin : public HashStoreContents {
+ public:
+ // Constructs a RegistryHashStoreContents which acts on a registry entry
+ // defined by |registry_path| and |store_key|.
+ explicit RegistryHashStoreContentsWin(const base::string16& registry_path,
+ const base::string16& store_key);
+
+ // HashStoreContents overrides:
+ bool IsCopyable() const override;
+ std::unique_ptr<HashStoreContents> MakeCopy() const override;
+ base::StringPiece GetUMASuffix() const override;
+ void Reset() override;
+ bool GetMac(const std::string& path, std::string* out_value) override;
+ bool GetSplitMacs(const std::string& path,
+ std::map<std::string, std::string>* split_macs) override;
+ void SetMac(const std::string& path, const std::string& value) override;
+ void SetSplitMac(const std::string& path,
+ const std::string& split_path,
+ const std::string& value) override;
+ bool RemoveEntry(const std::string& path) override;
+
+ // Unsupported HashStoreContents overrides:
+ void ImportEntry(const std::string& path,
+ const base::Value* in_value) override;
+ const base::DictionaryValue* GetContents() const override;
+ std::string GetSuperMac() const override;
+ void SetSuperMac(const std::string& super_mac) override;
+
+ private:
+ // Helper constructor for |MakeCopy|.
+ explicit RegistryHashStoreContentsWin(
+ const RegistryHashStoreContentsWin& other);
+
+ const base::string16 preference_key_name_;
+};
+
+#endif // SERVICES_PREFERENCES_TRACKED_REGISTRY_HASH_STORE_CONTENTS_WIN_H_
diff --git a/chromium/services/preferences/tracked/registry_hash_store_contents_win_unittest.cc b/chromium/services/preferences/tracked/registry_hash_store_contents_win_unittest.cc
new file mode 100644
index 00000000000..468843437fb
--- /dev/null
+++ b/chromium/services/preferences/tracked/registry_hash_store_contents_win_unittest.cc
@@ -0,0 +1,120 @@
+// Copyright (c) 2016 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 "services/preferences/tracked/registry_hash_store_contents_win.h"
+
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/test_reg_util_win.h"
+#include "base/values.h"
+#include "base/win/registry.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+constexpr base::char16 kRegistryPath[] = L"Foo\\TestStore";
+constexpr base::char16 kStoreKey[] = L"test_store_key";
+
+// Hex-encoded MACs are 64 characters long.
+constexpr char kTestStringA[] =
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+constexpr char kTestStringB[] =
+ "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
+
+constexpr char kAtomicPrefPath[] = "path1";
+constexpr char kSplitPrefPath[] = "extension";
+
+class RegistryHashStoreContentsWinTest : public testing::Test {
+ protected:
+ RegistryHashStoreContentsWinTest() {}
+
+ void SetUp() override {
+ ASSERT_NO_FATAL_FAILURE(
+ registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER));
+
+ contents.reset(new RegistryHashStoreContentsWin(kRegistryPath, kStoreKey));
+ }
+
+ std::unique_ptr<HashStoreContents> contents;
+
+ private:
+ registry_util::RegistryOverrideManager registry_override_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(RegistryHashStoreContentsWinTest);
+};
+
+} // namespace
+
+TEST_F(RegistryHashStoreContentsWinTest, TestSetAndGetMac) {
+ std::string stored_mac;
+ EXPECT_FALSE(contents->GetMac(kAtomicPrefPath, &stored_mac));
+
+ contents->SetMac(kAtomicPrefPath, kTestStringA);
+
+ EXPECT_TRUE(contents->GetMac(kAtomicPrefPath, &stored_mac));
+ EXPECT_EQ(kTestStringA, stored_mac);
+}
+
+TEST_F(RegistryHashStoreContentsWinTest, TestSetAndGetSplitMacs) {
+ std::map<std::string, std::string> split_macs;
+ EXPECT_FALSE(contents->GetSplitMacs(kSplitPrefPath, &split_macs));
+
+ contents->SetSplitMac(kSplitPrefPath, "a", kTestStringA);
+ contents->SetSplitMac(kSplitPrefPath, "b", kTestStringB);
+
+ EXPECT_TRUE(contents->GetSplitMacs(kSplitPrefPath, &split_macs));
+ EXPECT_EQ(2U, split_macs.size());
+ EXPECT_EQ(kTestStringA, split_macs.at("a"));
+ EXPECT_EQ(kTestStringB, split_macs.at("b"));
+}
+
+TEST_F(RegistryHashStoreContentsWinTest, TestRemoveAtomicMac) {
+ contents->SetMac(kAtomicPrefPath, kTestStringA);
+
+ std::string stored_mac;
+ EXPECT_TRUE(contents->GetMac(kAtomicPrefPath, &stored_mac));
+ EXPECT_EQ(kTestStringA, stored_mac);
+
+ contents->RemoveEntry(kAtomicPrefPath);
+
+ EXPECT_FALSE(contents->GetMac(kAtomicPrefPath, &stored_mac));
+}
+
+TEST_F(RegistryHashStoreContentsWinTest, TestRemoveSplitMacs) {
+ contents->SetSplitMac(kSplitPrefPath, "a", kTestStringA);
+ contents->SetSplitMac(kSplitPrefPath, "b", kTestStringB);
+
+ std::map<std::string, std::string> split_macs;
+ EXPECT_TRUE(contents->GetSplitMacs(kSplitPrefPath, &split_macs));
+ EXPECT_EQ(2U, split_macs.size());
+
+ contents->RemoveEntry(kSplitPrefPath);
+
+ split_macs.clear();
+ EXPECT_FALSE(contents->GetSplitMacs(kSplitPrefPath, &split_macs));
+ EXPECT_EQ(0U, split_macs.size());
+}
+
+TEST_F(RegistryHashStoreContentsWinTest, TestReset) {
+ contents->SetMac(kAtomicPrefPath, kTestStringA);
+ contents->SetSplitMac(kSplitPrefPath, "a", kTestStringA);
+
+ std::string stored_mac;
+ EXPECT_TRUE(contents->GetMac(kAtomicPrefPath, &stored_mac));
+ EXPECT_EQ(kTestStringA, stored_mac);
+
+ std::map<std::string, std::string> split_macs;
+ EXPECT_TRUE(contents->GetSplitMacs(kSplitPrefPath, &split_macs));
+ EXPECT_EQ(1U, split_macs.size());
+
+ contents->Reset();
+
+ stored_mac.clear();
+ EXPECT_FALSE(contents->GetMac(kAtomicPrefPath, &stored_mac));
+ EXPECT_TRUE(stored_mac.empty());
+
+ split_macs.clear();
+ EXPECT_FALSE(contents->GetSplitMacs(kSplitPrefPath, &split_macs));
+ EXPECT_EQ(0U, split_macs.size());
+}
diff --git a/chromium/services/preferences/tracked/segregated_pref_store.cc b/chromium/services/preferences/tracked/segregated_pref_store.cc
new file mode 100644
index 00000000000..af7349e0382
--- /dev/null
+++ b/chromium/services/preferences/tracked/segregated_pref_store.cc
@@ -0,0 +1,195 @@
+// 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 "services/preferences/tracked/segregated_pref_store.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/values.h"
+
+SegregatedPrefStore::AggregatingObserver::AggregatingObserver(
+ SegregatedPrefStore* outer)
+ : outer_(outer),
+ failed_sub_initializations_(0),
+ successful_sub_initializations_(0) {}
+
+void SegregatedPrefStore::AggregatingObserver::OnPrefValueChanged(
+ const std::string& key) {
+ // There is no need to tell clients about changes if they have not yet been
+ // told about initialization.
+ if (failed_sub_initializations_ + successful_sub_initializations_ < 2)
+ return;
+
+ for (auto& observer : outer_->observers_)
+ observer.OnPrefValueChanged(key);
+}
+
+void SegregatedPrefStore::AggregatingObserver::OnInitializationCompleted(
+ bool succeeded) {
+ if (succeeded)
+ ++successful_sub_initializations_;
+ else
+ ++failed_sub_initializations_;
+
+ DCHECK_LE(failed_sub_initializations_ + successful_sub_initializations_, 2);
+
+ if (failed_sub_initializations_ + successful_sub_initializations_ == 2) {
+ if (successful_sub_initializations_ == 2 && outer_->read_error_delegate_) {
+ PersistentPrefStore::PrefReadError read_error = outer_->GetReadError();
+ if (read_error != PersistentPrefStore::PREF_READ_ERROR_NONE)
+ outer_->read_error_delegate_->OnError(read_error);
+ }
+
+ for (auto& observer : outer_->observers_)
+ observer.OnInitializationCompleted(successful_sub_initializations_ == 2);
+ }
+}
+
+SegregatedPrefStore::SegregatedPrefStore(
+ const scoped_refptr<PersistentPrefStore>& default_pref_store,
+ const scoped_refptr<PersistentPrefStore>& selected_pref_store,
+ const std::set<std::string>& selected_pref_names,
+ prefs::mojom::TrackedPreferenceValidationDelegatePtr validation_delegate)
+ : validation_delegate_(std::move(validation_delegate)),
+ default_pref_store_(default_pref_store),
+ selected_pref_store_(selected_pref_store),
+ selected_preference_names_(selected_pref_names),
+ aggregating_observer_(this) {
+ default_pref_store_->AddObserver(&aggregating_observer_);
+ selected_pref_store_->AddObserver(&aggregating_observer_);
+}
+
+void SegregatedPrefStore::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void SegregatedPrefStore::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+bool SegregatedPrefStore::HasObservers() const {
+ return observers_.might_have_observers();
+}
+
+bool SegregatedPrefStore::IsInitializationComplete() const {
+ return default_pref_store_->IsInitializationComplete() &&
+ selected_pref_store_->IsInitializationComplete();
+}
+
+bool SegregatedPrefStore::GetValue(const std::string& key,
+ const base::Value** result) const {
+ return StoreForKey(key)->GetValue(key, result);
+}
+
+std::unique_ptr<base::DictionaryValue> SegregatedPrefStore::GetValues() const {
+ auto values = default_pref_store_->GetValues();
+ auto selected_pref_store_values = selected_pref_store_->GetValues();
+ for (const auto& key : selected_preference_names_) {
+ const base::Value* value = nullptr;
+ if (selected_pref_store_values->Get(key, &value)) {
+ values->Set(key, value->CreateDeepCopy());
+ } else {
+ values->Remove(key, nullptr);
+ }
+ }
+ return values;
+}
+
+void SegregatedPrefStore::SetValue(const std::string& key,
+ std::unique_ptr<base::Value> value,
+ uint32_t flags) {
+ StoreForKey(key)->SetValue(key, std::move(value), flags);
+}
+
+void SegregatedPrefStore::RemoveValue(const std::string& key, uint32_t flags) {
+ StoreForKey(key)->RemoveValue(key, flags);
+}
+
+bool SegregatedPrefStore::GetMutableValue(const std::string& key,
+ base::Value** result) {
+ return StoreForKey(key)->GetMutableValue(key, result);
+}
+
+void SegregatedPrefStore::ReportValueChanged(const std::string& key,
+ uint32_t flags) {
+ StoreForKey(key)->ReportValueChanged(key, flags);
+}
+
+void SegregatedPrefStore::SetValueSilently(const std::string& key,
+ std::unique_ptr<base::Value> value,
+ uint32_t flags) {
+ StoreForKey(key)->SetValueSilently(key, std::move(value), flags);
+}
+
+bool SegregatedPrefStore::ReadOnly() const {
+ return selected_pref_store_->ReadOnly() || default_pref_store_->ReadOnly();
+}
+
+PersistentPrefStore::PrefReadError SegregatedPrefStore::GetReadError() const {
+ PersistentPrefStore::PrefReadError read_error =
+ default_pref_store_->GetReadError();
+ if (read_error == PersistentPrefStore::PREF_READ_ERROR_NONE) {
+ read_error = selected_pref_store_->GetReadError();
+ // Ignore NO_FILE from selected_pref_store_.
+ if (read_error == PersistentPrefStore::PREF_READ_ERROR_NO_FILE)
+ read_error = PersistentPrefStore::PREF_READ_ERROR_NONE;
+ }
+ return read_error;
+}
+
+PersistentPrefStore::PrefReadError SegregatedPrefStore::ReadPrefs() {
+ // Note: Both of these stores own PrefFilters which makes ReadPrefs
+ // asynchronous. This is okay in this case as only the first call will be
+ // truly asynchronous, the second call will then unblock the migration in
+ // TrackedPreferencesMigrator and complete synchronously.
+ default_pref_store_->ReadPrefs();
+ PersistentPrefStore::PrefReadError selected_store_read_error =
+ selected_pref_store_->ReadPrefs();
+ DCHECK_NE(PersistentPrefStore::PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE,
+ selected_store_read_error);
+
+ return GetReadError();
+}
+
+void SegregatedPrefStore::ReadPrefsAsync(ReadErrorDelegate* error_delegate) {
+ read_error_delegate_.reset(error_delegate);
+ default_pref_store_->ReadPrefsAsync(NULL);
+ selected_pref_store_->ReadPrefsAsync(NULL);
+}
+
+void SegregatedPrefStore::CommitPendingWrite() {
+ default_pref_store_->CommitPendingWrite();
+ selected_pref_store_->CommitPendingWrite();
+}
+
+void SegregatedPrefStore::SchedulePendingLossyWrites() {
+ default_pref_store_->SchedulePendingLossyWrites();
+ selected_pref_store_->SchedulePendingLossyWrites();
+}
+
+void SegregatedPrefStore::ClearMutableValues() {
+ NOTIMPLEMENTED();
+}
+
+SegregatedPrefStore::~SegregatedPrefStore() {
+ default_pref_store_->RemoveObserver(&aggregating_observer_);
+ selected_pref_store_->RemoveObserver(&aggregating_observer_);
+}
+
+PersistentPrefStore* SegregatedPrefStore::StoreForKey(const std::string& key) {
+ return (base::ContainsKey(selected_preference_names_, key)
+ ? selected_pref_store_
+ : default_pref_store_)
+ .get();
+}
+
+const PersistentPrefStore* SegregatedPrefStore::StoreForKey(
+ const std::string& key) const {
+ return (base::ContainsKey(selected_preference_names_, key)
+ ? selected_pref_store_
+ : default_pref_store_)
+ .get();
+}
diff --git a/chromium/services/preferences/tracked/segregated_pref_store.h b/chromium/services/preferences/tracked/segregated_pref_store.h
new file mode 100644
index 00000000000..3621e49aa07
--- /dev/null
+++ b/chromium/services/preferences/tracked/segregated_pref_store.h
@@ -0,0 +1,122 @@
+// 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.
+
+#ifndef SERVICES_PREFERENCES_TRACKED_SEGREGATED_PREF_STORE_H_
+#define SERVICES_PREFERENCES_TRACKED_SEGREGATED_PREF_STORE_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
+#include "components/prefs/persistent_pref_store.h"
+#include "services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom.h"
+
+// Provides a unified PersistentPrefStore implementation that splits its storage
+// and retrieval between two underlying PersistentPrefStore instances: a set of
+// preference names is used to partition the preferences.
+//
+// Combines properties of the two stores as follows:
+// * The unified read error will be:
+// Selected Store Error
+// Default Store Error | NO_ERROR | NO_FILE | other selected |
+// NO_ERROR | NO_ERROR | NO_ERROR | other selected |
+// NO_FILE | NO_FILE | NO_FILE | NO_FILE |
+// other default | other default | other default | other default |
+// * The unified initialization success, initialization completion, and
+// read-only state are the boolean OR of the underlying stores' properties.
+class SegregatedPrefStore : public PersistentPrefStore {
+ public:
+ // Creates an instance that delegates to |selected_pref_store| for the
+ // preferences named in |selected_pref_names| and to |default_pref_store|
+ // for all others. If an unselected preference is present in
+ // |selected_pref_store| (i.e. because it was previously selected) it will
+ // be migrated back to |default_pref_store| upon access via a non-const
+ // method.
+ // |on_initialization| will be invoked when both stores have been initialized,
+ // before observers of the SegregatedPrefStore store are notified.
+ SegregatedPrefStore(
+ const scoped_refptr<PersistentPrefStore>& default_pref_store,
+ const scoped_refptr<PersistentPrefStore>& selected_pref_store,
+ const std::set<std::string>& selected_pref_names,
+ prefs::mojom::TrackedPreferenceValidationDelegatePtr validation_delegate);
+
+ // PrefStore implementation
+ void AddObserver(Observer* observer) override;
+ void RemoveObserver(Observer* observer) override;
+ bool HasObservers() const override;
+ bool IsInitializationComplete() const override;
+ bool GetValue(const std::string& key,
+ const base::Value** result) const override;
+ std::unique_ptr<base::DictionaryValue> GetValues() const override;
+
+ // WriteablePrefStore implementation
+ void SetValue(const std::string& key,
+ std::unique_ptr<base::Value> value,
+ uint32_t flags) override;
+ void RemoveValue(const std::string& key, uint32_t flags) override;
+
+ // PersistentPrefStore implementation
+ bool GetMutableValue(const std::string& key, base::Value** result) override;
+ void ReportValueChanged(const std::string& key, uint32_t flags) override;
+ void SetValueSilently(const std::string& key,
+ std::unique_ptr<base::Value> value,
+ uint32_t flags) override;
+ bool ReadOnly() const override;
+ PrefReadError GetReadError() const override;
+ PrefReadError ReadPrefs() override;
+ void ReadPrefsAsync(ReadErrorDelegate* error_delegate) override;
+ void CommitPendingWrite() override;
+ void SchedulePendingLossyWrites() override;
+
+ void ClearMutableValues() override;
+
+ private:
+ // Aggregates events from the underlying stores and synthesizes external
+ // events via |on_initialization|, |read_error_delegate_|, and |observers_|.
+ class AggregatingObserver : public PrefStore::Observer {
+ public:
+ explicit AggregatingObserver(SegregatedPrefStore* outer);
+
+ // PrefStore::Observer implementation
+ void OnPrefValueChanged(const std::string& key) override;
+ void OnInitializationCompleted(bool succeeded) override;
+
+ private:
+ SegregatedPrefStore* outer_;
+ int failed_sub_initializations_;
+ int successful_sub_initializations_;
+
+ DISALLOW_COPY_AND_ASSIGN(AggregatingObserver);
+ };
+
+ ~SegregatedPrefStore() override;
+
+ // Returns |selected_pref_store| if |key| is selected and |default_pref_store|
+ // otherwise.
+ PersistentPrefStore* StoreForKey(const std::string& key);
+ const PersistentPrefStore* StoreForKey(const std::string& key) const;
+
+ // |validation_delegate_| is used by |default_pref_store_| and
+ // |selected_pref_store_| PrefHashFilters. Its lifetime is managed here since
+ // a single owner is required.
+ prefs::mojom::TrackedPreferenceValidationDelegatePtr validation_delegate_;
+
+ scoped_refptr<PersistentPrefStore> default_pref_store_;
+ scoped_refptr<PersistentPrefStore> selected_pref_store_;
+ std::set<std::string> selected_preference_names_;
+
+ std::unique_ptr<PersistentPrefStore::ReadErrorDelegate> read_error_delegate_;
+ base::ObserverList<PrefStore::Observer, true> observers_;
+ AggregatingObserver aggregating_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(SegregatedPrefStore);
+};
+
+#endif // SERVICES_PREFERENCES_TRACKED_SEGREGATED_PREF_STORE_H_
diff --git a/chromium/services/preferences/tracked/segregated_pref_store_unittest.cc b/chromium/services/preferences/tracked/segregated_pref_store_unittest.cc
new file mode 100644
index 00000000000..a237f38285e
--- /dev/null
+++ b/chromium/services/preferences/tracked/segregated_pref_store_unittest.cc
@@ -0,0 +1,304 @@
+// 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 "services/preferences/tracked/segregated_pref_store.h"
+
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/values.h"
+#include "components/prefs/persistent_pref_store.h"
+#include "components/prefs/pref_store_observer_mock.h"
+#include "components/prefs/testing_pref_store.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kSelectedPref[] = "selected_pref";
+const char kUnselectedPref[] = "unselected_pref";
+const char kSharedPref[] = "shared_pref";
+
+const char kValue1[] = "value1";
+const char kValue2[] = "value2";
+
+class MockReadErrorDelegate : public PersistentPrefStore::ReadErrorDelegate {
+ public:
+ struct Data {
+ Data(bool invoked_in, PersistentPrefStore::PrefReadError read_error_in)
+ : invoked(invoked_in), read_error(read_error_in) {}
+
+ bool invoked;
+ PersistentPrefStore::PrefReadError read_error;
+ };
+
+ explicit MockReadErrorDelegate(Data* data) : data_(data) {
+ DCHECK(data_);
+ EXPECT_FALSE(data_->invoked);
+ }
+
+ // PersistentPrefStore::ReadErrorDelegate implementation
+ void OnError(PersistentPrefStore::PrefReadError read_error) override {
+ EXPECT_FALSE(data_->invoked);
+ data_->invoked = true;
+ data_->read_error = read_error;
+ }
+
+ private:
+ Data* data_;
+};
+
+} // namespace
+
+class SegregatedPrefStoreTest : public testing::Test {
+ public:
+ SegregatedPrefStoreTest()
+ : read_error_delegate_data_(false,
+ PersistentPrefStore::PREF_READ_ERROR_NONE),
+ read_error_delegate_(
+ new MockReadErrorDelegate(&read_error_delegate_data_)) {}
+
+ void SetUp() override {
+ selected_store_ = new TestingPrefStore;
+ default_store_ = new TestingPrefStore;
+
+ std::set<std::string> selected_pref_names;
+ selected_pref_names.insert(kSelectedPref);
+ selected_pref_names.insert(kSharedPref);
+
+ segregated_store_ = new SegregatedPrefStore(default_store_, selected_store_,
+ selected_pref_names, nullptr);
+
+ segregated_store_->AddObserver(&observer_);
+ }
+
+ void TearDown() override { segregated_store_->RemoveObserver(&observer_); }
+
+ protected:
+ std::unique_ptr<PersistentPrefStore::ReadErrorDelegate>
+ GetReadErrorDelegate() {
+ EXPECT_TRUE(read_error_delegate_);
+ return std::move(read_error_delegate_);
+ }
+
+ PrefStoreObserverMock observer_;
+
+ scoped_refptr<TestingPrefStore> default_store_;
+ scoped_refptr<TestingPrefStore> selected_store_;
+ scoped_refptr<SegregatedPrefStore> segregated_store_;
+
+ MockReadErrorDelegate::Data read_error_delegate_data_;
+
+ private:
+ std::unique_ptr<MockReadErrorDelegate> read_error_delegate_;
+};
+
+TEST_F(SegregatedPrefStoreTest, StoreValues) {
+ ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE,
+ segregated_store_->ReadPrefs());
+
+ // Properly stores new values.
+ segregated_store_->SetValue(kSelectedPref,
+ base::MakeUnique<base::Value>(kValue1),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+ segregated_store_->SetValue(kUnselectedPref,
+ base::MakeUnique<base::Value>(kValue2),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+
+ ASSERT_TRUE(selected_store_->GetValue(kSelectedPref, NULL));
+ ASSERT_FALSE(selected_store_->GetValue(kUnselectedPref, NULL));
+ ASSERT_FALSE(default_store_->GetValue(kSelectedPref, NULL));
+ ASSERT_TRUE(default_store_->GetValue(kUnselectedPref, NULL));
+
+ ASSERT_TRUE(segregated_store_->GetValue(kSelectedPref, NULL));
+ ASSERT_TRUE(segregated_store_->GetValue(kUnselectedPref, NULL));
+
+ ASSERT_FALSE(selected_store_->committed());
+ ASSERT_FALSE(default_store_->committed());
+
+ segregated_store_->CommitPendingWrite();
+
+ ASSERT_TRUE(selected_store_->committed());
+ ASSERT_TRUE(default_store_->committed());
+}
+
+TEST_F(SegregatedPrefStoreTest, ReadValues) {
+ selected_store_->SetValue(kSelectedPref,
+ base::MakeUnique<base::Value>(kValue1),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+ default_store_->SetValue(kUnselectedPref,
+ base::MakeUnique<base::Value>(kValue2),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+
+ // Works properly with values that are already there.
+ ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE,
+ segregated_store_->ReadPrefs());
+ ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE,
+ segregated_store_->GetReadError());
+
+ ASSERT_TRUE(selected_store_->GetValue(kSelectedPref, NULL));
+ ASSERT_FALSE(selected_store_->GetValue(kUnselectedPref, NULL));
+ ASSERT_FALSE(default_store_->GetValue(kSelectedPref, NULL));
+ ASSERT_TRUE(default_store_->GetValue(kUnselectedPref, NULL));
+
+ ASSERT_TRUE(segregated_store_->GetValue(kSelectedPref, NULL));
+ ASSERT_TRUE(segregated_store_->GetValue(kUnselectedPref, NULL));
+}
+
+TEST_F(SegregatedPrefStoreTest, Observer) {
+ EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE,
+ segregated_store_->ReadPrefs());
+ EXPECT_TRUE(observer_.initialized);
+ EXPECT_TRUE(observer_.initialization_success);
+ EXPECT_TRUE(observer_.changed_keys.empty());
+ segregated_store_->SetValue(kSelectedPref,
+ base::MakeUnique<base::Value>(kValue1),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+ observer_.VerifyAndResetChangedKey(kSelectedPref);
+ segregated_store_->SetValue(kUnselectedPref,
+ base::MakeUnique<base::Value>(kValue2),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+ observer_.VerifyAndResetChangedKey(kUnselectedPref);
+}
+
+TEST_F(SegregatedPrefStoreTest, SelectedPrefReadNoFileError) {
+ // PREF_READ_ERROR_NO_FILE for the selected prefs file is silently converted
+ // to PREF_READ_ERROR_NONE.
+ selected_store_->set_read_error(PersistentPrefStore::PREF_READ_ERROR_NO_FILE);
+ EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE,
+ segregated_store_->ReadPrefs());
+ EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE,
+ segregated_store_->GetReadError());
+}
+
+TEST_F(SegregatedPrefStoreTest, SelectedPrefReadError) {
+ selected_store_->set_read_error(
+ PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED);
+ EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED,
+ segregated_store_->ReadPrefs());
+ EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED,
+ segregated_store_->GetReadError());
+}
+
+TEST_F(SegregatedPrefStoreTest, SelectedPrefReadNoFileErrorAsync) {
+ // PREF_READ_ERROR_NO_FILE for the selected prefs file is silently converted
+ // to PREF_READ_ERROR_NONE.
+ selected_store_->set_read_error(PersistentPrefStore::PREF_READ_ERROR_NO_FILE);
+
+ default_store_->SetBlockAsyncRead(true);
+
+ EXPECT_FALSE(read_error_delegate_data_.invoked);
+
+ segregated_store_->ReadPrefsAsync(GetReadErrorDelegate().release());
+
+ EXPECT_FALSE(read_error_delegate_data_.invoked);
+
+ default_store_->SetBlockAsyncRead(false);
+
+ // ReadErrorDelegate is not invoked for ERROR_NONE.
+ EXPECT_FALSE(read_error_delegate_data_.invoked);
+ EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE,
+ segregated_store_->GetReadError());
+ EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE,
+ segregated_store_->GetReadError());
+}
+
+TEST_F(SegregatedPrefStoreTest, UnselectedPrefReadNoFileError) {
+ default_store_->set_read_error(PersistentPrefStore::PREF_READ_ERROR_NO_FILE);
+ EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE,
+ segregated_store_->ReadPrefs());
+ EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE,
+ segregated_store_->GetReadError());
+}
+
+TEST_F(SegregatedPrefStoreTest, UnselectedPrefReadError) {
+ default_store_->set_read_error(
+ PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED);
+ EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED,
+ segregated_store_->ReadPrefs());
+ EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED,
+ segregated_store_->GetReadError());
+}
+
+TEST_F(SegregatedPrefStoreTest, BothPrefReadError) {
+ default_store_->set_read_error(PersistentPrefStore::PREF_READ_ERROR_NO_FILE);
+ selected_store_->set_read_error(
+ PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED);
+ EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE,
+ segregated_store_->ReadPrefs());
+ EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE,
+ segregated_store_->GetReadError());
+}
+
+TEST_F(SegregatedPrefStoreTest, BothPrefReadErrorAsync) {
+ default_store_->set_read_error(PersistentPrefStore::PREF_READ_ERROR_NO_FILE);
+ selected_store_->set_read_error(
+ PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED);
+
+ selected_store_->SetBlockAsyncRead(true);
+
+ EXPECT_FALSE(read_error_delegate_data_.invoked);
+
+ segregated_store_->ReadPrefsAsync(GetReadErrorDelegate().release());
+
+ EXPECT_FALSE(read_error_delegate_data_.invoked);
+
+ selected_store_->SetBlockAsyncRead(false);
+
+ EXPECT_TRUE(read_error_delegate_data_.invoked);
+ EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE,
+ segregated_store_->GetReadError());
+ EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE,
+ segregated_store_->GetReadError());
+}
+
+TEST_F(SegregatedPrefStoreTest, IsInitializationComplete) {
+ EXPECT_FALSE(segregated_store_->IsInitializationComplete());
+ segregated_store_->ReadPrefs();
+ EXPECT_TRUE(segregated_store_->IsInitializationComplete());
+}
+
+TEST_F(SegregatedPrefStoreTest, IsInitializationCompleteAsync) {
+ selected_store_->SetBlockAsyncRead(true);
+ default_store_->SetBlockAsyncRead(true);
+ EXPECT_FALSE(segregated_store_->IsInitializationComplete());
+ segregated_store_->ReadPrefsAsync(NULL);
+ EXPECT_FALSE(segregated_store_->IsInitializationComplete());
+ selected_store_->SetBlockAsyncRead(false);
+ EXPECT_FALSE(segregated_store_->IsInitializationComplete());
+ default_store_->SetBlockAsyncRead(false);
+ EXPECT_TRUE(segregated_store_->IsInitializationComplete());
+}
+
+TEST_F(SegregatedPrefStoreTest, GetValues) {
+ // To check merge behavior, create selected and default stores so each has a
+ // key the other doesn't have and they have one key in common.
+ selected_store_->SetValue(kSelectedPref,
+ base::MakeUnique<base::Value>(kValue1),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+ default_store_->SetValue(kUnselectedPref,
+ base::MakeUnique<base::Value>(kValue2),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+ selected_store_->SetValue(kSharedPref, base::MakeUnique<base::Value>(kValue1),
+ WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+
+ auto values = segregated_store_->GetValues();
+ const base::Value* value = nullptr;
+ // Check that a selected preference is returned.
+ ASSERT_TRUE(values->Get(kSelectedPref, &value));
+ EXPECT_TRUE(base::Value(kValue1).Equals(value));
+
+ // Check that a a default preference is returned.
+ ASSERT_TRUE(values->Get(kUnselectedPref, &value));
+ EXPECT_TRUE(base::Value(kValue2).Equals(value));
+
+ // Check that the selected preference is preferred.
+ ASSERT_TRUE(values->Get(kSharedPref, &value));
+ EXPECT_TRUE(base::Value(kValue1).Equals(value));
+}
diff --git a/chromium/services/preferences/tracked/tracked_atomic_preference.cc b/chromium/services/preferences/tracked/tracked_atomic_preference.cc
new file mode 100644
index 00000000000..8df3a3609fa
--- /dev/null
+++ b/chromium/services/preferences/tracked/tracked_atomic_preference.cc
@@ -0,0 +1,89 @@
+// 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 "services/preferences/tracked/tracked_atomic_preference.h"
+
+#include "base/values.h"
+#include "services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom.h"
+#include "services/preferences/tracked/pref_hash_store_transaction.h"
+
+using ValueState =
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState;
+
+TrackedAtomicPreference::TrackedAtomicPreference(
+ const std::string& pref_path,
+ size_t reporting_id,
+ size_t reporting_ids_count,
+ prefs::mojom::TrackedPreferenceMetadata::EnforcementLevel enforcement_level,
+ prefs::mojom::TrackedPreferenceMetadata::ValueType value_type,
+ prefs::mojom::TrackedPreferenceValidationDelegate* delegate)
+ : pref_path_(pref_path),
+ helper_(pref_path,
+ reporting_id,
+ reporting_ids_count,
+ enforcement_level,
+ value_type),
+ delegate_(delegate) {}
+
+TrackedPreferenceType TrackedAtomicPreference::GetType() const {
+ return TrackedPreferenceType::ATOMIC;
+}
+
+void TrackedAtomicPreference::OnNewValue(
+ const base::Value* value,
+ PrefHashStoreTransaction* transaction) const {
+ transaction->StoreHash(pref_path_, value);
+}
+
+bool TrackedAtomicPreference::EnforceAndReport(
+ base::DictionaryValue* pref_store_contents,
+ PrefHashStoreTransaction* transaction,
+ PrefHashStoreTransaction* external_validation_transaction) const {
+ const base::Value* value = NULL;
+ pref_store_contents->Get(pref_path_, &value);
+ ValueState value_state = transaction->CheckValue(pref_path_, value);
+ helper_.ReportValidationResult(value_state, transaction->GetStoreUMASuffix());
+
+ ValueState external_validation_value_state = ValueState::UNSUPPORTED;
+ if (external_validation_transaction) {
+ external_validation_value_state =
+ external_validation_transaction->CheckValue(pref_path_, value);
+ helper_.ReportValidationResult(
+ external_validation_value_state,
+ external_validation_transaction->GetStoreUMASuffix());
+ }
+
+ if (delegate_) {
+ delegate_->OnAtomicPreferenceValidation(
+ pref_path_, value ? value->CreateDeepCopy() : nullptr, value_state,
+ external_validation_value_state, helper_.IsPersonal());
+ }
+ TrackedPreferenceHelper::ResetAction reset_action =
+ helper_.GetAction(value_state);
+ helper_.ReportAction(reset_action);
+
+ bool was_reset = false;
+ if (reset_action == TrackedPreferenceHelper::DO_RESET) {
+ pref_store_contents->RemovePath(pref_path_, NULL);
+ was_reset = true;
+ }
+
+ if (value_state != ValueState::UNCHANGED) {
+ // Store the hash for the new value (whether it was reset or not).
+ const base::Value* new_value = NULL;
+ pref_store_contents->Get(pref_path_, &new_value);
+ transaction->StoreHash(pref_path_, new_value);
+ }
+
+ // Update MACs in the external store if there is one and there either was a
+ // reset or external validation failed.
+ if (external_validation_transaction &&
+ (was_reset || external_validation_value_state != ValueState::UNCHANGED)) {
+ const base::Value* new_value = nullptr;
+ pref_store_contents->Get(pref_path_, &new_value);
+ external_validation_transaction->StoreHash(pref_path_, new_value);
+ }
+
+ return was_reset;
+}
diff --git a/chromium/services/preferences/tracked/tracked_atomic_preference.h b/chromium/services/preferences/tracked/tracked_atomic_preference.h
new file mode 100644
index 00000000000..3f3477e9dd9
--- /dev/null
+++ b/chromium/services/preferences/tracked/tracked_atomic_preference.h
@@ -0,0 +1,55 @@
+// 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.
+
+#ifndef SERVICES_PREFERENCES_TRACKED_TRACKED_ATOMIC_PREFERENCE_H_
+#define SERVICES_PREFERENCES_TRACKED_TRACKED_ATOMIC_PREFERENCE_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "services/preferences/tracked/pref_hash_filter.h"
+#include "services/preferences/tracked/tracked_preference.h"
+#include "services/preferences/tracked/tracked_preference_helper.h"
+
+namespace prefs {
+namespace mojom {
+class TrackedPreferenceValidationDelegate;
+}
+}
+
+// A TrackedAtomicPreference is tracked as a whole. A hash is stored for its
+// entire value and it is entirely reset on mismatch. An optional delegate is
+// notified of the status of the preference during enforcement.
+class TrackedAtomicPreference : public TrackedPreference {
+ public:
+ TrackedAtomicPreference(
+ const std::string& pref_path,
+ size_t reporting_id,
+ size_t reporting_ids_count,
+ prefs::mojom::TrackedPreferenceMetadata::EnforcementLevel
+ enforcement_level,
+ prefs::mojom::TrackedPreferenceMetadata::ValueType value_type,
+ prefs::mojom::TrackedPreferenceValidationDelegate* delegate);
+
+ // TrackedPreference implementation.
+ TrackedPreferenceType GetType() const override;
+ void OnNewValue(const base::Value* value,
+ PrefHashStoreTransaction* transaction) const override;
+ bool EnforceAndReport(
+ base::DictionaryValue* pref_store_contents,
+ PrefHashStoreTransaction* transaction,
+ PrefHashStoreTransaction* external_validation_transaction) const override;
+
+ private:
+ const std::string pref_path_;
+ const TrackedPreferenceHelper helper_;
+ prefs::mojom::TrackedPreferenceValidationDelegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrackedAtomicPreference);
+};
+
+#endif // SERVICES_PREFERENCES_TRACKED_TRACKED_ATOMIC_PREFERENCE_H_
diff --git a/chromium/services/preferences/tracked/tracked_persistent_pref_store_factory.cc b/chromium/services/preferences/tracked/tracked_persistent_pref_store_factory.cc
new file mode 100644
index 00000000000..ffb5ab2e9ac
--- /dev/null
+++ b/chromium/services/preferences/tracked/tracked_persistent_pref_store_factory.cc
@@ -0,0 +1,133 @@
+// Copyright 2017 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 "services/preferences/tracked/tracked_persistent_pref_store_factory.h"
+
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "components/prefs/json_pref_store.h"
+#include "components/prefs/pref_filter.h"
+#include "services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom.h"
+#include "services/preferences/tracked/pref_hash_filter.h"
+#include "services/preferences/tracked/pref_hash_store_impl.h"
+#include "services/preferences/tracked/segregated_pref_store.h"
+#include "services/preferences/tracked/tracked_preferences_migration.h"
+
+#if defined(OS_WIN)
+#include "services/preferences/tracked/registry_hash_store_contents_win.h"
+#endif
+
+namespace {
+
+void RemoveValueSilently(const base::WeakPtr<JsonPrefStore> pref_store,
+ const std::string& key) {
+ if (pref_store) {
+ pref_store->RemoveValueSilently(
+ key, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+ }
+}
+
+std::unique_ptr<PrefHashStore> CreatePrefHashStore(
+ const prefs::mojom::TrackedPersistentPrefStoreConfiguration& config,
+ bool use_super_mac) {
+ return base::MakeUnique<PrefHashStoreImpl>(
+ config.seed, config.legacy_device_id, use_super_mac);
+}
+
+std::pair<std::unique_ptr<PrefHashStore>, std::unique_ptr<HashStoreContents>>
+GetExternalVerificationPrefHashStorePair(
+ const prefs::mojom::TrackedPersistentPrefStoreConfiguration& config) {
+#if defined(OS_WIN)
+ return std::make_pair(
+ base::MakeUnique<PrefHashStoreImpl>(config.registry_seed,
+ config.legacy_device_id,
+ false /* use_super_mac */),
+ base::MakeUnique<RegistryHashStoreContentsWin>(
+ config.registry_path, config.unprotected_pref_filename.DirName()
+ .BaseName()
+ .LossyDisplayName()));
+#else
+ return std::make_pair(nullptr, nullptr);
+#endif
+}
+
+} // namespace
+
+PersistentPrefStore* CreateTrackedPersistentPrefStore(
+ prefs::mojom::TrackedPersistentPrefStoreConfigurationPtr config,
+ base::SequencedWorkerPool* worker_pool) {
+ auto io_task_runner = JsonPrefStore::GetTaskRunnerForFile(
+ config->unprotected_pref_filename.DirName(), worker_pool);
+
+ std::vector<prefs::mojom::TrackedPreferenceMetadataPtr>
+ unprotected_configuration;
+ std::vector<prefs::mojom::TrackedPreferenceMetadataPtr>
+ protected_configuration;
+ std::set<std::string> protected_pref_names;
+ std::set<std::string> unprotected_pref_names;
+ for (auto& metadata : config->tracking_configuration) {
+ if (metadata->enforcement_level > prefs::mojom::TrackedPreferenceMetadata::
+ EnforcementLevel::NO_ENFORCEMENT) {
+ protected_pref_names.insert(metadata->name);
+ protected_configuration.push_back(std::move(metadata));
+ } else {
+ unprotected_pref_names.insert(metadata->name);
+ unprotected_configuration.push_back(std::move(metadata));
+ }
+ }
+ config->tracking_configuration.clear();
+
+ std::unique_ptr<PrefHashFilter> unprotected_pref_hash_filter(
+ new PrefHashFilter(CreatePrefHashStore(*config, false),
+ GetExternalVerificationPrefHashStorePair(*config),
+ unprotected_configuration, nullptr,
+ config->validation_delegate.get(),
+ config->reporting_ids_count, false));
+ std::unique_ptr<PrefHashFilter> protected_pref_hash_filter(new PrefHashFilter(
+ CreatePrefHashStore(*config, true),
+ GetExternalVerificationPrefHashStorePair(*config),
+ protected_configuration, std::move(config->reset_on_load_observer),
+ config->validation_delegate.get(), config->reporting_ids_count, true));
+
+ PrefHashFilter* raw_unprotected_pref_hash_filter =
+ unprotected_pref_hash_filter.get();
+ PrefHashFilter* raw_protected_pref_hash_filter =
+ protected_pref_hash_filter.get();
+
+ scoped_refptr<JsonPrefStore> unprotected_pref_store(
+ new JsonPrefStore(config->unprotected_pref_filename, io_task_runner.get(),
+ std::move(unprotected_pref_hash_filter)));
+ scoped_refptr<JsonPrefStore> protected_pref_store(
+ new JsonPrefStore(config->protected_pref_filename, io_task_runner.get(),
+ std::move(protected_pref_hash_filter)));
+
+ SetupTrackedPreferencesMigration(
+ unprotected_pref_names, protected_pref_names,
+ base::Bind(&RemoveValueSilently, unprotected_pref_store->AsWeakPtr()),
+ base::Bind(&RemoveValueSilently, protected_pref_store->AsWeakPtr()),
+ base::Bind(&JsonPrefStore::RegisterOnNextSuccessfulWriteReply,
+ unprotected_pref_store->AsWeakPtr()),
+ base::Bind(&JsonPrefStore::RegisterOnNextSuccessfulWriteReply,
+ protected_pref_store->AsWeakPtr()),
+ CreatePrefHashStore(*config, false), CreatePrefHashStore(*config, true),
+ raw_unprotected_pref_hash_filter, raw_protected_pref_hash_filter);
+
+ return new SegregatedPrefStore(unprotected_pref_store, protected_pref_store,
+ protected_pref_names,
+ std::move(config->validation_delegate));
+}
+
+void InitializeMasterPrefsTracking(
+ prefs::mojom::TrackedPersistentPrefStoreConfigurationPtr configuration,
+ base::DictionaryValue* master_prefs) {
+ PrefHashFilter(CreatePrefHashStore(*configuration, false),
+ GetExternalVerificationPrefHashStorePair(*configuration),
+ configuration->tracking_configuration, nullptr, nullptr,
+ configuration->reporting_ids_count, false)
+ .Initialize(master_prefs);
+}
diff --git a/chromium/services/preferences/tracked/tracked_persistent_pref_store_factory.h b/chromium/services/preferences/tracked/tracked_persistent_pref_store_factory.h
new file mode 100644
index 00000000000..fb426bab47d
--- /dev/null
+++ b/chromium/services/preferences/tracked/tracked_persistent_pref_store_factory.h
@@ -0,0 +1,27 @@
+// Copyright 2017 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 SERVICES_PREFERENCES_TRACKED_TRACKED_PERSISTENT_PREF_STORE_FACTORY_H_
+#define SERVICES_PREFERENCES_TRACKED_TRACKED_PERSISTENT_PREF_STORE_FACTORY_H_
+
+#include <utility>
+#include "services/preferences/public/interfaces/preferences_configuration.mojom.h"
+
+namespace base {
+class DictionaryValue;
+class SequencedWorkerPool;
+}
+
+class PersistentPrefStore;
+
+PersistentPrefStore* CreateTrackedPersistentPrefStore(
+ prefs::mojom::TrackedPersistentPrefStoreConfigurationPtr config,
+ base::SequencedWorkerPool* worker_pool);
+
+// TODO(sammc): This should move somewhere more appropriate in the longer term.
+void InitializeMasterPrefsTracking(
+ prefs::mojom::TrackedPersistentPrefStoreConfigurationPtr configuration,
+ base::DictionaryValue* master_prefs);
+
+#endif // SERVICES_PREFERENCES_TRACKED_TRACKED_PERSISTENT_PREF_STORE_FACTORY_H_
diff --git a/chromium/services/preferences/tracked/tracked_preference.h b/chromium/services/preferences/tracked/tracked_preference.h
new file mode 100644
index 00000000000..eeb333c52a1
--- /dev/null
+++ b/chromium/services/preferences/tracked/tracked_preference.h
@@ -0,0 +1,44 @@
+// 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.
+
+#ifndef SERVICES_PREFERENCES_TRACKED_TRACKED_PREFERENCE_H_
+#define SERVICES_PREFERENCES_TRACKED_TRACKED_PREFERENCE_H_
+
+class PrefHashStoreTransaction;
+
+namespace base {
+class DictionaryValue;
+class Value;
+}
+
+enum class TrackedPreferenceType { ATOMIC, SPLIT };
+
+// A TrackedPreference tracks changes to an individual preference, reporting and
+// reacting to them according to preference-specific and browser-wide policies.
+class TrackedPreference {
+ public:
+ virtual ~TrackedPreference() {}
+
+ virtual TrackedPreferenceType GetType() const = 0;
+
+ // Notifies the underlying TrackedPreference about its new |value| which
+ // can update hashes in the corresponding hash store via |transaction|.
+ virtual void OnNewValue(const base::Value* value,
+ PrefHashStoreTransaction* transaction) const = 0;
+
+ // Verifies that the value of this TrackedPreference in |pref_store_contents|
+ // is valid. Responds to verification failures according to
+ // preference-specific and browser-wide policy and reports results to via UMA.
+ // May use |transaction| to check/modify hashes in the corresponding hash
+ // store. Performs validation and reports results without enforcing for
+ // |external_validation_transaction|. This call assumes exclusive access to
+ // |external_validation_transaction| and its associated state and as such
+ // should only be called before any other subsystem is made aware of it.
+ virtual bool EnforceAndReport(
+ base::DictionaryValue* pref_store_contents,
+ PrefHashStoreTransaction* transaction,
+ PrefHashStoreTransaction* external_validation_transaction) const = 0;
+};
+
+#endif // SERVICES_PREFERENCES_TRACKED_TRACKED_PREFERENCE_H_
diff --git a/chromium/services/preferences/tracked/tracked_preference_helper.cc b/chromium/services/preferences/tracked/tracked_preference_helper.cc
new file mode 100644
index 00000000000..d368ddb2f00
--- /dev/null
+++ b/chromium/services/preferences/tracked/tracked_preference_helper.cc
@@ -0,0 +1,139 @@
+// 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 "services/preferences/tracked/tracked_preference_helper.h"
+
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_macros.h"
+#include "services/preferences/public/cpp/tracked/tracked_preference_histogram_names.h"
+
+using ValueState =
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState;
+
+TrackedPreferenceHelper::TrackedPreferenceHelper(
+ const std::string& pref_path,
+ size_t reporting_id,
+ size_t reporting_ids_count,
+ prefs::mojom::TrackedPreferenceMetadata::EnforcementLevel enforcement_level,
+ prefs::mojom::TrackedPreferenceMetadata::ValueType value_type)
+ : pref_path_(pref_path),
+ reporting_id_(reporting_id),
+ reporting_ids_count_(reporting_ids_count),
+ enforce_(enforcement_level == prefs::mojom::TrackedPreferenceMetadata::
+ EnforcementLevel::ENFORCE_ON_LOAD),
+ personal_(value_type ==
+ prefs::mojom::TrackedPreferenceMetadata::ValueType::PERSONAL) {}
+
+TrackedPreferenceHelper::ResetAction TrackedPreferenceHelper::GetAction(
+ ValueState value_state) const {
+ switch (value_state) {
+ case ValueState::UNCHANGED:
+ // Desired case, nothing to do.
+ return DONT_RESET;
+ case ValueState::CLEARED:
+ // Unfortunate case, but there is nothing we can do.
+ return DONT_RESET;
+ case ValueState::TRUSTED_NULL_VALUE: // Falls through.
+ case ValueState::TRUSTED_UNKNOWN_VALUE:
+ // It is okay to seed the hash in this case.
+ return DONT_RESET;
+ case ValueState::SECURE_LEGACY:
+ // Accept secure legacy device ID based hashes.
+ return DONT_RESET;
+ case ValueState::UNSUPPORTED:
+ NOTREACHED()
+ << "GetAction should not be called with an UNSUPPORTED value state";
+ return DONT_RESET;
+ case ValueState::UNTRUSTED_UNKNOWN_VALUE: // Falls through.
+ case ValueState::CHANGED:
+ return enforce_ ? DO_RESET : WANTED_RESET;
+ }
+ NOTREACHED() << "Unexpected ValueState: " << value_state;
+ return DONT_RESET;
+}
+
+bool TrackedPreferenceHelper::IsPersonal() const {
+ return personal_;
+}
+
+void TrackedPreferenceHelper::ReportValidationResult(
+ ValueState value_state,
+ base::StringPiece validation_type_suffix) const {
+ const char* histogram_name = nullptr;
+ switch (value_state) {
+ case ValueState::UNCHANGED:
+ histogram_name = user_prefs::tracked::kTrackedPrefHistogramUnchanged;
+ break;
+ case ValueState::CLEARED:
+ histogram_name = user_prefs::tracked::kTrackedPrefHistogramCleared;
+ break;
+ case ValueState::SECURE_LEGACY:
+ histogram_name =
+ user_prefs::tracked::kTrackedPrefHistogramMigratedLegacyDeviceId;
+ break;
+ case ValueState::CHANGED:
+ histogram_name = user_prefs::tracked::kTrackedPrefHistogramChanged;
+ break;
+ case ValueState::UNTRUSTED_UNKNOWN_VALUE:
+ histogram_name = user_prefs::tracked::kTrackedPrefHistogramInitialized;
+ break;
+ case ValueState::TRUSTED_UNKNOWN_VALUE:
+ histogram_name =
+ user_prefs::tracked::kTrackedPrefHistogramTrustedInitialized;
+ break;
+ case ValueState::TRUSTED_NULL_VALUE:
+ histogram_name =
+ user_prefs::tracked::kTrackedPrefHistogramNullInitialized;
+ break;
+ case ValueState::UNSUPPORTED:
+ NOTREACHED() << "ReportValidationResult should not be called with an "
+ "UNSUPPORTED value state";
+ return;
+ }
+ DCHECK(histogram_name);
+
+ std::string full_histogram_name(histogram_name);
+ if (!validation_type_suffix.empty()) {
+ full_histogram_name.push_back('.');
+ validation_type_suffix.AppendToString(&full_histogram_name);
+ }
+
+ // Using FactoryGet to allow dynamic histogram names. This is equivalent to
+ // UMA_HISTOGRAM_ENUMERATION(name, reporting_id_, reporting_ids_count_);
+ base::HistogramBase* histogram = base::LinearHistogram::FactoryGet(
+ full_histogram_name, 1, reporting_ids_count_, reporting_ids_count_ + 1,
+ base::HistogramBase::kUmaTargetedHistogramFlag);
+ histogram->Add(reporting_id_);
+}
+
+void TrackedPreferenceHelper::ReportAction(ResetAction reset_action) const {
+ switch (reset_action) {
+ case DONT_RESET:
+ // No report for DONT_RESET.
+ break;
+ case WANTED_RESET:
+ UMA_HISTOGRAM_EXACT_LINEAR(
+ user_prefs::tracked::kTrackedPrefHistogramWantedReset, reporting_id_,
+ reporting_ids_count_);
+ break;
+ case DO_RESET:
+ UMA_HISTOGRAM_EXACT_LINEAR(
+ user_prefs::tracked::kTrackedPrefHistogramReset, reporting_id_,
+ reporting_ids_count_);
+ break;
+ }
+}
+
+void TrackedPreferenceHelper::ReportSplitPreferenceChangedCount(
+ size_t count) const {
+ // The histogram below is an expansion of the UMA_HISTOGRAM_COUNTS_100 macro
+ // adapted to allow for a dynamically suffixed histogram name.
+ // Note: The factory creates and owns the histogram.
+ base::HistogramBase* histogram = base::LinearHistogram::FactoryGet(
+ user_prefs::tracked::kTrackedSplitPrefHistogramChanged + pref_path_, 1,
+ 100, // Allow counts up to 100.
+ 101, base::HistogramBase::kUmaTargetedHistogramFlag);
+ histogram->Add(count);
+}
diff --git a/chromium/services/preferences/tracked/tracked_preference_helper.h b/chromium/services/preferences/tracked/tracked_preference_helper.h
new file mode 100644
index 00000000000..182520dd106
--- /dev/null
+++ b/chromium/services/preferences/tracked/tracked_preference_helper.h
@@ -0,0 +1,75 @@
+// 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.
+
+#ifndef SERVICES_PREFERENCES_TRACKED_TRACKED_PREFERENCE_HELPER_H_
+#define SERVICES_PREFERENCES_TRACKED_TRACKED_PREFERENCE_HELPER_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/macros.h"
+#include "services/preferences/tracked/pref_hash_filter.h"
+#include "services/preferences/tracked/pref_hash_store_transaction.h"
+
+// A TrackedPreferenceHelper is a helper class for TrackedPreference which
+// handles decision making and reporting for TrackedPreference's
+// implementations.
+class TrackedPreferenceHelper {
+ public:
+ enum ResetAction {
+ DONT_RESET,
+ // WANTED_RESET is reported when DO_RESET would have been reported but the
+ // current |enforcement_level| doesn't allow a reset for the detected state.
+ WANTED_RESET,
+ DO_RESET,
+ };
+
+ TrackedPreferenceHelper(
+ const std::string& pref_path,
+ size_t reporting_id,
+ size_t reporting_ids_count,
+ prefs::mojom::TrackedPreferenceMetadata::EnforcementLevel
+ enforcement_level,
+ prefs::mojom::TrackedPreferenceMetadata::ValueType value_type);
+
+ // Returns a ResetAction stating whether a reset is desired (DO_RESET) or not
+ // (DONT_RESET) based on observing |value_state|. Can also return WANTED_RESET
+ // if a reset would have been desired but the current |enforcement_level|
+ // doesn't allow it.
+ ResetAction GetAction(
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state)
+ const;
+
+ // Returns true if the preference value may contain personal information.
+ bool IsPersonal() const;
+
+ // Reports |value_state| via UMA under |reporting_id_|.
+ // |validation_type_suffix| is appended to the reported histogram's name.
+ void ReportValidationResult(
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state,
+ base::StringPiece validation_type_suffix) const;
+
+ // Reports |reset_action| via UMA under |reporting_id_|.
+ void ReportAction(ResetAction reset_action) const;
+
+ // Reports, via UMA, the |count| of split preference entries that were
+ // considered invalid in a CHANGED event.
+ void ReportSplitPreferenceChangedCount(size_t count) const;
+
+ private:
+ const std::string pref_path_;
+
+ const size_t reporting_id_;
+ const size_t reporting_ids_count_;
+
+ // Deny setting changes and hash seeding/migration.
+ const bool enforce_;
+
+ const bool personal_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrackedPreferenceHelper);
+};
+
+#endif // SERVICES_PREFERENCES_TRACKED_TRACKED_PREFERENCE_HELPER_H_
diff --git a/chromium/services/preferences/tracked/tracked_preferences_migration.cc b/chromium/services/preferences/tracked/tracked_preferences_migration.cc
new file mode 100644
index 00000000000..f2af336e350
--- /dev/null
+++ b/chromium/services/preferences/tracked/tracked_preferences_migration.cc
@@ -0,0 +1,333 @@
+// 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 "services/preferences/tracked/tracked_preferences_migration.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/metrics/histogram.h"
+#include "base/values.h"
+#include "services/preferences/tracked/dictionary_hash_store_contents.h"
+#include "services/preferences/tracked/hash_store_contents.h"
+#include "services/preferences/tracked/interceptable_pref_filter.h"
+#include "services/preferences/tracked/pref_hash_store.h"
+#include "services/preferences/tracked/pref_hash_store_transaction.h"
+
+namespace {
+
+class TrackedPreferencesMigrator
+ : public base::RefCounted<TrackedPreferencesMigrator> {
+ public:
+ TrackedPreferencesMigrator(
+ const std::set<std::string>& unprotected_pref_names,
+ const std::set<std::string>& protected_pref_names,
+ const base::Callback<void(const std::string& key)>&
+ unprotected_store_cleaner,
+ const base::Callback<void(const std::string& key)>&
+ protected_store_cleaner,
+ const base::Callback<void(const base::Closure&)>&
+ register_on_successful_unprotected_store_write_callback,
+ const base::Callback<void(const base::Closure&)>&
+ register_on_successful_protected_store_write_callback,
+ std::unique_ptr<PrefHashStore> unprotected_pref_hash_store,
+ std::unique_ptr<PrefHashStore> protected_pref_hash_store,
+ InterceptablePrefFilter* unprotected_pref_filter,
+ InterceptablePrefFilter* protected_pref_filter);
+
+ private:
+ friend class base::RefCounted<TrackedPreferencesMigrator>;
+
+ enum PrefFilterID { UNPROTECTED_PREF_FILTER, PROTECTED_PREF_FILTER };
+
+ ~TrackedPreferencesMigrator();
+
+ // Stores the data coming in from the filter identified by |id| into this
+ // class and then calls MigrateIfReady();
+ void InterceptFilterOnLoad(
+ PrefFilterID id,
+ const InterceptablePrefFilter::FinalizeFilterOnLoadCallback&
+ finalize_filter_on_load,
+ std::unique_ptr<base::DictionaryValue> prefs);
+
+ // Proceeds with migration if both |unprotected_prefs_| and |protected_prefs_|
+ // have been set.
+ void MigrateIfReady();
+
+ const std::set<std::string> unprotected_pref_names_;
+ const std::set<std::string> protected_pref_names_;
+
+ const base::Callback<void(const std::string& key)> unprotected_store_cleaner_;
+ const base::Callback<void(const std::string& key)> protected_store_cleaner_;
+ const base::Callback<void(const base::Closure&)>
+ register_on_successful_unprotected_store_write_callback_;
+ const base::Callback<void(const base::Closure&)>
+ register_on_successful_protected_store_write_callback_;
+
+ InterceptablePrefFilter::FinalizeFilterOnLoadCallback
+ finalize_unprotected_filter_on_load_;
+ InterceptablePrefFilter::FinalizeFilterOnLoadCallback
+ finalize_protected_filter_on_load_;
+
+ std::unique_ptr<PrefHashStore> unprotected_pref_hash_store_;
+ std::unique_ptr<PrefHashStore> protected_pref_hash_store_;
+
+ std::unique_ptr<base::DictionaryValue> unprotected_prefs_;
+ std::unique_ptr<base::DictionaryValue> protected_prefs_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrackedPreferencesMigrator);
+};
+
+// Invokes |store_cleaner| for every |keys_to_clean|.
+void CleanupPrefStore(
+ const base::Callback<void(const std::string& key)>& store_cleaner,
+ const std::set<std::string>& keys_to_clean) {
+ for (std::set<std::string>::const_iterator it = keys_to_clean.begin();
+ it != keys_to_clean.end(); ++it) {
+ store_cleaner.Run(*it);
+ }
+}
+
+// If |wait_for_commit_to_destination_store|: schedules (via
+// |register_on_successful_destination_store_write_callback|) a cleanup of the
+// |keys_to_clean| from the source pref store (through |source_store_cleaner|)
+// once the destination pref store they were migrated to was successfully
+// written to disk. Otherwise, executes the cleanup right away.
+void ScheduleSourcePrefStoreCleanup(
+ const base::Callback<void(const base::Closure&)>&
+ register_on_successful_destination_store_write_callback,
+ const base::Callback<void(const std::string& key)>& source_store_cleaner,
+ const std::set<std::string>& keys_to_clean,
+ bool wait_for_commit_to_destination_store) {
+ DCHECK(!keys_to_clean.empty());
+ if (wait_for_commit_to_destination_store) {
+ register_on_successful_destination_store_write_callback.Run(
+ base::Bind(&CleanupPrefStore, source_store_cleaner, keys_to_clean));
+ } else {
+ CleanupPrefStore(source_store_cleaner, keys_to_clean);
+ }
+}
+
+// Removes hashes for |migrated_pref_names| from |origin_pref_store| using
+// the configuration/implementation in |origin_pref_hash_store|.
+void CleanupMigratedHashes(const std::set<std::string>& migrated_pref_names,
+ PrefHashStore* origin_pref_hash_store,
+ base::DictionaryValue* origin_pref_store) {
+ DictionaryHashStoreContents dictionary_contents(origin_pref_store);
+ std::unique_ptr<PrefHashStoreTransaction> transaction(
+ origin_pref_hash_store->BeginTransaction(&dictionary_contents));
+ for (std::set<std::string>::const_iterator it = migrated_pref_names.begin();
+ it != migrated_pref_names.end(); ++it) {
+ transaction->ClearHash(*it);
+ }
+}
+
+// Copies the value of each pref in |pref_names| which is set in |old_store|,
+// but not in |new_store| into |new_store|. Sets |old_store_needs_cleanup| to
+// true if any old duplicates remain in |old_store| and sets |new_store_altered|
+// to true if any value was copied to |new_store|.
+void MigratePrefsFromOldToNewStore(const std::set<std::string>& pref_names,
+ base::DictionaryValue* old_store,
+ base::DictionaryValue* new_store,
+ PrefHashStore* new_hash_store,
+ bool* old_store_needs_cleanup,
+ bool* new_store_altered) {
+ const base::DictionaryValue* old_hash_store_contents =
+ DictionaryHashStoreContents(old_store).GetContents();
+ DictionaryHashStoreContents dictionary_contents(new_store);
+ std::unique_ptr<PrefHashStoreTransaction> new_hash_store_transaction(
+ new_hash_store->BeginTransaction(&dictionary_contents));
+
+ for (std::set<std::string>::const_iterator it = pref_names.begin();
+ it != pref_names.end(); ++it) {
+ const std::string& pref_name = *it;
+ const base::Value* value_in_old_store = NULL;
+
+ // If the destination does not have a hash for this pref we will
+ // unconditionally attempt to move it.
+ bool destination_hash_missing =
+ !new_hash_store_transaction->HasHash(pref_name);
+ // If we migrate the value we will also attempt to migrate the hash.
+ bool migrated_value = false;
+ if (old_store->Get(pref_name, &value_in_old_store)) {
+ // Whether this value ends up being copied below or was left behind by a
+ // previous incomplete migration, it should be cleaned up.
+ *old_store_needs_cleanup = true;
+
+ if (!new_store->Get(pref_name, NULL)) {
+ // Copy the value from |old_store| to |new_store| rather than moving it
+ // to avoid data loss should |old_store| be flushed to disk without
+ // |new_store| having equivalently been successfully flushed to disk
+ // (e.g., on crash or in cases where |new_store| is read-only following
+ // a read error on startup).
+ new_store->Set(pref_name, value_in_old_store->DeepCopy());
+ migrated_value = true;
+ *new_store_altered = true;
+ }
+ }
+
+ if (destination_hash_missing || migrated_value) {
+ const base::Value* old_hash = NULL;
+ if (old_hash_store_contents)
+ old_hash_store_contents->Get(pref_name, &old_hash);
+ if (old_hash) {
+ new_hash_store_transaction->ImportHash(pref_name, old_hash);
+ *new_store_altered = true;
+ } else if (!destination_hash_missing) {
+ // Do not allow values to be migrated without MACs if the destination
+ // already has a MAC (http://crbug.com/414554). Remove the migrated
+ // value in order to provide the same no-op behaviour as if the pref was
+ // added to the wrong file when there was already a value for
+ // |pref_name| in |new_store|.
+ new_store->Remove(pref_name, NULL);
+ *new_store_altered = true;
+ }
+ }
+ }
+}
+
+TrackedPreferencesMigrator::TrackedPreferencesMigrator(
+ const std::set<std::string>& unprotected_pref_names,
+ const std::set<std::string>& protected_pref_names,
+ const base::Callback<void(const std::string& key)>&
+ unprotected_store_cleaner,
+ const base::Callback<void(const std::string& key)>& protected_store_cleaner,
+ const base::Callback<void(const base::Closure&)>&
+ register_on_successful_unprotected_store_write_callback,
+ const base::Callback<void(const base::Closure&)>&
+ register_on_successful_protected_store_write_callback,
+ std::unique_ptr<PrefHashStore> unprotected_pref_hash_store,
+ std::unique_ptr<PrefHashStore> protected_pref_hash_store,
+ InterceptablePrefFilter* unprotected_pref_filter,
+ InterceptablePrefFilter* protected_pref_filter)
+ : unprotected_pref_names_(unprotected_pref_names),
+ protected_pref_names_(protected_pref_names),
+ unprotected_store_cleaner_(unprotected_store_cleaner),
+ protected_store_cleaner_(protected_store_cleaner),
+ register_on_successful_unprotected_store_write_callback_(
+ register_on_successful_unprotected_store_write_callback),
+ register_on_successful_protected_store_write_callback_(
+ register_on_successful_protected_store_write_callback),
+ unprotected_pref_hash_store_(std::move(unprotected_pref_hash_store)),
+ protected_pref_hash_store_(std::move(protected_pref_hash_store)) {
+ // The callbacks bound below will own this TrackedPreferencesMigrator by
+ // reference.
+ unprotected_pref_filter->InterceptNextFilterOnLoad(
+ base::Bind(&TrackedPreferencesMigrator::InterceptFilterOnLoad, this,
+ UNPROTECTED_PREF_FILTER));
+ protected_pref_filter->InterceptNextFilterOnLoad(
+ base::Bind(&TrackedPreferencesMigrator::InterceptFilterOnLoad, this,
+ PROTECTED_PREF_FILTER));
+}
+
+TrackedPreferencesMigrator::~TrackedPreferencesMigrator() {}
+
+void TrackedPreferencesMigrator::InterceptFilterOnLoad(
+ PrefFilterID id,
+ const InterceptablePrefFilter::FinalizeFilterOnLoadCallback&
+ finalize_filter_on_load,
+ std::unique_ptr<base::DictionaryValue> prefs) {
+ switch (id) {
+ case UNPROTECTED_PREF_FILTER:
+ finalize_unprotected_filter_on_load_ = finalize_filter_on_load;
+ unprotected_prefs_ = std::move(prefs);
+ break;
+ case PROTECTED_PREF_FILTER:
+ finalize_protected_filter_on_load_ = finalize_filter_on_load;
+ protected_prefs_ = std::move(prefs);
+ break;
+ }
+
+ MigrateIfReady();
+}
+
+void TrackedPreferencesMigrator::MigrateIfReady() {
+ // Wait for both stores to have been read before proceeding.
+ if (!protected_prefs_ || !unprotected_prefs_)
+ return;
+
+ bool protected_prefs_need_cleanup = false;
+ bool unprotected_prefs_altered = false;
+ MigratePrefsFromOldToNewStore(
+ unprotected_pref_names_, protected_prefs_.get(), unprotected_prefs_.get(),
+ unprotected_pref_hash_store_.get(), &protected_prefs_need_cleanup,
+ &unprotected_prefs_altered);
+ bool unprotected_prefs_need_cleanup = false;
+ bool protected_prefs_altered = false;
+ MigratePrefsFromOldToNewStore(
+ protected_pref_names_, unprotected_prefs_.get(), protected_prefs_.get(),
+ protected_pref_hash_store_.get(), &unprotected_prefs_need_cleanup,
+ &protected_prefs_altered);
+
+ if (!unprotected_prefs_altered && !protected_prefs_altered) {
+ // Clean up any MACs that might have been previously migrated from the
+ // various stores. It's safe to leave them behind for a little while as they
+ // will be ignored unless the corresponding value is _also_ present. The
+ // cleanup must be deferred until the MACs have been written to their target
+ // stores, and doing so in a subsequent launch is easier than within the
+ // same process.
+ CleanupMigratedHashes(unprotected_pref_names_,
+ protected_pref_hash_store_.get(),
+ protected_prefs_.get());
+ CleanupMigratedHashes(protected_pref_names_,
+ unprotected_pref_hash_store_.get(),
+ unprotected_prefs_.get());
+ }
+
+ // Hand the processed prefs back to their respective filters.
+ finalize_unprotected_filter_on_load_.Run(std::move(unprotected_prefs_),
+ unprotected_prefs_altered);
+ finalize_protected_filter_on_load_.Run(std::move(protected_prefs_),
+ protected_prefs_altered);
+
+ if (unprotected_prefs_need_cleanup) {
+ // Schedule a cleanup of the |protected_pref_names_| from the unprotected
+ // prefs once the protected prefs were successfully written to disk (or
+ // do it immediately if |!protected_prefs_altered|).
+ ScheduleSourcePrefStoreCleanup(
+ register_on_successful_protected_store_write_callback_,
+ unprotected_store_cleaner_, protected_pref_names_,
+ protected_prefs_altered);
+ }
+
+ if (protected_prefs_need_cleanup) {
+ // Schedule a cleanup of the |unprotected_pref_names_| from the protected
+ // prefs once the unprotected prefs were successfully written to disk (or
+ // do it immediately if |!unprotected_prefs_altered|).
+ ScheduleSourcePrefStoreCleanup(
+ register_on_successful_unprotected_store_write_callback_,
+ protected_store_cleaner_, unprotected_pref_names_,
+ unprotected_prefs_altered);
+ }
+}
+
+} // namespace
+
+void SetupTrackedPreferencesMigration(
+ const std::set<std::string>& unprotected_pref_names,
+ const std::set<std::string>& protected_pref_names,
+ const base::Callback<void(const std::string& key)>&
+ unprotected_store_cleaner,
+ const base::Callback<void(const std::string& key)>& protected_store_cleaner,
+ const base::Callback<void(const base::Closure&)>&
+ register_on_successful_unprotected_store_write_callback,
+ const base::Callback<void(const base::Closure&)>&
+ register_on_successful_protected_store_write_callback,
+ std::unique_ptr<PrefHashStore> unprotected_pref_hash_store,
+ std::unique_ptr<PrefHashStore> protected_pref_hash_store,
+ InterceptablePrefFilter* unprotected_pref_filter,
+ InterceptablePrefFilter* protected_pref_filter) {
+ scoped_refptr<TrackedPreferencesMigrator> prefs_migrator(
+ new TrackedPreferencesMigrator(
+ unprotected_pref_names, protected_pref_names,
+ unprotected_store_cleaner, protected_store_cleaner,
+ register_on_successful_unprotected_store_write_callback,
+ register_on_successful_protected_store_write_callback,
+ std::move(unprotected_pref_hash_store),
+ std::move(protected_pref_hash_store), unprotected_pref_filter,
+ protected_pref_filter));
+}
diff --git a/chromium/services/preferences/tracked/tracked_preferences_migration.h b/chromium/services/preferences/tracked/tracked_preferences_migration.h
new file mode 100644
index 00000000000..381434aada7
--- /dev/null
+++ b/chromium/services/preferences/tracked/tracked_preferences_migration.h
@@ -0,0 +1,45 @@
+// 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.
+
+#ifndef SERVICES_PREFERENCES_TRACKED_TRACKED_PREFERENCES_MIGRATION_H_
+#define SERVICES_PREFERENCES_TRACKED_TRACKED_PREFERENCES_MIGRATION_H_
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "services/preferences/tracked/pref_hash_store.h"
+
+class InterceptablePrefFilter;
+class PrefHashStore;
+
+// Sets up InterceptablePrefFilter::FilterOnLoadInterceptors on
+// |unprotected_pref_filter| and |protected_pref_filter| which prevents each
+// filter from running their on load operations until the interceptors decide to
+// hand the prefs back to them (after migration is complete). |
+// (un)protected_store_cleaner| and
+// |register_on_successful_(un)protected_store_write_callback| are used to do
+// post-migration cleanup tasks. Those should be bound to weak pointers to avoid
+// blocking shutdown. |(un)protected_pref_hash_store| is used to migrate MACs
+// along with their protected preferences. Migrated MACs will only be cleared
+// from their old location in a subsequent run. The migration framework is
+// resilient to a failed cleanup (it will simply try again in the next Chrome
+// run).
+void SetupTrackedPreferencesMigration(
+ const std::set<std::string>& unprotected_pref_names,
+ const std::set<std::string>& protected_pref_names,
+ const base::Callback<void(const std::string& key)>&
+ unprotected_store_cleaner,
+ const base::Callback<void(const std::string& key)>& protected_store_cleaner,
+ const base::Callback<void(const base::Closure&)>&
+ register_on_successful_unprotected_store_write_callback,
+ const base::Callback<void(const base::Closure&)>&
+ register_on_successful_protected_store_write_callback,
+ std::unique_ptr<PrefHashStore> unprotected_pref_hash_store,
+ std::unique_ptr<PrefHashStore> protected_pref_hash_store,
+ InterceptablePrefFilter* unprotected_pref_filter,
+ InterceptablePrefFilter* protected_pref_filter);
+
+#endif // SERVICES_PREFERENCES_TRACKED_TRACKED_PREFERENCES_MIGRATION_H_
diff --git a/chromium/services/preferences/tracked/tracked_preferences_migration_unittest.cc b/chromium/services/preferences/tracked/tracked_preferences_migration_unittest.cc
new file mode 100644
index 00000000000..fdbdd099d3d
--- /dev/null
+++ b/chromium/services/preferences/tracked/tracked_preferences_migration_unittest.cc
@@ -0,0 +1,645 @@
+// 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 "services/preferences/tracked/tracked_preferences_migration.h"
+
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/strings/string_split.h"
+#include "base/values.h"
+#include "components/prefs/testing_pref_service.h"
+#include "services/preferences/tracked/dictionary_hash_store_contents.h"
+#include "services/preferences/tracked/hash_store_contents.h"
+#include "services/preferences/tracked/interceptable_pref_filter.h"
+#include "services/preferences/tracked/pref_hash_store.h"
+#include "services/preferences/tracked/pref_hash_store_impl.h"
+#include "services/preferences/tracked/pref_hash_store_transaction.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// An unprotected pref.
+const char kUnprotectedPref[] = "unprotected";
+// A protected pref.
+const char kProtectedPref[] = "protected";
+// A protected pref which is initially stored in the unprotected store.
+const char kPreviouslyUnprotectedPref[] = "previously.unprotected";
+// An unprotected pref which is initially stored in the protected store.
+const char kPreviouslyProtectedPref[] = "previously.protected";
+
+const char kUnprotectedPrefValue[] = "unprotected_value";
+const char kProtectedPrefValue[] = "protected_value";
+const char kPreviouslyUnprotectedPrefValue[] = "previously_unprotected_value";
+const char kPreviouslyProtectedPrefValue[] = "previously_protected_value";
+
+// A simple InterceptablePrefFilter which doesn't do anything but hand the prefs
+// back downstream in FinalizeFilterOnLoad.
+class SimpleInterceptablePrefFilter : public InterceptablePrefFilter {
+ public:
+ // PrefFilter remaining implementation.
+ void FilterUpdate(const std::string& path) override { ADD_FAILURE(); }
+ OnWriteCallbackPair FilterSerializeData(
+ base::DictionaryValue* pref_store_contents) override {
+ ADD_FAILURE();
+ return std::make_pair(base::Closure(),
+ base::Callback<void(bool success)>());
+ }
+
+ private:
+ // InterceptablePrefFilter implementation.
+ void FinalizeFilterOnLoad(
+ const PostFilterOnLoadCallback& post_filter_on_load_callback,
+ std::unique_ptr<base::DictionaryValue> pref_store_contents,
+ bool prefs_altered) override {
+ post_filter_on_load_callback.Run(std::move(pref_store_contents),
+ prefs_altered);
+ }
+};
+
+// A test fixture designed to be used like this:
+// 1) Set up initial store prefs with PresetStoreValue().
+// 2) Hand both sets of prefs to the migrator via HandPrefsToMigrator().
+// 3) Migration completes synchronously when the second store hands its prefs
+// over.
+// 4) Verifications can be made via various methods of this fixture.
+// Call Reset() to perform a second migration.
+class TrackedPreferencesMigrationTest : public testing::Test {
+ public:
+ enum MockPrefStoreID {
+ MOCK_UNPROTECTED_PREF_STORE,
+ MOCK_PROTECTED_PREF_STORE,
+ };
+
+ TrackedPreferencesMigrationTest()
+ : unprotected_prefs_(new base::DictionaryValue),
+ protected_prefs_(new base::DictionaryValue),
+ migration_modified_unprotected_store_(false),
+ migration_modified_protected_store_(false),
+ unprotected_store_migration_complete_(false),
+ protected_store_migration_complete_(false) {}
+
+ void SetUp() override { Reset(); }
+
+ void Reset() {
+ std::set<std::string> unprotected_pref_names;
+ std::set<std::string> protected_pref_names;
+ unprotected_pref_names.insert(kUnprotectedPref);
+ unprotected_pref_names.insert(kPreviouslyProtectedPref);
+ protected_pref_names.insert(kProtectedPref);
+ protected_pref_names.insert(kPreviouslyUnprotectedPref);
+
+ migration_modified_unprotected_store_ = false;
+ migration_modified_protected_store_ = false;
+ unprotected_store_migration_complete_ = false;
+ protected_store_migration_complete_ = false;
+
+ unprotected_store_successful_write_callback_.Reset();
+ protected_store_successful_write_callback_.Reset();
+
+ SetupTrackedPreferencesMigration(
+ unprotected_pref_names, protected_pref_names,
+ base::Bind(&TrackedPreferencesMigrationTest::RemovePathFromStore,
+ base::Unretained(this), MOCK_UNPROTECTED_PREF_STORE),
+ base::Bind(&TrackedPreferencesMigrationTest::RemovePathFromStore,
+ base::Unretained(this), MOCK_PROTECTED_PREF_STORE),
+ base::Bind(
+ &TrackedPreferencesMigrationTest::RegisterSuccessfulWriteClosure,
+ base::Unretained(this), MOCK_UNPROTECTED_PREF_STORE),
+ base::Bind(
+ &TrackedPreferencesMigrationTest::RegisterSuccessfulWriteClosure,
+ base::Unretained(this), MOCK_PROTECTED_PREF_STORE),
+ std::unique_ptr<PrefHashStore>(
+ new PrefHashStoreImpl(kSeed, kDeviceId, false)),
+ std::unique_ptr<PrefHashStore>(
+ new PrefHashStoreImpl(kSeed, kDeviceId, true)),
+ &mock_unprotected_pref_filter_, &mock_protected_pref_filter_);
+
+ // Verify initial expectations are met.
+ EXPECT_TRUE(HasPrefs(MOCK_UNPROTECTED_PREF_STORE));
+ EXPECT_TRUE(HasPrefs(MOCK_PROTECTED_PREF_STORE));
+ EXPECT_FALSE(
+ WasOnSuccessfulWriteCallbackRegistered(MOCK_UNPROTECTED_PREF_STORE));
+ EXPECT_FALSE(
+ WasOnSuccessfulWriteCallbackRegistered(MOCK_PROTECTED_PREF_STORE));
+ }
+
+ protected:
+ // Sets |key| to |value| in the test store identified by |store_id| before
+ // migration begins. Also sets the corresponding hash in the same store.
+ void PresetStoreValue(MockPrefStoreID store_id,
+ const std::string& key,
+ const std::string value) {
+ PresetStoreValueOnly(store_id, key, value);
+ PresetStoreValueHash(store_id, key, value);
+ }
+
+ // Stores a hash for |key| and |value| in the hash store identified by
+ // |store_id| before migration begins.
+ void PresetStoreValueHash(MockPrefStoreID store_id,
+ const std::string& key,
+ const std::string value) {
+ base::DictionaryValue* store = NULL;
+ std::unique_ptr<PrefHashStore> pref_hash_store;
+ switch (store_id) {
+ case MOCK_UNPROTECTED_PREF_STORE:
+ store = unprotected_prefs_.get();
+ pref_hash_store.reset(new PrefHashStoreImpl(kSeed, kDeviceId, false));
+ break;
+ case MOCK_PROTECTED_PREF_STORE:
+ store = protected_prefs_.get();
+ pref_hash_store.reset(new PrefHashStoreImpl(kSeed, kDeviceId, true));
+ break;
+ }
+ DCHECK(store);
+
+ base::Value string_value(value);
+ DictionaryHashStoreContents contents(store);
+ pref_hash_store->BeginTransaction(&contents)->StoreHash(key, &string_value);
+ }
+
+ // Returns true if the store opposite to |store_id| is observed for its next
+ // successful write.
+ bool WasOnSuccessfulWriteCallbackRegistered(MockPrefStoreID store_id) {
+ switch (store_id) {
+ case MOCK_UNPROTECTED_PREF_STORE:
+ return !protected_store_successful_write_callback_.is_null();
+ case MOCK_PROTECTED_PREF_STORE:
+ return !unprotected_store_successful_write_callback_.is_null();
+ }
+ NOTREACHED();
+ return false;
+ }
+
+ // Verifies that the (key, value) pairs in |expected_prefs_in_store| are found
+ // in the store identified by |store_id|.
+ void VerifyValuesStored(MockPrefStoreID store_id,
+ const base::StringPairs& expected_prefs_in_store) {
+ base::DictionaryValue* store = NULL;
+ switch (store_id) {
+ case MOCK_UNPROTECTED_PREF_STORE:
+ store = unprotected_prefs_.get();
+ break;
+ case MOCK_PROTECTED_PREF_STORE:
+ store = protected_prefs_.get();
+ break;
+ }
+ DCHECK(store);
+
+ for (base::StringPairs::const_iterator it = expected_prefs_in_store.begin();
+ it != expected_prefs_in_store.end(); ++it) {
+ std::string val;
+ EXPECT_TRUE(store->GetString(it->first, &val));
+ EXPECT_EQ(it->second, val);
+ }
+ }
+
+ // Determines whether |expected_pref_in_hash_store| has a hash in the hash
+ // store identified by |store_id|.
+ bool ContainsHash(MockPrefStoreID store_id,
+ std::string expected_pref_in_hash_store) {
+ base::DictionaryValue* store = NULL;
+ switch (store_id) {
+ case MOCK_UNPROTECTED_PREF_STORE:
+ store = unprotected_prefs_.get();
+ break;
+ case MOCK_PROTECTED_PREF_STORE:
+ store = protected_prefs_.get();
+ break;
+ }
+ DCHECK(store);
+ const base::DictionaryValue* hash_store_contents =
+ DictionaryHashStoreContents(store).GetContents();
+ return hash_store_contents &&
+ hash_store_contents->GetString(expected_pref_in_hash_store,
+ static_cast<std::string*>(NULL));
+ }
+
+ // Both stores need to hand their prefs over in order for migration to kick
+ // in.
+ void HandPrefsToMigrator(MockPrefStoreID store_id) {
+ switch (store_id) {
+ case MOCK_UNPROTECTED_PREF_STORE:
+ mock_unprotected_pref_filter_.FilterOnLoad(
+ base::Bind(&TrackedPreferencesMigrationTest::GetPrefsBack,
+ base::Unretained(this), MOCK_UNPROTECTED_PREF_STORE),
+ std::move(unprotected_prefs_));
+ break;
+ case MOCK_PROTECTED_PREF_STORE:
+ mock_protected_pref_filter_.FilterOnLoad(
+ base::Bind(&TrackedPreferencesMigrationTest::GetPrefsBack,
+ base::Unretained(this), MOCK_PROTECTED_PREF_STORE),
+ std::move(protected_prefs_));
+ break;
+ }
+ }
+
+ bool HasPrefs(MockPrefStoreID store_id) {
+ switch (store_id) {
+ case MOCK_UNPROTECTED_PREF_STORE:
+ return !!unprotected_prefs_;
+ case MOCK_PROTECTED_PREF_STORE:
+ return !!protected_prefs_;
+ }
+ NOTREACHED();
+ return false;
+ }
+
+ bool StoreModifiedByMigration(MockPrefStoreID store_id) {
+ switch (store_id) {
+ case MOCK_UNPROTECTED_PREF_STORE:
+ return migration_modified_unprotected_store_;
+ case MOCK_PROTECTED_PREF_STORE:
+ return migration_modified_protected_store_;
+ }
+ NOTREACHED();
+ return false;
+ }
+
+ bool MigrationCompleted() {
+ return unprotected_store_migration_complete_ &&
+ protected_store_migration_complete_;
+ }
+
+ void SimulateSuccessfulWrite(MockPrefStoreID store_id) {
+ switch (store_id) {
+ case MOCK_UNPROTECTED_PREF_STORE:
+ EXPECT_FALSE(unprotected_store_successful_write_callback_.is_null());
+ unprotected_store_successful_write_callback_.Run();
+ unprotected_store_successful_write_callback_.Reset();
+ break;
+ case MOCK_PROTECTED_PREF_STORE:
+ EXPECT_FALSE(protected_store_successful_write_callback_.is_null());
+ protected_store_successful_write_callback_.Run();
+ protected_store_successful_write_callback_.Reset();
+ break;
+ }
+ }
+
+ private:
+ void RegisterSuccessfulWriteClosure(
+ MockPrefStoreID store_id,
+ const base::Closure& successful_write_closure) {
+ switch (store_id) {
+ case MOCK_UNPROTECTED_PREF_STORE:
+ EXPECT_TRUE(unprotected_store_successful_write_callback_.is_null());
+ unprotected_store_successful_write_callback_ = successful_write_closure;
+ break;
+ case MOCK_PROTECTED_PREF_STORE:
+ EXPECT_TRUE(protected_store_successful_write_callback_.is_null());
+ protected_store_successful_write_callback_ = successful_write_closure;
+ break;
+ }
+ }
+
+ // Helper given as an InterceptablePrefFilter::FinalizeFilterOnLoadCallback
+ // to the migrator to be invoked when it's done.
+ void GetPrefsBack(MockPrefStoreID store_id,
+ std::unique_ptr<base::DictionaryValue> prefs,
+ bool prefs_altered) {
+ switch (store_id) {
+ case MOCK_UNPROTECTED_PREF_STORE:
+ EXPECT_FALSE(unprotected_prefs_);
+ unprotected_prefs_ = std::move(prefs);
+ migration_modified_unprotected_store_ = prefs_altered;
+ unprotected_store_migration_complete_ = true;
+ break;
+ case MOCK_PROTECTED_PREF_STORE:
+ EXPECT_FALSE(protected_prefs_);
+ protected_prefs_ = std::move(prefs);
+ migration_modified_protected_store_ = prefs_altered;
+ protected_store_migration_complete_ = true;
+ break;
+ }
+ }
+
+ // Helper given as a cleaning callback to the migrator.
+ void RemovePathFromStore(MockPrefStoreID store_id, const std::string& key) {
+ switch (store_id) {
+ case MOCK_UNPROTECTED_PREF_STORE:
+ ASSERT_TRUE(unprotected_prefs_);
+ unprotected_prefs_->RemovePath(key, NULL);
+ break;
+ case MOCK_PROTECTED_PREF_STORE:
+ ASSERT_TRUE(protected_prefs_);
+ protected_prefs_->RemovePath(key, NULL);
+ break;
+ }
+ }
+
+ // Sets |key| to |value| in the test store identified by |store_id| before
+ // migration begins. Does not store a preference hash.
+ void PresetStoreValueOnly(MockPrefStoreID store_id,
+ const std::string& key,
+ const std::string value) {
+ base::DictionaryValue* store = NULL;
+ switch (store_id) {
+ case MOCK_UNPROTECTED_PREF_STORE:
+ store = unprotected_prefs_.get();
+ break;
+ case MOCK_PROTECTED_PREF_STORE:
+ store = protected_prefs_.get();
+ break;
+ }
+ DCHECK(store);
+
+ store->SetString(key, value);
+ }
+
+ static const char kSeed[];
+ static const char kDeviceId[];
+
+ std::unique_ptr<base::DictionaryValue> unprotected_prefs_;
+ std::unique_ptr<base::DictionaryValue> protected_prefs_;
+
+ SimpleInterceptablePrefFilter mock_unprotected_pref_filter_;
+ SimpleInterceptablePrefFilter mock_protected_pref_filter_;
+
+ base::Closure unprotected_store_successful_write_callback_;
+ base::Closure protected_store_successful_write_callback_;
+
+ bool migration_modified_unprotected_store_;
+ bool migration_modified_protected_store_;
+
+ bool unprotected_store_migration_complete_;
+ bool protected_store_migration_complete_;
+
+ TestingPrefServiceSimple local_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrackedPreferencesMigrationTest);
+};
+
+// static
+const char TrackedPreferencesMigrationTest::kSeed[] = "seed";
+
+// static
+const char TrackedPreferencesMigrationTest::kDeviceId[] = "device-id";
+
+} // namespace
+
+TEST_F(TrackedPreferencesMigrationTest, NoMigrationRequired) {
+ PresetStoreValue(MOCK_UNPROTECTED_PREF_STORE, kUnprotectedPref,
+ kUnprotectedPrefValue);
+ PresetStoreValue(MOCK_PROTECTED_PREF_STORE, kProtectedPref,
+ kProtectedPrefValue);
+
+ EXPECT_TRUE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kUnprotectedPref));
+ EXPECT_FALSE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kProtectedPref));
+
+ EXPECT_TRUE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kProtectedPref));
+ EXPECT_FALSE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kUnprotectedPref));
+
+ // Hand unprotected prefs to the migrator which should wait for the protected
+ // prefs.
+ HandPrefsToMigrator(MOCK_UNPROTECTED_PREF_STORE);
+ EXPECT_FALSE(HasPrefs(MOCK_UNPROTECTED_PREF_STORE));
+ EXPECT_TRUE(HasPrefs(MOCK_PROTECTED_PREF_STORE));
+ EXPECT_FALSE(MigrationCompleted());
+
+ // Hand protected prefs to the migrator which should proceed with the
+ // migration synchronously.
+ HandPrefsToMigrator(MOCK_PROTECTED_PREF_STORE);
+ EXPECT_TRUE(MigrationCompleted());
+
+ // Prefs should have been handed back over.
+ EXPECT_TRUE(HasPrefs(MOCK_UNPROTECTED_PREF_STORE));
+ EXPECT_TRUE(HasPrefs(MOCK_PROTECTED_PREF_STORE));
+ EXPECT_FALSE(
+ WasOnSuccessfulWriteCallbackRegistered(MOCK_UNPROTECTED_PREF_STORE));
+ EXPECT_FALSE(
+ WasOnSuccessfulWriteCallbackRegistered(MOCK_PROTECTED_PREF_STORE));
+ EXPECT_FALSE(StoreModifiedByMigration(MOCK_UNPROTECTED_PREF_STORE));
+ EXPECT_FALSE(StoreModifiedByMigration(MOCK_PROTECTED_PREF_STORE));
+
+ base::StringPairs expected_unprotected_values;
+ expected_unprotected_values.push_back(
+ std::make_pair(kUnprotectedPref, kUnprotectedPrefValue));
+ VerifyValuesStored(MOCK_UNPROTECTED_PREF_STORE, expected_unprotected_values);
+
+ base::StringPairs expected_protected_values;
+ expected_protected_values.push_back(
+ std::make_pair(kProtectedPref, kProtectedPrefValue));
+ VerifyValuesStored(MOCK_PROTECTED_PREF_STORE, expected_protected_values);
+
+ EXPECT_TRUE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kUnprotectedPref));
+ EXPECT_FALSE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kProtectedPref));
+
+ EXPECT_TRUE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kProtectedPref));
+ EXPECT_FALSE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kUnprotectedPref));
+}
+
+TEST_F(TrackedPreferencesMigrationTest, FullMigration) {
+ PresetStoreValue(MOCK_UNPROTECTED_PREF_STORE, kUnprotectedPref,
+ kUnprotectedPrefValue);
+ PresetStoreValue(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyUnprotectedPref,
+ kPreviouslyUnprotectedPrefValue);
+ PresetStoreValue(MOCK_PROTECTED_PREF_STORE, kProtectedPref,
+ kProtectedPrefValue);
+ PresetStoreValue(MOCK_PROTECTED_PREF_STORE, kPreviouslyProtectedPref,
+ kPreviouslyProtectedPrefValue);
+
+ EXPECT_TRUE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kUnprotectedPref));
+ EXPECT_TRUE(
+ ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyUnprotectedPref));
+ EXPECT_FALSE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kProtectedPref));
+ EXPECT_FALSE(
+ ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyProtectedPref));
+
+ EXPECT_FALSE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kUnprotectedPref));
+ EXPECT_FALSE(
+ ContainsHash(MOCK_PROTECTED_PREF_STORE, kPreviouslyUnprotectedPref));
+ EXPECT_TRUE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kProtectedPref));
+ EXPECT_TRUE(
+ ContainsHash(MOCK_PROTECTED_PREF_STORE, kPreviouslyProtectedPref));
+
+ HandPrefsToMigrator(MOCK_UNPROTECTED_PREF_STORE);
+ EXPECT_FALSE(HasPrefs(MOCK_UNPROTECTED_PREF_STORE));
+ EXPECT_TRUE(HasPrefs(MOCK_PROTECTED_PREF_STORE));
+ EXPECT_FALSE(MigrationCompleted());
+
+ HandPrefsToMigrator(MOCK_PROTECTED_PREF_STORE);
+ EXPECT_TRUE(MigrationCompleted());
+
+ EXPECT_TRUE(HasPrefs(MOCK_UNPROTECTED_PREF_STORE));
+ EXPECT_TRUE(HasPrefs(MOCK_PROTECTED_PREF_STORE));
+ EXPECT_TRUE(
+ WasOnSuccessfulWriteCallbackRegistered(MOCK_UNPROTECTED_PREF_STORE));
+ EXPECT_TRUE(
+ WasOnSuccessfulWriteCallbackRegistered(MOCK_PROTECTED_PREF_STORE));
+ EXPECT_TRUE(StoreModifiedByMigration(MOCK_UNPROTECTED_PREF_STORE));
+ EXPECT_TRUE(StoreModifiedByMigration(MOCK_PROTECTED_PREF_STORE));
+
+ // Values should have been migrated to their store, but migrated values should
+ // still remain in the source store until cleanup tasks are later invoked.
+ {
+ base::StringPairs expected_unprotected_values;
+ expected_unprotected_values.push_back(
+ std::make_pair(kUnprotectedPref, kUnprotectedPrefValue));
+ expected_unprotected_values.push_back(std::make_pair(
+ kPreviouslyProtectedPref, kPreviouslyProtectedPrefValue));
+ expected_unprotected_values.push_back(std::make_pair(
+ kPreviouslyUnprotectedPref, kPreviouslyUnprotectedPrefValue));
+ VerifyValuesStored(MOCK_UNPROTECTED_PREF_STORE,
+ expected_unprotected_values);
+
+ base::StringPairs expected_protected_values;
+ expected_protected_values.push_back(
+ std::make_pair(kProtectedPref, kProtectedPrefValue));
+ expected_protected_values.push_back(std::make_pair(
+ kPreviouslyUnprotectedPref, kPreviouslyUnprotectedPrefValue));
+ expected_unprotected_values.push_back(std::make_pair(
+ kPreviouslyProtectedPref, kPreviouslyProtectedPrefValue));
+ VerifyValuesStored(MOCK_PROTECTED_PREF_STORE, expected_protected_values);
+
+ EXPECT_TRUE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kUnprotectedPref));
+ EXPECT_TRUE(
+ ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyUnprotectedPref));
+ EXPECT_FALSE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kProtectedPref));
+ EXPECT_TRUE(
+ ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyProtectedPref));
+
+ EXPECT_FALSE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kUnprotectedPref));
+ EXPECT_TRUE(
+ ContainsHash(MOCK_PROTECTED_PREF_STORE, kPreviouslyUnprotectedPref));
+ EXPECT_TRUE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kProtectedPref));
+ EXPECT_TRUE(
+ ContainsHash(MOCK_PROTECTED_PREF_STORE, kPreviouslyProtectedPref));
+ }
+
+ // A successful write of the protected pref store should result in a clean up
+ // of the unprotected store.
+ SimulateSuccessfulWrite(MOCK_PROTECTED_PREF_STORE);
+
+ {
+ base::StringPairs expected_unprotected_values;
+ expected_unprotected_values.push_back(
+ std::make_pair(kUnprotectedPref, kUnprotectedPrefValue));
+ expected_unprotected_values.push_back(std::make_pair(
+ kPreviouslyProtectedPref, kPreviouslyProtectedPrefValue));
+ VerifyValuesStored(MOCK_UNPROTECTED_PREF_STORE,
+ expected_unprotected_values);
+
+ base::StringPairs expected_protected_values;
+ expected_protected_values.push_back(
+ std::make_pair(kProtectedPref, kProtectedPrefValue));
+ expected_protected_values.push_back(std::make_pair(
+ kPreviouslyUnprotectedPref, kPreviouslyUnprotectedPrefValue));
+ expected_unprotected_values.push_back(std::make_pair(
+ kPreviouslyProtectedPref, kPreviouslyProtectedPrefValue));
+ VerifyValuesStored(MOCK_PROTECTED_PREF_STORE, expected_protected_values);
+ }
+
+ SimulateSuccessfulWrite(MOCK_UNPROTECTED_PREF_STORE);
+
+ {
+ base::StringPairs expected_unprotected_values;
+ expected_unprotected_values.push_back(
+ std::make_pair(kUnprotectedPref, kUnprotectedPrefValue));
+ expected_unprotected_values.push_back(std::make_pair(
+ kPreviouslyProtectedPref, kPreviouslyProtectedPrefValue));
+ VerifyValuesStored(MOCK_UNPROTECTED_PREF_STORE,
+ expected_unprotected_values);
+
+ base::StringPairs expected_protected_values;
+ expected_protected_values.push_back(
+ std::make_pair(kProtectedPref, kProtectedPrefValue));
+ expected_protected_values.push_back(std::make_pair(
+ kPreviouslyUnprotectedPref, kPreviouslyUnprotectedPrefValue));
+ VerifyValuesStored(MOCK_PROTECTED_PREF_STORE, expected_protected_values);
+ }
+
+ // Hashes are not cleaned up yet.
+ EXPECT_TRUE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kUnprotectedPref));
+ EXPECT_TRUE(
+ ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyUnprotectedPref));
+ EXPECT_FALSE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kProtectedPref));
+ EXPECT_TRUE(
+ ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyProtectedPref));
+
+ EXPECT_FALSE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kUnprotectedPref));
+ EXPECT_TRUE(
+ ContainsHash(MOCK_PROTECTED_PREF_STORE, kPreviouslyUnprotectedPref));
+ EXPECT_TRUE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kProtectedPref));
+ EXPECT_TRUE(
+ ContainsHash(MOCK_PROTECTED_PREF_STORE, kPreviouslyProtectedPref));
+
+ Reset();
+
+ HandPrefsToMigrator(MOCK_UNPROTECTED_PREF_STORE);
+ HandPrefsToMigrator(MOCK_PROTECTED_PREF_STORE);
+ EXPECT_TRUE(MigrationCompleted());
+
+ // Hashes are cleaned up.
+ EXPECT_TRUE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kUnprotectedPref));
+ EXPECT_FALSE(
+ ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyUnprotectedPref));
+ EXPECT_FALSE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kProtectedPref));
+ EXPECT_TRUE(
+ ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyProtectedPref));
+
+ EXPECT_FALSE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kUnprotectedPref));
+ EXPECT_TRUE(
+ ContainsHash(MOCK_PROTECTED_PREF_STORE, kPreviouslyUnprotectedPref));
+ EXPECT_TRUE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kProtectedPref));
+ EXPECT_FALSE(
+ ContainsHash(MOCK_PROTECTED_PREF_STORE, kPreviouslyProtectedPref));
+}
+
+TEST_F(TrackedPreferencesMigrationTest, CleanupOnly) {
+ // Already migrated; only cleanup needed.
+ PresetStoreValue(MOCK_UNPROTECTED_PREF_STORE, kUnprotectedPref,
+ kUnprotectedPrefValue);
+ PresetStoreValue(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyProtectedPref,
+ kPreviouslyProtectedPrefValue);
+ PresetStoreValue(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyUnprotectedPref,
+ kPreviouslyUnprotectedPrefValue);
+ PresetStoreValue(MOCK_PROTECTED_PREF_STORE, kProtectedPref,
+ kProtectedPrefValue);
+ PresetStoreValue(MOCK_PROTECTED_PREF_STORE, kPreviouslyProtectedPref,
+ kPreviouslyProtectedPrefValue);
+ PresetStoreValue(MOCK_PROTECTED_PREF_STORE, kPreviouslyUnprotectedPref,
+ kPreviouslyUnprotectedPrefValue);
+
+ HandPrefsToMigrator(MOCK_UNPROTECTED_PREF_STORE);
+ EXPECT_FALSE(HasPrefs(MOCK_UNPROTECTED_PREF_STORE));
+ EXPECT_TRUE(HasPrefs(MOCK_PROTECTED_PREF_STORE));
+ EXPECT_FALSE(MigrationCompleted());
+
+ HandPrefsToMigrator(MOCK_PROTECTED_PREF_STORE);
+ EXPECT_TRUE(MigrationCompleted());
+
+ EXPECT_TRUE(HasPrefs(MOCK_UNPROTECTED_PREF_STORE));
+ EXPECT_TRUE(HasPrefs(MOCK_PROTECTED_PREF_STORE));
+ EXPECT_FALSE(
+ WasOnSuccessfulWriteCallbackRegistered(MOCK_UNPROTECTED_PREF_STORE));
+ EXPECT_FALSE(
+ WasOnSuccessfulWriteCallbackRegistered(MOCK_PROTECTED_PREF_STORE));
+ EXPECT_FALSE(StoreModifiedByMigration(MOCK_UNPROTECTED_PREF_STORE));
+ EXPECT_FALSE(StoreModifiedByMigration(MOCK_PROTECTED_PREF_STORE));
+
+ // Cleanup should happen synchronously if the values were already present in
+ // their destination stores.
+ {
+ base::StringPairs expected_unprotected_values;
+ expected_unprotected_values.push_back(
+ std::make_pair(kUnprotectedPref, kUnprotectedPrefValue));
+ expected_unprotected_values.push_back(std::make_pair(
+ kPreviouslyProtectedPref, kPreviouslyProtectedPrefValue));
+ VerifyValuesStored(MOCK_UNPROTECTED_PREF_STORE,
+ expected_unprotected_values);
+
+ base::StringPairs expected_protected_values;
+ expected_protected_values.push_back(
+ std::make_pair(kProtectedPref, kProtectedPrefValue));
+ expected_protected_values.push_back(std::make_pair(
+ kPreviouslyUnprotectedPref, kPreviouslyUnprotectedPrefValue));
+ VerifyValuesStored(MOCK_PROTECTED_PREF_STORE, expected_protected_values);
+ }
+}
diff --git a/chromium/services/preferences/tracked/tracked_split_preference.cc b/chromium/services/preferences/tracked/tracked_split_preference.cc
new file mode 100644
index 00000000000..da6ceba8fbf
--- /dev/null
+++ b/chromium/services/preferences/tracked/tracked_split_preference.cc
@@ -0,0 +1,120 @@
+// 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 "services/preferences/tracked/tracked_split_preference.h"
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom.h"
+#include "services/preferences/tracked/pref_hash_store_transaction.h"
+
+using ValueState =
+ prefs::mojom::TrackedPreferenceValidationDelegate::ValueState;
+
+TrackedSplitPreference::TrackedSplitPreference(
+ const std::string& pref_path,
+ size_t reporting_id,
+ size_t reporting_ids_count,
+ prefs::mojom::TrackedPreferenceMetadata::EnforcementLevel enforcement_level,
+ prefs::mojom::TrackedPreferenceMetadata::ValueType value_type,
+ prefs::mojom::TrackedPreferenceValidationDelegate* delegate)
+ : pref_path_(pref_path),
+ helper_(pref_path,
+ reporting_id,
+ reporting_ids_count,
+ enforcement_level,
+ value_type),
+ delegate_(delegate) {}
+
+TrackedPreferenceType TrackedSplitPreference::GetType() const {
+ return TrackedPreferenceType::SPLIT;
+}
+
+void TrackedSplitPreference::OnNewValue(
+ const base::Value* value,
+ PrefHashStoreTransaction* transaction) const {
+ const base::DictionaryValue* dict_value = NULL;
+ if (value && !value->GetAsDictionary(&dict_value)) {
+ NOTREACHED();
+ return;
+ }
+ transaction->StoreSplitHash(pref_path_, dict_value);
+}
+
+bool TrackedSplitPreference::EnforceAndReport(
+ base::DictionaryValue* pref_store_contents,
+ PrefHashStoreTransaction* transaction,
+ PrefHashStoreTransaction* external_validation_transaction) const {
+ base::DictionaryValue* dict_value = NULL;
+ if (!pref_store_contents->GetDictionary(pref_path_, &dict_value) &&
+ pref_store_contents->Get(pref_path_, NULL)) {
+ // There should be a dictionary or nothing at |pref_path_|.
+ NOTREACHED();
+ return false;
+ }
+
+ std::vector<std::string> invalid_keys;
+ ValueState value_state =
+ transaction->CheckSplitValue(pref_path_, dict_value, &invalid_keys);
+
+ if (value_state == ValueState::CHANGED)
+ helper_.ReportSplitPreferenceChangedCount(invalid_keys.size());
+
+ helper_.ReportValidationResult(value_state, transaction->GetStoreUMASuffix());
+
+ ValueState external_validation_value_state = ValueState::UNSUPPORTED;
+ std::vector<std::string> external_validation_invalid_keys;
+ if (external_validation_transaction) {
+ external_validation_value_state =
+ external_validation_transaction->CheckSplitValue(
+ pref_path_, dict_value, &external_validation_invalid_keys);
+ helper_.ReportValidationResult(
+ external_validation_value_state,
+ external_validation_transaction->GetStoreUMASuffix());
+ }
+
+ if (delegate_) {
+ delegate_->OnSplitPreferenceValidation(
+ pref_path_, invalid_keys, external_validation_invalid_keys, value_state,
+ external_validation_value_state, helper_.IsPersonal());
+ }
+ TrackedPreferenceHelper::ResetAction reset_action =
+ helper_.GetAction(value_state);
+ helper_.ReportAction(reset_action);
+
+ bool was_reset = false;
+ if (reset_action == TrackedPreferenceHelper::DO_RESET) {
+ if (value_state == ValueState::CHANGED) {
+ DCHECK(!invalid_keys.empty());
+
+ for (std::vector<std::string>::const_iterator it = invalid_keys.begin();
+ it != invalid_keys.end(); ++it) {
+ dict_value->Remove(*it, NULL);
+ }
+ } else {
+ pref_store_contents->RemovePath(pref_path_, NULL);
+ }
+ was_reset = true;
+ }
+
+ if (value_state != ValueState::UNCHANGED) {
+ // Store the hash for the new value (whether it was reset or not).
+ const base::DictionaryValue* new_dict_value = NULL;
+ pref_store_contents->GetDictionary(pref_path_, &new_dict_value);
+ transaction->StoreSplitHash(pref_path_, new_dict_value);
+ }
+
+ // Update MACs in the external store if there is one and there either was a
+ // reset or external validation failed.
+ if (external_validation_transaction &&
+ (was_reset || external_validation_value_state != ValueState::UNCHANGED)) {
+ const base::DictionaryValue* new_dict_value = nullptr;
+ pref_store_contents->GetDictionary(pref_path_, &new_dict_value);
+ external_validation_transaction->StoreSplitHash(pref_path_, new_dict_value);
+ }
+
+ return was_reset;
+}
diff --git a/chromium/services/preferences/tracked/tracked_split_preference.h b/chromium/services/preferences/tracked/tracked_split_preference.h
new file mode 100644
index 00000000000..f416b3695fa
--- /dev/null
+++ b/chromium/services/preferences/tracked/tracked_split_preference.h
@@ -0,0 +1,58 @@
+// 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.
+
+#ifndef SERVICES_PREFERENCES_TRACKED_TRACKED_SPLIT_PREFERENCE_H_
+#define SERVICES_PREFERENCES_TRACKED_TRACKED_SPLIT_PREFERENCE_H_
+
+#include <stddef.h>
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "services/preferences/tracked/pref_hash_filter.h"
+#include "services/preferences/tracked/tracked_preference.h"
+#include "services/preferences/tracked/tracked_preference_helper.h"
+
+namespace prefs {
+namespace mojom {
+class TrackedPreferenceValidationDelegate;
+}
+}
+
+// A TrackedSplitPreference must be tracking a dictionary pref. Each top-level
+// entry in its dictionary is tracked and enforced independently. An optional
+// delegate is notified of the status of the preference during enforcement.
+// Note: preferences using this strategy must be kept in sync with
+// TrackedSplitPreferences in histograms.xml.
+class TrackedSplitPreference : public TrackedPreference {
+ public:
+ // Constructs a TrackedSplitPreference. |pref_path| must be a dictionary pref.
+ TrackedSplitPreference(
+ const std::string& pref_path,
+ size_t reporting_id,
+ size_t reporting_ids_count,
+ prefs::mojom::TrackedPreferenceMetadata::EnforcementLevel
+ enforcement_level,
+ prefs::mojom::TrackedPreferenceMetadata::ValueType value_type,
+ prefs::mojom::TrackedPreferenceValidationDelegate* delegate);
+
+ // TrackedPreference implementation.
+ TrackedPreferenceType GetType() const override;
+ void OnNewValue(const base::Value* value,
+ PrefHashStoreTransaction* transaction) const override;
+ bool EnforceAndReport(
+ base::DictionaryValue* pref_store_contents,
+ PrefHashStoreTransaction* transaction,
+ PrefHashStoreTransaction* external_validation_transaction) const override;
+
+ private:
+ const std::string pref_path_;
+ const TrackedPreferenceHelper helper_;
+ prefs::mojom::TrackedPreferenceValidationDelegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrackedSplitPreference);
+};
+
+#endif // SERVICES_PREFERENCES_TRACKED_TRACKED_SPLIT_PREFERENCE_H_
diff --git a/chromium/services/preferences/unittest_manifest.json b/chromium/services/preferences/unittest_manifest.json
new file mode 100644
index 00000000000..df6d7441066
--- /dev/null
+++ b/chromium/services/preferences/unittest_manifest.json
@@ -0,0 +1,16 @@
+{
+ "name": "prefs_unittests",
+ "display_name": "Prefs Unittests",
+ "interface_provider_specs": {
+ "service_manager:connector": {
+ "provides": {
+ "service_manager:service_factory": [
+ "service_manager::mojom::ServiceFactory"
+ ]
+ },
+ "requires": {
+ "preferences": [ "pref_client", "pref_control" ]
+ }
+ }
+ }
+}