blob: b3827dbff56ced34db1badd1f1d4869ac105787a [file] [log] [blame]
// Copyright 2020 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/performance_manager/public/freezing/freezing.h"
#include <stdint.h>
#include <memory>
#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "base/thread_annotations.h"
#include "base/types/id_type.h"
#include "components/performance_manager/freezing/freezing_vote_aggregator.h"
#include "components/performance_manager/graph/graph_impl.h"
#include "components/performance_manager/graph/node_attached_data_impl.h"
#include "components/performance_manager/graph/page_node_impl.h"
#include "components/performance_manager/performance_manager_impl.h"
#include "components/performance_manager/public/graph/graph.h"
#include "components/performance_manager/public/graph/graph_registered.h"
#include "components/performance_manager/public/graph/page_node.h"
#include "components/performance_manager/public/performance_manager.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
namespace performance_manager {
namespace freezing {
namespace {
// Wrapper for `FreezingVoteToken*` that provides type checking and avoids being
// dereferenced.
class FreezingVoteTokenID
: public base::IdType<class FreezingVoteTokenTag, std::uintptr_t, 0> {
public:
explicit FreezingVoteTokenID(const FreezingVoteToken* token)
: FreezingVoteTokenID(reinterpret_cast<uintptr_t>(token)) {}
private:
constexpr explicit FreezingVoteTokenID(uintptr_t value)
: base::IdType<FreezingVoteTokenTag, std::uintptr_t, 0>(value) {}
constexpr static FreezingVoteTokenID FromUnsafeValue(uintptr_t value) {
return FreezingVoteTokenID(value);
}
};
// NodeAttachedData used to store the set of FreezingVoteTokenImpl objects
// associated with a PageNode.
class FreezingVoteNodeData : public NodeAttachedDataImpl<FreezingVoteNodeData> {
public:
struct Traits : public NodeAttachedDataInMap<PageNodeImpl> {};
~FreezingVoteNodeData() override = default;
void AddVote(FreezingVoteTokenID token);
void RemoveVote(FreezingVoteTokenID token);
bool IsEmpty() { return vote_tokens_.empty(); }
const base::flat_set<FreezingVoteTokenID>& vote_tokens() {
return vote_tokens_;
}
private:
friend class ::performance_manager::NodeAttachedDataImpl<
FreezingVoteNodeData>;
explicit FreezingVoteNodeData(const PageNodeImpl* page_node) {}
// The freezing votes associated with this node.
base::flat_set<FreezingVoteTokenID> vote_tokens_;
};
// A registry of FreezingVoteToken that lives on the PM sequence.
//
// There can be multiple freezing votes associated with the same page node.
class FreezingVoteTokenPMRegistry
: public PageNode::ObserverDefaultImpl,
public GraphOwned,
public GraphRegisteredImpl<FreezingVoteTokenPMRegistry> {
public:
// A map that associates a voting token to a
// <FreezingVotingChannel, const PageNode*> pair.
using VotingChannelsMap =
base::flat_map<FreezingVoteTokenID,
std::pair<FreezingVotingChannel, const PageNode*>>;
// Returns the FreezingVoteTokenPMRegistry graph owned instance, creates it if
// necessary. Can only be called from the PM sequence.
static FreezingVoteTokenPMRegistry* GetOrCreateInstance(Graph* graph);
FreezingVoteTokenPMRegistry(const FreezingVoteTokenPMRegistry& other) =
delete;
FreezingVoteTokenPMRegistry& operator=(const FreezingVoteTokenPMRegistry&) =
delete;
// Register a freezing vote for |contents|. |token| is an ID to associate with
// this vote, there can be only one vote associated with this ID and it as to
// be passed |UnregisterVote| when the vote is invalidated. This can only be
// called from the UI thread.
static void RegisterVoteForWebContents(content::WebContents* contents,
FreezingVoteValue vote_value,
const char* vote_reason,
FreezingVoteTokenID token);
// Unregister the vote associated with |token|. This can only be called from
// the UI thread.
static void UnregisterVote(FreezingVoteTokenID token);
const VotingChannelsMap& voting_channels_for_testing() {
return voting_channels_;
}
private:
FreezingVoteTokenPMRegistry() = default;
// Register a vote for |page_node| on the PM sequence. |token| is an ID
// associated with this vote that will be used when invalidating it.
void RegisterVoteOnPMSequence(base::WeakPtr<PageNode> page_node,
FreezingVote vote,
FreezingVoteTokenID token);
// Unregister the vote associated with |token|.
void UnregisterVoteOnPMSequence(FreezingVoteTokenID token);
// PageNodeObserver:
void OnBeforePageNodeRemoved(const PageNode* page_node) override;
// GraphOwned:
void OnPassedToGraph(Graph* graph) override;
void OnTakenFromGraph(Graph* graph) override;
// Reset the voting channel associated with |voting_channel_iter| and remove
// this entry from |voting_channels_|. |voting_channel_iter| has to be a valid
// iterator from |voting_channels_|
void ResetAndRemoveVotingChannel(
VotingChannelsMap::iterator& voting_channel_iter,
const PageNode* page_node);
VotingChannelsMap voting_channels_ GUARDED_BY_CONTEXT(sequence_checker_);
raw_ptr<Graph> graph_ GUARDED_BY_CONTEXT(sequence_checker_);
SEQUENCE_CHECKER(sequence_checker_);
};
// Concrete implementation of a FreezingVoteToken.
class FreezingVoteTokenImpl : public FreezingVoteToken {
public:
FreezingVoteTokenImpl(content::WebContents* contents,
FreezingVoteValue vote_value,
const char* vote_reason);
~FreezingVoteTokenImpl() override;
FreezingVoteTokenImpl(const FreezingVoteTokenImpl& other) = delete;
FreezingVoteTokenImpl& operator=(const FreezingVoteTokenImpl&) = delete;
};
} // namespace
FreezingVoteToken::FreezingVoteToken() = default;
FreezingVoteToken::~FreezingVoteToken() = default;
void FreezingVoteNodeData::AddVote(FreezingVoteTokenID token) {
DCHECK(!base::Contains(vote_tokens_, token));
vote_tokens_.insert(token);
}
void FreezingVoteNodeData::RemoveVote(FreezingVoteTokenID token) {
DCHECK(base::Contains(vote_tokens_, token));
vote_tokens_.erase(token);
}
// static
FreezingVoteTokenPMRegistry* FreezingVoteTokenPMRegistry::GetOrCreateInstance(
Graph* graph) {
DCHECK(PerformanceManager::GetTaskRunner()->RunsTasksInCurrentSequence());
auto* instance = graph->GetRegisteredObjectAs<FreezingVoteTokenPMRegistry>();
if (!instance) {
auto registry = base::WrapUnique(new FreezingVoteTokenPMRegistry());
instance = registry.get();
graph->PassToGraph(std::move(registry));
}
return instance;
}
// static
void FreezingVoteTokenPMRegistry::RegisterVoteForWebContents(
content::WebContents* contents,
FreezingVoteValue vote_value,
const char* vote_reason,
FreezingVoteTokenID token) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Register the vote on the PM sequence.
PerformanceManager::CallOnGraph(
FROM_HERE,
base::BindOnce(
[](base::WeakPtr<PageNode> page_node, FreezingVote vote,
FreezingVoteTokenID token, Graph* graph) {
auto* registry =
FreezingVoteTokenPMRegistry::GetOrCreateInstance(graph);
registry->RegisterVoteOnPMSequence(page_node, vote, token);
},
PerformanceManager::GetPrimaryPageNodeForWebContents(contents),
FreezingVote(vote_value, vote_reason), token));
}
// static
void FreezingVoteTokenPMRegistry::UnregisterVote(FreezingVoteTokenID token) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Unregister the vote on the PM sequence.
PerformanceManager::CallOnGraph(
FROM_HERE,
base::BindOnce(
[](FreezingVoteTokenID token, Graph* graph) {
auto* registry =
FreezingVoteTokenPMRegistry::GetOrCreateInstance(graph);
registry->UnregisterVoteOnPMSequence(token);
},
token));
}
void FreezingVoteTokenPMRegistry::RegisterVoteOnPMSequence(
base::WeakPtr<PageNode> page_node,
FreezingVote vote,
FreezingVoteTokenID token) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(page_node);
DCHECK(!base::Contains(voting_channels_, token));
auto* node_data = FreezingVoteNodeData::GetOrCreate(
PageNodeImpl::FromNode(page_node.get()));
auto voting_channel = graph_->GetRegisteredObjectAs<FreezingVoteAggregator>()
->GetVotingChannel();
voting_channel.SubmitVote(page_node.get(), vote);
voting_channels_[token] =
std::make_pair(std::move(voting_channel), page_node.get());
node_data->AddVote(token);
}
void FreezingVoteTokenPMRegistry::UnregisterVoteOnPMSequence(
FreezingVoteTokenID token) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto voting_channel = voting_channels_.find(token);
// The vote might be missing from |voting_channels_| if this gets called
// after the corresponding PageNode has been destroyed.
if (voting_channel == voting_channels_.end()) {
return;
}
auto* page_node = voting_channel->second.second;
ResetAndRemoveVotingChannel(voting_channel, page_node);
auto* node_data =
FreezingVoteNodeData::Get(PageNodeImpl::FromNode(page_node));
DCHECK(node_data);
node_data->RemoveVote(token);
// Removes the node attached data if there's no more vote associated with this
// node.
if (node_data->IsEmpty())
FreezingVoteNodeData::Destroy(PageNodeImpl::FromNode(page_node));
}
void FreezingVoteTokenPMRegistry::OnBeforePageNodeRemoved(
const PageNode* page_node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto* node_data =
FreezingVoteNodeData::Get(PageNodeImpl::FromNode(page_node));
if (!node_data)
return;
// Invalidate the votes if its associated page node is destroyed. This can
// happen if a freezing vote token is released after the destruction of the
// WebContents it's associated with.
for (const auto token_iter : node_data->vote_tokens()) {
auto voting_channel = voting_channels_.find(token_iter);
DCHECK(voting_channel != voting_channels_.end());
ResetAndRemoveVotingChannel(voting_channel, page_node);
}
}
void FreezingVoteTokenPMRegistry::OnPassedToGraph(Graph* graph) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
graph->RegisterObject(this);
graph->AddPageNodeObserver(this);
graph_ = graph;
}
void FreezingVoteTokenPMRegistry::OnTakenFromGraph(Graph* graph) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
graph->UnregisterObject(this);
graph->RemovePageNodeObserver(this);
graph_ = nullptr;
}
void FreezingVoteTokenPMRegistry::ResetAndRemoveVotingChannel(
VotingChannelsMap::iterator& voting_channel_iter,
const PageNode* page_node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
voting_channel_iter->second.first.InvalidateVote(page_node);
voting_channel_iter->second.first.Reset();
voting_channels_.erase(voting_channel_iter);
}
FreezingVoteTokenImpl::FreezingVoteTokenImpl(content::WebContents* contents,
FreezingVoteValue vote_value,
const char* vote_reason) {
FreezingVoteTokenPMRegistry::RegisterVoteForWebContents(
contents, vote_value, vote_reason, FreezingVoteTokenID(this));
}
FreezingVoteTokenImpl::~FreezingVoteTokenImpl() {
FreezingVoteTokenPMRegistry::UnregisterVote(FreezingVoteTokenID(this));
}
std::unique_ptr<FreezingVoteToken> EmitFreezingVoteForWebContents(
content::WebContents* contents,
FreezingVoteValue vote_value,
const char* vote_reason) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return std::make_unique<FreezingVoteTokenImpl>(contents, vote_value,
vote_reason);
}
const char* FreezingVoteValueToString(FreezingVoteValue freezing_vote_value) {
if (freezing_vote_value == freezing::FreezingVoteValue::kCanFreeze) {
return "kCanFreeze";
} else {
return "kCannotFreeze";
}
}
size_t FreezingVoteCountForPageOnPMForTesting(PageNode* page_node) {
DCHECK(PerformanceManager::GetTaskRunner()->RunsTasksInCurrentSequence());
auto* node_data =
FreezingVoteNodeData::Get(PageNodeImpl::FromNode(page_node));
if (!node_data)
return 0;
return node_data->vote_tokens().size();
}
size_t TotalFreezingVoteCountOnPMForTesting(Graph* graph) {
DCHECK(PerformanceManager::GetTaskRunner()->RunsTasksInCurrentSequence());
auto* registry = FreezingVoteTokenPMRegistry::GetOrCreateInstance(graph);
size_t registry_size = registry->voting_channels_for_testing().size();
size_t page_nodes_vote_count = 0U;
for (const PageNode* page_node : graph->GetAllPageNodes()) {
auto* node_data =
FreezingVoteNodeData::Get(PageNodeImpl::FromNode(page_node));
if (node_data)
page_nodes_vote_count += node_data->vote_tokens().size();
}
DCHECK_EQ(registry_size, page_nodes_vote_count);
return registry_size;
}
} // namespace freezing
} // namespace performance_manager