summaryrefslogtreecommitdiff
path: root/chromium/components/performance_manager
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/performance_manager')
-rw-r--r--chromium/components/performance_manager/BUILD.gn13
-rw-r--r--chromium/components/performance_manager/DEPS1
-rw-r--r--chromium/components/performance_manager/README.md14
-rw-r--r--chromium/components/performance_manager/decorators/site_data_recorder.cc345
-rw-r--r--chromium/components/performance_manager/decorators/site_data_recorder.h72
-rw-r--r--chromium/components/performance_manager/decorators/site_data_recorder_unittest.cc393
-rw-r--r--chromium/components/performance_manager/decorators/v8_per_frame_memory_decorator.cc483
-rw-r--r--chromium/components/performance_manager/decorators/v8_per_frame_memory_decorator_unittest.cc970
-rw-r--r--chromium/components/performance_manager/embedder/performance_manager_registry.h13
-rw-r--r--chromium/components/performance_manager/features.cc4
-rw-r--r--chromium/components/performance_manager/graph/frame_node_impl.cc121
-rw-r--r--chromium/components/performance_manager/graph/frame_node_impl.h60
-rw-r--r--chromium/components/performance_manager/graph/frame_node_impl_describer.cc3
-rw-r--r--chromium/components/performance_manager/graph/frame_node_impl_unittest.cc218
-rw-r--r--chromium/components/performance_manager/graph/graph_impl.cc44
-rw-r--r--chromium/components/performance_manager/graph/graph_impl.h32
-rw-r--r--chromium/components/performance_manager/graph/graph_impl_unittest.cc20
-rw-r--r--chromium/components/performance_manager/graph/graph_registered_unittest.cc55
-rw-r--r--chromium/components/performance_manager/graph/node_attached_data.h2
-rw-r--r--chromium/components/performance_manager/graph/node_attached_data_impl.h2
-rw-r--r--chromium/components/performance_manager/graph/node_base.cc4
-rw-r--r--chromium/components/performance_manager/graph/node_base.h7
-rw-r--r--chromium/components/performance_manager/graph/page_node.cc22
-rw-r--r--chromium/components/performance_manager/graph/page_node_impl.cc65
-rw-r--r--chromium/components/performance_manager/graph/page_node_impl.h22
-rw-r--r--chromium/components/performance_manager/graph/page_node_impl_describer.cc4
-rw-r--r--chromium/components/performance_manager/graph/page_node_impl_unittest.cc4
-rw-r--r--chromium/components/performance_manager/graph/policies/process_priority_policy.cc5
-rw-r--r--chromium/components/performance_manager/graph/policies/tab_loading_frame_navigation_policy.cc76
-rw-r--r--chromium/components/performance_manager/graph/policies/tab_loading_frame_navigation_policy_unittest.cc179
-rw-r--r--chromium/components/performance_manager/mechanisms/tab_loading_frame_navigation_scheduler.cc17
-rw-r--r--chromium/components/performance_manager/mechanisms/tab_loading_frame_navigation_scheduler_browsertest.cc118
-rw-r--r--chromium/components/performance_manager/owned_objects.h108
-rw-r--r--chromium/components/performance_manager/owned_objects_unittest.cc73
-rw-r--r--chromium/components/performance_manager/performance_manager.cc41
-rw-r--r--chromium/components/performance_manager/performance_manager_browsertest.cc37
-rw-r--r--chromium/components/performance_manager/performance_manager_impl.cc6
-rw-r--r--chromium/components/performance_manager/performance_manager_impl.h3
-rw-r--r--chromium/components/performance_manager/performance_manager_impl_unittest.cc21
-rw-r--r--chromium/components/performance_manager/performance_manager_registry.cc10
-rw-r--r--chromium/components/performance_manager/performance_manager_registry_impl.cc77
-rw-r--r--chromium/components/performance_manager/performance_manager_registry_impl.h29
-rw-r--r--chromium/components/performance_manager/performance_manager_registry_impl_unittest.cc128
-rw-r--r--chromium/components/performance_manager/performance_manager_tab_helper.cc185
-rw-r--r--chromium/components/performance_manager/performance_manager_tab_helper.h15
-rw-r--r--chromium/components/performance_manager/performance_manager_tab_helper_unittest.cc91
-rw-r--r--chromium/components/performance_manager/performance_manager_unittest.cc6
-rw-r--r--chromium/components/performance_manager/persistence/site_data/non_recording_site_data_cache.cc6
-rw-r--r--chromium/components/performance_manager/persistence/site_data/non_recording_site_data_cache.h3
-rw-r--r--chromium/components/performance_manager/persistence/site_data/site_data_cache.h5
-rw-r--r--chromium/components/performance_manager/persistence/site_data/site_data_cache_factory.cc80
-rw-r--r--chromium/components/performance_manager/persistence/site_data/site_data_cache_factory.h60
-rw-r--r--chromium/components/performance_manager/persistence/site_data/site_data_cache_factory_unittest.cc54
-rw-r--r--chromium/components/performance_manager/persistence/site_data/site_data_cache_impl.cc7
-rw-r--r--chromium/components/performance_manager/persistence/site_data/site_data_cache_impl.h3
-rw-r--r--chromium/components/performance_manager/persistence/site_data/site_data_impl.h2
-rw-r--r--chromium/components/performance_manager/persistence/site_data/site_data_writer.h1
-rw-r--r--chromium/components/performance_manager/public/decorators/page_load_tracker_decorator_helper.h2
-rw-r--r--chromium/components/performance_manager/public/decorators/v8_per_frame_memory_decorator.h198
-rw-r--r--chromium/components/performance_manager/public/features.h5
-rw-r--r--chromium/components/performance_manager/public/graph/frame_node.h30
-rw-r--r--chromium/components/performance_manager/public/graph/graph.h4
-rw-r--r--chromium/components/performance_manager/public/graph/node_attached_data.h2
-rw-r--r--chromium/components/performance_manager/public/graph/page_node.h42
-rw-r--r--chromium/components/performance_manager/public/graph/policies/tab_loading_frame_navigation_policy.h29
-rw-r--r--chromium/components/performance_manager/public/mechanisms/tab_loading_frame_navigation_scheduler.h1
-rw-r--r--chromium/components/performance_manager/public/performance_manager.h67
-rw-r--r--chromium/components/performance_manager/public/performance_manager_main_thread_observer.h18
-rw-r--r--chromium/components/performance_manager/public/performance_manager_owned.h51
-rw-r--r--chromium/components/performance_manager/public/performance_manager_registered.h83
-rw-r--r--chromium/components/performance_manager/public/web_contents_proxy.h6
-rw-r--r--chromium/components/performance_manager/registered_objects.h87
-rw-r--r--chromium/components/performance_manager/registered_objects_unittest.cc94
-rw-r--r--chromium/components/performance_manager/render_process_host_proxy_browsertest.cc13
-rw-r--r--chromium/components/performance_manager/service_worker_context_adapter.cc15
-rw-r--r--chromium/components/performance_manager/service_worker_context_adapter.h6
-rw-r--r--chromium/components/performance_manager/test_support/graph_test_harness.h21
-rw-r--r--chromium/components/performance_manager/test_support/performance_manager_browsertest_harness.cc15
-rw-r--r--chromium/components/performance_manager/test_support/performance_manager_browsertest_harness.h2
-rw-r--r--chromium/components/performance_manager/test_support/performance_manager_test_harness.cc8
-rw-r--r--chromium/components/performance_manager/test_support/performance_manager_test_harness.h19
-rw-r--r--chromium/components/performance_manager/test_support/test_harness_helper.h31
-rw-r--r--chromium/components/performance_manager/web_contents_proxy.cc7
-rw-r--r--chromium/components/performance_manager/web_contents_proxy_impl.h6
-rw-r--r--chromium/components/performance_manager/web_contents_proxy_unittest.cc13
-rw-r--r--chromium/components/performance_manager/worker_watcher.cc140
-rw-r--r--chromium/components/performance_manager/worker_watcher.h22
-rw-r--r--chromium/components/performance_manager/worker_watcher_unittest.cc84
88 files changed, 5063 insertions, 497 deletions
diff --git a/chromium/components/performance_manager/BUILD.gn b/chromium/components/performance_manager/BUILD.gn
index a22123b01e3..24846b4e4aa 100644
--- a/chromium/components/performance_manager/BUILD.gn
+++ b/chromium/components/performance_manager/BUILD.gn
@@ -15,6 +15,7 @@ static_library("performance_manager") {
"decorators/page_load_tracker_decorator.h",
"decorators/page_load_tracker_decorator_helper.cc",
"decorators/tab_properties_decorator.cc",
+ "decorators/v8_per_frame_memory_decorator.cc",
"embedder/binders.cc",
"embedder/binders.h",
"embedder/performance_manager_lifetime.h",
@@ -67,6 +68,7 @@ static_library("performance_manager") {
"graph/worker_node_impl_describer.cc",
"graph/worker_node_impl_describer.h",
"mechanisms/tab_loading_frame_navigation_scheduler.cc",
+ "owned_objects.h",
"performance_manager.cc",
"performance_manager_feature_observer_client.cc",
"performance_manager_feature_observer_client.h",
@@ -83,6 +85,7 @@ static_library("performance_manager") {
"public/decorators/page_live_state_decorator.h",
"public/decorators/page_load_tracker_decorator_helper.h",
"public/decorators/tab_properties_decorator.h",
+ "public/decorators/v8_per_frame_memory_decorator.h",
"public/features.h",
"public/frame_priority/boosting_vote_aggregator.h",
"public/frame_priority/frame_priority.h",
@@ -108,9 +111,12 @@ static_library("performance_manager") {
"public/performance_manager.h",
"public/performance_manager_main_thread_mechanism.h",
"public/performance_manager_main_thread_observer.h",
+ "public/performance_manager_owned.h",
+ "public/performance_manager_registered.h",
"public/render_frame_host_proxy.h",
"public/render_process_host_proxy.h",
"public/web_contents_proxy.h",
+ "registered_objects.h",
"render_frame_host_proxy.cc",
"render_process_host_proxy.cc",
"render_process_user_data.cc",
@@ -125,6 +131,7 @@ static_library("performance_manager") {
"worker_watcher.cc",
"worker_watcher.h",
]
+
public_deps = [
"//base",
"//base/allocator:buildflags",
@@ -136,6 +143,8 @@ static_library("performance_manager") {
if (!is_android) {
sources += [
+ "decorators/site_data_recorder.cc",
+ "decorators/site_data_recorder.h",
"persistence/site_data/exponential_moving_average.cc",
"persistence/site_data/exponential_moving_average.h",
"persistence/site_data/feature_usage.h",
@@ -176,6 +185,7 @@ source_set("unit_tests") {
"decorators/page_live_state_decorator_unittest.cc",
"decorators/page_load_tracker_decorator_unittest.cc",
"decorators/tab_properties_decorator_unittest.cc",
+ "decorators/v8_per_frame_memory_decorator_unittest.cc",
"frame_priority/boosting_vote_aggregator_unittest.cc",
"frame_priority/frame_priority_unittest.cc",
"frame_priority/max_vote_aggregator_unittest.cc",
@@ -194,10 +204,12 @@ source_set("unit_tests") {
"graph/properties_unittest.cc",
"graph/system_node_impl_unittest.cc",
"graph/worker_node_impl_unittest.cc",
+ "owned_objects_unittest.cc",
"performance_manager_impl_unittest.cc",
"performance_manager_registry_impl_unittest.cc",
"performance_manager_tab_helper_unittest.cc",
"performance_manager_unittest.cc",
+ "registered_objects_unittest.cc",
"web_contents_proxy_unittest.cc",
"worker_watcher_unittest.cc",
]
@@ -215,6 +227,7 @@ source_set("unit_tests") {
# The site data database isn't supported on Android.
if (!is_android) {
sources += [
+ "decorators/site_data_recorder_unittest.cc",
"persistence/site_data/exponential_moving_average_unittest.cc",
"persistence/site_data/leveldb_site_data_store_unittest.cc",
"persistence/site_data/non_recording_site_data_cache_unittest.cc",
diff --git a/chromium/components/performance_manager/DEPS b/chromium/components/performance_manager/DEPS
index 5809451a221..e2788459ac2 100644
--- a/chromium/components/performance_manager/DEPS
+++ b/chromium/components/performance_manager/DEPS
@@ -9,5 +9,6 @@ include_rules = [
"+services/metrics/public/cpp",
"+services/service_manager/public/cpp",
"+third_party/blink/public/mojom/favicon",
+ "+third_party/blink/public/mojom/service_worker",
"+third_party/leveldatabase",
]
diff --git a/chromium/components/performance_manager/README.md b/chromium/components/performance_manager/README.md
index ffdf8bd6818..21efe71dab4 100644
--- a/chromium/components/performance_manager/README.md
+++ b/chromium/components/performance_manager/README.md
@@ -94,6 +94,20 @@ A simple example Decorator might periodically measure the cumulative CPU usage
for each process Node in the Graph and adorn each node with the measured
cumulative CPU usage of the corresponding process.
+Decorators can make data available to consumers by updating core properties of
+a node or by exposing their own custom API. A common interface is to provide an
+associated Data class, with a static accessor to retrieve a Data object from a
+given Node. See
+[PageLiveStateDecorator::Data](public/decorators/page_live_state_decorator.h)
+for an example.
+
+## Observers
+
+Each node type provides an observer interface that callers can implement to
+receive notifications when core node properties change. Data that is exposed
+through a decorator or Node Attached Data may or may not provide change
+notifications depending on the details of the decorator's API.
+
## Aggregators
Aggregators, like Decorators, set properties on the graph nodes. The difference is that
diff --git a/chromium/components/performance_manager/decorators/site_data_recorder.cc b/chromium/components/performance_manager/decorators/site_data_recorder.cc
new file mode 100644
index 00000000000..e16f6cd7fa5
--- /dev/null
+++ b/chromium/components/performance_manager/decorators/site_data_recorder.cc
@@ -0,0 +1,345 @@
+// 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/decorators/site_data_recorder.h"
+
+#include "base/time/time.h"
+#include "components/performance_manager/graph/node_attached_data_impl.h"
+#include "components/performance_manager/graph/page_node_impl.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_factory.h"
+#include "components/performance_manager/persistence/site_data/site_data_writer.h"
+
+namespace performance_manager {
+
+// The period of time after loading during which we ignore title/favicon
+// change events. It's possible for some site that are loaded in background to
+// use some of these features without this being an attempt to communicate
+// with the user (e.g. the page is just really finishing to load).
+constexpr base::TimeDelta kTitleOrFaviconChangePostLoadGracePeriod =
+ base::TimeDelta::FromSeconds(20);
+
+// The period of time during which audio usage gets ignored after a page gets
+// backgrounded. It's necessary because there might be a delay between a media
+// request gets initiated and the time the audio actually starts.
+constexpr base::TimeDelta kFeatureUsagePostBackgroundGracePeriod =
+ base::TimeDelta::FromSeconds(10);
+
+// Provides SiteData machinery access to some internals of a PageNodeImpl.
+class SiteDataAccess {
+ public:
+ static std::unique_ptr<NodeAttachedData>* GetUniquePtrStorage(
+ PageNodeImpl* page_node) {
+ return &page_node->site_data_;
+ }
+};
+
+namespace {
+
+// NodeAttachedData used to adorn every page node with a SiteDataWriter.
+class SiteDataNodeData : public NodeAttachedDataImpl<SiteDataNodeData>,
+ public SiteDataRecorder::Data {
+ public:
+ struct Traits : public NodeAttachedDataOwnedByNodeType<PageNodeImpl> {};
+
+ explicit SiteDataNodeData(const PageNodeImpl* page_node) {}
+ ~SiteDataNodeData() override = default;
+
+ // NodeAttachedData:
+ static std::unique_ptr<NodeAttachedData>* GetUniquePtrStorage(
+ PageNodeImpl* page_node) {
+ return SiteDataAccess::GetUniquePtrStorage(page_node);
+ }
+
+ // Set the SiteDataCache that should be used to create the writer.
+ void set_data_cache(SiteDataCache* data_cache) {
+ DCHECK(data_cache);
+ data_cache_ = data_cache;
+ }
+
+ // Functions called whenever one of the tracked properties changes.
+ void OnMainFrameUrlChanged(const GURL& url, bool page_is_visible);
+ void OnIsLoadingChanged(bool is_loading);
+ void OnIsVisibleChanged(bool is_visible);
+ void OnIsAudibleChanged(bool audible);
+ void OnTitleUpdated();
+ void OnFaviconUpdated();
+
+ SiteDataWriter* writer() const override {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return writer_.get();
+ }
+
+ private:
+ // The features tracked by the SiteDataRecorder class.
+ enum class FeatureType {
+ kTitleChange,
+ kFaviconChange,
+ kAudioUsage,
+ };
+
+ void SetDataCacheForTesting(SiteDataCache* cache) override {
+ set_data_cache(cache);
+ }
+
+ // Indicates of a feature usage event should be ignored.
+ bool ShouldIgnoreFeatureUsageEvent(FeatureType feature_type);
+
+ // Records a feature usage event if necessary.
+ void MaybeNotifyBackgroundFeatureUsage(void (SiteDataWriter::*method)(),
+ FeatureType feature_type);
+
+ // Update |backgrounded_time_| depending on the visibility of the page.
+ void UpdateBackgroundedTime();
+
+ // The SiteDataCache used to serve writers for the PageNode owned by this
+ // object.
+ SiteDataCache* data_cache_ = nullptr;
+
+ bool is_visible_ = false;
+ bool is_loaded_ = false;
+
+ // The Origin tracked by the writer.
+ url::Origin last_origin_;
+
+ // The time at which this tab has been backgrounded, null if this tab is
+ // currently visible.
+ base::TimeTicks backgrounded_time_;
+
+ // The time at which this tab switched to the loaded state, null if this tab
+ // is not currently loaded.
+ base::TimeTicks loaded_time_;
+
+ std::unique_ptr<SiteDataWriter> writer_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ DISALLOW_COPY_AND_ASSIGN(SiteDataNodeData);
+};
+
+void SiteDataNodeData::OnMainFrameUrlChanged(const GURL& url,
+ bool page_is_visible) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ url::Origin origin = url::Origin::Create(url);
+
+ if (origin == last_origin_)
+ return;
+
+ // If the origin has changed then the writer should be invalidated.
+ writer_.reset();
+
+ if (!url.SchemeIsHTTPOrHTTPS())
+ return;
+
+ last_origin_ = origin;
+ writer_ = data_cache_->GetWriterForOrigin(
+ origin, page_is_visible ? TabVisibility::kForeground
+ : TabVisibility::kBackground);
+
+ is_visible_ = page_is_visible;
+ UpdateBackgroundedTime();
+}
+
+void SiteDataNodeData::OnIsLoadingChanged(bool is_loading) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (!writer_)
+ return;
+ if (is_loading) {
+ is_loaded_ = false;
+ writer_->NotifySiteUnloaded();
+ loaded_time_ = base::TimeTicks();
+ } else {
+ is_loaded_ = true;
+ writer_->NotifySiteLoaded();
+ loaded_time_ = base::TimeTicks::Now();
+ }
+}
+
+void SiteDataNodeData::OnIsVisibleChanged(bool is_visible) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (!writer_)
+ return;
+ is_visible_ = is_visible;
+ UpdateBackgroundedTime();
+ writer_->NotifySiteVisibilityChanged(is_visible ? TabVisibility::kForeground
+ : TabVisibility::kBackground);
+}
+
+void SiteDataNodeData::OnIsAudibleChanged(bool audible) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (!audible)
+ return;
+
+ MaybeNotifyBackgroundFeatureUsage(
+ &SiteDataWriter::NotifyUsesAudioInBackground, FeatureType::kAudioUsage);
+}
+
+void SiteDataNodeData::OnTitleUpdated() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ MaybeNotifyBackgroundFeatureUsage(
+ &SiteDataWriter::NotifyUpdatesTitleInBackground,
+ FeatureType::kTitleChange);
+}
+
+void SiteDataNodeData::OnFaviconUpdated() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ MaybeNotifyBackgroundFeatureUsage(
+ &SiteDataWriter::NotifyUpdatesFaviconInBackground,
+ FeatureType::kFaviconChange);
+}
+
+bool SiteDataNodeData::ShouldIgnoreFeatureUsageEvent(FeatureType feature_type) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // The feature usage should be ignored if there's no writer for this page.
+ if (!writer_)
+ return true;
+
+ // Ignore all features happening before the website gets fully loaded.
+ if (!is_loaded_)
+ return true;
+
+ // Ignore events if the tab is not in background.
+ if (is_visible_)
+ return true;
+
+ if (feature_type == FeatureType::kTitleChange ||
+ feature_type == FeatureType::kFaviconChange) {
+ DCHECK(!loaded_time_.is_null());
+ if (base::TimeTicks::Now() - loaded_time_ <
+ kTitleOrFaviconChangePostLoadGracePeriod) {
+ return true;
+ }
+ }
+
+ // Ignore events happening shortly after the tab being backgrounded, they're
+ // usually false positives.
+ DCHECK(!backgrounded_time_.is_null());
+ if ((base::TimeTicks::Now() - backgrounded_time_ <
+ kFeatureUsagePostBackgroundGracePeriod)) {
+ return true;
+ }
+
+ return false;
+}
+
+void SiteDataNodeData::MaybeNotifyBackgroundFeatureUsage(
+ void (SiteDataWriter::*method)(),
+ FeatureType feature_type) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (ShouldIgnoreFeatureUsageEvent(feature_type))
+ return;
+
+ (writer_.get()->*method)();
+}
+
+void SiteDataNodeData::UpdateBackgroundedTime() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (is_visible_) {
+ backgrounded_time_ = base::TimeTicks();
+ } else {
+ backgrounded_time_ = base::TimeTicks::Now();
+ }
+}
+
+SiteDataNodeData* GetSiteDataNodeDataFromPageNode(const PageNode* page_node) {
+ auto* page_node_impl = PageNodeImpl::FromNode(page_node);
+ DCHECK(page_node_impl);
+ auto* data = SiteDataNodeData::Get(page_node_impl);
+ DCHECK(data);
+ return data;
+}
+
+} // namespace
+
+// static
+SiteDataRecorder::Data* SiteDataRecorder::Data::GetForTesting(
+ const PageNode* page_node) {
+ return GetSiteDataNodeDataFromPageNode(page_node);
+}
+
+SiteDataRecorder::SiteDataRecorder() {
+ DETACH_FROM_SEQUENCE(sequence_checker_);
+}
+
+SiteDataRecorder::~SiteDataRecorder() = default;
+
+void SiteDataRecorder::OnPassedToGraph(Graph* graph) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ RegisterObservers(graph);
+}
+
+void SiteDataRecorder::OnTakenFromGraph(Graph* graph) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ UnregisterObservers(graph);
+}
+
+void SiteDataRecorder::OnPageNodeAdded(const PageNode* page_node) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ SetPageNodeDataCache(page_node);
+}
+
+void SiteDataRecorder::OnMainFrameUrlChanged(const PageNode* page_node) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ auto* data = GetSiteDataNodeDataFromPageNode(page_node);
+ data->OnMainFrameUrlChanged(page_node->GetMainFrameUrl(),
+ page_node->IsVisible());
+}
+
+void SiteDataRecorder::OnIsLoadingChanged(const PageNode* page_node) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ auto* data = GetSiteDataNodeDataFromPageNode(page_node);
+ data->OnIsLoadingChanged(page_node->IsLoading());
+}
+
+void SiteDataRecorder::OnIsVisibleChanged(const PageNode* page_node) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ auto* data = GetSiteDataNodeDataFromPageNode(page_node);
+ data->OnIsVisibleChanged(page_node->IsVisible());
+}
+
+void SiteDataRecorder::OnIsAudibleChanged(const PageNode* page_node) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ auto* data = GetSiteDataNodeDataFromPageNode(page_node);
+ data->OnIsAudibleChanged(page_node->IsAudible());
+}
+
+void SiteDataRecorder::OnTitleUpdated(const PageNode* page_node) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ auto* data = GetSiteDataNodeDataFromPageNode(page_node);
+ data->OnTitleUpdated();
+}
+
+void SiteDataRecorder::OnFaviconUpdated(const PageNode* page_node) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ auto* data = GetSiteDataNodeDataFromPageNode(page_node);
+ data->OnFaviconUpdated();
+}
+
+void SiteDataRecorder::RegisterObservers(Graph* graph) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ graph->AddPageNodeObserver(this);
+}
+
+void SiteDataRecorder::UnregisterObservers(Graph* graph) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ graph->RemovePageNodeObserver(this);
+}
+
+void SiteDataRecorder::SetPageNodeDataCache(const PageNode* page_node) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ auto* page_node_impl = PageNodeImpl::FromNode(page_node);
+ DCHECK(page_node_impl);
+ DCHECK(!SiteDataNodeData::Get(page_node_impl));
+ auto* data = SiteDataNodeData::GetOrCreate(page_node_impl);
+ data->set_data_cache(
+ SiteDataCacheFactory::GetInstance()->GetDataCacheForBrowserContext(
+ page_node->GetBrowserContextID()));
+}
+
+SiteDataNodeData::Data::Data() = default;
+SiteDataNodeData::Data::~Data() = default;
+
+} // namespace performance_manager
diff --git a/chromium/components/performance_manager/decorators/site_data_recorder.h b/chromium/components/performance_manager/decorators/site_data_recorder.h
new file mode 100644
index 00000000000..5278e8329f1
--- /dev/null
+++ b/chromium/components/performance_manager/decorators/site_data_recorder.h
@@ -0,0 +1,72 @@
+// 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.
+
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_DECORATORS_SITE_DATA_RECORDER_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_DECORATORS_SITE_DATA_RECORDER_H_
+
+#include "base/macros.h"
+#include "base/sequence_checker.h"
+#include "components/performance_manager/public/graph/graph.h"
+#include "components/performance_manager/public/graph/page_node.h"
+
+namespace performance_manager {
+
+class SiteDataWriter;
+class SiteDataCache;
+
+// The SiteDataRecorder decorator is responsible for adorning PageNodes with a
+// SiteDataWriter and for forwarding the event of interest to this writer.
+class SiteDataRecorder : public GraphOwned,
+ public PageNode::ObserverDefaultImpl {
+ public:
+ class Data;
+
+ SiteDataRecorder();
+ ~SiteDataRecorder() override;
+ SiteDataRecorder(const SiteDataRecorder& other) = delete;
+ SiteDataRecorder& operator=(const SiteDataRecorder&) = delete;
+
+ // GraphOwned implementation:
+ void OnPassedToGraph(Graph* graph) override;
+ void OnTakenFromGraph(Graph* graph) override;
+
+ // PageNode::ObserverDefaultImpl:
+ void OnPageNodeAdded(const PageNode* page_node) override;
+ void OnMainFrameUrlChanged(const PageNode* page_node) override;
+ void OnIsLoadingChanged(const PageNode* page_node) override;
+ void OnIsVisibleChanged(const PageNode* page_node) override;
+ void OnIsAudibleChanged(const PageNode* page_node) override;
+ void OnTitleUpdated(const PageNode* page_node) override;
+ void OnFaviconUpdated(const PageNode* page_node) override;
+
+ private:
+ // (Un)registers the various node observer flavors of this object with the
+ // graph.
+ void RegisterObservers(Graph* graph);
+ void UnregisterObservers(Graph* graph);
+
+ // Set the site data cache that should be used by |page_node| to create its
+ // site data writer.
+ void SetPageNodeDataCache(const PageNode* page_node);
+
+ SEQUENCE_CHECKER(sequence_checker_);
+};
+
+// Allows retrieving the SiteDataWriter associated with a PageNode.
+class SiteDataRecorder::Data {
+ public:
+ Data();
+ virtual ~Data();
+ Data(const Data& other) = delete;
+ Data& operator=(const Data&) = delete;
+
+ virtual SiteDataWriter* writer() const = 0;
+
+ virtual void SetDataCacheForTesting(SiteDataCache* cache) = 0;
+ static Data* GetForTesting(const PageNode* page_node);
+};
+
+} // namespace performance_manager
+
+#endif // COMPONENTS_PERFORMANCE_MANAGER_DECORATORS_SITE_DATA_RECORDER_H_
diff --git a/chromium/components/performance_manager/decorators/site_data_recorder_unittest.cc b/chromium/components/performance_manager/decorators/site_data_recorder_unittest.cc
new file mode 100644
index 00000000000..68d48ab714c
--- /dev/null
+++ b/chromium/components/performance_manager/decorators/site_data_recorder_unittest.cc
@@ -0,0 +1,393 @@
+// 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/decorators/site_data_recorder.h"
+#include <memory>
+
+#include "base/callback_forward.h"
+#include "base/location.h"
+#include "base/run_loop.h"
+#include "base/task/post_task.h"
+#include "base/test/bind_test_util.h"
+#include "base/threading/sequence_bound.h"
+#include "components/performance_manager/graph/page_node_impl.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache.h"
+#include "components/performance_manager/persistence/site_data/site_data_cache_factory.h"
+#include "components/performance_manager/persistence/site_data/site_data_impl.h"
+#include "components/performance_manager/persistence/site_data/site_data_writer.h"
+#include "components/performance_manager/persistence/site_data/tab_visibility.h"
+#include "components/performance_manager/persistence/site_data/unittest_utils.h"
+#include "components/performance_manager/public/graph/graph.h"
+#include "components/performance_manager/public/performance_manager.h"
+#include "components/performance_manager/test_support/performance_manager_test_harness.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/web_contents_tester.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/origin.h"
+
+namespace performance_manager {
+
+constexpr base::TimeDelta kTitleOrFaviconChangePostLoadGracePeriod =
+ base::TimeDelta::FromSeconds(20);
+constexpr base::TimeDelta kFeatureUsagePostBackgroundGracePeriod =
+ base::TimeDelta::FromSeconds(10);
+
+// A mock implementation of a SiteDataWriter.
+class LenientMockDataWriter : public SiteDataWriter {
+ public:
+ LenientMockDataWriter(const url::Origin& origin,
+ scoped_refptr<internal::SiteDataImpl> impl)
+ : SiteDataWriter(impl, TabVisibility::kBackground), origin_(origin) {}
+ ~LenientMockDataWriter() override {
+ if (on_destroy_indicator_)
+ *on_destroy_indicator_ = true;
+ }
+ LenientMockDataWriter(const LenientMockDataWriter& other) = delete;
+ LenientMockDataWriter& operator=(const LenientMockDataWriter&) = delete;
+
+ MOCK_METHOD0(NotifySiteLoaded, void());
+ MOCK_METHOD0(NotifySiteUnloaded, void());
+ MOCK_METHOD1(NotifySiteVisibilityChanged,
+ void(performance_manager::TabVisibility));
+ MOCK_METHOD0(NotifyUpdatesFaviconInBackground, void());
+ MOCK_METHOD0(NotifyUpdatesTitleInBackground, void());
+ MOCK_METHOD0(NotifyUsesAudioInBackground, void());
+ MOCK_METHOD3(NotifyLoadTimePerformanceMeasurement,
+ void(base::TimeDelta, base::TimeDelta, uint64_t));
+
+ // Used to record the destruction of this object.
+ void SetOnDestroyIndicator(bool* on_destroy_indicator) {
+ if (on_destroy_indicator)
+ EXPECT_FALSE(*on_destroy_indicator);
+ on_destroy_indicator_ = on_destroy_indicator;
+ }
+
+ const url::Origin& Origin() const { return origin_; }
+
+ private:
+ bool* on_destroy_indicator_ = nullptr;
+ url::Origin origin_;
+};
+using MockDataWriter = ::testing::StrictMock<LenientMockDataWriter>;
+
+// A data cache that serves MockDataWriter objects.
+class MockDataCache : public SiteDataCache {
+ public:
+ MockDataCache() = default;
+ ~MockDataCache() override = default;
+ MockDataCache(const MockDataCache& other) = delete;
+ MockDataCache& operator=(const MockDataCache&) = delete;
+
+ // SiteDataCache:
+ std::unique_ptr<SiteDataReader> GetReaderForOrigin(
+ const url::Origin& origin) override {
+ return nullptr;
+ }
+ std::unique_ptr<SiteDataWriter> GetWriterForOrigin(
+ const url::Origin& origin,
+ performance_manager::TabVisibility tab_visibility) override {
+ scoped_refptr<internal::SiteDataImpl> fake_impl = base::WrapRefCounted(
+ new internal::SiteDataImpl(origin, &delegate_, &data_store_));
+
+ return std::make_unique<MockDataWriter>(origin, fake_impl);
+ }
+ bool IsRecordingForTesting() const override { return true; }
+ int Size() const override { return 0; }
+
+ private:
+ testing::NoopSiteDataStore data_store_;
+
+ // The mock delegate used by the SiteDataImpl objects
+ // created by this class, NiceMock is used to avoid having to set
+ // expectations in test cases that don't care about this.
+ ::testing::NiceMock<testing::MockSiteDataImplOnDestroyDelegate> delegate_;
+};
+
+void NavigatePageNodeOnUIThread(content::WebContents* contents,
+ const GURL& url) {
+ EXPECT_TRUE(contents);
+ content::WebContentsTester* web_contents_tester =
+ content::WebContentsTester::For(contents);
+ EXPECT_TRUE(web_contents_tester);
+ web_contents_tester->NavigateAndCommit(url);
+}
+
+void RunTaskOnPMSequence(base::OnceClosure task) {
+ base::RunLoop run_loop;
+ PerformanceManager::CallOnGraph(FROM_HERE, std::move(task));
+ PerformanceManager::CallOnGraph(FROM_HERE, run_loop.QuitClosure());
+ run_loop.Run();
+}
+
+MockDataWriter* GetMockWriterForPageNode(const PageNode* page_node) {
+ return static_cast<MockDataWriter*>(
+ SiteDataRecorder::Data::GetForTesting(page_node)->writer());
+}
+
+class SiteDataRecorderTest : public PerformanceManagerTestHarness {
+ public:
+ SiteDataRecorderTest()
+ : PerformanceManagerTestHarness(
+ content::BrowserTaskEnvironment::TimeSource::MOCK_TIME) {}
+ ~SiteDataRecorderTest() override = default;
+ explicit SiteDataRecorderTest(const SiteDataRecorderTest& other) = delete;
+ SiteDataRecorderTest& operator=(const SiteDataRecorderTest&) = delete;
+
+ void SetUp() override {
+ PerformanceManagerTestHarness::SetUp();
+ cache_factory_ = base::SequenceBound<SiteDataCacheFactory>(
+ PerformanceManager::GetTaskRunner());
+ auto recorder = std::make_unique<SiteDataRecorder>();
+ recorder_ = recorder.get();
+ PerformanceManager::PassToGraph(FROM_HERE, std::move(recorder));
+
+ auto browser_context_id = GetBrowserContext()->UniqueId();
+ RunTaskOnPMSequence(base::BindLambdaForTesting([&]() {
+ SiteDataCacheFactory::GetInstance()->SetCacheForTesting(
+ browser_context_id, std::make_unique<MockDataCache>());
+ }));
+
+ SetContents(CreateTestWebContents());
+ base::WeakPtr<PageNode> page_node =
+ PerformanceManager::GetPageNodeForWebContents(web_contents());
+ RunTaskOnPMSequence(base::BindLambdaForTesting([&]() {
+ auto* page_node_impl = PageNodeImpl::FromNode(page_node.get());
+ page_node_impl->SetIsAudible(false);
+ page_node_impl->SetIsVisible(false);
+ page_node_impl->SetIsLoading(true);
+ }));
+ }
+
+ void TearDown() override {
+ DeleteContents();
+ recorder_ = nullptr;
+ base::RunLoop run_loop;
+ cache_factory_.ResetWithCallbackAfterDestruction(run_loop.QuitClosure());
+ run_loop.Run();
+ PerformanceManagerTestHarness::TearDown();
+ }
+
+ const GURL kTestUrl1 = GURL("http://foo.com");
+ const GURL kTestUrl2 = GURL("http://bar.com");
+
+ private:
+ SiteDataRecorder* recorder_ = nullptr;
+ base::SequenceBound<SiteDataCacheFactory> cache_factory_;
+};
+
+TEST_F(SiteDataRecorderTest, NavigationEventsBasicTests) {
+ base::WeakPtr<PageNode> page_node =
+ PerformanceManager::GetPageNodeForWebContents(web_contents());
+
+ RunTaskOnPMSequence(base::BindLambdaForTesting([&]() {
+ EXPECT_TRUE(page_node);
+ EXPECT_FALSE(
+ SiteDataRecorder::Data::GetForTesting(page_node.get())->writer());
+ }));
+
+ // Send a navigation event with the |committed| bit set and make sure that a
+ // writer has been created for this origin.
+ NavigatePageNodeOnUIThread(web_contents(), kTestUrl1);
+
+ MockDataWriter* mock_writer = nullptr;
+ RunTaskOnPMSequence(base::BindLambdaForTesting([&]() {
+ mock_writer = GetMockWriterForPageNode(page_node.get());
+ ASSERT_TRUE(mock_writer);
+ EXPECT_EQ(url::Origin::Create(kTestUrl1), mock_writer->Origin());
+ }));
+
+ {
+ // A navigation to the same origin shouldn't cause caused this writer to get
+ // destroyed.
+ bool writer_has_been_destroyed = false;
+
+ RunTaskOnPMSequence(base::BindLambdaForTesting([&]() {
+ mock_writer->SetOnDestroyIndicator(&writer_has_been_destroyed);
+ }));
+
+ NavigatePageNodeOnUIThread(web_contents(), kTestUrl1);
+ RunTaskOnPMSequence(base::BindLambdaForTesting(
+ [&]() { EXPECT_FALSE(writer_has_been_destroyed); }));
+
+ // Navigate to a different origin and make sure that this causes the
+ // destruction of the writer.
+ NavigatePageNodeOnUIThread(web_contents(), kTestUrl2);
+
+ RunTaskOnPMSequence(base::BindLambdaForTesting([&]() {
+ EXPECT_TRUE(writer_has_been_destroyed);
+ mock_writer = GetMockWriterForPageNode(page_node.get());
+ EXPECT_EQ(url::Origin::Create(kTestUrl2), mock_writer->Origin());
+ mock_writer->SetOnDestroyIndicator(nullptr);
+ }));
+ }
+}
+
+// Test that the feature usage events get forwarded to the writer when the tab
+// is in background.
+TEST_F(SiteDataRecorderTest, FeatureEventsGetForwardedWhenInBackground) {
+ base::WeakPtr<PageNode> page_node =
+ PerformanceManager::GetPageNodeForWebContents(web_contents());
+
+ NavigatePageNodeOnUIThread(web_contents(), kTestUrl1);
+
+ MockDataWriter* mock_writer = nullptr;
+ PageNodeImpl* node_impl = nullptr;
+ RunTaskOnPMSequence(base::BindLambdaForTesting([&]() {
+ mock_writer = GetMockWriterForPageNode(page_node.get());
+ ASSERT_TRUE(mock_writer);
+ EXPECT_EQ(url::Origin::Create(kTestUrl1), mock_writer->Origin());
+
+ node_impl = PageNodeImpl::FromNode(page_node.get());
+ EXPECT_CALL(*mock_writer, NotifySiteLoaded());
+ node_impl->SetIsLoading(false);
+ ::testing::Mock::VerifyAndClear(mock_writer);
+
+ EXPECT_CALL(*mock_writer,
+ NotifySiteVisibilityChanged(
+ performance_manager::TabVisibility::kForeground));
+ }));
+
+ web_contents()->WasShown();
+
+ RunTaskOnPMSequence(base::BindLambdaForTesting([&]() {
+ ::testing::Mock::VerifyAndClear(mock_writer);
+
+ // Ensure that no event gets forwarded if the tab is not in background.
+ node_impl->OnFaviconUpdated();
+ ::testing::Mock::VerifyAndClear(mock_writer);
+ node_impl->OnTitleUpdated();
+ ::testing::Mock::VerifyAndClear(mock_writer);
+ node_impl->SetIsAudible(true);
+ ::testing::Mock::VerifyAndClear(mock_writer);
+
+ EXPECT_CALL(*mock_writer,
+ NotifySiteVisibilityChanged(
+ performance_manager::TabVisibility::kBackground));
+ }));
+ web_contents()->WasHidden();
+
+ RunTaskOnPMSequence(base::BindLambdaForTesting([&]() {
+ ::testing::Mock::VerifyAndClear(mock_writer);
+
+ // Title and Favicon should be ignored during the post-loading grace period.
+ node_impl->OnFaviconUpdated();
+ node_impl->OnTitleUpdated();
+ ::testing::Mock::VerifyAndClear(mock_writer);
+ }));
+
+ task_environment()->FastForwardBy(kTitleOrFaviconChangePostLoadGracePeriod);
+
+ RunTaskOnPMSequence(base::BindLambdaForTesting([&]() {
+ EXPECT_CALL(*mock_writer, NotifyUpdatesFaviconInBackground());
+ node_impl->OnFaviconUpdated();
+ ::testing::Mock::VerifyAndClear(mock_writer);
+ EXPECT_CALL(*mock_writer, NotifyUpdatesTitleInBackground());
+ node_impl->OnTitleUpdated();
+ ::testing::Mock::VerifyAndClear(mock_writer);
+
+ // Brievly switch the tab to foreground to reset the last backgrounded time.
+ EXPECT_CALL(*mock_writer,
+ NotifySiteVisibilityChanged(
+ performance_manager::TabVisibility::kForeground));
+ EXPECT_CALL(*mock_writer,
+ NotifySiteVisibilityChanged(
+ performance_manager::TabVisibility::kBackground));
+ }));
+ web_contents()->WasShown();
+ web_contents()->WasHidden();
+
+ RunTaskOnPMSequence(base::BindLambdaForTesting([&]() {
+ ::testing::Mock::VerifyAndClear(mock_writer);
+
+ // These events should be ignored during the post-background grace period.
+ node_impl->SetIsAudible(true);
+ node_impl->SetIsAudible(false);
+ node_impl->OnFaviconUpdated();
+ node_impl->OnTitleUpdated();
+ ::testing::Mock::VerifyAndClear(mock_writer);
+ }));
+
+ task_environment()->FastForwardBy(kFeatureUsagePostBackgroundGracePeriod);
+
+ RunTaskOnPMSequence(base::BindLambdaForTesting([&]() {
+ EXPECT_CALL(*mock_writer, NotifyUsesAudioInBackground());
+ EXPECT_CALL(*mock_writer, NotifyUpdatesFaviconInBackground());
+ EXPECT_CALL(*mock_writer, NotifyUpdatesTitleInBackground());
+ node_impl->SetIsAudible(true);
+ node_impl->OnFaviconUpdated();
+ node_impl->OnTitleUpdated();
+ ::testing::Mock::VerifyAndClear(mock_writer);
+ }));
+}
+
+TEST_F(SiteDataRecorderTest, FeatureEventsIgnoredWhenLoadingInBackground) {
+ base::WeakPtr<PageNode> page_node =
+ PerformanceManager::GetPageNodeForWebContents(web_contents());
+ NavigatePageNodeOnUIThread(web_contents(), kTestUrl1);
+
+ RunTaskOnPMSequence(base::BindLambdaForTesting([&]() {
+ MockDataWriter* mock_writer = GetMockWriterForPageNode(page_node.get());
+ ASSERT_TRUE(mock_writer);
+ EXPECT_EQ(url::Origin::Create(kTestUrl1), mock_writer->Origin());
+
+ PageNodeImpl* node_impl = PageNodeImpl::FromNode(page_node.get());
+ ::testing::Mock::VerifyAndClear(mock_writer);
+ node_impl->OnFaviconUpdated();
+ ::testing::Mock::VerifyAndClear(mock_writer);
+ node_impl->OnTitleUpdated();
+ ::testing::Mock::VerifyAndClear(mock_writer);
+ node_impl->SetIsAudible(true);
+ ::testing::Mock::VerifyAndClear(mock_writer);
+ }));
+}
+
+TEST_F(SiteDataRecorderTest, VisibilityEvent) {
+ base::WeakPtr<PageNode> page_node =
+ PerformanceManager::GetPageNodeForWebContents(web_contents());
+ NavigatePageNodeOnUIThread(web_contents(), kTestUrl1);
+
+ RunTaskOnPMSequence(base::BindLambdaForTesting([&]() {
+ MockDataWriter* mock_writer = GetMockWriterForPageNode(page_node.get());
+ PageNodeImpl* node_impl = PageNodeImpl::FromNode(page_node.get());
+
+ // Test that the visibility events get forwarded to the writer.
+
+ EXPECT_CALL(*mock_writer,
+ NotifySiteVisibilityChanged(
+ performance_manager::TabVisibility::kForeground));
+ node_impl->SetIsVisible(true);
+ ::testing::Mock::VerifyAndClear(mock_writer);
+
+ EXPECT_CALL(*mock_writer,
+ NotifySiteVisibilityChanged(
+ performance_manager::TabVisibility::kBackground));
+ node_impl->SetIsVisible(false);
+ ::testing::Mock::VerifyAndClear(mock_writer);
+ }));
+}
+
+TEST_F(SiteDataRecorderTest, LoadEvent) {
+ base::WeakPtr<PageNode> page_node =
+ PerformanceManager::GetPageNodeForWebContents(web_contents());
+ NavigatePageNodeOnUIThread(web_contents(), kTestUrl1);
+
+ RunTaskOnPMSequence(base::BindLambdaForTesting([&]() {
+ MockDataWriter* mock_writer = GetMockWriterForPageNode(page_node.get());
+ PageNodeImpl* node_impl = PageNodeImpl::FromNode(page_node.get());
+
+ // Test that the load/unload events get forwarded to the writer.
+
+ EXPECT_CALL(*mock_writer, NotifySiteLoaded());
+ node_impl->SetIsLoading(false);
+ ::testing::Mock::VerifyAndClear(mock_writer);
+
+ EXPECT_CALL(*mock_writer, NotifySiteUnloaded());
+ node_impl->SetIsLoading(true);
+ ::testing::Mock::VerifyAndClear(mock_writer);
+ }));
+}
+
+} // namespace performance_manager
diff --git a/chromium/components/performance_manager/decorators/v8_per_frame_memory_decorator.cc b/chromium/components/performance_manager/decorators/v8_per_frame_memory_decorator.cc
new file mode 100644
index 00000000000..c778ad0156c
--- /dev/null
+++ b/chromium/components/performance_manager/decorators/v8_per_frame_memory_decorator.cc
@@ -0,0 +1,483 @@
+// 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/decorators/v8_per_frame_memory_decorator.h"
+
+#include "base/bind.h"
+#include "base/check.h"
+#include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
+#include "base/stl_util.h"
+#include "base/timer/timer.h"
+#include "components/performance_manager/public/graph/frame_node.h"
+#include "components/performance_manager/public/graph/node_attached_data.h"
+#include "components/performance_manager/public/graph/node_data_describer_registry.h"
+#include "components/performance_manager/public/graph/process_node.h"
+#include "components/performance_manager/public/performance_manager.h"
+#include "components/performance_manager/public/render_process_host_proxy.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_process_host.h"
+
+namespace performance_manager {
+
+namespace {
+
+// Forwards the pending receiver to the RenderProcessHost and binds it on the
+// UI thread.
+void BindReceiverOnUIThread(
+ mojo::PendingReceiver<performance_manager::mojom::V8PerFrameMemoryReporter>
+ pending_receiver,
+ RenderProcessHostProxy proxy) {
+ auto* render_process_host = proxy.Get();
+ if (render_process_host) {
+ render_process_host->BindReceiver(std::move(pending_receiver));
+ }
+}
+
+internal::BindV8PerFrameMemoryReporterCallback* g_test_bind_callback = nullptr;
+
+// Private implementations of the node attached data. This keeps the complexity
+// out of the header file.
+
+class NodeAttachedFrameData
+ : public ExternalNodeAttachedDataImpl<NodeAttachedFrameData> {
+ public:
+ explicit NodeAttachedFrameData(const FrameNode* frame_node) {}
+ ~NodeAttachedFrameData() override = default;
+
+ NodeAttachedFrameData(const NodeAttachedFrameData&) = delete;
+ NodeAttachedFrameData& operator=(const NodeAttachedFrameData&) = delete;
+
+ const V8PerFrameMemoryDecorator::FrameData* data() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return data_available_ ? &data_ : nullptr;
+ }
+
+ private:
+ friend class NodeAttachedProcessData;
+
+ V8PerFrameMemoryDecorator::FrameData data_;
+ bool data_available_ = false;
+ SEQUENCE_CHECKER(sequence_checker_);
+};
+
+class NodeAttachedProcessData
+ : public ExternalNodeAttachedDataImpl<NodeAttachedProcessData> {
+ public:
+ explicit NodeAttachedProcessData(const ProcessNode* process_node)
+ : process_node_(process_node) {}
+ ~NodeAttachedProcessData() override = default;
+
+ NodeAttachedProcessData(const NodeAttachedProcessData&) = delete;
+ NodeAttachedProcessData& operator=(const NodeAttachedProcessData&) = delete;
+
+ const V8PerFrameMemoryDecorator::ProcessData* data() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return data_available_ ? &data_ : nullptr;
+ }
+
+ void Initialize(const V8PerFrameMemoryDecorator* decorator);
+ void ScheduleNextMeasurement();
+
+ private:
+ void StartMeasurement();
+ void EnsureRemote();
+ void OnPerFrameV8MemoryUsageData(
+ performance_manager::mojom::PerProcessV8MemoryUsageDataPtr result);
+
+ const ProcessNode* const process_node_;
+ const V8PerFrameMemoryDecorator* decorator_ = nullptr;
+
+ mojo::Remote<performance_manager::mojom::V8PerFrameMemoryReporter>
+ resource_usage_reporter_;
+
+ enum class State {
+ kUninitialized,
+ kWaiting, // Waiting to take a measurement.
+ kMeasuring, // Waiting for measurement results.
+ kIdle, // No measurements scheduled.
+ };
+ State state_ = State::kUninitialized;
+
+ // Used to schedule the next measurement.
+ base::TimeTicks last_request_time_;
+ base::OneShotTimer timer_;
+
+ V8PerFrameMemoryDecorator::ProcessData data_;
+ bool data_available_ = false;
+ SEQUENCE_CHECKER(sequence_checker_);
+};
+
+void NodeAttachedProcessData::Initialize(
+ const V8PerFrameMemoryDecorator* decorator) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(nullptr, decorator_);
+ decorator_ = decorator;
+ DCHECK_EQ(state_, State::kUninitialized);
+
+ state_ = State::kWaiting;
+ ScheduleNextMeasurement();
+}
+
+void NodeAttachedProcessData::ScheduleNextMeasurement() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_NE(nullptr, decorator_);
+ DCHECK_NE(state_, State::kUninitialized);
+
+ if (state_ == State::kMeasuring) {
+ // Don't restart the timer until the current measurement finishes.
+ // ScheduleNextMeasurement will be called again at that point.
+ return;
+ }
+
+ if (decorator_->GetMinTimeBetweenRequestsPerProcess().is_zero()) {
+ // All measurements have been cancelled.
+ state_ = State::kIdle;
+ timer_.Stop();
+ last_request_time_ = base::TimeTicks();
+ return;
+ }
+
+ state_ = State::kWaiting;
+ if (last_request_time_.is_null()) {
+ // This is the first measurement. Perform it immediately.
+ StartMeasurement();
+ return;
+ }
+
+ base::TimeTicks next_request_time =
+ last_request_time_ + decorator_->GetMinTimeBetweenRequestsPerProcess();
+ timer_.Start(FROM_HERE, next_request_time - base::TimeTicks::Now(), this,
+ &NodeAttachedProcessData::StartMeasurement);
+}
+
+void NodeAttachedProcessData::StartMeasurement() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(state_, State::kWaiting);
+ state_ = State::kMeasuring;
+ last_request_time_ = base::TimeTicks::Now();
+
+ EnsureRemote();
+ resource_usage_reporter_->GetPerFrameV8MemoryUsageData(
+ base::BindOnce(&NodeAttachedProcessData::OnPerFrameV8MemoryUsageData,
+ base::Unretained(this)));
+}
+
+void NodeAttachedProcessData::OnPerFrameV8MemoryUsageData(
+ performance_manager::mojom::PerProcessV8MemoryUsageDataPtr result) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(state_, State::kMeasuring);
+
+ // Distribute the data to the frames.
+ // If a frame doesn't have corresponding data in the result, clear any data
+ // it may have had. Any datum in the result that doesn't correspond to an
+ // existing frame is likewise accured to unassociated usage.
+ uint64_t unassociated_v8_bytes_used = result->unassociated_bytes_used;
+
+ // Create a mapping from token to per-frame usage for the merge below.
+ std::vector<std::pair<FrameToken, mojom::PerFrameV8MemoryUsageDataPtr>> tmp;
+ for (auto& entry : result->associated_memory) {
+ tmp.emplace_back(
+ std::make_pair(FrameToken(entry->frame_token), std::move(entry)));
+ }
+ DCHECK_EQ(tmp.size(), result->associated_memory.size());
+
+ base::flat_map<FrameToken, mojom::PerFrameV8MemoryUsageDataPtr>
+ associated_memory(std::move(tmp));
+ // Validate that the frame tokens were all unique. If there are duplicates,
+ // the map will arbirarily drop all but one record per unique token.
+ DCHECK_EQ(associated_memory.size(), result->associated_memory.size());
+
+ base::flat_set<const FrameNode*> frame_nodes = process_node_->GetFrameNodes();
+ for (const FrameNode* frame_node : frame_nodes) {
+ auto it = associated_memory.find(frame_node->GetFrameToken());
+ if (it == associated_memory.end()) {
+ // No data for this node, clear any data associated with it.
+ NodeAttachedFrameData::Destroy(frame_node);
+ } else {
+ // There should always be data for the main isolated world for each frame.
+ DCHECK(base::Contains(it->second->associated_bytes, 0));
+
+ NodeAttachedFrameData* frame_data =
+ NodeAttachedFrameData::GetOrCreate(frame_node);
+ for (const auto& kv : it->second->associated_bytes) {
+ if (kv.first == 0) {
+ frame_data->data_available_ = true;
+ frame_data->data_.set_v8_bytes_used(kv.second->bytes_used);
+ } else {
+ // TODO(crbug.com/1080672): Where to stash the rest of the data?
+ }
+ }
+
+ // Clear this datum as its usage has been consumed.
+ associated_memory.erase(it);
+ }
+ }
+
+ for (const auto& it : associated_memory) {
+ // Accrue the data for non-existent frames to unassociated bytes.
+ unassociated_v8_bytes_used += it.second->associated_bytes[0]->bytes_used;
+ }
+
+ data_available_ = true;
+ data_.set_unassociated_v8_bytes_used(unassociated_v8_bytes_used);
+
+ // Schedule another measurement for this process node.
+ state_ = State::kIdle;
+ ScheduleNextMeasurement();
+}
+
+void NodeAttachedProcessData::EnsureRemote() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (resource_usage_reporter_.is_bound())
+ return;
+
+ // This interface is implemented in //content/renderer/performance_manager.
+ mojo::PendingReceiver<performance_manager::mojom::V8PerFrameMemoryReporter>
+ pending_receiver = resource_usage_reporter_.BindNewPipeAndPassReceiver();
+
+ RenderProcessHostProxy proxy = process_node_->GetRenderProcessHostProxy();
+
+ if (g_test_bind_callback) {
+ g_test_bind_callback->Run(std::move(pending_receiver), std::move(proxy));
+ } else {
+ content::GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE,
+ base::BindOnce(&BindReceiverOnUIThread, std::move(pending_receiver),
+ std::move(proxy)));
+ }
+}
+
+} // namespace
+
+namespace internal {
+
+void SetBindV8PerFrameMemoryReporterCallbackForTesting(
+ BindV8PerFrameMemoryReporterCallback* callback) {
+ g_test_bind_callback = callback;
+}
+
+} // namespace internal
+
+V8PerFrameMemoryDecorator::MeasurementRequest::MeasurementRequest(
+ const base::TimeDelta& sample_frequency)
+ : sample_frequency_(sample_frequency) {
+ DCHECK_GT(sample_frequency_, base::TimeDelta());
+}
+
+V8PerFrameMemoryDecorator::MeasurementRequest::MeasurementRequest(
+ const base::TimeDelta& sample_frequency,
+ Graph* graph)
+ : sample_frequency_(sample_frequency) {
+ DCHECK_GT(sample_frequency_, base::TimeDelta());
+ StartMeasurement(graph);
+}
+
+V8PerFrameMemoryDecorator::MeasurementRequest::~MeasurementRequest() {
+ if (decorator_)
+ decorator_->RemoveMeasurementRequest(this);
+ // TODO(crbug.com/1080672): Delete the decorator and its NodeAttachedData
+ // when the last request is destroyed. Make sure this doesn't mess up any
+ // measurement that's already in progress.
+}
+
+void V8PerFrameMemoryDecorator::MeasurementRequest::StartMeasurement(
+ Graph* graph) {
+ DCHECK_EQ(nullptr, decorator_);
+ decorator_ = graph->GetRegisteredObjectAs<V8PerFrameMemoryDecorator>();
+ if (!decorator_) {
+ // Create the decorator when the first measurement starts.
+ auto decorator_ptr = std::make_unique<V8PerFrameMemoryDecorator>();
+ decorator_ = decorator_ptr.get();
+ graph->PassToGraph(std::move(decorator_ptr));
+ }
+
+ decorator_->AddMeasurementRequest(this);
+}
+
+void V8PerFrameMemoryDecorator::MeasurementRequest::OnDecoratorUnregistered() {
+ decorator_ = nullptr;
+}
+
+const V8PerFrameMemoryDecorator::FrameData*
+V8PerFrameMemoryDecorator::FrameData::ForFrameNode(const FrameNode* node) {
+ auto* node_data = NodeAttachedFrameData::Get(node);
+ return node_data ? node_data->data() : nullptr;
+}
+
+const V8PerFrameMemoryDecorator::ProcessData*
+V8PerFrameMemoryDecorator::ProcessData::ForProcessNode(
+ const ProcessNode* node) {
+ auto* node_data = NodeAttachedProcessData::Get(node);
+ return node_data ? node_data->data() : nullptr;
+}
+
+V8PerFrameMemoryDecorator::V8PerFrameMemoryDecorator() = default;
+
+V8PerFrameMemoryDecorator::~V8PerFrameMemoryDecorator() {
+ DCHECK(measurement_requests_.empty());
+}
+
+void V8PerFrameMemoryDecorator::OnPassedToGraph(Graph* graph) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(nullptr, graph_);
+ graph_ = graph;
+
+ graph->RegisterObject(this);
+
+ // Iterate over the existing process nodes to put them under observation.
+ for (const ProcessNode* process_node : graph->GetAllProcessNodes())
+ OnProcessNodeAdded(process_node);
+
+ graph->AddProcessNodeObserver(this);
+ graph->GetNodeDataDescriberRegistry()->RegisterDescriber(
+ this, "V8PerFrameMemoryDecorator");
+}
+
+void V8PerFrameMemoryDecorator::OnTakenFromGraph(Graph* graph) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(graph, graph_);
+ for (MeasurementRequest* request : measurement_requests_) {
+ request->OnDecoratorUnregistered();
+ }
+ measurement_requests_.clear();
+ UpdateProcessMeasurementSchedules();
+
+ graph->GetNodeDataDescriberRegistry()->UnregisterDescriber(this);
+ graph->RemoveProcessNodeObserver(this);
+ graph->UnregisterObject(this);
+ graph_ = nullptr;
+}
+
+void V8PerFrameMemoryDecorator::OnProcessNodeAdded(
+ const ProcessNode* process_node) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(nullptr, NodeAttachedProcessData::Get(process_node));
+
+ // Only renderer processes have frames. Don't attempt to connect to other
+ // process types.
+ if (process_node->GetProcessType() != content::PROCESS_TYPE_RENDERER)
+ return;
+
+ NodeAttachedProcessData* process_data =
+ NodeAttachedProcessData::GetOrCreate(process_node);
+ process_data->Initialize(this);
+}
+
+base::Value V8PerFrameMemoryDecorator::DescribeFrameNodeData(
+ const FrameNode* frame_node) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ const FrameData* const frame_data = FrameData::ForFrameNode(frame_node);
+ if (!frame_data)
+ return base::Value();
+
+ base::Value dict(base::Value::Type::DICTIONARY);
+ dict.SetIntKey("v8_bytes_used", frame_data->v8_bytes_used());
+ return dict;
+}
+
+base::Value V8PerFrameMemoryDecorator::DescribeProcessNodeData(
+ const ProcessNode* process_node) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ const ProcessData* const process_data =
+ ProcessData::ForProcessNode(process_node);
+ if (!process_data)
+ return base::Value();
+
+ DCHECK_EQ(content::PROCESS_TYPE_RENDERER, process_node->GetProcessType());
+
+ base::Value dict(base::Value::Type::DICTIONARY);
+ dict.SetIntKey("unassociated_v8_bytes_used",
+ process_data->unassociated_v8_bytes_used());
+ return dict;
+}
+
+base::TimeDelta V8PerFrameMemoryDecorator::GetMinTimeBetweenRequestsPerProcess()
+ const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return measurement_requests_.empty()
+ ? base::TimeDelta()
+ : measurement_requests_.front()->sample_frequency();
+}
+
+void V8PerFrameMemoryDecorator::AddMeasurementRequest(
+ MeasurementRequest* request) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(request);
+ DCHECK(!base::Contains(measurement_requests_, request))
+ << "MeasurementRequest object added twice";
+ // Each user of this decorator is expected to issue a single
+ // MeasurementRequest, so the size of measurement_requests_ is too low to
+ // make the complexity of real priority queue worthwhile.
+ for (std::vector<MeasurementRequest*>::const_iterator it =
+ measurement_requests_.begin();
+ it != measurement_requests_.end(); ++it) {
+ if (request->sample_frequency() < (*it)->sample_frequency()) {
+ measurement_requests_.insert(it, request);
+ UpdateProcessMeasurementSchedules();
+ return;
+ }
+ }
+ measurement_requests_.push_back(request);
+ UpdateProcessMeasurementSchedules();
+}
+
+void V8PerFrameMemoryDecorator::RemoveMeasurementRequest(
+ MeasurementRequest* request) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(request);
+ size_t num_erased = base::Erase(measurement_requests_, request);
+ DCHECK_EQ(num_erased, 1ULL);
+ UpdateProcessMeasurementSchedules();
+}
+
+void V8PerFrameMemoryDecorator::UpdateProcessMeasurementSchedules() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(graph_);
+#if DCHECK_IS_ON()
+ // Check the data invariant on measurement_requests_, which will be used by
+ // ScheduleNextMeasurement.
+ for (size_t i = 1; i < measurement_requests_.size(); ++i) {
+ DCHECK(measurement_requests_[i - 1]);
+ DCHECK(measurement_requests_[i]);
+ DCHECK_LE(measurement_requests_[i - 1]->sample_frequency(),
+ measurement_requests_[i]->sample_frequency());
+ }
+#endif
+ for (const ProcessNode* node : graph_->GetAllProcessNodes()) {
+ NodeAttachedProcessData* process_data = NodeAttachedProcessData::Get(node);
+ if (!process_data) {
+ DCHECK_NE(content::PROCESS_TYPE_RENDERER, node->GetProcessType())
+ << "NodeAttachedProcessData should have been created for all "
+ "renderer processes in OnProcessNodeAdded.";
+ continue;
+ }
+ process_data->ScheduleNextMeasurement();
+ }
+}
+
+V8PerFrameMemoryRequest::V8PerFrameMemoryRequest(
+ const base::TimeDelta& sample_frequency)
+ : request_(std::make_unique<V8PerFrameMemoryDecorator::MeasurementRequest>(
+ sample_frequency)) {
+ // Unretained is safe since the destructor will run on the same sequence as
+ // StartMeasurement.
+ PerformanceManager::CallOnGraph(
+ FROM_HERE,
+ base::BindOnce(
+ &V8PerFrameMemoryDecorator::MeasurementRequest::StartMeasurement,
+ base::Unretained(request_.get())));
+}
+
+V8PerFrameMemoryRequest::~V8PerFrameMemoryRequest() {
+ PerformanceManager::CallOnGraph(
+ FROM_HERE,
+ base::BindOnce(
+ [](std::unique_ptr<V8PerFrameMemoryDecorator::MeasurementRequest>
+ request) { request.reset(); },
+ std::move(request_)));
+}
+
+} // namespace performance_manager
diff --git a/chromium/components/performance_manager/decorators/v8_per_frame_memory_decorator_unittest.cc b/chromium/components/performance_manager/decorators/v8_per_frame_memory_decorator_unittest.cc
new file mode 100644
index 00000000000..1dfdea0e905
--- /dev/null
+++ b/chromium/components/performance_manager/decorators/v8_per_frame_memory_decorator_unittest.cc
@@ -0,0 +1,970 @@
+// 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/decorators/v8_per_frame_memory_decorator.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/gtest_util.h"
+#include "components/performance_manager/graph/frame_node_impl.h"
+#include "components/performance_manager/graph/page_node_impl.h"
+#include "components/performance_manager/graph/process_node_impl.h"
+#include "components/performance_manager/public/performance_manager.h"
+#include "components/performance_manager/public/render_process_host_proxy.h"
+#include "components/performance_manager/test_support/graph_test_harness.h"
+#include "components/performance_manager/test_support/mock_graphs.h"
+#include "components/performance_manager/test_support/performance_manager_test_harness.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace performance_manager {
+
+class V8PerFrameMemoryDecoratorTest;
+using testing::_;
+
+constexpr int kTestProcessID = 0xFAB;
+constexpr uint64_t kUnassociatedBytes = 0xABBA;
+
+namespace {
+
+using FrameData = V8PerFrameMemoryDecorator::FrameData;
+using ProcessData = V8PerFrameMemoryDecorator::ProcessData;
+
+class LenientMockV8PerFrameMemoryReporter
+ : public mojom::V8PerFrameMemoryReporter {
+ public:
+ LenientMockV8PerFrameMemoryReporter() : receiver_(this) {}
+
+ MOCK_METHOD1(GetPerFrameV8MemoryUsageData,
+ void(GetPerFrameV8MemoryUsageDataCallback callback));
+
+ void Bind(
+ mojo::PendingReceiver<mojom::V8PerFrameMemoryReporter> pending_receiver) {
+ return receiver_.Bind(std::move(pending_receiver));
+ }
+
+ private:
+ mojo::Receiver<mojom::V8PerFrameMemoryReporter> receiver_;
+};
+
+using MockV8PerFrameMemoryReporter =
+ testing::StrictMock<LenientMockV8PerFrameMemoryReporter>;
+
+void AddPerFrameIsolateMemoryUsage(FrameToken frame_token,
+ int64_t world_id,
+ uint64_t bytes_used,
+ mojom::PerProcessV8MemoryUsageData* data) {
+ mojom::PerFrameV8MemoryUsageData* per_frame_data = nullptr;
+ for (auto& datum : data->associated_memory) {
+ if (datum->frame_token == frame_token.value()) {
+ per_frame_data = datum.get();
+ break;
+ }
+ }
+
+ if (!per_frame_data) {
+ mojom::PerFrameV8MemoryUsageDataPtr datum =
+ mojom::PerFrameV8MemoryUsageData::New();
+ datum->frame_token = frame_token.value();
+ per_frame_data = datum.get();
+ data->associated_memory.push_back(std::move(datum));
+ }
+ ASSERT_FALSE(base::Contains(per_frame_data->associated_bytes, world_id));
+
+ auto isolated_world_usage = mojom::V8IsolatedWorldMemoryUsage::New();
+ isolated_world_usage->bytes_used = bytes_used;
+ per_frame_data->associated_bytes[world_id] = std::move(isolated_world_usage);
+}
+
+} // namespace
+
+class V8PerFrameMemoryDecoratorTest : public GraphTestHarness {
+ public:
+ static constexpr base::TimeDelta kMinTimeBetweenRequests =
+ base::TimeDelta::FromSeconds(30);
+
+ V8PerFrameMemoryDecoratorTest() {
+ internal::SetBindV8PerFrameMemoryReporterCallbackForTesting(
+ &bind_callback_);
+ }
+
+ ~V8PerFrameMemoryDecoratorTest() override {
+ internal::SetBindV8PerFrameMemoryReporterCallbackForTesting(nullptr);
+ }
+
+ void ReplyWithData(
+ mojom::PerProcessV8MemoryUsageDataPtr data,
+ MockV8PerFrameMemoryReporter::GetPerFrameV8MemoryUsageDataCallback
+ callback) {
+ std::move(callback).Run(std::move(data));
+ }
+
+ void DelayedReplyWithData(
+ const base::TimeDelta& delay,
+ mojom::PerProcessV8MemoryUsageDataPtr data,
+ MockV8PerFrameMemoryReporter::GetPerFrameV8MemoryUsageDataCallback
+ callback) {
+ task_env().GetMainThreadTaskRunner()->PostDelayedTask(
+ FROM_HERE, base::BindOnce(std::move(callback), std::move(data)), delay);
+ }
+
+ void ExpectQuery(
+ MockV8PerFrameMemoryReporter* mock_reporter,
+ base::RepeatingCallback<void(
+ MockV8PerFrameMemoryReporter::GetPerFrameV8MemoryUsageDataCallback
+ callback)> responder) {
+ EXPECT_CALL(*mock_reporter, GetPerFrameV8MemoryUsageData(_))
+ .WillOnce([this, responder](
+ MockV8PerFrameMemoryReporter::
+ GetPerFrameV8MemoryUsageDataCallback callback) {
+ this->last_query_time_ = this->task_env().NowTicks();
+ responder.Run(std::move(callback));
+ });
+ }
+
+ void ExpectQueryAndReply(MockV8PerFrameMemoryReporter* mock_reporter,
+ mojom::PerProcessV8MemoryUsageDataPtr data) {
+ ExpectQuery(
+ mock_reporter,
+ base::BindRepeating(&V8PerFrameMemoryDecoratorTest::ReplyWithData,
+ base::Unretained(this), base::Passed(&data)));
+ }
+
+ void ExpectQueryAndDelayReply(MockV8PerFrameMemoryReporter* mock_reporter,
+ const base::TimeDelta& delay,
+ mojom::PerProcessV8MemoryUsageDataPtr data) {
+ ExpectQuery(mock_reporter,
+ base::BindRepeating(
+ &V8PerFrameMemoryDecoratorTest::DelayedReplyWithData,
+ base::Unretained(this), delay, base::Passed(&data)));
+ }
+
+ void ExpectBindAndRespondToQuery(MockV8PerFrameMemoryReporter* mock_reporter,
+ mojom::PerProcessV8MemoryUsageDataPtr data) {
+ // Wrap the move-only |data| in a callback for the expectation below.
+ ExpectQueryAndReply(mock_reporter, std::move(data));
+
+ EXPECT_CALL(*this, BindReceiverWithProxyHost(_, _))
+ .WillOnce([mock_reporter](
+ mojo::PendingReceiver<
+ performance_manager::mojom::V8PerFrameMemoryReporter>
+ pending_receiver,
+ RenderProcessHostProxy proxy) {
+ DCHECK_EQ(kTestProcessID, proxy.render_process_host_id());
+ mock_reporter->Bind(std::move(pending_receiver));
+ });
+ }
+
+ MOCK_METHOD2(BindReceiverWithProxyHost,
+ void(mojo::PendingReceiver<
+ performance_manager::mojom::V8PerFrameMemoryReporter>
+ pending_receiver,
+ RenderProcessHostProxy proxy));
+
+ internal::BindV8PerFrameMemoryReporterCallback bind_callback_ =
+ base::BindRepeating(
+ &V8PerFrameMemoryDecoratorTest::BindReceiverWithProxyHost,
+ base::Unretained(this));
+
+ base::TimeTicks last_query_time_;
+};
+
+class V8PerFrameMemoryDecoratorDeathTest
+ : public V8PerFrameMemoryDecoratorTest {};
+
+class V8PerFrameMemoryRequestTest : public PerformanceManagerTestHarness {};
+
+constexpr base::TimeDelta
+ V8PerFrameMemoryDecoratorTest::kMinTimeBetweenRequests;
+
+TEST_F(V8PerFrameMemoryDecoratorTest, InstantiateOnEmptyGraph) {
+ V8PerFrameMemoryDecorator::MeasurementRequest memory_request(
+ kMinTimeBetweenRequests, graph());
+
+ MockV8PerFrameMemoryReporter mock_reporter;
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = kUnassociatedBytes;
+ ExpectBindAndRespondToQuery(&mock_reporter, std::move(data));
+
+ // Create a process node and validate that it gets a request.
+ auto process = CreateNode<ProcessNodeImpl>(
+ content::PROCESS_TYPE_RENDERER,
+ RenderProcessHostProxy::CreateForTesting(kTestProcessID));
+
+ // Data should not be available until the measurement is taken.
+ EXPECT_FALSE(ProcessData::ForProcessNode(process.get()));
+
+ // Run until idle to make sure the measurement isn't a hard loop.
+ task_env().RunUntilIdle();
+
+ EXPECT_TRUE(ProcessData::ForProcessNode(process.get()));
+ EXPECT_EQ(
+ kUnassociatedBytes,
+ ProcessData::ForProcessNode(process.get())->unassociated_v8_bytes_used());
+}
+
+TEST_F(V8PerFrameMemoryDecoratorTest, InstantiateOnNonEmptyGraph) {
+ // Instantiate the decorator with an existing process node and validate that
+ // it gets a request.
+ auto process = CreateNode<ProcessNodeImpl>(
+ content::PROCESS_TYPE_RENDERER,
+ RenderProcessHostProxy::CreateForTesting(kTestProcessID));
+
+ MockV8PerFrameMemoryReporter mock_reporter;
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = kUnassociatedBytes;
+ ExpectBindAndRespondToQuery(&mock_reporter, std::move(data));
+
+ V8PerFrameMemoryDecorator::MeasurementRequest memory_request(
+ kMinTimeBetweenRequests, graph());
+
+ // Data should not be available until the measurement is taken.
+ EXPECT_FALSE(ProcessData::ForProcessNode(process.get()));
+
+ // Run until idle to make sure the measurement isn't a hard loop.
+ task_env().RunUntilIdle();
+
+ EXPECT_TRUE(ProcessData::ForProcessNode(process.get()));
+ EXPECT_EQ(
+ kUnassociatedBytes,
+ ProcessData::ForProcessNode(process.get())->unassociated_v8_bytes_used());
+}
+
+TEST_F(V8PerFrameMemoryDecoratorTest, OnlyMeasureRenderers) {
+ V8PerFrameMemoryDecorator::MeasurementRequest memory_request(
+ kMinTimeBetweenRequests, graph());
+
+ for (int type = content::PROCESS_TYPE_BROWSER;
+ type < content::PROCESS_TYPE_CONTENT_END; ++type) {
+ if (type == content::PROCESS_TYPE_RENDERER)
+ continue;
+
+ // Instantiate a non-renderer process node and validate that it causes no
+ // bind requests.
+ EXPECT_CALL(*this, BindReceiverWithProxyHost(_, _)).Times(0);
+ auto process = CreateNode<ProcessNodeImpl>(
+ static_cast<content::ProcessType>(type),
+ RenderProcessHostProxy::CreateForTesting(kTestProcessID));
+
+ task_env().RunUntilIdle();
+ testing::Mock::VerifyAndClearExpectations(this);
+ }
+}
+
+TEST_F(V8PerFrameMemoryDecoratorTest, QueryRateIsLimited) {
+ auto process = CreateNode<ProcessNodeImpl>(
+ content::PROCESS_TYPE_RENDERER,
+ RenderProcessHostProxy::CreateForTesting(kTestProcessID));
+
+ MockV8PerFrameMemoryReporter mock_reporter;
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ // Response to request 1.
+ data->unassociated_bytes_used = 1;
+ ExpectBindAndRespondToQuery(&mock_reporter, std::move(data));
+ }
+
+ V8PerFrameMemoryDecorator::MeasurementRequest memory_request(
+ kMinTimeBetweenRequests, graph());
+
+ // Run until idle to make sure the measurement isn't a hard loop.
+ task_env().RunUntilIdle();
+
+ EXPECT_TRUE(ProcessData::ForProcessNode(process.get()));
+ EXPECT_EQ(
+ 1u,
+ ProcessData::ForProcessNode(process.get())->unassociated_v8_bytes_used());
+
+ // There shouldn't be an additional request this soon.
+ task_env().FastForwardBy(kMinTimeBetweenRequests / 2);
+ testing::Mock::VerifyAndClearExpectations(&mock_reporter);
+
+ // Set up another request and capture the callback for later invocation.
+ MockV8PerFrameMemoryReporter::GetPerFrameV8MemoryUsageDataCallback callback;
+ ExpectQuery(
+ &mock_reporter,
+ base::BindLambdaForTesting(
+ [&callback](
+ MockV8PerFrameMemoryReporter::GetPerFrameV8MemoryUsageDataCallback
+ result_callback) { callback = std::move(result_callback); }));
+
+ // Skip forward to when another request should be issued.
+ task_env().FastForwardBy(kMinTimeBetweenRequests);
+ ASSERT_FALSE(callback.is_null());
+
+ // Skip forward a long while, and validate that no additional requests are
+ // issued until the pending request has completed.
+ task_env().FastForwardBy(10 * kMinTimeBetweenRequests);
+ testing::Mock::VerifyAndClearExpectations(&mock_reporter);
+
+ EXPECT_TRUE(ProcessData::ForProcessNode(process.get()));
+ EXPECT_EQ(
+ 1u,
+ ProcessData::ForProcessNode(process.get())->unassociated_v8_bytes_used());
+
+ // Expect another query once completing the query above.
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ // Response to request 3.
+ data->unassociated_bytes_used = 3;
+ ExpectQueryAndReply(&mock_reporter, std::move(data));
+ }
+
+ // Reply to the request above.
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ // Response to request 2.
+ data->unassociated_bytes_used = 2;
+ std::move(callback).Run(std::move(data));
+ }
+
+ task_env().RunUntilIdle();
+
+ // This should have updated all the way to the third response.
+ EXPECT_TRUE(ProcessData::ForProcessNode(process.get()));
+ EXPECT_EQ(
+ 3u,
+ ProcessData::ForProcessNode(process.get())->unassociated_v8_bytes_used());
+
+ // Despite the long delay to respond to request 2, there shouldn't be another
+ // request until kMinTimeBetweenRequests has expired.
+ task_env().FastForwardBy(kMinTimeBetweenRequests / 2);
+ testing::Mock::VerifyAndClearExpectations(&mock_reporter);
+}
+
+TEST_F(V8PerFrameMemoryDecoratorTest, MultipleProcessesHaveDistinctSchedules) {
+ V8PerFrameMemoryDecorator::MeasurementRequest memory_request(
+ kMinTimeBetweenRequests, graph());
+
+ // Create a process node and validate that it gets a request.
+ MockV8PerFrameMemoryReporter reporter1;
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 1;
+ ExpectBindAndRespondToQuery(&reporter1, std::move(data));
+ }
+
+ auto process1 = CreateNode<ProcessNodeImpl>(
+ content::PROCESS_TYPE_RENDERER,
+ RenderProcessHostProxy::CreateForTesting(kTestProcessID));
+
+ task_env().FastForwardBy(kMinTimeBetweenRequests / 4);
+ testing::Mock::VerifyAndClearExpectations(&reporter1);
+
+ // Create a second process node and validate that it gets a request.
+ MockV8PerFrameMemoryReporter reporter2;
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 2;
+ ExpectBindAndRespondToQuery(&reporter2, std::move(data));
+ }
+
+ auto process2 = CreateNode<ProcessNodeImpl>(
+ content::PROCESS_TYPE_RENDERER,
+ RenderProcessHostProxy::CreateForTesting(kTestProcessID));
+
+ task_env().RunUntilIdle();
+ testing::Mock::VerifyAndClearExpectations(&reporter2);
+
+ EXPECT_TRUE(ProcessData::ForProcessNode(process1.get()));
+ EXPECT_EQ(1u, ProcessData::ForProcessNode(process1.get())
+ ->unassociated_v8_bytes_used());
+ EXPECT_TRUE(ProcessData::ForProcessNode(process2.get()));
+ EXPECT_EQ(2u, ProcessData::ForProcessNode(process2.get())
+ ->unassociated_v8_bytes_used());
+
+ // Capture the request time from each process.
+ auto capture_time_lambda =
+ [](base::TimeTicks* request_time,
+ MockV8PerFrameMemoryReporter::GetPerFrameV8MemoryUsageDataCallback
+ callback) {
+ *request_time = base::TimeTicks::Now();
+ std::move(callback).Run(mojom::PerProcessV8MemoryUsageData::New());
+ };
+
+ base::TimeTicks process1_request_time;
+ ExpectQuery(&reporter1,
+ base::BindRepeating(capture_time_lambda,
+ base::Unretained(&process1_request_time)));
+ base::TimeTicks process2_request_time;
+ ExpectQuery(&reporter2,
+ base::BindRepeating(capture_time_lambda,
+ base::Unretained(&process2_request_time)));
+
+ task_env().FastForwardBy(kMinTimeBetweenRequests * 1.25);
+
+ // Check that both processes got polled, and that process2 was polled after
+ // process1.
+ EXPECT_FALSE(process1_request_time.is_null());
+ EXPECT_FALSE(process2_request_time.is_null());
+ EXPECT_GT(process2_request_time, process1_request_time);
+}
+
+TEST_F(V8PerFrameMemoryDecoratorTest, PerFrameDataIsDistributed) {
+ V8PerFrameMemoryDecorator::MeasurementRequest memory_request(
+ kMinTimeBetweenRequests, graph());
+
+ MockV8PerFrameMemoryReporter reporter;
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ // Add data for an unknown frame.
+ AddPerFrameIsolateMemoryUsage(FrameToken(base::UnguessableToken::Create()),
+ 0, 1024u, data.get());
+
+ ExpectBindAndRespondToQuery(&reporter, std::move(data));
+ }
+
+ auto process = CreateNode<ProcessNodeImpl>(
+ content::PROCESS_TYPE_RENDERER,
+ RenderProcessHostProxy::CreateForTesting(kTestProcessID));
+
+ task_env().RunUntilIdle();
+ testing::Mock::VerifyAndClearExpectations(&reporter);
+
+ // Since the frame was unknown, the usage should have accrued to unassociated.
+ EXPECT_TRUE(ProcessData::ForProcessNode(process.get()));
+ EXPECT_EQ(
+ 1024u,
+ ProcessData::ForProcessNode(process.get())->unassociated_v8_bytes_used());
+
+ // Create a couple of frames with specified IDs.
+ auto page = CreateNode<PageNodeImpl>();
+
+ FrameToken frame1_id = FrameToken(base::UnguessableToken::Create());
+ auto frame1 = CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr, 1,
+ 2, frame1_id);
+
+ FrameToken frame2_id = FrameToken(base::UnguessableToken::Create());
+ auto frame2 = CreateNode<FrameNodeImpl>(process.get(), page.get(), nullptr, 3,
+ 4, frame2_id);
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ AddPerFrameIsolateMemoryUsage(frame1_id, 0, 1001u, data.get());
+ AddPerFrameIsolateMemoryUsage(frame2_id, 0, 1002u, data.get());
+ ExpectQueryAndReply(&reporter, std::move(data));
+ }
+
+ task_env().FastForwardBy(kMinTimeBetweenRequests * 1.5);
+ testing::Mock::VerifyAndClearExpectations(&reporter);
+
+ ASSERT_TRUE(FrameData::ForFrameNode(frame1.get()));
+ EXPECT_EQ(1001u, FrameData::ForFrameNode(frame1.get())->v8_bytes_used());
+ ASSERT_TRUE(FrameData::ForFrameNode(frame2.get()));
+ EXPECT_EQ(1002u, FrameData::ForFrameNode(frame2.get())->v8_bytes_used());
+
+ // Now verify that data is cleared for any frame that doesn't get an update,
+ // plus verify that unknown frame data toes to unassociated bytes.
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ AddPerFrameIsolateMemoryUsage(frame1_id, 0, 1003u, data.get());
+ AddPerFrameIsolateMemoryUsage(FrameToken(base::UnguessableToken::Create()),
+ 0, 2233u, data.get());
+ ExpectQueryAndReply(&reporter, std::move(data));
+ }
+ task_env().FastForwardBy(kMinTimeBetweenRequests);
+ testing::Mock::VerifyAndClearExpectations(&reporter);
+
+ ASSERT_TRUE(FrameData::ForFrameNode(frame1.get()));
+ EXPECT_EQ(1003u, FrameData::ForFrameNode(frame1.get())->v8_bytes_used());
+ EXPECT_FALSE(FrameData::ForFrameNode(frame2.get()));
+ ASSERT_TRUE(ProcessData::ForProcessNode(process.get()));
+ EXPECT_EQ(
+ 2233u,
+ ProcessData::ForProcessNode(process.get())->unassociated_v8_bytes_used());
+}
+
+TEST_F(V8PerFrameMemoryDecoratorTest, MeasurementRequestsSorted) {
+ // Create some queries with different sample frequencies.
+ constexpr base::TimeDelta kShortInterval(kMinTimeBetweenRequests);
+ constexpr base::TimeDelta kMediumInterval(2 * kMinTimeBetweenRequests);
+ constexpr base::TimeDelta kLongInterval(3 * kMinTimeBetweenRequests);
+
+ // Create longer requests first to be sure they sort correctly.
+ auto medium_memory_request =
+ std::make_unique<V8PerFrameMemoryDecorator::MeasurementRequest>(
+ kMediumInterval, graph());
+
+ auto short_memory_request =
+ std::make_unique<V8PerFrameMemoryDecorator::MeasurementRequest>(
+ kShortInterval, graph());
+
+ auto long_memory_request =
+ std::make_unique<V8PerFrameMemoryDecorator::MeasurementRequest>(
+ kLongInterval, graph());
+
+ auto* decorator = V8PerFrameMemoryDecorator::GetFromGraph(graph());
+ ASSERT_TRUE(decorator);
+
+ // A single measurement should be taken immediately regardless of the overall
+ // frequency.
+ MockV8PerFrameMemoryReporter mock_reporter;
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 1U;
+ ExpectBindAndRespondToQuery(&mock_reporter, std::move(data));
+ }
+
+ auto process = CreateNode<ProcessNodeImpl>(
+ content::PROCESS_TYPE_RENDERER,
+ RenderProcessHostProxy::CreateForTesting(kTestProcessID));
+ EXPECT_FALSE(ProcessData::ForProcessNode(process.get()));
+
+ task_env().FastForwardBy(base::TimeDelta::FromSeconds(1));
+ // All the following FastForwardBy calls will place the clock 1 sec after a
+ // measurement is expected.
+
+ ASSERT_TRUE(ProcessData::ForProcessNode(process.get()));
+ EXPECT_EQ(
+ 1U,
+ ProcessData::ForProcessNode(process.get())->unassociated_v8_bytes_used());
+
+ // Another measurement should be taken after the shortest interval.
+ EXPECT_EQ(kShortInterval, decorator->GetMinTimeBetweenRequestsPerProcess());
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 2U;
+ ExpectQueryAndReply(&mock_reporter, std::move(data));
+
+ task_env().FastForwardBy(kShortInterval);
+ EXPECT_EQ(2U, ProcessData::ForProcessNode(process.get())
+ ->unassociated_v8_bytes_used());
+ }
+
+ // Remove the shortest request. Now a measurement should be taken after the
+ // medium interval, which is twice the short interval.
+ short_memory_request.reset();
+ EXPECT_EQ(kMediumInterval, decorator->GetMinTimeBetweenRequestsPerProcess());
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 3U;
+ ExpectQueryAndReply(&mock_reporter, std::move(data));
+
+ task_env().FastForwardBy(kShortInterval);
+ EXPECT_EQ(2U, ProcessData::ForProcessNode(process.get())
+ ->unassociated_v8_bytes_used());
+ task_env().FastForwardBy(kShortInterval);
+ EXPECT_EQ(3U, ProcessData::ForProcessNode(process.get())
+ ->unassociated_v8_bytes_used());
+ }
+
+ // Remove the longest request. A measurement should still be taken after the
+ // medium interval.
+ long_memory_request.reset();
+ EXPECT_EQ(kMediumInterval, decorator->GetMinTimeBetweenRequestsPerProcess());
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 4U;
+ ExpectQueryAndReply(&mock_reporter, std::move(data));
+
+ task_env().FastForwardBy(kMediumInterval);
+ EXPECT_EQ(4U, ProcessData::ForProcessNode(process.get())
+ ->unassociated_v8_bytes_used());
+ }
+
+ // Remove the medium request, making the queue empty.
+ medium_memory_request.reset();
+ EXPECT_TRUE(decorator->GetMinTimeBetweenRequestsPerProcess().is_zero());
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 5U;
+ ExpectQueryAndReply(&mock_reporter, std::move(data));
+
+ task_env().FastForwardBy(kLongInterval);
+ EXPECT_EQ(4U, ProcessData::ForProcessNode(process.get())
+ ->unassociated_v8_bytes_used());
+ }
+
+ // Create another request. Since this is the first request in an empty queue
+ // the measurement should be taken immediately.
+ long_memory_request =
+ std::make_unique<V8PerFrameMemoryDecorator::MeasurementRequest>(
+ kLongInterval, graph());
+ EXPECT_EQ(kLongInterval, decorator->GetMinTimeBetweenRequestsPerProcess());
+
+ task_env().FastForwardBy(base::TimeDelta::FromSeconds(1));
+ EXPECT_EQ(
+ 5U,
+ ProcessData::ForProcessNode(process.get())->unassociated_v8_bytes_used());
+
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 6U;
+ ExpectQueryAndReply(&mock_reporter, std::move(data));
+
+ task_env().FastForwardBy(kLongInterval);
+ EXPECT_EQ(6U, ProcessData::ForProcessNode(process.get())
+ ->unassociated_v8_bytes_used());
+ }
+
+ // Now there should be kLongInterval - 1 sec until the next measurement.
+ // Make sure a shorter request replaces this (the new interval should cause a
+ // measurement and the old interval should not).
+ medium_memory_request =
+ std::make_unique<V8PerFrameMemoryDecorator::MeasurementRequest>(
+ kMediumInterval, graph());
+ EXPECT_EQ(kMediumInterval, decorator->GetMinTimeBetweenRequestsPerProcess());
+
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 7U;
+ ExpectQueryAndReply(&mock_reporter, std::move(data));
+
+ task_env().FastForwardBy(kMediumInterval);
+ EXPECT_EQ(7U, ProcessData::ForProcessNode(process.get())
+ ->unassociated_v8_bytes_used());
+ }
+
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 8U;
+ ExpectQueryAndReply(&mock_reporter, std::move(data));
+
+ constexpr base::TimeDelta kRestOfLongInterval =
+ kLongInterval - kMediumInterval;
+ task_env().FastForwardBy(kRestOfLongInterval);
+ EXPECT_EQ(7U, ProcessData::ForProcessNode(process.get())
+ ->unassociated_v8_bytes_used());
+
+ task_env().FastForwardBy(kMediumInterval - kRestOfLongInterval);
+ EXPECT_EQ(8U, ProcessData::ForProcessNode(process.get())
+ ->unassociated_v8_bytes_used());
+ }
+
+ // Remove the medium request and add it back. The measurement interval should
+ // not change.
+ medium_memory_request.reset();
+ EXPECT_EQ(kLongInterval, decorator->GetMinTimeBetweenRequestsPerProcess());
+ medium_memory_request =
+ std::make_unique<V8PerFrameMemoryDecorator::MeasurementRequest>(
+ kMediumInterval, graph());
+ EXPECT_EQ(kMediumInterval, decorator->GetMinTimeBetweenRequestsPerProcess());
+
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 9U;
+ ExpectQueryAndReply(&mock_reporter, std::move(data));
+
+ task_env().FastForwardBy(kMediumInterval);
+ EXPECT_EQ(9U, ProcessData::ForProcessNode(process.get())
+ ->unassociated_v8_bytes_used());
+ }
+
+ // Add another long request. There should still be requests after the medium
+ // interval.
+ auto long_memory_request2 =
+ std::make_unique<V8PerFrameMemoryDecorator::MeasurementRequest>(
+ kLongInterval, graph());
+ EXPECT_EQ(kMediumInterval, decorator->GetMinTimeBetweenRequestsPerProcess());
+
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 10U;
+ ExpectQueryAndReply(&mock_reporter, std::move(data));
+
+ task_env().FastForwardBy(kMediumInterval);
+ EXPECT_EQ(10U, ProcessData::ForProcessNode(process.get())
+ ->unassociated_v8_bytes_used());
+ }
+
+ // Remove the medium request. Now there are 2 requests which should cause
+ // measurements at the same interval. Make sure only 1 measurement is taken.
+ medium_memory_request.reset();
+ EXPECT_EQ(kLongInterval, decorator->GetMinTimeBetweenRequestsPerProcess());
+
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 11U;
+ ExpectQueryAndReply(&mock_reporter, std::move(data));
+
+ task_env().FastForwardBy(kLongInterval);
+ EXPECT_EQ(11U, ProcessData::ForProcessNode(process.get())
+ ->unassociated_v8_bytes_used());
+ }
+
+ // Remove 1 of the 2 long requests. Measurements should not change.
+ long_memory_request2.reset();
+ EXPECT_EQ(kLongInterval, decorator->GetMinTimeBetweenRequestsPerProcess());
+
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 12U;
+ ExpectQueryAndReply(&mock_reporter, std::move(data));
+
+ task_env().FastForwardBy(kLongInterval);
+ EXPECT_EQ(12U, ProcessData::ForProcessNode(process.get())
+ ->unassociated_v8_bytes_used());
+ }
+}
+
+TEST_F(V8PerFrameMemoryDecoratorTest, MeasurementRequestsWithDelay) {
+ // Create some queries with different sample frequencies.
+ constexpr base::TimeDelta kShortInterval(kMinTimeBetweenRequests);
+ constexpr base::TimeDelta kMediumInterval(2 * kMinTimeBetweenRequests);
+ constexpr base::TimeDelta kLongInterval(3 * kMinTimeBetweenRequests);
+
+ // Make measurements take long enough that a second request could be sent.
+ constexpr base::TimeDelta kMeasurementLength(1.5 * kShortInterval);
+ constexpr base::TimeDelta kOneSecond = base::TimeDelta::FromSeconds(1);
+
+ auto long_memory_request =
+ std::make_unique<V8PerFrameMemoryDecorator::MeasurementRequest>(
+ kLongInterval, graph());
+
+ auto* decorator = V8PerFrameMemoryDecorator::GetFromGraph(graph());
+ ASSERT_TRUE(decorator);
+
+ // Move past the first request since it's complicated to untangle the Bind
+ // and QueryAndDelayReply expectations.
+ MockV8PerFrameMemoryReporter mock_reporter;
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 0U;
+ ExpectBindAndRespondToQuery(&mock_reporter, std::move(data));
+ }
+ auto process = CreateNode<ProcessNodeImpl>(
+ content::PROCESS_TYPE_RENDERER,
+ RenderProcessHostProxy::CreateForTesting(kTestProcessID));
+ task_env().FastForwardBy(kOneSecond);
+ // All the following FastForwardBy calls will place the clock 1 sec after a
+ // measurement is expected.
+
+ // Advance to the middle of a measurement and create a new request. Should
+ // update GetMinTimeBetweenRequestsPerProcess but not start a new
+ // measurement until the existing measurement finishes.
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 1U;
+ ExpectQueryAndDelayReply(&mock_reporter, kMeasurementLength,
+ std::move(data));
+ }
+ task_env().FastForwardBy(kLongInterval);
+ EXPECT_EQ(last_query_time_, task_env().NowTicks() - kOneSecond)
+ << "Measurement didn't start when expected";
+ EXPECT_EQ(
+ 0U,
+ ProcessData::ForProcessNode(process.get())->unassociated_v8_bytes_used())
+ << "Measurement ended early";
+ base::TimeTicks measurement_start_time = last_query_time_;
+
+ auto medium_memory_request =
+ std::make_unique<V8PerFrameMemoryDecorator::MeasurementRequest>(
+ kMediumInterval, graph());
+ EXPECT_EQ(kMediumInterval, decorator->GetMinTimeBetweenRequestsPerProcess());
+ task_env().FastForwardBy(kMeasurementLength);
+ ASSERT_EQ(
+ 1U,
+ ProcessData::ForProcessNode(process.get())->unassociated_v8_bytes_used())
+ << "Measurement didn't end when expected";
+ EXPECT_EQ(last_query_time_, measurement_start_time);
+
+ // Next measurement should start kMediumInterval secs after the START of the
+ // last measurement.
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 2U;
+ ExpectQueryAndDelayReply(&mock_reporter, kMeasurementLength,
+ std::move(data));
+ }
+ task_env().FastForwardBy(kMediumInterval - kMeasurementLength);
+ EXPECT_EQ(last_query_time_, task_env().NowTicks() - kOneSecond)
+ << "Measurement didn't start when expected";
+ EXPECT_EQ(
+ 1U,
+ ProcessData::ForProcessNode(process.get())->unassociated_v8_bytes_used())
+ << "Measurement ended early";
+ measurement_start_time = last_query_time_;
+
+ task_env().FastForwardBy(kMeasurementLength);
+ EXPECT_EQ(
+ 2U,
+ ProcessData::ForProcessNode(process.get())->unassociated_v8_bytes_used())
+ << "Measurement didn't end when expected";
+ EXPECT_EQ(last_query_time_, measurement_start_time);
+
+ // Create a request that would be sent in the middle of a measurement. It
+ // should start immediately after the measurement finishes.
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 3U;
+ ExpectQueryAndDelayReply(&mock_reporter, kMeasurementLength,
+ std::move(data));
+ }
+ task_env().FastForwardBy(kMediumInterval - kMeasurementLength);
+ EXPECT_EQ(last_query_time_, task_env().NowTicks() - kOneSecond)
+ << "Measurement didn't start when expected";
+ EXPECT_EQ(
+ 2U,
+ ProcessData::ForProcessNode(process.get())->unassociated_v8_bytes_used())
+ << "Measurement ended early";
+ measurement_start_time = last_query_time_;
+
+ auto short_memory_request =
+ std::make_unique<V8PerFrameMemoryDecorator::MeasurementRequest>(
+ kShortInterval, graph());
+ EXPECT_EQ(kShortInterval, decorator->GetMinTimeBetweenRequestsPerProcess());
+ EXPECT_EQ(last_query_time_, measurement_start_time);
+
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 4U;
+ ExpectQueryAndDelayReply(&mock_reporter, kMeasurementLength,
+ std::move(data));
+ }
+ task_env().FastForwardBy(kMeasurementLength);
+ EXPECT_EQ(last_query_time_, task_env().NowTicks() - kOneSecond)
+ << "Measurement didn't start when expected";
+ EXPECT_EQ(
+ 3U,
+ ProcessData::ForProcessNode(process.get())->unassociated_v8_bytes_used())
+ << "Measurement ended early";
+ measurement_start_time = last_query_time_;
+
+ // Delete the short request. Should update
+ // GetMinTimeBetweenRequestsPerProcess but not start a new measurement
+ // until the existing measurement finishes.
+ short_memory_request.reset();
+ EXPECT_EQ(kMediumInterval, decorator->GetMinTimeBetweenRequestsPerProcess());
+ task_env().FastForwardBy(kMeasurementLength);
+ EXPECT_EQ(
+ 4U,
+ ProcessData::ForProcessNode(process.get())->unassociated_v8_bytes_used())
+ << "Measurement didn't end when expected";
+ EXPECT_EQ(last_query_time_, measurement_start_time);
+
+ // Delete the last request while a measurement is in process. The
+ // measurement should finish successfully but no more should be sent.
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 5U;
+ ExpectQueryAndDelayReply(&mock_reporter, kMeasurementLength,
+ std::move(data));
+ }
+ task_env().FastForwardBy(kMediumInterval - kMeasurementLength);
+ EXPECT_EQ(last_query_time_, task_env().NowTicks() - kOneSecond)
+ << "Measurement didn't start when expected";
+ EXPECT_EQ(
+ 4U,
+ ProcessData::ForProcessNode(process.get())->unassociated_v8_bytes_used())
+ << "Measurement ended early";
+ measurement_start_time = last_query_time_;
+
+ medium_memory_request.reset();
+ long_memory_request.reset();
+ EXPECT_TRUE(decorator->GetMinTimeBetweenRequestsPerProcess().is_zero());
+ task_env().FastForwardBy(kMeasurementLength);
+ EXPECT_EQ(
+ 5U,
+ ProcessData::ForProcessNode(process.get())->unassociated_v8_bytes_used())
+ << "Measurement didn't end when expected";
+ EXPECT_EQ(last_query_time_, measurement_start_time);
+
+ // No more requests should be sent.
+ testing::Mock::VerifyAndClearExpectations(this);
+ task_env().FastForwardBy(kLongInterval);
+}
+
+TEST_F(V8PerFrameMemoryDecoratorTest, MeasurementRequestOutlivesDecorator) {
+ V8PerFrameMemoryDecorator::MeasurementRequest memory_request(
+ kMinTimeBetweenRequests, graph());
+
+ auto* decorator = V8PerFrameMemoryDecorator::GetFromGraph(graph());
+ ASSERT_TRUE(decorator);
+
+ MockV8PerFrameMemoryReporter mock_reporter;
+ {
+ auto data = mojom::PerProcessV8MemoryUsageData::New();
+ data->unassociated_bytes_used = 1U;
+ ExpectBindAndRespondToQuery(&mock_reporter, std::move(data));
+ }
+ auto process = CreateNode<ProcessNodeImpl>(
+ content::PROCESS_TYPE_RENDERER,
+ RenderProcessHostProxy::CreateForTesting(kTestProcessID));
+ task_env().FastForwardBy(base::TimeDelta::FromSeconds(1));
+ ASSERT_EQ(
+ 1U,
+ ProcessData::ForProcessNode(process.get())->unassociated_v8_bytes_used())
+ << "First measurement didn't happen when expected";
+
+ graph()->TakeFromGraph(decorator);
+
+ // No request should be sent, and the decorator destructor should not DCHECK.
+ testing::Mock::VerifyAndClearExpectations(this);
+ task_env().FastForwardBy(kMinTimeBetweenRequests);
+}
+
+TEST_F(V8PerFrameMemoryDecoratorDeathTest,
+ MeasurementRequestMultipleStartMeasurement) {
+ EXPECT_DCHECK_DEATH({
+ V8PerFrameMemoryDecorator::MeasurementRequest request(
+ kMinTimeBetweenRequests);
+ request.StartMeasurement(graph());
+ request.StartMeasurement(graph());
+ });
+
+ EXPECT_DCHECK_DEATH({
+ V8PerFrameMemoryDecorator::MeasurementRequest request(
+ kMinTimeBetweenRequests, graph());
+ request.StartMeasurement(graph());
+ });
+}
+
+TEST_F(V8PerFrameMemoryRequestTest, RequestIsSequenceSafe) {
+ base::RunLoop run_loop;
+
+ // Precondition: CallOnGraph must run on a different sequence. Note that all
+ // tasks passed to CallOnGraph will only run when run_loop.Run() is called
+ // below.
+ ASSERT_TRUE(task_environment()
+ ->GetMainThreadTaskRunner()
+ ->RunsTasksInCurrentSequence());
+ PerformanceManager::CallOnGraph(
+ FROM_HERE, base::BindLambdaForTesting([this] {
+ EXPECT_FALSE(this->task_environment()
+ ->GetMainThreadTaskRunner()
+ ->RunsTasksInCurrentSequence());
+ }));
+
+ // Decorator should not exist before creating a request.
+ PerformanceManager::CallOnGraph(
+ FROM_HERE, base::BindOnce([](Graph* graph) {
+ EXPECT_FALSE(V8PerFrameMemoryDecorator::GetFromGraph(graph));
+ }));
+
+ // This object is created on the main sequence but should cause a
+ // MeasurementRequest to be created on the graph sequence after the above
+ // task.
+ auto request = std::make_unique<V8PerFrameMemoryRequest>(
+ V8PerFrameMemoryDecoratorTest::kMinTimeBetweenRequests);
+
+ // Decorator now exists and has the request frequency set, proving that the
+ // MeasurementRequest was created.
+ PerformanceManager::CallOnGraph(
+ FROM_HERE, base::BindOnce([](Graph* graph) {
+ auto* decorator = V8PerFrameMemoryDecorator::GetFromGraph(graph);
+ ASSERT_TRUE(decorator);
+ EXPECT_EQ(V8PerFrameMemoryDecoratorTest::kMinTimeBetweenRequests,
+ decorator->GetMinTimeBetweenRequestsPerProcess());
+ }));
+
+ // Destroying the object on the main sequence should cause the wrapped
+ // MeasurementRequest to be destroyed on the graph sequence after the above
+ // tasks.
+ request.reset();
+
+ // Request frequency is reset, proving the MeasurementRequest was destroyed.
+ PerformanceManager::CallOnGraph(
+ FROM_HERE, base::BindOnce([](Graph* graph) {
+ auto* decorator = V8PerFrameMemoryDecorator::GetFromGraph(graph);
+ ASSERT_TRUE(decorator);
+ EXPECT_TRUE(decorator->GetMinTimeBetweenRequestsPerProcess().is_zero());
+ }));
+
+ // Now execute all the queued CallOnGraph tasks and exit.
+ PerformanceManager::CallOnGraph(FROM_HERE, run_loop.QuitClosure());
+ run_loop.Run();
+}
+
+} // namespace performance_manager
diff --git a/chromium/components/performance_manager/embedder/performance_manager_registry.h b/chromium/components/performance_manager/embedder/performance_manager_registry.h
index 61b02aec71e..65d8250cbb0 100644
--- a/chromium/components/performance_manager/embedder/performance_manager_registry.h
+++ b/chromium/components/performance_manager/embedder/performance_manager_registry.h
@@ -51,14 +51,13 @@ class PerformanceManagerRegistry {
// process, or nullptr if there is none.
static PerformanceManagerRegistry* GetInstance();
+ // Helper function that invokes CreatePageNodeForWebContents only if it hasn't
+ // already been called for the provided WebContents.
+ void MaybeCreatePageNodeForWebContents(content::WebContents* web_contents);
+
// Must be invoked when a WebContents is created. Creates an associated
- // PageNode in the PerformanceManager, if it doesn't already exist.
- //
- // Note: As of December 2019, this is called by the constructor of
- // DevtoolsWindow on its main WebContents. It may be called again for the same
- // WebContents by TabHelpers::AttachTabHelpers() when Devtools is docked.
- // Hence the support for calling CreatePageNodeForWebContents() for a
- // WebContents that already has a PageNode.
+ // PageNode in the PerformanceManager, if it doesn't already exist. This
+ // should only be called once for a given |web_contents|.
virtual void CreatePageNodeForWebContents(
content::WebContents* web_contents) = 0;
diff --git a/chromium/components/performance_manager/features.cc b/chromium/components/performance_manager/features.cc
index 057d2fb49d3..a5836952e93 100644
--- a/chromium/components/performance_manager/features.cc
+++ b/chromium/components/performance_manager/features.cc
@@ -24,6 +24,9 @@ const base::FeatureParam<int> kMinimumThrottleTimeoutMilliseconds = {
const base::FeatureParam<int> kMaximumThrottleTimeoutMilliseconds = {
&kTabLoadingFrameNavigationThrottles, "MaximumThrottleTimeoutMilliseconds",
40000};
+// This defaults to 3 since 3 * 99th%ile FCP ~= 99th%ile LCP.
+const base::FeatureParam<double> kFCPMultiple = {
+ &kTabLoadingFrameNavigationThrottles, "FCPMultiple", 3.0};
TabLoadingFrameNavigationThrottlesParams::
TabLoadingFrameNavigationThrottlesParams() = default;
@@ -39,6 +42,7 @@ TabLoadingFrameNavigationThrottlesParams::GetParams() {
kMinimumThrottleTimeoutMilliseconds.Get());
params.maximum_throttle_timeout = base::TimeDelta::FromMilliseconds(
kMaximumThrottleTimeoutMilliseconds.Get());
+ params.fcp_multiple = kFCPMultiple.Get();
return params;
}
diff --git a/chromium/components/performance_manager/graph/frame_node_impl.cc b/chromium/components/performance_manager/graph/frame_node_impl.cc
index bd2d47c950e..d09bcce5831 100644
--- a/chromium/components/performance_manager/graph/frame_node_impl.cc
+++ b/chromium/components/performance_manager/graph/frame_node_impl.cc
@@ -26,7 +26,7 @@ FrameNodeImpl::FrameNodeImpl(ProcessNodeImpl* process_node,
FrameNodeImpl* parent_frame_node,
int frame_tree_node_id,
int render_frame_id,
- const base::UnguessableToken& dev_tools_token,
+ const FrameToken& frame_token,
int32_t browsing_instance_id,
int32_t site_instance_id)
: parent_frame_node_(parent_frame_node),
@@ -34,7 +34,7 @@ FrameNodeImpl::FrameNodeImpl(ProcessNodeImpl* process_node,
process_node_(process_node),
frame_tree_node_id_(frame_tree_node_id),
render_frame_id_(render_frame_id),
- dev_tools_token_(dev_tools_token),
+ frame_token_(frame_token),
browsing_instance_id_(browsing_instance_id),
site_instance_id_(site_instance_id),
render_frame_host_proxy_(content::GlobalFrameRoutingId(
@@ -49,6 +49,7 @@ FrameNodeImpl::FrameNodeImpl(ProcessNodeImpl* process_node,
FrameNodeImpl::~FrameNodeImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(child_worker_nodes_.empty());
+ DCHECK(opened_page_nodes_.empty());
}
void FrameNodeImpl::Bind(
@@ -135,8 +136,8 @@ int FrameNodeImpl::render_frame_id() const {
return render_frame_id_;
}
-const base::UnguessableToken& FrameNodeImpl::dev_tools_token() const {
- return dev_tools_token_;
+const FrameToken& FrameNodeImpl::frame_token() const {
+ return frame_token_;
}
int32_t FrameNodeImpl::browsing_instance_id() const {
@@ -156,6 +157,11 @@ const base::flat_set<FrameNodeImpl*>& FrameNodeImpl::child_frame_nodes() const {
return child_frame_nodes_;
}
+const base::flat_set<PageNodeImpl*>& FrameNodeImpl::opened_page_nodes() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return opened_page_nodes_;
+}
+
mojom::LifecycleState FrameNodeImpl::lifecycle_state() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return lifecycle_state_.value();
@@ -208,6 +214,7 @@ const base::flat_set<WorkerNodeImpl*>& FrameNodeImpl::child_worker_nodes()
}
const PriorityAndReason& FrameNodeImpl::priority_and_reason() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return priority_and_reason_.value();
}
@@ -216,6 +223,11 @@ bool FrameNodeImpl::had_form_interaction() const {
return document_.had_form_interaction.value();
}
+bool FrameNodeImpl::is_audible() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return is_audible_.value();
+}
+
void FrameNodeImpl::SetIsCurrent(bool is_current) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
is_current_.SetAndMaybeNotify(this, is_current);
@@ -255,6 +267,12 @@ void FrameNodeImpl::SetIsHoldingIndexedDBLock(bool is_holding_indexeddb_lock) {
is_holding_indexeddb_lock_.SetAndMaybeNotify(this, is_holding_indexeddb_lock);
}
+void FrameNodeImpl::SetIsAudible(bool is_audible) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_NE(is_audible, is_audible_.value());
+ is_audible_.SetAndMaybeNotify(this, is_audible);
+}
+
void FrameNodeImpl::OnNavigationCommitted(const GURL& url, bool same_document) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -309,6 +327,28 @@ void FrameNodeImpl::SetPriorityAndReason(
priority_and_reason_.SetAndMaybeNotify(this, priority_and_reason);
}
+void FrameNodeImpl::AddOpenedPage(util::PassKey<PageNodeImpl>,
+ PageNodeImpl* page_node) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(page_node);
+ DCHECK_NE(page_node_, page_node);
+ DCHECK(graph()->NodeInGraph(page_node));
+ DCHECK_EQ(this, page_node->opener_frame_node());
+ bool inserted = opened_page_nodes_.insert(page_node).second;
+ DCHECK(inserted);
+}
+
+void FrameNodeImpl::RemoveOpenedPage(util::PassKey<PageNodeImpl>,
+ PageNodeImpl* page_node) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(page_node);
+ DCHECK_NE(page_node_, page_node);
+ DCHECK(graph()->NodeInGraph(page_node));
+ DCHECK_EQ(this, page_node->opener_frame_node());
+ size_t removed = opened_page_nodes_.erase(page_node);
+ DCHECK_EQ(1u, removed);
+}
+
const FrameNode* FrameNodeImpl::GetParentFrameNode() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return parent_frame_node();
@@ -329,9 +369,9 @@ int FrameNodeImpl::GetFrameTreeNodeId() const {
return frame_tree_node_id();
}
-const base::UnguessableToken& FrameNodeImpl::GetDevToolsToken() const {
+const FrameToken& FrameNodeImpl::GetFrameToken() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- return dev_tools_token();
+ return frame_token();
}
int32_t FrameNodeImpl::GetBrowsingInstanceId() const {
@@ -365,6 +405,26 @@ const base::flat_set<const FrameNode*> FrameNodeImpl::GetChildFrameNodes()
return children;
}
+bool FrameNodeImpl::VisitOpenedPageNodes(const PageNodeVisitor& visitor) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ for (auto* page_impl : opened_page_nodes()) {
+ const PageNode* page = page_impl;
+ if (!visitor.Run(page))
+ return false;
+ }
+ return true;
+}
+
+const base::flat_set<const PageNode*> FrameNodeImpl::GetOpenedPageNodes()
+ const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ base::flat_set<const PageNode*> opened;
+ for (auto* page : opened_page_nodes())
+ opened.insert(static_cast<const PageNode*>(page));
+ DCHECK_EQ(opened.size(), opened_page_nodes().size());
+ return opened;
+}
+
FrameNodeImpl::LifecycleState FrameNodeImpl::GetLifecycleState() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return lifecycle_state();
@@ -431,6 +491,11 @@ bool FrameNodeImpl::HadFormInteraction() const {
return had_form_interaction();
}
+bool FrameNodeImpl::IsAudible() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return is_audible();
+}
+
void FrameNodeImpl::AddChildFrame(FrameNodeImpl* child_frame_node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(child_frame_node);
@@ -474,6 +539,9 @@ void FrameNodeImpl::OnBeforeLeavingGraph() {
DCHECK(child_frame_nodes_.empty());
+ // Sever opener relationships.
+ SeverOpenedPagesAndMaybeReparent();
+
// Leave the page.
DCHECK(graph()->NodeInGraph(page_node_));
page_node_->RemoveFrame(this);
@@ -493,6 +561,42 @@ void FrameNodeImpl::OnBeforeLeavingGraph() {
render_frame_id_, this);
}
+void FrameNodeImpl::SeverOpenedPagesAndMaybeReparent() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // Copy |opened_page_nodes_| as we'll be modifying it in this loop: when we
+ // call PageNodeImpl::(Set|Clear)OpenerFrameNodeAndOpenedType() this will call
+ // back into this frame node and call RemoveOpenedPage().
+ base::flat_set<PageNodeImpl*> opened_nodes = opened_page_nodes_;
+ for (auto* opened_node : opened_nodes) {
+ auto opened_type = opened_node->opened_type();
+
+ // Reparent opened pages to this frame's parent to maintain the relationship
+ // between the frame trees for bookkeeping. For the relationship to be
+ // finally severed one of the frame trees must completely disappear, or it
+ // must be explicitly severed (this can happen with portals).
+ if (parent_frame_node_) {
+ opened_node->SetOpenerFrameNodeAndOpenedType(parent_frame_node_,
+ opened_type);
+ } else {
+ // There's no new parent, so simply clear the opener.
+ opened_node->ClearOpenerFrameNodeAndOpenedType();
+ }
+ }
+
+ // Expect each page node to have called RemoveOpenedPage(), and for this to
+ // now be empty.
+ DCHECK(opened_page_nodes_.empty());
+}
+
+FrameNodeImpl* FrameNodeImpl::GetFrameTreeRoot() const {
+ FrameNodeImpl* root = const_cast<FrameNodeImpl*>(this);
+ while (root->parent_frame_node())
+ root = parent_frame_node();
+ DCHECK_NE(nullptr, root);
+ return root;
+}
+
bool FrameNodeImpl::HasFrameNodeInAncestors(FrameNodeImpl* frame_node) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (parent_frame_node_ == frame_node ||
@@ -513,6 +617,11 @@ bool FrameNodeImpl::HasFrameNodeInDescendants(FrameNodeImpl* frame_node) const {
return false;
}
+bool FrameNodeImpl::HasFrameNodeInTree(FrameNodeImpl* frame_node) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return GetFrameTreeRoot() == frame_node->GetFrameTreeRoot();
+}
+
FrameNodeImpl::DocumentProperties::DocumentProperties() = default;
FrameNodeImpl::DocumentProperties::~DocumentProperties() = default;
diff --git a/chromium/components/performance_manager/graph/frame_node_impl.h b/chromium/components/performance_manager/graph/frame_node_impl.h
index a568de6b5c1..bcc31d55609 100644
--- a/chromium/components/performance_manager/graph/frame_node_impl.h
+++ b/chromium/components/performance_manager/graph/frame_node_impl.h
@@ -11,6 +11,7 @@
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/unguessable_token.h"
+#include "base/util/type_safety/pass_key.h"
#include "components/performance_manager/graph/node_base.h"
#include "components/performance_manager/public/graph/frame_node.h"
#include "components/performance_manager/public/render_frame_host_proxy.h"
@@ -64,7 +65,7 @@ class FrameNodeImpl
FrameNodeImpl* parent_frame_node,
int frame_tree_node_id,
int render_frame_id,
- const base::UnguessableToken& dev_tools_token,
+ const FrameToken& frame_token,
int32_t browsing_instance_id,
int32_t site_instance_id);
~FrameNodeImpl() override;
@@ -92,13 +93,14 @@ class FrameNodeImpl
ProcessNodeImpl* process_node() const;
int frame_tree_node_id() const;
int render_frame_id() const;
- const base::UnguessableToken& dev_tools_token() const;
+ const FrameToken& frame_token() const;
int32_t browsing_instance_id() const;
int32_t site_instance_id() const;
const RenderFrameHostProxy& render_frame_host_proxy() const;
// Getters for non-const properties. These are not thread safe.
const base::flat_set<FrameNodeImpl*>& child_frame_nodes() const;
+ const base::flat_set<PageNodeImpl*>& opened_page_nodes() const;
LifecycleState lifecycle_state() const;
InterventionPolicy origin_trial_freeze_policy() const;
bool has_nonempty_beforeunload() const;
@@ -111,11 +113,13 @@ class FrameNodeImpl
const base::flat_set<WorkerNodeImpl*>& child_worker_nodes() const;
const PriorityAndReason& priority_and_reason() const;
bool had_form_interaction() const;
+ bool is_audible() const;
// Setters are not thread safe.
void SetIsCurrent(bool is_current);
void SetIsHoldingWebLock(bool is_holding_weblock);
void SetIsHoldingIndexedDBLock(bool is_holding_indexeddb_lock);
+ void SetIsAudible(bool is_audible);
// Invoked when a navigation is committed in the frame.
void OnNavigationCommitted(const GURL& url, bool same_document);
@@ -131,10 +135,22 @@ class FrameNodeImpl
return weak_factory_.GetWeakPtr();
}
+ void SeverOpenedPagesAndMaybeReparentForTesting() {
+ SeverOpenedPagesAndMaybeReparent();
+ }
+
+ protected:
+ friend class PageNodeImpl;
+
+ // Invoked by opened pages when this frame is set/cleared as their opener.
+ // See PageNodeImpl::(Set|Clear)OpenerFrameNodeAndOpenedType.
+ void AddOpenedPage(util::PassKey<PageNodeImpl> key, PageNodeImpl* page_node);
+ void RemoveOpenedPage(util::PassKey<PageNodeImpl> key,
+ PageNodeImpl* page_node);
+
private:
friend class FrameNodeImplDescriber;
friend class FramePriorityAccess;
- friend class PageNodeImpl;
friend class ProcessNodeImpl;
// Rest of FrameNode implementation. These are private so that users of the
@@ -143,11 +159,13 @@ class FrameNodeImpl
const PageNode* GetPageNode() const override;
const ProcessNode* GetProcessNode() const override;
int GetFrameTreeNodeId() const override;
- const base::UnguessableToken& GetDevToolsToken() const override;
+ const FrameToken& GetFrameToken() const override;
int32_t GetBrowsingInstanceId() const override;
int32_t GetSiteInstanceId() const override;
bool VisitChildFrameNodes(const FrameNodeVisitor& visitor) const override;
const base::flat_set<const FrameNode*> GetChildFrameNodes() const override;
+ bool VisitOpenedPageNodes(const PageNodeVisitor& visitor) const override;
+ const base::flat_set<const PageNode*> GetOpenedPageNodes() const override;
LifecycleState GetLifecycleState() const override;
InterventionPolicy GetOriginTrialFreezePolicy() const override;
bool HasNonemptyBeforeUnload() const override;
@@ -160,6 +178,7 @@ class FrameNodeImpl
const base::flat_set<const WorkerNode*> GetChildWorkerNodes() const override;
const PriorityAndReason& GetPriorityAndReason() const override;
bool HadFormInteraction() const override;
+ bool IsAudible() const override;
// Properties associated with a Document, which are reset when a
// different-document navigation is committed in the frame.
@@ -205,8 +224,21 @@ class FrameNodeImpl
void OnJoiningGraph() override;
void OnBeforeLeavingGraph() override;
+ // Helper function to sever all opened page relationships. This is called
+ // before destroying the frame node in "OnBeforeLeavingGraph". Note that this
+ // will reparent opened pages to this frame's parent so that tracking is
+ // maintained.
+ void SeverOpenedPagesAndMaybeReparent();
+
+ // This is not quite the same as GetMainFrame, because there can be multiple
+ // main frames while the main frame is navigating. This explicitly walks up
+ // the tree to find the main frame that corresponds to this frame tree node,
+ // even if it is not current.
+ FrameNodeImpl* GetFrameTreeRoot() const;
+
bool HasFrameNodeInAncestors(FrameNodeImpl* frame_node) const;
bool HasFrameNodeInDescendants(FrameNodeImpl* frame_node) const;
+ bool HasFrameNodeInTree(FrameNodeImpl* frame_node) const;
mojo::Receiver<mojom::DocumentCoordinationUnit> receiver_{this};
@@ -218,11 +250,11 @@ class FrameNodeImpl
const int frame_tree_node_id_;
// The routing id of the frame.
const int render_frame_id_;
- // A unique identifier shared with all representations of this node across
- // content and blink. The token is only defined by the browser process and
- // is never sent back from the renderer in control calls. It should never be
- // used to look up the FrameTreeNode instance.
- const base::UnguessableToken dev_tools_token_;
+
+ // This is the unique token for this frame instance as per e.g.
+ // RenderFrameHost::GetFrameToken().
+ const FrameToken frame_token_;
+
// The unique ID of the BrowsingInstance this frame belongs to. Frames in the
// same BrowsingInstance are allowed to script each other at least
// asynchronously (if cross-site), and sometimes synchronously (if same-site,
@@ -238,6 +270,9 @@ class FrameNodeImpl
base::flat_set<FrameNodeImpl*> child_frame_nodes_;
+ // The set of pages that have been opened by this frame.
+ base::flat_set<PageNodeImpl*> opened_page_nodes_;
+
// Does *not* change when a navigation is committed.
ObservedProperty::NotifiesOnlyOnChanges<
LifecycleState,
@@ -283,6 +318,13 @@ class FrameNodeImpl
priority_and_reason_{PriorityAndReason(base::TaskPriority::LOWEST,
kDefaultPriorityReason)};
+ // Indicates if the frame is audible. This is tracked independently of a
+ // document, and if a document swap occurs the audio stream monitor machinery
+ // will keep this up to date.
+ ObservedProperty::
+ NotifiesOnlyOnChanges<bool, &FrameNodeObserver::OnIsAudibleChanged>
+ is_audible_{false};
+
// Inline storage for FramePriorityDecorator data.
frame_priority::AcceptedVote accepted_vote_;
diff --git a/chromium/components/performance_manager/graph/frame_node_impl_describer.cc b/chromium/components/performance_manager/graph/frame_node_impl_describer.cc
index 7eadd8a358a..8e1e61c2245 100644
--- a/chromium/components/performance_manager/graph/frame_node_impl_describer.cc
+++ b/chromium/components/performance_manager/graph/frame_node_impl_describer.cc
@@ -83,7 +83,7 @@ base::Value FrameNodeImplDescriber::DescribeFrameNodeData(
// Frame node properties.
ret.SetIntKey("frame_tree_node_id", impl->frame_tree_node_id_);
ret.SetIntKey("render_frame_id", impl->render_frame_id_);
- ret.SetStringKey("dev_tools_token", impl->dev_tools_token_.ToString());
+ ret.SetStringKey("frame_token", impl->frame_token_.value().ToString());
ret.SetIntKey("browsing_instance_id", impl->browsing_instance_id_);
ret.SetIntKey("site_instance_id", impl->site_instance_id_);
ret.SetStringKey("lifecycle_state",
@@ -95,6 +95,7 @@ base::Value FrameNodeImplDescriber::DescribeFrameNodeData(
ret.SetBoolKey("is_current", impl->is_current_.value());
ret.SetKey("priority",
PriorityAndReasonToValue(impl->priority_and_reason_.value()));
+ ret.SetBoolKey("is_audible", impl->is_audible_.value());
return ret;
}
diff --git a/chromium/components/performance_manager/graph/frame_node_impl_unittest.cc b/chromium/components/performance_manager/graph/frame_node_impl_unittest.cc
index cb971e443eb..467696d12dc 100644
--- a/chromium/components/performance_manager/graph/frame_node_impl_unittest.cc
+++ b/chromium/components/performance_manager/graph/frame_node_impl_unittest.cc
@@ -4,6 +4,7 @@
#include "components/performance_manager/graph/frame_node_impl.h"
+#include "base/test/gtest_util.h"
#include "components/performance_manager/graph/page_node_impl.h"
#include "components/performance_manager/graph/process_node_impl.h"
#include "components/performance_manager/test_support/graph_test_harness.h"
@@ -21,6 +22,10 @@ const FrameNode* ToPublic(FrameNodeImpl* frame_node) {
return frame_node;
}
+const PageNode* ToPublic(PageNodeImpl* page_node) {
+ return page_node;
+}
+
} // namespace
TEST_F(FrameNodeImplTest, SafeDowncast) {
@@ -142,6 +147,7 @@ class LenientMockObserver : public FrameNodeImpl::Observer {
MOCK_METHOD2(OnPriorityAndReasonChanged,
void(const FrameNode*, const PriorityAndReason& previous_value));
MOCK_METHOD1(OnHadFormInteractionChanged, void(const FrameNode*));
+ MOCK_METHOD1(OnIsAudibleChanged, void(const FrameNode*));
MOCK_METHOD1(OnNonPersistentNotificationCreated, void(const FrameNode*));
MOCK_METHOD2(OnFirstContentfulPaint, void(const FrameNode*, base::TimeDelta));
@@ -343,6 +349,22 @@ TEST_F(FrameNodeImplTest, FormInteractions) {
graph()->RemoveFrameNodeObserver(&obs);
}
+TEST_F(FrameNodeImplTest, IsAudible) {
+ auto process = CreateNode<ProcessNodeImpl>();
+ auto page = CreateNode<PageNodeImpl>();
+ auto frame_node = CreateFrameNodeAutoId(process.get(), page.get());
+ EXPECT_FALSE(frame_node->is_audible());
+
+ MockObserver obs;
+ graph()->AddFrameNodeObserver(&obs);
+
+ EXPECT_CALL(obs, OnIsAudibleChanged(frame_node.get()));
+ frame_node->SetIsAudible(true);
+ EXPECT_TRUE(frame_node->is_audible());
+
+ graph()->RemoveFrameNodeObserver(&obs);
+}
+
TEST_F(FrameNodeImplTest, FirstContentfulPaint) {
auto process = CreateNode<ProcessNodeImpl>();
auto page = CreateNode<PageNodeImpl>();
@@ -375,8 +397,7 @@ TEST_F(FrameNodeImplTest, PublicInterface) {
public_frame_node->GetProcessNode());
EXPECT_EQ(frame_node->frame_tree_node_id(),
public_frame_node->GetFrameTreeNodeId());
- EXPECT_EQ(frame_node->dev_tools_token(),
- public_frame_node->GetDevToolsToken());
+ EXPECT_EQ(frame_node->frame_token(), public_frame_node->GetFrameToken());
EXPECT_EQ(frame_node->browsing_instance_id(),
public_frame_node->GetBrowsingInstanceId());
EXPECT_EQ(frame_node->site_instance_id(),
@@ -438,4 +459,197 @@ TEST_F(FrameNodeImplTest, VisitChildFrameNodes) {
EXPECT_EQ(1u, visited.size());
}
+namespace {
+
+class LenientMockPageObserver : public PageNode::ObserverDefaultImpl {
+ public:
+ LenientMockPageObserver() = default;
+ ~LenientMockPageObserver() override = default;
+
+ MOCK_METHOD1(OnBeforePageNodeRemoved, void(const PageNode* page_node));
+
+ // Note that opener functionality is actually tested in the
+ // performance_manager_browsertest.
+ MOCK_METHOD3(OnOpenerFrameNodeChanged,
+ void(const PageNode*, const FrameNode*, OpenedType));
+};
+
+using MockPageObserver = ::testing::StrictMock<LenientMockPageObserver>;
+
+} // namespace
+
+TEST_F(FrameNodeImplTest, OpenerRelationships) {
+ using OpenedType = PageNode::OpenedType;
+
+ auto process = CreateNode<ProcessNodeImpl>();
+ auto pageA = CreateNode<PageNodeImpl>();
+ auto frameA1 = CreateFrameNodeAutoId(process.get(), pageA.get());
+ auto frameA2 =
+ CreateFrameNodeAutoId(process.get(), pageA.get(), frameA1.get());
+ auto pageB = CreateNode<PageNodeImpl>();
+ auto frameB1 = CreateFrameNodeAutoId(process.get(), pageB.get());
+ auto pageC = CreateNode<PageNodeImpl>();
+ auto frameC1 = CreateFrameNodeAutoId(process.get(), pageC.get());
+
+ // Use these to test the public APIs as well.
+ const FrameNode* pframeA1 = static_cast<const FrameNode*>(frameA1.get());
+ const PageNode* ppageB = static_cast<const PageNode*>(pageB.get());
+
+ MockPageObserver obs;
+ graph()->AddPageNodeObserver(&obs);
+
+ // You can always call the pre-delete opener clearing helper, even if you
+ // have no such relationships.
+ frameB1->SeverOpenedPagesAndMaybeReparentForTesting();
+
+ // You can't clear an opener if you don't already have one.
+ EXPECT_DCHECK_DEATH(pageB->ClearOpenerFrameNodeAndOpenedType());
+
+ // You can't be an opener for your own frame tree.
+ EXPECT_DCHECK_DEATH(pageA->SetOpenerFrameNodeAndOpenedType(
+ frameA1.get(), OpenedType::kPopup));
+
+ // You can't set a null opener or an invalid opened type.
+ EXPECT_DCHECK_DEATH(
+ pageB->SetOpenerFrameNodeAndOpenedType(nullptr, OpenedType::kInvalid));
+ EXPECT_DCHECK_DEATH(pageB->SetOpenerFrameNodeAndOpenedType(
+ frameA1.get(), OpenedType::kInvalid));
+
+ EXPECT_EQ(nullptr, pageB->opener_frame_node());
+ EXPECT_EQ(nullptr, ppageB->GetOpenerFrameNode());
+ EXPECT_EQ(OpenedType::kInvalid, pageB->opened_type());
+ EXPECT_EQ(OpenedType::kInvalid, ppageB->GetOpenedType());
+ EXPECT_TRUE(frameA1->opened_page_nodes().empty());
+ EXPECT_TRUE(pframeA1->GetOpenedPageNodes().empty());
+
+ // Set an opener relationship.
+ EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), nullptr,
+ OpenedType::kInvalid));
+ pageB->SetOpenerFrameNodeAndOpenedType(frameA1.get(), OpenedType::kGuestView);
+ EXPECT_EQ(frameA1.get(), pageB->opener_frame_node());
+ EXPECT_EQ(frameA1.get(), ppageB->GetOpenerFrameNode());
+ EXPECT_EQ(OpenedType::kGuestView, pageB->opened_type());
+ EXPECT_EQ(OpenedType::kGuestView, ppageB->GetOpenedType());
+ EXPECT_EQ(1u, frameA1->opened_page_nodes().size());
+ EXPECT_EQ(1u, pframeA1->GetOpenedPageNodes().size());
+ EXPECT_EQ(1u, frameA1->opened_page_nodes().count(pageB.get()));
+ EXPECT_EQ(1u, pframeA1->GetOpenedPageNodes().count(pageB.get()));
+ testing::Mock::VerifyAndClear(&obs);
+
+ // Set another opener relationship.
+ EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageC.get(), nullptr,
+ OpenedType::kInvalid));
+ pageC->SetOpenerFrameNodeAndOpenedType(frameA1.get(), OpenedType::kPopup);
+ EXPECT_EQ(frameA1.get(), pageC->opener_frame_node());
+ EXPECT_EQ(OpenedType::kPopup, pageC->opened_type());
+ EXPECT_EQ(2u, frameA1->opened_page_nodes().size());
+ EXPECT_EQ(1u, frameA1->opened_page_nodes().count(pageB.get()));
+ testing::Mock::VerifyAndClear(&obs);
+
+ // Do a traversal.
+ std::set<const PageNode*> visited;
+ EXPECT_TRUE(
+ ToPublic(frameA1.get())
+ ->VisitOpenedPageNodes(base::BindRepeating(
+ [](std::set<const PageNode*>* visited, const PageNode* page) {
+ EXPECT_TRUE(visited->insert(page).second);
+ return true;
+ },
+ base::Unretained(&visited))));
+ EXPECT_THAT(visited, testing::UnorderedElementsAre(ToPublic(pageB.get()),
+ ToPublic(pageC.get())));
+
+ // Do an aborted visit.
+ visited.clear();
+ EXPECT_FALSE(
+ ToPublic(frameA1.get())
+ ->VisitOpenedPageNodes(base::BindRepeating(
+ [](std::set<const PageNode*>* visited, const PageNode* page) {
+ EXPECT_TRUE(visited->insert(page).second);
+ return false;
+ },
+ base::Unretained(&visited))));
+ EXPECT_EQ(1u, visited.size());
+
+ // Manually clear the first relationship (initiated from the page).
+ EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), frameA1.get(),
+ OpenedType::kGuestView));
+ pageB->ClearOpenerFrameNodeAndOpenedType();
+ EXPECT_EQ(nullptr, pageB->opener_frame_node());
+ EXPECT_EQ(OpenedType::kInvalid, pageB->opened_type());
+ EXPECT_EQ(frameA1.get(), pageC->opener_frame_node());
+ EXPECT_EQ(OpenedType::kPopup, pageC->opened_type());
+ EXPECT_EQ(1u, frameA1->opened_page_nodes().size());
+ EXPECT_EQ(0u, frameA1->opened_page_nodes().count(pageB.get()));
+ testing::Mock::VerifyAndClear(&obs);
+
+ // Clear the second relationship (initiated from the frame).
+ EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageC.get(), frameA1.get(),
+ OpenedType::kPopup));
+ frameA1->SeverOpenedPagesAndMaybeReparentForTesting();
+ EXPECT_EQ(nullptr, pageC->opener_frame_node());
+ EXPECT_EQ(OpenedType::kInvalid, pageC->opened_type());
+ EXPECT_TRUE(frameA1->opened_page_nodes().empty());
+ testing::Mock::VerifyAndClear(&obs);
+
+ // Set a popup opener relationship on node A2.
+ EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), nullptr,
+ OpenedType::kInvalid));
+ pageB->SetOpenerFrameNodeAndOpenedType(frameA2.get(), OpenedType::kPopup);
+ EXPECT_EQ(frameA2.get(), pageB->opener_frame_node());
+ EXPECT_EQ(OpenedType::kPopup, pageB->opened_type());
+ EXPECT_TRUE(frameA1->opened_page_nodes().empty());
+ EXPECT_EQ(1u, frameA2->opened_page_nodes().size());
+ EXPECT_EQ(1u, frameA2->opened_page_nodes().count(pageB.get()));
+ testing::Mock::VerifyAndClear(&obs);
+
+ // Clear it with the helper, and expect it to be reparented to node A1.
+ EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), frameA2.get(),
+ OpenedType::kPopup));
+ frameA2->SeverOpenedPagesAndMaybeReparentForTesting();
+ EXPECT_EQ(frameA1.get(), pageB->opener_frame_node());
+ EXPECT_EQ(OpenedType::kPopup, pageB->opened_type());
+ EXPECT_EQ(1u, frameA1->opened_page_nodes().size());
+ EXPECT_EQ(1u, frameA1->opened_page_nodes().count(pageB.get()));
+ EXPECT_TRUE(frameA2->opened_page_nodes().empty());
+ testing::Mock::VerifyAndClear(&obs);
+
+ // Clear it again with the helper. This time reparenting can't happen, as it
+ // was already parented to the root.
+ EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), frameA1.get(),
+ OpenedType::kPopup));
+ frameA1->SeverOpenedPagesAndMaybeReparentForTesting();
+ EXPECT_EQ(nullptr, pageB->opener_frame_node());
+ EXPECT_EQ(OpenedType::kInvalid, pageB->opened_type());
+ EXPECT_TRUE(frameA1->opened_page_nodes().empty());
+ EXPECT_TRUE(frameA2->opened_page_nodes().empty());
+ testing::Mock::VerifyAndClear(&obs);
+
+ // verify that the opener relationship is torn down before any node removal
+ // notification arrives.
+ EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), nullptr,
+ OpenedType::kInvalid));
+ pageB->SetOpenerFrameNodeAndOpenedType(frameA2.get(), OpenedType::kPopup);
+ EXPECT_EQ(frameA2.get(), pageB->opener_frame_node());
+ EXPECT_EQ(OpenedType::kPopup, pageB->opened_type());
+ EXPECT_TRUE(frameA1->opened_page_nodes().empty());
+ EXPECT_EQ(1u, frameA2->opened_page_nodes().size());
+ EXPECT_EQ(1u, frameA2->opened_page_nodes().count(pageB.get()));
+ testing::Mock::VerifyAndClear(&obs);
+
+ {
+ ::testing::InSequence seq;
+
+ // These must occur in sequence.
+ EXPECT_CALL(obs, OnOpenerFrameNodeChanged(pageB.get(), frameA2.get(),
+ OpenedType::kPopup));
+ EXPECT_CALL(obs, OnBeforePageNodeRemoved(pageB.get()));
+ }
+ frameB1.reset();
+ pageB.reset();
+ testing::Mock::VerifyAndClear(&obs);
+
+ graph()->RemovePageNodeObserver(&obs);
+}
+
} // namespace performance_manager
diff --git a/chromium/components/performance_manager/graph/graph_impl.cc b/chromium/components/performance_manager/graph/graph_impl.cc
index 30c1fabef64..81425e79a14 100644
--- a/chromium/components/performance_manager/graph/graph_impl.cc
+++ b/chromium/components/performance_manager/graph/graph_impl.cc
@@ -153,7 +153,8 @@ GraphImpl::GraphImpl() {
GraphImpl::~GraphImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- // All graph registered objects should have been unregistered.
+ // All graph registered and owned objects should have been cleaned up.
+ DCHECK(graph_owned_.empty());
DCHECK(registered_objects_.empty());
// At this point, all typed observers should be empty.
@@ -180,8 +181,7 @@ void GraphImpl::TearDown() {
// Clean up graph owned objects. This causes their TakeFromGraph callbacks to
// be invoked, and ideally they clean up any observers they may have, etc.
- while (!graph_owned_.empty())
- auto object = TakeFromGraph(graph_owned_.begin()->first);
+ graph_owned_.ReleaseObjects(this);
// At this point, all typed observers should be empty.
DCHECK(graph_observers_.empty());
@@ -258,39 +258,22 @@ void GraphImpl::RemoveWorkerNodeObserver(WorkerNodeObserver* observer) {
void GraphImpl::PassToGraph(std::unique_ptr<GraphOwned> graph_owned) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- auto* raw = graph_owned.get();
- DCHECK(!base::Contains(graph_owned_, raw));
- graph_owned_.insert(std::make_pair(raw, std::move(graph_owned)));
- raw->OnPassedToGraph(this);
+ graph_owned_.PassObject(std::move(graph_owned), this);
}
std::unique_ptr<GraphOwned> GraphImpl::TakeFromGraph(GraphOwned* graph_owned) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- std::unique_ptr<GraphOwned> object;
- auto it = graph_owned_.find(graph_owned);
- if (it != graph_owned_.end()) {
- DCHECK_EQ(graph_owned, it->first);
- DCHECK_EQ(graph_owned, it->second.get());
- object = std::move(it->second);
- graph_owned_.erase(it);
- object->OnTakenFromGraph(this);
- }
- return object;
+ return graph_owned_.TakeObject(graph_owned, this);
}
void GraphImpl::RegisterObject(GraphRegistered* object) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DCHECK_EQ(nullptr, GetRegisteredObject(object->GetTypeId()));
- registered_objects_.insert(object);
- // If there are ever so many registered objects we should consider changing
- // data structures.
- DCHECK_GT(100u, registered_objects_.size());
+ registered_objects_.RegisterObject(object);
}
void GraphImpl::UnregisterObject(GraphRegistered* object) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DCHECK_EQ(object, GetRegisteredObject(object->GetTypeId()));
- registered_objects_.erase(object);
+ registered_objects_.UnregisterObject(object);
}
const SystemNode* GraphImpl::FindOrCreateSystemNode() {
@@ -344,11 +327,7 @@ const void* GraphImpl::GetImpl() const {
GraphRegistered* GraphImpl::GetRegisteredObject(uintptr_t type_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- auto it = registered_objects_.find(type_id);
- if (it == registered_objects_.end())
- return nullptr;
- DCHECK_EQ((*it)->GetTypeId(), type_id);
- return *it;
+ return registered_objects_.GetRegisteredObject(type_id);
}
// static
@@ -396,6 +375,10 @@ void GraphImpl::OnNodeAdded(NodeBase* node) {
void GraphImpl::OnBeforeNodeRemoved(NodeBase* node) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // Clear any node-specific state and issue the relevant notifications before
+ // sending the last-gasp removal notification for this node.
+ node->OnBeforeLeavingGraph();
+
// This handles the strongly typed observer notifications.
switch (node->type()) {
case NodeTypeEnum::kFrame: {
@@ -524,8 +507,9 @@ void GraphImpl::AddNewNode(NodeBase* new_node) {
auto it = nodes_.insert(new_node);
DCHECK(it.second); // Inserted successfully
- // Allow the node to initialize itself now that it's been added.
+ // Add the node to the graph and allow it to initialize itself.
new_node->JoinGraph(this);
+ new_node->OnJoiningGraph();
// Then notify observers.
OnNodeAdded(new_node);
diff --git a/chromium/components/performance_manager/graph/graph_impl.h b/chromium/components/performance_manager/graph/graph_impl.h
index fc4232ab05d..158459ee706 100644
--- a/chromium/components/performance_manager/graph/graph_impl.h
+++ b/chromium/components/performance_manager/graph/graph_impl.h
@@ -19,9 +19,11 @@
#include "base/macros.h"
#include "base/process/process_handle.h"
#include "base/sequence_checker.h"
+#include "components/performance_manager/owned_objects.h"
#include "components/performance_manager/public/graph/graph.h"
#include "components/performance_manager/public/graph/graph_registered.h"
#include "components/performance_manager/public/graph/node_attached_data.h"
+#include "components/performance_manager/registered_objects.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
namespace performance_manager {
@@ -187,7 +189,11 @@ class GraphImpl : public Graph {
// Graph-owned objects. For now we only expect O(10) clients, hence the
// flat_map.
- base::flat_map<GraphOwned*, std::unique_ptr<GraphOwned>> graph_owned_;
+ OwnedObjects<GraphOwned,
+ /* CallbackArgType = */ Graph*,
+ &GraphOwned::OnPassedToGraph,
+ &GraphOwned::OnTakenFromGraph>
+ graph_owned_;
// Allocated on first use.
mutable std::unique_ptr<NodeDataDescriberRegistry> describer_registry_;
@@ -199,28 +205,8 @@ class GraphImpl : public Graph {
std::map<NodeAttachedDataKey, std::unique_ptr<NodeAttachedData>>;
NodeAttachedDataMap node_attached_data_map_;
- // Comparator for GraphRegistered objects, which sorts by TypeId. This is a
- // transparent comparator (see base::flat_tree) which allows comparing
- // GraphRegistered objects and TypeIds with each other.
- struct GraphRegisteredComparator {
- using is_transparent = void;
- bool operator()(const GraphRegistered* gr1,
- const GraphRegistered* gr2) const {
- return gr1->GetTypeId() < gr2->GetTypeId();
- }
- bool operator()(const GraphRegistered* gr1, uintptr_t type_id) const {
- return gr1->GetTypeId() < type_id;
- }
- bool operator()(uintptr_t type_id, const GraphRegistered* gr2) const {
- return type_id < gr2->GetTypeId();
- }
- };
-
- // Storage for GraphRegistered objects. They are stored by pointer to object,
- // but sorted by their uintptr_t TypeIds. These must all be unregistered
- // before this object is destroyed.
- base::flat_set<GraphRegistered*, GraphRegisteredComparator>
- registered_objects_;
+ // Storage for GraphRegistered objects.
+ RegisteredObjects<GraphRegistered> registered_objects_;
// The most recently assigned serialization ID.
int64_t current_node_serialization_id_ = 0u;
diff --git a/chromium/components/performance_manager/graph/graph_impl_unittest.cc b/chromium/components/performance_manager/graph/graph_impl_unittest.cc
index bee42687424..05d5de1f915 100644
--- a/chromium/components/performance_manager/graph/graph_impl_unittest.cc
+++ b/chromium/components/performance_manager/graph/graph_impl_unittest.cc
@@ -322,4 +322,24 @@ TEST_F(GraphImplTest, NodeDataDescribers) {
EXPECT_EQ(0u, descr.DictSize());
}
+TEST_F(GraphImplTest, OpenersClearedOnTeardown) {
+ auto process = CreateNode<ProcessNodeImpl>();
+ auto pageA = CreateNode<PageNodeImpl>();
+ auto frameA1 = CreateFrameNodeAutoId(process.get(), pageA.get());
+ auto frameA2 =
+ CreateFrameNodeAutoId(process.get(), pageA.get(), frameA1.get());
+ auto pageB = CreateNode<PageNodeImpl>();
+ auto frameB1 = CreateFrameNodeAutoId(process.get(), pageB.get());
+ auto pageC = CreateNode<PageNodeImpl>();
+ auto frameC1 = CreateFrameNodeAutoId(process.get(), pageC.get());
+
+ // Set up some opener relationships. These should be gracefully torn down as
+ // the graph cleans up nodes, otherwise the frame and page node destructors
+ // will explode.
+ pageB->SetOpenerFrameNodeAndOpenedType(frameA1.get(),
+ PageNode::OpenedType::kGuestView);
+ pageC->SetOpenerFrameNodeAndOpenedType(frameA2.get(),
+ PageNode::OpenedType::kPopup);
+}
+
} // namespace performance_manager
diff --git a/chromium/components/performance_manager/graph/graph_registered_unittest.cc b/chromium/components/performance_manager/graph/graph_registered_unittest.cc
index de6737571ad..e7b6a2450e1 100644
--- a/chromium/components/performance_manager/graph/graph_registered_unittest.cc
+++ b/chromium/components/performance_manager/graph/graph_registered_unittest.cc
@@ -5,7 +5,10 @@
#include "components/performance_manager/public/graph/graph_registered.h"
#include "base/test/gtest_util.h"
+#include "components/performance_manager/performance_manager_registry_impl.h"
+#include "components/performance_manager/public/performance_manager.h"
#include "components/performance_manager/test_support/graph_test_harness.h"
+#include "components/performance_manager/test_support/performance_manager_test_harness.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -91,4 +94,56 @@ TEST_F(GraphRegisteredTest, GraphRegistrationWorks) {
graph()->UnregisterObject(&foo);
}
+namespace {
+
+// This class is non-sensically both a GraphRegistered object and a
+// PerformanceManagerRegistered object. This is done to ensure that the
+// implementations use the appropriately typed "TypeId" functions, as there are
+// now two of them available!
+class Baz : public GraphRegisteredImpl<Baz>,
+ public PerformanceManagerRegisteredImpl<Baz> {
+ public:
+ Baz() = default;
+ ~Baz() override = default;
+};
+
+using GraphAndPerformanceManagerRegisteredTest = PerformanceManagerTestHarness;
+
+} // namespace
+
+TEST_F(GraphAndPerformanceManagerRegisteredTest, GraphAndPMRegistered) {
+ // Each TypeId should be backed by a distinct "TypeId" implementation and
+ // value.
+ const uintptr_t kGraphId = GraphRegisteredImpl<Baz>::TypeId();
+ const uintptr_t kPMId = PerformanceManagerRegisteredImpl<Baz>::TypeId();
+ ASSERT_NE(kGraphId, kPMId);
+
+ // Create a stand-alone graph that is bound to this sequence so we can test
+ // both the PM and a graph on the same sequence.
+ std::unique_ptr<GraphImpl> graph(new GraphImpl());
+
+ PerformanceManagerRegistryImpl* registry =
+ PerformanceManagerRegistryImpl::GetInstance();
+
+ Baz baz;
+ graph->RegisterObject(&baz);
+ registry->RegisterObject(&baz);
+
+ EXPECT_EQ(&baz, graph->GetRegisteredObject(kGraphId));
+ EXPECT_EQ(&baz, registry->GetRegisteredObject(kPMId));
+ EXPECT_EQ(nullptr, graph->GetRegisteredObject(kPMId));
+ EXPECT_EQ(nullptr, registry->GetRegisteredObject(kGraphId));
+
+ // This directly tests that the templated helper function uses the correct
+ // instance of TypeId.
+ EXPECT_EQ(&baz, graph->GetRegisteredObjectAs<Baz>());
+ EXPECT_EQ(&baz, PerformanceManager::GetRegisteredObjectAs<Baz>());
+
+ graph->UnregisterObject(&baz);
+ registry->UnregisterObject(&baz);
+
+ graph->TearDown();
+ graph.reset();
+}
+
} // namespace performance_manager \ No newline at end of file
diff --git a/chromium/components/performance_manager/graph/node_attached_data.h b/chromium/components/performance_manager/graph/node_attached_data.h
index 6256f169fe9..3f432bb70a7 100644
--- a/chromium/components/performance_manager/graph/node_attached_data.h
+++ b/chromium/components/performance_manager/graph/node_attached_data.h
@@ -7,7 +7,7 @@
#include <memory>
-#include "base/logging.h"
+#include "base/check_op.h"
#include "base/macros.h"
#include "components/performance_manager/graph/node_base.h"
#include "components/performance_manager/public/graph/node_attached_data.h"
diff --git a/chromium/components/performance_manager/graph/node_attached_data_impl.h b/chromium/components/performance_manager/graph/node_attached_data_impl.h
index 147244f6f3e..6c2185980ee 100644
--- a/chromium/components/performance_manager/graph/node_attached_data_impl.h
+++ b/chromium/components/performance_manager/graph/node_attached_data_impl.h
@@ -9,7 +9,7 @@
#include <type_traits>
#include <utility>
-#include "base/logging.h"
+#include "base/check_op.h"
#include "base/memory/ptr_util.h"
#include "components/performance_manager/graph/node_attached_data.h"
diff --git a/chromium/components/performance_manager/graph/node_base.cc b/chromium/components/performance_manager/graph/node_base.cc
index e458a8eb016..36d28adb73e 100644
--- a/chromium/components/performance_manager/graph/node_base.cc
+++ b/chromium/components/performance_manager/graph/node_base.cc
@@ -40,8 +40,6 @@ void NodeBase::JoinGraph(GraphImpl* graph) {
DCHECK(graph->NodeInGraph(this));
graph_ = graph;
-
- OnJoiningGraph();
}
void NodeBase::LeaveGraph() {
@@ -49,8 +47,6 @@ void NodeBase::LeaveGraph() {
DCHECK(graph_);
DCHECK(graph_->NodeInGraph(this));
- OnBeforeLeavingGraph();
-
graph_ = nullptr;
}
diff --git a/chromium/components/performance_manager/graph/node_base.h b/chromium/components/performance_manager/graph/node_base.h
index a37e31687b7..0a05e7b070c 100644
--- a/chromium/components/performance_manager/graph/node_base.h
+++ b/chromium/components/performance_manager/graph/node_base.h
@@ -73,13 +73,10 @@ class NodeBase {
return graph->GetObservers<Observer>();
}
- // Joins the |graph|. Assigns |graph_| and invokes OnJoiningGraph() to allow
- // subclasses to initialize.
+ // Joins the |graph|.
void JoinGraph(GraphImpl* graph);
- // Leaves the graph that this node is a part of. Invokes
- // OnBeforeLeavingGraph() to allow subclasses to uninitialize then clears
- // |graph_|.
+ // Leaves the graph that this node is a part of.
void LeaveGraph();
// Called as this node is joining |graph_|, a good opportunity to initialize
diff --git a/chromium/components/performance_manager/graph/page_node.cc b/chromium/components/performance_manager/graph/page_node.cc
index 772581fa119..113fcf4c684 100644
--- a/chromium/components/performance_manager/graph/page_node.cc
+++ b/chromium/components/performance_manager/graph/page_node.cc
@@ -8,6 +8,21 @@
namespace performance_manager {
+// static
+const char* PageNode::ToString(PageNode::OpenedType opened_type) {
+ switch (opened_type) {
+ case PageNode::OpenedType::kInvalid:
+ return "kInvalid";
+ case PageNode::OpenedType::kPopup:
+ return "kPopup";
+ case PageNode::OpenedType::kGuestView:
+ return "kGuestView";
+ case PageNode::OpenedType::kPortal:
+ return "kPortal";
+ }
+ NOTREACHED();
+}
+
PageNode::PageNode() = default;
PageNode::~PageNode() = default;
@@ -18,3 +33,10 @@ PageNode::ObserverDefaultImpl::ObserverDefaultImpl() = default;
PageNode::ObserverDefaultImpl::~ObserverDefaultImpl() = default;
} // namespace performance_manager
+
+std::ostream& operator<<(
+ std::ostream& os,
+ performance_manager::PageNode::OpenedType opened_type) {
+ os << performance_manager::PageNode::ToString(opened_type);
+ return os;
+} \ No newline at end of file
diff --git a/chromium/components/performance_manager/graph/page_node_impl.cc b/chromium/components/performance_manager/graph/page_node_impl.cc
index b64294cd990..14ec570a3fa 100644
--- a/chromium/components/performance_manager/graph/page_node_impl.cc
+++ b/chromium/components/performance_manager/graph/page_node_impl.cc
@@ -34,6 +34,8 @@ PageNodeImpl::PageNodeImpl(const WebContentsProxy& contents_proxy,
PageNodeImpl::~PageNodeImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(nullptr, opener_frame_node_);
+ DCHECK_EQ(OpenedType::kInvalid, opened_type_);
}
const WebContentsProxy& PageNodeImpl::contents_proxy() const {
@@ -150,6 +152,18 @@ FrameNodeImpl* PageNodeImpl::GetMainFrameNodeImpl() const {
return *main_frame_nodes_.begin();
}
+FrameNodeImpl* PageNodeImpl::opener_frame_node() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(opener_frame_node_ || opened_type_ == OpenedType::kInvalid);
+ return opener_frame_node_;
+}
+
+PageNodeImpl::OpenedType PageNodeImpl::opened_type() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(opener_frame_node_ || opened_type_ == OpenedType::kInvalid);
+ return opened_type_;
+}
+
bool PageNodeImpl::is_visible() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_visible_.value();
@@ -231,6 +245,43 @@ bool PageNodeImpl::had_form_interaction() const {
return had_form_interaction_.value();
}
+void PageNodeImpl::SetOpenerFrameNodeAndOpenedType(FrameNodeImpl* opener,
+ OpenedType opened_type) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(opener);
+ DCHECK(graph()->NodeInGraph(opener));
+ DCHECK_NE(this, opener->page_node());
+ DCHECK_NE(OpenedType::kInvalid, opened_type);
+
+ auto* previous_opener = opener_frame_node_;
+ auto previous_type = opened_type_;
+
+ if (previous_opener)
+ previous_opener->RemoveOpenedPage(PassKey(), this);
+ opener_frame_node_ = opener;
+ opened_type_ = opened_type;
+ opener->AddOpenedPage(PassKey(), this);
+
+ for (auto* observer : GetObservers())
+ observer->OnOpenerFrameNodeChanged(this, previous_opener, previous_type);
+}
+
+void PageNodeImpl::ClearOpenerFrameNodeAndOpenedType() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_NE(nullptr, opener_frame_node_);
+ DCHECK_NE(OpenedType::kInvalid, opened_type_);
+
+ auto* previous_opener = opener_frame_node_;
+ auto previous_type = opened_type_;
+
+ opener_frame_node_->RemoveOpenedPage(PassKey(), this);
+ opener_frame_node_ = nullptr;
+ opened_type_ = OpenedType::kInvalid;
+
+ for (auto* observer : GetObservers())
+ observer->OnOpenerFrameNodeChanged(this, previous_opener, previous_type);
+}
+
void PageNodeImpl::set_usage_estimate_time(
base::TimeTicks usage_estimate_time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -262,6 +313,10 @@ void PageNodeImpl::OnJoiningGraph() {
void PageNodeImpl::OnBeforeLeavingGraph() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // Sever opener relationships.
+ if (opener_frame_node_)
+ ClearOpenerFrameNodeAndOpenedType();
+
DCHECK_EQ(0u, frame_node_count_);
}
@@ -270,6 +325,16 @@ const std::string& PageNodeImpl::GetBrowserContextID() const {
return browser_context_id();
}
+const FrameNode* PageNodeImpl::GetOpenerFrameNode() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return opener_frame_node();
+}
+
+PageNodeImpl::OpenedType PageNodeImpl::GetOpenedType() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return opened_type();
+}
+
bool PageNodeImpl::IsVisible() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_visible();
diff --git a/chromium/components/performance_manager/graph/page_node_impl.h b/chromium/components/performance_manager/graph/page_node_impl.h
index 3c391831177..91197591060 100644
--- a/chromium/components/performance_manager/graph/page_node_impl.h
+++ b/chromium/components/performance_manager/graph/page_node_impl.h
@@ -13,6 +13,7 @@
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
+#include "base/util/type_safety/pass_key.h"
#include "components/performance_manager/graph/node_attached_data.h"
#include "components/performance_manager/graph/node_base.h"
#include "components/performance_manager/public/graph/page_node.h"
@@ -27,6 +28,8 @@ class PageNodeImpl
: public PublicNodeImpl<PageNodeImpl, PageNode>,
public TypedNodeBase<PageNodeImpl, PageNode, PageNodeObserver> {
public:
+ using PassKey = util::PassKey<PageNodeImpl>;
+
static constexpr NodeTypeEnum Type() { return NodeTypeEnum::kPage; }
PageNodeImpl(const WebContentsProxy& contents_proxy,
@@ -70,6 +73,8 @@ class PageNodeImpl
// Accessors.
const std::string& browser_context_id() const;
+ FrameNodeImpl* opener_frame_node() const;
+ OpenedType opened_type() const;
bool is_visible() const;
bool is_audible() const;
bool is_loading() const;
@@ -86,6 +91,11 @@ class PageNodeImpl
const std::string& contents_mime_type() const;
bool had_form_interaction() const;
+ // Invoked to set/clear the opener of this page.
+ void SetOpenerFrameNodeAndOpenedType(FrameNodeImpl* opener,
+ OpenedType opened_type);
+ void ClearOpenerFrameNodeAndOpenedType();
+
void set_usage_estimate_time(base::TimeTicks usage_estimate_time);
void set_private_footprint_kb_estimate(
uint64_t private_footprint_kb_estimate);
@@ -113,9 +123,12 @@ class PageNodeImpl
friend class PageAggregatorAccess;
friend class PageLoadTrackerAccess;
friend class PageNodeImplDescriber;
+ friend class SiteDataAccess;
// PageNode implementation.
const std::string& GetBrowserContextID() const override;
+ const FrameNode* GetOpenerFrameNode() const override;
+ OpenedType GetOpenedType() const override;
bool IsVisible() const override;
base::TimeDelta GetTimeSinceLastVisibilityChange() const override;
bool IsAudible() const override;
@@ -196,6 +209,12 @@ class PageNodeImpl
// The unique ID of the browser context that this page belongs to.
const std::string browser_context_id_;
+ // The opener of this page, if there is one.
+ FrameNodeImpl* opener_frame_node_ = nullptr;
+
+ // The way in which this page was opened, if it was opened.
+ OpenedType opened_type_ = OpenedType::kInvalid;
+
// Whether or not the page is visible. Driven by browser instrumentation.
// Initialized on construction.
ObservedProperty::NotifiesOnlyOnChanges<bool,
@@ -249,6 +268,9 @@ class PageNodeImpl
// Storage for PageLoadTracker user data.
std::unique_ptr<NodeAttachedData> page_load_tracker_data_;
+ // Storage for SiteDataNodeData user data.
+ std::unique_ptr<NodeAttachedData> site_data_;
+
// Inline storage for FrozenFrameAggregator user data.
InternalNodeAttachedDataStorage<sizeof(uintptr_t) + 8> frozen_frame_data_;
diff --git a/chromium/components/performance_manager/graph/page_node_impl_describer.cc b/chromium/components/performance_manager/graph/page_node_impl_describer.cc
index 8b04bc72769..6032b968847 100644
--- a/chromium/components/performance_manager/graph/page_node_impl_describer.cc
+++ b/chromium/components/performance_manager/graph/page_node_impl_describer.cc
@@ -76,6 +76,10 @@ base::Value PageNodeImplDescriber::DescribePageNodeData(
page_node_impl->is_holding_indexeddb_lock_.value());
result.SetBoolKey("had_form_interaction",
page_node_impl->had_form_interaction_.value());
+ if (page_node_impl->opened_type_ != PageNode::OpenedType::kInvalid) {
+ result.SetStringKey("opened_type",
+ PageNode::ToString(page_node_impl->opened_type_));
+ }
return result;
}
diff --git a/chromium/components/performance_manager/graph/page_node_impl_unittest.cc b/chromium/components/performance_manager/graph/page_node_impl_unittest.cc
index 6283d5f6c7a..08cdcfb9755 100644
--- a/chromium/components/performance_manager/graph/page_node_impl_unittest.cc
+++ b/chromium/components/performance_manager/graph/page_node_impl_unittest.cc
@@ -201,6 +201,10 @@ class LenientMockObserver : public PageNodeImpl::Observer {
MOCK_METHOD1(OnPageNodeAdded, void(const PageNode*));
MOCK_METHOD1(OnBeforePageNodeRemoved, void(const PageNode*));
+ // Note that opener functionality is actually tested in the FrameNodeImpl
+ // and GraphImpl unittests.
+ MOCK_METHOD3(OnOpenerFrameNodeChanged,
+ void(const PageNode*, const FrameNode*, OpenedType));
MOCK_METHOD1(OnIsVisibleChanged, void(const PageNode*));
MOCK_METHOD1(OnIsAudibleChanged, void(const PageNode*));
MOCK_METHOD1(OnIsLoadingChanged, void(const PageNode*));
diff --git a/chromium/components/performance_manager/graph/policies/process_priority_policy.cc b/chromium/components/performance_manager/graph/policies/process_priority_policy.cc
index 91c79a717fb..cfc313d4b4b 100644
--- a/chromium/components/performance_manager/graph/policies/process_priority_policy.cc
+++ b/chromium/components/performance_manager/graph/policies/process_priority_policy.cc
@@ -6,7 +6,6 @@
#include "base/bind.h"
#include "base/memory/ptr_util.h"
-#include "base/task/post_task.h"
#include "components/performance_manager/public/render_process_host_proxy.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
@@ -69,8 +68,8 @@ void DispatchSetProcessPriority(const ProcessNode* process_node,
// driven 100% from the PM, we could post directly to the launcher thread
// via the base::Process directly.
const auto& proxy = process_node->GetRenderProcessHostProxy();
- base::PostTask(
- FROM_HERE, {content::BrowserThread::UI},
+ content::GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE,
base::BindOnce(&SetProcessPriorityOnUIThread, proxy, foreground));
}
diff --git a/chromium/components/performance_manager/graph/policies/tab_loading_frame_navigation_policy.cc b/chromium/components/performance_manager/graph/policies/tab_loading_frame_navigation_policy.cc
index 8b27ecc2e6f..22bedb9e0bb 100644
--- a/chromium/components/performance_manager/graph/policies/tab_loading_frame_navigation_policy.cc
+++ b/chromium/components/performance_manager/graph/policies/tab_loading_frame_navigation_policy.cc
@@ -6,7 +6,6 @@
#include "base/bind.h"
#include "base/no_destructor.h"
-#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "components/performance_manager/graph/page_node_impl.h"
#include "components/performance_manager/public/features.h"
@@ -60,6 +59,7 @@ TabLoadingFrameNavigationPolicy::TabLoadingFrameNavigationPolicy()
auto params = features::TabLoadingFrameNavigationThrottlesParams::GetParams();
timeout_min_ = params.minimum_throttle_timeout;
timeout_max_ = params.maximum_throttle_timeout;
+ fcp_multiple_ = params.fcp_multiple;
}
TabLoadingFrameNavigationPolicy::~TabLoadingFrameNavigationPolicy() {
@@ -123,6 +123,24 @@ bool TabLoadingFrameNavigationPolicy::ShouldThrottleNavigation(
return true;
}
+base::TimeTicks TabLoadingFrameNavigationPolicy::GetPageTimeoutForTesting(
+ const PageNode* page_node) const {
+ size_t i = 0;
+ for (; i < timeouts_.size(); ++i) {
+ if (timeouts_[i].page_node == page_node)
+ return timeouts_[i].timeout;
+ }
+ return base::TimeTicks();
+}
+
+base::TimeDelta TabLoadingFrameNavigationPolicy::CalculateTimeoutFromFCP(
+ base::TimeDelta fcp) const {
+ // No need to cap the timeout with |timeout_max_|, as the timeout starts with
+ // that by default, and timeout updates can only make the timeout decrease.
+ // See MaybeUpdatePageTimeout for details.
+ return std::max(fcp * (fcp_multiple_ - 1.0), timeout_min_);
+}
+
void TabLoadingFrameNavigationPolicy::OnBeforePageNodeRemoved(
const PageNode* page_node) {
// There's no public graph accessor. We could cache this in OnPassedToGraph,
@@ -142,22 +160,19 @@ void TabLoadingFrameNavigationPolicy::OnFirstContentfulPaint(
if (!frame_node->IsMainFrame() || !frame_node->IsCurrent())
return;
- // Wait for another FCP time period, but enforce a lower bound.
- double fcp_ms = time_since_navigation_start.InMillisecondsF();
- double delta_ms = std::max(timeout_min_.InMillisecondsF(), fcp_ms);
+ // Update the timer if needed.
MaybeUpdatePageTimeout(frame_node->GetPageNode(),
- base::TimeDelta::FromMillisecondsD(delta_ms));
+ CalculateTimeoutFromFCP(time_since_navigation_start));
}
void TabLoadingFrameNavigationPolicy::OnPassedToGraph(Graph* graph) {
DCHECK(NothingRegistered(graph));
- base::PostTask(FROM_HERE,
- {content::BrowserThread::UI, base::TaskPriority::USER_VISIBLE},
- base::BindOnce(
- [](MechanismDelegate* mechanism) {
- mechanism->SetThrottlingEnabled(true);
- },
- base::Unretained(mechanism_)));
+ content::GetUIThreadTaskRunner({base::TaskPriority::USER_VISIBLE})
+ ->PostTask(FROM_HERE, base::BindOnce(
+ [](MechanismDelegate* mechanism) {
+ mechanism->SetThrottlingEnabled(true);
+ },
+ base::Unretained(mechanism_)));
graph->AddFrameNodeObserver(this);
graph->AddPageNodeObserver(this);
graph->RegisterObject(this);
@@ -165,13 +180,12 @@ void TabLoadingFrameNavigationPolicy::OnPassedToGraph(Graph* graph) {
void TabLoadingFrameNavigationPolicy::OnTakenFromGraph(Graph* graph) {
DCHECK(IsRegistered(graph));
- base::PostTask(FROM_HERE,
- {content::BrowserThread::UI, base::TaskPriority::USER_VISIBLE},
- base::BindOnce(
- [](MechanismDelegate* mechanism) {
- mechanism->SetThrottlingEnabled(false);
- },
- base::Unretained(mechanism_)));
+ content::GetUIThreadTaskRunner({base::TaskPriority::USER_VISIBLE})
+ ->PostTask(FROM_HERE, base::BindOnce(
+ [](MechanismDelegate* mechanism) {
+ mechanism->SetThrottlingEnabled(false);
+ },
+ base::Unretained(mechanism_)));
graph->UnregisterObject(this);
graph->RemovePageNodeObserver(this);
graph->RemoveFrameNodeObserver(this);
@@ -232,7 +246,7 @@ void TabLoadingFrameNavigationPolicy::CreatePageTimeout(
void TabLoadingFrameNavigationPolicy::MaybeUpdatePageTimeout(
const PageNode* page_node,
base::TimeDelta timeout) {
- // Find or create an entry for the given |page_node|.
+ // Find the entry for the given |page_node|.
size_t i = 0;
for (; i < timeouts_.size(); ++i) {
if (timeouts_[i].page_node == page_node)
@@ -324,16 +338,18 @@ void TabLoadingFrameNavigationPolicy::StopThrottlingExpiredPages() {
// the contents. Note that |mechanism_| is expected to effectively live
// forever (it is only a testing seam, in production it is a static
// singleton), so passing base::Unretained is safe.
- base::PostTask(
- FROM_HERE,
- {content::BrowserThread::UI, base::TaskPriority::USER_VISIBLE},
- base::BindOnce(
- [](MechanismDelegate* mechanism, const WebContentsProxy& proxy) {
- auto* contents = proxy.Get();
- if (contents)
- mechanism->StopThrottling(contents, proxy.LastNavigationId());
- },
- base::Unretained(mechanism_), page_node->GetContentsProxy()));
+ content::GetUIThreadTaskRunner({base::TaskPriority::USER_VISIBLE})
+ ->PostTask(FROM_HERE, base::BindOnce(
+ [](MechanismDelegate* mechanism,
+ const WebContentsProxy& proxy) {
+ auto* contents = proxy.Get();
+ if (contents)
+ mechanism->StopThrottling(
+ contents,
+ proxy.LastNewDocNavigationId());
+ },
+ base::Unretained(mechanism_),
+ page_node->GetContentsProxy()));
}
}
diff --git a/chromium/components/performance_manager/graph/policies/tab_loading_frame_navigation_policy_unittest.cc b/chromium/components/performance_manager/graph/policies/tab_loading_frame_navigation_policy_unittest.cc
index 6332d5a19a7..262cf8edf03 100644
--- a/chromium/components/performance_manager/graph/policies/tab_loading_frame_navigation_policy_unittest.cc
+++ b/chromium/components/performance_manager/graph/policies/tab_loading_frame_navigation_policy_unittest.cc
@@ -5,16 +5,17 @@
#include "components/performance_manager/public/graph/policies/tab_loading_frame_navigation_policy.h"
#include "base/bind.h"
-#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
+#include "components/performance_manager/performance_manager_tab_helper.h"
#include "components/performance_manager/public/features.h"
#include "components/performance_manager/public/performance_manager.h"
#include "components/performance_manager/test_support/performance_manager_test_harness.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/mock_navigation_handle.h"
#include "content/public/test/navigation_simulator.h"
@@ -95,6 +96,28 @@ class TabLoadingFrameNavigationPolicyTest
quit_closure_.Reset();
}
+ // Returns the scheduled page timeout after simulating FCP.
+ base::TimeTicks SimulateFirstContentfulPaint(content::WebContents* contents,
+ base::TimeDelta fcp) {
+ base::RunLoop run_loop;
+ base::TimeTicks timeout;
+
+ auto frame = PerformanceManager::GetFrameNodeForRenderFrameHost(
+ contents->GetMainFrame());
+ PerformanceManager::CallOnGraph(
+ FROM_HERE,
+ base::BindLambdaForTesting(
+ [policy = policy(), fcp, frame, &run_loop, &timeout](Graph* graph) {
+ EXPECT_TRUE(frame.get());
+ policy->OnFirstContentfulPaintForTesting(frame.get(), fcp);
+ timeout = policy->GetPageTimeoutForTesting(frame->GetPageNode());
+ run_loop.Quit();
+ }));
+
+ run_loop.Run();
+ return timeout;
+ }
+
void ExpectThrottledPageCount(size_t expected_throttled_page_count) {
base::RunLoop run_loop;
@@ -120,8 +143,8 @@ class TabLoadingFrameNavigationPolicyTest
// Post back to the UI thread with the answer, where it will be
// validated and the run loop exited.
- base::PostTask(FROM_HERE, {content::BrowserThread::UI},
- base::BindOnce(on_ui_thread, throttled_page_count,
+ content::GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE, base::BindOnce(on_ui_thread, throttled_page_count,
timer_running));
}));
@@ -131,7 +154,8 @@ class TabLoadingFrameNavigationPolicyTest
// The MechanismDelegate calls redirect here.
MOCK_METHOD1(OnSetThrottlingEnabled, void(bool));
- MOCK_METHOD1(OnStopThrottling, void(content::WebContents*));
+ MOCK_METHOD2(OnStopThrottling,
+ void(content::WebContents*, int64_t last_navigation_id));
// Accessors.
TabLoadingFrameNavigationPolicy* policy() const { return policy_; }
@@ -154,8 +178,8 @@ class TabLoadingFrameNavigationPolicyTest
quit_closure_.Run();
}
void StopThrottling(content::WebContents* contents,
- int64_t last_navigation_id_unused) override {
- OnStopThrottling(contents);
+ int64_t last_navigation_id) override {
+ OnStopThrottling(contents, last_navigation_id);
// Time can be manually advanced as well, so we're not always in a RunLoop.
// Only try to invoke the quit closure if it has been set.
@@ -169,6 +193,35 @@ class TabLoadingFrameNavigationPolicyTest
base::TimeTicks start_;
};
+// Navigates and commits from the browser, returning the navigation id.
+int64_t NavigateAndCommitFromBrowser(content::WebContents* contents,
+ GURL gurl) {
+ std::unique_ptr<content::NavigationSimulator> simulator(
+ content::NavigationSimulator::CreateBrowserInitiated(gurl, contents));
+ simulator->Start();
+ int64_t navigation_id = simulator->GetNavigationHandle()->GetNavigationId();
+ simulator->Commit();
+ auto* pmth = PerformanceManagerTabHelper::FromWebContents(contents);
+ CHECK(pmth);
+ EXPECT_EQ(pmth->LastNavigationId(), navigation_id);
+ EXPECT_EQ(pmth->LastNewDocNavigationId(), navigation_id);
+ return navigation_id;
+}
+
+// Navigates and commits a same document navigation from the renderer.
+// Returns the navigation id.
+int64_t NavigateAndCommitSameDocFromRenderer(content::WebContents* contents,
+ GURL gurl) {
+ std::unique_ptr<content::NavigationSimulator> simulator(
+ content::NavigationSimulator::CreateRendererInitiated(
+ gurl, contents->GetMainFrame()));
+ simulator->CommitSameDocument();
+ auto* pmth = PerformanceManagerTabHelper::FromWebContents(contents);
+ CHECK(pmth);
+ EXPECT_NE(pmth->LastNavigationId(), pmth->LastNewDocNavigationId());
+ return pmth->LastNavigationId();
+}
+
} // namespace
TEST_F(TabLoadingFrameNavigationPolicyTest, OnlyHttpContentsThrottled) {
@@ -276,7 +329,7 @@ TEST_F(TabLoadingFrameNavigationPolicyTest, TimeoutWorks) {
// Navigate and throttle a first contents. It will expire at time T.
std::unique_ptr<content::WebContents> contents1 = CreateTestWebContents();
- content::NavigationSimulator::NavigateAndCommitFromBrowser(
+ int64_t navigation_id = NavigateAndCommitFromBrowser(
contents1.get(), GURL("http://www.foo1.com/"));
EXPECT_TRUE(policy()->ShouldThrottleWebContents(contents1.get()));
ExpectThrottledPageCount(1);
@@ -293,13 +346,12 @@ TEST_F(TabLoadingFrameNavigationPolicyTest, TimeoutWorks) {
// Navigate and throttle a second contents. It will expire at 1.5 T.
std::unique_ptr<content::WebContents> contents2 = CreateTestWebContents();
- content::NavigationSimulator::NavigateAndCommitFromBrowser(
- contents2.get(), GURL("https://www.foo2.com/"));
+ NavigateAndCommitFromBrowser(contents2.get(), GURL("https://www.foo2.com/"));
EXPECT_TRUE(policy()->ShouldThrottleWebContents(contents2.get()));
ExpectThrottledPageCount(2);
// Run until the first contents times out.
- EXPECT_CALL(*this, OnStopThrottling(contents1.get()));
+ EXPECT_CALL(*this, OnStopThrottling(contents1.get(), navigation_id));
RunUntilStopThrottling();
ExpectThrottledPageCount(1);
base::TimeTicks stop1 = task_environment()->GetMockTickClock()->NowTicks();
@@ -310,8 +362,8 @@ TEST_F(TabLoadingFrameNavigationPolicyTest, TimeoutWorks) {
// Navigate and throttle a third contents. It will expire at time 2 T.
std::unique_ptr<content::WebContents> contents3 = CreateTestWebContents();
- content::NavigationSimulator::NavigateAndCommitFromBrowser(
- contents3.get(), GURL("https://www.foo3.com/"));
+ navigation_id = NavigateAndCommitFromBrowser(contents3.get(),
+ GURL("https://www.foo3.com/"));
EXPECT_TRUE(policy()->ShouldThrottleWebContents(contents3.get()));
ExpectThrottledPageCount(2);
@@ -325,6 +377,12 @@ TEST_F(TabLoadingFrameNavigationPolicyTest, TimeoutWorks) {
// We are now at time 1.25 T.
ASSERT_EQ(1.25, GetRelativeTime());
+ // Do a same document navigation. This will cause another navigation commit,
+ // but the policy should remain bound to the previous navigation id.
+ int64_t same_doc_navigation_id = NavigateAndCommitSameDocFromRenderer(
+ contents3.get(), GURL("https://www.foo3.com/#somehash"));
+ ASSERT_NE(navigation_id, same_doc_navigation_id);
+
// Close the 2nd contents before the timeout expires, and expect the throttled
// count to drop. Now the timer should be running for the 3rd contents.
contents2.reset();
@@ -342,7 +400,7 @@ TEST_F(TabLoadingFrameNavigationPolicyTest, TimeoutWorks) {
ASSERT_EQ(1.6, GetRelativeTime());
// Finally, run until the third contents times out.
- EXPECT_CALL(*this, OnStopThrottling(contents3.get()));
+ EXPECT_CALL(*this, OnStopThrottling(contents3.get(), navigation_id));
RunUntilStopThrottling();
ExpectThrottledPageCount(0);
base::TimeTicks stop3 = task_environment()->GetMockTickClock()->NowTicks();
@@ -352,24 +410,117 @@ TEST_F(TabLoadingFrameNavigationPolicyTest, TimeoutWorks) {
ASSERT_EQ(2.0, GetRelativeTime());
}
+TEST_F(TabLoadingFrameNavigationPolicyTest, MinTimeoutUpdateWorks) {
+ // Navigate and throttle a contents.
+ std::unique_ptr<content::WebContents> contents1 = CreateTestWebContents();
+ content::NavigationSimulator::NavigateAndCommitFromBrowser(
+ contents1.get(), GURL("http://www.foo1.com/"));
+ EXPECT_TRUE(policy()->ShouldThrottleWebContents(contents1.get()));
+ ExpectThrottledPageCount(1);
+
+ // Simulate an FCP that would cause a minimum timeout to be applied.
+ base::TimeDelta fcp = policy()->GetMinTimeoutForTesting() /
+ policy()->GetFCPMultipleForTesting() -
+ base::TimeDelta::FromMilliseconds(100);
+ ASSERT_LT(base::TimeDelta(), fcp);
+
+ // Advance time by that amount and simulate FCP. No callbacks should fire.
+ task_environment()->FastForwardBy(fcp);
+ base::TimeTicks now = task_environment()->GetMockTickClock()->NowTicks();
+ base::TimeTicks timeout = SimulateFirstContentfulPaint(contents1.get(), fcp);
+ testing::Mock::VerifyAndClearExpectations(this);
+
+ // The timeout should be set at the minimum timeout.
+ EXPECT_EQ(now + policy()->GetMinTimeoutForTesting(), timeout);
+}
+
+TEST_F(TabLoadingFrameNavigationPolicyTest, FCPTimeoutUpdateWorks) {
+ // Navigate and throttle a contents.
+ std::unique_ptr<content::WebContents> contents1 = CreateTestWebContents();
+ content::NavigationSimulator::NavigateAndCommitFromBrowser(
+ contents1.get(), GURL("http://www.foo1.com/"));
+ EXPECT_TRUE(policy()->ShouldThrottleWebContents(contents1.get()));
+ ExpectThrottledPageCount(1);
+
+ // Simulate an FCP that will cause a timeout update.
+ base::TimeDelta fcp = policy()->GetMinTimeoutForTesting() +
+ base::TimeDelta::FromMilliseconds(100);
+
+ // Advance time by that amount and simulate FCP. No callbacks should fire.
+ task_environment()->FastForwardBy(fcp);
+ base::TimeTicks timeout = SimulateFirstContentfulPaint(contents1.get(), fcp);
+ testing::Mock::VerifyAndClearExpectations(this);
+
+ // The timeout should be set at the expected calculated timeout (to ms
+ // precision).
+ EXPECT_EQ((fcp * policy()->GetFCPMultipleForTesting()).InMilliseconds(),
+ (timeout - start()).InMilliseconds());
+}
+
+TEST_F(TabLoadingFrameNavigationPolicyTest, MaxTimeoutWorks) {
+ // Navigate and throttle a contents.
+ std::unique_ptr<content::WebContents> contents1 = CreateTestWebContents();
+ content::NavigationSimulator::NavigateAndCommitFromBrowser(
+ contents1.get(), GURL("http://www.foo1.com/"));
+ EXPECT_TRUE(policy()->ShouldThrottleWebContents(contents1.get()));
+ ExpectThrottledPageCount(1);
+
+ // Calculate an FCP that would cause a calculated timeout that exceeds the
+ // maximum.
+ base::TimeDelta fcp = policy()->GetMaxTimeoutForTesting() /
+ policy()->GetFCPMultipleForTesting() +
+ base::TimeDelta::FromMilliseconds(100);
+
+ // Advance time by that amount and simulate FCP. No callbacks should fire.
+ task_environment()->FastForwardBy(fcp);
+ base::TimeTicks timeout = SimulateFirstContentfulPaint(contents1.get(), fcp);
+ testing::Mock::VerifyAndClearExpectations(this);
+
+ // The timeout should remain at the max timeout it was initially set to.
+ EXPECT_EQ(start() + policy()->GetMaxTimeoutForTesting(), timeout);
+}
+
TEST(TabLoadingFrameNavigationThrottlesParams, FeatureParamsWork) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
features::kTabLoadingFrameNavigationThrottles,
{{"MinimumThrottleTimeoutMilliseconds", "2500"},
- {"MaximumThrottleTimeoutMilliseconds", "25000"}});
+ {"MaximumThrottleTimeoutMilliseconds", "25000"},
+ {"FCPMultiple", "3.14"}});
// Make sure the parsing works.
auto params = features::TabLoadingFrameNavigationThrottlesParams::GetParams();
EXPECT_EQ(base::TimeDelta::FromMilliseconds(2500),
params.minimum_throttle_timeout);
EXPECT_EQ(base::TimeDelta::FromSeconds(25), params.maximum_throttle_timeout);
+ EXPECT_EQ(3.14, params.fcp_multiple);
// And make sure the plumbing works.
std::unique_ptr<TabLoadingFrameNavigationPolicy> policy =
std::make_unique<TabLoadingFrameNavigationPolicy>();
EXPECT_EQ(params.minimum_throttle_timeout, policy->GetMinTimeoutForTesting());
EXPECT_EQ(params.maximum_throttle_timeout, policy->GetMaxTimeoutForTesting());
+ EXPECT_EQ(params.fcp_multiple, policy->GetFCPMultipleForTesting());
+
+ // Finally make sure that the calculation is as expected.
+
+ // An FCP of 300ms would yield 942ms, or 642ms of additional timeout. This is
+ // less than timeout_min_, so we should get that.
+ EXPECT_EQ(params.minimum_throttle_timeout,
+ policy->CalculateTimeoutFromFCPForTesting(
+ base::TimeDelta::FromMilliseconds(300)));
+
+ // An FCP of 1000ms would yield 3140ms, or 2140ms of additional timeout. This
+ // is also less than timeout_min_, so we should get that.
+ EXPECT_EQ(params.minimum_throttle_timeout,
+ policy->CalculateTimeoutFromFCPForTesting(
+ base::TimeDelta::FromMilliseconds(1000)));
+
+ // An FCP of 2000ms would yield 6280ms, or 4280ms of additional timeout. We
+ // should get that.
+ EXPECT_EQ(base::TimeDelta::FromMilliseconds(4280),
+ policy->CalculateTimeoutFromFCPForTesting(
+ base::TimeDelta::FromMilliseconds(2000)));
}
} // namespace policies
diff --git a/chromium/components/performance_manager/mechanisms/tab_loading_frame_navigation_scheduler.cc b/chromium/components/performance_manager/mechanisms/tab_loading_frame_navigation_scheduler.cc
index f1e164dc720..d5cfad06434 100644
--- a/chromium/components/performance_manager/mechanisms/tab_loading_frame_navigation_scheduler.cc
+++ b/chromium/components/performance_manager/mechanisms/tab_loading_frame_navigation_scheduler.cc
@@ -8,6 +8,7 @@
#include "components/performance_manager/public/graph/policies/tab_loading_frame_navigation_policy.h"
#include "components/performance_manager/public/performance_manager.h"
#include "components/performance_manager/public/performance_manager_main_thread_mechanism.h"
+#include "components/performance_manager/public/performance_manager_owned.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/navigation_throttle.h"
@@ -228,8 +229,10 @@ void TabLoadingFrameNavigationScheduler::StopThrottling(
// a StopThrottling notification.
auto* scheduler = FromWebContents(contents);
// There is a race between renavigations and policy messages. Only dispatch
- // this if its intended for the appropriate navigation ID.
- if (scheduler->navigation_id_ != last_navigation_id)
+ // this if the contents is still being throttled (the logic in
+ // MaybeCreateThrottleForNavigation can cause the scheduler to be deleted),
+ // and if its intended for the appropriate navigation ID.
+ if (!scheduler || scheduler->navigation_id_ != last_navigation_id)
return;
scheduler->StopThrottlingImpl();
}
@@ -273,12 +276,16 @@ void TabLoadingFrameNavigationScheduler::DidFinishNavigation(
void TabLoadingFrameNavigationScheduler::StopThrottlingImpl() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- // Release all of the throttles.
- for (auto& entry : throttles_) {
+ // Release all of the throttles. Note that releasing a throttle will cause
+ // "DidFinishNavigation" to be invoked for the associated NavigationHandle,
+ // which would modify |throttles_|. We instead take the data structure before
+ // iterating.
+ auto throttles = std::move(throttles_);
+ DCHECK(throttles_.empty());
+ for (auto& entry : throttles) {
auto* throttle = entry.second;
throttle->Resume();
}
- throttles_.clear();
// Tear down this object. This must be called last so as not to UAF ourselves.
// Note that this is always called from static functions in this translation
diff --git a/chromium/components/performance_manager/mechanisms/tab_loading_frame_navigation_scheduler_browsertest.cc b/chromium/components/performance_manager/mechanisms/tab_loading_frame_navigation_scheduler_browsertest.cc
index 02caf2c71c4..bb387262276 100644
--- a/chromium/components/performance_manager/mechanisms/tab_loading_frame_navigation_scheduler_browsertest.cc
+++ b/chromium/components/performance_manager/mechanisms/tab_loading_frame_navigation_scheduler_browsertest.cc
@@ -150,10 +150,10 @@ IN_PROC_BROWSER_TEST_F(TabLoadingFrameNavigationSchedulerTest,
// false.
base::RunLoop run_loop;
EXPECT_CALL(mock_policy_delegate_, ShouldThrottleWebContents(contents))
- .WillOnce(testing::Invoke([&run_loop](content::WebContents*) -> bool {
+ .WillOnce([&run_loop](content::WebContents*) -> bool {
run_loop.Quit();
return false;
- }));
+ });
StartNavigation(contents, url);
run_loop.Run();
auto* scheduler = GetScheduler(contents);
@@ -177,10 +177,10 @@ IN_PROC_BROWSER_TEST_F(TabLoadingFrameNavigationSchedulerTest,
{
base::RunLoop run_loop;
EXPECT_CALL(mock_policy_delegate_, ShouldThrottleWebContents(contents1))
- .WillOnce(testing::Invoke([&run_loop](content::WebContents*) -> bool {
+ .WillOnce([&run_loop](content::WebContents*) -> bool {
run_loop.Quit();
return true;
- }));
+ });
StartNavigation(contents1, url1);
run_loop.Run();
auto* scheduler = GetScheduler(contents1);
@@ -192,10 +192,10 @@ IN_PROC_BROWSER_TEST_F(TabLoadingFrameNavigationSchedulerTest,
{
base::RunLoop run_loop;
EXPECT_CALL(mock_policy_delegate_, ShouldThrottleWebContents(contents2))
- .WillOnce(testing::Invoke([&run_loop](content::WebContents*) -> bool {
+ .WillOnce([&run_loop](content::WebContents*) -> bool {
run_loop.Quit();
return true;
- }));
+ });
StartNavigation(contents2, url2);
run_loop.Run();
auto* scheduler = GetScheduler(contents2);
@@ -225,16 +225,15 @@ IN_PROC_BROWSER_TEST_F(TabLoadingFrameNavigationSchedulerTest,
base::RunLoop run_loop1;
base::RunLoop run_loop2;
EXPECT_CALL(mock_policy_delegate_, ShouldThrottleWebContents(contents))
- .WillOnce(testing::Invoke([&run_loop1](content::WebContents*) -> bool {
+ .WillOnce([&run_loop1](content::WebContents*) -> bool {
run_loop1.Quit();
return true;
- }));
+ });
EXPECT_CALL(mock_policy_delegate_, ShouldThrottleNavigation(testing::_))
- .WillOnce(
- testing::Invoke([&run_loop2](content::NavigationHandle*) -> bool {
- run_loop2.Quit();
- return true;
- }));
+ .WillOnce([&run_loop2](content::NavigationHandle*) -> bool {
+ run_loop2.Quit();
+ return true;
+ });
// Start the navigation, and expect scheduler to have been created.
StartNavigation(contents, url);
@@ -263,6 +262,68 @@ IN_PROC_BROWSER_TEST_F(TabLoadingFrameNavigationSchedulerTest,
}
IN_PROC_BROWSER_TEST_F(TabLoadingFrameNavigationSchedulerTest,
+ NavigationCancelsThrottling) {
+ GURL url(embedded_test_server()->GetURL("a.com", "/a_embeds_b.html"));
+ auto* contents = shell()->web_contents();
+
+ // Throttle the navigation, and throttle the child frame.
+ base::RunLoop run_loop1;
+ base::RunLoop run_loop2;
+ EXPECT_CALL(mock_policy_delegate_, ShouldThrottleWebContents(contents))
+ .WillOnce([&run_loop1](content::WebContents*) -> bool {
+ run_loop1.Quit();
+ return true;
+ });
+ EXPECT_CALL(mock_policy_delegate_, ShouldThrottleNavigation(testing::_))
+ .WillOnce([&run_loop2](content::NavigationHandle*) -> bool {
+ run_loop2.Quit();
+ return true;
+ });
+
+ // Start the navigation, and expect scheduler to have been created.
+ StartNavigation(contents, url);
+ run_loop1.Run();
+ auto* scheduler = GetScheduler(contents);
+ EXPECT_NE(nullptr, scheduler);
+ EXPECT_EQ(0u, scheduler->GetThrottleCountForTesting());
+ int64_t original_navigation_id = scheduler->GetNavigationIdForTesting();
+
+ // Wait for the child navigation to have started. We'll know once
+ // the policy function has been invoked, which will quit the runloop.
+ run_loop2.Run();
+
+ // At this point the child frame navigation should be throttled, waiting for
+ // the policy object to notify that the throttles should be removed.
+ EXPECT_EQ(1u, scheduler->GetThrottleCountForTesting());
+
+ // Reuse the contents for another navigation. This should result in another
+ // call to ShouldThrottleWebContents (which returns false), and the
+ // scheduler should be deleted.
+ url = embedded_test_server()->GetURL("b.com", "/b.html");
+ base::RunLoop run_loop3;
+ EXPECT_CALL(mock_policy_delegate_, ShouldThrottleWebContents(contents))
+ .WillOnce([&run_loop3](content::WebContents* contents) -> bool {
+ run_loop3.Quit();
+ return false;
+ });
+ StartNavigation(contents, url);
+ run_loop3.Run();
+ scheduler = GetScheduler(contents);
+ EXPECT_EQ(nullptr, scheduler);
+
+ // Simulate a delayed arrival of a policy message for the previous navigation
+ // and expect it to do nothing.
+ TabLoadingFrameNavigationScheduler::StopThrottling(contents,
+ original_navigation_id);
+ scheduler = GetScheduler(contents);
+ EXPECT_EQ(nullptr, scheduler);
+
+ // Wait for the load to finish so that it's not ongoing while the test
+ // fixture tears down.
+ WaitForLoad(contents);
+}
+
+IN_PROC_BROWSER_TEST_F(TabLoadingFrameNavigationSchedulerTest,
NavigationInterruptsThrottling) {
GURL url(embedded_test_server()->GetURL("a.com", "/a_embeds_b.html"));
auto* contents = shell()->web_contents();
@@ -271,16 +332,15 @@ IN_PROC_BROWSER_TEST_F(TabLoadingFrameNavigationSchedulerTest,
base::RunLoop run_loop1;
base::RunLoop run_loop2;
EXPECT_CALL(mock_policy_delegate_, ShouldThrottleWebContents(contents))
- .WillOnce(testing::Invoke([&run_loop1](content::WebContents*) -> bool {
+ .WillOnce([&run_loop1](content::WebContents*) -> bool {
run_loop1.Quit();
return true;
- }));
+ });
EXPECT_CALL(mock_policy_delegate_, ShouldThrottleNavigation(testing::_))
- .WillOnce(
- testing::Invoke([&run_loop2](content::NavigationHandle*) -> bool {
- run_loop2.Quit();
- return true;
- }));
+ .WillOnce([&run_loop2](content::NavigationHandle*) -> bool {
+ run_loop2.Quit();
+ return true;
+ });
// Start the navigation, and expect scheduler to have been created.
StartNavigation(contents, url);
@@ -288,6 +348,7 @@ IN_PROC_BROWSER_TEST_F(TabLoadingFrameNavigationSchedulerTest,
auto* scheduler = GetScheduler(contents);
EXPECT_NE(nullptr, scheduler);
EXPECT_EQ(0u, scheduler->GetThrottleCountForTesting());
+ int64_t original_navigation_id = scheduler->GetNavigationIdForTesting();
// Wait for the child navigation to have started. We'll know once
// the policy function has been invoked, which will quit the runloop.
@@ -303,17 +364,24 @@ IN_PROC_BROWSER_TEST_F(TabLoadingFrameNavigationSchedulerTest,
url = embedded_test_server()->GetURL("b.com", "/b.html");
base::RunLoop run_loop3;
EXPECT_CALL(mock_policy_delegate_, ShouldThrottleWebContents(contents))
- .WillOnce(
- testing::Invoke([&run_loop3](content::WebContents* contents) -> bool {
- run_loop3.Quit();
- return true;
- }));
+ .WillOnce([&run_loop3](content::WebContents* contents) -> bool {
+ run_loop3.Quit();
+ return true;
+ });
StartNavigation(contents, url);
run_loop3.Run();
scheduler = GetScheduler(contents);
EXPECT_NE(nullptr, scheduler);
EXPECT_EQ(0u, scheduler->GetThrottleCountForTesting());
+ // Simulate a delayed arrival of a policy message for the previous navigation
+ // and expect it to do nothing.
+ TabLoadingFrameNavigationScheduler::StopThrottling(contents,
+ original_navigation_id);
+ auto* scheduler2 = GetScheduler(contents);
+ EXPECT_EQ(scheduler, scheduler2);
+ EXPECT_EQ(0u, scheduler2->GetThrottleCountForTesting());
+
// Wait for the load to finish so that it's not ongoing while the test
// fixture tears down.
WaitForLoad(contents);
diff --git a/chromium/components/performance_manager/owned_objects.h b/chromium/components/performance_manager/owned_objects.h
new file mode 100644
index 00000000000..f3735b5d266
--- /dev/null
+++ b/chromium/components/performance_manager/owned_objects.h
@@ -0,0 +1,108 @@
+// 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.
+
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_OWNED_OBJECTS_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_OWNED_OBJECTS_H_
+
+#include <memory>
+
+#include "base/check_op.h"
+#include "base/containers/flat_set.h"
+#include "base/containers/unique_ptr_adapters.h"
+#include "base/stl_util.h"
+
+namespace performance_manager {
+
+namespace internal {
+
+// Builds the callback type from the ObjectType and CallbackArgType.
+template <typename ObjectType, typename CallbackArgType>
+struct CallbackType {
+ typedef void (ObjectType::*Type)(CallbackArgType);
+};
+
+// Specialization for void CallbackArgType.
+template <typename ObjectType>
+struct CallbackType<ObjectType, void> {
+ typedef void (ObjectType::*Type)();
+};
+
+} // namespace internal
+
+// Helper class defining storage for a collection of "owned" objects. These
+// are objects whose ownership has explicitly been passed to the container.
+// The objects can be taken back from the container, or will be torn down
+// with the container. Note that the owner of this container should
+// explicitly call ReleaseObjects prior to the object being torn down; the
+// container expects to be empty at destruction.
+// TODO: Once C++17 is available, use "auto" here and simply accept the 2
+// member function pointers, deducing all other type info.
+template <typename OwnedType,
+ typename CallbackArgType,
+ typename internal::CallbackType<OwnedType, CallbackArgType>::Type
+ OnPassedMemberFunction,
+ typename internal::CallbackType<OwnedType, CallbackArgType>::Type
+ OnTakenMemberFunction>
+class OwnedObjects {
+ public:
+ OwnedObjects() = default;
+ ~OwnedObjects() { DCHECK(objects_.empty()); }
+
+ OwnedObjects(const OwnedObjects&) = delete;
+ OwnedObjects& operator=(const OwnedObjects&) = delete;
+
+ // Passes an object into this container, and invokes the OnPassedFunctionPtr.
+ template <typename... ArgTypes>
+ void PassObject(std::unique_ptr<OwnedType> object, ArgTypes... args) {
+ auto* raw = object.get();
+ DCHECK(!base::Contains(objects_, raw));
+ objects_.insert(std::move(object));
+ // We should stop using a flat_set at this point.
+ DCHECK_GE(100u, objects_.size());
+ ((raw)->*(OnPassedMemberFunction))(std::forward<ArgTypes>(args)...);
+ }
+
+ // Takes an object back from this container, and invokes the
+ // OnTakenFunctionPtr (if the object is found).
+ template <typename... ArgTypes>
+ std::unique_ptr<OwnedType> TakeObject(OwnedType* raw, ArgTypes... args) {
+ std::unique_ptr<OwnedType> object;
+ auto it = objects_.find(raw);
+ if (it != objects_.end()) {
+ DCHECK_EQ(raw, it->get());
+ // base::flat_set doesn't yet support "extract", but this is the approved
+ // way of doing this for now.
+ object = std::move(*it);
+ objects_.erase(it);
+ ((raw)->*(OnTakenMemberFunction))(std::forward<ArgTypes>(args)...);
+ }
+ return object;
+ }
+
+ // Releases all the objects owned by this container, invoking their
+ // OnTakenFunctionPtr as they are released.
+ template <typename... ArgTypes>
+ void ReleaseObjects(ArgTypes... args) {
+ // Release the last object first to be friendly with base::flat_set, which
+ // is actually a std::vector.
+ while (!objects_.empty())
+ TakeObject(objects_.rbegin()->get(), std::forward<ArgTypes>(args)...);
+ }
+
+ // Returns the current size of this container.
+ size_t size() const { return objects_.size(); }
+
+ // Returns true if this container is empty.
+ bool empty() const { return objects_.empty(); }
+
+ private:
+ // If this ever uses an STL compliant set with "extract", then modify
+ // TakeObject to use that instead!
+ base::flat_set<std::unique_ptr<OwnedType>, base::UniquePtrComparator>
+ objects_;
+};
+
+} // namespace performance_manager
+
+#endif // COMPONENTS_PERFORMANCE_MANAGER_OWNED_OBJECTS_H_ \ No newline at end of file
diff --git a/chromium/components/performance_manager/owned_objects_unittest.cc b/chromium/components/performance_manager/owned_objects_unittest.cc
new file mode 100644
index 00000000000..9939a936883
--- /dev/null
+++ b/chromium/components/performance_manager/owned_objects_unittest.cc
@@ -0,0 +1,73 @@
+// Copyright 2019 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/owned_objects.h"
+
+#include "base/memory/ptr_util.h"
+#include "base/test/gtest_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace performance_manager {
+
+namespace {
+
+class Owned {
+ public:
+ Owned() = default;
+ virtual ~Owned() { OnDestructor(); }
+
+ Owned(const Owned&) = delete;
+ Owned& operator=(const Owned) = delete;
+
+ MOCK_METHOD1(OnPassedTo, void(void*));
+ MOCK_METHOD1(OnTakenFrom, void(void*));
+ MOCK_METHOD0(OnDestructor, void());
+};
+
+} // namespace
+
+TEST(OwnedObjectsTest, ContainerWorksAsAdvertised) {
+ using Owner =
+ OwnedObjects<Owned, void*, &Owned::OnPassedTo, &Owned::OnTakenFrom>;
+ std::unique_ptr<Owner> owner = base::WrapUnique(new Owner());
+
+ std::unique_ptr<Owned> owned1 = base::WrapUnique(new Owned());
+ std::unique_ptr<Owned> owned2 = base::WrapUnique(new Owned());
+ auto* raw1 = owned1.get();
+ auto* raw2 = owned2.get();
+
+ // Pass both objects to the owner.
+ EXPECT_EQ(0u, owner->size());
+ EXPECT_CALL(*raw1, OnPassedTo(owner.get()));
+ owner->PassObject(std::move(owned1), owner.get());
+ EXPECT_EQ(1u, owner->size());
+ EXPECT_CALL(*raw2, OnPassedTo(owner.get()));
+ owner->PassObject(std::move(owned2), owner.get());
+ EXPECT_EQ(2u, owner->size());
+
+ // Take one back.
+ EXPECT_CALL(*raw1, OnTakenFrom(owner.get()));
+ owned1 = owner->TakeObject(raw1, owner.get());
+ EXPECT_EQ(1u, owner->size());
+
+ // Destroy that object and expect its destructor to have been invoked.
+ EXPECT_CALL(*raw1, OnDestructor());
+ owned1.reset();
+ raw1 = nullptr;
+
+ // Expect the container to explode if deleted with objects.
+ EXPECT_DCHECK_DEATH(owner.reset());
+
+ // Ask the container to release the remaining objects.
+ EXPECT_CALL(*raw2, OnTakenFrom(owner.get()));
+ EXPECT_CALL(*raw2, OnDestructor());
+ owner->ReleaseObjects(owner.get());
+ raw2 = nullptr;
+
+ // Destroying the container is now safe.
+ owner.reset();
+}
+
+} // namespace performance_manager
diff --git a/chromium/components/performance_manager/performance_manager.cc b/chromium/components/performance_manager/performance_manager.cc
index ea828fde963..e71417f4bdb 100644
--- a/chromium/components/performance_manager/performance_manager.cc
+++ b/chromium/components/performance_manager/performance_manager.cc
@@ -11,6 +11,7 @@
#include "components/performance_manager/performance_manager_impl.h"
#include "components/performance_manager/performance_manager_registry_impl.h"
#include "components/performance_manager/performance_manager_tab_helper.h"
+#include "components/performance_manager/public/performance_manager_owned.h"
namespace performance_manager {
@@ -62,7 +63,6 @@ base::WeakPtr<PageNode> PerformanceManager::GetPageNodeForWebContents(
PerformanceManagerTabHelper::FromWebContents(wc);
if (!helper)
return nullptr;
-
return helper->page_node()->GetWeakPtr();
}
@@ -109,4 +109,43 @@ bool PerformanceManager::HasMechanism(
return PerformanceManagerRegistryImpl::GetInstance()->HasMechanism(mechanism);
}
+// static
+void PerformanceManager::PassToPM(
+ std::unique_ptr<PerformanceManagerOwned> pm_owned) {
+ return PerformanceManagerRegistryImpl::GetInstance()->PassToPM(
+ std::move(pm_owned));
+}
+
+// static
+std::unique_ptr<PerformanceManagerOwned> PerformanceManager::TakeFromPM(
+ PerformanceManagerOwned* pm_owned) {
+ return PerformanceManagerRegistryImpl::GetInstance()->TakeFromPM(pm_owned);
+}
+
+// static
+void PerformanceManager::RegisterObject(
+ PerformanceManagerRegistered* pm_object) {
+ return PerformanceManagerRegistryImpl::GetInstance()->RegisterObject(
+ pm_object);
+}
+
+// static
+void PerformanceManager::UnregisterObject(
+ PerformanceManagerRegistered* pm_object) {
+ return PerformanceManagerRegistryImpl::GetInstance()->UnregisterObject(
+ pm_object);
+}
+
+// static
+PerformanceManagerRegistered* PerformanceManager::GetRegisteredObject(
+ uintptr_t type_id) {
+ return PerformanceManagerRegistryImpl::GetInstance()->GetRegisteredObject(
+ type_id);
+}
+
+// static
+scoped_refptr<base::SequencedTaskRunner> PerformanceManager::GetTaskRunner() {
+ return PerformanceManagerImpl::GetTaskRunner();
+}
+
} // namespace performance_manager
diff --git a/chromium/components/performance_manager/performance_manager_browsertest.cc b/chromium/components/performance_manager/performance_manager_browsertest.cc
index 37cf33fe147..0f4d18895ea 100644
--- a/chromium/components/performance_manager/performance_manager_browsertest.cc
+++ b/chromium/components/performance_manager/performance_manager_browsertest.cc
@@ -7,7 +7,6 @@
#include <utility>
#include "base/run_loop.h"
-#include "base/task/post_task.h"
#include "base/test/bind_test_util.h"
#include "components/performance_manager/public/graph/frame_node.h"
#include "components/performance_manager/public/graph/page_node.h"
@@ -15,10 +14,12 @@
#include "components/performance_manager/public/web_contents_proxy.h"
#include "components/performance_manager/test_support/performance_manager_browsertest_harness.h"
#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace performance_manager {
@@ -57,8 +58,8 @@ IN_PROC_BROWSER_TEST_F(PerformanceManagerBrowserTest,
auto call_on_graph_cb = base::BindLambdaForTesting([&]() {
EXPECT_TRUE(frame_node.get());
- base::PostTask(FROM_HERE, {content::BrowserThread::UI},
- base::BindOnce(std::move(check_rfh_on_main_thread),
+ content::GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE, base::BindOnce(std::move(check_rfh_on_main_thread),
frame_node->GetRenderFrameHostProxy()));
});
@@ -84,4 +85,34 @@ IN_PROC_BROWSER_TEST_F(PerformanceManagerBrowserTest,
run_loop_after_contents_reset.Run();
}
+IN_PROC_BROWSER_TEST_F(PerformanceManagerBrowserTest,
+ PopupOpenerTrackingWorks) {
+ // Load a page that will load a popup.
+ GURL url(embedded_test_server()->GetURL("a.com", "/a_popup_a.html"));
+ content::ShellAddedObserver shell_added_observer;
+ ASSERT_TRUE(NavigateToURL(shell(), url));
+
+ // Wait for the popup window to appear, and then wait for it to load.
+ auto* popup = shell_added_observer.GetShell();
+ ASSERT_TRUE(popup);
+ WaitForLoad(popup->web_contents());
+
+ auto* contents = shell()->web_contents();
+ auto page = PerformanceManager::GetPageNodeForWebContents(contents);
+
+ // Jump into the graph and make sure everything is connected as expected.
+ base::RunLoop run_loop;
+ PerformanceManager::CallOnGraph(
+ FROM_HERE, base::BindLambdaForTesting([&page, &run_loop]() {
+ EXPECT_TRUE(page);
+ auto* frame = page->GetMainFrameNode();
+ EXPECT_EQ(1u, frame->GetOpenedPageNodes().size());
+ auto* opened_page = *(frame->GetOpenedPageNodes().begin());
+ EXPECT_EQ(PageNode::OpenedType::kPopup, opened_page->GetOpenedType());
+ EXPECT_EQ(frame, opened_page->GetOpenerFrameNode());
+ run_loop.Quit();
+ }));
+ run_loop.Run();
+}
+
} // namespace performance_manager
diff --git a/chromium/components/performance_manager/performance_manager_impl.cc b/chromium/components/performance_manager/performance_manager_impl.cc
index 59d50aba2c8..dfd5a1f91ec 100644
--- a/chromium/components/performance_manager/performance_manager_impl.cc
+++ b/chromium/components/performance_manager/performance_manager_impl.cc
@@ -117,14 +117,14 @@ std::unique_ptr<FrameNodeImpl> PerformanceManagerImpl::CreateFrameNode(
FrameNodeImpl* parent_frame_node,
int frame_tree_node_id,
int render_frame_id,
- const base::UnguessableToken& dev_tools_token,
+ const FrameToken& frame_token,
int32_t browsing_instance_id,
int32_t site_instance_id,
FrameNodeCreationCallback creation_callback) {
return CreateNodeImpl<FrameNodeImpl>(
std::move(creation_callback), process_node, page_node, parent_frame_node,
- frame_tree_node_id, render_frame_id, dev_tools_token,
- browsing_instance_id, site_instance_id);
+ frame_tree_node_id, render_frame_id, frame_token, browsing_instance_id,
+ site_instance_id);
}
// static
diff --git a/chromium/components/performance_manager/performance_manager_impl.h b/chromium/components/performance_manager/performance_manager_impl.h
index 30579d162c0..25869114d82 100644
--- a/chromium/components/performance_manager/performance_manager_impl.h
+++ b/chromium/components/performance_manager/performance_manager_impl.h
@@ -17,6 +17,7 @@
#include "base/sequenced_task_runner.h"
#include "base/task_runner_util.h"
#include "components/performance_manager/graph/graph_impl.h"
+#include "components/performance_manager/public/graph/frame_node.h"
#include "components/performance_manager/public/graph/worker_node.h"
#include "components/performance_manager/public/performance_manager.h"
#include "components/performance_manager/public/render_process_host_proxy.h"
@@ -91,7 +92,7 @@ class PerformanceManagerImpl : public PerformanceManager {
FrameNodeImpl* parent_frame_node,
int frame_tree_node_id,
int render_frame_id,
- const base::UnguessableToken& dev_tools_token,
+ const FrameToken& frame_token,
int32_t browsing_instance_id,
int32_t site_instance_id,
FrameNodeCreationCallback creation_callback =
diff --git a/chromium/components/performance_manager/performance_manager_impl_unittest.cc b/chromium/components/performance_manager/performance_manager_impl_unittest.cc
index b5087c96c59..e07263edb1a 100644
--- a/chromium/components/performance_manager/performance_manager_impl_unittest.cc
+++ b/chromium/components/performance_manager/performance_manager_impl_unittest.cc
@@ -64,7 +64,8 @@ TEST_F(PerformanceManagerImplTest, InstantiateNodes) {
std::unique_ptr<FrameNodeImpl> frame_node =
PerformanceManagerImpl::CreateFrameNode(
process_node.get(), page_node.get(), nullptr, 0,
- ++next_render_frame_id, base::UnguessableToken::Create(), 0, 0);
+ ++next_render_frame_id, FrameToken(base::UnguessableToken::Create()),
+ 0, 0);
EXPECT_NE(nullptr, frame_node.get());
PerformanceManagerImpl::DeleteNode(std::move(frame_node));
@@ -86,29 +87,35 @@ TEST_F(PerformanceManagerImplTest, BatchDeleteNodes) {
std::unique_ptr<FrameNodeImpl> parent1_frame =
PerformanceManagerImpl::CreateFrameNode(
process_node.get(), page_node.get(), nullptr, 0,
- ++next_render_frame_id, base::UnguessableToken::Create(), 0, 0);
+ ++next_render_frame_id, FrameToken(base::UnguessableToken::Create()),
+ 0, 0);
std::unique_ptr<FrameNodeImpl> parent2_frame =
PerformanceManagerImpl::CreateFrameNode(
process_node.get(), page_node.get(), nullptr, 1,
- ++next_render_frame_id, base::UnguessableToken::Create(), 0, 0);
+ ++next_render_frame_id, FrameToken(base::UnguessableToken::Create()),
+ 0, 0);
std::unique_ptr<FrameNodeImpl> child1_frame =
PerformanceManagerImpl::CreateFrameNode(
process_node.get(), page_node.get(), parent1_frame.get(), 2,
- ++next_render_frame_id, base::UnguessableToken::Create(), 0, 0);
+ ++next_render_frame_id, FrameToken(base::UnguessableToken::Create()),
+ 0, 0);
std::unique_ptr<FrameNodeImpl> child2_frame =
PerformanceManagerImpl::CreateFrameNode(
process_node.get(), page_node.get(), parent2_frame.get(), 3,
- ++next_render_frame_id, base::UnguessableToken::Create(), 0, 0);
+ ++next_render_frame_id, FrameToken(base::UnguessableToken::Create()),
+ 0, 0);
std::vector<std::unique_ptr<NodeBase>> nodes;
for (size_t i = 0; i < 10; ++i) {
nodes.push_back(PerformanceManagerImpl::CreateFrameNode(
process_node.get(), page_node.get(), child1_frame.get(), 0,
- ++next_render_frame_id, base::UnguessableToken::Create(), 0, 0));
+ ++next_render_frame_id, FrameToken(base::UnguessableToken::Create()), 0,
+ 0));
nodes.push_back(PerformanceManagerImpl::CreateFrameNode(
process_node.get(), page_node.get(), child1_frame.get(), 1,
- ++next_render_frame_id, base::UnguessableToken::Create(), 0, 0));
+ ++next_render_frame_id, FrameToken(base::UnguessableToken::Create()), 0,
+ 0));
}
nodes.push_back(std::move(process_node));
diff --git a/chromium/components/performance_manager/performance_manager_registry.cc b/chromium/components/performance_manager/performance_manager_registry.cc
index 51fb7dd5640..db30f46e81b 100644
--- a/chromium/components/performance_manager/performance_manager_registry.cc
+++ b/chromium/components/performance_manager/performance_manager_registry.cc
@@ -5,6 +5,7 @@
#include "components/performance_manager/embedder/performance_manager_registry.h"
#include "components/performance_manager/performance_manager_registry_impl.h"
+#include "components/performance_manager/performance_manager_tab_helper.h"
namespace performance_manager {
@@ -19,4 +20,13 @@ PerformanceManagerRegistry* PerformanceManagerRegistry::GetInstance() {
return PerformanceManagerRegistryImpl::GetInstance();
}
+void PerformanceManagerRegistry::MaybeCreatePageNodeForWebContents(
+ content::WebContents* web_contents) {
+ DCHECK(web_contents);
+ // Do not attach if we're already attached.
+ if (PerformanceManagerTabHelper::FromWebContents(web_contents))
+ return;
+ CreatePageNodeForWebContents(web_contents);
+}
+
} // namespace performance_manager
diff --git a/chromium/components/performance_manager/performance_manager_registry_impl.cc b/chromium/components/performance_manager/performance_manager_registry_impl.cc
index 69fc727c616..19e93c7be6a 100644
--- a/chromium/components/performance_manager/performance_manager_registry_impl.cc
+++ b/chromium/components/performance_manager/performance_manager_registry_impl.cc
@@ -14,6 +14,7 @@
#include "components/performance_manager/public/performance_manager.h"
#include "components/performance_manager/public/performance_manager_main_thread_mechanism.h"
#include "components/performance_manager/public/performance_manager_main_thread_observer.h"
+#include "components/performance_manager/public/performance_manager_owned.h"
#include "components/performance_manager/service_worker_context_adapter.h"
#include "components/performance_manager/worker_watcher.h"
#include "content/public/browser/browser_context.h"
@@ -45,6 +46,10 @@ PerformanceManagerRegistryImpl::~PerformanceManagerRegistryImpl() {
DCHECK(!g_instance);
DCHECK(web_contents_.empty());
DCHECK(render_process_hosts_.empty());
+ DCHECK(pm_owned_.empty());
+ DCHECK(pm_registered_.empty());
+ // TODO(crbug.com/1084611): |observers_| and |mechanisms_| should also be
+ // empty by now!
}
// static
@@ -82,25 +87,51 @@ bool PerformanceManagerRegistryImpl::HasMechanism(
return mechanisms_.HasObserver(mechanism);
}
+void PerformanceManagerRegistryImpl::PassToPM(
+ std::unique_ptr<PerformanceManagerOwned> pm_owned) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ pm_owned_.PassObject(std::move(pm_owned));
+}
+
+std::unique_ptr<PerformanceManagerOwned>
+PerformanceManagerRegistryImpl::TakeFromPM(PerformanceManagerOwned* pm_owned) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return pm_owned_.TakeObject(pm_owned);
+}
+
+void PerformanceManagerRegistryImpl::RegisterObject(
+ PerformanceManagerRegistered* pm_object) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ pm_registered_.RegisterObject(pm_object);
+}
+
+void PerformanceManagerRegistryImpl::UnregisterObject(
+ PerformanceManagerRegistered* pm_object) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ pm_registered_.UnregisterObject(pm_object);
+}
+
+PerformanceManagerRegistered*
+PerformanceManagerRegistryImpl::GetRegisteredObject(uintptr_t type_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return pm_registered_.GetRegisteredObject(type_id);
+}
+
void PerformanceManagerRegistryImpl::CreatePageNodeForWebContents(
content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto result = web_contents_.insert(web_contents);
- if (result.second) {
- // Create a PerformanceManagerTabHelper if |web_contents| doesn't already
- // have one. Support for multiple calls to CreatePageNodeForWebContents()
- // with the same WebContents is required for Devtools -- see comment in
- // header file.
- PerformanceManagerTabHelper::CreateForWebContents(web_contents);
- PerformanceManagerTabHelper* tab_helper =
- PerformanceManagerTabHelper::FromWebContents(web_contents);
- DCHECK(tab_helper);
- tab_helper->SetDestructionObserver(this);
+ DCHECK(result.second);
- for (auto& observer : observers_)
- observer.OnPageNodeCreatedForWebContents(web_contents);
- }
+ PerformanceManagerTabHelper::CreateForWebContents(web_contents);
+ PerformanceManagerTabHelper* tab_helper =
+ PerformanceManagerTabHelper::FromWebContents(web_contents);
+ DCHECK(tab_helper);
+ tab_helper->SetDestructionObserver(this);
+
+ for (auto& observer : observers_)
+ observer.OnPageNodeCreatedForWebContents(web_contents);
}
PerformanceManagerRegistryImpl::Throttles
@@ -177,12 +208,17 @@ void PerformanceManagerRegistryImpl::NotifyBrowserContextRemoved(
void PerformanceManagerRegistryImpl::TearDown() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DCHECK_EQ(g_instance, this);
- g_instance = nullptr;
-
// The registry should be torn down before the PerformanceManager.
DCHECK(PerformanceManager::IsAvailable());
+ // Notify any observers of the tear down. This lets them unregister things,
+ // etc.
+ for (auto& observer : observers_)
+ observer.OnBeforePerformanceManagerDestroyed();
+
+ DCHECK_EQ(g_instance, this);
+ g_instance = nullptr;
+
// Destroy WorkerNodes before ProcessNodes, because ProcessNode checks that it
// has no associated WorkerNode when torn down.
for (auto& kv : worker_watchers_) {
@@ -214,6 +250,15 @@ void PerformanceManagerRegistryImpl::TearDown() {
render_process_host->RemoveUserData(RenderProcessUserData::UserDataKey());
}
render_process_hosts_.clear();
+
+ // Tear down PM owned objects. This lets them clear up object registrations,
+ // observers, mechanisms, etc.
+ pm_owned_.ReleaseObjects();
+
+ DCHECK(pm_owned_.empty());
+ DCHECK(pm_registered_.empty());
+ // TODO(crbug.com/1084611): |observers_| and |mechanisms_| should also be
+ // empty by now!
}
void PerformanceManagerRegistryImpl::OnPerformanceManagerTabHelperDestroying(
diff --git a/chromium/components/performance_manager/performance_manager_registry_impl.h b/chromium/components/performance_manager/performance_manager_registry_impl.h
index c9281f37338..e2109926059 100644
--- a/chromium/components/performance_manager/performance_manager_registry_impl.h
+++ b/chromium/components/performance_manager/performance_manager_registry_impl.h
@@ -13,8 +13,12 @@
#include "base/observer_list.h"
#include "base/sequence_checker.h"
#include "components/performance_manager/embedder/performance_manager_registry.h"
+#include "components/performance_manager/owned_objects.h"
#include "components/performance_manager/performance_manager_tab_helper.h"
#include "components/performance_manager/process_node_source.h"
+#include "components/performance_manager/public/performance_manager_owned.h"
+#include "components/performance_manager/public/performance_manager_registered.h"
+#include "components/performance_manager/registered_objects.h"
#include "components/performance_manager/render_process_user_data.h"
#include "components/performance_manager/tab_helper_frame_node_source.h"
@@ -57,6 +61,18 @@ class PerformanceManagerRegistryImpl
void RemoveMechanism(PerformanceManagerMainThreadMechanism* mechanism);
bool HasMechanism(PerformanceManagerMainThreadMechanism* mechanism);
+ // PM owned objects. Forwarded to from the public PerformanceManager
+ // interface. See performance_manager.h for details.
+ void PassToPM(std::unique_ptr<PerformanceManagerOwned> pm_owned);
+ std::unique_ptr<PerformanceManagerOwned> TakeFromPM(
+ PerformanceManagerOwned* pm_owned);
+
+ // PM registered objects. Forwarded to from the public PerformanceManager
+ // interface. See performance_manager.h for details.
+ void RegisterObject(PerformanceManagerRegistered* pm_object);
+ void UnregisterObject(PerformanceManagerRegistered* object);
+ PerformanceManagerRegistered* GetRegisteredObject(uintptr_t type_id);
+
// PerformanceManagerRegistry:
void CreatePageNodeForWebContents(
content::WebContents* web_contents) override;
@@ -87,6 +103,9 @@ class PerformanceManagerRegistryImpl
void EnsureProcessNodeForRenderProcessHost(
content::RenderProcessHost* render_process_host);
+ size_t GetOwnedCountForTesting() const { return pm_owned_.size(); }
+ size_t GetRegisteredCountForTesting() const { return pm_registered_.size(); }
+
private:
SEQUENCE_CHECKER(sequence_checker_);
@@ -111,6 +130,16 @@ class PerformanceManagerRegistryImpl
base::ObserverList<PerformanceManagerMainThreadObserver> observers_;
base::ObserverList<PerformanceManagerMainThreadMechanism> mechanisms_;
+
+ // Objects owned by the PM.
+ OwnedObjects<PerformanceManagerOwned,
+ /* CallbackArgType = */ void,
+ &PerformanceManagerOwned::OnPassedToPM,
+ &PerformanceManagerOwned::OnTakenFromPM>
+ pm_owned_;
+
+ // Storage for PerformanceManagerRegistered objects.
+ RegisteredObjects<PerformanceManagerRegistered> pm_registered_;
};
} // namespace performance_manager
diff --git a/chromium/components/performance_manager/performance_manager_registry_impl_unittest.cc b/chromium/components/performance_manager/performance_manager_registry_impl_unittest.cc
index 7381157f931..2e31e2a7d7e 100644
--- a/chromium/components/performance_manager/performance_manager_registry_impl_unittest.cc
+++ b/chromium/components/performance_manager/performance_manager_registry_impl_unittest.cc
@@ -5,8 +5,11 @@
#include "components/performance_manager/performance_manager_registry_impl.h"
#include "base/memory/ptr_util.h"
+#include "base/test/gtest_util.h"
#include "components/performance_manager/public/performance_manager_main_thread_mechanism.h"
#include "components/performance_manager/public/performance_manager_main_thread_observer.h"
+#include "components/performance_manager/public/performance_manager_owned.h"
+#include "components/performance_manager/public/performance_manager_registered.h"
#include "components/performance_manager/test_support/performance_manager_test_harness.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/test/mock_navigation_handle.h"
@@ -30,6 +33,7 @@ class LenientMockObserver : public PerformanceManagerMainThreadObserver {
~LenientMockObserver() override = default;
MOCK_METHOD1(OnPageNodeCreatedForWebContents, void(content::WebContents*));
+ MOCK_METHOD0(OnBeforePerformanceManagerDestroyed, void());
};
using MockObserver = ::testing::StrictMock<LenientMockObserver>;
@@ -66,8 +70,7 @@ using MockMechanism = ::testing::StrictMock<LenientMockMechanism>;
} // namespace
-TEST_F(PerformanceManagerRegistryImplTest,
- ObserverOnPageNodeCreatedForWebContents) {
+TEST_F(PerformanceManagerRegistryImplTest, ObserverWorks) {
MockObserver observer;
PerformanceManagerRegistryImpl* registry =
PerformanceManagerRegistryImpl::GetInstance();
@@ -79,7 +82,11 @@ TEST_F(PerformanceManagerRegistryImplTest,
registry->CreatePageNodeForWebContents(contents.get());
testing::Mock::VerifyAndClear(&observer);
- registry->RemoveObserver(&observer);
+ // Expect a tear down notification, and use it to unregister ourselves.
+ EXPECT_CALL(observer, OnBeforePerformanceManagerDestroyed())
+ .WillOnce(testing::Invoke(
+ [&registry, &observer]() { registry->RemoveObserver(&observer); }));
+ TearDownNow();
}
TEST_F(PerformanceManagerRegistryImplTest,
@@ -127,4 +134,119 @@ TEST_F(PerformanceManagerRegistryImplTest,
registry->RemoveMechanism(&mechanism2);
}
+namespace {
+
+class LenientOwned : public PerformanceManagerOwned {
+ public:
+ LenientOwned() = default;
+ ~LenientOwned() override { OnDestructor(); }
+
+ LenientOwned(const LenientOwned&) = delete;
+ LenientOwned& operator=(const LenientOwned) = delete;
+
+ // PerformanceManagerOwned implementation:
+ MOCK_METHOD0(OnPassedToPM, void());
+ MOCK_METHOD0(OnTakenFromPM, void());
+ MOCK_METHOD0(OnDestructor, void());
+};
+
+using Owned = testing::StrictMock<LenientOwned>;
+
+} // namespace
+
+TEST_F(PerformanceManagerRegistryImplTest, PerformanceManagerOwned) {
+ PerformanceManagerRegistryImpl* registry =
+ PerformanceManagerRegistryImpl::GetInstance();
+
+ std::unique_ptr<Owned> owned1 = base::WrapUnique(new Owned());
+ std::unique_ptr<Owned> owned2 = base::WrapUnique(new Owned());
+ auto* raw1 = owned1.get();
+ auto* raw2 = owned2.get();
+
+ // Pass both objects to the PM.
+ EXPECT_EQ(0u, registry->GetOwnedCountForTesting());
+ EXPECT_CALL(*raw1, OnPassedToPM());
+ PerformanceManager::PassToPM(std::move(owned1));
+ EXPECT_EQ(1u, registry->GetOwnedCountForTesting());
+ EXPECT_CALL(*raw2, OnPassedToPM());
+ PerformanceManager::PassToPM(std::move(owned2));
+ EXPECT_EQ(2u, registry->GetOwnedCountForTesting());
+
+ // Take one back.
+ EXPECT_CALL(*raw1, OnTakenFromPM());
+ owned1 = PerformanceManager::TakeFromPMAs<Owned>(raw1);
+ EXPECT_EQ(1u, registry->GetOwnedCountForTesting());
+
+ // Destroy that object and expect its destructor to have been invoked.
+ EXPECT_CALL(*raw1, OnDestructor());
+ owned1.reset();
+ raw1 = nullptr;
+
+ // Tear down the PM and expect the other object to die with it.
+ EXPECT_CALL(*raw2, OnTakenFromPM());
+ EXPECT_CALL(*raw2, OnDestructor());
+ TearDownNow();
+}
+
+namespace {
+
+class Foo : public PerformanceManagerRegisteredImpl<Foo> {
+ public:
+ Foo() = default;
+ ~Foo() override = default;
+};
+
+class Bar : public PerformanceManagerRegisteredImpl<Bar> {
+ public:
+ Bar() = default;
+ ~Bar() override = default;
+};
+
+} // namespace
+
+TEST_F(PerformanceManagerRegistryImplTest, RegistrationWorks) {
+ // The bulk of the tests for the registry are done in the RegisteredObjects
+ // templated base class. This simply checks the additional functionality
+ // endowed by the PM wrapper.
+
+ PerformanceManagerRegistryImpl* registry =
+ PerformanceManagerRegistryImpl::GetInstance();
+
+ // This ensures that the templated distinct TypeId generation works.
+ ASSERT_NE(Foo::TypeId(), Bar::TypeId());
+
+ EXPECT_EQ(0u, registry->GetRegisteredCountForTesting());
+ EXPECT_FALSE(PerformanceManager::GetRegisteredObjectAs<Foo>());
+ EXPECT_FALSE(PerformanceManager::GetRegisteredObjectAs<Bar>());
+
+ // Insertion works.
+ Foo foo;
+ Bar bar;
+ PerformanceManager::RegisterObject(&foo);
+ EXPECT_EQ(1u, registry->GetRegisteredCountForTesting());
+ PerformanceManager::RegisterObject(&bar);
+ EXPECT_EQ(2u, registry->GetRegisteredCountForTesting());
+ EXPECT_EQ(&foo, PerformanceManager::GetRegisteredObjectAs<Foo>());
+ EXPECT_EQ(&bar, PerformanceManager::GetRegisteredObjectAs<Bar>());
+
+ // Check the various helper functions.
+ EXPECT_EQ(&foo, Foo::GetFromPM());
+ EXPECT_EQ(&bar, Bar::GetFromPM());
+ EXPECT_TRUE(foo.IsRegisteredInPM());
+ EXPECT_TRUE(bar.IsRegisteredInPM());
+ EXPECT_FALSE(Foo::NothingRegisteredInPM());
+ EXPECT_FALSE(Bar::NothingRegisteredInPM());
+ PerformanceManager::UnregisterObject(&bar);
+ EXPECT_EQ(1u, registry->GetRegisteredCountForTesting());
+ EXPECT_EQ(&foo, Foo::GetFromPM());
+ EXPECT_FALSE(Bar::GetFromPM());
+ EXPECT_TRUE(foo.IsRegisteredInPM());
+ EXPECT_FALSE(bar.IsRegisteredInPM());
+ EXPECT_FALSE(Foo::NothingRegisteredInPM());
+ EXPECT_TRUE(Bar::NothingRegisteredInPM());
+
+ PerformanceManager::UnregisterObject(&foo);
+ EXPECT_EQ(0u, registry->GetRegisteredCountForTesting());
+}
+
} // namespace performance_manager
diff --git a/chromium/components/performance_manager/performance_manager_tab_helper.cc b/chromium/components/performance_manager/performance_manager_tab_helper.cc
index ade7ebb9120..ae47965f87a 100644
--- a/chromium/components/performance_manager/performance_manager_tab_helper.cc
+++ b/chromium/components/performance_manager/performance_manager_tab_helper.cc
@@ -25,24 +25,73 @@
namespace performance_manager {
+namespace {
+
+// Returns true if the opener relationship exists, false otherwise.
+bool ConnectWindowOpenRelationshipIfExists(PerformanceManagerTabHelper* helper,
+ content::WebContents* web_contents) {
+ // Prefer to use GetOpener() if available, as it is more specific and points
+ // directly to the frame that actually called window.open.
+ auto* opener_rfh = web_contents->GetOpener();
+ if (!opener_rfh) {
+ // If the child page is opened with "noopener" then the parent document
+ // maintains the ability to close the child, but the child can't reach back
+ // and see it's parent. In this case there will be no "Opener", but there
+ // will be an "OriginalOpener".
+ opener_rfh = web_contents->GetOriginalOpener();
+ }
+
+ if (!opener_rfh)
+ return false;
+
+ // You can't simultaneously be a portal (an embedded child element of a
+ // document loaded via the <portal> tag) and a popup (a child document
+ // loaded in a new window).
+ DCHECK(!web_contents->IsPortal());
+
+ // Connect this new page to its opener.
+ auto* opener_wc = content::WebContents::FromRenderFrameHost(opener_rfh);
+ auto* opener_helper = PerformanceManagerTabHelper::FromWebContents(opener_wc);
+ DCHECK(opener_helper); // We should already have seen the opener WC.
+
+ // On CrOS the opener can be the ChromeKeyboardWebContents, whose RFHs never
+ // make it to a "created" state, so the PM never learns about them.
+ // https://crbug.com/1090374
+ auto* opener_frame_node = opener_helper->GetFrameNode(opener_rfh);
+ if (!opener_frame_node)
+ return false;
+
+ PerformanceManagerImpl::CallOnGraphImpl(
+ FROM_HERE, base::BindOnce(&PageNodeImpl::SetOpenerFrameNodeAndOpenedType,
+ base::Unretained(helper->page_node()),
+ base::Unretained(opener_frame_node),
+ PageNode::OpenedType::kPopup));
+ return true;
+}
+
+} // namespace
+
PerformanceManagerTabHelper::PerformanceManagerTabHelper(
content::WebContents* web_contents)
: content::WebContentsObserver(web_contents) {
+ // Create the page node.
page_node_ = PerformanceManagerImpl::CreatePageNode(
WebContentsProxy(weak_factory_.GetWeakPtr()),
web_contents->GetBrowserContext()->UniqueId(),
web_contents->GetVisibleURL(),
web_contents->GetVisibility() == content::Visibility::VISIBLE,
web_contents->IsCurrentlyAudible(), web_contents->GetLastActiveTime());
- // Dispatch creation notifications for any pre-existing frames.
- std::vector<content::RenderFrameHost*> existing_frames =
- web_contents->GetAllFrames();
- for (content::RenderFrameHost* frame : existing_frames) {
- // Only send notifications for created frames, the others will generate
- // creation notifications in due course (or not at all).
- if (frame->IsRenderFrameCreated())
- RenderFrameCreated(frame);
- }
+
+ // We have an early WebContents creation hook so should see it when there is
+ // only a single frame, and it is not yet created. We sanity check that here.
+#if DCHECK_IS_ON()
+ DCHECK(!web_contents->GetMainFrame()->IsRenderFrameCreated());
+ std::vector<content::RenderFrameHost*> frames = web_contents->GetAllFrames();
+ DCHECK_EQ(1u, frames.size());
+ DCHECK_EQ(web_contents->GetMainFrame(), frames[0]);
+#endif
+
+ ConnectWindowOpenRelationshipIfExists(this, web_contents);
}
PerformanceManagerTabHelper::~PerformanceManagerTabHelper() {
@@ -117,7 +166,7 @@ void PerformanceManagerTabHelper::RenderFrameCreated(
process_node, page_node_.get(), parent_frame_node,
render_frame_host->GetFrameTreeNodeId(),
render_frame_host->GetRoutingID(),
- render_frame_host->GetDevToolsFrameToken(),
+ FrameToken(render_frame_host->GetFrameToken()),
site_instance->GetBrowsingInstanceId(), site_instance->GetId(),
base::BindOnce(
[](const GURL& url, bool is_current, FrameNodeImpl* frame_node) {
@@ -207,8 +256,14 @@ void PerformanceManagerTabHelper::RenderFrameHostChanged(
old_frame->SetIsCurrent(false);
}
if (new_frame) {
- DCHECK(!new_frame->is_current());
- new_frame->SetIsCurrent(true);
+ if (!new_frame->is_current()) {
+ new_frame->SetIsCurrent(true);
+ } else {
+ // The very first frame to be created is already
+ // current by default. In which case the swap must be
+ // from no frame to a frame.
+ DCHECK(!old_frame);
+ }
}
},
old_frame, new_frame));
@@ -229,6 +284,25 @@ void PerformanceManagerTabHelper::OnAudioStateChanged(bool audible) {
base::Unretained(page_node_.get()), audible));
}
+void PerformanceManagerTabHelper::OnFrameAudioStateChanged(
+ content::RenderFrameHost* render_frame_host,
+ bool is_audible) {
+ auto frame_it = frames_.find(render_frame_host);
+ // Ideally this would be a DCHECK, but RenderFrameHost sends out one last
+ // notification in its destructor; at this point we've already torn down the
+ // FrameNode in response to the RenderFrameDeleted which comes *before* the
+ // destructor is run.
+ if (frame_it == frames_.end()) {
+ // We should only ever see this for a frame transitioning to *not* audible.
+ DCHECK(!is_audible);
+ return;
+ }
+ auto* frame_node = frame_it->second.get();
+ PerformanceManagerImpl::CallOnGraphImpl(
+ FROM_HERE, base::BindOnce(&FrameNodeImpl::SetIsAudible,
+ base::Unretained(frame_node), is_audible));
+}
+
void PerformanceManagerTabHelper::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
if (!navigation_handle->HasCommitted())
@@ -261,7 +335,8 @@ void PerformanceManagerTabHelper::DidFinishNavigation(
// Make sure the hierarchical structure is constructed before sending signal
// to the performance manager.
- OnMainFrameNavigation(navigation_handle->GetNavigationId());
+ OnMainFrameNavigation(navigation_handle->GetNavigationId(),
+ navigation_handle->IsSameDocument());
PerformanceManagerImpl::CallOnGraphImpl(
FROM_HERE,
base::BindOnce(
@@ -283,12 +358,87 @@ void PerformanceManagerTabHelper::TitleWasSet(content::NavigationEntry* entry) {
base::Unretained(page_node_.get())));
}
+void PerformanceManagerTabHelper::InnerWebContentsAttached(
+ content::WebContents* inner_web_contents,
+ content::RenderFrameHost* render_frame_host,
+ bool /* is_full_page */) {
+ // Note that we sometimes learn of contents creation at this point (before
+ // other helpers get a chance to attach), so we need to ensure our helper
+ // exists.
+ CreateForWebContents(inner_web_contents);
+ auto* helper = FromWebContents(inner_web_contents);
+ DCHECK(helper);
+ auto* page = helper->page_node_.get();
+ DCHECK(page);
+ auto* frame = GetFrameNode(render_frame_host);
+
+ // Determine the opened type.
+ auto opened_type = PageNode::OpenedType::kInvalid;
+ if (inner_web_contents->IsPortal()) {
+ // Portals don't have openers.
+ DCHECK(!inner_web_contents->HasOpener() &&
+ !inner_web_contents->HasOriginalOpener());
+ opened_type = PageNode::OpenedType::kPortal;
+
+ // In the case of portals there can be a temporary RFH that is created that
+ // will never actually be committed to the frame tree (for which we'll never
+ // see RenderFrameCreated and RenderFrameDestroyed notifications). Find a
+ // parent that we do know about instead. Note that this is not *always*
+ // true, because portals are reusable.
+ if (!frame)
+ frame = GetFrameNode(render_frame_host->GetParent());
+ } else {
+ opened_type = PageNode::OpenedType::kGuestView;
+ // For a guest view, the RFH should already have been seen.
+
+ // Note that guest views can simultaneously have openers *and* be embedded.
+ // The embedded relationship has higher priority, but we'll fall back to
+ // using the window.open relationship if the embedded relationship is
+ // severed.
+ }
+ DCHECK_NE(PageNode::OpenedType::kInvalid, opened_type);
+ DCHECK(frame);
+
+ PerformanceManagerImpl::CallOnGraphImpl(
+ FROM_HERE, base::BindOnce(&PageNodeImpl::SetOpenerFrameNodeAndOpenedType,
+ base::Unretained(page), base::Unretained(frame),
+ opened_type));
+}
+
+void PerformanceManagerTabHelper::InnerWebContentsDetached(
+ content::WebContents* inner_web_contents) {
+ auto* helper = FromWebContents(inner_web_contents);
+ DCHECK(helper);
+
+ // Fall back to using the window.open opener if it exists. If not, simply
+ // clear the opener relationship entirely.
+ if (!ConnectWindowOpenRelationshipIfExists(helper, inner_web_contents)) {
+ PerformanceManagerImpl::CallOnGraphImpl(
+ FROM_HERE,
+ base::BindOnce(&PageNodeImpl::ClearOpenerFrameNodeAndOpenedType,
+ base::Unretained(helper->page_node())));
+ }
+}
+
void PerformanceManagerTabHelper::WebContentsDestroyed() {
+ // Remember the contents, as TearDown clears observer.
+ auto* contents = web_contents();
TearDown();
+ // Immediately remove ourselves from the WCUD. After TearDown the tab helper
+ // is in an inconsistent state. This will prevent other
+ // WCO::WebContentsDestroyed handlers from trying to access the tab helper in
+ // this inconsistent state.
+ contents->RemoveUserData(UserDataKey());
}
void PerformanceManagerTabHelper::DidUpdateFaviconURL(
+ content::RenderFrameHost* render_frame_host,
const std::vector<blink::mojom::FaviconURLPtr>& candidates) {
+ // This favicon change might have been initiated by a different frame some
+ // time ago and the main frame might have changed.
+ if (!render_frame_host->IsCurrent())
+ return;
+
// TODO(siggi): This logic belongs in the policy layer rather than here.
if (!first_time_favicon_set_) {
first_time_favicon_set_ = true;
@@ -335,6 +485,10 @@ int64_t PerformanceManagerTabHelper::LastNavigationId() const {
return last_navigation_id_;
}
+int64_t PerformanceManagerTabHelper::LastNewDocNavigationId() const {
+ return last_new_doc_navigation_id_;
+}
+
FrameNodeImpl* PerformanceManagerTabHelper::GetFrameNode(
content::RenderFrameHost* render_frame_host) {
auto it = frames_.find(render_frame_host);
@@ -349,8 +503,11 @@ void PerformanceManagerTabHelper::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
-void PerformanceManagerTabHelper::OnMainFrameNavigation(int64_t navigation_id) {
+void PerformanceManagerTabHelper::OnMainFrameNavigation(int64_t navigation_id,
+ bool same_doc) {
last_navigation_id_ = navigation_id;
+ if (!same_doc)
+ last_new_doc_navigation_id_ = navigation_id;
ukm_source_id_ =
ukm::ConvertToSourceId(navigation_id, ukm::SourceIdType::NAVIGATION_ID);
PerformanceManagerImpl::CallOnGraphImpl(
diff --git a/chromium/components/performance_manager/performance_manager_tab_helper.h b/chromium/components/performance_manager/performance_manager_tab_helper.h
index 0089b7edda8..8ffb2c7e9ae 100644
--- a/chromium/components/performance_manager/performance_manager_tab_helper.h
+++ b/chromium/components/performance_manager/performance_manager_tab_helper.h
@@ -64,16 +64,25 @@ class PerformanceManagerTabHelper
content::RenderFrameHost* new_host) override;
void OnVisibilityChanged(content::Visibility visibility) override;
void OnAudioStateChanged(bool audible) override;
+ void OnFrameAudioStateChanged(content::RenderFrameHost* render_frame_host,
+ bool is_audible) override;
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override;
void TitleWasSet(content::NavigationEntry* entry) override;
+ void InnerWebContentsAttached(content::WebContents* inner_web_contents,
+ content::RenderFrameHost* render_frame_host,
+ bool is_full_page) override;
+ void InnerWebContentsDetached(
+ content::WebContents* inner_web_contents) override;
void WebContentsDestroyed() override;
void DidUpdateFaviconURL(
+ content::RenderFrameHost* render_frame_host,
const std::vector<blink::mojom::FaviconURLPtr>& candidates) override;
// WebContentsProxyImpl overrides.
content::WebContents* GetWebContents() const override;
int64_t LastNavigationId() const override;
+ int64_t LastNewDocNavigationId() const override;
void BindDocumentCoordinationUnit(
content::RenderFrameHost* render_frame_host,
@@ -108,7 +117,7 @@ class PerformanceManagerTabHelper
// PerformanceManagerRegistry.
using WebContentsUserData<PerformanceManagerTabHelper>::CreateForWebContents;
- void OnMainFrameNavigation(int64_t navigation_id);
+ void OnMainFrameNavigation(int64_t navigation_id, bool same_doc);
std::unique_ptr<PageNodeImpl> page_node_;
ukm::SourceId ukm_source_id_ = ukm::kInvalidSourceId;
@@ -123,6 +132,10 @@ class PerformanceManagerTabHelper
// The last navigation ID that was committed to a main frame in this web
// contents.
int64_t last_navigation_id_ = 0;
+ // Similar to the above, but for the last non same-document navigation
+ // associated with this WebContents. This is always for a navigation that is
+ // older or equal to |last_navigation_id_|.
+ int64_t last_new_doc_navigation_id_ = 0;
// Maps from RenderFrameHost to the associated PM node.
std::map<content::RenderFrameHost*, std::unique_ptr<FrameNodeImpl>> frames_;
diff --git a/chromium/components/performance_manager/performance_manager_tab_helper_unittest.cc b/chromium/components/performance_manager/performance_manager_tab_helper_unittest.cc
index ae05d5e1d06..1765ae4a38f 100644
--- a/chromium/components/performance_manager/performance_manager_tab_helper_unittest.cc
+++ b/chromium/components/performance_manager/performance_manager_tab_helper_unittest.cc
@@ -14,11 +14,15 @@
#include "components/performance_manager/graph/graph_impl_operations.h"
#include "components/performance_manager/graph/page_node_impl.h"
#include "components/performance_manager/performance_manager_impl.h"
+#include "components/performance_manager/public/graph/page_node.h"
#include "components/performance_manager/render_process_user_data.h"
#include "components/performance_manager/test_support/performance_manager_test_harness.h"
#include "content/public/browser/render_process_host.h"
+#include "content/public/common/content_features.h"
#include "content/public/test/navigation_simulator.h"
+#include "content/public/test/render_frame_host_test_support.h"
#include "content/public/test/web_contents_tester.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace performance_manager {
@@ -30,6 +34,7 @@ const char kChild1Url[] = "https://child1.com/";
const char kChild2Url[] = "https://child2.com/";
const char kGrandchildUrl[] = "https://grandchild.com/";
const char kNewGrandchildUrl[] = "https://newgrandchild.com/";
+const char kCousinFreddyUrl[] = "https://cousinfreddy.com/";
class PerformanceManagerTabHelperTest : public PerformanceManagerTestHarness {
public:
@@ -249,4 +254,90 @@ TEST_F(PerformanceManagerTabHelperTest, GetFrameNode) {
EXPECT_TRUE(new_frame_node);
}
+namespace {
+
+class LenientMockPageNodeObserver : public PageNode::ObserverDefaultImpl {
+ public:
+ LenientMockPageNodeObserver() = default;
+ ~LenientMockPageNodeObserver() override = default;
+ LenientMockPageNodeObserver(const LenientMockPageNodeObserver& other) =
+ delete;
+ LenientMockPageNodeObserver& operator=(const LenientMockPageNodeObserver&) =
+ delete;
+
+ MOCK_METHOD1(OnFaviconUpdated, void(const PageNode*));
+};
+using MockPageNodeObserver = ::testing::StrictMock<LenientMockPageNodeObserver>;
+
+} // namespace
+
+TEST_F(PerformanceManagerTabHelperTest,
+ NotificationsFromInactiveFrameTreeAreIgnored) {
+ SetContents(CreateTestWebContents());
+
+ content::NavigationSimulator::NavigateAndCommitFromBrowser(web_contents(),
+ GURL(kParentUrl));
+ auto* first_nav_main_rfh = web_contents()->GetMainFrame();
+
+ content::LeaveInPendingDeletionState(first_nav_main_rfh);
+
+ content::NavigationSimulator::NavigateAndCommitFromBrowser(
+ web_contents(), GURL(kCousinFreddyUrl));
+ EXPECT_NE(web_contents()->GetMainFrame(), first_nav_main_rfh);
+
+ // Mock observer, this can only be used from the PM sequence.
+ MockPageNodeObserver observer;
+ {
+ base::RunLoop run_loop;
+ auto quit_closure = run_loop.QuitClosure();
+ PerformanceManager::CallOnGraph(
+ FROM_HERE, base::BindLambdaForTesting([&](Graph* graph) {
+ graph->AddPageNodeObserver(&observer);
+ std::move(quit_closure).Run();
+ }));
+ run_loop.Run();
+ }
+
+ auto* tab_helper =
+ PerformanceManagerTabHelper::FromWebContents(web_contents());
+ ASSERT_TRUE(tab_helper);
+
+ // The first favicon change is always ignored, call DidUpdateFaviconURL twice
+ // to ensure that the test doesn't pass simply because of that.
+ tab_helper->DidUpdateFaviconURL(first_nav_main_rfh, {});
+ tab_helper->DidUpdateFaviconURL(first_nav_main_rfh, {});
+
+ {
+ base::RunLoop run_loop;
+ auto quit_closure = run_loop.QuitClosure();
+ PerformanceManager::CallOnGraph(
+ FROM_HERE, base::BindLambdaForTesting([&]() {
+ // The observer shouldn't have been called at this point.
+ testing::Mock::VerifyAndClear(&observer);
+ std::move(quit_closure).Run();
+ // Set the expectation for the next check.
+ EXPECT_CALL(observer, OnFaviconUpdated(::testing::_));
+ }));
+ run_loop.Run();
+ }
+
+ // Sanity check to ensure that notification sent to the active main frame are
+ // forwarded. DidUpdateFaviconURL needs to be called twice as the first
+ // favicon change is always ignored.
+ tab_helper->DidUpdateFaviconURL(web_contents()->GetMainFrame(), {});
+ tab_helper->DidUpdateFaviconURL(web_contents()->GetMainFrame(), {});
+
+ {
+ base::RunLoop run_loop;
+ auto quit_closure = run_loop.QuitClosure();
+ PerformanceManager::CallOnGraph(
+ FROM_HERE, base::BindLambdaForTesting([&](Graph* graph) {
+ testing::Mock::VerifyAndClear(&observer);
+ graph->RemovePageNodeObserver(&observer);
+ std::move(quit_closure).Run();
+ }));
+ run_loop.Run();
+ }
+}
+
} // namespace performance_manager
diff --git a/chromium/components/performance_manager/performance_manager_unittest.cc b/chromium/components/performance_manager/performance_manager_unittest.cc
index 6eb0ae5c031..af8ac21c225 100644
--- a/chromium/components/performance_manager/performance_manager_unittest.cc
+++ b/chromium/components/performance_manager/performance_manager_unittest.cc
@@ -7,12 +7,12 @@
#include <utility>
#include "base/run_loop.h"
-#include "base/task/post_task.h"
#include "base/test/bind_test_util.h"
#include "components/performance_manager/public/graph/page_node.h"
#include "components/performance_manager/public/web_contents_proxy.h"
#include "components/performance_manager/test_support/performance_manager_test_harness.h"
#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -59,8 +59,8 @@ TEST_F(PerformanceManagerTest, GetPageNodeForWebContents) {
auto call_on_graph_cb = base::BindLambdaForTesting([&]() {
EXPECT_TRUE(page_node.get());
- base::PostTask(FROM_HERE, {content::BrowserThread::UI},
- base::BindOnce(std::move(check_wc_on_main_thread),
+ content::GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE, base::BindOnce(std::move(check_wc_on_main_thread),
page_node->GetContentsProxy()));
});
diff --git a/chromium/components/performance_manager/persistence/site_data/non_recording_site_data_cache.cc b/chromium/components/performance_manager/persistence/site_data/non_recording_site_data_cache.cc
index a207284070d..73c5d04f659 100644
--- a/chromium/components/performance_manager/persistence/site_data/non_recording_site_data_cache.cc
+++ b/chromium/components/performance_manager/persistence/site_data/non_recording_site_data_cache.cc
@@ -43,10 +43,14 @@ std::unique_ptr<SiteDataWriter> NonRecordingSiteDataCache::GetWriterForOrigin(
return base::WrapUnique(writer);
}
-bool NonRecordingSiteDataCache::IsRecordingForTesting() {
+bool NonRecordingSiteDataCache::IsRecordingForTesting() const {
return false;
}
+int NonRecordingSiteDataCache::Size() const {
+ return 0;
+}
+
const char* NonRecordingSiteDataCache::GetDataCacheName() {
return "NonRecordingSiteDataCache";
}
diff --git a/chromium/components/performance_manager/persistence/site_data/non_recording_site_data_cache.h b/chromium/components/performance_manager/persistence/site_data/non_recording_site_data_cache.h
index 72295ec5058..faea2ccdf03 100644
--- a/chromium/components/performance_manager/persistence/site_data/non_recording_site_data_cache.h
+++ b/chromium/components/performance_manager/persistence/site_data/non_recording_site_data_cache.h
@@ -32,7 +32,8 @@ class NonRecordingSiteDataCache : public SiteDataCache,
std::unique_ptr<SiteDataWriter> GetWriterForOrigin(
const url::Origin& origin,
performance_manager::TabVisibility tab_visibility) override;
- bool IsRecordingForTesting() override;
+ bool IsRecordingForTesting() const override;
+ int Size() const override;
// SiteDataCacheInspector:
const char* GetDataCacheName() override;
diff --git a/chromium/components/performance_manager/persistence/site_data/site_data_cache.h b/chromium/components/performance_manager/persistence/site_data/site_data_cache.h
index f37d0997fa1..6e1c430f4ad 100644
--- a/chromium/components/performance_manager/persistence/site_data/site_data_cache.h
+++ b/chromium/components/performance_manager/persistence/site_data/site_data_cache.h
@@ -36,7 +36,10 @@ class SiteDataCache {
// Indicate if the SiteDataWriter served by this data cache
// actually persist information.
- virtual bool IsRecordingForTesting() = 0;
+ virtual bool IsRecordingForTesting() const = 0;
+
+ // Returns the number of element in the cache.
+ virtual int Size() const = 0;
private:
DISALLOW_COPY_AND_ASSIGN(SiteDataCache);
diff --git a/chromium/components/performance_manager/persistence/site_data/site_data_cache_factory.cc b/chromium/components/performance_manager/persistence/site_data/site_data_cache_factory.cc
index 6789c9b60f2..a66ba58b3b0 100644
--- a/chromium/components/performance_manager/persistence/site_data/site_data_cache_factory.cc
+++ b/chromium/components/performance_manager/persistence/site_data/site_data_cache_factory.cc
@@ -23,55 +23,25 @@ namespace {
SiteDataCacheFactory* g_instance = nullptr;
} // namespace
-SiteDataCacheFactory* SiteDataCacheFactory::GetInstance() {
- return g_instance;
-}
-
SiteDataCacheFactory::SiteDataCacheFactory() {
- DETACH_FROM_SEQUENCE(sequence_checker_);
- DCHECK_EQ(nullptr, g_instance);
+ DCHECK(!g_instance);
g_instance = this;
}
SiteDataCacheFactory::~SiteDataCacheFactory() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(this, g_instance);
+ // Clear the cache map before unsetting |g_instance| as this will cause some
+ // calls to |SetDataCacheInspectorForBrowserContext|.
+ data_cache_map_.clear();
+ for (const auto& iter : data_cache_map_)
+ DCHECK_EQ(0, iter.second->Size());
g_instance = nullptr;
}
// static
-void SiteDataCacheFactory::OnBrowserContextCreatedOnUIThread(
- SiteDataCacheFactory* factory,
- content::BrowserContext* browser_context,
- content::BrowserContext* parent_context) {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- DCHECK(factory);
-
- // As |factory| will be deleted on its task runner it's safe to pass the raw
- // pointer to BindOnce, it's guaranteed that this task will run before the
- // factory.
- base::Optional<std::string> parent_context_id;
- if (parent_context) {
- DCHECK(browser_context->IsOffTheRecord());
- parent_context_id = parent_context->UniqueId();
- }
- PerformanceManagerImpl::CallOnGraphImpl(
- FROM_HERE,
- base::BindOnce(&SiteDataCacheFactory::OnBrowserContextCreated,
- base::Unretained(factory), browser_context->UniqueId(),
- browser_context->GetPath(), parent_context_id));
-}
-
-// static
-void SiteDataCacheFactory::OnBrowserContextDestroyedOnUIThread(
- SiteDataCacheFactory* factory,
- content::BrowserContext* browser_context) {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- DCHECK(factory);
- PerformanceManagerImpl::CallOnGraphImpl(
- FROM_HERE,
- base::BindOnce(&SiteDataCacheFactory::OnBrowserContextDestroyed,
- base::Unretained(factory), browser_context->UniqueId()));
+SiteDataCacheFactory* SiteDataCacheFactory::GetInstance() {
+ return g_instance;
}
SiteDataCache* SiteDataCacheFactory::GetDataCacheForBrowserContext(
@@ -106,35 +76,29 @@ void SiteDataCacheFactory::SetDataCacheInspectorForBrowserContext(
}
}
-void SiteDataCacheFactory::IsDataCacheRecordingForTesting(
- const std::string& browser_context_id,
- base::OnceCallback<void(bool)> cb) {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- PerformanceManagerImpl::CallOnGraphImpl(
- FROM_HERE, base::BindOnce(
- [](SiteDataCacheFactory* factory,
- const std::string& browser_context_id,
- base::OnceCallback<void(bool)> cb) {
- auto it =
- factory->data_cache_map_.find(browser_context_id);
- CHECK(it != factory->data_cache_map_.end());
- std::move(cb).Run(it->second->IsRecordingForTesting());
- },
- this, browser_context_id, std::move(cb)));
+bool SiteDataCacheFactory::IsDataCacheRecordingForTesting(
+ const std::string& browser_context_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ auto it = data_cache_map_.find(browser_context_id);
+ CHECK(it != data_cache_map_.end());
+ return it->second->IsRecordingForTesting();
}
-void SiteDataCacheFactory::ReplaceCacheForTesting(
+void SiteDataCacheFactory::SetCacheForTesting(
const std::string& browser_context_id,
- std::unique_ptr<SiteDataCacheImpl> cache) {
+ std::unique_ptr<SiteDataCache> cache) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- auto* cache_raw = cache.get();
- DCHECK(base::Contains(data_cache_map_, browser_context_id));
data_cache_map_.erase(browser_context_id);
data_cache_map_.emplace(browser_context_id, std::move(cache));
+}
+void SiteDataCacheFactory::SetCacheInspectorForTesting(
+ const std::string& browser_context_id,
+ SiteDataCacheInspector* inspector) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!base::Contains(data_cache_inspector_map_, browser_context_id));
- data_cache_inspector_map_.emplace(browser_context_id, cache_raw);
+ data_cache_inspector_map_.emplace(browser_context_id, inspector);
}
void SiteDataCacheFactory::OnBrowserContextCreated(
diff --git a/chromium/components/performance_manager/persistence/site_data/site_data_cache_factory.h b/chromium/components/performance_manager/persistence/site_data/site_data_cache_factory.h
index 2173d53f449..9d9c89586f2 100644
--- a/chromium/components/performance_manager/persistence/site_data/site_data_cache_factory.h
+++ b/chromium/components/performance_manager/persistence/site_data/site_data_cache_factory.h
@@ -17,7 +17,6 @@
#include "base/sequence_checker.h"
#include "base/sequenced_task_runner.h"
#include "components/performance_manager/persistence/site_data/site_data_cache.h"
-#include "components/performance_manager/public/graph/graph.h"
#include "content/public/browser/browser_context.h"
namespace content {
@@ -27,53 +26,26 @@ class BrowserContext;
namespace performance_manager {
class SiteDataCacheInspector;
-class SiteDataCacheImpl;
// This class is responsible for tracking the SiteDataCache instances associated
-// with each browser context. It is meant to be used as a bridge between the
-// browser contexts living on the UI thread and the PerformanceManager
-// sequence.
-//
-// This can be created on any sequence but it then should be passed to the
-// graph and used on the PerformanceManager sequence.
-class SiteDataCacheFactory : public GraphOwnedDefaultImpl {
+// with each browser context. Instances of this class should be created and used
+// from the same sequence (this is enforced via a sequence checker). It is the
+// counterpart of the SiteDataCacheFacadeFactory living on the UI thread.
+class SiteDataCacheFactory {
public:
SiteDataCacheFactory();
- ~SiteDataCacheFactory() override;
+ ~SiteDataCacheFactory();
- // Retrieves the currently registered instance.
- // The caller needs to ensure that the lifetime of the registered instance
- // exceeds the use of this function and the retrieved pointer.
- // This function can be called from any sequence with those caveats.
+ // Returns a pointer to the global instance.
static SiteDataCacheFactory* GetInstance();
- // Functions that should be called when a new browser context is created or
- // destroyed. They should be called from the UI thread, a task will then be
- // posted to the task_runner owned by |factory| to create the data store
- // associated with this browser context.
- //
- // If this browser context is inheriting from a parent context (e.g. if it's
- // off the record) then this parent context should be specified via
- // |parent_context|.
- static void OnBrowserContextCreatedOnUIThread(
- SiteDataCacheFactory* factory,
- content::BrowserContext* browser_context,
- content::BrowserContext* parent_context);
- static void OnBrowserContextDestroyedOnUIThread(
- SiteDataCacheFactory* factory,
- content::BrowserContext* browser_context);
-
// Returns a pointer to the data cache associated with |browser_context_id|,
// or null if there's no cache for this context yet.
- //
- // Should only be called from the Performance Manager sequence.
SiteDataCache* GetDataCacheForBrowserContext(
const std::string& browser_context_id) const;
// Returns the data cache inspector associated with |browser_context_id|, or
// null if there's no data cache inspector for this context yet.
- //
- // Should only be called from the Performance Manager sequence.
SiteDataCacheInspector* GetInspectorForBrowserContext(
const std::string& browser_context_id) const;
@@ -83,23 +55,22 @@ class SiteDataCacheFactory : public GraphOwnedDefaultImpl {
// |inspector| or |browser_context| are deleted.
// The intent is for this to be called from the SiteDataCache implementation
// class' constructors and destructors.
- //
- // Should only be called from the Performance Manager sequence.
void SetDataCacheInspectorForBrowserContext(
SiteDataCacheInspector* inspector,
const std::string& browser_context_id);
// Testing functions to check if the data cache associated with
- // |browser_context_id| is recording. This will be completed asynchronously
- // and |cb| will be called on the caller's sequence. This should be called
- // only on the UI thread.
- void IsDataCacheRecordingForTesting(const std::string& browser_context_id,
- base::OnceCallback<void(bool)> cb);
+ // |browser_context_id| is recording.
+ bool IsDataCacheRecordingForTesting(const std::string& browser_context_id);
- void ReplaceCacheForTesting(const std::string& browser_context_id,
- std::unique_ptr<SiteDataCacheImpl> cache);
+ // Set the cache for a given browser context, this will replace any existing
+ // cache.
+ void SetCacheForTesting(const std::string& browser_context_id,
+ std::unique_ptr<SiteDataCache> cache);
+
+ void SetCacheInspectorForTesting(const std::string& browser_context_id,
+ SiteDataCacheInspector* inspector);
- private:
// Implementation of the corresponding *OnUIThread public static functions
// that runs on this object's task runner.
void OnBrowserContextCreated(const std::string& browser_context_id,
@@ -107,6 +78,7 @@ class SiteDataCacheFactory : public GraphOwnedDefaultImpl {
base::Optional<std::string> parent_context_id);
void OnBrowserContextDestroyed(const std::string& browser_context_id);
+ private:
// A map that associates a BrowserContext's ID with a SiteDataCache. This
// object owns the caches.
base::flat_map<std::string, std::unique_ptr<SiteDataCache>> data_cache_map_;
diff --git a/chromium/components/performance_manager/persistence/site_data/site_data_cache_factory_unittest.cc b/chromium/components/performance_manager/persistence/site_data/site_data_cache_factory_unittest.cc
index 4383f02159c..a8b23f675bf 100644
--- a/chromium/components/performance_manager/persistence/site_data/site_data_cache_factory_unittest.cc
+++ b/chromium/components/performance_manager/persistence/site_data/site_data_cache_factory_unittest.cc
@@ -9,10 +9,12 @@
#include "base/macros.h"
#include "base/memory/ptr_util.h"
+#include "base/optional.h"
#include "base/run_loop.h"
#include "base/sequenced_task_runner.h"
#include "base/task/post_task.h"
#include "base/test/bind_test_util.h"
+#include "base/threading/sequence_bound.h"
#include "components/performance_manager/performance_manager_impl.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
@@ -23,59 +25,49 @@ namespace performance_manager {
TEST(SiteDataCacheFactoryTest, EndToEnd) {
content::BrowserTaskEnvironment task_environment;
auto performance_manager = PerformanceManagerImpl::Create(base::DoNothing());
- std::unique_ptr<SiteDataCacheFactory> factory =
- std::make_unique<SiteDataCacheFactory>();
- SiteDataCacheFactory* factory_raw = factory.get();
- PerformanceManagerImpl::CallOnGraphImpl(
- FROM_HERE,
- base::BindOnce(
- [](std::unique_ptr<SiteDataCacheFactory> site_data_cache_factory,
- performance_manager::GraphImpl* graph) {
- graph->PassToGraph(std::move(site_data_cache_factory));
- },
- std::move(factory)));
+ base::SequenceBound<SiteDataCacheFactory> cache_factory(
+ PerformanceManager::GetTaskRunner());
content::TestBrowserContext browser_context;
- SiteDataCacheFactory::OnBrowserContextCreatedOnUIThread(
- factory_raw, &browser_context, nullptr);
+ cache_factory.Post(FROM_HERE, &SiteDataCacheFactory::OnBrowserContextCreated,
+ browser_context.UniqueId(), browser_context.GetPath(),
+ base::nullopt);
{
base::RunLoop run_loop;
- PerformanceManagerImpl::CallOnGraphImpl(
+ cache_factory.PostTaskWithThisObject(
FROM_HERE,
base::BindOnce(
- [](SiteDataCacheFactory* factory,
- const std::string& browser_context_id,
- base::OnceClosure quit_closure) {
- DCHECK_NE(nullptr, factory->GetDataCacheForBrowserContext(
+ [](const std::string& browser_context_id,
+ base::OnceClosure quit_closure, SiteDataCacheFactory* factory) {
+ EXPECT_TRUE(factory);
+ EXPECT_NE(nullptr, factory->GetDataCacheForBrowserContext(
browser_context_id));
- DCHECK_NE(nullptr, factory->GetInspectorForBrowserContext(
+ EXPECT_NE(nullptr, factory->GetInspectorForBrowserContext(
browser_context_id));
std::move(quit_closure).Run();
},
- base::Unretained(factory_raw), browser_context.UniqueId(),
- run_loop.QuitClosure()));
+ browser_context.UniqueId(), run_loop.QuitClosure()));
run_loop.Run();
}
- SiteDataCacheFactory::OnBrowserContextDestroyedOnUIThread(factory_raw,
- &browser_context);
+ cache_factory.Post(FROM_HERE,
+ &SiteDataCacheFactory::OnBrowserContextDestroyed,
+ browser_context.UniqueId());
{
base::RunLoop run_loop;
- PerformanceManagerImpl::CallOnGraphImpl(
+ cache_factory.PostTaskWithThisObject(
FROM_HERE,
base::BindOnce(
- [](SiteDataCacheFactory* factory,
- const std::string& browser_context_id,
- base::OnceClosure quit_closure) {
- DCHECK_EQ(nullptr, factory->GetDataCacheForBrowserContext(
+ [](const std::string& browser_context_id,
+ base::OnceClosure quit_closure, SiteDataCacheFactory* factory) {
+ EXPECT_EQ(nullptr, factory->GetDataCacheForBrowserContext(
browser_context_id));
- DCHECK_EQ(nullptr, factory->GetInspectorForBrowserContext(
+ EXPECT_EQ(nullptr, factory->GetInspectorForBrowserContext(
browser_context_id));
std::move(quit_closure).Run();
},
- base::Unretained(factory_raw), browser_context.UniqueId(),
- run_loop.QuitClosure()));
+ browser_context.UniqueId(), run_loop.QuitClosure()));
run_loop.Run();
}
diff --git a/chromium/components/performance_manager/persistence/site_data/site_data_cache_impl.cc b/chromium/components/performance_manager/persistence/site_data/site_data_cache_impl.cc
index fafd3036fdb..96ab9fcfb97 100644
--- a/chromium/components/performance_manager/persistence/site_data/site_data_cache_impl.cc
+++ b/chromium/components/performance_manager/persistence/site_data/site_data_cache_impl.cc
@@ -63,11 +63,16 @@ std::unique_ptr<SiteDataWriter> SiteDataCacheImpl::GetWriterForOrigin(
return base::WrapUnique(data_writer);
}
-bool SiteDataCacheImpl::IsRecordingForTesting() {
+bool SiteDataCacheImpl::IsRecordingForTesting() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return true;
}
+int SiteDataCacheImpl::Size() const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return origin_data_map_.size();
+}
+
const char* SiteDataCacheImpl::GetDataCacheName() {
return "SiteDataCache";
}
diff --git a/chromium/components/performance_manager/persistence/site_data/site_data_cache_impl.h b/chromium/components/performance_manager/persistence/site_data/site_data_cache_impl.h
index 587ce99fdc9..508ad2e16ba 100644
--- a/chromium/components/performance_manager/persistence/site_data/site_data_cache_impl.h
+++ b/chromium/components/performance_manager/persistence/site_data/site_data_cache_impl.h
@@ -43,7 +43,8 @@ class SiteDataCacheImpl : public SiteDataCache,
std::unique_ptr<SiteDataWriter> GetWriterForOrigin(
const url::Origin& origin,
performance_manager::TabVisibility tab_visibility) override;
- bool IsRecordingForTesting() override;
+ bool IsRecordingForTesting() const override;
+ int Size() const override;
const SiteDataMap& origin_data_map_for_testing() const {
return origin_data_map_;
diff --git a/chromium/components/performance_manager/persistence/site_data/site_data_impl.h b/chromium/components/performance_manager/persistence/site_data/site_data_impl.h
index 55620e18649..eef1eb145af 100644
--- a/chromium/components/performance_manager/persistence/site_data/site_data_impl.h
+++ b/chromium/components/performance_manager/persistence/site_data/site_data_impl.h
@@ -27,6 +27,7 @@ namespace performance_manager {
class SiteDataCacheImpl;
class SiteDataReaderTest;
class SiteDataWriterTest;
+class MockDataCache;
FORWARD_DECLARE_TEST(SiteDataReaderTest,
DestroyingReaderCancelsPendingCallbacks);
@@ -155,6 +156,7 @@ class SiteDataImpl : public base::RefCounted<SiteDataImpl> {
friend class SiteDataImplTest;
friend class performance_manager::SiteDataReaderTest;
friend class performance_manager::SiteDataWriterTest;
+ friend class performance_manager::MockDataCache;
SiteDataImpl(const url::Origin& origin,
OnDestroyDelegate* delegate,
diff --git a/chromium/components/performance_manager/persistence/site_data/site_data_writer.h b/chromium/components/performance_manager/persistence/site_data/site_data_writer.h
index 7bd7176d4fa..eca8da39dd1 100644
--- a/chromium/components/performance_manager/persistence/site_data/site_data_writer.h
+++ b/chromium/components/performance_manager/persistence/site_data/site_data_writer.h
@@ -43,6 +43,7 @@ class SiteDataWriter {
protected:
friend class SiteDataWriterTest;
friend class SiteDataCacheImpl;
+ friend class LenientMockDataWriter;
// Protected constructor, these objects are meant to be created by a site data
// store.
diff --git a/chromium/components/performance_manager/public/decorators/page_load_tracker_decorator_helper.h b/chromium/components/performance_manager/public/decorators/page_load_tracker_decorator_helper.h
index 5ebe065fb5e..e4fa05d4960 100644
--- a/chromium/components/performance_manager/public/decorators/page_load_tracker_decorator_helper.h
+++ b/chromium/components/performance_manager/public/decorators/page_load_tracker_decorator_helper.h
@@ -12,7 +12,7 @@ namespace performance_manager {
// This class must be instantiated on the UI thread in order to maintain the
// PageLoadTracker decorator of PageNodes.
class PageLoadTrackerDecoratorHelper
- : public PerformanceManagerMainThreadObserver {
+ : public PerformanceManagerMainThreadObserverDefaultImpl {
public:
PageLoadTrackerDecoratorHelper();
~PageLoadTrackerDecoratorHelper() override;
diff --git a/chromium/components/performance_manager/public/decorators/v8_per_frame_memory_decorator.h b/chromium/components/performance_manager/public/decorators/v8_per_frame_memory_decorator.h
new file mode 100644
index 00000000000..2d3b2c59b40
--- /dev/null
+++ b/chromium/components/performance_manager/public/decorators/v8_per_frame_memory_decorator.h
@@ -0,0 +1,198 @@
+// 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.
+
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_DECORATORS_V8_PER_FRAME_MEMORY_DECORATOR_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_DECORATORS_V8_PER_FRAME_MEMORY_DECORATOR_H_
+
+#include "base/sequence_checker.h"
+#include "base/time/time.h"
+#include "components/performance_manager/public/graph/graph.h"
+#include "components/performance_manager/public/graph/graph_registered.h"
+#include "components/performance_manager/public/graph/node_data_describer.h"
+#include "components/performance_manager/public/graph/process_node.h"
+#include "content/public/common/performance_manager/v8_per_frame_memory.mojom.h"
+
+namespace performance_manager {
+
+namespace internal {
+
+// A callback that will bind a V8PerFrameMemoryReporter interface to
+// communicate with the given process. Exposed so that it can be overridden to
+// implement the interface with a test fake.
+using BindV8PerFrameMemoryReporterCallback = base::RepeatingCallback<void(
+ mojo::PendingReceiver<performance_manager::mojom::V8PerFrameMemoryReporter>,
+ RenderProcessHostProxy)>;
+
+// Sets a callback that will be used to bind the V8PerFrameMemoryReporter
+// interface. The callback is owned by the caller and must live until this
+// function is called again with nullptr.
+void SetBindV8PerFrameMemoryReporterCallbackForTesting(
+ BindV8PerFrameMemoryReporterCallback* callback);
+
+} // namespace internal
+
+// A decorator that queries each renderer process for the amount of memory used
+// by V8 in each frame.
+//
+// To start sampling create a MeasurementRequest object that specifies how
+// often to request a memory measurement. Delete the object when you no longer
+// need measurements. Measurement involves some overhead so choose the lowest
+// sampling frequency your use case needs. The decorator will use the highest
+// sampling frequency that any caller requests, and stop measurements entirely
+// when no more MeasurementRequest objects exist.
+//
+// When measurements are available the decorator attaches them to FrameData and
+// ProcessData objects that can be retrieved with FrameData::ForFrameNode and
+// ProcessData::ForProcessNode. ProcessData objects can be cleaned up when
+// MeasurementRequest objects are deleted so callers must save the measurements
+// they are interested in before releasing their MeasurementRequest.
+//
+// MeasurementRequest, FrameData and ProcessData must all be accessed on the
+// graph sequence.
+class V8PerFrameMemoryDecorator
+ : public GraphOwned,
+ public GraphRegisteredImpl<V8PerFrameMemoryDecorator>,
+ public ProcessNode::ObserverDefaultImpl,
+ public NodeDataDescriberDefaultImpl {
+ public:
+ class MeasurementRequest;
+ class FrameData;
+ class ProcessData;
+
+ V8PerFrameMemoryDecorator();
+ ~V8PerFrameMemoryDecorator() override;
+
+ V8PerFrameMemoryDecorator(const V8PerFrameMemoryDecorator&) = delete;
+ V8PerFrameMemoryDecorator& operator=(const V8PerFrameMemoryDecorator&) =
+ delete;
+
+ // GraphOwned implementation.
+ void OnPassedToGraph(Graph* graph) override;
+ void OnTakenFromGraph(Graph* graph) override;
+
+ // ProcessNodeObserver overrides.
+ void OnProcessNodeAdded(const ProcessNode* process_node) override;
+
+ // NodeDataDescriber overrides.
+ base::Value DescribeFrameNodeData(const FrameNode* node) const override;
+ base::Value DescribeProcessNodeData(const ProcessNode* node) const override;
+
+ // Returns the amount of time to wait between requests for each process.
+ // Returns a zero TimeDelta if no requests should be made.
+ base::TimeDelta GetMinTimeBetweenRequestsPerProcess() const;
+
+ private:
+ friend class MeasurementRequest;
+
+ void AddMeasurementRequest(MeasurementRequest* request);
+ void RemoveMeasurementRequest(MeasurementRequest* request);
+ void UpdateProcessMeasurementSchedules() const;
+
+ Graph* graph_ = nullptr;
+
+ // List of requests sorted by sample_frequency (lowest first).
+ std::vector<MeasurementRequest*> measurement_requests_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+};
+
+class V8PerFrameMemoryDecorator::MeasurementRequest {
+ public:
+ // Creates a MeasurementRequest but does not start the measurements. Call
+ // StartMeasurement to add it to the request list.
+ explicit MeasurementRequest(const base::TimeDelta& sample_frequency);
+
+ // Creates a MeasurementRequest and calls StartMeasurement. This will request
+ // measurements for all ProcessNode's in |graph| with frequency
+ // |sample_frequency|.
+ MeasurementRequest(const base::TimeDelta& sample_frequency, Graph* graph);
+ ~MeasurementRequest();
+
+ MeasurementRequest(const MeasurementRequest&) = delete;
+ MeasurementRequest& operator=(const MeasurementRequest&) = delete;
+
+ const base::TimeDelta& sample_frequency() const { return sample_frequency_; }
+
+ // Requests measurements for all ProcessNode's in |graph| with this object's
+ // sample frequency. This must only be called once for each
+ // MeasurementRequest.
+ void StartMeasurement(Graph* graph);
+
+ private:
+ friend class V8PerFrameMemoryDecorator;
+ void OnDecoratorUnregistered();
+
+ base::TimeDelta sample_frequency_;
+ V8PerFrameMemoryDecorator* decorator_ = nullptr;
+};
+
+class V8PerFrameMemoryDecorator::FrameData {
+ public:
+ FrameData() = default;
+ virtual ~FrameData() = default;
+
+ FrameData(const FrameData&) = delete;
+ FrameData& operator=(const FrameData&) = delete;
+
+ // Returns the number of bytes used by V8 for this frame at the last
+ // measurement.
+ uint64_t v8_bytes_used() const { return v8_bytes_used_; }
+
+ void set_v8_bytes_used(uint64_t v8_bytes_used) {
+ v8_bytes_used_ = v8_bytes_used;
+ }
+
+ // Returns FrameData for the given node, or nullptr if no measurement has
+ // been taken. The returned pointer must only be accessed on the graph
+ // sequence and may go invalid at any time after leaving the calling scope.
+ static const FrameData* ForFrameNode(const FrameNode* node);
+
+ private:
+ uint64_t v8_bytes_used_ = 0;
+};
+
+class V8PerFrameMemoryDecorator::ProcessData {
+ public:
+ ProcessData() = default;
+ virtual ~ProcessData() = default;
+
+ ProcessData(const ProcessData&) = delete;
+ ProcessData& operator=(const ProcessData&) = delete;
+
+ // Returns the number of bytes used by V8 at the last measurement in this
+ // process that could not be attributed to a frame.
+ uint64_t unassociated_v8_bytes_used() const {
+ return unassociated_v8_bytes_used_;
+ }
+
+ void set_unassociated_v8_bytes_used(uint64_t unassociated_v8_bytes_used) {
+ unassociated_v8_bytes_used_ = unassociated_v8_bytes_used;
+ }
+
+ // Returns FrameData for the given node, or nullptr if no measurement has
+ // been taken. The returned pointer must only be accessed on the graph
+ // sequence and may go invalid at any time after leaving the calling scope.
+ static const ProcessData* ForProcessNode(const ProcessNode* node);
+
+ private:
+ uint64_t unassociated_v8_bytes_used_ = 0;
+};
+
+// Wrapper that can instantiate a V8PerFrameMemoryDecorator::MeasurementRequest
+// from any sequence.
+class V8PerFrameMemoryRequest {
+ public:
+ explicit V8PerFrameMemoryRequest(const base::TimeDelta& sample_frequency);
+ ~V8PerFrameMemoryRequest();
+
+ V8PerFrameMemoryRequest(const V8PerFrameMemoryRequest&) = delete;
+ V8PerFrameMemoryRequest& operator=(const V8PerFrameMemoryRequest&) = delete;
+
+ private:
+ std::unique_ptr<V8PerFrameMemoryDecorator::MeasurementRequest> request_;
+};
+
+} // namespace performance_manager
+
+#endif // COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_DECORATORS_V8_PER_FRAME_MEMORY_DECORATOR_H_
diff --git a/chromium/components/performance_manager/public/features.h b/chromium/components/performance_manager/public/features.h
index 0c4e76f7333..2f22e6d5118 100644
--- a/chromium/components/performance_manager/public/features.h
+++ b/chromium/components/performance_manager/public/features.h
@@ -29,6 +29,11 @@ struct TabLoadingFrameNavigationThrottlesParams {
// non-primary content frames.
base::TimeDelta minimum_throttle_timeout;
base::TimeDelta maximum_throttle_timeout;
+
+ // The multiple of elapsed time from navigation start until
+ // FirstContentfulPaint (FCP) that is used in calculating the timeout to apply
+ // to the throttles.
+ double fcp_multiple;
};
} // namespace features
diff --git a/chromium/components/performance_manager/public/graph/frame_node.h b/chromium/components/performance_manager/public/graph/frame_node.h
index bf74cc4fd8c..1adfefadedb 100644
--- a/chromium/components/performance_manager/public/graph/frame_node.h
+++ b/chromium/components/performance_manager/public/graph/frame_node.h
@@ -7,6 +7,7 @@
#include "base/containers/flat_set.h"
#include "base/macros.h"
+#include "base/util/type_safety/strong_alias.h"
#include "components/performance_manager/public/frame_priority/frame_priority.h"
#include "components/performance_manager/public/graph/node.h"
#include "components/performance_manager/public/mojom/coordination_unit.mojom.h"
@@ -26,6 +27,10 @@ class ProcessNode;
class RenderFrameHostProxy;
class WorkerNode;
+// A strongly typed unguessable token for the frame tokens.
+using FrameToken =
+ util::StrongAlias<class FrameTokenTag, base::UnguessableToken>;
+
// Frame nodes form a tree structure, each FrameNode at most has one parent that
// is a FrameNode. Conceptually, a frame corresponds to a
// content::RenderFrameHost in the browser, and a content::RenderFrameImpl /
@@ -57,6 +62,7 @@ class FrameNode : public Node {
using InterventionPolicy = mojom::InterventionPolicy;
using LifecycleState = mojom::LifecycleState;
using Observer = FrameNodeObserver;
+ using PageNodeVisitor = base::RepeatingCallback<bool(const PageNode*)>;
using PriorityAndReason = frame_priority::PriorityAndReason;
class ObserverDefaultImpl;
@@ -84,9 +90,9 @@ class FrameNode : public Node {
// be current at a time. This is a constant over the lifetime of the frame.
virtual int GetFrameTreeNodeId() const = 0;
- // Gets the devtools token associated with this frame. This is a constant over
- // the lifetime of the frame.
- virtual const base::UnguessableToken& GetDevToolsToken() const = 0;
+ // Gets the unique token associated with this frame. This is a constant over
+ // the lifetime of the frame and unique across all frames for all time.
+ virtual const FrameToken& GetFrameToken() const = 0;
// Gets the ID of the browsing instance to which this frame belongs. This is a
// constant over the lifetime of the frame.
@@ -110,6 +116,17 @@ class FrameNode : public Node {
// VisitChildFrameNodes when that makes sense.
virtual const base::flat_set<const FrameNode*> GetChildFrameNodes() const = 0;
+ // Visits the page nodes that have been opened by this frame. The iteration
+ // is halted if the visitor returns false. Returns true if every call to the
+ // visitor returned true, false otherwise.
+ virtual bool VisitOpenedPageNodes(const PageNodeVisitor& visitor) const = 0;
+
+ // Returns the set of opened pages associatted with this frame. Note that
+ // this incurs a full container copy all the opened nodes. Please use
+ // VisitOpenedPageNodes when that makes sense. This can change over the
+ // lifetime of the frame.
+ virtual const base::flat_set<const PageNode*> GetOpenedPageNodes() const = 0;
+
// Returns the current lifecycle state of this frame. See
// FrameNodeObserver::OnFrameLifecycleStateChanged.
virtual LifecycleState GetLifecycleState() const = 0;
@@ -160,6 +177,9 @@ class FrameNode : public Node {
// Returns true if at least one form of the frame has been interacted with.
virtual bool HadFormInteraction() const = 0;
+ // Returns true if the frame is audible, false otherwise.
+ virtual bool IsAudible() const = 0;
+
// Returns a proxy to the RenderFrameHost associated with this node. The
// proxy may only be dereferenced on the UI thread.
virtual const RenderFrameHostProxy& GetRenderFrameHostProxy() const = 0;
@@ -224,6 +244,9 @@ class FrameNodeObserver {
// Called when the frame receives a form interaction.
virtual void OnHadFormInteractionChanged(const FrameNode* frame_node) = 0;
+ // Invoked when the IsAudible property changes.
+ virtual void OnIsAudibleChanged(const FrameNode* frame_node) = 0;
+
// Events with no property changes.
// Invoked when a non-persistent notification has been issued by the frame.
@@ -270,6 +293,7 @@ class FrameNode::ObserverDefaultImpl : public FrameNodeObserver {
const FrameNode* frame_node,
const PriorityAndReason& previous_value) override {}
void OnHadFormInteractionChanged(const FrameNode* frame_node) override {}
+ void OnIsAudibleChanged(const FrameNode* frame_node) override {}
void OnNonPersistentNotificationCreated(
const FrameNode* frame_node) override {}
void OnFirstContentfulPaint(
diff --git a/chromium/components/performance_manager/public/graph/graph.h b/chromium/components/performance_manager/public/graph/graph.h
index 74e1ae56bb6..f00a3212388 100644
--- a/chromium/components/performance_manager/public/graph/graph.h
+++ b/chromium/components/performance_manager/public/graph/graph.h
@@ -92,8 +92,8 @@ class Graph {
// registered.
template <typename DerivedType>
DerivedType* GetRegisteredObjectAs() {
- // Be sure to access the TypeId provided GraphRegisteredImpl, in case this
- // class has other TypeId implementations.
+ // Be sure to access the TypeId provided by GraphRegisteredImpl, in case
+ // this class has other TypeId implementations.
GraphRegistered* object =
GetRegisteredObject(GraphRegisteredImpl<DerivedType>::TypeId());
return static_cast<DerivedType*>(object);
diff --git a/chromium/components/performance_manager/public/graph/node_attached_data.h b/chromium/components/performance_manager/public/graph/node_attached_data.h
index 88e9561351a..51fab9fdd12 100644
--- a/chromium/components/performance_manager/public/graph/node_attached_data.h
+++ b/chromium/components/performance_manager/public/graph/node_attached_data.h
@@ -7,7 +7,7 @@
#include <memory>
-#include "base/logging.h"
+#include "base/check_op.h"
#include "base/macros.h"
namespace performance_manager {
diff --git a/chromium/components/performance_manager/public/graph/page_node.h b/chromium/components/performance_manager/public/graph/page_node.h
index d02c6ed6b8b..422c043ff72 100644
--- a/chromium/components/performance_manager/public/graph/page_node.h
+++ b/chromium/components/performance_manager/public/graph/page_node.h
@@ -5,6 +5,7 @@
#ifndef COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_GRAPH_PAGE_NODE_H_
#define COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_GRAPH_PAGE_NODE_H_
+#include <ostream>
#include <string>
#include "base/containers/flat_set.h"
@@ -33,12 +34,36 @@ class PageNode : public Node {
using Observer = PageNodeObserver;
class ObserverDefaultImpl;
+ // Reasons for which a frame can become the opener of a page.
+ enum class OpenedType {
+ // Returned if this node doesn't have an opener.
+ kInvalid,
+ // This page is a popup (the opener created it via window.open).
+ kPopup,
+ // This page is a guest view. This can be many things (<webview>, <appview>,
+ // etc) but is backed by the same inner/outer WebContents mechanism.
+ kGuestView,
+ // This page is a portal.
+ kPortal,
+ };
+
+ // Returns a string for a PageNode::OpenedType enumeration.
+ static const char* ToString(PageNode::OpenedType opened_type);
+
PageNode();
~PageNode() override;
// Returns the unique ID of the browser context that this page belongs to.
virtual const std::string& GetBrowserContextID() const = 0;
+ // Returns the opener frame node, if there is one. This may change over the
+ // lifetime of this page. See "OnOpenerFrameNodeChanged".
+ virtual const FrameNode* GetOpenerFrameNode() const = 0;
+
+ // Returns the type of relationship this node has with its opener, if it has
+ // an opener.
+ virtual OpenedType GetOpenedType() const = 0;
+
// Returns true if this page is currently visible, false otherwise.
// See PageNodeObserver::OnIsVisibleChanged.
virtual bool IsVisible() const = 0;
@@ -132,6 +157,8 @@ class PageNode : public Node {
// implement the entire interface.
class PageNodeObserver {
public:
+ using OpenedType = PageNode::OpenedType;
+
PageNodeObserver();
virtual ~PageNodeObserver();
@@ -145,6 +172,14 @@ class PageNodeObserver {
// Notifications of property changes.
+ // Invoked when this page has been assigned an opener, had the opener change,
+ // or had the opener removed. This can happen if a page is opened via
+ // window.open, webviews, portals, etc, or when that relationship is
+ // subsequently severed or reparented.
+ virtual void OnOpenerFrameNodeChanged(const PageNode* page_node,
+ const FrameNode* previous_opener,
+ OpenedType previous_opened_type) = 0;
+
// Invoked when the IsVisible property changes.
virtual void OnIsVisibleChanged(const PageNode* page_node) = 0;
@@ -207,6 +242,9 @@ class PageNode::ObserverDefaultImpl : public PageNodeObserver {
// PageNodeObserver implementation:
void OnPageNodeAdded(const PageNode* page_node) override {}
void OnBeforePageNodeRemoved(const PageNode* page_node) override {}
+ void OnOpenerFrameNodeChanged(const PageNode* page_node,
+ const FrameNode* previous_opener,
+ OpenedType previous_opened_type) override {}
void OnIsVisibleChanged(const PageNode* page_node) override {}
void OnIsAudibleChanged(const PageNode* page_node) override {}
void OnIsLoadingChanged(const PageNode* page_node) override {}
@@ -229,4 +267,8 @@ class PageNode::ObserverDefaultImpl : public PageNodeObserver {
} // namespace performance_manager
+// std::ostream support for PageNode::OpenedType.
+std::ostream& operator<<(std::ostream& os,
+ performance_manager::PageNode::OpenedType opened_type);
+
#endif // COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_GRAPH_PAGE_NODE_H_
diff --git a/chromium/components/performance_manager/public/graph/policies/tab_loading_frame_navigation_policy.h b/chromium/components/performance_manager/public/graph/policies/tab_loading_frame_navigation_policy.h
index d49831a404c..5c37649835c 100644
--- a/chromium/components/performance_manager/public/graph/policies/tab_loading_frame_navigation_policy.h
+++ b/chromium/components/performance_manager/public/graph/policies/tab_loading_frame_navigation_policy.h
@@ -39,10 +39,11 @@ namespace policies {
// - Main frames are never throttled.
// - Child frames with the same eTLD+1 as the main frame are not throttled.
// - All other frames are throttled.
-// - Throttling continues until the main frame hits hits LargestContentfulPaint.
+// - Throttling continues until the main frame hits LargestContentfulPaint.
// Unfortunately, we can't know LCP in real time, so we use
-// 2 x FirstContentfulPaint as an estimate, with an upper bound of the UMA
-// measures 95th %ile of LCP.
+// 3 x FirstContentfulPaint as an estimate, with an upper bound of
+// the UMA measures 95th %ile of LCP (both the multiple and the upper bound
+// are configurable via Finch).
//
// See design document:
//
@@ -73,11 +74,16 @@ class TabLoadingFrameNavigationPolicy
// Exposed for testing. Only safe to call on the PM thread.
size_t GetThrottledPageCountForTesting() const { return timeouts_.size(); }
bool IsTimerRunningForTesting() const { return timeout_timer_.IsRunning(); }
+ base::TimeTicks GetPageTimeoutForTesting(const PageNode* page_node) const;
- // Exposed for testing. Can be called on any sequence, as this is initialized
- // at construction and stays constant afterwards.
+ // Exposed for testing. Can be called on any sequence, as these are
+ // initialized at construction and stays constant afterwards.
base::TimeDelta GetMinTimeoutForTesting() const { return timeout_min_; }
base::TimeDelta GetMaxTimeoutForTesting() const { return timeout_max_; }
+ double GetFCPMultipleForTesting() const { return fcp_multiple_; }
+ base::TimeDelta CalculateTimeoutFromFCPForTesting(base::TimeDelta fcp) const {
+ return CalculateTimeoutFromFCP(fcp);
+ }
// Exposed for testing. Allows setting a MechanismDelegate. This should be
// done immediately after construction and *before* passing to the PM graph.
@@ -91,7 +97,16 @@ class TabLoadingFrameNavigationPolicy
mechanism_ = mechanism;
}
+ // Exposed for testing. Can only be called on PM sequence.
+ void OnFirstContentfulPaintForTesting(
+ const FrameNode* frame_node,
+ base::TimeDelta time_since_navigation_start) {
+ OnFirstContentfulPaint(frame_node, time_since_navigation_start);
+ }
+
private:
+ base::TimeDelta CalculateTimeoutFromFCP(base::TimeDelta fcp) const;
+
// PageNodeObserver:
void OnBeforePageNodeRemoved(const PageNode* page_node) override;
@@ -161,6 +176,10 @@ class TabLoadingFrameNavigationPolicy
base::TimeDelta timeout_min_;
base::TimeDelta timeout_max_;
+ // The multiple applied to FCP to calculate the throttle timeout.
+ // Configured via Finch. See features.cc.
+ double fcp_multiple_;
+
// A one shot timer that is used to timeout existing throttles. This will be
// running whenever |timeouts_| is not empty.
base::OneShotTimer timeout_timer_;
diff --git a/chromium/components/performance_manager/public/mechanisms/tab_loading_frame_navigation_scheduler.h b/chromium/components/performance_manager/public/mechanisms/tab_loading_frame_navigation_scheduler.h
index 520031b0d34..fb6e8671706 100644
--- a/chromium/components/performance_manager/public/mechanisms/tab_loading_frame_navigation_scheduler.h
+++ b/chromium/components/performance_manager/public/mechanisms/tab_loading_frame_navigation_scheduler.h
@@ -64,6 +64,7 @@ class TabLoadingFrameNavigationScheduler
static bool IsMechanismRegisteredForTesting();
void StopThrottlingForTesting() { StopThrottlingImpl(); }
size_t GetThrottleCountForTesting() const { return throttles_.size(); }
+ int64_t GetNavigationIdForTesting() const { return navigation_id_; }
private:
friend class content::WebContentsUserData<TabLoadingFrameNavigationScheduler>;
diff --git a/chromium/components/performance_manager/public/performance_manager.h b/chromium/components/performance_manager/public/performance_manager.h
index 4616099f63c..0ea87f8c671 100644
--- a/chromium/components/performance_manager/public/performance_manager.h
+++ b/chromium/components/performance_manager/public/performance_manager.h
@@ -9,7 +9,9 @@
#include "base/callback.h"
#include "base/location.h"
+#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
+#include "base/sequenced_task_runner.h"
namespace content {
class RenderFrameHost;
@@ -24,6 +26,11 @@ class GraphOwned;
class PageNode;
class PerformanceManagerMainThreadMechanism;
class PerformanceManagerMainThreadObserver;
+class PerformanceManagerOwned;
+class PerformanceManagerRegistered;
+
+template <typename DerivedType>
+class PerformanceManagerRegisteredImpl;
// The performance manager is a rendezvous point for communicating with the
// performance manager graph on its dedicated sequence.
@@ -31,6 +38,9 @@ class PerformanceManager {
public:
virtual ~PerformanceManager();
+ PerformanceManager(const PerformanceManager&) = delete;
+ PerformanceManager& operator=(const PerformanceManager&) = delete;
+
// Returns true if the performance manager is initialized. Valid to call from
// the main thread only.
static bool IsAvailable();
@@ -84,11 +94,64 @@ class PerformanceManager {
static void RemoveMechanism(PerformanceManagerMainThreadMechanism* mechanism);
static bool HasMechanism(PerformanceManagerMainThreadMechanism* mechanism);
+ // For convenience, allows you to pass ownership of an object that lives on
+ // the main thread to the performance manager. Useful for attaching observers
+ // or mechanisms that will live with the PM until it dies. If you can name the
+ // object you can also take it back via "TakeFromPM". The objects will be
+ // torn down gracefully (and their "OnTakenFromPM" functions invoked) as the
+ // PM itself is torn down.
+ static void PassToPM(std::unique_ptr<PerformanceManagerOwned> pm_owned);
+ static std::unique_ptr<PerformanceManagerOwned> TakeFromPM(
+ PerformanceManagerOwned* pm_owned);
+
+ // A TakeFromPM helper for taking back the ownership of a
+ // PerformanceManagerOwned object via its DerivedType.
+ template <typename DerivedType>
+ static std::unique_ptr<DerivedType> TakeFromPMAs(DerivedType* pm_owned) {
+ return base::WrapUnique(
+ static_cast<DerivedType*>(TakeFromPM(pm_owned).release()));
+ }
+
+ // Registers an object with the PM. It is expected that no more than one
+ // object of a given type is registered at a given moment, and that all
+ // registered objects are unregistered before PM tear-down. This allows the
+ // PM to act as a rendez-vous point for objects that live on the main thread.
+ // Combined with PerformanceManagerOwned this offers an alternative to
+ // using singletons, and brings clear ownerships and lifetime semantics.
+ static void RegisterObject(PerformanceManagerRegistered* pm_object);
+
+ // Unregisters the provided |object|, which must previously have been
+ // registered with "RegisterObject". It is expected that all registered
+ // objects are unregistered before graph tear-down.
+ static void UnregisterObject(PerformanceManagerRegistered* object);
+
+ // Returns the registered object of the given type, nullptr if none has been
+ // registered.
+ template <typename DerivedType>
+ static DerivedType* GetRegisteredObjectAs() {
+ // Be sure to access the TypeId provided by PerformanceManagerRegisteredImpl
+ // in case this class has other TypeId implementations.
+ PerformanceManagerRegistered* object = GetRegisteredObject(
+ PerformanceManagerRegisteredImpl<DerivedType>::TypeId());
+ return static_cast<DerivedType*>(object);
+ }
+
+ // Returns the performance manager graph task runner. This is safe to call
+ // from any thread at any time between the creation of the thread pool and its
+ // destruction.
+ //
+ // NOTE: Tasks posted to this sequence from any thread but the UI thread, or
+ // on the UI thread after IsAvailable() returns false, cannot safely access
+ // the graph, graphowned objects or other performance manager related objects.
+ // In practice it's preferable to use CallOnGraph() whenever possible.
+ static scoped_refptr<base::SequencedTaskRunner> GetTaskRunner();
+
protected:
PerformanceManager();
- private:
- DISALLOW_COPY_AND_ASSIGN(PerformanceManager);
+ // Retrieves the object with the given |type_id|, returning nullptr if none
+ // exists. Clients must use the GetRegisteredObjectAs wrapper instead.
+ static PerformanceManagerRegistered* GetRegisteredObject(uintptr_t type_id);
};
} // namespace performance_manager
diff --git a/chromium/components/performance_manager/public/performance_manager_main_thread_observer.h b/chromium/components/performance_manager/public/performance_manager_main_thread_observer.h
index 6ec1c58a981..d0f85a7bd92 100644
--- a/chromium/components/performance_manager/public/performance_manager_main_thread_observer.h
+++ b/chromium/components/performance_manager/public/performance_manager_main_thread_observer.h
@@ -26,10 +26,28 @@ class PerformanceManagerMainThreadObserver : public base::CheckedObserver {
virtual void OnPageNodeCreatedForWebContents(
content::WebContents* web_contents) = 0;
+ // Invoked before the PM is torn down on the main thread.
+ virtual void OnBeforePerformanceManagerDestroyed() = 0;
+
protected:
PerformanceManagerMainThreadObserver() = default;
};
+// A default implementation of the observer, with all methods stubbed out.
+class PerformanceManagerMainThreadObserverDefaultImpl
+ : public PerformanceManagerMainThreadObserver {
+ public:
+ ~PerformanceManagerMainThreadObserverDefaultImpl() override = default;
+
+ // PerformanceManagerMainThreadObserver implementation:
+ void OnPageNodeCreatedForWebContents(
+ content::WebContents* web_contents) override {}
+ void OnBeforePerformanceManagerDestroyed() override {}
+
+ protected:
+ PerformanceManagerMainThreadObserverDefaultImpl() = default;
+};
+
} // namespace performance_manager
#endif // COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_PERFORMANCE_MANAGER_MAIN_THREAD_OBSERVER_H_
diff --git a/chromium/components/performance_manager/public/performance_manager_owned.h b/chromium/components/performance_manager/public/performance_manager_owned.h
new file mode 100644
index 00000000000..f33e8d9a56b
--- /dev/null
+++ b/chromium/components/performance_manager/public/performance_manager_owned.h
@@ -0,0 +1,51 @@
+// 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.
+
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_PERFORMANCE_MANAGER_OWNED_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_PERFORMANCE_MANAGER_OWNED_H_
+
+namespace performance_manager {
+
+// Helper class for passing ownership of objects to the PerformanceManager.
+// The object is expected to live on the main thread.
+class PerformanceManagerOwned {
+ public:
+ virtual ~PerformanceManagerOwned() = default;
+
+ PerformanceManagerOwned(const PerformanceManagerOwned&) = delete;
+ PerformanceManagerOwned& operator=(const PerformanceManagerOwned&) = delete;
+
+ // Called when the object is passed into the PerformanceManager.
+ virtual void OnPassedToPM() = 0;
+
+ // Called when the object is removed from the PerformanceManager, either via
+ // an explicit call to TakeFromPM, or prior to the PerformanceManager being
+ // destroyed.
+ virtual void OnTakenFromPM() = 0;
+
+ protected:
+ PerformanceManagerOwned() = default;
+};
+
+// A default implementation of PerformanceManagerOwned.
+class PerformanceManagerOwnedDefaultImpl : public PerformanceManagerOwned {
+ public:
+ ~PerformanceManagerOwnedDefaultImpl() override = default;
+
+ PerformanceManagerOwnedDefaultImpl(
+ const PerformanceManagerOwnedDefaultImpl&) = delete;
+ PerformanceManagerOwnedDefaultImpl& operator=(
+ const PerformanceManagerOwnedDefaultImpl*) = delete;
+
+ // PerformanceManagerOwned implementation:
+ void OnPassedToPM() override {}
+ void OnTakenFromPM() override {}
+
+ protected:
+ PerformanceManagerOwnedDefaultImpl() = default;
+};
+
+} // namespace performance_manager
+
+#endif // COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_PERFORMANCE_MANAGER_OWNED_H_
diff --git a/chromium/components/performance_manager/public/performance_manager_registered.h b/chromium/components/performance_manager/public/performance_manager_registered.h
new file mode 100644
index 00000000000..df64ec21c25
--- /dev/null
+++ b/chromium/components/performance_manager/public/performance_manager_registered.h
@@ -0,0 +1,83 @@
+// 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.
+
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_PERFORMANCE_MANAGER_REGISTERED_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_PERFORMANCE_MANAGER_REGISTERED_H_
+
+#include "components/performance_manager/public/performance_manager.h"
+
+namespace performance_manager {
+
+// This provides functionality that allows an instance of a PM-associated
+// object to be looked up by type, allowing the PM to act as a rendezvous
+// point for main thread objects. It enforces singleton semantics, so there may
+// be no more than one instance of an object of a given type registered with the
+// PM at the same time. All registration and unregistration must happen on the
+// main thread. It is illegal to register more than one object of the same class
+// at a time, and all registered objects must be unregistered prior to PM tear-
+// down.
+
+template <typename SelfType>
+class PerformanceManagerRegisteredImpl;
+
+// Interface that PerformanceManager registered objects must implement. Should
+// only be implemented via PerformanceManagerRegisteredImpl.
+class PerformanceManagerRegistered {
+ public:
+ PerformanceManagerRegistered(const PerformanceManagerRegistered&) = delete;
+ PerformanceManagerRegistered& operator=(const PerformanceManagerRegistered&) =
+ delete;
+ virtual ~PerformanceManagerRegistered() = default;
+
+ // Returns the unique type of the object. Implemented by
+ // PerformanceManagerRegisteredImpl.
+ virtual uintptr_t GetTypeId() const = 0;
+
+ protected:
+ template <typename SelfType>
+ friend class PerformanceManagerRegisteredImpl;
+
+ PerformanceManagerRegistered() = default;
+};
+
+// Fully implements PerformanceManagerRegistered. Clients should derive from
+// this class.
+template <typename SelfType>
+class PerformanceManagerRegisteredImpl : public PerformanceManagerRegistered {
+ public:
+ PerformanceManagerRegisteredImpl() = default;
+ ~PerformanceManagerRegisteredImpl() override = default;
+
+ // The static TypeId associated with this class.
+ static uintptr_t TypeId() {
+ // The pointer to this object acts as a unique key that identifies the type
+ // at runtime. Note that the address of this should be taken only from a
+ // single library, as a different address will be returned from each library
+ // into which a given data type is linked. Note that if base/type_id ever
+ // becomes a thing, this should use that!
+ static constexpr int kTypeId = 0;
+ return reinterpret_cast<uintptr_t>(&kTypeId);
+ }
+
+ // PerformanceManagerRegistered implementation:
+ uintptr_t GetTypeId() const override { return TypeId(); }
+
+ // Helper function for looking up the registered object of this type from the
+ // PM. Syntactic sugar for "PerformanceManager::GetRegisteredObjectAs".
+ static SelfType* GetFromPM() {
+ return PerformanceManager::GetRegisteredObjectAs<SelfType>();
+ }
+
+ // Returns true if this object is the registered object in the PM, false
+ // otherwise. Useful for DCHECKing contract conditions.
+ bool IsRegisteredInPM() const { return GetFromPM() == this; }
+
+ // Returns true if no object of this type is registered in the PM, false
+ // otherwise. Useful for DCHECKing contract conditions.
+ static bool NothingRegisteredInPM() { return GetFromPM() == nullptr; }
+};
+
+} // namespace performance_manager
+
+#endif // COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_PERFORMANCE_MANAGER_REGISTERED_H_ \ No newline at end of file
diff --git a/chromium/components/performance_manager/public/web_contents_proxy.h b/chromium/components/performance_manager/public/web_contents_proxy.h
index 45cf541a6fd..80bc2738a25 100644
--- a/chromium/components/performance_manager/public/web_contents_proxy.h
+++ b/chromium/components/performance_manager/public/web_contents_proxy.h
@@ -42,6 +42,12 @@ class WebContentsProxy {
// on the UI thread.
int64_t LastNavigationId() const;
+ // Similar to the above, but for the last non same-document navigation
+ // associated with this WebContents. This is always for a navigation that is
+ // older or equal to "LastNavigationId". This must only be called on the UI
+ // thread.
+ int64_t LastNewDocNavigationId() const;
+
protected:
friend class PerformanceManagerTabHelper;
diff --git a/chromium/components/performance_manager/registered_objects.h b/chromium/components/performance_manager/registered_objects.h
new file mode 100644
index 00000000000..e03d84f6886
--- /dev/null
+++ b/chromium/components/performance_manager/registered_objects.h
@@ -0,0 +1,87 @@
+// Copyright 2019 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.
+
+#ifndef COMPONENTS_PERFORMANCE_MANAGER_REGISTERED_OBJECTS_H_
+#define COMPONENTS_PERFORMANCE_MANAGER_REGISTERED_OBJECTS_H_
+
+#include <type_traits>
+
+#include "base/check_op.h"
+#include "base/containers/flat_set.h"
+
+namespace performance_manager {
+
+// Container for holding registered objects. The objects are stored as raw
+// pointers. At most a single instance of an object of a given type may exist
+// at a moment. It is expected that RegisteredType satisfies the following
+// interface:
+//
+// // Returns the type id of the derived type.
+// uintptr_t GetTypeId() const;
+//
+// The container is expected to be empty by the time of its destruction.
+template <typename RegisteredType>
+class RegisteredObjects {
+ public:
+ RegisteredObjects() = default;
+ ~RegisteredObjects() { DCHECK(objects_.empty()); }
+
+ RegisteredObjects(const RegisteredObjects&) = delete;
+ RegisteredObjects& operator=(const RegisteredObjects&) = delete;
+
+ // Registers an object with this container. No more than one object of a given
+ // type may be registered at once.
+ void RegisterObject(RegisteredType* object) {
+ DCHECK_EQ(nullptr, GetRegisteredObject(object->GetTypeId()));
+ objects_.insert(object);
+ // If there are ever so many registered objects we should consider changing
+ // data structures.
+ DCHECK_GT(100u, objects_.size());
+ }
+
+ // Unregisters an object from this container. The object must previously have
+ // been registered.
+ void UnregisterObject(RegisteredType* object) {
+ DCHECK_EQ(object, GetRegisteredObject(object->GetTypeId()));
+ objects_.erase(object);
+ }
+
+ // Returns the object with the registered type, nullptr if none exists.
+ RegisteredType* GetRegisteredObject(uintptr_t type_id) {
+ auto it = objects_.find(type_id);
+ if (it == objects_.end())
+ return nullptr;
+ DCHECK_EQ((*it)->GetTypeId(), type_id);
+ return *it;
+ }
+
+ // Returns the current size of this container.
+ size_t size() const { return objects_.size(); }
+
+ // Returns true if this container is empty.
+ bool empty() const { return objects_.empty(); }
+
+ private:
+ // Comparator for registered objects. They are stored by raw pointers but
+ // sorted by their type IDs. This is a transparent comparator that also allows
+ // comparing with type IDs directly.
+ struct RegisteredComparator {
+ using is_transparent = void;
+ bool operator()(const RegisteredType* r1, const RegisteredType* r2) const {
+ return r1->GetTypeId() < r2->GetTypeId();
+ }
+ bool operator()(const RegisteredType* r1, uintptr_t type_id) const {
+ return r1->GetTypeId() < type_id;
+ }
+ bool operator()(uintptr_t type_id, const RegisteredType* r2) const {
+ return type_id < r2->GetTypeId();
+ }
+ };
+
+ base::flat_set<RegisteredType*, RegisteredComparator> objects_;
+};
+
+} // namespace performance_manager
+
+#endif // COMPONENTS_PERFORMANCE_MANAGER_REGISTERED_OBJECTS_H_ \ No newline at end of file
diff --git a/chromium/components/performance_manager/registered_objects_unittest.cc b/chromium/components/performance_manager/registered_objects_unittest.cc
new file mode 100644
index 00000000000..23270d094da
--- /dev/null
+++ b/chromium/components/performance_manager/registered_objects_unittest.cc
@@ -0,0 +1,94 @@
+// Copyright 2019 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/registered_objects.h"
+
+#include "base/memory/ptr_util.h"
+#include "base/test/gtest_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace performance_manager {
+
+namespace {
+
+class Registered {
+ public:
+ Registered() = default;
+ virtual ~Registered() = default;
+ virtual uintptr_t GetTypeId() const = 0;
+};
+
+class Foo : public Registered {
+ public:
+ Foo() = default;
+ ~Foo() override = default;
+ static uintptr_t TypeId() { return 0; }
+ uintptr_t GetTypeId() const override { return TypeId(); }
+};
+
+class Bar : public Registered {
+ public:
+ Bar() = default;
+ ~Bar() override = default;
+ static uintptr_t TypeId() { return 1; }
+ uintptr_t GetTypeId() const override { return TypeId(); }
+};
+
+} // namespace
+
+TEST(RegisteredObjectsTest, ContainerWorksAsAdvertised) {
+ std::unique_ptr<RegisteredObjects<Registered>> registry(
+ new RegisteredObjects<Registered>());
+
+ ASSERT_NE(Foo::TypeId(), Bar::TypeId());
+
+ EXPECT_FALSE(registry->GetRegisteredObject(Foo::TypeId()));
+ EXPECT_FALSE(registry->GetRegisteredObject(Bar::TypeId()));
+
+ // Insertion works.
+ Foo foo;
+ EXPECT_EQ(0u, registry->size());
+ registry->RegisterObject(&foo);
+ EXPECT_EQ(1u, registry->size());
+ EXPECT_EQ(&foo, registry->GetRegisteredObject(Foo::TypeId()));
+ EXPECT_FALSE(registry->GetRegisteredObject(Bar::TypeId()));
+
+ // Inserting again fails.
+ EXPECT_DCHECK_DEATH(registry->RegisterObject(&foo));
+
+ // Unregistered the wrong object fails.
+ Foo foo2;
+ EXPECT_DCHECK_DEATH(registry->UnregisterObject(&foo2));
+
+ // Unregistering works.
+ registry->UnregisterObject(&foo);
+ EXPECT_EQ(0u, registry->size());
+ EXPECT_FALSE(registry->GetRegisteredObject(Foo::TypeId()));
+ EXPECT_FALSE(registry->GetRegisteredObject(Bar::TypeId()));
+
+ // Unregistering again fails.
+ EXPECT_DCHECK_DEATH(registry->UnregisterObject(&foo));
+ EXPECT_DCHECK_DEATH(registry->UnregisterObject(&foo2));
+
+ // Registering multiple objects works.
+ Bar bar;
+ registry->RegisterObject(&foo);
+ EXPECT_EQ(1u, registry->size());
+ registry->RegisterObject(&bar);
+ EXPECT_EQ(2u, registry->size());
+ EXPECT_EQ(&foo, registry->GetRegisteredObject(Foo::TypeId()));
+ EXPECT_EQ(&bar, registry->GetRegisteredObject(Bar::TypeId()));
+
+ // Expect the container to explode if deleted with objects.
+ EXPECT_DCHECK_DEATH(registry.reset());
+
+ // Empty the registry.
+ registry->UnregisterObject(&bar);
+ EXPECT_EQ(1u, registry->size());
+ registry->UnregisterObject(&foo);
+ EXPECT_EQ(0u, registry->size());
+}
+
+} // namespace performance_manager
diff --git a/chromium/components/performance_manager/render_process_host_proxy_browsertest.cc b/chromium/components/performance_manager/render_process_host_proxy_browsertest.cc
index dbbef25ee3b..3c06ec94e91 100644
--- a/chromium/components/performance_manager/render_process_host_proxy_browsertest.cc
+++ b/chromium/components/performance_manager/render_process_host_proxy_browsertest.cc
@@ -7,7 +7,6 @@
#include "base/bind_helpers.h"
#include "base/memory/weak_ptr.h"
#include "base/process/process.h"
-#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/test/bind_test_util.h"
#include "components/performance_manager/graph/process_node_impl.h"
@@ -65,8 +64,8 @@ IN_PROC_BROWSER_TEST_F(RenderProcessHostProxyTest,
FROM_HERE,
base::BindLambdaForTesting(
[&deref_proxy, process_node, quit_loop = run_loop.QuitClosure()]() {
- base::PostTask(
- FROM_HERE, {content::BrowserThread::UI},
+ content::GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE,
base::BindOnce(deref_proxy,
process_node->GetRenderProcessHostProxy(),
std::move(quit_loop)));
@@ -85,11 +84,11 @@ IN_PROC_BROWSER_TEST_F(RenderProcessHostProxyTest,
base::BindLambdaForTesting([&deref_proxy, process_node,
shell = this->shell(),
quit_loop = run_loop.QuitClosure()]() {
- base::PostTask(
- FROM_HERE, {content::BrowserThread::UI},
+ content::GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE,
base::BindLambdaForTesting([shell]() { shell->Close(); }));
- base::PostTask(
- FROM_HERE, {content::BrowserThread::UI},
+ content::GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE,
base::BindOnce(deref_proxy,
process_node->GetRenderProcessHostProxy(),
std::move(quit_loop)));
diff --git a/chromium/components/performance_manager/service_worker_context_adapter.cc b/chromium/components/performance_manager/service_worker_context_adapter.cc
index 0d141942d53..6617b4982f2 100644
--- a/chromium/components/performance_manager/service_worker_context_adapter.cc
+++ b/chromium/components/performance_manager/service_worker_context_adapter.cc
@@ -116,6 +116,21 @@ void ServiceWorkerContextAdapter::CountExternalRequestsForTest(
NOTIMPLEMENTED();
}
+bool ServiceWorkerContextAdapter::MaybeHasRegistrationForOrigin(
+ const url::Origin& origin) {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+void ServiceWorkerContextAdapter::WaitForRegistrationsInitializedForTest() {
+ NOTIMPLEMENTED();
+}
+
+void ServiceWorkerContextAdapter::AddRegistrationToRegisteredOriginsForTest(
+ const url::Origin& origin) {
+ NOTIMPLEMENTED();
+}
+
void ServiceWorkerContextAdapter::GetAllOriginsInfo(
GetUsageInfoCallback callback) {
NOTIMPLEMENTED();
diff --git a/chromium/components/performance_manager/service_worker_context_adapter.h b/chromium/components/performance_manager/service_worker_context_adapter.h
index ae7a8798bf6..e4fb179a8d7 100644
--- a/chromium/components/performance_manager/service_worker_context_adapter.h
+++ b/chromium/components/performance_manager/service_worker_context_adapter.h
@@ -8,9 +8,9 @@
#include <memory>
#include <string>
+#include "base/check_op.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
-#include "base/logging.h"
#include "base/observer_list.h"
#include "base/scoped_observer.h"
#include "content/public/browser/service_worker_context.h"
@@ -61,6 +61,10 @@ class ServiceWorkerContextAdapter
void CountExternalRequestsForTest(
const GURL& origin,
CountExternalRequestsCallback callback) override;
+ bool MaybeHasRegistrationForOrigin(const url::Origin& origin) override;
+ void WaitForRegistrationsInitializedForTest() override;
+ void AddRegistrationToRegisteredOriginsForTest(
+ const url::Origin& origin) override;
void GetAllOriginsInfo(GetUsageInfoCallback callback) override;
void DeleteForOrigin(const GURL& origin_url,
ResultCallback callback) override;
diff --git a/chromium/components/performance_manager/test_support/graph_test_harness.h b/chromium/components/performance_manager/test_support/graph_test_harness.h
index 0041e34f7a7..87f1236064c 100644
--- a/chromium/components/performance_manager/test_support/graph_test_harness.h
+++ b/chromium/components/performance_manager/test_support/graph_test_harness.h
@@ -79,12 +79,13 @@ struct TestNodeWrapper<FrameNodeImpl>::Factory {
FrameNodeImpl* parent_frame_node,
int frame_tree_node_id,
int render_frame_id,
- const base::UnguessableToken& token = base::UnguessableToken::Create(),
+ const FrameToken& frame_token =
+ FrameToken(base::UnguessableToken::Create()),
int32_t browsing_instance_id = 0,
int32_t site_instance_id = 0) {
return std::make_unique<FrameNodeImpl>(
process_node, page_node, parent_frame_node, frame_tree_node_id,
- render_frame_id, token, browsing_instance_id, site_instance_id);
+ render_frame_id, frame_token, browsing_instance_id, site_instance_id);
}
};
@@ -188,6 +189,22 @@ class TestGraphImpl : public GraphImpl {
int next_frame_routing_id_ = 0;
};
+// A test harness that initializes the graph without the rest of
+// PerformanceManager. Allows for creating individual nodes without going
+// through an embedder. The structs in mock_graphs.h are useful for this.
+//
+// This is intended for testing code that is entirely bound to the
+// PerformanceManager sequence. Since the PerformanceManager itself is not
+// initialized messages posted using CallOnGraph or
+// PerformanceManager::GetTaskRunner will go into the void. To test code that
+// posts to and from the PerformanceManager sequence use
+// PerformanceManagerTestHarness.
+//
+// If you need to write tests that manipulate graph nodes and also use
+// CallOnGraph, you probably want to split the code under test into a
+// sequence-bound portion that deals with the graph (tested using
+// GraphTestHarness) and an interface that marshals to the PerformanceManager
+// sequence (tested using PerformanceManagerTestHarness).
class GraphTestHarness : public ::testing::Test {
public:
GraphTestHarness();
diff --git a/chromium/components/performance_manager/test_support/performance_manager_browsertest_harness.cc b/chromium/components/performance_manager/test_support/performance_manager_browsertest_harness.cc
index aa347db9f33..8db09cdfbd2 100644
--- a/chromium/components/performance_manager/test_support/performance_manager_browsertest_harness.cc
+++ b/chromium/components/performance_manager/test_support/performance_manager_browsertest_harness.cc
@@ -10,6 +10,7 @@
#include "content/public/common/content_switches.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_content_browser_client.h"
+#include "content/shell/browser/shell_web_contents_view_delegate_creator.h"
#include "mojo/public/cpp/bindings/binder_map.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
@@ -26,8 +27,6 @@ PerformanceManagerBrowserTestHarness::~PerformanceManagerBrowserTestHarness() =
void PerformanceManagerBrowserTestHarness::PreRunTestOnMainThread() {
Super::PreRunTestOnMainThread();
- helper_->SetUp();
- helper_->OnWebContentsCreated(shell()->web_contents());
// Set up the embedded web server.
host_resolver()->AddRule("*", "127.0.0.1");
@@ -49,11 +48,20 @@ void PerformanceManagerBrowserTestHarness::SetUpCommandLine(
}
// We're a full embedder of the PM, so we have to wire up all of the embedder
-// hooks.
+// hooks. Note that this runs *before* PreRunTestOnMainThread.
void PerformanceManagerBrowserTestHarness::CreatedBrowserMainParts(
content::BrowserMainParts* browser_main_parts) {
helper_->SetUp();
+ content::ShellContentBrowserClient::Get()
+ ->set_web_contents_view_delegate_callback(
+ base::BindRepeating([](content::WebContents* contents)
+ -> content::WebContentsViewDelegate* {
+ PerformanceManagerRegistry::GetInstance()
+ ->MaybeCreatePageNodeForWebContents(contents);
+ return content::CreateShellWebContentsViewDelegate(contents);
+ }));
+
// Expose interfaces to RenderProcess.
content::ShellContentBrowserClient::Get()
->set_expose_interfaces_to_renderer_callback(base::BindRepeating(
@@ -78,7 +86,6 @@ void PerformanceManagerBrowserTestHarness::CreatedBrowserMainParts(
content::Shell* PerformanceManagerBrowserTestHarness::CreateShell() {
content::Shell* shell = CreateBrowser();
- helper_->OnWebContentsCreated(shell->web_contents());
return shell;
}
diff --git a/chromium/components/performance_manager/test_support/performance_manager_browsertest_harness.h b/chromium/components/performance_manager/test_support/performance_manager_browsertest_harness.h
index f3218a952a7..1a517113165 100644
--- a/chromium/components/performance_manager/test_support/performance_manager_browsertest_harness.h
+++ b/chromium/components/performance_manager/test_support/performance_manager_browsertest_harness.h
@@ -11,7 +11,7 @@
namespace performance_manager {
// Like the above, but for browser tests. Full process trees and live RFHs, etc,
-// are created.
+// are created. Meant to be used from components_browsertests and browser_tests.
class PerformanceManagerBrowserTestHarness
: public content::ContentBrowserTest {
using Super = content::ContentBrowserTest;
diff --git a/chromium/components/performance_manager/test_support/performance_manager_test_harness.cc b/chromium/components/performance_manager/test_support/performance_manager_test_harness.cc
index 36b274c7c90..eb6c3fb0e00 100644
--- a/chromium/components/performance_manager/test_support/performance_manager_test_harness.cc
+++ b/chromium/components/performance_manager/test_support/performance_manager_test_harness.cc
@@ -19,7 +19,8 @@ void PerformanceManagerTestHarness::SetUp() {
}
void PerformanceManagerTestHarness::TearDown() {
- helper_->TearDown();
+ if (helper_)
+ TearDownNow();
Super::TearDown();
}
@@ -31,4 +32,9 @@ PerformanceManagerTestHarness::CreateTestWebContents() {
return contents;
}
+void PerformanceManagerTestHarness::TearDownNow() {
+ helper_->TearDown();
+ helper_.reset();
+}
+
} // namespace performance_manager
diff --git a/chromium/components/performance_manager/test_support/performance_manager_test_harness.h b/chromium/components/performance_manager/test_support/performance_manager_test_harness.h
index d4aa42c5022..acae2fd8848 100644
--- a/chromium/components/performance_manager/test_support/performance_manager_test_harness.h
+++ b/chromium/components/performance_manager/test_support/performance_manager_test_harness.h
@@ -14,8 +14,14 @@ namespace performance_manager {
// RenderViewHost harness. Allows for creating full WebContents, and their
// accompanying structures in the graph. The task environment is accessed
// via content::RenderViewHostTestHarness::task_environment(). RenderFrameHosts
-// and such are not created, so this is suitable for unittests but not
-// browsertests.
+// and such are not created, so this is suitable for unit tests but not
+// browser tests.
+//
+// Meant to be used from components_unittests, but not from unit_tests.
+//
+// If you just want to test how code interacts with the graph use
+// GraphTestHarness, which has a richer set of methods for creating graph
+// nodes.
class PerformanceManagerTestHarness
: public content::RenderViewHostTestHarness {
public:
@@ -27,8 +33,9 @@ class PerformanceManagerTestHarness
// initialize its BrowserTaskEnvironment.
template <typename... TaskEnvironmentTraits>
explicit PerformanceManagerTestHarness(TaskEnvironmentTraits&&... traits)
- : RenderViewHostTestHarness(
- std::forward<TaskEnvironmentTraits>(traits)...) {}
+ : Super(std::forward<TaskEnvironmentTraits>(traits)...) {
+ helper_ = std::make_unique<PerformanceManagerTestHarnessHelper>();
+ }
PerformanceManagerTestHarness(const PerformanceManagerTestHarness&) = delete;
PerformanceManagerTestHarness& operator=(
@@ -43,6 +50,10 @@ class PerformanceManagerTestHarness
// via WebContentsTester.
std::unique_ptr<content::WebContents> CreateTestWebContents();
+ // Allows a test to cause the PM to be torn down early, so it can explicitly
+ // test TearDown logic. This may only be called once.
+ void TearDownNow();
+
private:
std::unique_ptr<PerformanceManagerTestHarnessHelper> helper_;
};
diff --git a/chromium/components/performance_manager/test_support/test_harness_helper.h b/chromium/components/performance_manager/test_support/test_harness_helper.h
index 3cab83e2093..f8e87f05c50 100644
--- a/chromium/components/performance_manager/test_support/test_harness_helper.h
+++ b/chromium/components/performance_manager/test_support/test_harness_helper.h
@@ -18,6 +18,31 @@ namespace performance_manager {
class PerformanceManagerImpl;
class PerformanceManagerRegistry;
+// A test harness helper. Manages the PM in a test environment.
+//
+// Note that in some test environments we have automatic WebContents creation
+// hooks, and in others the test code must manually call "OnWebContentsCreated".
+//
+// Rough directions for use:
+//
+// In browser tests:
+// - components_browsertests:
+// The PerformanceManagerBrowserTestHarness fixture brings its own
+// OnWebContentsCreated hooks.
+// - browser_tests:
+// The PerformanceManagerBrowserTestHarness and
+// ChromeRenderViewHostTestHarness have their own OnWebContentsCreated hooks.
+// If using ChromeRenderViewHostTestHarness you need to embed an instance of
+// this helper in order to initialize the PM.
+//
+// In unit tests:
+// - components_unittests:
+// The PerformanceManagerTestHarness brings its own OnWebContentsCreated
+// hooks providing you use its CreateTestWebContents helper.
+// - unit_tests:
+// The ChromeRenderViewHostTestHarness brings its own OnWebContentsCreated
+// hooks, but you need to embed an instance of this helper in order to
+// initialize the PM.
class PerformanceManagerTestHarnessHelper {
public:
PerformanceManagerTestHarnessHelper();
@@ -34,7 +59,11 @@ class PerformanceManagerTestHarnessHelper {
// are torn down.
void TearDown();
- // Attaches tab helpers to the provided |contents|.
+ // Attaches tab helpers to the provided |contents|. This should only need to
+ // be called explicitly in components_unittests. In unit_tests, browser_tests
+ // and components_browsertests we have the necessary hooks into WebContents
+ // creation to automatically add our observers; it suffices to ensure that the
+ // PM is initialized (ie, initialize an instance of this helper).
void OnWebContentsCreated(content::WebContents* contents);
private:
diff --git a/chromium/components/performance_manager/web_contents_proxy.cc b/chromium/components/performance_manager/web_contents_proxy.cc
index a2b7f5e304b..e21a449ec2b 100644
--- a/chromium/components/performance_manager/web_contents_proxy.cc
+++ b/chromium/components/performance_manager/web_contents_proxy.cc
@@ -42,6 +42,13 @@ int64_t WebContentsProxy::LastNavigationId() const {
return proxy->LastNavigationId();
}
+int64_t WebContentsProxy::LastNewDocNavigationId() const {
+ auto* proxy = impl_.get();
+ if (!proxy)
+ return 0;
+ return proxy->LastNewDocNavigationId();
+}
+
WebContentsProxy::WebContentsProxy(
const base::WeakPtr<WebContentsProxyImpl>& impl)
: impl_(impl) {}
diff --git a/chromium/components/performance_manager/web_contents_proxy_impl.h b/chromium/components/performance_manager/web_contents_proxy_impl.h
index 2448af60f9d..db4daa880f2 100644
--- a/chromium/components/performance_manager/web_contents_proxy_impl.h
+++ b/chromium/components/performance_manager/web_contents_proxy_impl.h
@@ -35,6 +35,12 @@ class WebContentsProxyImpl {
// web contents. This must only be called on the UI thread.
virtual int64_t LastNavigationId() const = 0;
+ // Similar to the above, but for the last non same-document navigation
+ // associated with this WebContents. This is always for a navigation that is
+ // older or equal to "LastNavigationId". This must only be called on the UI
+ // thread.
+ virtual int64_t LastNewDocNavigationId() const = 0;
+
private:
DISALLOW_COPY_AND_ASSIGN(WebContentsProxyImpl);
};
diff --git a/chromium/components/performance_manager/web_contents_proxy_unittest.cc b/chromium/components/performance_manager/web_contents_proxy_unittest.cc
index dfba3fcf508..b1ac9dbfb14 100644
--- a/chromium/components/performance_manager/web_contents_proxy_unittest.cc
+++ b/chromium/components/performance_manager/web_contents_proxy_unittest.cc
@@ -8,7 +8,6 @@
#include <utility>
#include "base/run_loop.h"
-#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/test/bind_test_util.h"
#include "components/performance_manager/graph/page_node_impl.h"
@@ -56,8 +55,8 @@ TEST_F(WebContentsProxyTest, EndToEnd) {
FROM_HERE,
base::BindLambdaForTesting(
[&deref_proxy, page_node, quit_loop = run_loop.QuitClosure()]() {
- base::PostTask(
- FROM_HERE, {content::BrowserThread::UI},
+ content::GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE,
base::BindOnce(deref_proxy, page_node->contents_proxy(),
std::move(quit_loop)));
}));
@@ -74,11 +73,11 @@ TEST_F(WebContentsProxyTest, EndToEnd) {
FROM_HERE,
base::BindLambdaForTesting([&contents, &deref_proxy, page_node,
quit_loop = run_loop.QuitClosure()]() {
- base::PostTask(
- FROM_HERE, {content::BrowserThread::UI},
+ content::GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE,
base::BindLambdaForTesting([&contents]() { contents.reset(); }));
- base::PostTask(
- FROM_HERE, {content::BrowserThread::UI},
+ content::GetUIThreadTaskRunner({})->PostTask(
+ FROM_HERE,
base::BindOnce(deref_proxy, page_node->contents_proxy(),
std::move(quit_loop)));
}));
diff --git a/chromium/components/performance_manager/worker_watcher.cc b/chromium/components/performance_manager/worker_watcher.cc
index 2641ee7ffbc..1d05b728d2b 100644
--- a/chromium/components/performance_manager/worker_watcher.cc
+++ b/chromium/components/performance_manager/worker_watcher.cc
@@ -24,26 +24,38 @@ void RecordWorkerClientFound(bool found) {
UMA_HISTOGRAM_BOOLEAN("PerformanceManager.WorkerClientFound", found);
}
-// Helper function to add |worker_node| as a child to |frame_node| on the PM
-// sequence.
-void AddWorkerToFrameNode(FrameNodeImpl* frame_node,
- WorkerNodeImpl* worker_node) {
- worker_node->AddClientFrame(frame_node);
+// Helper function to add |client_frame_node| as a client of |worker_node| on
+// the PM sequence.
+void ConnectClientOnGraph(WorkerNodeImpl* worker_node,
+ FrameNodeImpl* client_frame_node) {
+ PerformanceManagerImpl::CallOnGraphImpl(
+ FROM_HERE,
+ base::BindOnce(&WorkerNodeImpl::AddClientFrame,
+ base::Unretained(worker_node), client_frame_node));
}
-// Helper function to remove |worker_node| from |frame_node| on the PM sequence.
-void RemoveWorkerFromFrameNode(FrameNodeImpl* frame_node,
- WorkerNodeImpl* worker_node) {
- worker_node->RemoveClientFrame(frame_node);
+// Helper function to remove |client_frame_node| as a client of |worker_node|
+// on the PM sequence.
+void DisconnectClientOnGraph(WorkerNodeImpl* worker_node,
+ FrameNodeImpl* client_frame_node) {
+ PerformanceManagerImpl::CallOnGraphImpl(
+ FROM_HERE,
+ base::BindOnce(&WorkerNodeImpl::RemoveClientFrame,
+ base::Unretained(worker_node), client_frame_node));
}
-// Helper function to remove all |worker_nodes| from |frame_node| on the PM
-// sequence.
-void RemoveWorkersFromFrameNode(
- FrameNodeImpl* frame_node,
- const base::flat_set<WorkerNodeImpl*>& worker_nodes) {
- for (auto* worker_node : worker_nodes)
- worker_node->RemoveClientFrame(frame_node);
+// Helper function to remove |client_frame_node| as a client of all worker nodes
+// in |worker_nodes| on the PM sequence.
+void DisconnectClientsOnGraph(base::flat_set<WorkerNodeImpl*> worker_nodes,
+ FrameNodeImpl* client_frame_node) {
+ PerformanceManagerImpl::CallOnGraphImpl(
+ FROM_HERE, base::BindOnce(
+ [](base::flat_set<WorkerNodeImpl*> worker_nodes,
+ FrameNodeImpl* client_frame_node) {
+ for (auto* worker_node : worker_nodes)
+ worker_node->RemoveClientFrame(client_frame_node);
+ },
+ std::move(worker_nodes), client_frame_node));
}
// Helper function that posts a task on the PM sequence that will invoke
@@ -66,17 +78,20 @@ WorkerWatcher::WorkerWatcher(
: browser_context_id_(browser_context_id),
process_node_source_(process_node_source),
frame_node_source_(frame_node_source) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(dedicated_worker_service);
DCHECK(shared_worker_service);
DCHECK(service_worker_context);
DCHECK(process_node_source_);
DCHECK(frame_node_source_);
+
dedicated_worker_service_observer_.Add(dedicated_worker_service);
shared_worker_service_observer_.Add(shared_worker_service);
service_worker_context_observer_.Add(service_worker_context);
}
WorkerWatcher::~WorkerWatcher() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(frame_node_child_workers_.empty());
DCHECK(dedicated_worker_nodes_.empty());
DCHECK(!dedicated_worker_service_observer_.IsObservingSources());
@@ -87,10 +102,13 @@ WorkerWatcher::~WorkerWatcher() {
}
void WorkerWatcher::TearDown() {
- // First clear client-child relations between frames and workers.
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // First clear client-child connections between frames and workers.
for (auto& kv : frame_node_child_workers_) {
const content::GlobalFrameRoutingId& render_frame_host_id = kv.first;
base::flat_set<WorkerNodeImpl*>& child_workers = kv.second;
+ DCHECK(!child_workers.empty());
frame_node_source_->UnsubscribeFromFrameNode(render_frame_host_id);
@@ -98,10 +116,7 @@ void WorkerWatcher::TearDown() {
FrameNodeImpl* frame_node =
frame_node_source_->GetFrameNode(render_frame_host_id);
DCHECK(frame_node);
- DCHECK(!child_workers.empty());
- PerformanceManagerImpl::CallOnGraphImpl(
- FROM_HERE,
- base::BindOnce(&RemoveWorkersFromFrameNode, frame_node, child_workers));
+ DisconnectClientsOnGraph(std::move(child_workers), frame_node);
}
frame_node_child_workers_.clear();
@@ -129,10 +144,12 @@ void WorkerWatcher::TearDown() {
service_worker_context_observer_.RemoveAll();
}
-void WorkerWatcher::OnWorkerStarted(
+void WorkerWatcher::OnWorkerCreated(
content::DedicatedWorkerId dedicated_worker_id,
int worker_process_id,
content::GlobalFrameRoutingId ancestor_render_frame_host_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
// TODO(https://crbug.com/993029): Plumb through the URL and the DevTools
// token.
auto worker_node = PerformanceManagerImpl::CreateWorkerNode(
@@ -143,20 +160,22 @@ void WorkerWatcher::OnWorkerStarted(
dedicated_worker_id, std::move(worker_node));
DCHECK(insertion_result.second);
- AddClientFrame(insertion_result.first->second.get(),
- ancestor_render_frame_host_id);
+ ConnectClient(insertion_result.first->second.get(),
+ ancestor_render_frame_host_id);
}
-void WorkerWatcher::OnBeforeWorkerTerminated(
+void WorkerWatcher::OnBeforeWorkerDestroyed(
content::DedicatedWorkerId dedicated_worker_id,
content::GlobalFrameRoutingId ancestor_render_frame_host_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
auto it = dedicated_worker_nodes_.find(dedicated_worker_id);
DCHECK(it != dedicated_worker_nodes_.end());
auto worker_node = std::move(it->second);
// First disconnect the ancestor's frame node from this worker node.
- RemoveClientFrame(worker_node.get(), ancestor_render_frame_host_id);
+ DisconnectClient(worker_node.get(), ancestor_render_frame_host_id);
#if DCHECK_IS_ON()
DCHECK(!base::Contains(detached_frame_count_per_worker_, worker_node.get()));
@@ -169,13 +188,17 @@ void WorkerWatcher::OnBeforeWorkerTerminated(
void WorkerWatcher::OnFinalResponseURLDetermined(
content::DedicatedWorkerId dedicated_worker_id,
const GURL& url) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
SetFinalResponseURL(GetDedicatedWorkerNode(dedicated_worker_id), url);
}
-void WorkerWatcher::OnWorkerStarted(
+void WorkerWatcher::OnWorkerCreated(
content::SharedWorkerId shared_worker_id,
int worker_process_id,
const base::UnguessableToken& dev_tools_token) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
auto worker_node = PerformanceManagerImpl::CreateWorkerNode(
browser_context_id_, WorkerNode::WorkerType::kShared,
process_node_source_->GetProcessNode(worker_process_id), dev_tools_token);
@@ -185,8 +208,10 @@ void WorkerWatcher::OnWorkerStarted(
DCHECK(inserted);
}
-void WorkerWatcher::OnBeforeWorkerTerminated(
+void WorkerWatcher::OnBeforeWorkerDestroyed(
content::SharedWorkerId shared_worker_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
auto it = shared_worker_nodes_.find(shared_worker_id);
DCHECK(it != shared_worker_nodes_.end());
@@ -202,25 +227,32 @@ void WorkerWatcher::OnBeforeWorkerTerminated(
void WorkerWatcher::OnFinalResponseURLDetermined(
content::SharedWorkerId shared_worker_id,
const GURL& url) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
SetFinalResponseURL(GetSharedWorkerNode(shared_worker_id), url);
}
void WorkerWatcher::OnClientAdded(
content::SharedWorkerId shared_worker_id,
content::GlobalFrameRoutingId render_frame_host_id) {
- AddClientFrame(GetSharedWorkerNode(shared_worker_id), render_frame_host_id);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ ConnectClient(GetSharedWorkerNode(shared_worker_id), render_frame_host_id);
}
void WorkerWatcher::OnClientRemoved(
content::SharedWorkerId shared_worker_id,
content::GlobalFrameRoutingId render_frame_host_id) {
- RemoveClientFrame(GetSharedWorkerNode(shared_worker_id),
- render_frame_host_id);
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ DisconnectClient(GetSharedWorkerNode(shared_worker_id), render_frame_host_id);
}
void WorkerWatcher::OnVersionStartedRunning(
int64_t version_id,
const content::ServiceWorkerRunningInfo& running_info) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
// TODO(pmonette): Plumb in the DevTools token.
auto worker_node = PerformanceManagerImpl::CreateWorkerNode(
browser_context_id_, WorkerNode::WorkerType::kService,
@@ -232,6 +264,8 @@ void WorkerWatcher::OnVersionStartedRunning(
}
void WorkerWatcher::OnVersionStoppedRunning(int64_t version_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
auto it = service_worker_nodes_.find(version_id);
DCHECK(it != service_worker_nodes_.end());
@@ -244,9 +278,12 @@ void WorkerWatcher::OnVersionStoppedRunning(int64_t version_id) {
service_worker_nodes_.erase(it);
}
-void WorkerWatcher::AddClientFrame(
+void WorkerWatcher::ConnectClient(
WorkerNodeImpl* worker_node,
content::GlobalFrameRoutingId client_render_frame_host_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(worker_node);
+
FrameNodeImpl* frame_node =
frame_node_source_->GetFrameNode(client_render_frame_host_id);
// TODO(https://crbug.com/1078161): The client frame's node should always be
@@ -255,7 +292,7 @@ void WorkerWatcher::AddClientFrame(
if (!frame_node) {
RecordWorkerClientFound(false);
#if DCHECK_IS_ON()
- // A call to RemoveClientFrame() is still expected to be received for this
+ // A call to DisconnectClient() is still expected to be received for this
// frame and worker pair.
detached_frame_count_per_worker_[worker_node]++;
#endif // DCHECK_IS_ON()
@@ -264,12 +301,9 @@ void WorkerWatcher::AddClientFrame(
RecordWorkerClientFound(true);
- // Connect the nodes in the PM graph.
- PerformanceManagerImpl::CallOnGraphImpl(
- FROM_HERE,
- base::BindOnce(&AddWorkerToFrameNode, frame_node, worker_node));
+ ConnectClientOnGraph(worker_node, frame_node);
- // Keep track of the shared workers that this frame is a client to.
+ // Keep track of the workers that this frame is a client to.
if (AddChildWorker(client_render_frame_host_id, worker_node)) {
frame_node_source_->SubscribeToFrameNode(
client_render_frame_host_id,
@@ -278,9 +312,12 @@ void WorkerWatcher::AddClientFrame(
}
}
-void WorkerWatcher::RemoveClientFrame(
+void WorkerWatcher::DisconnectClient(
WorkerNodeImpl* worker_node,
content::GlobalFrameRoutingId client_render_frame_host_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(worker_node);
+
FrameNodeImpl* frame_node =
frame_node_source_->GetFrameNode(client_render_frame_host_id);
@@ -293,7 +330,7 @@ void WorkerWatcher::RemoveClientFrame(
// possible to connect a worker to its client frame.
if (!frame_node) {
#if DCHECK_IS_ON()
- // These debug only checks are used to ensure that this RemoveClientFrame()
+ // These debug only checks are used to ensure that this DisconnectClient()
// was still expected even though the client frame node no longer exist.
auto it = detached_frame_count_per_worker_.find(worker_node);
DCHECK(it != detached_frame_count_per_worker_.end());
@@ -308,10 +345,7 @@ void WorkerWatcher::RemoveClientFrame(
return;
}
- // Disconnect the node.
- PerformanceManagerImpl::CallOnGraphImpl(
- FROM_HERE,
- base::BindOnce(&RemoveWorkerFromFrameNode, frame_node, worker_node));
+ DisconnectClientOnGraph(worker_node, frame_node);
// Remove |worker_node| from the set of workers that this frame is a client
// of.
@@ -322,6 +356,8 @@ void WorkerWatcher::RemoveClientFrame(
void WorkerWatcher::OnBeforeFrameNodeRemoved(
content::GlobalFrameRoutingId render_frame_host_id,
FrameNodeImpl* frame_node) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
auto it = frame_node_child_workers_.find(render_frame_host_id);
DCHECK(it != frame_node_child_workers_.end());
@@ -331,13 +367,11 @@ void WorkerWatcher::OnBeforeFrameNodeRemoved(
// Disconnect all child workers from |frame_node|.
DCHECK(!child_workers.empty());
- PerformanceManagerImpl::CallOnGraphImpl(
- FROM_HERE,
- base::BindOnce(&RemoveWorkersFromFrameNode, frame_node, child_workers));
+ DisconnectClientsOnGraph(child_workers, frame_node);
#if DCHECK_IS_ON()
for (WorkerNodeImpl* worker_node : child_workers) {
- // A call to RemoveClientFrame() is still expected to be received for this
+ // A call to DisconnectClient() is still expected to be received for this
// frame to all workers in |child_workers|.
// Note: the [] operator is intentionally used to default initialize the
// count to zero if needed.
@@ -349,6 +383,8 @@ void WorkerWatcher::OnBeforeFrameNodeRemoved(
bool WorkerWatcher::AddChildWorker(
content::GlobalFrameRoutingId render_frame_host_id,
WorkerNodeImpl* child_worker_node) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
auto insertion_result =
frame_node_child_workers_.insert({render_frame_host_id, {}});
@@ -362,6 +398,8 @@ bool WorkerWatcher::AddChildWorker(
bool WorkerWatcher::RemoveChildWorker(
content::GlobalFrameRoutingId render_frame_host_id,
WorkerNodeImpl* child_worker_node) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
auto it = frame_node_child_workers_.find(render_frame_host_id);
DCHECK(it != frame_node_child_workers_.end());
auto& child_workers = it->second;
@@ -378,6 +416,8 @@ bool WorkerWatcher::RemoveChildWorker(
WorkerNodeImpl* WorkerWatcher::GetDedicatedWorkerNode(
content::DedicatedWorkerId dedicated_worker_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
auto it = dedicated_worker_nodes_.find(dedicated_worker_id);
if (it == dedicated_worker_nodes_.end()) {
NOTREACHED();
@@ -388,6 +428,8 @@ WorkerNodeImpl* WorkerWatcher::GetDedicatedWorkerNode(
WorkerNodeImpl* WorkerWatcher::GetSharedWorkerNode(
content::SharedWorkerId shared_worker_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
auto it = shared_worker_nodes_.find(shared_worker_id);
if (it == shared_worker_nodes_.end()) {
NOTREACHED();
@@ -397,6 +439,8 @@ WorkerNodeImpl* WorkerWatcher::GetSharedWorkerNode(
}
WorkerNodeImpl* WorkerWatcher::GetServiceWorkerNode(int64_t version_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
auto it = service_worker_nodes_.find(version_id);
if (it == service_worker_nodes_.end()) {
NOTREACHED();
diff --git a/chromium/components/performance_manager/worker_watcher.h b/chromium/components/performance_manager/worker_watcher.h
index a069d8a5b1b..6c6ddb62456 100644
--- a/chromium/components/performance_manager/worker_watcher.h
+++ b/chromium/components/performance_manager/worker_watcher.h
@@ -8,11 +8,12 @@
#include <memory>
#include <string>
+#include "base/check_op.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
-#include "base/logging.h"
#include "base/macros.h"
#include "base/scoped_observer.h"
+#include "base/sequence_checker.h"
#include "content/public/browser/dedicated_worker_id.h"
#include "content/public/browser/dedicated_worker_service.h"
#include "content/public/browser/global_routing_id.h"
@@ -49,11 +50,11 @@ class WorkerWatcher : public content::DedicatedWorkerService::Observer,
void TearDown();
// content::DedicatedWorkerService::Observer:
- void OnWorkerStarted(
+ void OnWorkerCreated(
content::DedicatedWorkerId dedicated_worker_id,
int worker_process_id,
content::GlobalFrameRoutingId ancestor_render_frame_host_id) override;
- void OnBeforeWorkerTerminated(
+ void OnBeforeWorkerDestroyed(
content::DedicatedWorkerId dedicated_worker_id,
content::GlobalFrameRoutingId ancestor_render_frame_host_id) override;
void OnFinalResponseURLDetermined(
@@ -61,10 +62,10 @@ class WorkerWatcher : public content::DedicatedWorkerService::Observer,
const GURL& url) override;
// content::SharedWorkerService::Observer:
- void OnWorkerStarted(content::SharedWorkerId shared_worker_id,
+ void OnWorkerCreated(content::SharedWorkerId shared_worker_id,
int worker_process_id,
const base::UnguessableToken& dev_tools_token) override;
- void OnBeforeWorkerTerminated(
+ void OnBeforeWorkerDestroyed(
content::SharedWorkerId shared_worker_id) override;
void OnFinalResponseURLDetermined(content::SharedWorkerId shared_worker_id,
const GURL& url) override;
@@ -84,10 +85,9 @@ class WorkerWatcher : public content::DedicatedWorkerService::Observer,
private:
friend class WorkerWatcherTest;
- void AddClientFrame(
- WorkerNodeImpl* worker_node,
- content::GlobalFrameRoutingId client_render_frame_host_id);
- void RemoveClientFrame(
+ void ConnectClient(WorkerNodeImpl* worker_node,
+ content::GlobalFrameRoutingId client_render_frame_host_id);
+ void DisconnectClient(
WorkerNodeImpl* worker_node,
content::GlobalFrameRoutingId client_render_frame_host_id);
@@ -95,6 +95,8 @@ class WorkerWatcher : public content::DedicatedWorkerService::Observer,
content::GlobalFrameRoutingId render_frame_host_id,
FrameNodeImpl* frame_node);
+ // Inserts/removes |child_worker_node| into the set of child workers of a
+ // frame. Returns true if this is the first child added to that frame.
bool AddChildWorker(content::GlobalFrameRoutingId render_frame_host_id,
WorkerNodeImpl* child_worker_node);
bool RemoveChildWorker(content::GlobalFrameRoutingId render_frame_host_id,
@@ -106,6 +108,8 @@ class WorkerWatcher : public content::DedicatedWorkerService::Observer,
WorkerNodeImpl* GetSharedWorkerNode(content::SharedWorkerId shared_worker_id);
WorkerNodeImpl* GetServiceWorkerNode(int64_t version_id);
+ SEQUENCE_CHECKER(sequence_checker_);
+
// The ID of the BrowserContext who owns the shared worker service.
const std::string browser_context_id_;
diff --git a/chromium/components/performance_manager/worker_watcher_unittest.cc b/chromium/components/performance_manager/worker_watcher_unittest.cc
index 51317287927..53c8ad5b88c 100644
--- a/chromium/components/performance_manager/worker_watcher_unittest.cc
+++ b/chromium/components/performance_manager/worker_watcher_unittest.cc
@@ -53,8 +53,8 @@ bool IsWorkerClient(WorkerNodeImpl* worker_node,
// TestDedicatedWorkerService --------------------------------------------------
-// A test TestDedicatedWorkerService that allows to simulate starting and
-// stopping a dedicated worker.
+// A test DedicatedWorkerService that allows to simulate creating and destroying
+// dedicated workers.
class TestDedicatedWorkerService : public content::DedicatedWorkerService {
public:
TestDedicatedWorkerService();
@@ -65,13 +65,13 @@ class TestDedicatedWorkerService : public content::DedicatedWorkerService {
void RemoveObserver(Observer* observer) override;
void EnumerateDedicatedWorkers(Observer* observer) override;
- // Starts a new dedicated worker and returns its ID.
- content::DedicatedWorkerId StartDedicatedWorker(
+ // Creates a new dedicated worker and returns its ID.
+ content::DedicatedWorkerId CreateDedicatedWorker(
int worker_process_id,
content::GlobalFrameRoutingId client_render_frame_host_id);
- // Stops a running shared worker.
- void StopDedicatedWorker(content::DedicatedWorkerId dedicated_worker_id);
+ // Destroys an existing dedicated worker.
+ void DestroyDedicatedWorker(content::DedicatedWorkerId dedicated_worker_id);
private:
base::ObserverList<Observer> observer_list_;
@@ -103,7 +103,7 @@ void TestDedicatedWorkerService::EnumerateDedicatedWorkers(Observer* observer) {
ADD_FAILURE();
}
-content::DedicatedWorkerId TestDedicatedWorkerService::StartDedicatedWorker(
+content::DedicatedWorkerId TestDedicatedWorkerService::CreateDedicatedWorker(
int worker_process_id,
content::GlobalFrameRoutingId client_render_frame_host_id) {
// Create a new DedicatedWorkerId for the worker and add it to the map, along
@@ -118,21 +118,21 @@ content::DedicatedWorkerId TestDedicatedWorkerService::StartDedicatedWorker(
// Notify observers.
for (auto& observer : observer_list_) {
- observer.OnWorkerStarted(dedicated_worker_id, worker_process_id,
+ observer.OnWorkerCreated(dedicated_worker_id, worker_process_id,
client_render_frame_host_id);
}
return dedicated_worker_id;
}
-void TestDedicatedWorkerService::StopDedicatedWorker(
+void TestDedicatedWorkerService::DestroyDedicatedWorker(
content::DedicatedWorkerId dedicated_worker_id) {
auto it = dedicated_worker_client_frame_.find(dedicated_worker_id);
DCHECK(it != dedicated_worker_client_frame_.end());
- // Notify observers that the worker is terminating.
+ // Notify observers that the worker is being destroyed.
for (auto& observer : observer_list_)
- observer.OnBeforeWorkerTerminated(dedicated_worker_id, it->second);
+ observer.OnBeforeWorkerDestroyed(dedicated_worker_id, it->second);
// Remove the worker ID from the map.
dedicated_worker_client_frame_.erase(it);
@@ -140,8 +140,8 @@ void TestDedicatedWorkerService::StopDedicatedWorker(
// TestSharedWorkerService -----------------------------------------------------
-// A test SharedWorkerService that allows to simulate a worker starting and
-// stopping and adding clients to running workers.
+// A test SharedWorkerService that allows to simulate creating and destroying
+// shared workers and adding clients to existing workers.
class TestSharedWorkerService : public content::SharedWorkerService {
public:
TestSharedWorkerService();
@@ -155,11 +155,11 @@ class TestSharedWorkerService : public content::SharedWorkerService {
const std::string& name,
const url::Origin& constructor_origin) override;
- // Starts a new shared worker and returns its ID.
- content::SharedWorkerId StartSharedWorker(int worker_process_id);
+ // Creates a new shared worker and returns its ID.
+ content::SharedWorkerId CreateSharedWorker(int worker_process_id);
- // Stops a running shared worker.
- void StopSharedWorker(content::SharedWorkerId shared_worker_id);
+ // Destroys a running shared worker.
+ void DestroySharedWorker(content::SharedWorkerId shared_worker_id);
// Adds a new frame client to an existing worker.
void AddFrameClientToWorker(
@@ -211,7 +211,7 @@ bool TestSharedWorkerService::TerminateWorker(
return false;
}
-content::SharedWorkerId TestSharedWorkerService::StartSharedWorker(
+content::SharedWorkerId TestSharedWorkerService::CreateSharedWorker(
int worker_process_id) {
// Create a new DedicatedWorkerId for the worker and add it to the map.
content::SharedWorkerId shared_worker_id =
@@ -224,24 +224,24 @@ content::SharedWorkerId TestSharedWorkerService::StartSharedWorker(
// Notify observers.
for (auto& observer : observer_list_) {
- observer.OnWorkerStarted(shared_worker_id, worker_process_id,
+ observer.OnWorkerCreated(shared_worker_id, worker_process_id,
base::UnguessableToken::Create());
}
return shared_worker_id;
}
-void TestSharedWorkerService::StopSharedWorker(
+void TestSharedWorkerService::DestroySharedWorker(
content::SharedWorkerId shared_worker_id) {
auto it = shared_worker_client_frames_.find(shared_worker_id);
DCHECK(it != shared_worker_client_frames_.end());
- // A stopping worker should have no clients.
+ // The worker should no longer have any clients.
DCHECK(it->second.empty());
- // Notify observers that the worker is terminating.
+ // Notify observers that the worker is being destroyed.
for (auto& observer : observer_list_)
- observer.OnBeforeWorkerTerminated(shared_worker_id);
+ observer.OnBeforeWorkerDestroyed(shared_worker_id);
// Remove the worker ID from the map.
shared_worker_client_frames_.erase(it);
@@ -557,7 +557,7 @@ content::GlobalFrameRoutingId TestFrameNodeSource::CreateFrameNode(
frame_id);
auto frame_node = PerformanceManagerImpl::CreateFrameNode(
process_node, page_node_.get(), nullptr, 0, frame_id,
- base::UnguessableToken::Null(), 0, 0);
+ FrameToken(base::UnguessableToken::Create()), 0, 0);
bool inserted =
frame_node_map_.insert({render_frame_host_id, std::move(frame_node)})
@@ -714,8 +714,8 @@ TEST_F(WorkerWatcherTest, SimpleDedicatedWorker) {
// Create the worker.
content::DedicatedWorkerId dedicated_worker_id =
- dedicated_worker_service()->StartDedicatedWorker(render_process_id,
- render_frame_host_id);
+ dedicated_worker_service()->CreateDedicatedWorker(render_process_id,
+ render_frame_host_id);
// Check expectations on the graph.
CallOnGraphAndWait(base::BindLambdaForTesting(
@@ -731,7 +731,7 @@ TEST_F(WorkerWatcherTest, SimpleDedicatedWorker) {
}));
// Disconnect and clean up the worker.
- dedicated_worker_service()->StopDedicatedWorker(dedicated_worker_id);
+ dedicated_worker_service()->DestroyDedicatedWorker(dedicated_worker_id);
}
// This test creates one shared worker with one client frame.
@@ -746,7 +746,7 @@ TEST_F(WorkerWatcherTest, SimpleSharedWorker) {
// Create the worker.
content::SharedWorkerId shared_worker_id =
- shared_worker_service()->StartSharedWorker(render_process_id);
+ shared_worker_service()->CreateSharedWorker(render_process_id);
// Connect the frame to the worker.
shared_worker_service()->AddFrameClientToWorker(shared_worker_id,
@@ -767,7 +767,7 @@ TEST_F(WorkerWatcherTest, SimpleSharedWorker) {
// Disconnect and clean up the worker.
shared_worker_service()->RemoveFrameClientFromWorker(shared_worker_id,
render_frame_host_id);
- shared_worker_service()->StopSharedWorker(shared_worker_id);
+ shared_worker_service()->DestroySharedWorker(shared_worker_id);
}
// This test creates one service worker with one client frame.
@@ -819,7 +819,7 @@ TEST_F(WorkerWatcherTest, SharedWorkerCrossProcessClient) {
// Create the worker in a different process.
int worker_process_id = process_node_source()->CreateProcessNode();
content::SharedWorkerId shared_worker_id =
- shared_worker_service()->StartSharedWorker(worker_process_id);
+ shared_worker_service()->CreateSharedWorker(worker_process_id);
// Connect the frame to the worker.
shared_worker_service()->AddFrameClientToWorker(shared_worker_id,
@@ -843,7 +843,7 @@ TEST_F(WorkerWatcherTest, SharedWorkerCrossProcessClient) {
// Disconnect and clean up the worker.
shared_worker_service()->RemoveFrameClientFromWorker(shared_worker_id,
render_frame_host_id);
- shared_worker_service()->StopSharedWorker(shared_worker_id);
+ shared_worker_service()->DestroySharedWorker(shared_worker_id);
}
TEST_F(WorkerWatcherTest, OneSharedWorkerTwoClients) {
@@ -851,7 +851,7 @@ TEST_F(WorkerWatcherTest, OneSharedWorkerTwoClients) {
// Create the worker.
content::SharedWorkerId shared_worker_id =
- shared_worker_service()->StartSharedWorker(render_process_id);
+ shared_worker_service()->CreateSharedWorker(render_process_id);
// Create 2 client frame nodes and connect them to the worker.
content::GlobalFrameRoutingId render_frame_host_id_1 =
@@ -890,7 +890,7 @@ TEST_F(WorkerWatcherTest, OneSharedWorkerTwoClients) {
render_frame_host_id_1);
shared_worker_service()->RemoveFrameClientFromWorker(shared_worker_id,
render_frame_host_id_2);
- shared_worker_service()->StopSharedWorker(shared_worker_id);
+ shared_worker_service()->DestroySharedWorker(shared_worker_id);
}
TEST_F(WorkerWatcherTest, OneClientTwoSharedWorkers) {
@@ -904,12 +904,12 @@ TEST_F(WorkerWatcherTest, OneClientTwoSharedWorkers) {
// Create the 2 workers and connect them to the frame.
content::SharedWorkerId shared_worker_id_1 =
- shared_worker_service()->StartSharedWorker(render_process_id);
+ shared_worker_service()->CreateSharedWorker(render_process_id);
shared_worker_service()->AddFrameClientToWorker(shared_worker_id_1,
render_frame_host_id);
content::SharedWorkerId shared_worker_id_2 =
- shared_worker_service()->StartSharedWorker(render_process_id);
+ shared_worker_service()->CreateSharedWorker(render_process_id);
shared_worker_service()->AddFrameClientToWorker(shared_worker_id_2,
render_frame_host_id);
@@ -935,11 +935,11 @@ TEST_F(WorkerWatcherTest, OneClientTwoSharedWorkers) {
// Disconnect and clean up the workers.
shared_worker_service()->RemoveFrameClientFromWorker(shared_worker_id_1,
render_frame_host_id);
- shared_worker_service()->StopSharedWorker(shared_worker_id_1);
+ shared_worker_service()->DestroySharedWorker(shared_worker_id_1);
shared_worker_service()->RemoveFrameClientFromWorker(shared_worker_id_2,
render_frame_host_id);
- shared_worker_service()->StopSharedWorker(shared_worker_id_2);
+ shared_worker_service()->DestroySharedWorker(shared_worker_id_2);
}
TEST_F(WorkerWatcherTest, FrameDestroyed) {
@@ -953,10 +953,10 @@ TEST_F(WorkerWatcherTest, FrameDestroyed) {
// Create a worker of each type.
content::DedicatedWorkerId dedicated_worker_id =
- dedicated_worker_service()->StartDedicatedWorker(render_process_id,
- render_frame_host_id);
+ dedicated_worker_service()->CreateDedicatedWorker(render_process_id,
+ render_frame_host_id);
content::SharedWorkerId shared_worker_id =
- shared_worker_service()->StartSharedWorker(render_process_id);
+ shared_worker_service()->CreateSharedWorker(render_process_id);
int64_t service_worker_version_id =
service_worker_context()->StartServiceWorker(render_process_id);
@@ -1005,8 +1005,8 @@ TEST_F(WorkerWatcherTest, FrameDestroyed) {
service_worker_context()->StopServiceWorker(service_worker_version_id);
shared_worker_service()->RemoveFrameClientFromWorker(shared_worker_id,
render_frame_host_id);
- shared_worker_service()->StopSharedWorker(shared_worker_id);
- dedicated_worker_service()->StopDedicatedWorker(dedicated_worker_id);
+ shared_worker_service()->DestroySharedWorker(shared_worker_id);
+ dedicated_worker_service()->DestroyDedicatedWorker(dedicated_worker_id);
}
} // namespace performance_manager