blob: 5dc608308757c7f0a2335b71a626387a70727075 [file] [log] [blame]
// 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);
}