| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.h" |
| |
| #include <memory> |
| |
| #include "base/memory/raw_ptr.h" |
| #include "base/metrics/metrics_hashes.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/simple_test_clock.h" |
| #include "base/test/trace_event_analyzer.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/traced_value.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/bookmarks/bookmark_model_factory.h" |
| #include "chrome/browser/content_settings/cookie_settings_factory.h" |
| #include "chrome/browser/history/history_service_factory.h" |
| #include "chrome/browser/history/history_tab_helper.h" |
| #include "chrome/browser/history_clusters/history_clusters_service_factory.h" |
| #include "chrome/browser/history_clusters/history_clusters_tab_helper.h" |
| #include "chrome/browser/page_load_metrics/observers/page_load_metrics_observer_test_harness.h" |
| #include "chrome/browser/search_engines/template_url_service_factory.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/search_test_utils.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/bookmarks/browser/bookmark_model.h" |
| #include "components/bookmarks/test/bookmark_test_helpers.h" |
| #include "components/content_settings/core/browser/cookie_settings.h" |
| #include "components/content_settings/core/common/content_settings.h" |
| #include "components/content_settings/core/common/pref_names.h" |
| #include "components/history/core/browser/history_service.h" |
| #include "components/history/core/browser/history_types.h" |
| #include "components/history/core/test/history_service_test_util.h" |
| #include "components/history_clusters/core/history_clusters_service.h" |
| #include "components/keyed_service/core/service_access_type.h" |
| #include "components/ntp_tiles/custom_links_store.h" |
| #include "components/page_load_metrics/browser/observers/core/largest_contentful_paint_handler.h" |
| #include "components/page_load_metrics/browser/page_load_metrics_observer.h" |
| #include "components/page_load_metrics/browser/page_load_tracker.h" |
| #include "components/page_load_metrics/common/page_visit_final_status.h" |
| #include "components/page_load_metrics/common/test/page_load_metrics_test_util.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/search_engines/template_url_service.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/navigation_simulator.h" |
| #include "net/base/ip_endpoint.h" |
| #include "net/nqe/effective_connection_type.h" |
| #include "services/metrics/public/cpp/metrics_utils.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "services/metrics/public/cpp/ukm_source.h" |
| #include "services/network/public/cpp/network_quality_tracker.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/metrics_proto/system_profile.pb.h" |
| #include "third_party/perfetto/include/perfetto/tracing/track.h" |
| |
| using content::NavigationSimulator; |
| using content::RenderFrameHost; |
| using content::RenderFrameHostTester; |
| using page_load_metrics::PageVisitFinalStatus; |
| using testing::AnyNumber; |
| using testing::Mock; |
| using testing::Return; |
| using UserInteractionLatenciesPtr = |
| page_load_metrics::mojom::UserInteractionLatenciesPtr; |
| using UserInteractionLatencies = |
| page_load_metrics::mojom::UserInteractionLatencies; |
| using UserInteractionLatency = page_load_metrics::mojom::UserInteractionLatency; |
| using UserInteractionType = page_load_metrics::mojom::UserInteractionType; |
| |
| namespace { |
| |
| using GeneratedNavigation = ukm::builders::GeneratedNavigation; |
| using LargestContentState = |
| page_load_metrics::PageLoadMetricsObserver::LargestContentState; |
| using LargestContentTextOrImage = |
| page_load_metrics::ContentfulPaintTimingInfo::LargestContentTextOrImage; |
| using PageLoad = ukm::builders::PageLoad; |
| using MobileFriendliness = ukm::builders::MobileFriendliness; |
| using PageLoad_Internal = ukm::builders::PageLoad_Internal; |
| using UserPerceivedPageVisit = ukm::builders::UserPerceivedPageVisit; |
| |
| const char kTestUrl1[] = "https://www.google.com/"; |
| const char kTestUrl2[] = "https://www.example.com/"; |
| const char kSubframeTestUrl[] = "https://www.google.com/subframe.html"; |
| |
| class MockNetworkQualityProvider : public network::NetworkQualityTracker { |
| public: |
| MOCK_CONST_METHOD0(GetEffectiveConnectionType, |
| net::EffectiveConnectionType()); |
| MOCK_CONST_METHOD0(GetHttpRTT, base::TimeDelta()); |
| MOCK_CONST_METHOD0(GetTransportRTT, base::TimeDelta()); |
| MOCK_CONST_METHOD0(GetDownstreamThroughputKbps, int32_t()); |
| }; |
| |
| } // namespace |
| |
| class UkmPageLoadMetricsObserverTest |
| : public page_load_metrics::PageLoadMetricsObserverTestHarness { |
| protected: |
| void RegisterObservers(page_load_metrics::PageLoadTracker* tracker) override { |
| std::unique_ptr<UkmPageLoadMetricsObserver> observer = |
| std::make_unique<UkmPageLoadMetricsObserver>( |
| &mock_network_quality_provider_); |
| observer_ = observer.get(); |
| tracker->AddObserver(std::move(observer)); |
| } |
| |
| void SetUp() override { |
| page_load_metrics::PageLoadMetricsObserverTestHarness::SetUp(); |
| page_load_metrics::LargestContentfulPaintHandler::SetTestMode(true); |
| |
| EXPECT_CALL(mock_network_quality_provider_, GetEffectiveConnectionType()) |
| .Times(AnyNumber()) |
| .WillRepeatedly(Return(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN)); |
| |
| EXPECT_CALL(mock_network_quality_provider_, GetHttpRTT()) |
| .Times(AnyNumber()) |
| .WillRepeatedly(Return(base::TimeDelta())); |
| |
| EXPECT_CALL(mock_network_quality_provider_, GetTransportRTT()) |
| .Times(AnyNumber()) |
| .WillRepeatedly(Return(base::TimeDelta())); |
| |
| EXPECT_CALL(mock_network_quality_provider_, GetDownstreamThroughputKbps()) |
| .Times(AnyNumber()) |
| .WillRepeatedly(Return(int32_t())); |
| |
| bookmarks::BookmarkModel* bookmark_model = |
| BookmarkModelFactory::GetForBrowserContext(profile()); |
| bookmarks::test::WaitForBookmarkModelToLoad(bookmark_model); |
| |
| HistoryTabHelper::CreateForWebContents(web_contents()); |
| HistoryTabHelper::FromWebContents(web_contents()) |
| ->SetForceEligibleTabForTesting(true); |
| |
| HistoryClustersTabHelper::CreateForWebContents(web_contents()); |
| } |
| |
| TestingProfile::TestingFactories GetTestingFactories() const override { |
| return { |
| {BookmarkModelFactory::GetInstance(), |
| BookmarkModelFactory::GetDefaultFactory()}, |
| {HistoryServiceFactory::GetInstance(), |
| HistoryServiceFactory::GetDefaultFactory()}, |
| {TemplateURLServiceFactory::GetInstance(), |
| base::BindRepeating(&TemplateURLServiceFactory::BuildInstanceFor)}}; |
| } |
| |
| MockNetworkQualityProvider& mock_network_quality_provider() { |
| return mock_network_quality_provider_; |
| } |
| |
| // Tests that LCP reports the given |value|, |
| // and tests that the LCP content type reported is |text_or_image|. If |
| // |test_main_frame| is set, also tests that the main frame LCP histograms |
| // also report |value|. If |text_or_image| is kText, then tests that image |
| // BPP is not reported, and otherwise tests that it matches |bpp_bucket|. |
| void TestLCP(int value, |
| LargestContentTextOrImage text_or_image, |
| bool test_main_frame, |
| uint32_t bpp_bucket = 0) { |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| const ukm::mojom::UkmEntry* entry = merged_entries.begin()->second.get(); |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(entry, |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry, PageLoad::kPaintTiming_NavigationToLargestContentfulPaint2Name, |
| value); |
| if (test_main_frame) { |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry, |
| PageLoad:: |
| kPaintTiming_NavigationToLargestContentfulPaint2_MainFrameName, |
| value); |
| } |
| EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric( |
| entry, PageLoad::kPageTiming_ForegroundDurationName)); |
| |
| if (text_or_image == LargestContentTextOrImage::kText) { |
| EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric( |
| entry, PageLoad::kPaintTiming_LargestContentfulPaintBPPName)); |
| } else { |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry, PageLoad::kPaintTiming_LargestContentfulPaintBPPName, |
| bpp_bucket); |
| } |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> internal_merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad_Internal::kEntryName); |
| EXPECT_EQ(1ul, internal_merged_entries.size()); |
| const ukm::mojom::UkmEntry* internal_entry = |
| internal_merged_entries.begin()->second.get(); |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(internal_entry, |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| internal_entry, |
| PageLoad_Internal::kPaintTiming_LargestContentfulPaint_ContentTypeName, |
| static_cast<int>(text_or_image)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| internal_entry, |
| PageLoad_Internal:: |
| kPaintTiming_LargestContentfulPaint_TerminationStateName, |
| static_cast<int>(LargestContentState::kReported)); |
| } |
| |
| void TestNoLCP(LargestContentState state) { |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| const ukm::mojom::UkmEntry* entry = merged_entries.begin()->second.get(); |
| EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric( |
| entry, PageLoad::kPaintTiming_NavigationToLargestContentfulPaint2Name)); |
| EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric( |
| entry, |
| PageLoad:: |
| kPaintTiming_NavigationToLargestContentfulPaint2_MainFrameName)); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> internal_merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad_Internal::kEntryName); |
| EXPECT_EQ(1ul, internal_merged_entries.size()); |
| const ukm::mojom::UkmEntry* internal_entry = |
| internal_merged_entries.begin()->second.get(); |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(internal_entry, |
| GURL(kTestUrl1)); |
| EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric( |
| internal_entry, |
| PageLoad_Internal:: |
| kPaintTiming_LargestContentfulPaint_ContentTypeName)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| internal_entry, |
| PageLoad_Internal:: |
| kPaintTiming_LargestContentfulPaint_TerminationStateName, |
| static_cast<int>(state)); |
| } |
| |
| UkmPageLoadMetricsObserver* observer() const { return observer_; } |
| |
| private: |
| raw_ptr<UkmPageLoadMetricsObserver> observer_; // Non-owning raw pointer. |
| |
| MockNetworkQualityProvider mock_network_quality_provider_; |
| }; |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, NoMetrics) { |
| EXPECT_EQ(0ul, tester()->test_ukm_recorder().sources_count()); |
| EXPECT_EQ(0ul, tester()->test_ukm_recorder().entries_count()); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, Basic) { |
| // PageLoadTiming with all recorded metrics other than FMP. This allows us to |
| // verify both that all metrics are logged, and that we don't log metrics that |
| // aren't present in the PageLoadTiming struct. Logging of FMP is verified in |
| // the FirstMeaningfulPaint test below. |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| timing.parse_timing->parse_start = base::Milliseconds(100); |
| timing.document_timing->dom_content_loaded_event_start = |
| base::Milliseconds(200); |
| timing.paint_timing->first_paint = base::Milliseconds(250); |
| timing.paint_timing->first_contentful_paint = base::Milliseconds(300); |
| timing.document_timing->load_event_start = base::Milliseconds(500); |
| timing.input_to_navigation_start = base::Milliseconds(50); |
| PopulateRequiredTimingFields(&timing); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| tester()->SimulateTimingUpdate(timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kExperimental_InputToNavigationStartName, |
| 50); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kExperimental_Navigation_UserInitiatedName, |
| true); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kNavigation_PageTransitionName, |
| ui::PAGE_TRANSITION_LINK); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kNavigation_PageEndReason3Name, |
| page_load_metrics::END_CLOSE); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kParseTiming_NavigationToParseStartName, |
| 100); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad::kDocumentTiming_NavigationToDOMContentLoadedEventFiredName, |
| 200); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kPaintTiming_NavigationToFirstPaintName, |
| 250); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad::kPaintTiming_NavigationToFirstContentfulPaintName, 300); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad::kDocumentTiming_NavigationToLoadEventFiredName, 500); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kNet_HttpResponseCodeName, 200); |
| EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric( |
| kv.second.get(), |
| PageLoad:: |
| kExperimental_PaintTiming_NavigationToFirstMeaningfulPaintName)); |
| EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric( |
| kv.second.get(), PageLoad::kPageTiming_ForegroundDurationName)); |
| } |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> pagevisit_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| ukm::builders::UserPerceivedPageVisit::kEntryName); |
| EXPECT_EQ(1ul, pagevisit_entries.size()); |
| for (const auto& kv : pagevisit_entries) { |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| ukm::builders::UserPerceivedPageVisit::kUserInitiatedName, true); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, FailedProvisionalLoad) { |
| EXPECT_CALL(mock_network_quality_provider(), GetEffectiveConnectionType()) |
| .WillRepeatedly(Return(net::EFFECTIVE_CONNECTION_TYPE_2G)); |
| |
| // The following simulates a navigation that fails and should commit an error |
| // page, but finishes before the error page actually commits. |
| GURL url(kTestUrl1); |
| std::unique_ptr<content::NavigationSimulator> navigation = |
| content::NavigationSimulator::CreateRendererInitiated(url, main_rfh()); |
| navigation->Fail(net::ERR_TIMED_OUT); |
| navigation->AbortCommit(); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| // Make sure that only the following metrics are logged. In particular, no |
| // paint/document/etc timing metrics should be logged for failed provisional |
| // loads. |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kNavigation_PageTransitionName, |
| ui::PAGE_TRANSITION_LINK); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kNavigation_PageEndReason3Name, |
| page_load_metrics::END_PROVISIONAL_LOAD_FAILED); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad::kNet_EffectiveConnectionType2_OnNavigationStartName, |
| metrics::SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kNet_ErrorCode_OnFailedProvisionalLoadName, |
| static_cast<int64_t>(net::ERR_TIMED_OUT) * -1); |
| EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric( |
| kv.second.get(), PageLoad::kPageTiming_ForegroundDurationName)); |
| EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric( |
| kv.second.get(), |
| PageLoad::kPageTiming_NavigationToFailedProvisionalLoadName)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kExperimental_Navigation_UserInitiatedName, |
| false); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, FirstMeaningfulPaint) { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| timing.parse_timing->parse_start = base::Milliseconds(10); |
| timing.paint_timing->first_meaningful_paint = base::Milliseconds(600); |
| PopulateRequiredTimingFields(&timing); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| tester()->SimulateTimingUpdate(timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad:: |
| kExperimental_PaintTiming_NavigationToFirstMeaningfulPaintName, |
| 600); |
| EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric( |
| kv.second.get(), PageLoad::kPageTiming_ForegroundDurationName)); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, LargestImagePaint) { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| timing.paint_timing->largest_contentful_paint->largest_image_paint = |
| base::Milliseconds(600); |
| timing.paint_timing->largest_contentful_paint->largest_image_paint_size = 50u; |
| timing.paint_timing->largest_contentful_paint->image_bpp = 8.5; |
| PopulateExperimentalLCP(timing.paint_timing); |
| PopulateRequiredTimingFields(&timing); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| tester()->SimulateTimingUpdate(timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| TestLCP(600, LargestContentTextOrImage::kImage, true /* test_main_frame */, |
| 30 /* image_bpp = "8.0 - 9.0" */); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, LargestImageLoading) { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| // The largest image is loading so its paint time is set to TimeDelta(). |
| timing.paint_timing->largest_contentful_paint->largest_image_paint = |
| base::TimeDelta(); |
| timing.paint_timing->largest_contentful_paint->largest_image_paint_size = 50u; |
| // There is a text paint, but it must be ignored because it is smaller than |
| // the image paint. |
| timing.paint_timing->largest_contentful_paint->largest_text_paint = |
| base::Milliseconds(600); |
| timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 25u; |
| PopulateExperimentalLCP(timing.paint_timing); |
| PopulateRequiredTimingFields(&timing); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| tester()->SimulateTimingUpdate(timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| TestNoLCP(LargestContentState::kLargestImageLoading); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, LargestImageLoadingSmallerThanText) { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| // Largest image is loading. |
| timing.paint_timing->largest_contentful_paint->largest_image_paint = |
| base::TimeDelta(); |
| timing.paint_timing->largest_contentful_paint->largest_image_paint_size = 50u; |
| // Largest text is larger than image. |
| timing.paint_timing->largest_contentful_paint->largest_text_paint = |
| base::Milliseconds(600); |
| timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 80u; |
| PopulateExperimentalLCP(timing.paint_timing); |
| PopulateRequiredTimingFields(&timing); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| tester()->SimulateTimingUpdate(timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| TestLCP(600, LargestContentTextOrImage::kText, true /* test_main_frame */); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, |
| LargestImagePaint_DiscardBackgroundResult) { |
| std::unique_ptr<base::SimpleTestClock> mock_clock( |
| new base::SimpleTestClock()); |
| mock_clock->SetNow(base::Time::NowFromSystemTime()); |
| |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.paint_timing->largest_contentful_paint->largest_image_paint = |
| base::Milliseconds(600); |
| PopulateRequiredTimingFields(&timing); |
| // The duration between nav start and first background set to 1ms. |
| mock_clock->Advance(base::Milliseconds(1)); |
| web_contents()->WasHidden(); |
| NavigateAndCommit(GURL(kTestUrl1)); |
| tester()->SimulateTimingUpdate(timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| const ukm::mojom::UkmEntry* entry = merged_entries.begin()->second.get(); |
| EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric( |
| entry, PageLoad::kPaintTiming_NavigationToLargestContentfulPaint2Name)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry, PageLoad::kNavigation_PageEndReason3Name, |
| page_load_metrics::END_CLOSE); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> internal_merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad_Internal::kEntryName); |
| // RecordTimingMetrics() is not called in this test. |
| EXPECT_EQ(0ul, internal_merged_entries.size()); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, AbortNeverForegrounded) { |
| std::unique_ptr<base::SimpleTestClock> mock_clock( |
| new base::SimpleTestClock()); |
| mock_clock->SetNow(base::Time::NowFromSystemTime()); |
| |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| PopulateRequiredTimingFields(&timing); |
| // The duration between nav start and first background set to 1ms. |
| mock_clock->Advance(base::Milliseconds(1)); |
| web_contents()->WasHidden(); |
| NavigateAndCommit(GURL(kTestUrl1)); |
| tester()->SimulateTimingUpdate(timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| const ukm::mojom::UkmEntry* entry = merged_entries.begin()->second.get(); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry, PageLoad::kNavigation_PageEndReason3Name, |
| page_load_metrics::END_CLOSE); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry, PageLoad::kPageVisitFinalStatusName, |
| static_cast<int64_t>(PageVisitFinalStatus::kNeverForegrounded)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry, PageLoad::kNavigation_PageTransitionName, |
| ui::PAGE_TRANSITION_LINK); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry, PageLoad::kPageTiming_TotalForegroundDurationName, 0); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, FCPPlusPlus_DiscardBackgroundResult) { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| |
| PopulateRequiredTimingFields(&timing); |
| |
| // Backgrounding. The backgrounding time will be recorded. This time is very |
| // short but indefinite, so we should make sure we set a large enough time for |
| // the timings so that they are larger than the backgrounding time. |
| web_contents()->WasHidden(); |
| // Set a large enough value to make sure it will be larger than backgrounding |
| // time, so that the result will be discarded. |
| timing.paint_timing->largest_contentful_paint->largest_image_paint = |
| base::Seconds(10); |
| timing.paint_timing->largest_contentful_paint->largest_text_paint = |
| base::Seconds(10); |
| NavigateAndCommit(GURL(kTestUrl1)); |
| tester()->SimulateTimingUpdate(timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| const ukm::mojom::UkmEntry* entry = merged_entries.begin()->second.get(); |
| EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric( |
| entry, PageLoad::kPaintTiming_NavigationToLargestContentfulPaint2Name)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry, PageLoad::kNavigation_PageEndReason3Name, |
| page_load_metrics::END_CLOSE); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> internal_merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad_Internal::kEntryName); |
| // RecordTimingMetrics() is not called in this test. |
| EXPECT_EQ(0ul, internal_merged_entries.size()); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, FCPPlusPlus_ReportLastCandidate) { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| |
| PopulateRequiredTimingFields(&timing); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| timing.paint_timing->largest_contentful_paint->largest_image_paint = |
| base::Milliseconds(60); |
| timing.paint_timing->largest_contentful_paint->largest_image_paint_size = 10u; |
| timing.paint_timing->largest_contentful_paint->largest_text_paint = |
| base::Milliseconds(60); |
| timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 10u; |
| PopulateExperimentalLCP(timing.paint_timing); |
| tester()->SimulateTimingUpdate(timing); |
| |
| timing.paint_timing->largest_contentful_paint->largest_image_paint = |
| base::Milliseconds(600); |
| timing.paint_timing->largest_contentful_paint->largest_image_paint_size = 10u; |
| timing.paint_timing->largest_contentful_paint->largest_text_paint = |
| base::Milliseconds(600); |
| timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 10u; |
| PopulateExperimentalLCP(timing.paint_timing); |
| tester()->SimulateTimingUpdate(timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| // There's a tie here and in this case the LCP handler returns image as the |
| // type. |
| TestLCP(600, LargestContentTextOrImage::kImage, true /* test_main_frame */); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, LargestTextPaint) { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| timing.paint_timing->largest_contentful_paint->largest_text_paint = |
| base::Milliseconds(600); |
| timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 50u; |
| PopulateExperimentalLCP(timing.paint_timing); |
| PopulateRequiredTimingFields(&timing); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| tester()->SimulateTimingUpdate(timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| TestLCP(600, LargestContentTextOrImage::kText, true /* test_main_frame */); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, LargestContentfulPaint_Trace) { |
| // TODO(https://crbug.com/1266001): Improve unit tests support for tracing. |
| // In particular, the initialization call below is most likely too narrow / |
| // doesn't take care of everything that is needed. In the future we might |
| // need to 1) initialize tracing from a better place (maybe |
| // RenderViewHostTestEnabler) and 2) initialize more broadly (maybe via |
| // tracing::PerfettoTracedProcess::SetupForTesting method once it is |
| // reintroduced). |
| perfetto::internal::TrackRegistry::InitializeInstance(); |
| |
| using trace_analyzer::Query; |
| trace_analyzer::Start("*"); |
| { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| timing.paint_timing->largest_contentful_paint->largest_text_paint = |
| base::Milliseconds(600); |
| timing.paint_timing->largest_contentful_paint->largest_text_paint_size = |
| 1000; |
| PopulateRequiredTimingFields(&timing); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| tester()->SimulateTimingUpdate(timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| } |
| auto analyzer = trace_analyzer::Stop(); |
| trace_analyzer::TraceEventVector events; |
| Query q = Query::EventNameIs( |
| "NavStartToLargestContentfulPaint::Candidate::AllFrames::UKM"); |
| analyzer->FindEvents(q, &events); |
| EXPECT_EQ(1u, events.size()); |
| EXPECT_EQ("loading", events[0]->category); |
| EXPECT_TRUE(events[0]->HasArg("data")); |
| base::Value arg; |
| EXPECT_TRUE(events[0]->GetArgAsValue("data", &arg)); |
| ASSERT_TRUE(arg.is_dict()); |
| int time = arg.FindIntKey("durationInMilliseconds").value_or(0); |
| EXPECT_EQ(600, time); |
| int size = arg.FindIntKey("size").value_or(0); |
| EXPECT_EQ(1000, size); |
| const std::string* type = arg.FindStringKey("type"); |
| ASSERT_TRUE(type); |
| EXPECT_EQ("text", *type); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, |
| LargestContentfulPaint_Trace_InvalidateCandidate) { |
| using trace_analyzer::Query; |
| trace_analyzer::Start("loading"); |
| { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| timing.paint_timing->largest_contentful_paint->largest_text_paint = |
| base::Milliseconds(600); |
| timing.paint_timing->largest_contentful_paint->largest_text_paint_size = |
| 1000; |
| PopulateRequiredTimingFields(&timing); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| tester()->SimulateTimingUpdate(timing); |
| |
| timing.paint_timing->largest_contentful_paint->largest_text_paint = |
| absl::optional<base::TimeDelta>(); |
| timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 0; |
| PopulateRequiredTimingFields(&timing); |
| |
| tester()->SimulateTimingUpdate(timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| } |
| auto analyzer = trace_analyzer::Stop(); |
| |
| trace_analyzer::TraceEventVector candidate_events; |
| Query candidate_query = Query::EventNameIs( |
| "NavStartToLargestContentfulPaint::Candidate::AllFrames::UKM"); |
| analyzer->FindEvents(candidate_query, &candidate_events); |
| EXPECT_EQ(1u, candidate_events.size()); |
| |
| trace_analyzer::TraceEventVector invalidate_events; |
| Query invalidate_query = Query::EventNameIs( |
| "NavStartToLargestContentfulPaint::" |
| "Invalidate::AllFrames::UKM"); |
| analyzer->FindEvents(invalidate_query, &invalidate_events); |
| EXPECT_EQ(1u, invalidate_events.size()); |
| EXPECT_EQ("loading", invalidate_events[0]->category); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, LargestContentfulPaint_OnlyText) { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| timing.paint_timing->largest_contentful_paint->largest_text_paint = |
| base::Milliseconds(600); |
| timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 1000; |
| PopulateExperimentalLCP(timing.paint_timing); |
| PopulateRequiredTimingFields(&timing); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| tester()->SimulateTimingUpdate(timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| TestLCP(600, LargestContentTextOrImage::kText, true /* test_main_frame */); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, LargestContentfulPaint_OnlyImage) { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| timing.paint_timing->largest_contentful_paint->largest_image_paint = |
| base::Milliseconds(600); |
| timing.paint_timing->largest_contentful_paint->largest_image_paint_size = |
| 1000; |
| PopulateExperimentalLCP(timing.paint_timing); |
| PopulateRequiredTimingFields(&timing); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| tester()->SimulateTimingUpdate(timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| TestLCP(600, LargestContentTextOrImage::kImage, true /* test_main_frame */); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, |
| LargestContentfulPaint_ImageLargerThanText) { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| timing.paint_timing->largest_contentful_paint->largest_image_paint = |
| base::Milliseconds(600); |
| timing.paint_timing->largest_contentful_paint->largest_image_paint_size = |
| 1000; |
| timing.paint_timing->largest_contentful_paint->largest_text_paint = |
| base::Milliseconds(1000); |
| timing.paint_timing->largest_contentful_paint->largest_text_paint_size = 500; |
| PopulateExperimentalLCP(timing.paint_timing); |
| PopulateRequiredTimingFields(&timing); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| tester()->SimulateTimingUpdate(timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| TestLCP(600, LargestContentTextOrImage::kImage, true /* test_main_frame */); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, |
| LargestContentfulPaintAllFrames_OnlySubframe) { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| PopulateRequiredTimingFields(&timing); |
| |
| page_load_metrics::mojom::PageLoadTiming subframe_timing; |
| page_load_metrics::InitPageLoadTimingForTest(&subframe_timing); |
| subframe_timing.navigation_start = base::Time::FromDoubleT(2); |
| subframe_timing.paint_timing->largest_contentful_paint->largest_image_paint = |
| base::Milliseconds(4780); |
| subframe_timing.paint_timing->largest_contentful_paint |
| ->largest_image_paint_size = 100u; |
| PopulateExperimentalLCP(subframe_timing.paint_timing); |
| PopulateRequiredTimingFields(&subframe_timing); |
| |
| // Commit the main frame and a subframe. |
| NavigateAndCommit(GURL(kTestUrl1)); |
| RenderFrameHost* subframe = |
| NavigationSimulator::NavigateAndCommitFromDocument( |
| GURL(kSubframeTestUrl), |
| RenderFrameHostTester::For(web_contents()->GetMainFrame()) |
| ->AppendChild("subframe")); |
| |
| // Simulate timing updates in the main frame and the subframe. |
| tester()->SimulateTimingUpdate(timing); |
| tester()->SimulateTimingUpdate(subframe_timing, subframe); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| TestLCP(4780, LargestContentTextOrImage::kImage, false /* test_main_frame */); |
| |
| // Now test that there is no main-frame LCP. |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| const ukm::mojom::UkmEntry* entry = merged_entries.begin()->second.get(); |
| EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric( |
| entry, |
| PageLoad:: |
| kPaintTiming_NavigationToLargestContentfulPaint2_MainFrameName)); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, |
| LargestContentfulPaintAllFrames_SubframeImageLoading) { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| PopulateRequiredTimingFields(&timing); |
| |
| page_load_metrics::mojom::PageLoadTiming subframe_timing; |
| page_load_metrics::InitPageLoadTimingForTest(&subframe_timing); |
| subframe_timing.navigation_start = base::Time::FromDoubleT(2); |
| // Subframe's largest image is still loading. |
| subframe_timing.paint_timing->largest_contentful_paint->largest_image_paint = |
| base::TimeDelta(); |
| subframe_timing.paint_timing->largest_contentful_paint |
| ->largest_image_paint_size = 100u; |
| // Subframe has text but it should be ignored as it's smaller than image. |
| subframe_timing.paint_timing->largest_contentful_paint->largest_text_paint = |
| base::Milliseconds(3000); |
| subframe_timing.paint_timing->largest_contentful_paint |
| ->largest_text_paint_size = 80u; |
| PopulateExperimentalLCP(subframe_timing.paint_timing); |
| PopulateRequiredTimingFields(&subframe_timing); |
| |
| // Commit the main frame and a subframe. |
| NavigateAndCommit(GURL(kTestUrl1)); |
| RenderFrameHost* subframe = |
| NavigationSimulator::NavigateAndCommitFromDocument( |
| GURL(kSubframeTestUrl), |
| RenderFrameHostTester::For(web_contents()->GetMainFrame()) |
| ->AppendChild("subframe")); |
| |
| // Simulate timing updates in the main frame and the subframe. |
| tester()->SimulateTimingUpdate(timing); |
| tester()->SimulateTimingUpdate(subframe_timing, subframe); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| TestNoLCP(LargestContentState::kLargestImageLoading); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, |
| LargestContentfulPaintAllFrames_OnlyMainFrame) { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| timing.paint_timing->largest_contentful_paint->largest_image_paint = |
| base::Milliseconds(4780); |
| timing.paint_timing->largest_contentful_paint->largest_image_paint_size = |
| 100u; |
| PopulateExperimentalLCP(timing.paint_timing); |
| PopulateRequiredTimingFields(&timing); |
| |
| page_load_metrics::mojom::PageLoadTiming subframe_timing; |
| page_load_metrics::InitPageLoadTimingForTest(&subframe_timing); |
| subframe_timing.navigation_start = base::Time::FromDoubleT(2); |
| PopulateRequiredTimingFields(&subframe_timing); |
| |
| // Commit the main frame and a subframe. |
| NavigateAndCommit(GURL(kTestUrl1)); |
| RenderFrameHost* subframe = |
| NavigationSimulator::NavigateAndCommitFromDocument( |
| GURL(kSubframeTestUrl), |
| RenderFrameHostTester::For(web_contents()->GetMainFrame()) |
| ->AppendChild("subframe")); |
| |
| // Simulate timing updates in the main frame and the subframe. |
| tester()->SimulateTimingUpdate(timing); |
| tester()->SimulateTimingUpdate(subframe_timing, subframe); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| TestLCP(4780, LargestContentTextOrImage::kImage, true /* test_main_frame */); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, |
| LargestContentfulPaintAllFrames_CrossSiteSubFrame) { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| timing.paint_timing->largest_contentful_paint->largest_image_paint = |
| base::Milliseconds(4780); |
| timing.paint_timing->largest_contentful_paint->largest_image_paint_size = |
| 100u; |
| PopulateExperimentalLCP(timing.paint_timing); |
| PopulateRequiredTimingFields(&timing); |
| |
| // Subframe timing for same-site |
| page_load_metrics::mojom::PageLoadTiming subframe_timing; |
| page_load_metrics::InitPageLoadTimingForTest(&subframe_timing); |
| subframe_timing.navigation_start = base::Time::FromDoubleT(2); |
| subframe_timing.paint_timing->largest_contentful_paint->largest_image_paint = |
| base::Milliseconds(4790); |
| subframe_timing.paint_timing->largest_contentful_paint |
| ->largest_image_paint_size = 110u; |
| PopulateExperimentalLCP(subframe_timing.paint_timing); |
| PopulateRequiredTimingFields(&subframe_timing); |
| |
| // Subframe timing for cross-site |
| page_load_metrics::mojom::PageLoadTiming subframe_timing2; |
| page_load_metrics::InitPageLoadTimingForTest(&subframe_timing2); |
| subframe_timing2.navigation_start = base::Time::FromDoubleT(2); |
| subframe_timing2.paint_timing->largest_contentful_paint->largest_image_paint = |
| base::Milliseconds(4800); |
| subframe_timing2.paint_timing->largest_contentful_paint |
| ->largest_image_paint_size = 50u; |
| PopulateExperimentalLCP(subframe_timing2.paint_timing); |
| PopulateRequiredTimingFields(&subframe_timing2); |
| |
| // Commit the main frame and a subframe. |
| const char kSameSiteSubframeTestUrl[] = |
| "https://sub.google.com/subframe.html"; |
| const char kCrossSiteSubframeTestUrl[] = "https://example.com/subframe.html"; |
| NavigateAndCommit(GURL(kTestUrl1)); |
| RenderFrameHost* same_site_subframe = |
| NavigationSimulator::NavigateAndCommitFromDocument( |
| GURL(kSameSiteSubframeTestUrl), |
| RenderFrameHostTester::For(web_contents()->GetMainFrame()) |
| ->AppendChild("same_site_subframe")); |
| RenderFrameHost* cross_site_subframe = |
| NavigationSimulator::NavigateAndCommitFromDocument( |
| GURL(kCrossSiteSubframeTestUrl), |
| RenderFrameHostTester::For(web_contents()->GetMainFrame()) |
| ->AppendChild("cross_site_subframe")); |
| |
| // Simulate timing updates in the main frame and the subframe. |
| tester()->SimulateTimingUpdate(timing); |
| tester()->SimulateTimingUpdate(subframe_timing, same_site_subframe); |
| tester()->SimulateTimingUpdate(subframe_timing2, cross_site_subframe); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| // Now test that there is a cross site subframe lcp. |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| const ukm::mojom::UkmEntry* entry = merged_entries.begin()->second.get(); |
| |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry, |
| PageLoad:: |
| kPaintTiming_NavigationToLargestContentfulPaint2_CrossSiteSubFrameName, |
| 4800); |
| } |
| |
| // This is to test whether LargestContentfulPaintAllFrames can merge the |
| // candidates from different frames correctly. The metric should pick the larger |
| // candidate during merging. |
| TEST_F(UkmPageLoadMetricsObserverTest, |
| LargestContentfulPaintAllFrames_MergeFrameCandidateBySize) { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| timing.paint_timing->largest_contentful_paint->largest_image_paint = |
| base::Milliseconds(4780); |
| timing.paint_timing->largest_contentful_paint->largest_image_paint_size = 50u; |
| PopulateExperimentalLCP(timing.paint_timing); |
| PopulateRequiredTimingFields(&timing); |
| |
| page_load_metrics::mojom::PageLoadTiming subframe_timing; |
| page_load_metrics::InitPageLoadTimingForTest(&subframe_timing); |
| subframe_timing.navigation_start = base::Time::FromDoubleT(2); |
| subframe_timing.paint_timing->largest_contentful_paint->largest_image_paint = |
| base::Milliseconds(990); |
| subframe_timing.paint_timing->largest_contentful_paint |
| ->largest_image_paint_size = 100u; |
| PopulateExperimentalLCP(subframe_timing.paint_timing); |
| PopulateRequiredTimingFields(&subframe_timing); |
| |
| // Commit the main frame and a subframe. |
| NavigateAndCommit(GURL(kTestUrl1)); |
| RenderFrameHost* subframe = |
| NavigationSimulator::NavigateAndCommitFromDocument( |
| GURL(kSubframeTestUrl), |
| RenderFrameHostTester::For(web_contents()->GetMainFrame()) |
| ->AppendChild("subframe")); |
| |
| // Simulate timing updates in the main frame and the subframe. |
| tester()->SimulateTimingUpdate(timing); |
| tester()->SimulateTimingUpdate(subframe_timing, subframe); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| TestLCP(990, LargestContentTextOrImage::kImage, false /* test_main_frame */); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, |
| NormalizedUserInteractionLatenciesWithoutAllLatencies) { |
| NavigateAndCommit(GURL(kTestUrl1)); |
| |
| page_load_metrics::mojom::InputTiming input_timing; |
| input_timing.num_interactions = 3; |
| input_timing.max_event_durations = |
| UserInteractionLatencies::NewWorstInteractionLatency( |
| base::Milliseconds(120)); |
| input_timing.total_event_durations = |
| UserInteractionLatencies::NewWorstInteractionLatency( |
| base::Milliseconds(140)); |
| |
| tester()->SimulateInputTimingUpdate(input_timing); |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad:: |
| kInteractiveTiming_WorstUserInteractionLatency_MaxEventDurationName, |
| 120); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad:: |
| kInteractiveTiming_WorstUserInteractionLatency_TotalEventDurationName, |
| 140); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, |
| NormalizedUserInteractionLatenciesWithAllLatencies) { |
| // Flip the flag. |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| blink::features::kSendAllUserInteractionLatencies); |
| NavigateAndCommit(GURL(kTestUrl1)); |
| |
| page_load_metrics::mojom::InputTiming input_timing; |
| input_timing.num_interactions = 3; |
| input_timing.max_event_durations = |
| UserInteractionLatencies::NewUserInteractionLatencies({}); |
| auto& max_event_durations = |
| input_timing.max_event_durations->get_user_interaction_latencies(); |
| |
| max_event_durations.emplace_back(UserInteractionLatency::New( |
| base::Milliseconds(50), UserInteractionType::kKeyboard)); |
| max_event_durations.emplace_back(UserInteractionLatency::New( |
| base::Milliseconds(100), UserInteractionType::kTapOrClick)); |
| max_event_durations.emplace_back(UserInteractionLatency::New( |
| base::Milliseconds(150), UserInteractionType::kDrag)); |
| input_timing.total_event_durations = |
| UserInteractionLatencies::NewUserInteractionLatencies({}); |
| auto& total_event_durations = |
| input_timing.total_event_durations->get_user_interaction_latencies(); |
| |
| total_event_durations.emplace_back(UserInteractionLatency::New( |
| base::Milliseconds(55), UserInteractionType::kKeyboard)); |
| total_event_durations.emplace_back(UserInteractionLatency::New( |
| base::Milliseconds(105), UserInteractionType::kTapOrClick)); |
| total_event_durations.emplace_back(UserInteractionLatency::New( |
| base::Milliseconds(155), UserInteractionType::kDrag)); |
| |
| tester()->SimulateInputTimingUpdate(input_timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad:: |
| kInteractiveTiming_WorstUserInteractionLatency_MaxEventDurationName, |
| 150); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad:: |
| kInteractiveTiming_WorstUserInteractionLatencyOverBudget_MaxEventDurationName, |
| 50); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad:: |
| kInteractiveTiming_SumOfUserInteractionLatencyOverBudget_MaxEventDurationName, |
| 50); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad:: |
| kInteractiveTiming_AverageUserInteractionLatencyOverBudget_MaxEventDurationName, |
| 16); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad:: |
| kInteractiveTiming_SlowUserInteractionLatencyOverBudget_HighPercentile_MaxEventDurationName, |
| 50); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad:: |
| kInteractiveTiming_SlowUserInteractionLatencyOverBudget_HighPercentile2_MaxEventDurationName, |
| 50); |
| |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad:: |
| kInteractiveTiming_WorstUserInteractionLatency_TotalEventDurationName, |
| 155); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad:: |
| kInteractiveTiming_WorstUserInteractionLatencyOverBudget_TotalEventDurationName, |
| 55); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad:: |
| kInteractiveTiming_SumOfUserInteractionLatencyOverBudget_TotalEventDurationName, |
| 65); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad:: |
| kInteractiveTiming_AverageUserInteractionLatencyOverBudget_TotalEventDurationName, |
| 21); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad:: |
| kInteractiveTiming_SlowUserInteractionLatencyOverBudget_HighPercentile_TotalEventDurationName, |
| 55); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad:: |
| kInteractiveTiming_SlowUserInteractionLatencyOverBudget_HighPercentile2_TotalEventDurationName, |
| 55); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, |
| FirstInputDelayAndTimestampAndProcessingTime) { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| timing.interactive_timing->first_input_delay = base::Milliseconds(50); |
| timing.interactive_timing->first_input_timestamp = base::Milliseconds(712); |
| timing.interactive_timing->first_input_processing_time = |
| base::Milliseconds(25); |
| PopulateRequiredTimingFields(&timing); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| tester()->SimulateTimingUpdate(timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kInteractiveTiming_FirstInputDelay4Name, 50); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kInteractiveTiming_FirstInputTimestamp4Name, |
| 712); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad::kInteractiveTiming_FirstInputProcessingTimesName, 25); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, LongestInputDelayAndTimestamp) { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| timing.interactive_timing->longest_input_delay = base::Milliseconds(50); |
| timing.interactive_timing->longest_input_timestamp = base::Milliseconds(712); |
| PopulateRequiredTimingFields(&timing); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| tester()->SimulateTimingUpdate(timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kInteractiveTiming_LongestInputDelay4Name, |
| 50); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad::kInteractiveTiming_LongestInputTimestamp4Name, 712); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, InputTiming) { |
| NavigateAndCommit(GURL(kTestUrl1)); |
| |
| page_load_metrics::mojom::InputTiming input_timing; |
| input_timing.num_input_events = 2; |
| input_timing.total_input_delay = base::Milliseconds(100); |
| input_timing.total_adjusted_input_delay = base::Milliseconds(10); |
| tester()->SimulateInputTimingUpdate(input_timing); |
| |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kInteractiveTiming_NumInputEventsName, 2); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kInteractiveTiming_TotalInputDelayName, 100); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad::kInteractiveTiming_TotalAdjustedInputDelayName, 10); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, MobileFriendliness) { |
| NavigateAndCommit(GURL(kTestUrl1)); |
| blink::MobileFriendliness mobile_friendliness; |
| mobile_friendliness.viewport_device_width = false; |
| mobile_friendliness.viewport_hardcoded_width = 533; |
| mobile_friendliness.viewport_initial_scale_x10 = 10; |
| mobile_friendliness.allow_user_zoom = true; |
| mobile_friendliness.small_text_ratio = 2; |
| const int expected_viewport_hardcoded_width = 520; |
| const int expected_viewport_initial_scale = 10; |
| |
| tester()->SimulateMobileFriendlinessUpdate(mobile_friendliness); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| MobileFriendliness::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), MobileFriendliness::kViewportDeviceWidthName, false); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), MobileFriendliness::kViewportHardcodedWidthName, |
| expected_viewport_hardcoded_width); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), MobileFriendliness::kViewportInitialScaleX10Name, |
| expected_viewport_initial_scale); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), MobileFriendliness::kAllowUserZoomName, true); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), MobileFriendliness::kSmallTextRatioName, 2); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, FirstScrollDelayAndTimestamp) { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| timing.interactive_timing->first_scroll_delay = base::Milliseconds(50); |
| timing.interactive_timing->first_scroll_timestamp = base::Milliseconds(70); |
| PopulateRequiredTimingFields(&timing); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| tester()->SimulateTimingUpdate(timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kInteractiveTiming_FirstScrollDelayName, 50); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kInteractiveTiming_FirstScrollTimestampName, |
| 64); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, MultiplePageLoads) { |
| page_load_metrics::mojom::PageLoadTiming timing1; |
| page_load_metrics::InitPageLoadTimingForTest(&timing1); |
| timing1.parse_timing->parse_start = base::Milliseconds(10); |
| timing1.navigation_start = base::Time::FromDoubleT(1); |
| timing1.paint_timing->first_contentful_paint = base::Milliseconds(200); |
| PopulateRequiredTimingFields(&timing1); |
| |
| // Second navigation reports no timing metrics. |
| page_load_metrics::mojom::PageLoadTiming timing2; |
| page_load_metrics::InitPageLoadTimingForTest(&timing2); |
| timing2.navigation_start = base::Time::FromDoubleT(1); |
| PopulateRequiredTimingFields(&timing2); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| tester()->SimulateTimingUpdate(timing1); |
| |
| NavigateAndCommit(GURL(kTestUrl2)); |
| tester()->SimulateTimingUpdate(timing2); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(2ul, merged_entries.size()); |
| const ukm::mojom::UkmEntry* entry1 = nullptr; |
| const ukm::mojom::UkmEntry* entry2 = nullptr; |
| for (const auto& kv : merged_entries) { |
| if (tester()->test_ukm_recorder().EntryHasMetric( |
| kv.second.get(), |
| PageLoad::kPaintTiming_NavigationToFirstContentfulPaintName)) { |
| entry1 = kv.second.get(); |
| } else { |
| entry2 = kv.second.get(); |
| } |
| } |
| ASSERT_NE(entry1, nullptr); |
| ASSERT_NE(entry2, nullptr); |
| |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(entry1, |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry1, PageLoad::kNavigation_PageEndReason3Name, |
| page_load_metrics::END_NEW_NAVIGATION); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry1, PageLoad::kPaintTiming_NavigationToFirstContentfulPaintName, 200); |
| EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric( |
| entry1, |
| PageLoad:: |
| kExperimental_PaintTiming_NavigationToFirstMeaningfulPaintName)); |
| EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric( |
| entry1, PageLoad::kPageTiming_ForegroundDurationName)); |
| |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(entry2, |
| GURL(kTestUrl2)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry2, PageLoad::kNavigation_PageEndReason3Name, |
| page_load_metrics::END_CLOSE); |
| EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric( |
| entry2, PageLoad::kParseTiming_NavigationToParseStartName)); |
| EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric( |
| entry2, PageLoad::kPaintTiming_NavigationToFirstContentfulPaintName)); |
| EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric( |
| entry2, |
| PageLoad:: |
| kExperimental_PaintTiming_NavigationToFirstMeaningfulPaintName)); |
| EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric( |
| entry2, PageLoad::kPageTiming_ForegroundDurationName)); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, NetworkQualityEstimates) { |
| EXPECT_CALL(mock_network_quality_provider(), GetEffectiveConnectionType()) |
| .WillRepeatedly(Return(net::EFFECTIVE_CONNECTION_TYPE_3G)); |
| EXPECT_CALL(mock_network_quality_provider(), GetHttpRTT()) |
| .WillRepeatedly(Return(base::Milliseconds(100))); |
| EXPECT_CALL(mock_network_quality_provider(), GetTransportRTT()) |
| .WillRepeatedly(Return(base::Milliseconds(200))); |
| EXPECT_CALL(mock_network_quality_provider(), GetDownstreamThroughputKbps()) |
| .WillRepeatedly(Return(300)); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad::kNet_EffectiveConnectionType2_OnNavigationStartName, |
| metrics::SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_3G); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kNet_HttpRttEstimate_OnNavigationStartName, |
| 100); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad::kNet_TransportRttEstimate_OnNavigationStartName, 200); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| PageLoad::kNet_DownstreamKbpsEstimate_OnNavigationStartName, 300); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, PageTransitionReload) { |
| GURL url(kTestUrl1); |
| tester()->NavigateWithPageTransitionAndCommit(GURL(kTestUrl1), |
| ui::PAGE_TRANSITION_RELOAD); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kNavigation_PageTransitionName, |
| ui::PAGE_TRANSITION_RELOAD); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, PageSizeMetrics) { |
| NavigateAndCommit(GURL(kTestUrl1)); |
| |
| std::vector<page_load_metrics::mojom::ResourceDataUpdatePtr> resources; |
| // Cached resource. |
| resources.push_back(CreateResource(true /* was_cached */, 0 /* delta_bytes */, |
| 20 * 1024 /* encoded_body_length */, |
| 30 * 1024 /* decoded_body_length */, |
| true /* is_complete */)); |
| // Uncached resource. |
| resources.push_back(CreateResource( |
| false /* was_cached */, 40 * 1024 /* delta_bytes */, |
| 40 * 1024 /* encoded_body_length */, 50 * 1024 /* decoded_body_length */, |
| true /* is_complete */)); |
| tester()->SimulateResourceDataUseUpdate(resources); |
| |
| int64_t network_bytes = 0; |
| int64_t cache_bytes = 0; |
| for (const auto& request : resources) { |
| if (request->cache_type == |
| page_load_metrics::mojom::CacheType::kNotCached) { |
| network_bytes += request->delta_bytes; |
| } else { |
| cache_bytes += request->encoded_body_length; |
| } |
| } |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| int64_t bucketed_network_bytes = |
| ukm::GetExponentialBucketMin(network_bytes, 1.3); |
| int64_t bucketed_cache_bytes = ukm::GetExponentialBucketMin(cache_bytes, 1.3); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), "Net.NetworkBytes2", bucketed_network_bytes); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), "Net.CacheBytes2", bucketed_cache_bytes); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, JSSizeMetrics) { |
| NavigateAndCommit(GURL(kTestUrl1)); |
| |
| std::vector<page_load_metrics::mojom::ResourceDataUpdatePtr> resources; |
| // 30 kilobytes after decoding. |
| resources.push_back(CreateResource(true /* was_cached */, 0 /* delta_bytes */, |
| 20 * 1024 /* encoded_body_length */, |
| 30 * 1024 /* decoded_body_length */, |
| true /* is_complete */)); |
| |
| // 50 kilobytes after decoding. |
| resources.push_back(CreateResource( |
| false /* was_cached */, 40 * 1024 /* delta_bytes */, |
| 40 * 1024 /* encoded_body_length */, 50 * 1024 /* decoded_body_length */, |
| true /* is_complete */)); |
| |
| // 120 kilobytes after decoding, not JS. |
| resources.push_back(CreateResource( |
| false /* was_cached */, 40 * 1024 /* delta_bytes */, |
| 100 * 1024 /* encoded_body_length */, |
| 120 * 1024 /* decoded_body_length */, true /* is_complete */)); |
| |
| resources[0]->mime_type = "application/javascript"; |
| resources[1]->mime_type = "application/javascript"; |
| resources[2]->mime_type = "test"; |
| |
| tester()->SimulateResourceDataUseUpdate(resources); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| // Metrics look at decoded body length. |
| // 30 + 50 = 80 kilobytes. |
| int64_t bucketed_network_js_bytes = |
| ukm::GetExponentialBucketMinForBytes(80 * 1024); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), "Net.JavaScriptBytes2", bucketed_network_js_bytes); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, JSMaxSizeMetrics) { |
| NavigateAndCommit(GURL(kTestUrl1)); |
| |
| std::vector<page_load_metrics::mojom::ResourceDataUpdatePtr> resources; |
| |
| // 30 kilobytes after decoding. |
| resources.push_back(CreateResource(true /* was_cached */, 0 /* delta_bytes */, |
| 20 * 1024 /* encoded_body_length */, |
| 30 * 1024 /* decoded_body_length */, |
| true /* is_complete */)); |
| |
| // 500 kilobytes after decoding. |
| resources.push_back(CreateResource( |
| false /* was_cached */, 400 * 1024 /* delta_bytes */, |
| 400 * 1024 /* encoded_body_length */, |
| 500 * 1024 /* decoded_body_length */, true /* is_complete */)); |
| |
| // 120 kilobytes after decoding, not JS. |
| resources.push_back(CreateResource( |
| false /* was_cached */, 40 * 1024 /* delta_bytes */, |
| 100 * 1024 /* encoded_body_length */, |
| 120 * 1024 /* decoded_body_length */, true /* is_complete */)); |
| |
| resources[0]->mime_type = "application/javascript"; |
| resources[1]->mime_type = "application/javascript"; |
| resources[2]->mime_type = "test"; |
| |
| tester()->SimulateResourceDataUseUpdate(resources); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| // Metrics look at max decoded body length. |
| // max(30,500) = 500 kilobytes. |
| int64_t bucketed_network_js_max_bytes = |
| ukm::GetExponentialBucketMinForBytes(500 * 1024); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), "Net.JavaScriptMaxBytes2", |
| bucketed_network_js_max_bytes); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, ImageMediaSizeMetrics) { |
| NavigateAndCommit(GURL(kTestUrl1)); |
| |
| std::vector<page_load_metrics::mojom::ResourceDataUpdatePtr> resources; |
| resources.push_back(CreateResource( |
| false /* was_cached */, 10 * 1024 /* delta_bytes */, |
| 10 * 1024 /* encoded_body_length */, 10 * 1024 /* decoded_body_length */, |
| true /* is_complete */)); |
| resources.push_back(CreateResource( |
| false /* was_cached */, 20 * 1024 /* delta_bytes */, |
| 20 * 1024 /* encoded_body_length */, 20 * 1024 /* decoded_body_length */, |
| true /* is_complete */)); |
| resources.push_back(CreateResource( |
| false /* was_cached */, 50 * 1024 /* delta_bytes */, |
| 50 * 1024 /* encoded_body_length */, 50 * 1024 /* decoded_body_length */, |
| true /* is_complete */)); |
| |
| resources[0]->mime_type = "image/png"; |
| resources[0]->is_main_frame_resource = true; |
| resources[1]->mime_type = "image/jpg"; |
| resources[1]->is_main_frame_resource = false; |
| resources[2]->mime_type = "video/mp4"; |
| |
| tester()->SimulateResourceDataUseUpdate(resources); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| // 30 KB for all images, 20 KB for subframe images, and 50 KB for media. |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), "Net.ImageBytes2", |
| ukm::GetExponentialBucketMinForBytes(30 * 1024)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), "Net.ImageSubframeBytes2", |
| ukm::GetExponentialBucketMinForBytes(20 * 1024)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), "Net.MediaBytes2", |
| ukm::GetExponentialBucketMinForBytes(50 * 1024)); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, CpuTimeMetrics) { |
| NavigateAndCommit(GURL(kTestUrl1)); |
| |
| // Simulate some CPU usage. |
| page_load_metrics::mojom::CpuTiming cpu_timing(base::Milliseconds(500)); |
| tester()->SimulateCpuTimingUpdate(cpu_timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kCpuTimeName, 500); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, LayoutInstability) { |
| NavigateAndCommit(GURL(kTestUrl1)); |
| base::TimeTicks current_time = base::TimeTicks::Now(); |
| page_load_metrics::mojom::FrameRenderDataUpdate render_data(1.0, 1.0, 0, 0, 0, |
| 0, {}); |
| render_data.new_layout_shifts.emplace_back( |
| page_load_metrics::mojom::LayoutShift::New( |
| current_time - base::Milliseconds(4000), 0.5)); |
| render_data.new_layout_shifts.emplace_back( |
| page_load_metrics::mojom::LayoutShift::New( |
| current_time - base::Milliseconds(3500), 0.5)); |
| |
| tester()->SimulateRenderDataUpdate(render_data); |
| |
| // Simulate hiding the tab (the report should include shifts after hide). |
| web_contents()->WasHidden(); |
| |
| page_load_metrics::mojom::FrameRenderDataUpdate render_data_2(1.5, 0.0, 0, 0, |
| 0, 0, {}); |
| render_data_2.new_layout_shifts.emplace_back( |
| page_load_metrics::mojom::LayoutShift::New( |
| current_time - base::Milliseconds(2500), 1.5)); |
| tester()->SimulateRenderDataUpdate(render_data_2); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| const auto& ukm_recorder = tester()->test_ukm_recorder(); |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| ukm_recorder.GetMergedEntriesByName(PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| const ukm::mojom::UkmEntry* ukm_entry = kv.second.get(); |
| ukm_recorder.ExpectEntrySourceHasUrl(ukm_entry, GURL(kTestUrl1)); |
| ukm_recorder.ExpectEntryMetric( |
| ukm_entry, PageLoad::kLayoutInstability_CumulativeShiftScoreName, 250); |
| ukm_recorder.ExpectEntryMetric( |
| ukm_entry, |
| PageLoad:: |
| kLayoutInstability_CumulativeShiftScore_MainFrame_BeforeInputOrScrollName, |
| 100); |
| ukm_recorder.ExpectEntryMetric( |
| ukm_entry, |
| PageLoad:: |
| kLayoutInstability_MaxCumulativeShiftScore_SessionWindow_Gap1000ms_Max5000msName, |
| 250); |
| ukm_recorder.ExpectEntryMetric(kv.second.get(), |
| PageLoad::kNavigation_PageEndReason3Name, |
| page_load_metrics::END_CLOSE); |
| } |
| EXPECT_THAT(tester()->histogram_tester().GetAllSamples( |
| "PageLoad.LayoutInstability.CumulativeShiftScore"), |
| testing::ElementsAre(base::Bucket(25, 1))); |
| EXPECT_THAT(tester()->histogram_tester().GetAllSamples( |
| "PageLoad.LayoutInstability.MaxCumulativeShiftScore." |
| "SessionWindow.Gap1000ms.Max5000ms"), |
| testing::ElementsAre(base::Bucket(25, 1))); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, |
| ExperimentalLayoutInstabilityRecordOnHidden) { |
| NavigateAndCommit(GURL(kTestUrl1)); |
| base::TimeTicks current_time = base::TimeTicks::Now(); |
| page_load_metrics::mojom::FrameRenderDataUpdate render_data(1.0, 1.0, 0, 0, 0, |
| 0, {}); |
| render_data.new_layout_shifts.emplace_back( |
| page_load_metrics::mojom::LayoutShift::New( |
| current_time - base::Milliseconds(4000), 0.5)); |
| render_data.new_layout_shifts.emplace_back( |
| page_load_metrics::mojom::LayoutShift::New( |
| current_time - base::Milliseconds(3500), 0.5)); |
| |
| tester()->SimulateRenderDataUpdate(render_data); |
| |
| // Simulate hiding the tab (the experimental CLS metrics should include |
| // shifts before the first hide). |
| web_contents()->WasHidden(); |
| |
| const auto& ukm_recorder = tester()->test_ukm_recorder(); |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| ukm_recorder.GetMergedEntriesByName(PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| const ukm::mojom::UkmEntry* ukm_entry = kv.second.get(); |
| ukm_recorder.ExpectEntrySourceHasUrl(ukm_entry, GURL(kTestUrl1)); |
| ukm_recorder.ExpectEntryMetric( |
| ukm_entry, |
| PageLoad:: |
| kExperimental_LayoutInstability_CumulativeShiftScoreAtFirstOnHiddenName, |
| 100); |
| ukm_recorder.ExpectEntryMetric( |
| ukm_entry, |
| PageLoad:: |
| kExperimental_LayoutInstability_MaxCumulativeShiftScoreAtFirstOnHidden_SessionWindow_Gap1000ms_Max5000msName, |
| 100); |
| } |
| |
| page_load_metrics::mojom::FrameRenderDataUpdate render_data_2(1.5, 0.0, 0, 0, |
| 0, 0, {}); |
| render_data_2.new_layout_shifts.emplace_back( |
| page_load_metrics::mojom::LayoutShift::New( |
| current_time - base::Milliseconds(2500), 1.5)); |
| tester()->SimulateRenderDataUpdate(render_data_2); |
| |
| // Simulate closing the tab (the CLS metrics should include all the shifts |
| // before the tab closes). |
| DeleteContents(); |
| |
| const auto& ukm_recorder_2 = tester()->test_ukm_recorder(); |
| merged_entries = ukm_recorder_2.GetMergedEntriesByName(PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| const ukm::mojom::UkmEntry* ukm_entry = kv.second.get(); |
| ukm_recorder_2.ExpectEntrySourceHasUrl(ukm_entry, GURL(kTestUrl1)); |
| ukm_recorder.ExpectEntryMetric( |
| ukm_entry, |
| PageLoad:: |
| kExperimental_LayoutInstability_CumulativeShiftScoreAtFirstOnHiddenName, |
| 100); |
| ukm_recorder.ExpectEntryMetric( |
| ukm_entry, |
| PageLoad:: |
| kExperimental_LayoutInstability_MaxCumulativeShiftScoreAtFirstOnHidden_SessionWindow_Gap1000ms_Max5000msName, |
| 100); |
| ukm_recorder_2.ExpectEntryMetric( |
| ukm_entry, PageLoad::kLayoutInstability_CumulativeShiftScoreName, 250); |
| ukm_recorder_2.ExpectEntryMetric( |
| ukm_entry, |
| PageLoad:: |
| kLayoutInstability_CumulativeShiftScore_MainFrame_BeforeInputOrScrollName, |
| 100); |
| ukm_recorder_2.ExpectEntryMetric( |
| ukm_entry, |
| PageLoad:: |
| kLayoutInstability_MaxCumulativeShiftScore_SessionWindow_Gap1000ms_Max5000msName, |
| 250); |
| ukm_recorder_2.ExpectEntryMetric(kv.second.get(), |
| PageLoad::kNavigation_PageEndReason3Name, |
| page_load_metrics::END_CLOSE); |
| } |
| EXPECT_THAT(tester()->histogram_tester().GetAllSamples( |
| "PageLoad.LayoutInstability.CumulativeShiftScore"), |
| testing::ElementsAre(base::Bucket(25, 1))); |
| EXPECT_THAT(tester()->histogram_tester().GetAllSamples( |
| "PageLoad.LayoutInstability.MaxCumulativeShiftScore." |
| "SessionWindow.Gap1000ms.Max5000ms"), |
| testing::ElementsAre(base::Bucket(25, 1))); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, |
| ExperimentalLayoutInstabilityRecordOnPageOpenBackground) { |
| // Open the page at the background. |
| web_contents()->WasHidden(); |
| NavigateAndCommit(GURL(kTestUrl1)); |
| base::TimeTicks current_time = base::TimeTicks::Now(); |
| |
| // Bring the tab to the foreground and simulate a layout shift. |
| web_contents()->WasShown(); |
| page_load_metrics::mojom::FrameRenderDataUpdate render_data(1.0, 1.0, 0, 0, 0, |
| 0, {}); |
| render_data.new_layout_shifts.emplace_back( |
| page_load_metrics::mojom::LayoutShift::New( |
| current_time - base::Milliseconds(4000), 0.5)); |
| render_data.new_layout_shifts.emplace_back( |
| page_load_metrics::mojom::LayoutShift::New( |
| current_time - base::Milliseconds(3500), 0.5)); |
| |
| tester()->SimulateRenderDataUpdate(render_data); |
| |
| // Simulate hiding the tab (the report should include shifts before hide). |
| web_contents()->WasHidden(); |
| |
| const auto& ukm_recorder = tester()->test_ukm_recorder(); |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| ukm_recorder.GetMergedEntriesByName(PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| const ukm::mojom::UkmEntry* ukm_entry = kv.second.get(); |
| ukm_recorder.ExpectEntrySourceHasUrl(ukm_entry, GURL(kTestUrl1)); |
| ukm_recorder.ExpectEntryMetric( |
| ukm_entry, |
| PageLoad:: |
| kExperimental_LayoutInstability_CumulativeShiftScoreAtFirstOnHiddenName, |
| 100); |
| ukm_recorder.ExpectEntryMetric( |
| ukm_entry, |
| PageLoad:: |
| kExperimental_LayoutInstability_MaxCumulativeShiftScoreAtFirstOnHidden_SessionWindow_Gap1000ms_Max5000msName, |
| 100); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, SiteInstanceRenderProcessAssignment) { |
| NavigateAndCommit(GURL(kTestUrl1)); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| const auto& ukm_recorder = tester()->test_ukm_recorder(); |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| ukm_recorder.GetMergedEntriesByName(PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| const int64_t* metric = ukm_recorder.GetEntryMetric( |
| kv.second.get(), |
| ukm::builders::PageLoad::kSiteInstanceRenderProcessAssignmentName); |
| EXPECT_TRUE(metric); |
| EXPECT_NE(0u, *metric); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, MHTMLNotTrackedOfflinePreview) { |
| auto navigation = content::NavigationSimulator::CreateBrowserInitiated( |
| GURL(kTestUrl1), web_contents()); |
| navigation->SetContentsMimeType("multipart/related"); |
| navigation->Commit(); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(0ul, merged_entries.size()); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, LayoutInstabilitySubframeAggregation) { |
| NavigateAndCommit(GURL(kTestUrl1)); |
| |
| // Simulate layout instability in the main frame. |
| page_load_metrics::mojom::FrameRenderDataUpdate render_data(1.0, 1.0, 0, 0, 0, |
| 0, {}); |
| tester()->SimulateRenderDataUpdate(render_data); |
| |
| RenderFrameHost* subframe = |
| NavigationSimulator::NavigateAndCommitFromDocument( |
| GURL(kSubframeTestUrl), |
| RenderFrameHostTester::For(web_contents()->GetMainFrame()) |
| ->AppendChild("subframe")); |
| |
| // Simulate layout instability in the subframe. |
| render_data.layout_shift_delta = 1.5; |
| tester()->SimulateRenderDataUpdate(render_data, subframe); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| // CLS score should be the sum of LS scores from all frames. |
| EXPECT_THAT(tester()->histogram_tester().GetAllSamples( |
| "PageLoad.LayoutInstability.CumulativeShiftScore"), |
| testing::ElementsAre(base::Bucket(25, 1))); |
| |
| // Main-frame (DCLS) score includes only the LS scores in the main frame. |
| EXPECT_THAT(tester()->histogram_tester().GetAllSamples( |
| "PageLoad.LayoutInstability.CumulativeShiftScore.MainFrame"), |
| testing::ElementsAre(base::Bucket(10, 1))); |
| |
| const auto& ukm_recorder = tester()->test_ukm_recorder(); |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| ukm_recorder.GetMergedEntriesByName(PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| const ukm::mojom::UkmEntry* ukm_entry = kv.second.get(); |
| ukm_recorder.ExpectEntrySourceHasUrl(ukm_entry, GURL(kTestUrl1)); |
| |
| // Check CLS score in UKM. |
| ukm_recorder.ExpectEntryMetric( |
| ukm_entry, PageLoad::kLayoutInstability_CumulativeShiftScoreName, 250); |
| |
| // Check DCLS score in UKM. |
| ukm_recorder.ExpectEntryMetric( |
| ukm_entry, |
| PageLoad::kLayoutInstability_CumulativeShiftScore_MainFrameName, 100); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, ThirdPartyCookieBlockingDisabled) { |
| profile()->GetPrefs()->SetInteger( |
| prefs::kCookieControlsMode, |
| static_cast<int>(content_settings::CookieControlsMode::kOff)); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric( |
| kv.second.get(), |
| PageLoad::kThirdPartyCookieBlockingEnabledForSiteName)); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, ThirdPartyCookieBlockingEnabled) { |
| profile()->GetPrefs()->SetInteger( |
| prefs::kCookieControlsMode, |
| static_cast<int>(content_settings::CookieControlsMode::kBlockThirdParty)); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kThirdPartyCookieBlockingEnabledForSiteName, |
| true); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, |
| ThirdPartyCookieBlockingDisabledForSite) { |
| profile()->GetPrefs()->SetInteger( |
| prefs::kCookieControlsMode, |
| static_cast<int>(content_settings::CookieControlsMode::kBlockThirdParty)); |
| auto cookie_settings = CookieSettingsFactory::GetForProfile(profile()); |
| cookie_settings->SetThirdPartyCookieSetting(GURL(kTestUrl1), |
| CONTENT_SETTING_ALLOW); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kThirdPartyCookieBlockingEnabledForSiteName, |
| false); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, NotSearchOrHomePage) { |
| static const char kOtherURL[] = "https://www.other.com"; |
| |
| NavigateAndCommit(GURL(kOtherURL)); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| GeneratedNavigation::kEntryName); |
| EXPECT_EQ(0ul, merged_entries.size()); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, HomePageReported) { |
| static const char kOtherURL[] = "https://www.homepage.com/"; |
| |
| Profile::FromBrowserContext(browser_context()) |
| ->GetPrefs() |
| ->SetString(prefs::kHomePage, kOtherURL); |
| |
| NavigateAndCommit(GURL(kOtherURL)); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| GeneratedNavigation::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kOtherURL)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| GeneratedNavigation::kFirstURLIsDefaultSearchEngineName, false); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| GeneratedNavigation::kFinalURLIsDefaultSearchEngineName, false); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), GeneratedNavigation::kFirstURLIsHomePageName, true); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), GeneratedNavigation::kFinalURLIsHomePageName, true); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, DefaultSearchReported) { |
| static const char16_t kShortName[] = u"test"; |
| static const char kSearchURL[] = |
| "https://www.searchurl.com/search?q={searchTerms}"; |
| static const char kSearchURLWithQuery[] = |
| "https://www.searchurl.com/search?q=somequery"; |
| |
| TemplateURLService* model = TemplateURLServiceFactory::GetForProfile( |
| Profile::FromBrowserContext(browser_context())); |
| ASSERT_TRUE(model); |
| search_test_utils::WaitForTemplateURLServiceToLoad(model); |
| ASSERT_TRUE(model->loaded()); |
| |
| TemplateURLData data; |
| data.SetShortName(kShortName); |
| data.SetKeyword(data.short_name()); |
| data.SetURL(kSearchURL); |
| |
| // Set the DSE to the test URL. |
| TemplateURL* template_url = model->Add(std::make_unique<TemplateURL>(data)); |
| ASSERT_TRUE(template_url); |
| model->SetUserSelectedDefaultSearchProvider(template_url); |
| |
| NavigateAndCommit(GURL(kSearchURLWithQuery)); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| GeneratedNavigation::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl( |
| kv.second.get(), GURL(kSearchURLWithQuery)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| GeneratedNavigation::kFirstURLIsDefaultSearchEngineName, true); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), |
| GeneratedNavigation::kFinalURLIsDefaultSearchEngineName, true); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), GeneratedNavigation::kFirstURLIsHomePageName, false); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), GeneratedNavigation::kFinalURLIsHomePageName, false); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, NoLargestContentfulPaint) { |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| PopulateRequiredTimingFields(&timing); |
| |
| NavigateAndCommit(GURL(kTestUrl1)); |
| tester()->SimulateTimingUpdate(timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| TestNoLCP(LargestContentState::kNotFound); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, FCPHiddenWhileFlushing) { |
| NavigateAndCommit(GURL(kTestUrl1)); |
| |
| // Simulate hiding the tab. |
| web_contents()->WasHidden(); |
| |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.parse_timing->parse_start = base::TimeDelta(); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| timing.paint_timing->first_contentful_paint = |
| tester()->GetDelegateForCommittedLoad().GetTimeToFirstBackground(); |
| PopulateRequiredTimingFields(&timing); |
| |
| // Simulate FCP at the same time as the hide (but reported after). |
| tester()->SimulateTimingUpdate(timing); |
| |
| const auto& ukm_recorder = tester()->test_ukm_recorder(); |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| ukm_recorder.GetMergedEntriesByName(PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| // Check that we reported the FCP UKM. |
| for (const auto& kv : merged_entries) { |
| EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric( |
| kv.second.get(), |
| PageLoad::kPaintTiming_NavigationToFirstContentfulPaintName)); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, LCPHiddenWhileFlushing) { |
| NavigateAndCommit(GURL(kTestUrl1)); |
| |
| // Simulate hiding the tab. |
| web_contents()->WasHidden(); |
| base::TimeDelta time_to_first_background = |
| *tester()->GetDelegateForCommittedLoad().GetTimeToFirstBackground(); |
| |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| timing.navigation_start = base::Time::FromDoubleT(1); |
| timing.paint_timing->largest_contentful_paint->largest_image_paint = |
| time_to_first_background; |
| timing.paint_timing->largest_contentful_paint->largest_image_paint_size = 50u; |
| PopulateExperimentalLCP(timing.paint_timing); |
| PopulateRequiredTimingFields(&timing); |
| |
| // Simulate LCP at the same time as the hide (but reported after). |
| tester()->SimulateTimingUpdate(timing); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| // Check that we reported the LCP UKM. |
| TestLCP(time_to_first_background.InMilliseconds(), |
| LargestContentTextOrImage::kImage, true /* test_main_frame */); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, AppEnterBackground) { |
| NavigateAndCommit(GURL(kTestUrl1)); |
| page_load_metrics::mojom::PageLoadTiming timing; |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| |
| tester()->SimulateAppEnterBackground(); |
| |
| const auto& ukm_recorder = tester()->test_ukm_recorder(); |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| ukm_recorder.GetMergedEntriesByName(PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| const ukm::mojom::UkmEntry* entry = merged_entries.begin()->second.get(); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry, PageLoad::kNavigation_PageEndReason3Name, |
| page_load_metrics::END_APP_ENTER_BACKGROUND); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, IsExistingBookmark) { |
| GURL url(kTestUrl1); |
| |
| bookmarks::BookmarkModel* model = |
| BookmarkModelFactory::GetForBrowserContext(browser_context()); |
| ASSERT_TRUE(model); |
| ASSERT_TRUE( |
| model->AddURL(model->bookmark_bar_node(), 0, std::u16string(), url)); |
| |
| NavigateAndCommit(url); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| const auto& ukm_recorder = tester()->test_ukm_recorder(); |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| ukm_recorder.GetMergedEntriesByName(PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| const ukm::mojom::UkmEntry* entry = merged_entries.begin()->second.get(); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry, PageLoad::kIsExistingBookmarkName, 1); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry, PageLoad::kIsNewBookmarkName, 0); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, IsNewBookmark) { |
| GURL url(kTestUrl1); |
| |
| history::HistoryService* history_service = |
| HistoryServiceFactory::GetForProfile(profile(), |
| ServiceAccessType::IMPLICIT_ACCESS); |
| ASSERT_TRUE(history_service); |
| history_service->AddPage(url, base::Time::Now(), |
| history::VisitSource::SOURCE_BROWSED); |
| |
| NavigateAndCommit(url); |
| history::BlockUntilHistoryProcessesPendingRequests(history_service); |
| |
| bookmarks::BookmarkModel* model = |
| BookmarkModelFactory::GetForBrowserContext(browser_context()); |
| ASSERT_TRUE(model); |
| ASSERT_TRUE( |
| model->AddURL(model->bookmark_bar_node(), 0, std::u16string(), url)); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| const auto& ukm_recorder = tester()->test_ukm_recorder(); |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| ukm_recorder.GetMergedEntriesByName(PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| const ukm::mojom::UkmEntry* entry = merged_entries.begin()->second.get(); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry, PageLoad::kIsExistingBookmarkName, 0); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry, PageLoad::kIsNewBookmarkName, 1); |
| } |
| |
| // Android does not have NTP Custom Links. |
| #if !BUILDFLAG(IS_ANDROID) |
| TEST_F(UkmPageLoadMetricsObserverTest, IsNTPCustomLink) { |
| GURL url(kTestUrl1); |
| |
| history::HistoryService* history_service = |
| HistoryServiceFactory::GetForProfile(profile(), |
| ServiceAccessType::IMPLICIT_ACCESS); |
| ASSERT_TRUE(history_service); |
| history_service->AddPage(url, base::Time::Now(), |
| history::VisitSource::SOURCE_BROWSED); |
| |
| NavigateAndCommit(url); |
| history::BlockUntilHistoryProcessesPendingRequests(history_service); |
| |
| ntp_tiles::CustomLinksStore custom_link_store(profile()->GetPrefs()); |
| custom_link_store.StoreLinks({ |
| {url, u"Test Title"}, |
| }); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| const auto& ukm_recorder = tester()->test_ukm_recorder(); |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| ukm_recorder.GetMergedEntriesByName(PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| const ukm::mojom::UkmEntry* entry = merged_entries.begin()->second.get(); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry, PageLoad::kIsNTPCustomLinkName, 1); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, DurationSinceLastVisitSeconds) { |
| // TODO(tommycli): Should we move this test to either HistoryClustersService |
| // or HistoryClustersTabHelper? On the one hand, the logic resides there. On |
| // the other hand this serves as a good integration test with UKM. |
| GURL url(kTestUrl1); |
| |
| history::HistoryService* history_service = |
| HistoryServiceFactory::GetForProfile(profile(), |
| ServiceAccessType::IMPLICIT_ACCESS); |
| ASSERT_TRUE(history_service); |
| // Fake that we visited this site 45 days ago. |
| history_service->AddPage(url, base::Time::Now() - base::Days(45), |
| history::VisitSource::SOURCE_BROWSED); |
| NavigateAndCommit(url); |
| history::BlockUntilHistoryProcessesPendingRequests(history_service); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| // Verify UKM records that we visited the page clamped to 30 days ago to |
| // respect the UKM retention period. |
| const auto& ukm_recorder = tester()->test_ukm_recorder(); |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| ukm_recorder.GetMergedEntriesByName(PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| const ukm::mojom::UkmEntry* entry = merged_entries.begin()->second.get(); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry, PageLoad::kDurationSinceLastVisitSecondsName, |
| base::Days(30).InSeconds()); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, |
| DurationSinceLastVisitSecondsHistoryServiceLosesRace) { |
| GURL url(kTestUrl1); |
| |
| // Simulate that we navigated, but HistoryService doesn't respond back to the |
| // UKM observer before it's destroyed. |
| NavigateAndCommit(url); |
| DeleteContents(); |
| |
| // Verify UKM records -1 in this case. |
| const auto& ukm_recorder = tester()->test_ukm_recorder(); |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| ukm_recorder.GetMergedEntriesByName(PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| const ukm::mojom::UkmEntry* entry = merged_entries.begin()->second.get(); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| entry, PageLoad::kDurationSinceLastVisitSecondsName, -1); |
| } |
| |
| class TestOfflinePreviewsUkmPageLoadMetricsObserver |
| : public UkmPageLoadMetricsObserver { |
| public: |
| explicit TestOfflinePreviewsUkmPageLoadMetricsObserver( |
| MockNetworkQualityProvider* network_quality_provider) |
| : UkmPageLoadMetricsObserver(network_quality_provider) {} |
| |
| ~TestOfflinePreviewsUkmPageLoadMetricsObserver() override = default; |
| |
| bool IsOfflinePreview(content::WebContents* web_contents) const override { |
| return true; |
| } |
| }; |
| |
| class OfflinePreviewsUKMPageLoadMetricsObserverTest |
| : public UkmPageLoadMetricsObserverTest { |
| public: |
| OfflinePreviewsUKMPageLoadMetricsObserverTest() = default; |
| ~OfflinePreviewsUKMPageLoadMetricsObserverTest() override = default; |
| |
| void RegisterObservers(page_load_metrics::PageLoadTracker* tracker) override { |
| tracker->AddObserver( |
| std::make_unique<TestOfflinePreviewsUkmPageLoadMetricsObserver>( |
| &mock_network_quality_provider())); |
| } |
| }; |
| |
| TEST_F(OfflinePreviewsUKMPageLoadMetricsObserverTest, OfflinePreviewReported) { |
| auto navigation = content::NavigationSimulator::CreateBrowserInitiated( |
| GURL(kTestUrl1), web_contents()); |
| navigation->SetContentsMimeType("multipart/related"); |
| navigation->Commit(); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), |
| GURL(kTestUrl1)); |
| tester()->test_ukm_recorder().ExpectEntryMetric( |
| kv.second.get(), PageLoad::kNavigation_PageEndReason3Name, |
| page_load_metrics::END_CLOSE); |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, NavigationTiming) { |
| GURL url(kTestUrl1); |
| NavigateAndCommit(url); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| using ukm::builders::NavigationTiming; |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| tester()->test_ukm_recorder().GetMergedEntriesByName( |
| NavigationTiming::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| const std::vector<const char*> metrics = { |
| NavigationTiming::kFirstRequestStartName, |
| NavigationTiming::kFirstResponseStartName, |
| NavigationTiming::kFirstLoaderCallbackName, |
| NavigationTiming::kFinalRequestStartName, |
| NavigationTiming::kFinalResponseStartName, |
| NavigationTiming::kFinalLoaderCallbackName, |
| NavigationTiming::kNavigationCommitSentName}; |
| |
| for (const auto& kv : merged_entries) { |
| tester()->test_ukm_recorder().ExpectEntrySourceHasUrl(kv.second.get(), url); |
| |
| // Verify if the elapsed times from the navigation start are recorded. |
| for (const char* metric : metrics) { |
| EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric(kv.second.get(), |
| metric)); |
| } |
| } |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, CLSNeverForegroundedNoReport) { |
| web_contents()->WasHidden(); |
| NavigateAndCommit(GURL(kTestUrl1)); |
| |
| page_load_metrics::mojom::FrameRenderDataUpdate render_data(1.0, 1.0, 0, 0, 0, |
| 0, {}); |
| tester()->SimulateRenderDataUpdate(render_data); |
| |
| // Simulate closing the tab. |
| DeleteContents(); |
| |
| const auto& ukm_recorder = tester()->test_ukm_recorder(); |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| ukm_recorder.GetMergedEntriesByName(PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| const ukm::mojom::UkmEntry* ukm_entry = kv.second.get(); |
| ukm_recorder.ExpectEntrySourceHasUrl(ukm_entry, GURL(kTestUrl1)); |
| EXPECT_FALSE(ukm_recorder.EntryHasMetric( |
| ukm_entry, PageLoad::kLayoutInstability_CumulativeShiftScoreName)); |
| } |
| } |
| |
| class CLSUkmPageLoadMetricsObserverTest |
| : public UkmPageLoadMetricsObserverTest { |
| protected: |
| void RunBeforeInputOrScrollCase(bool input_in_subframe); |
| void SimulateShiftDelta(float delta, content::RenderFrameHost* frame); |
| RenderFrameHost* NavigateSubframe(); |
| void VerifyUKMBuckets(int total, int before_input_or_scroll); |
| void InitPageLoadTimingWithInputOrScroll( |
| page_load_metrics::mojom::PageLoadTiming& timing, |
| base::TimeDelta timestamp); |
| }; |
| |
| void CLSUkmPageLoadMetricsObserverTest::SimulateShiftDelta( |
| float delta, |
| content::RenderFrameHost* frame) { |
| page_load_metrics::mojom::FrameRenderDataUpdate render_data(delta, delta, 0, |
| 0, 0, 0, {}); |
| tester()->SimulateRenderDataUpdate(render_data, frame); |
| } |
| |
| RenderFrameHost* CLSUkmPageLoadMetricsObserverTest::NavigateSubframe() { |
| return NavigationSimulator::NavigateAndCommitFromDocument( |
| GURL(kSubframeTestUrl), |
| RenderFrameHostTester::For(web_contents()->GetMainFrame()) |
| ->AppendChild("subframe")); |
| } |
| |
| void CLSUkmPageLoadMetricsObserverTest::VerifyUKMBuckets( |
| int total, |
| int before_input_or_scroll) { |
| const char* total_name = |
| PageLoad::kLayoutInstability_CumulativeShiftScoreName; |
| const char* before_input_or_scroll_name = |
| PageLoad::kLayoutInstability_CumulativeShiftScore_BeforeInputOrScrollName; |
| |
| const auto& ukm_recorder = tester()->test_ukm_recorder(); |
| std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries = |
| ukm_recorder.GetMergedEntriesByName(PageLoad::kEntryName); |
| EXPECT_EQ(1ul, merged_entries.size()); |
| |
| for (const auto& kv : merged_entries) { |
| const ukm::mojom::UkmEntry* ukm_entry = kv.second.get(); |
| ukm_recorder.ExpectEntrySourceHasUrl(ukm_entry, GURL(kTestUrl1)); |
| |
| ukm_recorder.ExpectEntryMetric(ukm_entry, total_name, total); |
| ukm_recorder.ExpectEntryMetric(ukm_entry, before_input_or_scroll_name, |
| before_input_or_scroll); |
| } |
| } |
| |
| void CLSUkmPageLoadMetricsObserverTest::InitPageLoadTimingWithInputOrScroll( |
| page_load_metrics::mojom::PageLoadTiming& timing, |
| base::TimeDelta timestamp) { |
| page_load_metrics::InitPageLoadTimingForTest(&timing); |
| PopulateRequiredTimingFields(&timing); |
| timing.paint_timing->first_input_or_scroll_notified_timestamp = timestamp; |
| } |
| |
| void CLSUkmPageLoadMetricsObserverTest::RunBeforeInputOrScrollCase( |
| bool input_in_subframe) { |
| NavigateAndCommit(GURL(kTestUrl1)); |
| |
| RenderFrameHost* main_frame = web_contents()->GetMainFrame(); |
| RenderFrameHost* subframe = NavigateSubframe(); |
| |
| SimulateShiftDelta(1.0, main_frame); |
| SimulateShiftDelta(1.5, subframe); |
| |
| // Simulate input. |
| page_load_metrics::mojom::PageLoadTiming timing; |
| InitPageLoadTimingWithInputOrScroll(timing, base::Seconds(1)); |
| tester()->SimulateTimingUpdate(timing, |
| input_in_subframe ? subframe : main_frame); |
| |
| SimulateShiftDelta(1.2, main_frame); |
| SimulateShiftDelta(0.8, subframe); |
| |
| DeleteContents(); |
| |
| // Total CLS: 1.0 + 1.5 + 1.2 + 0.8 = 4.5 (bucket 450). |
| // Before input: 1.0 + 1.5 = 2.5 (bucket 250). |
| VerifyUKMBuckets(450, 250); |
| } |
| |
| TEST_F(CLSUkmPageLoadMetricsObserverTest, BeforeInputOrScroll_Main) { |
| RunBeforeInputOrScrollCase(false); |
| } |
| |
| TEST_F(CLSUkmPageLoadMetricsObserverTest, BeforeInputOrScroll_Sub) { |
| RunBeforeInputOrScrollCase(true); |
| } |
| |
| void TestViewportInitialScale(int expected, int input) { |
| blink::MobileFriendliness mf; |
| mf.viewport_initial_scale_x10 = input; |
| EXPECT_EQ(expected, page_load_metrics::GetBucketedViewportInitialScale(mf)); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, BucketingViewportInitialScale) { |
| // Default value to be ignored. |
| TestViewportInitialScale(-1, -1); |
| |
| // Typical case initail-scale=1.0. |
| TestViewportInitialScale(10, 10); |
| |
| // Bigger number cases. |
| TestViewportInitialScale(12, 12); |
| TestViewportInitialScale(14, 15); |
| TestViewportInitialScale(14, 15); |
| TestViewportInitialScale(14, 17); |
| TestViewportInitialScale(18, 18); |
| TestViewportInitialScale(18, 25); |
| TestViewportInitialScale(26, 26); |
| |
| // Smaller number cases. |
| TestViewportInitialScale(10, 9); |
| TestViewportInitialScale(8, 8); |
| TestViewportInitialScale(8, 7); |
| TestViewportInitialScale(6, 6); |
| TestViewportInitialScale(6, 3); |
| TestViewportInitialScale(2, 1); |
| TestViewportInitialScale(2, 0); |
| } |
| |
| void TestViewportHardcodedWidth(int expected, int input) { |
| blink::MobileFriendliness mf; |
| mf.viewport_hardcoded_width = input; |
| EXPECT_EQ(expected, page_load_metrics::GetBucketedViewportHardcodedWidth(mf)); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, BucketingViewportHardcodedWidth) { |
| // Default value to be ignored. |
| TestViewportHardcodedWidth(-1, -1); |
| |
| // Middle case. |
| TestViewportHardcodedWidth(500, 500); |
| |
| // Bigger number cases. |
| TestViewportHardcodedWidth(500, 509); |
| |
| TestViewportHardcodedWidth(510, 510); |
| TestViewportHardcodedWidth(510, 519); |
| TestViewportHardcodedWidth(520, 520); |
| TestViewportHardcodedWidth(520, 539); |
| TestViewportHardcodedWidth(540, 540); |
| TestViewportHardcodedWidth(540, 579); |
| TestViewportHardcodedWidth(580, 580); |
| TestViewportHardcodedWidth(580, 640); |
| TestViewportHardcodedWidth(820, 1000); |
| TestViewportHardcodedWidth(1780, 2000); |
| |
| // Smaller number cases. |
| TestViewportHardcodedWidth(500, 491); |
| TestViewportHardcodedWidth(490, 490); |
| TestViewportHardcodedWidth(480, 480); |
| TestViewportHardcodedWidth(460, 421); |
| TestViewportHardcodedWidth(180, 180); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, |
| TestLogsBrowserInitiatedNavigationAsUserInitiated) { |
| // Simulate a browser initiated navigation, which is always considered |
| // user initiated. |
| auto& test_ukm_recorder = tester()->test_ukm_recorder(); |
| tester()->NavigateWithPageTransitionAndCommit( |
| GURL(kTestUrl1), ui::PageTransition::PAGE_TRANSITION_TYPED); |
| tester()->NavigateToUntrackedUrl(); |
| |
| auto result_metrics = test_ukm_recorder.FilteredHumanReadableMetricForEntry( |
| PageLoad::kEntryName, |
| PageLoad::kExperimental_Navigation_UserInitiatedName); |
| EXPECT_EQ(1U, result_metrics.size()); |
| EXPECT_EQ(PageLoad::kExperimental_Navigation_UserInitiatedName, |
| result_metrics[0].begin()->first); |
| EXPECT_TRUE(result_metrics[0].begin()->second); |
| // Check the UserPerceivedPageVisit version of the metrics as well. |
| result_metrics = test_ukm_recorder.FilteredHumanReadableMetricForEntry( |
| UserPerceivedPageVisit::kEntryName, |
| UserPerceivedPageVisit::kUserInitiatedName); |
| EXPECT_EQ(1U, result_metrics.size()); |
| EXPECT_EQ(UserPerceivedPageVisit::kUserInitiatedName, |
| result_metrics[0].begin()->first); |
| EXPECT_TRUE(result_metrics[0].begin()->second); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, |
| TestLogsUserInitiatedRendererNavigationAsUserInitiated) { |
| auto& test_ukm_recorder = tester()->test_ukm_recorder(); |
| // Simulate a renderer initiated navigation. The associated navigation input |
| // start time means this will also be considered user initiated. |
| std::unique_ptr<content::NavigationSimulator> navigation = |
| content::NavigationSimulator::CreateRendererInitiated(GURL(kTestUrl1), |
| main_rfh()); |
| navigation->SetNavigationInputStart(base::TimeTicks::Now()); |
| navigation->Commit(); |
| tester()->NavigateToUntrackedUrl(); |
| auto result_metrics = test_ukm_recorder.FilteredHumanReadableMetricForEntry( |
| PageLoad::kEntryName, |
| PageLoad::kExperimental_Navigation_UserInitiatedName); |
| EXPECT_EQ(1U, result_metrics.size()); |
| EXPECT_EQ(PageLoad::kExperimental_Navigation_UserInitiatedName, |
| result_metrics[0].begin()->first); |
| EXPECT_TRUE(result_metrics[0].begin()->second); |
| |
| result_metrics = test_ukm_recorder.FilteredHumanReadableMetricForEntry( |
| UserPerceivedPageVisit::kEntryName, |
| UserPerceivedPageVisit::kUserInitiatedName); |
| EXPECT_EQ(1U, result_metrics.size()); |
| EXPECT_EQ(UserPerceivedPageVisit::kUserInitiatedName, |
| result_metrics[0].begin()->first); |
| EXPECT_TRUE(result_metrics[0].begin()->second); |
| } |
| |
| TEST_F(UkmPageLoadMetricsObserverTest, |
| TestLogsRendererInitiatedRendererNavigationAsUserInitiated) { |
| auto& test_ukm_recorder = tester()->test_ukm_recorder(); |
| // Simulate a renderer initiated navigation without an associated |
| // navigation input start time. This will be considered not user |
| // initiated. |
| std::unique_ptr<content::NavigationSimulator> navigation = |
| content::NavigationSimulator::CreateRendererInitiated(GURL(kTestUrl1), |
| main_rfh()); |
| navigation->Commit(); |
| tester()->NavigateToUntrackedUrl(); |
| auto result_metrics = test_ukm_recorder.FilteredHumanReadableMetricForEntry( |
| PageLoad::kEntryName, |
| PageLoad::kExperimental_Navigation_UserInitiatedName); |
| EXPECT_EQ(1U, result_metrics.size()); |
| EXPECT_EQ(PageLoad::kExperimental_Navigation_UserInitiatedName, |
| result_metrics[0].begin()->first); |
| EXPECT_FALSE(result_metrics[0].begin()->second); |
| |
| result_metrics = test_ukm_recorder.FilteredHumanReadableMetricForEntry( |
| UserPerceivedPageVisit::kEntryName, |
| UserPerceivedPageVisit::kUserInitiatedName); |
| EXPECT_EQ(1U, result_metrics.size()); |
| EXPECT_EQ(UserPerceivedPageVisit::kUserInitiatedName, |
| result_metrics[0].begin()->first); |
| EXPECT_FALSE(result_metrics[0].begin()->second); |
| } |