blob: 01949d8d4eaeb938825306f893be15ec07f75585 [file] [log] [blame]
// Copyright 2018 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/invalidation/impl/invalidator_registrar_with_memory.h"
#include <cstddef>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/observer_list.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "components/invalidation/public/invalidation.h"
#include "components/invalidation/public/invalidation_util.h"
#include "components/invalidation/public/invalidator_state.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/scoped_user_pref_update.h"
namespace invalidation {
namespace {
constexpr char kTopicsToHandler[] = "invalidation.per_sender_topics_to_handler";
constexpr char kDeprecatedSyncInvalidationGCMSenderId[] = "8181035976";
constexpr char kHandler[] = "handler";
constexpr char kIsPublic[] = "is_public";
std::string DumpRegisteredHandlers(
const base::ObserverList<InvalidationHandler, true>& handlers) {
if (handlers.empty()) {
return "empty";
}
std::vector<std::string> handler_names;
for (const auto& handler : handlers) {
handler_names.emplace_back(handler.GetOwnerName());
}
return base::JoinString(handler_names, ",");
}
std::string DumpRegisteredHandlersToTopics(
const std::map<InvalidationHandler*, TopicMap, std::less<>>&
registered_handler_to_topics_map) {
if (registered_handler_to_topics_map.empty()) {
return "empty";
}
std::vector<std::string> handler_names;
for (const auto& [handler, topics] : registered_handler_to_topics_map) {
handler_names.emplace_back(handler->GetOwnerName());
}
return base::JoinString(handler_names, ",");
}
} // namespace
BASE_FEATURE(kRestoreInterestingTopicsFeature,
"InvalidatorRestoreInterestingTopics",
base::FEATURE_ENABLED_BY_DEFAULT);
// static
void InvalidatorRegistrarWithMemory::RegisterProfilePrefs(
PrefRegistrySimple* registry) {
registry->RegisterDictionaryPref(kTopicsToHandler);
}
// static
void InvalidatorRegistrarWithMemory::RegisterPrefs(
PrefRegistrySimple* registry) {
// For local state, we want to register exactly the same prefs as for profile
// prefs; see comment in the header.
RegisterProfilePrefs(registry);
}
// static
void InvalidatorRegistrarWithMemory::ClearDeprecatedPrefs(PrefService* prefs) {
if (prefs->HasPrefPath(kTopicsToHandler)) {
ScopedDictPrefUpdate update(prefs, kTopicsToHandler);
update->Remove(kDeprecatedSyncInvalidationGCMSenderId);
}
}
InvalidatorRegistrarWithMemory::InvalidatorRegistrarWithMemory(
PrefService* prefs,
const std::string& sender_id)
: state_(InvalidatorState::kDisabled),
prefs_(prefs),
sender_id_(sender_id) {
CHECK(!sender_id_.empty());
const base::Value::Dict* pref_data =
prefs_->GetDict(kTopicsToHandler).FindDict(sender_id_);
if (!pref_data) {
ScopedDictPrefUpdate update(prefs_, kTopicsToHandler);
update->Set(sender_id_, base::Value::Dict());
return;
}
// Restore |handler_name_to_subscribed_topics_map_| from prefs.
if (!base::FeatureList::IsEnabled(kRestoreInterestingTopicsFeature))
return;
for (auto it : *pref_data) {
const std::string& topic_name = it.first;
if (it.second.is_dict()) {
const base::Value::Dict& second_dict = it.second.GetDict();
const std::string* handler = second_dict.FindString(kHandler);
const std::optional<bool> is_public = second_dict.FindBool(kIsPublic);
if (!handler || !is_public) {
continue;
}
handler_name_to_subscribed_topics_map_[*handler][topic_name] =
TopicMetadata{.is_public = *is_public};
} else if (it.second.is_string()) {
handler_name_to_subscribed_topics_map_[it.second.GetString()]
[topic_name] = TopicMetadata{
.is_public = false};
}
}
}
InvalidatorRegistrarWithMemory::~InvalidatorRegistrarWithMemory() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!registered_handler_to_topics_map_.empty() || !handlers_.empty()) {
// TODO(crbug.com/40070281) figure out with these logs.
// Note: This can't be just a `CHECK(...) << ...` because `CHECK` eats the
// message in production builds.
LOG(ERROR) << "Registered handlers during destruction: "
<< DumpRegisteredHandlers(handlers_)
<< ". Handlers listening to topics: "
<< DumpRegisteredHandlersToTopics(
registered_handler_to_topics_map_)
<< ".";
NOTREACHED_NORETURN();
}
}
void InvalidatorRegistrarWithMemory::AddObserver(InvalidationHandler* handler) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(handler);
CHECK(!handlers_.HasObserver(handler));
handlers_.AddObserver(handler);
}
bool InvalidatorRegistrarWithMemory::HasObserver(
const InvalidationHandler* handler) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return handlers_.HasObserver(handler);
}
void InvalidatorRegistrarWithMemory::RemoveObserver(
const InvalidationHandler* handler) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(handler);
CHECK(handlers_.HasObserver(handler));
handlers_.RemoveObserver(handler);
if (const auto it = registered_handler_to_topics_map_.find(handler);
it != registered_handler_to_topics_map_.end()) {
registered_handler_to_topics_map_.erase(it);
}
// Note: Do *not* remove the entry from
// |handler_name_to_subscribed_topics_map_| - we haven't actually unsubscribed
// from any of the topics on the server, so GetAllSubscribedTopics() should
// still return the topics.
}
bool InvalidatorRegistrarWithMemory::UpdateRegisteredTopics(
InvalidationHandler* handler,
const TopicMap& topics) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(handler);
CHECK(handlers_.HasObserver(handler));
if (HasDuplicateTopicRegistration(handler, topics)) {
return false;
}
TopicMap old_topics = registered_handler_to_topics_map_[handler];
if (topics.empty()) {
registered_handler_to_topics_map_.erase(handler);
} else {
registered_handler_to_topics_map_[handler] = topics;
}
// This does *not* remove subscribed topics which are not registered. This
// behaviour is used by some handlers to keep topic subscriptions after
// browser startup even if they are not included in the first call of this
// method. It's useful to prevent unsubscribing from and subscribing to the
// topics on each browser startup.
//
// TODO(crbug.com/1051893): make the unsubscription behaviour consistent
// regardless of browser restart in between.
ScopedDictPrefUpdate update(prefs_, kTopicsToHandler);
base::Value::Dict* pref_data = update->FindDict(sender_id_);
CHECK(pref_data);
auto& topic_map =
handler_name_to_subscribed_topics_map_[handler->GetOwnerName()];
for (const auto& [topic, meta_data] : old_topics) {
pref_data->Remove(topic);
topic_map.erase(topic);
}
for (const auto& [topic, meta_data] : topics) {
topic_map[topic] = meta_data;
pref_data->Set(topic, base::Value::Dict()
.Set(kHandler, handler->GetOwnerName())
.Set(kIsPublic, meta_data.is_public));
}
return true;
}
TopicMap InvalidatorRegistrarWithMemory::GetRegisteredTopics(
InvalidationHandler* handler) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto lookup = registered_handler_to_topics_map_.find(handler);
return lookup != registered_handler_to_topics_map_.end() ? lookup->second
: TopicMap();
}
TopicMap InvalidatorRegistrarWithMemory::GetAllSubscribedTopics() const {
TopicMap subscribed_topics;
for (const auto& handler_to_topic : handler_name_to_subscribed_topics_map_) {
subscribed_topics.insert(handler_to_topic.second.begin(),
handler_to_topic.second.end());
}
return subscribed_topics;
}
void InvalidatorRegistrarWithMemory::DispatchInvalidationToHandlers(
const Invalidation& invalidation) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// If we have no handlers, there's nothing to do.
if (handlers_.empty()) {
return;
}
// Each handler has a set of registered topics. In order to send the incoming
// invalidation to the correct handlers we are going through each handler and
// each of their sets of topics.
for (const auto& [handler, registered_topics] :
registered_handler_to_topics_map_) {
for (const auto& [registered_topic, meta_data] : registered_topics) {
// If the topic of the invalidation matches a registered topic, we send
// the invalidation to the respective handler.
if (invalidation.topic() != registered_topic) {
continue;
}
handler->OnIncomingInvalidation(invalidation);
}
}
}
void InvalidatorRegistrarWithMemory::DispatchSuccessfullySubscribedToHandlers(
const Topic& topic) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// If we have no handlers, there's nothing to do.
if (handlers_.empty()) {
return;
}
for (const auto& [handler, registered_topics] :
registered_handler_to_topics_map_) {
for (const auto& [registered_topic, meta_data] : registered_topics) {
if (topic != registered_topic) {
continue;
}
handler->OnSuccessfullySubscribed(topic);
}
}
}
void InvalidatorRegistrarWithMemory::UpdateInvalidatorState(
InvalidatorState state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(1) << "New invalidator state: " << InvalidatorStateToString(state_)
<< " -> " << InvalidatorStateToString(state);
state_ = state;
for (auto& observer : handlers_)
observer.OnInvalidatorStateChange(state);
}
InvalidatorState InvalidatorRegistrarWithMemory::GetInvalidatorState() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return state_;
}
bool InvalidatorRegistrarWithMemory::HasDuplicateTopicRegistration(
InvalidationHandler* handler,
const TopicMap& new_topics) const {
for (const auto& [registered_handler, handler_topics] :
registered_handler_to_topics_map_) {
if (registered_handler == handler) {
continue;
}
for (const auto& [new_topic, meta_data] : new_topics) {
if (handler_topics.contains(new_topic)) {
DVLOG(1) << "Duplicate registration: trying to register " << new_topic
<< " for " << handler->GetOwnerName()
<< " when it's already registered for "
<< registered_handler->GetOwnerName();
return true;
}
}
}
return false;
}
} // namespace invalidation