blob: e7866ca1cea41d97fd628b3874f0eb64964f61a9 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync_preferences/dual_layer_user_pref_store.h"
#include <optional>
#include <string>
#include <utility>
#include "base/auto_reset.h"
#include "base/barrier_closure.h"
#include "base/logging.h"
#include "base/observer_list.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "build/chromeos_buildflags.h"
#include "components/sync/service/sync_service.h"
#include "components/sync/service/sync_user_settings.h"
#include "components/sync_preferences/pref_model_associator_client.h"
#include "components/sync_preferences/preferences_merge_helper.h"
#include "components/sync_preferences/syncable_prefs_database.h"
namespace sync_preferences {
DualLayerUserPrefStore::UnderlyingPrefStoreObserver::
UnderlyingPrefStoreObserver(DualLayerUserPrefStore* outer,
bool is_account_store)
: outer_(outer), is_account_store_(is_account_store) {
DCHECK(outer_);
}
void DualLayerUserPrefStore::UnderlyingPrefStoreObserver::OnPrefValueChanged(
const std::string& key) {
// Ignore this notification if it originated from the outer store - in that
// case, `DualLayerUserPrefStore` itself will send notifications as
// appropriate. This avoids dual notifications even though there are dual
// writes.
if (outer_->is_setting_prefs_) {
return;
}
// Otherwise: This must've been a write directly to the underlying store, so
// notify any observers.
// Note: Observers should only be notified if the effective value of a pref
// changes.
// Note: The effective value will not change if this is a write to the local
// store, but the account store has a value that overrides it.
if (!is_account_store_ &&
outer_->GetAccountPrefStore()->GetValue(key, nullptr) &&
!outer_->IsPrefKeyMergeable(key)) {
return;
}
for (PrefStore::Observer& observer : outer_->observers_) {
observer.OnPrefValueChanged(key);
}
}
void DualLayerUserPrefStore::UnderlyingPrefStoreObserver::
OnInitializationCompleted(bool succeeded) {
initialization_succeeded_ = succeeded;
// Notify observers only after all underlying PrefStores are initialized.
if (!outer_->IsInitializationComplete()) {
return;
}
// Forward error if any of the underlying store reported error upon
// ReadPrefsAsync().
if (outer_->read_error_delegate_) {
if (auto read_error = outer_->GetReadError();
read_error != PersistentPrefStore::PREF_READ_ERROR_NONE) {
outer_->read_error_delegate_->OnError(read_error);
}
}
for (auto& observer : outer_->observers_) {
observer.OnInitializationCompleted(outer_->IsInitializationSuccessful());
}
}
bool DualLayerUserPrefStore::UnderlyingPrefStoreObserver::
initialization_succeeded() const {
CHECK(outer_->IsInitializationComplete());
return initialization_succeeded_;
}
DualLayerUserPrefStore::DualLayerUserPrefStore(
scoped_refptr<PersistentPrefStore> local_pref_store,
scoped_refptr<PersistentPrefStore> account_pref_store,
scoped_refptr<PrefModelAssociatorClient> pref_model_associator_client)
: local_pref_store_(std::move(local_pref_store)),
account_pref_store_(std::move(account_pref_store)),
local_pref_store_observer_(this, /*is_account_store=*/false),
account_pref_store_observer_(this, /*is_account_store=*/true),
pref_model_associator_client_(pref_model_associator_client) {
local_pref_store_->AddObserver(&local_pref_store_observer_);
account_pref_store_->AddObserver(&account_pref_store_observer_);
}
DualLayerUserPrefStore::~DualLayerUserPrefStore() {
account_pref_store_->RemoveObserver(&account_pref_store_observer_);
local_pref_store_->RemoveObserver(&local_pref_store_observer_);
}
scoped_refptr<PersistentPrefStore> DualLayerUserPrefStore::GetLocalPrefStore() {
return local_pref_store_;
}
scoped_refptr<WriteablePrefStore>
DualLayerUserPrefStore::GetAccountPrefStore() {
return account_pref_store_;
}
void DualLayerUserPrefStore::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void DualLayerUserPrefStore::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
bool DualLayerUserPrefStore::HasObservers() const {
return !observers_.empty();
}
bool DualLayerUserPrefStore::IsInitializationComplete() const {
return local_pref_store_->IsInitializationComplete() &&
account_pref_store_->IsInitializationComplete();
}
bool DualLayerUserPrefStore::GetValue(base::StringPiece key,
const base::Value** result) const {
const std::string pref_name(key);
if (!ShouldGetValueFromAccountStore(pref_name)) {
return local_pref_store_->GetValue(key, result);
}
const base::Value* account_value = nullptr;
const base::Value* local_value = nullptr;
account_pref_store_->GetValue(key, &account_value);
local_pref_store_->GetValue(key, &local_value);
if (!account_value && !local_value) {
// Pref doesn't exist.
return false;
}
if (result) {
// Merge pref if `key` exists in both the stores.
if (account_value && local_value) {
*result = MaybeMerge(pref_name, *local_value, *account_value);
CHECK(*result);
} else if (account_value) {
*result = account_value;
} else {
*result = local_value;
}
}
return true;
}
base::Value::Dict DualLayerUserPrefStore::GetValues() const {
base::Value::Dict values = local_pref_store_->GetValues();
for (const std::string& pref_name : GetPrefNamesInAccountStore()) {
// Filter out prefs which should not be queried from the account store, for
// example, prefs requiring history opt-in if history sync is off.
if (ShouldGetValueFromAccountStore(pref_name)) {
const base::Value* value = nullptr;
// GetValue() will merge the value if needed.
GetValue(pref_name, &value);
CHECK(value);
values.SetByDottedPath(pref_name, value->Clone());
}
}
return values;
}
void DualLayerUserPrefStore::SetValue(const std::string& key,
base::Value value,
uint32_t flags) {
const base::Value* initial_value = nullptr;
// Only notify if something actually changed.
// Note: `value` is still added to the stores in case `key` was missing from
// any or had a different value.
bool should_notify =
!GetValue(key, &initial_value) || (*initial_value != value);
{
base::AutoReset<bool> setting_prefs(&is_setting_prefs_, true);
if (ShouldSetValueInAccountStore(key)) {
if (IsPrefKeyMergeable(key)) {
auto [new_local_value, new_account_value] =
UnmergeValue(key, std::move(value), flags);
account_pref_store_->SetValue(key, std::move(new_account_value), flags);
local_pref_store_->SetValue(key, std::move(new_local_value), flags);
} else {
account_pref_store_->SetValue(key, value.Clone(), flags);
local_pref_store_->SetValue(key, std::move(value), flags);
}
} else {
local_pref_store_->SetValue(key, std::move(value), flags);
}
}
if (should_notify) {
for (PrefStore::Observer& observer : observers_) {
observer.OnPrefValueChanged(key);
}
}
}
void DualLayerUserPrefStore::RemoveValue(const std::string& key,
uint32_t flags) {
// Only proceed if the pref exists.
if (!GetValue(key, nullptr)) {
return;
}
{
base::AutoReset<bool> setting_prefs(&is_setting_prefs_, true);
local_pref_store_->RemoveValue(key, flags);
if (ShouldSetValueInAccountStore(key)) {
account_pref_store_->RemoveValue(key, flags);
}
}
// Remove from the list of merge prefs if exists.
merged_prefs_.RemoveValue(key);
for (PrefStore::Observer& observer : observers_) {
observer.OnPrefValueChanged(key);
}
}
bool DualLayerUserPrefStore::GetMutableValue(const std::string& key,
base::Value** result) {
if (!ShouldGetValueFromAccountStore(key)) {
return local_pref_store_->GetMutableValue(key, result);
}
base::Value* local_value = nullptr;
local_pref_store_->GetMutableValue(key, &local_value);
base::Value* account_value = nullptr;
account_pref_store_->GetMutableValue(key, &account_value);
if (!account_value && !local_value) {
// Pref doesn't exist.
return false;
}
if (result) {
// Note: Any changes to the returned Value will only directly take effect
// in the underlying store. However, callers of this method are required to
// call ReportValueChanged() once they're done modifying it, and that
// propagates the change to all the underlying stores.
// If pref exists it both stores, create a merged pref.
if (account_value && local_value) {
*result = MaybeMerge(key, *local_value, *account_value);
CHECK(*result);
} else if (account_value) {
*result = account_value;
} else {
*result = local_value;
}
}
return true;
}
void DualLayerUserPrefStore::ReportValueChanged(const std::string& key,
uint32_t flags) {
{
base::AutoReset<bool> setting_prefs(&is_setting_prefs_, true);
if (ShouldSetValueInAccountStore(key)) {
const base::Value* new_value = nullptr;
// In case a merged value was updated, it would exist in `merged_prefs_`.
// Else, get the new value from whichever store has it and copy it to the
// other one.
if (merged_prefs_.GetValue(key, &new_value)) {
auto [new_local_value, new_account_value] =
UnmergeValue(key, new_value->Clone(), flags);
account_pref_store_->SetValueSilently(key, std::move(new_account_value),
flags);
local_pref_store_->SetValueSilently(key, std::move(new_local_value),
flags);
} else if (account_pref_store_->GetValue(key, &new_value)) {
local_pref_store_->SetValueSilently(key, new_value->Clone(), flags);
} else if (local_pref_store_->GetValue(key, &new_value)) {
account_pref_store_->SetValueSilently(key, new_value->Clone(), flags);
}
// It is possible that the pref just doesn't exist (anymore).
}
// Forward the ReportValueChanged() call to the underlying stores, so they
// can notify their own observers.
local_pref_store_->ReportValueChanged(key, flags);
if (ShouldSetValueInAccountStore(key)) {
account_pref_store_->ReportValueChanged(key, flags);
}
}
for (PrefStore::Observer& observer : observers_) {
observer.OnPrefValueChanged(key);
}
}
void DualLayerUserPrefStore::SetValueSilently(const std::string& key,
base::Value value,
uint32_t flags) {
if (ShouldSetValueInAccountStore(key)) {
if (IsPrefKeyMergeable(key)) {
auto [new_local_value, new_account_value] =
UnmergeValue(key, std::move(value), flags);
account_pref_store_->SetValueSilently(key, std::move(new_account_value),
flags);
local_pref_store_->SetValueSilently(key, std::move(new_local_value),
flags);
} else {
account_pref_store_->SetValueSilently(key, value.Clone(), flags);
local_pref_store_->SetValueSilently(key, std::move(value), flags);
}
} else {
local_pref_store_->SetValueSilently(key, std::move(value), flags);
}
}
void DualLayerUserPrefStore::RemoveValuesByPrefixSilently(
const std::string& prefix) {
local_pref_store_->RemoveValuesByPrefixSilently(prefix);
// RemoveValuesByPrefixSilently() is not used for the account store since it
// will remove values which are not being synced yet(for e.g. prefs behind
// history opt-in). Instead, each pref in the account store is checked and
// removed if it is writeable right now.
{
base::AutoReset<bool> setting_prefs(&is_setting_prefs_, true);
// Clear all synced preferences with the prefix from the account store.
for (const std::string& pref_name : GetPrefNamesInAccountStore()) {
if (base::StartsWith(pref_name, prefix) &&
ShouldSetValueInAccountStore(pref_name)) {
account_pref_store_->RemoveValue(
pref_name, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
}
}
}
// Remove from the list of merged prefs if exists.
merged_prefs_.ClearWithPrefix(prefix);
}
bool DualLayerUserPrefStore::ReadOnly() const {
return local_pref_store_->ReadOnly() || account_pref_store_->ReadOnly();
}
PersistentPrefStore::PrefReadError DualLayerUserPrefStore::GetReadError()
const {
if (auto local_prefs_read_error = local_pref_store_->GetReadError();
local_prefs_read_error != PersistentPrefStore::PREF_READ_ERROR_NONE) {
return local_prefs_read_error;
}
return account_pref_store_->GetReadError();
}
PersistentPrefStore::PrefReadError DualLayerUserPrefStore::ReadPrefs() {
// Call ReadPrefs() on both stores before reporting error.
auto local_prefs_read_error = local_pref_store_->ReadPrefs();
auto account_prefs_read_error = account_pref_store_->ReadPrefs();
if (local_prefs_read_error != PersistentPrefStore::PREF_READ_ERROR_NONE) {
return local_prefs_read_error;
}
return account_prefs_read_error;
}
void DualLayerUserPrefStore::ReadPrefsAsync(ReadErrorDelegate* error_delegate) {
// The store is expected to take ownership of `error_delegate`, thus it's not
// valid to forward the same to the two underlying stores. Instead, if any
// error occurs, it's reported in OnInitializationCompleted() handle.
read_error_delegate_.reset(error_delegate);
local_pref_store_->ReadPrefsAsync(nullptr);
account_pref_store_->ReadPrefsAsync(nullptr);
}
void DualLayerUserPrefStore::CommitPendingWrite(
base::OnceClosure reply_callback,
base::OnceClosure synchronous_done_callback) {
// A BarrierClosure will run its callback wherever the last instance of the
// returned wrapper is invoked. As such it is guaranteed to respect the reply
// vs synchronous semantics assuming `local_pref_store_` and
// `account_pref_store_` honor it.
static constexpr int kNumStores = 2;
base::RepeatingClosure reply_callback_wrapper =
reply_callback
? base::BarrierClosure(kNumStores, std::move(reply_callback))
: base::RepeatingClosure();
base::RepeatingClosure synchronous_callback_wrapper =
synchronous_done_callback
? base::BarrierClosure(kNumStores,
std::move(synchronous_done_callback))
: base::RepeatingClosure();
local_pref_store_->CommitPendingWrite(reply_callback_wrapper,
synchronous_callback_wrapper);
account_pref_store_->CommitPendingWrite(reply_callback_wrapper,
synchronous_callback_wrapper);
}
void DualLayerUserPrefStore::SchedulePendingLossyWrites() {
local_pref_store_->SchedulePendingLossyWrites();
account_pref_store_->SchedulePendingLossyWrites();
}
void DualLayerUserPrefStore::OnStoreDeletionFromDisk() {
local_pref_store_->OnStoreDeletionFromDisk();
account_pref_store_->OnStoreDeletionFromDisk();
}
bool DualLayerUserPrefStore::ShouldSetValueInAccountStore(
const std::string& key) const {
// A preference `key` is added to account store only if it is syncable, the
// corresponding pref type is active, and falls under the current user
// consent, i.e. "privacy-sensitive" prefs require history opt-in.
// Never write to the account store if it's not read from the account store.
if (!ShouldGetValueFromAccountStore(key)) {
return false;
}
auto metadata = pref_model_associator_client_->GetSyncablePrefsDatabase()
.GetSyncablePrefMetadata(key);
// Checks if the pref type is active.
if (!active_types_.count(metadata->model_type()) &&
// Checks if the pref already exists in the account store.
// This is to handle cases where a pref might pre-exist before sync is
// initialized and the type is marked as active.
!account_pref_store_->GetValue(key, nullptr)) {
return false;
}
return true;
}
bool DualLayerUserPrefStore::ShouldGetValueFromAccountStore(
const std::string& key) const {
// A preference `key` is queried from account store only if it is syncable and
// falls under the current user consent, i.e. "privacy-sensitive" prefs
// require history opt-in.
// Note: There is no check if the pref type is active because they are
// determined only after the Sync machinery is initialized, but account-store
// values need to be applied even before that.
if (!pref_model_associator_client_) {
// Safer this way.
return false;
}
auto metadata = pref_model_associator_client_->GetSyncablePrefsDatabase()
.GetSyncablePrefMetadata(key);
// Checks if the pref is a syncable pref.
if (!metadata.has_value()) {
return false;
}
// Checks if the pref requires a history opt-in.
if (metadata->is_history_opt_in_required() && !IsHistorySyncEnabled()) {
return false;
}
return true;
}
void DualLayerUserPrefStore::EnableType(syncer::ModelType model_type) {
CHECK(model_type == syncer::PREFERENCES ||
model_type == syncer::PRIORITY_PREFERENCES
#if BUILDFLAG(IS_CHROMEOS)
|| model_type == syncer::OS_PREFERENCES ||
model_type == syncer::OS_PRIORITY_PREFERENCES
#endif
);
active_types_.insert(model_type);
}
void DualLayerUserPrefStore::DisableTypeAndClearAccountStore(
syncer::ModelType model_type) {
CHECK(model_type == syncer::PREFERENCES ||
model_type == syncer::PRIORITY_PREFERENCES
#if BUILDFLAG(IS_CHROMEOS)
|| model_type == syncer::OS_PREFERENCES ||
model_type == syncer::OS_PRIORITY_PREFERENCES
#endif
);
active_types_.erase(model_type);
if (!pref_model_associator_client_) {
// No pref is treated as syncable in this case. No need to clear the account
// store.
return;
}
// Clear all synced preferences from the account store.
for (const std::string& pref_name : GetPrefNamesInAccountStore()) {
std::optional<SyncablePrefMetadata> metadata =
pref_model_associator_client_->GetSyncablePrefsDatabase()
.GetSyncablePrefMetadata(pref_name);
CHECK(metadata.has_value());
if (metadata->model_type() != model_type) {
continue;
}
const base::Value* value = nullptr;
// Should only notify observers if the effective value changes.
// Note: A notification is still sent if a pref goes from an
// explicitly-set value to an equal default value.
// Note: If the pref requires history opt-in, but history sync is
// disabled, GetValue() will not return the account value, and in case
// no value for the pref exists in the local store, no notification should
// be sent out.
bool should_notify = GetValue(pref_name, &value);
if (const base::Value* local_value = nullptr;
value && local_pref_store_->GetValue(pref_name, &local_value)) {
should_notify = (*local_value != *value);
}
{
base::AutoReset<bool> setting_prefs(&is_setting_prefs_, true);
// The write flags only affect persistence, and the default flag is the
// safer choice.
account_pref_store_->RemoveValue(
pref_name, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
merged_prefs_.RemoveValue(pref_name);
}
if (should_notify) {
for (PrefStore::Observer& observer : observers_) {
observer.OnPrefValueChanged(pref_name);
}
}
}
if (active_types_.empty()) {
// Clear the account store of any garbage value without notifications. This
// can happen if a previously syncable pref was persisted to the account
// store but is no longer syncable.
// TODO(crbug.com/1466439): Look into if the garbage values can cleared on
// browser startup.
// Since there's no direct way to clear the pref store or get a list of all
// keys (because of the dotted paths) and `RemoveValuesByPrefixSilently("")`
// is disallowed, the following workaround is used to clear the store.
for (auto [key, value] : account_pref_store_->GetValues()) {
account_pref_store_->RemoveValuesByPrefixSilently(key);
}
}
}
bool DualLayerUserPrefStore::IsPrefKeyMergeable(const std::string& key) const {
if (!pref_model_associator_client_) {
return false;
}
const auto& syncable_prefs_database =
pref_model_associator_client_->GetSyncablePrefsDatabase();
return syncable_prefs_database.IsPreferenceSyncable(key) &&
syncable_prefs_database.IsPreferenceMergeable(key);
}
const base::Value* DualLayerUserPrefStore::MaybeMerge(
const std::string& pref_name,
const base::Value& local_value,
const base::Value& account_value) const {
// Return the account value if `pref_name` is not mergeable.
if (!IsPrefKeyMergeable(pref_name)) {
return &account_value;
}
// Note: The merged value is evaluated every time and not re-used from
// `merged_prefs_`. This is to:
// 1. Handle the cases where SetValueSilently() or
// RemoveValueByPrefixSilently() is called on the underlying stores directly,
// without a corresponding call to ReportValueChanged().
// 2. Avoid removing the entry from `merged_prefs_` every time pref is
// updated.
base::Value merged_value =
helper::MergePreference(pref_model_associator_client_.get(), pref_name,
local_value, account_value);
// Add to `merged_prefs_` only if value doesn't already exist. This is done
// because the previously returned value might be in use and replacing the
// value would be risky - multiple successive calls to the getter shouldn't
// invalidate previous results.
if (base::Value* original_value = nullptr;
!merged_prefs_.GetValue(pref_name, &original_value) ||
*original_value != merged_value) {
merged_prefs_.SetValue(pref_name, std::move(merged_value));
}
const base::Value* merged_pref = nullptr;
merged_prefs_.GetValue(pref_name, &merged_pref);
DCHECK(merged_pref);
return merged_pref;
}
base::Value* DualLayerUserPrefStore::MaybeMerge(const std::string& pref_name,
base::Value& local_value,
base::Value& account_value) {
// Doing const_cast should be safe as ultimately the value being pointed to is
// a non-const object.
return const_cast<base::Value*>(
std::as_const(*this).MaybeMerge(pref_name, local_value, account_value));
}
std::pair<base::Value, base::Value> DualLayerUserPrefStore::UnmergeValue(
const std::string& pref_name,
base::Value value,
uint32_t flags) const {
CHECK(ShouldSetValueInAccountStore(pref_name));
// Note: There is no "standard" unmerging logic for list or scalar prefs.
// TODO(crbug.com/1416479): Allow support for custom unmerge logic.
if (pref_model_associator_client_->GetSyncablePrefsDatabase()
.GetSyncablePrefMetadata(pref_name)
->merge_behavior() == MergeBehavior::kMergeableDict) {
// Per crbug.com/1430854, it is possible for the value to not be of dict
// type. However, in this case, whatever is the type of `value` it's bound
// to be correct, as UnmergeValue() is called by setters which in turn are
// only called after a type check.
if (value.is_dict()) {
base::Value::Dict local_dict;
if (const base::Value* local_dict_value = nullptr;
local_pref_store_->GetValue(pref_name, &local_dict_value)) {
// It is assumed that the local store cannot contain value of incorrect
// type.
local_dict = local_dict_value->GetDict().Clone();
}
base::Value::Dict account_dict;
if (const base::Value* account_dict_value = nullptr;
account_pref_store_->GetValue(pref_name, &account_dict_value)) {
// It is assumed that the account store cannot contain value of
// incorrect type.
account_dict = account_dict_value->GetDict().Clone();
}
auto [new_local_dict, new_account_dict] = helper::UnmergeDictionaryValues(
std::move(value).TakeDict(), local_dict, account_dict);
// Note: This would still return an empty dict even if the pref didn't
// exist in either of the stores. This should however be okay since no
// actual pref value is leaked to the other.
return {base::Value(std::move(new_local_dict)),
base::Value(std::move(new_account_dict))};
} else {
DLOG(ERROR) << pref_name
<< " marked as a mergeable dict pref but is of type "
<< value.type();
}
}
// Directly pass the new value as both the local value and the account value
// for prefs with no specific merge logic.
base::Value new_account_value(value.Clone());
base::Value new_local_value(std::move(value));
return {std::move(new_local_value), std::move(new_account_value)};
}
bool DualLayerUserPrefStore::IsInitializationSuccessful() const {
return local_pref_store_observer_.initialization_succeeded() &&
account_pref_store_observer_.initialization_succeeded();
}
std::vector<std::string> DualLayerUserPrefStore::GetPrefNamesInAccountStore()
const {
std::vector<std::string> keys;
if (!pref_model_associator_client_) {
return keys;
}
// GetValues() returns a dict which is set using SetByDottedPaths(). That
// means, a key "a.b.c" is presented as: `{'a': {'b': {'c': ... }}}`. This
// util recurses over the nested dicts with keys being joined with a dot, till
// the string forms a valid pref name, for eg. it will recurse with keys, "a",
// "a.b", and then "a.b.c" which was the original key.
auto recurse_and_insert = [&](const std::string& key,
const base::Value& value,
auto& recurse_and_insert_ref) -> void {
// Checks if `key` is a pref name using syncable pref database. This is
// different from ShouldSetValueInAccountStore() which checks whether or not
// a pref should synced right now based on enabled ModelTypes.
if (pref_model_associator_client_->GetSyncablePrefsDatabase()
.IsPreferenceSyncable(key)) {
keys.push_back(key);
} else if (value.is_dict()) {
for (auto [k, v] : value.GetDict()) {
recurse_and_insert_ref(key + "." + k, v, recurse_and_insert_ref);
}
}
};
for (auto [key, value] : account_pref_store_->GetValues()) {
recurse_and_insert(key, value, recurse_and_insert);
}
return keys;
}
base::flat_set<syncer::ModelType>
DualLayerUserPrefStore::GetActiveTypesForTest() const {
return active_types_;
}
bool DualLayerUserPrefStore::IsHistorySyncEnabled() const {
return is_history_sync_enabled_;
}
bool DualLayerUserPrefStore::IsHistorySyncEnabledForTest() const {
return IsHistorySyncEnabled();
}
void DualLayerUserPrefStore::SetIsHistorySyncEnabledForTest(
bool is_history_sync_enabled) {
is_history_sync_enabled_ = is_history_sync_enabled;
}
void DualLayerUserPrefStore::OnSyncServiceInitialized(
syncer::SyncService* sync_service) {
sync_service->AddObserver(this);
// `sync_service` init should be considered as a state change.
OnStateChanged(sync_service);
}
void DualLayerUserPrefStore::OnStateChanged(syncer::SyncService* sync_service) {
bool is_history_sync_enabled =
sync_service->GetUserSettings()->GetSelectedTypes().Has(
syncer::UserSelectableType::kHistory);
if (is_history_sync_enabled == is_history_sync_enabled_) {
return;
}
if (!pref_model_associator_client_) {
is_history_sync_enabled_ = is_history_sync_enabled;
return;
}
// Store the old values for sensitive prefs in a map and only inform the
// observers if the effective values change.
// Note: std::optional is used as the value type since it makes the
// comparison with the new values easier.
std::map<std::string, std::optional<base::Value>> old_values;
for (const std::string& pref_name : GetPrefNamesInAccountStore()) {
auto metadata = pref_model_associator_client_->GetSyncablePrefsDatabase()
.GetSyncablePrefMetadata(pref_name);
CHECK(metadata.has_value());
// Add effective value for sensitive prefs to `old_values`.
if (metadata->is_history_opt_in_required()) {
if (const base::Value* value = nullptr; GetValue(pref_name, &value)) {
old_values.emplace(pref_name, value->Clone());
} else {
// Put in std::nullopt to mark pref not existing in the store. This
// helps avoid an extra call to GetPrefNamesInAccount() later.
old_values.emplace(pref_name, std::nullopt);
}
}
}
is_history_sync_enabled_ = is_history_sync_enabled;
// The history sync state has changed. Check for any change in the effective
// values of any of the sensitive prefs as a consequence.
for (const auto& [pref_name, old_value] : old_values) {
std::optional<base::Value> new_value;
if (const base::Value* value = nullptr; GetValue(pref_name, &value)) {
new_value = value->Clone();
}
// Only notify the observers if the effective value is changing.
if (old_value != new_value) {
for (PrefStore::Observer& observer : observers_) {
observer.OnPrefValueChanged(pref_name);
}
}
}
}
void DualLayerUserPrefStore::OnSyncShutdown(syncer::SyncService* sync_service) {
// Pref service and hence the pref store outlives sync service.
sync_service->RemoveObserver(this);
}
void DualLayerUserPrefStore::SetValueInAccountStoreOnly(const std::string& key,
base::Value value,
uint32_t flags) {
const base::Value* initial_value = nullptr;
// Only notify if the effective value actually changes.
bool should_notify =
!GetValue(key, &initial_value) || (*initial_value != value);
{
base::AutoReset<bool> setting_prefs(&is_setting_prefs_, true);
account_pref_store_->SetValue(key, std::move(value), flags);
}
if (should_notify) {
for (PrefStore::Observer& observer : observers_) {
observer.OnPrefValueChanged(key);
}
}
}
} // namespace sync_preferences