blob: 5a8755ff2894bf2e3abc7ae259c7bae78c522820 [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/ukm_page_load_metrics_observer.h"
#include <memory>
#include "base/metrics/metrics_hashes.h"
#include "base/optional.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 "chrome/browser/content_settings/cookie_settings_factory.h"
#include "chrome/browser/page_load_metrics/observers/page_load_metrics_observer_test_harness.h"
#include "chrome/test/base/testing_profile.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/features.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/page_load_metrics/browser/page_load_tracker.h"
#include "components/page_load_metrics/common/test/page_load_metrics_test_util.h"
#include "components/prefs/pref_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/metrics_proto/system_profile.pb.h"
using content::NavigationSimulator;
using content::RenderFrameHost;
using content::RenderFrameHostTester;
using testing::AnyNumber;
using testing::Mock;
using testing::Return;
namespace {
using PageLoad = ukm::builders::PageLoad;
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 {
tracker->AddObserver(std::make_unique<UkmPageLoadMetricsObserver>(
&mock_network_quality_provider_));
}
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()));
}
MockNetworkQualityProvider& mock_network_quality_provider() {
return mock_network_quality_provider_;
}
private:
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::TimeDelta::FromMilliseconds(100);
timing.document_timing->dom_content_loaded_event_start =
base::TimeDelta::FromMilliseconds(200);
timing.paint_timing->first_paint = base::TimeDelta::FromMilliseconds(250);
timing.paint_timing->first_contentful_paint =
base::TimeDelta::FromMilliseconds(300);
timing.document_timing->load_event_start =
base::TimeDelta::FromMilliseconds(500);
timing.input_to_navigation_start = base::TimeDelta::FromMilliseconds(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_PageEndReasonName,
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));
}
}
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_PageEndReasonName,
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.paint_timing->first_meaningful_paint =
base::TimeDelta::FromMilliseconds(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_image_paint =
base::TimeDelta::FromMilliseconds(600);
timing.paint_timing->largest_image_paint_size = 50u;
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::kPaintTiming_NavigationToLargestContentfulPaintName, 600);
EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric(
kv.second.get(), PageLoad::kPageTiming_ForegroundDurationName));
}
}
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_image_paint =
base::TimeDelta::FromMilliseconds(600);
PopulateRequiredTimingFields(&timing);
// The duration between nav start and first background set to 1ms.
mock_clock->Advance(base::TimeDelta::FromMilliseconds(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);
for (const auto& kv : merged_entries) {
EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric(
kv.second.get(),
PageLoad::kPaintTiming_NavigationToLargestContentfulPaintName));
}
}
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_image_paint = base::TimeDelta::FromSeconds(10);
timing.paint_timing->largest_text_paint = base::TimeDelta::FromSeconds(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);
for (const auto& kv : merged_entries) {
EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric(
kv.second.get(),
PageLoad::kPaintTiming_NavigationToLargestContentfulPaintName));
}
}
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_image_paint =
base::TimeDelta::FromMilliseconds(60);
timing.paint_timing->largest_image_paint_size = 10u;
timing.paint_timing->largest_text_paint =
base::TimeDelta::FromMilliseconds(60);
timing.paint_timing->largest_text_paint_size = 10u;
tester()->SimulateTimingUpdate(timing);
timing.paint_timing->largest_image_paint =
base::TimeDelta::FromMilliseconds(600);
timing.paint_timing->largest_image_paint_size = 10u;
timing.paint_timing->largest_text_paint =
base::TimeDelta::FromMilliseconds(600);
timing.paint_timing->largest_text_paint_size = 10u;
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::kPaintTiming_NavigationToLargestContentfulPaintName, 600);
EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric(
kv.second.get(), PageLoad::kPageTiming_ForegroundDurationName));
}
}
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_text_paint =
base::TimeDelta::FromMilliseconds(600);
timing.paint_timing->largest_text_paint_size = 50u;
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::kPaintTiming_NavigationToLargestContentfulPaintName, 600);
EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric(
kv.second.get(), PageLoad::kPageTiming_ForegroundDurationName));
}
}
TEST_F(UkmPageLoadMetricsObserverTest, LargestContentfulPaint_Trace) {
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_text_paint =
base::TimeDelta::FromMilliseconds(600);
timing.paint_timing->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"));
std::unique_ptr<base::Value> arg;
EXPECT_TRUE(events[0]->GetArgAsValue("data", &arg));
base::DictionaryValue* arg_dict;
EXPECT_TRUE(arg->GetAsDictionary(&arg_dict));
int time;
EXPECT_TRUE(arg_dict->GetInteger("durationInMilliseconds", &time));
EXPECT_EQ(600, time);
int size;
EXPECT_TRUE(arg_dict->GetInteger("size", &size));
EXPECT_EQ(1000, size);
std::string type;
EXPECT_TRUE(arg_dict->GetString("type", &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_text_paint =
base::TimeDelta::FromMilliseconds(600);
timing.paint_timing->largest_text_paint_size = 1000;
PopulateRequiredTimingFields(&timing);
NavigateAndCommit(GURL(kTestUrl1));
tester()->SimulateTimingUpdate(timing);
timing.paint_timing->largest_text_paint = base::Optional<base::TimeDelta>();
timing.paint_timing->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_text_paint =
base::TimeDelta::FromMilliseconds(600);
timing.paint_timing->largest_text_paint_size = 1000;
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::kPaintTiming_NavigationToLargestContentfulPaintName, 600);
EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric(
kv.second.get(), PageLoad::kPageTiming_ForegroundDurationName));
}
}
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_image_paint =
base::TimeDelta::FromMilliseconds(600);
timing.paint_timing->largest_image_paint_size = 1000;
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::kPaintTiming_NavigationToLargestContentfulPaintName, 600);
EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric(
kv.second.get(), PageLoad::kPageTiming_ForegroundDurationName));
}
}
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_image_paint =
base::TimeDelta::FromMilliseconds(600);
timing.paint_timing->largest_image_paint_size = 1000;
timing.paint_timing->largest_text_paint =
base::TimeDelta::FromMilliseconds(1000);
timing.paint_timing->largest_text_paint_size = 500;
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::kPaintTiming_NavigationToLargestContentfulPaintName, 600);
EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric(
kv.second.get(), PageLoad::kPageTiming_ForegroundDurationName));
}
}
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_image_paint =
base::TimeDelta::FromMilliseconds(4780);
subframe_timing.paint_timing->largest_image_paint_size = 100u;
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();
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::kPaintTiming_NavigationToLargestContentfulPaintName, 4780);
EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric(
kv.second.get(),
PageLoad::
kPaintTiming_NavigationToLargestContentfulPaint_MainFrameName));
EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric(
kv.second.get(), PageLoad::kPageTiming_ForegroundDurationName));
}
}
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_image_paint =
base::TimeDelta::FromMilliseconds(4780);
timing.paint_timing->largest_image_paint_size = 100u;
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();
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::kPaintTiming_NavigationToLargestContentfulPaintName, 4780);
tester()->test_ukm_recorder().ExpectEntryMetric(
kv.second.get(),
PageLoad::kPaintTiming_NavigationToLargestContentfulPaint_MainFrameName,
4780);
EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric(
kv.second.get(), PageLoad::kPageTiming_ForegroundDurationName));
}
}
// 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_image_paint =
base::TimeDelta::FromMilliseconds(4780);
timing.paint_timing->largest_image_paint_size = 50u;
PopulateRequiredTimingFields(&timing);
page_load_metrics::mojom::PageLoadTiming subframe_timing;
page_load_metrics::InitPageLoadTimingForTest(&subframe_timing);
subframe_timing.navigation_start = base::Time::FromDoubleT(2);
timing.paint_timing->largest_image_paint =
base::TimeDelta::FromMilliseconds(990);
timing.paint_timing->largest_image_paint_size = 100u;
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();
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::kPaintTiming_NavigationToLargestContentfulPaintName, 990);
EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric(
kv.second.get(), PageLoad::kPageTiming_ForegroundDurationName));
}
}
TEST_F(UkmPageLoadMetricsObserverTest, PageInteractive) {
page_load_metrics::mojom::PageLoadTiming timing;
page_load_metrics::InitPageLoadTimingForTest(&timing);
timing.navigation_start = base::Time::FromDoubleT(1);
timing.interactive_timing->interactive =
base::TimeDelta::FromMilliseconds(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_NavigationToInteractiveName,
600);
EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric(
kv.second.get(), PageLoad::kPageTiming_ForegroundDurationName));
}
}
TEST_F(UkmPageLoadMetricsObserverTest, PageInteractiveInputInvalidated) {
page_load_metrics::mojom::PageLoadTiming timing;
page_load_metrics::InitPageLoadTimingForTest(&timing);
timing.navigation_start = base::Time::FromDoubleT(1);
timing.interactive_timing->interactive =
base::TimeDelta::FromMilliseconds(1000);
timing.interactive_timing->first_invalidating_input =
base::TimeDelta::FromMilliseconds(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));
EXPECT_FALSE(tester()->test_ukm_recorder().EntryHasMetric(
kv.second.get(), PageLoad::kExperimental_NavigationToInteractiveName));
EXPECT_TRUE(tester()->test_ukm_recorder().EntryHasMetric(
kv.second.get(), PageLoad::kPageTiming_ForegroundDurationName));
}
}
TEST_F(UkmPageLoadMetricsObserverTest, FirstInputDelayAndTimestamp) {
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::TimeDelta::FromMilliseconds(50);
timing.interactive_timing->first_input_timestamp =
base::TimeDelta::FromMilliseconds(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_FirstInputDelay4Name, 50);
tester()->test_ukm_recorder().ExpectEntryMetric(
kv.second.get(), PageLoad::kInteractiveTiming_FirstInputTimestamp4Name,
712);
}
}
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::TimeDelta::FromMilliseconds(50);
timing.interactive_timing->longest_input_timestamp =
base::TimeDelta::FromMilliseconds(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, MultiplePageLoads) {
page_load_metrics::mojom::PageLoadTiming timing1;
page_load_metrics::InitPageLoadTimingForTest(&timing1);
timing1.navigation_start = base::Time::FromDoubleT(1);
timing1.paint_timing->first_contentful_paint =
base::TimeDelta::FromMilliseconds(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_PageEndReasonName,
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_PageEndReasonName,
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::TimeDelta::FromMilliseconds(100)));
EXPECT_CALL(mock_network_quality_provider(), GetTransportRTT())
.WillRepeatedly(Return(base::TimeDelta::FromMilliseconds(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 */,
true /* is_complete */));
// Uncached resource.
resources.push_back(CreateResource(
false /* was_cached */, 40 * 1024 /* delta_bytes */,
40 * 1024 /* encoded_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, CpuTimeMetrics) {
NavigateAndCommit(GURL(kTestUrl1));
// Simulate some CPU usage.
page_load_metrics::mojom::CpuTiming cpu_timing(
base::TimeDelta::FromMilliseconds(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));
page_load_metrics::mojom::FrameRenderDataUpdate render_data(1.0, 1.0);
tester()->SimulateRenderDataUpdate(render_data);
// Simulate hiding the tab (the report should include shifts after hide).
web_contents()->WasHidden();
render_data.layout_shift_delta = 1.5;
render_data.layout_shift_delta_before_input_or_scroll = 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));
ukm_recorder.ExpectEntryMetric(
ukm_entry, PageLoad::kLayoutInstability_CumulativeShiftScoreName, 250);
ukm_recorder.ExpectEntryMetric(
ukm_entry,
PageLoad::
kLayoutInstability_CumulativeShiftScore_MainFrame_BeforeInputOrScrollName,
100);
}
EXPECT_THAT(tester()->histogram_tester().GetAllSamples(
"PageLoad.LayoutInstability.CumulativeShiftScore"),
testing::ElementsAre(base::Bucket(25, 1)));
}
TEST_F(UkmPageLoadMetricsObserverTest, MHTMLNotTracked) {
auto navigation = content::NavigationSimulator::CreateBrowserInitiated(
GURL(kTestUrl1), web_contents());
navigation->SetContentsMimeType("multipart/related");
navigation->Commit();
// Simulate closing the tab.
DeleteContents();
EXPECT_EQ(0ul, tester()->test_ukm_recorder().entries_count());
}
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);
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) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(content_settings::kImprovedCookieControls);
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,
ThirdPartyCookieBlockingFeatureDisabled) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndDisableFeature(content_settings::kImprovedCookieControls);
profile()->GetPrefs()->SetInteger(
prefs::kCookieControlsMode,
static_cast<int>(content_settings::CookieControlsMode::kOn));
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) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(content_settings::kImprovedCookieControls);
profile()->GetPrefs()->SetInteger(
prefs::kCookieControlsMode,
static_cast<int>(content_settings::CookieControlsMode::kOn));
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) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(content_settings::kImprovedCookieControls);
profile()->GetPrefs()->SetInteger(
prefs::kCookieControlsMode,
static_cast<int>(content_settings::CookieControlsMode::kOn));
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);
}
}
class TestOfflinePreviewsUkmPageLoadMetricsObserver
: public UkmPageLoadMetricsObserver {
public:
TestOfflinePreviewsUkmPageLoadMetricsObserver(
MockNetworkQualityProvider* network_quality_provider)
: UkmPageLoadMetricsObserver(network_quality_provider) {}
~TestOfflinePreviewsUkmPageLoadMetricsObserver() override {}
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_PageEndReasonName,
page_load_metrics::END_CLOSE);
}
}