[PM] Add a mechanism to emit freezing votes from the UI thread.

Bug: 1144025
Change-Id: I7371085cfca50eb94c55e637597fd8acdf3dae25
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2593767
Commit-Queue: François Doray <[email protected]>
Reviewed-by: François Doray <[email protected]>
Cr-Commit-Position: refs/heads/master@{#838088}
diff --git a/components/performance_manager/freezing/freezing.cc b/components/performance_manager/freezing/freezing.cc
new file mode 100644
index 0000000..90bb84f7
--- /dev/null
+++ b/components/performance_manager/freezing/freezing.cc
@@ -0,0 +1,141 @@
+// Copyright 2020 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 "components/performance_manager/public/freezing/freezing.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/sequence_checker.h"
+#include "base/sequenced_task_runner.h"
+#include "base/task/post_task.h"
+#include "components/performance_manager/freezing/freezing_vote_aggregator.h"
+#include "components/performance_manager/performance_manager_impl.h"
+#include "components/performance_manager/public/graph/page_node.h"
+#include "components/performance_manager/public/performance_manager.h"
+#include "content/public/browser/web_contents.h"
+
+namespace performance_manager {
+
+namespace freezing {
+
+namespace {
+
+// The counterpart of a FreezingVoteToken that lives on the PM sequence.
+class FreezingVoteTokenPMImpl : public PageNode::ObserverDefaultImpl {
+ public:
+  FreezingVoteTokenPMImpl(content::WebContents* content,
+                          FreezingVoteValue vote_value,
+                          const char* vote_reason);
+  ~FreezingVoteTokenPMImpl() override;
+  FreezingVoteTokenPMImpl(const FreezingVoteTokenPMImpl& other) = delete;
+  FreezingVoteTokenPMImpl& operator=(const FreezingVoteTokenPMImpl&) = delete;
+
+  // PageNodeObserver:
+  void OnBeforePageNodeRemoved(const PageNode* page_node) override;
+
+ private:
+  const PageNode* page_node_ = nullptr;
+  Graph* graph_ = nullptr;
+
+  // Voting channel wrapper. This objects should only be used on the PM
+  // sequence.
+  std::unique_ptr<FreezingVotingChannelWrapper> voter_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+// Concrete implementation of a FreezingVoteToken.
+class FreezingVoteTokenImpl : public FreezingVoteToken {
+ public:
+  FreezingVoteTokenImpl(content::WebContents* content,
+                        FreezingVoteValue vote_value,
+                        const char* vote_reason);
+  ~FreezingVoteTokenImpl() override;
+  FreezingVoteTokenImpl(const FreezingVoteTokenImpl& other) = delete;
+  FreezingVoteTokenImpl& operator=(const FreezingVoteTokenImpl&) = delete;
+
+ private:
+  // Voting channel wrapper. This objects should only be used on the PM
+  // sequence.
+  std::unique_ptr<FreezingVoteTokenPMImpl, base::OnTaskRunnerDeleter> pm_impl_;
+};
+
+}  // namespace
+
+FreezingVoteToken::FreezingVoteToken() = default;
+FreezingVoteToken::~FreezingVoteToken() = default;
+
+FreezingVoteTokenPMImpl::FreezingVoteTokenPMImpl(content::WebContents* content,
+                                                 FreezingVoteValue vote_value,
+                                                 const char* vote_reason) {
+  DETACH_FROM_SEQUENCE(sequence_checker_);
+  // Register the vote on the PM sequence.
+  PerformanceManager::CallOnGraph(
+      FROM_HERE,
+      base::BindOnce(
+          [](base::WeakPtr<PageNode> page_node, FreezingVoteValue vote_value,
+             const char* vote_reason, FreezingVoteTokenPMImpl* voter_pm_impl,
+             Graph* graph) {
+            voter_pm_impl->voter_ =
+                std::make_unique<FreezingVotingChannelWrapper>();
+            voter_pm_impl->graph_ = graph;
+            graph->AddPageNodeObserver(voter_pm_impl);
+            voter_pm_impl->voter_->SetVotingChannel(
+                graph->GetRegisteredObjectAs<FreezingVoteAggregator>()
+                    ->GetVotingChannel());
+            if (page_node) {
+              voter_pm_impl->voter_->SubmitVote(page_node.get(),
+                                                {vote_value, vote_reason});
+              voter_pm_impl->page_node_ = page_node.get();
+            }
+          },
+          PerformanceManager::GetPageNodeForWebContents(content), vote_value,
+          // It's safe to use Unretained because |vote_reason| is a static
+          // string.
+          base::Unretained(vote_reason),
+          // It's safe to use Unretained because |this| can only be deleted
+          // from a task running on the PM sequence after this callback.
+          base::Unretained(this)));
+}
+
+FreezingVoteTokenPMImpl::~FreezingVoteTokenPMImpl() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (graph_)
+    graph_->RemovePageNodeObserver(this);
+}
+
+void FreezingVoteTokenPMImpl::OnBeforePageNodeRemoved(
+    const PageNode* page_node) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  if (page_node == page_node_) {
+    // Invalidate the vote 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.
+    voter_->InvalidateVote(page_node);
+    page_node_ = nullptr;
+    graph_->RemovePageNodeObserver(this);
+    graph_ = nullptr;
+  }
+}
+
+FreezingVoteTokenImpl::FreezingVoteTokenImpl(content::WebContents* content,
+                                             FreezingVoteValue vote_value,
+                                             const char* vote_reason)
+    : pm_impl_(new FreezingVoteTokenPMImpl(content, vote_value, vote_reason),
+               base::OnTaskRunnerDeleter(PerformanceManager::GetTaskRunner())) {
+}
+
+FreezingVoteTokenImpl::~FreezingVoteTokenImpl() = default;
+
+std::unique_ptr<FreezingVoteToken> EmitFreezingVoteForWebContents(
+    content::WebContents* content,
+    FreezingVoteValue vote_value,
+    const char* vote_reason) {
+  return std::make_unique<FreezingVoteTokenImpl>(content, vote_value,
+                                                 vote_reason);
+}
+
+}  // namespace freezing
+}  // namespace performance_manager
\ No newline at end of file