blob: e468804e29ee8a09eba2def963f055c230ff166b [file] [log] [blame]
// 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 "base/bind.h"
#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 {
namespace {
const base::Value* LookupPath(const base::Value* root,
const std::vector<std::string>& path_components) {
const base::DictionaryValue* dictionary_value = nullptr;
bool success = root->GetAsDictionary(&dictionary_value);
DCHECK(success);
for (size_t i = 0; i < path_components.size() - 1; ++i) {
if (!dictionary_value->GetDictionaryWithoutPathExpansion(
path_components[i], &dictionary_value)) {
return nullptr;
}
}
const base::Value* result = nullptr;
dictionary_value->GetWithoutPathExpansion(path_components.back(), &result);
return result;
}
template <typename StringType>
bool IsPrefix(const std::vector<StringType>& prefix,
const std::vector<StringType>& full_path) {
if (prefix.size() >= full_path.size())
return false;
for (size_t i = 0; i < prefix.size(); i++) {
if (prefix[i] != full_path[i])
return false;
}
return true;
}
// Removes paths that where a prefix is also contained in |updated_paths|.
void RemoveRedundantPaths(std::set<std::vector<std::string>>* updated_paths) {
for (auto it = updated_paths->begin(), previous_it = updated_paths->end();
it != updated_paths->end();) {
if (previous_it != updated_paths->end() && IsPrefix(*previous_it, *it)) {
it = updated_paths->erase(it);
} else {
previous_it = it;
++it;
}
}
}
} // namespace
// A trie of writes that have been applied locally and sent to the service
// backend, but have not been acked.
class PersistentPrefStoreClient::InFlightWriteTrie {
public:
// Decision on what to do with writes incoming from the service.
enum class Decision {
// The write should be allowed.
kAllow,
// The write has already been superceded locally and should be ignored.
kIgnore,
// The write may have been partially superceded locally and should be
// ignored but an updated value is needed from the service.
kResolve
};
InFlightWriteTrie() = default;
void Add() {
std::vector<std::string> v;
Add(v.begin(), v.end());
}
template <typename It, typename Jt>
void Add(It path_start, Jt path_end) {
if (path_start == path_end) {
++writes_in_flight_;
return;
}
children_[*path_start].Add(path_start + 1, path_end);
}
bool Remove() {
std::vector<std::string> v;
return Remove(v.begin(), v.end());
}
template <typename It, typename Jt>
bool Remove(It path_start, Jt path_end) {
if (path_start == path_end) {
DCHECK_GT(writes_in_flight_, 0);
return --writes_in_flight_ == 0 && children_.empty();
}
auto it = children_.find(*path_start);
DCHECK(it != children_.end()) << *path_start;
auto removed = it->second.Remove(path_start + 1, path_end);
if (removed)
children_.erase(*path_start);
return children_.empty() && writes_in_flight_ == 0;
}
template <typename It, typename Jt>
Decision Lookup(It path_start, Jt path_end) {
if (path_start == path_end) {
if (children_.empty()) {
DCHECK_GT(writes_in_flight_, 0);
return Decision::kIgnore;
}
return Decision::kResolve;
}
if (writes_in_flight_ != 0) {
return Decision::kIgnore;
}
auto it = children_.find(*path_start);
if (it == children_.end()) {
return Decision::kAllow;
}
return it->second.Lookup(path_start + 1, path_end);
}
private:
std::map<std::string, InFlightWriteTrie> children_;
int writes_in_flight_ = 0;
DISALLOW_COPY_AND_ASSIGN(InFlightWriteTrie);
};
struct PersistentPrefStoreClient::InFlightWrite {
std::string key;
std::vector<std::vector<std::string>> sub_pref_paths;
};
PersistentPrefStoreClient::PersistentPrefStoreClient(
mojom::PersistentPrefStoreConnectionPtr connection)
: weak_factory_(this) {
read_error_ = connection->read_error;
read_only_ = connection->read_only;
pref_store_.Bind(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(base::DictionaryValue::From(base::Value::ToUniquePtrValue(
std::move(connection->pref_store_connection->initial_prefs))),
true, std::move(connection->pref_store_connection->observer));
} else {
Init(nullptr, false, nullptr);
}
}
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_);
ReportSubValuesChanged(
key, std::set<std::vector<std::string>>{std::vector<std::string>{}},
flags);
}
void PersistentPrefStoreClient::ReportSubValuesChanged(
const std::string& key,
std::set<std::vector<std::string>> path_components,
uint32_t flags) {
QueueWrite(key, std::move(path_components), flags);
ReportPrefValueChanged(key);
}
void PersistentPrefStoreClient::SetValueSilently(
const std::string& key,
std::unique_ptr<base::Value> value,
uint32_t flags) {
DCHECK(pref_store_);
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() {
return GetReadError();
}
void PersistentPrefStoreClient::ReadPrefsAsync(
ReadErrorDelegate* error_delegate) {}
void PersistentPrefStoreClient::CommitPendingWrite(
base::OnceClosure reply_callback,
base::OnceClosure synchronous_done_callback) {
// Supporting |synchronous_done_callback| semantics would require a sync IPC.
// This isn't implemented as such at the moment as this functionality isn't
// used in practice (if it ever becomes necessary, this check will fire).
DCHECK(!synchronous_done_callback);
DCHECK(pref_store_);
if (!pending_writes_.empty())
FlushPendingWrites();
pref_store_->CommitPendingWrite(std::move(reply_callback));
}
void PersistentPrefStoreClient::SchedulePendingLossyWrites() {
DCHECK(pref_store_);
return pref_store_->SchedulePendingLossyWrites();
}
void PersistentPrefStoreClient::ClearMutableValues() {
DCHECK(pref_store_);
return pref_store_->ClearMutableValues();
}
void PersistentPrefStoreClient::OnStoreDeletionFromDisk() {
DCHECK(pref_store_);
return pref_store_->OnStoreDeletionFromDisk();
}
PersistentPrefStoreClient::~PersistentPrefStoreClient() {
if (!pref_store_)
return;
CommitPendingWrite();
}
void PersistentPrefStoreClient::QueueWrite(
const std::string& key,
std::set<std::vector<std::string>> path_components,
uint32_t flags) {
DCHECK(!path_components.empty());
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::BindOnce(&PersistentPrefStoreClient::FlushPendingWrites,
weak_factory_.GetWeakPtr()));
}
RemoveRedundantPaths(&path_components);
auto& entry = pending_writes_[key];
entry.second = flags;
for (auto& path : path_components) {
entry.first.insert(std::move(path));
}
}
void PersistentPrefStoreClient::FlushPendingWrites() {
weak_factory_.InvalidateWeakPtrs();
if (pending_writes_.empty())
return;
std::vector<mojom::PrefUpdatePtr> updates;
std::vector<InFlightWrite> writes;
for (auto& pref : pending_writes_) {
auto update_value = mojom::PrefUpdateValue::New();
const base::Value* value = nullptr;
if (GetValue(pref.first, &value)) {
std::vector<mojom::SubPrefUpdatePtr> pref_updates;
std::vector<std::vector<std::string>> sub_pref_writes;
RemoveRedundantPaths(&pref.second.first);
for (const auto& path : pref.second.first) {
if (path.empty()) {
pref_updates.clear();
sub_pref_writes.clear();
break;
}
const base::Value* nested_value = LookupPath(value, path);
if (nested_value) {
pref_updates.emplace_back(base::in_place, path,
nested_value->Clone());
} else {
pref_updates.emplace_back(base::in_place, path, base::nullopt);
}
sub_pref_writes.push_back(path);
}
if (pref_updates.empty()) {
update_value->set_atomic_update(value->Clone());
writes.push_back({pref.first});
} else {
update_value->set_split_updates(std::move(pref_updates));
writes.push_back({pref.first, std::move(sub_pref_writes)});
}
} else {
update_value->set_atomic_update(base::nullopt);
writes.push_back({pref.first});
}
updates.emplace_back(base::in_place, pref.first, std::move(update_value),
pref.second.second);
}
pref_store_->SetValues(std::move(updates));
pending_writes_.clear();
for (const auto& write : writes) {
auto& trie = in_flight_writes_tries_[write.key];
if (write.sub_pref_paths.empty()) {
trie.Add();
} else {
for (const auto& subpref_update : write.sub_pref_paths) {
trie.Add(subpref_update.begin(), subpref_update.end());
}
}
}
in_flight_writes_queue_.push(std::move(writes));
}
void PersistentPrefStoreClient::OnPrefChangeAck() {
const auto& writes = in_flight_writes_queue_.front();
for (const auto& write : writes) {
auto it = in_flight_writes_tries_.find(write.key);
DCHECK(it != in_flight_writes_tries_.end()) << write.key;
bool remove = false;
if (write.sub_pref_paths.empty()) {
remove = it->second.Remove();
} else {
for (const auto& subpref_update : write.sub_pref_paths) {
remove =
it->second.Remove(subpref_update.begin(), subpref_update.end());
}
}
if (remove) {
in_flight_writes_tries_.erase(it);
}
}
in_flight_writes_queue_.pop();
}
bool PersistentPrefStoreClient::ShouldSkipWrite(
const std::string& key,
const std::vector<std::string>& path,
const base::Value* new_value) {
if (!pending_writes_.empty()) {
FlushPendingWrites();
}
auto it = in_flight_writes_tries_.find(key);
if (it == in_flight_writes_tries_.end()) {
return false;
}
auto decision = it->second.Lookup(path.begin(), path.end());
if (decision == InFlightWriteTrie::Decision::kAllow) {
return false;
}
if (decision == InFlightWriteTrie::Decision::kResolve)
pref_store_->RequestValue(key, path);
return true;
}
} // namespace prefs