blob: 27a1d2bb98d42bd36ed5b7ddccb35ccb687472fe [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <iterator>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_set>
#include <utility>
#include <vector>
#include "base/check_op.h"
#include "base/containers/to_vector.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/gtest_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/trace_event_analyzer.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "cc/base/switches.h"
#include "chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/document_write_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/page_load_metrics_initialize.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/preloading/prefetch/no_state_prefetch/no_state_prefetch_manager_factory.h"
#include "chrome/browser/preloading/prefetch/no_state_prefetch/no_state_prefetch_test_utils.h"
#include "chrome/browser/preloading/preview/preview_test_util.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/resource_coordinator/tab_lifecycle_unit_external.h"
#include "chrome/browser/sessions/session_restore.h"
#include "chrome/browser/sessions/session_restore_test_helper.h"
#include "chrome/browser/sessions/session_service_factory.h"
#include "chrome/browser/sessions/session_service_test_helper.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/content_settings/core/common/features.h"
#include "components/embedder_support/user_agent_utils.h"
#include "components/keep_alive_registry/keep_alive_types.h"
#include "components/keep_alive_registry/scoped_keep_alive.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_handle.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_histograms.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_manager.h"
#include "components/no_state_prefetch/common/no_state_prefetch_origin.h"
#include "components/page_load_metrics/browser/observers/abandoned_page_load_metrics_observer.h"
#include "components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer.h"
#include "components/page_load_metrics/browser/observers/service_worker_page_load_metrics_observer.h"
#include "components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.h"
#include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h"
#include "components/page_load_metrics/browser/page_load_tracker.h"
#include "components/prefs/pref_service.h"
#include "components/sessions/content/content_test_helper.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/tracing_controller.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_paths.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/referrer.h"
#include "content/public/common/result_codes.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/hit_test_region_observer.h"
#include "content/public/test/navigation_handle_observer.h"
#include "content/public/test/no_renderer_crashes_assertion.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.h"
#include "net/base/net_errors.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/network/public/cpp/features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/chrome_debug_urls.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/use_counter/metrics/css_property_id.mojom.h"
#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h"
#include "third_party/blink/public/mojom/use_counter/metrics/webdx_feature.mojom-shared.h"
#include "ui/gfx/geometry/size.h"
#include "url/gurl.h"
using page_load_metrics::PageEndReason;
using page_load_metrics::PageLoadMetricsTestWaiter;
using TimingField = page_load_metrics::PageLoadMetricsTestWaiter::TimingField;
using WebDXFeature = blink::mojom::WebDXFeature;
using WebFeature = blink::mojom::WebFeature;
using testing::SizeIs;
using testing::UnorderedElementsAre;
using NoStatePrefetch = ukm::builders::NoStatePrefetch;
using PageLoad = ukm::builders::PageLoad;
using HistoryNavigation = ukm::builders::HistoryNavigation;
using trace_analyzer::Query;
using trace_analyzer::TraceAnalyzer;
using trace_analyzer::TraceEventVector;
namespace {
constexpr char kCacheablePathPrefix[] = "/cacheable";
const char kResponseWithNoStore[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Cache-Control: no-store\r\n"
"\r\n"
"The server speaks HTTP!";
constexpr char kCreateFrameAtPositionScript[] = R"(
var new_iframe = document.createElement('iframe');
new_iframe.src = $1;
new_iframe.name = 'frame';
new_iframe.frameBorder = 0;
new_iframe.setAttribute('style',
'position:absolute; top:$2; left:$3; width:$4; height:$4;');
document.body.appendChild(new_iframe);)";
constexpr char kCreateFrameAtPositionNotifyOnLoadScript[] = R"(
var new_iframe = document.createElement('iframe');
new_iframe.src = $1;
new_iframe.name = 'frame';
new_iframe.frameBorder = 0;
new_iframe.setAttribute('style',
'position:absolute; top:$2; left:$3; width:$4; height:$4;');
new_iframe.onload = function() {
window.domAutomationController.send('iframe.onload');
};
document.body.appendChild(new_iframe);)";
constexpr char kCreateFrameAtTopRightPositionScript[] = R"(
var new_iframe = document.createElement('iframe');
new_iframe.src = $1;
new_iframe.name = 'frame';
new_iframe.frameBorder = 0;
new_iframe.setAttribute('style',
'position:absolute; top:$2; right:$3; width:$4; height:$4;');
document.body.appendChild(new_iframe);)";
std::unique_ptr<net::test_server::HttpResponse> HandleCachableRequestHandler(
const net::test_server::HttpRequest& request) {
if (!base::StartsWith(request.relative_url, kCacheablePathPrefix,
base::CompareCase::SENSITIVE)) {
return nullptr;
}
if (request.headers.find("If-None-Match") != request.headers.end()) {
return std::make_unique<net::test_server::RawHttpResponse>(
"HTTP/1.1 304 Not Modified", "");
}
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
response->set_code(net::HTTP_OK);
response->set_content_type("text/html");
response->AddCustomHeader("cache-control", "max-age=60");
response->AddCustomHeader("etag", "foobar");
response->set_content("hi");
return std::move(response);
}
struct DecoupleComputedWidthCase {
const char* property_name;
WebFeature feature;
};
static const DecoupleComputedWidthCase kDecoupleComputedWidthCases[] = {
{
"border-top-width",
WebFeature::kComputedBorderTopWidthWithNoneOrHiddenStyle,
},
{
"border-right-width",
WebFeature::kComputedBorderRightWidthWithNoneOrHiddenStyle,
},
{
"border-bottom-width",
WebFeature::kComputedBorderBottomWidthWithNoneOrHiddenStyle,
},
{
"border-left-width",
WebFeature::kComputedBorderLeftWidthWithNoneOrHiddenStyle,
},
{
"border-width",
WebFeature::kComputedBorderWidthWithNoneOrHiddenStyle,
},
{
"column-rule-width",
WebFeature::kComputedColumnRuleWidthWithNoneOrHiddenStyle,
},
{
"outline-width",
WebFeature::kComputedOutlineWidthWithNoneOrHiddenStyle,
},
};
} // namespace
class PageLoadMetricsBrowserTest : public InProcessBrowserTest {
public:
PageLoadMetricsBrowserTest() {
scoped_feature_list_.InitWithFeatures(
{ukm::kUkmFeature},
// TODO(crbug.com/40248833): Use HTTPS URLs in tests to avoid having to
// disable this feature.
{features::kHttpsUpgrades});
}
PageLoadMetricsBrowserTest(const PageLoadMetricsBrowserTest&) = delete;
PageLoadMetricsBrowserTest& operator=(const PageLoadMetricsBrowserTest&) =
delete;
~PageLoadMetricsBrowserTest() override = default;
protected:
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
embedded_test_server()->RegisterRequestHandler(
base::BindRepeating(&HandleCachableRequestHandler));
}
void PreRunTestOnMainThread() override {
InProcessBrowserTest::PreRunTestOnMainThread();
histogram_tester_ = std::make_unique<base::HistogramTester>();
test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
}
// Force navigation to a new page, so the currently tracked page load runs its
// OnComplete callback. You should prefer to use PageLoadMetricsTestWaiter,
// and only use NavigateToUntrackedUrl for cases where the waiter isn't
// sufficient.
void NavigateToUntrackedUrl() {
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
}
int64_t GetUKMPageLoadMetric(std::string metric_name) {
std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries =
test_ukm_recorder_->GetMergedEntriesByName(PageLoad::kEntryName);
EXPECT_EQ(1ul, merged_entries.size());
const auto& kv = merged_entries.begin();
const int64_t* recorded =
ukm::TestUkmRecorder::GetEntryMetric(kv->second.get(), metric_name);
EXPECT_TRUE(recorded != nullptr);
return (*recorded);
}
void MakeComponentFullscreen(const std::string& id) {
EXPECT_TRUE(content::ExecJs(
browser()->tab_strip_model()->GetActiveWebContents(),
"document.getElementById(\"" + id + "\").webkitRequestFullscreen();"));
}
std::string GetRecordedPageLoadMetricNames() {
auto entries = histogram_tester_->GetTotalCountsForPrefix("PageLoad.");
std::vector<std::string> names = base::ToVector(
entries, &base::HistogramTester::CountsMap::value_type::first);
return base::JoinString(names, ",");
}
bool NoPageLoadMetricsRecorded() {
// Determine whether any 'public' page load metrics are recorded. We exclude
// 'internal' metrics as these may be recorded for debugging purposes, and
// abandonment-related metrics, which are expected to be recorded for all
// kinds of navigations.
size_t total_pageload_histograms =
histogram_tester_->GetTotalCountsForPrefix("PageLoad.").size();
size_t total_internal_histograms =
histogram_tester_->GetTotalCountsForPrefix("PageLoad.Internal.").size();
size_t total_abandon_histograms =
histogram_tester_
->GetTotalCountsForPrefix(
internal::kAbandonedPageLoadMetricsHistogramPrefix)
.size();
DCHECK_GE(total_pageload_histograms,
total_internal_histograms + total_abandon_histograms);
return total_pageload_histograms - total_internal_histograms -
total_abandon_histograms ==
0;
}
std::unique_ptr<PageLoadMetricsTestWaiter> CreatePageLoadMetricsTestWaiter(
const char* observer_name,
content::WebContents* web_contents = nullptr) {
if (!web_contents)
web_contents = browser()->tab_strip_model()->GetActiveWebContents();
return std::make_unique<PageLoadMetricsTestWaiter>(web_contents,
observer_name);
}
// Triggers nostate prefetch of |url|.
void TriggerNoStatePrefetch(const GURL& url) {
prerender::NoStatePrefetchManager* no_state_prefetch_manager =
prerender::NoStatePrefetchManagerFactory::GetForBrowserContext(
browser()->profile());
ASSERT_TRUE(no_state_prefetch_manager);
prerender::test_utils::TestNoStatePrefetchContentsFactory*
no_state_prefetch_contents_factory =
new prerender::test_utils::TestNoStatePrefetchContentsFactory();
no_state_prefetch_manager->SetNoStatePrefetchContentsFactoryForTest(
no_state_prefetch_contents_factory);
content::SessionStorageNamespace* storage_namespace =
browser()
->tab_strip_model()
->GetActiveWebContents()
->GetController()
.GetDefaultSessionStorageNamespace();
ASSERT_TRUE(storage_namespace);
std::unique_ptr<prerender::test_utils::TestPrerender> test_prerender =
no_state_prefetch_contents_factory->ExpectNoStatePrefetchContents(
prerender::FINAL_STATUS_NOSTATE_PREFETCH_FINISHED);
std::unique_ptr<prerender::NoStatePrefetchHandle> no_state_prefetch_handle =
no_state_prefetch_manager->AddSameOriginSpeculation(
url, storage_namespace, gfx::Size(640, 480),
url::Origin::Create(url));
ASSERT_EQ(no_state_prefetch_handle->contents(), test_prerender->contents());
// The final status may be either FINAL_STATUS_NOSTATE_PREFETCH_FINISHED or
// FINAL_STATUS_RECENTLY_VISITED.
test_prerender->contents()->set_skip_final_checks(true);
}
void VerifyBasicPageLoadUkms(const GURL& expected_source_url) {
const auto& entries =
test_ukm_recorder_->GetMergedEntriesByName(PageLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
for (const auto& kv : entries) {
test_ukm_recorder_->ExpectEntrySourceHasUrl(kv.second.get(),
expected_source_url);
EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
kv.second.get(),
PageLoad::
kDocumentTiming_NavigationToDOMContentLoadedEventFiredName));
EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
kv.second.get(),
PageLoad::kDocumentTiming_NavigationToLoadEventFiredName));
EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
kv.second.get(), PageLoad::kPaintTiming_NavigationToFirstPaintName));
EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
kv.second.get(),
PageLoad::kPaintTiming_NavigationToFirstContentfulPaintName));
EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
kv.second.get(), PageLoad::kMainFrameResource_SocketReusedName));
EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
kv.second.get(), PageLoad::kMainFrameResource_DNSDelayName));
EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
kv.second.get(), PageLoad::kMainFrameResource_ConnectDelayName));
EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
kv.second.get(),
PageLoad::kMainFrameResource_RequestStartToSendStartName));
EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
kv.second.get(),
PageLoad::kMainFrameResource_SendStartToReceiveHeadersEndName));
EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
kv.second.get(),
PageLoad::kMainFrameResource_RequestStartToReceiveHeadersEndName));
EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
kv.second.get(),
PageLoad::kMainFrameResource_NavigationStartToRequestStartName));
EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
kv.second.get(),
PageLoad::kMainFrameResource_HttpProtocolSchemeName));
EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
kv.second.get(), PageLoad::kSiteEngagementScoreName));
}
}
void VerifyNavigationMetrics(std::vector<GURL> expected_source_urls) {
int expected_count = expected_source_urls.size();
// Verify if the elapsed time from the navigation start are recorded.
histogram_tester_->ExpectTotalCount(
internal::kHistogramNavigationTimingNavigationStartToFirstRequestStart,
expected_count);
histogram_tester_->ExpectTotalCount(
internal::kHistogramNavigationTimingNavigationStartToFirstResponseStart,
expected_count);
histogram_tester_->ExpectTotalCount(
internal::
kHistogramNavigationTimingNavigationStartToFirstLoaderCallback,
expected_count);
histogram_tester_->ExpectTotalCount(
internal::kHistogramNavigationTimingNavigationStartToFinalRequestStart,
expected_count);
histogram_tester_->ExpectTotalCount(
internal::kHistogramNavigationTimingNavigationStartToFinalResponseStart,
expected_count);
histogram_tester_->ExpectTotalCount(
internal::
kHistogramNavigationTimingNavigationStartToFinalLoaderCallback,
expected_count);
histogram_tester_->ExpectTotalCount(
internal::
kHistogramNavigationTimingNavigationStartToNavigationCommitSent,
expected_count);
// Verify if the intervals between adjacent milestones are recorded.
histogram_tester_->ExpectTotalCount(
internal::
kHistogramNavigationTimingFirstRequestStartToFirstResponseStart,
expected_count);
histogram_tester_->ExpectTotalCount(
internal::
kHistogramNavigationTimingFirstResponseStartToFirstLoaderCallback,
expected_count);
histogram_tester_->ExpectTotalCount(
internal::
kHistogramNavigationTimingFinalRequestStartToFinalResponseStart,
expected_count);
histogram_tester_->ExpectTotalCount(
internal::
kHistogramNavigationTimingFinalResponseStartToFinalLoaderCallback,
expected_count);
histogram_tester_->ExpectTotalCount(
internal::
kHistogramNavigationTimingFinalLoaderCallbackToNavigationCommitSent,
expected_count);
using ukm::builders::NavigationTiming;
const std::vector<const char*> metrics = {
NavigationTiming::kFirstRequestStartName,
NavigationTiming::kFirstResponseStartName,
NavigationTiming::kFirstLoaderCallbackName,
NavigationTiming::kFinalRequestStartName,
NavigationTiming::kFinalResponseStartName,
NavigationTiming::kFinalLoaderCallbackName,
NavigationTiming::kNavigationCommitSentName};
const auto& entries = test_ukm_recorder_->GetMergedEntriesByName(
NavigationTiming::kEntryName);
ASSERT_EQ(expected_source_urls.size(), entries.size());
int i = 0;
for (const auto& kv : entries) {
test_ukm_recorder_->ExpectEntrySourceHasUrl(kv.second.get(),
expected_source_urls[i++]);
// Verify if the elapsed times from the navigation start are recorded.
for (const char* metric : metrics) {
EXPECT_TRUE(
test_ukm_recorder_->EntryHasMetric(kv.second.get(), metric));
}
}
}
content::WebContents* web_contents() const {
return browser()->tab_strip_model()->GetActiveWebContents();
}
content::RenderFrameHost* RenderFrameHost() const {
return web_contents()->GetPrimaryMainFrame();
}
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<base::HistogramTester> histogram_tester_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
};
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PageLCPImagePriority) {
// Waiter to ensure main content is loaded.
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
waiter->AddPageExpectation(TimingField::kLargestContentfulPaint);
const char kHtmlHttpResponseHeader[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"\r\n";
const char kImgHttpResponseHeader[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: image/png\r\n"
"\r\n";
auto main_html_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/mock_page.html",
false /*relative_url_is_prefix*/);
auto img_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/images/lcp.jpg",
false /*relative_url_is_prefix*/);
ASSERT_TRUE(embedded_test_server()->Start());
// File is under content/test/data/
std::string file_contents;
{
base::ScopedAllowBlockingForTesting allow_io;
base::FilePath test_dir;
ASSERT_TRUE(base::PathService::Get(content::DIR_TEST_DATA, &test_dir));
base::FilePath file_name = test_dir.AppendASCII("single_face.jpg");
ASSERT_TRUE(base::ReadFileToString(file_name, &file_contents));
}
browser()->OpenURL(
content::OpenURLParams(embedded_test_server()->GetURL("/mock_page.html"),
content::Referrer(),
WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_TYPED, false),
/*navigation_handle_callback=*/{});
main_html_response->WaitForRequest();
main_html_response->Send(kHtmlHttpResponseHeader);
main_html_response->Send(
"<html><body><img src=\"/images/lcp.jpg\"></body></html>");
main_html_response->Done();
img_response->WaitForRequest();
// Force layout and thus the visibility-based priority to be set, before the
// loading is finished.
content::EvalJsResult result =
EvalJs(browser()->tab_strip_model()->GetActiveWebContents(), R"(
new Promise(resolve => {
const forceLayout = () => {
document.querySelector('img').offsetTop;
resolve();
};
if (document.querySelector('img')) {
forceLayout();
} else {
// Wait for DOMContentLoaded to ensure <img> is inserted.
document.addEventListener('DOMContentLoaded', forceLayout);
}
})
)");
EXPECT_TRUE(result.is_ok());
img_response->Send(kImgHttpResponseHeader);
img_response->Send(file_contents);
img_response->Done();
// Wait on an LCP entry to make sure we have one to report when navigating
// away.
content::EvalJsResult result2 =
EvalJs(browser()->tab_strip_model()->GetActiveWebContents(), R"(
(async () => {
await new Promise(resolve => {
(new PerformanceObserver(list => {
const entries = list.getEntries();
for (let entry of entries) {
if (entry.url.includes('images')) {resolve()}
}
}))
.observe({type: 'largest-contentful-paint', buffered: true});
})})())");
EXPECT_TRUE(result2.is_ok());
waiter->Wait();
// LCP is collected only at the end of the page lifecycle. Navigate to
// flush.
NavigateToUntrackedUrl();
// Image should be loaded with `net::MEDIUM` priority because the image is
// visible.
int64_t value = GetUKMPageLoadMetric(
PageLoad::PageLoad::
kPaintTiming_LargestContentfulPaintRequestPriorityName);
ASSERT_EQ(value, static_cast<int>(net::MEDIUM));
}
class PageLoadMetricsBrowserTestAnimatedLCP
: public PageLoadMetricsBrowserTest {
protected:
void test_animated_image_lcp(bool smaller, bool animated) {
// Waiter to ensure main content is loaded.
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
waiter->AddPageExpectation(TimingField::kLargestContentfulPaint);
const char kHtmlHttpResponseHeader[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"\r\n";
const char kImgHttpResponseHeader[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: image/png\r\n"
"\r\n";
auto main_html_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/mock_page.html",
false /*relative_url_is_prefix*/);
auto img_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(),
animated ? "/images/animated-delayed.png" : "/images/delayed.jpg",
false /*relative_url_is_prefix*/);
ASSERT_TRUE(embedded_test_server()->Start());
// File is under content/test/data/
const std::string file_name_string =
animated ? "animated.png" : "single_face.jpg";
std::string file_contents;
// The first_frame_size number for the animated case (262), represents the
// first frame of the animated PNG + an extra chunk enabling the decoder to
// understand the first frame is done and decode it.
// For the non-animated case (5000), it's an arbitrary number that
// represents a part of the JPEG's frame.
const unsigned first_frame_size = animated ? 262 : 5000;
// Read the animated image into two frames.
{
base::ScopedAllowBlockingForTesting allow_io;
base::FilePath test_dir;
ASSERT_TRUE(base::PathService::Get(content::DIR_TEST_DATA, &test_dir));
base::FilePath file_name = test_dir.AppendASCII(file_name_string);
ASSERT_TRUE(base::ReadFileToString(file_name, &file_contents));
}
// Split the contents into 2 frames
std::string first_frame = file_contents.substr(0, first_frame_size);
std::string second_frame = file_contents.substr(first_frame_size);
browser()->OpenURL(
content::OpenURLParams(
embedded_test_server()->GetURL("/mock_page.html"),
content::Referrer(), WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_TYPED, false),
/*navigation_handle_callback=*/{});
main_html_response->WaitForRequest();
main_html_response->Send(kHtmlHttpResponseHeader);
main_html_response->Send(
animated ? "<html><body></body><img "
"src=\"/images/animated-delayed.png\"></script></html>"
: "<html><body></body><img "
"src=\"/images/delayed.jpg\"></script></html>");
main_html_response->Done();
img_response->WaitForRequest();
img_response->Send(kImgHttpResponseHeader);
img_response->Send(first_frame);
// Trigger a double rAF and wait a bit, then take a timestamp that's after
// the presentation time of the first frame.
// Then wait some more to ensure the timestamp is not too close to the point
// where the second frame is sent.
content::EvalJsResult result =
EvalJs(browser()->tab_strip_model()->GetActiveWebContents(), R"(
(async () => {
const double_raf = () => {
return new Promise(r => {
requestAnimationFrame(()=>requestAnimationFrame(r));
})
};
await double_raf();
await new Promise(r => setTimeout(r, 500));
const timestamp = performance.now();
await new Promise(r => setTimeout(r, 50));
return timestamp;
})();)");
EXPECT_TRUE(result.is_ok());
double timestamp = result.ExtractDouble();
img_response->Send(second_frame);
img_response->Done();
// Wait on an LCP entry to make sure we have one to report when navigating
// away.
content::EvalJsResult result2 =
EvalJs(browser()->tab_strip_model()->GetActiveWebContents(), R"(
(async () => {
await new Promise(resolve => {
(new PerformanceObserver(list => {
const entries = list.getEntries();
for (let entry of entries) {
if (entry.url.includes('images')) {resolve()}
}
}))
.observe({type: 'largest-contentful-paint', buffered: true});
})})())");
EXPECT_TRUE(result2.is_ok());
waiter->Wait();
// LCP is collected only at the end of the page lifecycle. Navigate to
// flush.
NavigateToUntrackedUrl();
int64_t value = GetUKMPageLoadMetric(
PageLoad::kPaintTiming_NavigationToLargestContentfulPaint2Name);
if (smaller) {
ASSERT_LT(value, timestamp);
} else {
ASSERT_GT(value, timestamp);
}
}
};
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, NoNavigation) {
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_TRUE(NoPageLoadMetricsRecorded())
<< "Recorded metrics: " << GetRecordedPageLoadMetricNames();
}
// TODO(crbug.com/40839280): Re-enable this test
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
DISABLED_MainFrameViewportRect) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url = embedded_test_server()->GetURL(
"a.com", "/scroll/scrollable_page_with_content.html");
auto main_frame_viewport_rect_expectation_waiter =
CreatePageLoadMetricsTestWaiter("waiter");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
int side_scrollbar_width =
EvalJs(web_contents,
"window.innerWidth - document.documentElement.clientWidth")
.ExtractInt();
int bottom_scrollbar_height =
EvalJs(web_contents,
"window.innerHeight - document.documentElement.clientHeight")
.ExtractInt();
content::RenderWidgetHostView* guest_host_view =
web_contents->GetRenderWidgetHostView();
gfx::Size viewport_size = guest_host_view->GetVisibleViewportSize();
viewport_size -= gfx::Size(side_scrollbar_width, bottom_scrollbar_height);
main_frame_viewport_rect_expectation_waiter
->AddMainFrameViewportRectExpectation(
gfx::Rect(5000, 5000, viewport_size.width(), viewport_size.height()));
ASSERT_TRUE(ExecJs(web_contents, "window.scrollTo(5000, 5000)"));
main_frame_viewport_rect_expectation_waiter->Wait();
}
// TODO(crbug.com/40916877): Fix this test on Mac.
#if BUILDFLAG(IS_MAC)
#define MAYBE_MainFrameIntersection_RTLPage \
DISABLED_MainFrameIntersection_RTLPage
#else
#define MAYBE_MainFrameIntersection_RTLPage MainFrameIntersection_RTLPage
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
MAYBE_MainFrameIntersection_RTLPage) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url = embedded_test_server()->GetURL(
"a.com", "/scroll/scrollable_page_with_content_rtl.html");
auto main_frame_intersection_rect_expectation_waiter =
CreatePageLoadMetricsTestWaiter("waiter");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
int document_width =
EvalJs(web_contents, "document.body.scrollWidth").ExtractInt();
int side_scrollbar_width =
EvalJs(web_contents,
"window.innerWidth - document.documentElement.clientWidth")
.ExtractInt();
int bottom_scrollbar_height =
EvalJs(web_contents,
"window.innerHeight - document.documentElement.clientHeight")
.ExtractInt();
content::RenderWidgetHostView* guest_host_view =
web_contents->GetRenderWidgetHostView();
gfx::Size viewport_size = guest_host_view->GetVisibleViewportSize();
viewport_size -= gfx::Size(side_scrollbar_width, bottom_scrollbar_height);
main_frame_intersection_rect_expectation_waiter
->AddMainFrameIntersectionExpectation(
gfx::Rect(document_width - 100 - 50, 5050, 100, 100));
ASSERT_TRUE(ExecJs(web_contents, "window.scrollTo(0, 5000)"));
GURL subframe_url = embedded_test_server()->GetURL("a.com", "/title1.html");
EXPECT_TRUE(
ExecJs(web_contents,
content::JsReplace(kCreateFrameAtTopRightPositionScript,
subframe_url.spec().c_str(), 5050, 50, 100)));
main_frame_intersection_rect_expectation_waiter->Wait();
}
IN_PROC_BROWSER_TEST_F(
PageLoadMetricsBrowserTest,
// TODO(crbug.com/40839452): Re-enable this test
DISABLED_NonZeroMainFrameScrollOffset_NestedSameOriginFrame_MainFrameIntersection) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url = embedded_test_server()->GetURL(
"a.com", "/scroll/scrollable_page_with_content.html");
auto main_frame_intersection_expectation_waiter =
CreatePageLoadMetricsTestWaiter("waiter");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Subframe
main_frame_intersection_expectation_waiter
->AddMainFrameIntersectionExpectation(
gfx::Rect(/*x=*/50, /*y=*/5050, /*width=*/100, /*height=*/100));
// Nested frame
main_frame_intersection_expectation_waiter
->AddMainFrameIntersectionExpectation(
gfx::Rect(/*x=*/55, /*y=*/5055, /*width=*/1, /*height=*/1));
ASSERT_TRUE(ExecJs(web_contents, "window.scrollTo(0, 5000)"));
GURL subframe_url = embedded_test_server()->GetURL("a.com", "/title1.html");
content::DOMMessageQueue dom_message_queue(web_contents);
std::string message;
content::ExecuteScriptAsync(
web_contents,
content::JsReplace(kCreateFrameAtPositionNotifyOnLoadScript,
subframe_url.spec().c_str(), 5050, 50, 100));
EXPECT_TRUE(dom_message_queue.WaitForMessage(&message));
EXPECT_EQ("\"iframe.onload\"", message);
content::RenderFrameHost* subframe = content::FrameMatchingPredicate(
web_contents->GetPrimaryPage(),
base::BindRepeating(&content::FrameMatchesName, "frame"));
GURL nested_frame_url =
embedded_test_server()->GetURL("a.com", "/title1.html");
EXPECT_TRUE(ExecJs(
subframe, content::JsReplace(kCreateFrameAtPositionScript,
nested_frame_url.spec().c_str(), 5, 5, 1)));
main_frame_intersection_expectation_waiter->Wait();
}
// TODO(crbug.com/40234728): Fix flakiness.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
#define MAYBE_NonZeroMainFrameScrollOffset_NestedCrossOriginFrame_MainFrameIntersection \
DISABLED_NonZeroMainFrameScrollOffset_NestedCrossOriginFrame_MainFrameIntersection
#else
#define MAYBE_NonZeroMainFrameScrollOffset_NestedCrossOriginFrame_MainFrameIntersection \
NonZeroMainFrameScrollOffset_NestedCrossOriginFrame_MainFrameIntersection
#endif
IN_PROC_BROWSER_TEST_F(
PageLoadMetricsBrowserTest,
MAYBE_NonZeroMainFrameScrollOffset_NestedCrossOriginFrame_MainFrameIntersection) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url = embedded_test_server()->GetURL(
"a.com", "/scroll/scrollable_page_with_content.html");
auto main_frame_intersection_expectation_waiter =
CreatePageLoadMetricsTestWaiter("waiter");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Subframe
main_frame_intersection_expectation_waiter
->AddMainFrameIntersectionExpectation(
gfx::Rect(/*x=*/50, /*y=*/5050, /*width=*/100, /*height=*/100));
// Nested frame
main_frame_intersection_expectation_waiter
->AddMainFrameIntersectionExpectation(
gfx::Rect(/*x=*/55, /*y=*/5055, /*width=*/1, /*height=*/1));
ASSERT_TRUE(ExecJs(web_contents, "window.scrollTo(0, 5000)"));
GURL subframe_url = embedded_test_server()->GetURL("b.com", "/title1.html");
content::DOMMessageQueue dom_message_queue(web_contents);
std::string message;
content::ExecuteScriptAsync(
web_contents,
content::JsReplace(kCreateFrameAtPositionNotifyOnLoadScript,
subframe_url.spec().c_str(), 5050, 50, 100));
EXPECT_TRUE(dom_message_queue.WaitForMessage(&message));
EXPECT_EQ("\"iframe.onload\"", message);
content::RenderFrameHost* subframe = content::FrameMatchingPredicate(
web_contents->GetPrimaryPage(),
base::BindRepeating(&content::FrameMatchesName, "frame"));
GURL nested_frame_url =
embedded_test_server()->GetURL("c.com", "/title1.html");
EXPECT_TRUE(ExecJs(
subframe, content::JsReplace(kCreateFrameAtPositionScript,
nested_frame_url.spec().c_str(), 5, 5, 1)));
main_frame_intersection_expectation_waiter->Wait();
}
// TODO(crbug.com/40916877): Fix this test on Mac.
#if BUILDFLAG(IS_MAC)
#define MAYBE_NonZeroMainFrameScrollOffset_SameOriginFrameAppended_MainFrameIntersection \
DISABLED_NonZeroMainFrameScrollOffset_SameOriginFrameAppended_MainFrameIntersection
#else
#define MAYBE_NonZeroMainFrameScrollOffset_SameOriginFrameAppended_MainFrameIntersection \
NonZeroMainFrameScrollOffset_SameOriginFrameAppended_MainFrameIntersection
#endif
IN_PROC_BROWSER_TEST_F(
PageLoadMetricsBrowserTest,
MAYBE_NonZeroMainFrameScrollOffset_SameOriginFrameAppended_MainFrameIntersection) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url = embedded_test_server()->GetURL(
"a.com", "/scroll/scrollable_page_with_content.html");
auto main_frame_intersection_expectation_waiter =
CreatePageLoadMetricsTestWaiter("waiter");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
main_frame_intersection_expectation_waiter
->AddMainFrameIntersectionExpectation(
gfx::Rect(/*x=*/50, /*y=*/5050, /*width=*/1, /*height=*/1));
ASSERT_TRUE(ExecJs(web_contents, "window.scrollTo(0, 5000)"));
GURL subframe_url = embedded_test_server()->GetURL("a.com", "/title1.html");
EXPECT_TRUE(
ExecJs(web_contents,
content::JsReplace(kCreateFrameAtPositionScript,
subframe_url.spec().c_str(), 5050, 50, 1)));
main_frame_intersection_expectation_waiter->Wait();
}
// TODO(crbug.com/40916877): Fix this test on Mac.
#if BUILDFLAG(IS_MAC)
#define MAYBE_NonZeroMainFrameScrollOffset_CrossOriginFrameAppended_MainFrameIntersection \
DISABLED_NonZeroMainFrameScrollOffset_CrossOriginFrameAppended_MainFrameIntersection
#else
#define MAYBE_NonZeroMainFrameScrollOffset_CrossOriginFrameAppended_MainFrameIntersection \
NonZeroMainFrameScrollOffset_CrossOriginFrameAppended_MainFrameIntersection
#endif
IN_PROC_BROWSER_TEST_F(
PageLoadMetricsBrowserTest,
MAYBE_NonZeroMainFrameScrollOffset_CrossOriginFrameAppended_MainFrameIntersection) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url = embedded_test_server()->GetURL(
"a.com", "/scroll/scrollable_page_with_content.html");
auto main_frame_intersection_expectation_waiter =
CreatePageLoadMetricsTestWaiter("waiter");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
main_frame_intersection_expectation_waiter
->AddMainFrameIntersectionExpectation(
gfx::Rect(/*x=*/50, /*y=*/5050, /*width=*/1, /*height=*/1));
ASSERT_TRUE(ExecJs(web_contents, "window.scrollTo(0, 5000)"));
GURL subframe_url = embedded_test_server()->GetURL("b.com", "/title1.html");
EXPECT_TRUE(
ExecJs(web_contents,
content::JsReplace(kCreateFrameAtPositionScript,
subframe_url.spec().c_str(), 5050, 50, 1)));
main_frame_intersection_expectation_waiter->Wait();
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, NewPage) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url = embedded_test_server()->GetURL("/title1.html");
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kFirstPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
waiter->Wait();
histogram_tester_->ExpectTotalCount(internal::kHistogramDomContentLoaded, 1);
histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 1);
histogram_tester_->ExpectTotalCount(
internal::kHistogramParseBlockedOnScriptLoad, 1);
histogram_tester_->ExpectTotalCount(
internal::kHistogramParseBlockedOnScriptExecution, 1);
// Force navigation to another page, which should force logging of histograms
// persisted at the end of the page load lifetime.
NavigateToUntrackedUrl();
histogram_tester_->ExpectTotalCount(
internal::kHistogramPageTimingForegroundDuration, 1);
VerifyBasicPageLoadUkms(url);
const auto& nostate_prefetch_entries =
test_ukm_recorder_->GetMergedEntriesByName(NoStatePrefetch::kEntryName);
EXPECT_EQ(0u, nostate_prefetch_entries.size());
VerifyNavigationMetrics({url});
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, Redirect) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL final_url = embedded_test_server()->GetURL("/title1.html");
GURL first_url =
embedded_test_server()->GetURL("/server-redirect?" + final_url.spec());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kFirstPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), first_url));
waiter->Wait();
histogram_tester_->ExpectTotalCount(internal::kHistogramDomContentLoaded, 1);
histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 1);
histogram_tester_->ExpectTotalCount(
internal::kHistogramParseBlockedOnScriptLoad, 1);
histogram_tester_->ExpectTotalCount(
internal::kHistogramParseBlockedOnScriptExecution, 1);
// Force navigation to another page, which should force logging of histograms
// persisted at the end of the page load lifetime.
NavigateToUntrackedUrl();
histogram_tester_->ExpectTotalCount(
internal::kHistogramPageTimingForegroundDuration, 1);
VerifyBasicPageLoadUkms(final_url);
const auto& nostate_prefetch_entries =
test_ukm_recorder_->GetMergedEntriesByName(NoStatePrefetch::kEntryName);
EXPECT_EQ(0u, nostate_prefetch_entries.size());
VerifyNavigationMetrics({final_url});
}
class PageLoadMetricsPre3pcdBrowserTest : public PageLoadMetricsBrowserTest {
public:
PageLoadMetricsPre3pcdBrowserTest() {
scoped_feature_list_.InitAndDisableFeature(
content_settings::features::kTrackingProtection3pcd);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Triggers nostate prefetch, and verifies that the UKM metrics related to
// nostate prefetch are recorded correctly.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsPre3pcdBrowserTest,
NoStatePrefetchMetrics) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url = embedded_test_server()->GetURL("/title1.html");
TriggerNoStatePrefetch(url);
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kFirstPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
waiter->Wait();
// Force navigation to another page, which should force logging of histograms
// persisted at the end of the page load lifetime.
NavigateToUntrackedUrl();
histogram_tester_->ExpectTotalCount(
internal::kHistogramPageTimingForegroundDuration, 1);
const auto& entries =
test_ukm_recorder_->GetMergedEntriesByName(NoStatePrefetch::kEntryName);
EXPECT_EQ(1u, entries.size());
for (const auto& kv : entries) {
test_ukm_recorder_->ExpectEntrySourceHasUrl(kv.second.get(), url);
// UKM metrics related to attempted nostate prefetch should be recorded.
EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
kv.second.get(), NoStatePrefetch::kPrefetchedRecently_FinalStatusName));
EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
kv.second.get(), NoStatePrefetch::kPrefetchedRecently_OriginName));
EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
kv.second.get(), NoStatePrefetch::kPrefetchedRecently_PrefetchAgeName));
}
VerifyNavigationMetrics({url});
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, CachedPage) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url = embedded_test_server()->GetURL(kCacheablePathPrefix);
// Navigate to the |url| to cache the main resource.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
NavigateToUntrackedUrl();
auto entries =
test_ukm_recorder_->GetMergedEntriesByName(PageLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
for (const auto& kv : entries) {
auto* const uncached_load_entry = kv.second.get();
test_ukm_recorder_->ExpectEntrySourceHasUrl(uncached_load_entry, url);
EXPECT_FALSE(test_ukm_recorder_->EntryHasMetric(uncached_load_entry,
PageLoad::kWasCachedName));
}
VerifyNavigationMetrics({url});
// Reset the recorders so it would only contain the cached pageload.
histogram_tester_ = std::make_unique<base::HistogramTester>();
test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
// Second navigation to the |url| should hit cache.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
// Force navigation to another page, which should force logging of histograms
// persisted at the end of the page load lifetime.
NavigateToUntrackedUrl();
entries = test_ukm_recorder_->GetMergedEntriesByName(PageLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
for (const auto& kv : entries) {
auto* const cached_load_entry = kv.second.get();
test_ukm_recorder_->ExpectEntrySourceHasUrl(cached_load_entry, url);
EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(cached_load_entry,
PageLoad::kWasCachedName));
}
VerifyNavigationMetrics({url});
}
// Test that we log kMainFrameResource_RequestHasNoStore when response has
// cache-control:no-store response header.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, MainFrameHasNoStore) {
// Create a HTTP response to control main-frame navigation to send no-store
// response.
net::test_server::ControllableHttpResponse response(embedded_test_server(),
"/main_document");
ASSERT_TRUE(embedded_test_server()->Start());
const GURL kUrl = embedded_test_server()->GetURL("/main_document");
// Load the document and specify no-store for the main resource.
content::TestNavigationManager navigation_manager(web_contents(), kUrl);
browser()->OpenURL(content::OpenURLParams(kUrl, content::Referrer(),
WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_TYPED, false),
/*navigation_handle_callback=*/{});
// The navigation starts.
EXPECT_TRUE(navigation_manager.WaitForRequestStart());
navigation_manager.ResumeNavigation();
// The response's headers are received.
response.WaitForRequest();
response.Send(kResponseWithNoStore);
response.Done();
EXPECT_TRUE(navigation_manager.WaitForResponse());
navigation_manager.ResumeNavigation();
ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
NavigateToUntrackedUrl();
auto entries =
test_ukm_recorder_->GetMergedEntriesByName(PageLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
for (const auto& kv : entries) {
auto* const no_store_entry = kv.second.get();
test_ukm_recorder_->ExpectEntrySourceHasUrl(no_store_entry, kUrl);
// RequestHasNoStore event should be recorded with value 1 as the response
// as no-store in it.
EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
no_store_entry, PageLoad::kMainFrameResource_RequestHasNoStoreName));
test_ukm_recorder_->ExpectEntryMetric(
no_store_entry, PageLoad::kMainFrameResource_RequestHasNoStoreName, 1);
}
}
// Test that we set kMainFrameResource_RequestHasNoStore to false when response
// has no cache-control:no-store header.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
MainFrameDoesnotHaveNoStore) {
ASSERT_TRUE(embedded_test_server()->Start());
// Navigate to an URL to see if metrics are recorded.
const GURL kUrl = embedded_test_server()->GetURL("/title1.html");
// Navigate to the |kUrl| with no cache-control: no store header.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kUrl));
NavigateToUntrackedUrl();
auto entries =
test_ukm_recorder_->GetMergedEntriesByName(PageLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
for (const auto& kv : entries) {
auto* const no_store_entry = kv.second.get();
test_ukm_recorder_->ExpectEntrySourceHasUrl(no_store_entry, kUrl);
// RequestHasNoStore event should be recorded with value false.
EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
no_store_entry, PageLoad::kMainFrameResource_RequestHasNoStoreName));
test_ukm_recorder_->ExpectEntryMetric(
no_store_entry, PageLoad::kMainFrameResource_RequestHasNoStoreName, 0);
}
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, NewPageInNewForegroundTab) {
ASSERT_TRUE(embedded_test_server()->Start());
NavigateParams params(browser(),
embedded_test_server()->GetURL("/title1.html"),
ui::PAGE_TRANSITION_LINK);
params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
Navigate(&params);
auto waiter = std::make_unique<PageLoadMetricsTestWaiter>(
params.navigated_or_inserted_contents);
waiter->AddPageExpectation(TimingField::kLoadEvent);
waiter->Wait();
// Due to crbug.com/725347, with browser side navigation enabled, navigations
// in new tabs were recorded as starting in the background. Here we verify
// that navigations initiated in a new tab are recorded as happening in the
// foreground.
histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
histogram_tester_->ExpectTotalCount(internal::kBackgroundHistogramLoad, 0);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, NoPaintForEmptyDocument) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/empty.html")));
waiter->Wait();
EXPECT_FALSE(waiter->DidObserveInPage(TimingField::kFirstPaint));
histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 0);
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstContentfulPaint,
0);
}
// TODO(crbug.com/41472183): Flaky on Win and Linux.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#define MAYBE_NoPaintForEmptyDocumentInChildFrame \
DISABLED_NoPaintForEmptyDocumentInChildFrame
#else
#define MAYBE_NoPaintForEmptyDocumentInChildFrame \
NoPaintForEmptyDocumentInChildFrame
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
MAYBE_NoPaintForEmptyDocumentInChildFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL a_url(
embedded_test_server()->GetURL("/page_load_metrics/empty_iframe.html"));
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
waiter->AddSubFrameExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), a_url));
waiter->Wait();
EXPECT_FALSE(waiter->DidObserveInPage(TimingField::kFirstPaint));
histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 0);
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstContentfulPaint,
0);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PaintInChildFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL a_url(embedded_test_server()->GetURL("/page_load_metrics/iframe.html"));
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
waiter->AddPageExpectation(TimingField::kFirstPaint);
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
waiter->AddSubFrameExpectation(TimingField::kFirstPaint);
waiter->AddSubFrameExpectation(TimingField::kFirstContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), a_url));
waiter->Wait();
histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PaintInDynamicChildFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
waiter->AddPageExpectation(TimingField::kFirstPaint);
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
waiter->AddSubFrameExpectation(TimingField::kFirstPaint);
waiter->AddSubFrameExpectation(TimingField::kFirstContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/page_load_metrics/dynamic_iframe.html")));
waiter->Wait();
histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PaintInMultipleChildFrames) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL a_url(embedded_test_server()->GetURL("/page_load_metrics/iframes.html"));
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
waiter->AddSubFrameExpectation(TimingField::kFirstPaint);
waiter->AddPageExpectation(TimingField::kFirstPaint);
waiter->AddSubFrameExpectation(TimingField::kFirstContentfulPaint);
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), a_url));
waiter->Wait();
histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 1);
}
// TODO(crbug.com/334416161): Re-enable this test on Windows.
#if BUILDFLAG(IS_WIN)
#define MAYBE_PaintInMainAndChildFrame DISABLED_PaintInMainAndChildFrame
#else
#define MAYBE_PaintInMainAndChildFrame PaintInMainAndChildFrame
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
MAYBE_PaintInMainAndChildFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL a_url(embedded_test_server()->GetURL(
"/page_load_metrics/main_frame_with_iframe.html"));
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
waiter->AddPageExpectation(TimingField::kFirstPaint);
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
waiter->AddSubFrameExpectation(TimingField::kFirstPaint);
waiter->AddSubFrameExpectation(TimingField::kFirstContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), a_url));
waiter->Wait();
histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, SameDocumentNavigation) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
GURL url = embedded_test_server()->GetURL("/title1.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
waiter->Wait();
histogram_tester_->ExpectTotalCount(internal::kHistogramDomContentLoaded, 1);
histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
// Perform a same-document navigation. No additional metrics should be logged.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/title1.html#hash")));
NavigateToUntrackedUrl();
histogram_tester_->ExpectTotalCount(internal::kHistogramDomContentLoaded, 1);
histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
VerifyNavigationMetrics({url});
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, SameUrlNavigation) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
GURL url = embedded_test_server()->GetURL("/title1.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
waiter->Wait();
histogram_tester_->ExpectTotalCount(internal::kHistogramDomContentLoaded, 1);
histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
waiter->Wait();
VerifyNavigationMetrics({url});
// We expect one histogram sample for each navigation to title1.html.
histogram_tester_->ExpectTotalCount(internal::kHistogramDomContentLoaded, 2);
histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 2);
// Force navigation to another page, which should force logging of histograms
// persisted at the end of the page load lifetime.
NavigateToUntrackedUrl();
VerifyNavigationMetrics({url, url});
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
DocWriteAbortsSubframeNavigation) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/page_load_metrics/doc_write_aborts_subframe.html")));
waiter->AddMinimumCompleteResourcesExpectation(4);
waiter->Wait();
EXPECT_FALSE(waiter->DidObserveInPage(TimingField::kFirstPaint));
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, NonHtmlMainResource) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/circle.svg")));
NavigateToUntrackedUrl();
EXPECT_TRUE(NoPageLoadMetricsRecorded())
<< "Recorded metrics: " << GetRecordedPageLoadMetricNames();
}
// TODO(crbug.com/40774566): Test flakes on Chrome OS.
#if BUILDFLAG(IS_CHROMEOS)
#define MAYBE_NonHttpOrHttpsUrl DISABLED_NonHttpOrHttpsUrl
#else
#define MAYBE_NonHttpOrHttpsUrl NonHttpOrHttpsUrl
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, MAYBE_NonHttpOrHttpsUrl) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(),
GURL(chrome::kChromeUIVersionURL)));
NavigateToUntrackedUrl();
EXPECT_TRUE(NoPageLoadMetricsRecorded())
<< "Recorded metrics: " << GetRecordedPageLoadMetricNames();
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, HttpErrorPage) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("/page_load_metrics/404.html")));
NavigateToUntrackedUrl();
EXPECT_TRUE(NoPageLoadMetricsRecorded())
<< "Recorded metrics: " << GetRecordedPageLoadMetricNames();
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, ChromeErrorPage) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url = embedded_test_server()->GetURL("/title1.html");
// By shutting down the server, we ensure a failure.
ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
content::NavigationHandleObserver observer(
browser()->tab_strip_model()->GetActiveWebContents(), url);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_TRUE(observer.is_error());
NavigateToUntrackedUrl();
EXPECT_TRUE(NoPageLoadMetricsRecorded())
<< "Recorded metrics: " << GetRecordedPageLoadMetricNames();
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, Ignore204Pages) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/page204.html")));
NavigateToUntrackedUrl();
EXPECT_TRUE(NoPageLoadMetricsRecorded())
<< "Recorded metrics: " << GetRecordedPageLoadMetricNames();
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, IgnoreDownloads) {
ASSERT_TRUE(embedded_test_server()->Start());
content::DownloadTestObserverTerminal downloads_observer(
browser()->profile()->GetDownloadManager(),
1, // == wait_count (only waiting for "download-test3.gif").
content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/download-test3.gif")));
downloads_observer.WaitForFinished();
NavigateToUntrackedUrl();
EXPECT_TRUE(NoPageLoadMetricsRecorded())
<< "Recorded metrics: " << GetRecordedPageLoadMetricNames();
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, NoDocumentWrite) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/title1.html")));
waiter->Wait();
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstContentfulPaint,
1);
histogram_tester_->ExpectTotalCount(
internal::kHistogramDocWriteBlockParseStartToFirstContentfulPaint, 0);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, DocumentWriteBlock) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/page_load_metrics/document_write_script_block.html")));
waiter->Wait();
histogram_tester_->ExpectTotalCount(
internal::kHistogramDocWriteBlockParseStartToFirstContentfulPaint, 1);
}
// TODO(crbug.com/334416161): Re-enable this test on Windows.
#if BUILDFLAG(IS_WIN)
#define MAYBE_DocumentWriteReload DISABLED_DocumentWriteReload
#else
#define MAYBE_DocumentWriteReload DocumentWriteReload
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, MAYBE_DocumentWriteReload) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/page_load_metrics/document_write_script_block.html")));
waiter->Wait();
histogram_tester_->ExpectTotalCount(
internal::kHistogramDocWriteBlockParseStartToFirstContentfulPaint, 1);
// Reload should not log the histogram as the script is not blocked.
waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/page_load_metrics/document_write_script_block.html")));
waiter->Wait();
waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/page_load_metrics/document_write_script_block.html")));
waiter->Wait();
histogram_tester_->ExpectTotalCount(
internal::kHistogramDocWriteBlockParseStartToFirstContentfulPaint, 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, DocumentWriteAsync) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/page_load_metrics/document_write_async_script.html")));
waiter->Wait();
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstContentfulPaint,
1);
histogram_tester_->ExpectTotalCount(
internal::kHistogramDocWriteBlockParseStartToFirstContentfulPaint, 0);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, DocumentWriteSameDomain) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"/page_load_metrics/document_write_external_script.html")));
waiter->Wait();
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstContentfulPaint,
1);
histogram_tester_->ExpectTotalCount(
internal::kHistogramDocWriteBlockParseStartToFirstContentfulPaint, 0);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, NoDocumentWriteScript) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/page_load_metrics/document_write_no_script.html")));
waiter->Wait();
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstContentfulPaint,
1);
histogram_tester_->ExpectTotalCount(
internal::kHistogramDocWriteBlockParseStartToFirstContentfulPaint, 0);
}
// TODO(crbug.com/40516222): Flaky on Linux dbg.
// TODO(crbug.com/41328109): Now flaky on Win and Mac.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, DISABLED_BadXhtml) {
ASSERT_TRUE(embedded_test_server()->Start());
// When an XHTML page contains invalid XML, it causes a paint of the error
// message without a layout. Page load metrics currently treats this as an
// error. Eventually, we'll fix this by special casing the handling of
// documents with non-well-formed XML on the blink side. See crbug.com/627607
// for more.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("/page_load_metrics/badxml.xhtml")));
NavigateToUntrackedUrl();
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 0);
histogram_tester_->ExpectBucketCount(
page_load_metrics::internal::kErrorEvents,
page_load_metrics::ERR_BAD_TIMING_IPC_INVALID_TIMING, 1);
histogram_tester_->ExpectTotalCount(
page_load_metrics::internal::kPageLoadTimingStatus, 1);
histogram_tester_->ExpectBucketCount(
page_load_metrics::internal::kPageLoadTimingStatus,
page_load_metrics::internal::INVALID_ORDER_PARSE_START_FIRST_PAINT, 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PayloadSize) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("/page_load_metrics/large.html")));
waiter->Wait();
// Payload histograms are only logged when a page load terminates, so force
// navigation to another page.
NavigateToUntrackedUrl();
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PayloadSizeChildFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("/page_load_metrics/large_iframe.html")));
waiter->Wait();
// Payload histograms are only logged when a page load terminates, so force
// navigation to another page.
NavigateToUntrackedUrl();
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
PayloadSizeIgnoresDownloads) {
ASSERT_TRUE(embedded_test_server()->Start());
content::DownloadTestObserverTerminal downloads_observer(
browser()->profile()->GetDownloadManager(),
1, // == wait_count (only waiting for "download-test1.lib").
content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/page_load_metrics/download_anchor_click.html")));
downloads_observer.WaitForFinished();
NavigateToUntrackedUrl();
}
// Test UseCounter Features observed in the main frame are recorded, exactly
// once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterFeaturesInMainFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/page_load_metrics/use_counter_features.html")));
waiter->Wait();
NavigateToUntrackedUrl();
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kTextWholeText), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.MainFrame.Features",
static_cast<int32_t>(WebFeature::kTextWholeText), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.MainFrame.Features",
static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kNavigatorVibrate), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.MainFrame.Features",
static_cast<int32_t>(WebFeature::kNavigatorVibrate), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kPageVisits), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.MainFrame.Features",
static_cast<int32_t>(WebFeature::kPageVisits), 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterCSSPropertiesInMainFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/page_load_metrics/use_counter_features.html")));
waiter->Wait();
NavigateToUntrackedUrl();
// CSSPropertyID::kFontFamily
histogram_tester_->ExpectBucketCount("Blink.UseCounter.CSSProperties", 6, 1);
// CSSPropertyID::kFontSize
histogram_tester_->ExpectBucketCount("Blink.UseCounter.CSSProperties", 7, 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.CSSProperties",
blink::mojom::CSSSampleId::kTotalPagesMeasured, 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterAnimatedCSSPropertiesInMainFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/page_load_metrics/use_counter_features.html")));
waiter->Wait();
NavigateToUntrackedUrl();
// CSSPropertyID::kWidth
histogram_tester_->ExpectBucketCount("Blink.UseCounter.AnimatedCSSProperties",
161, 1);
// CSSPropertyID::kMarginLeft
histogram_tester_->ExpectBucketCount("Blink.UseCounter.AnimatedCSSProperties",
91, 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.AnimatedCSSProperties",
blink::mojom::CSSSampleId::kTotalPagesMeasured, 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterCSSVisitedColumnRuleColorInMainFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
histogram_tester_->ExpectBucketCount("Blink.UseCounter.Features",
WebFeature::kVisitedColumnRuleColor, 0);
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/page_load_metrics/use_counter_features.html")));
waiter->Wait();
NavigateToUntrackedUrl();
histogram_tester_->ExpectBucketCount("Blink.UseCounter.Features",
WebFeature::kVisitedColumnRuleColor, 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterWebDXFeaturesInMainFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/page_load_metrics/use_counter_features.html")));
waiter->Wait();
NavigateToUntrackedUrl();
histogram_tester_->ExpectBucketCount("Blink.UseCounter.WebDXFeatures",
WebDXFeature::kPageVisits, 1);
histogram_tester_->ExpectBucketCount("Blink.UseCounter.WebDXFeatures",
WebDXFeature::kWebAnimations, 1);
}
class PageLoadMetricsBrowserTestWithAutoupgradesDisabled
: public PageLoadMetricsBrowserTest {
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
PageLoadMetricsBrowserTest::SetUpCommandLine(command_line);
feature_list.InitAndDisableFeature(
blink::features::kMixedContentAutoupgrade);
}
private:
base::test::ScopedFeatureList feature_list;
};
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestWithAutoupgradesDisabled,
UseCounterFeaturesMixedContent) {
// UseCounterFeaturesInMainFrame loads the test file on a loopback
// address. Loopback is treated as a secure origin in most ways, but it
// doesn't count as mixed content when it loads http://
// subresources. Therefore, this test loads the test file on a real HTTPS
// server.
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.AddDefaultHandlers(GetChromeTestDataDir());
ASSERT_TRUE(https_server.Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
https_server.GetURL("/page_load_metrics/use_counter_features.html")));
waiter->Wait();
NavigateToUntrackedUrl();
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kMixedContentAudio), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kMixedContentImage), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kMixedContentVideo), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kPageVisits), 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestWithAutoupgradesDisabled,
UseCounterCSSPropertiesMixedContent) {
// UseCounterCSSPropertiesInMainFrame loads the test file on a loopback
// address. Loopback is treated as a secure origin in most ways, but it
// doesn't count as mixed content when it loads http://
// subresources. Therefore, this test loads the test file on a real HTTPS
// server.
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.AddDefaultHandlers(GetChromeTestDataDir());
ASSERT_TRUE(https_server.Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
https_server.GetURL("/page_load_metrics/use_counter_features.html")));
waiter->Wait();
NavigateToUntrackedUrl();
// CSSPropertyID::kFontFamily
histogram_tester_->ExpectBucketCount("Blink.UseCounter.CSSProperties", 6, 1);
// CSSPropertyID::kFontSize
histogram_tester_->ExpectBucketCount("Blink.UseCounter.CSSProperties", 7, 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.CSSProperties",
blink::mojom::CSSSampleId::kTotalPagesMeasured, 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestWithAutoupgradesDisabled,
UseCounterAnimatedCSSPropertiesMixedContent) {
// UseCounterCSSPropertiesInMainFrame loads the test file on a loopback
// address. Loopback is treated as a secure origin in most ways, but it
// doesn't count as mixed content when it loads http://
// subresources. Therefore, this test loads the test file on a real HTTPS
// server.
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.AddDefaultHandlers(GetChromeTestDataDir());
ASSERT_TRUE(https_server.Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
https_server.GetURL("/page_load_metrics/use_counter_features.html")));
waiter->Wait();
NavigateToUntrackedUrl();
// CSSPropertyID::kWidth
histogram_tester_->ExpectBucketCount("Blink.UseCounter.AnimatedCSSProperties",
161, 1);
// CSSPropertyID::kMarginLeft
histogram_tester_->ExpectBucketCount("Blink.UseCounter.AnimatedCSSProperties",
91, 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.AnimatedCSSProperties",
blink::mojom::CSSSampleId::kTotalPagesMeasured, 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestWithAutoupgradesDisabled,
UseCounterWebDXFeaturesMixedContent) {
// UseCounterWebDXFeaturesInMainFrame loads the test file on a loopback
// address. Loopback is treated as a secure origin in most ways, but it
// doesn't count as mixed content when it loads http://
// subresources. Therefore, this test loads the test file on a real HTTPS
// server.
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.AddDefaultHandlers(GetChromeTestDataDir());
ASSERT_TRUE(https_server.Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
https_server.GetURL("/page_load_metrics/use_counter_features.html")));
waiter->Wait();
NavigateToUntrackedUrl();
histogram_tester_->ExpectBucketCount("Blink.UseCounter.WebDXFeatures",
WebDXFeature::kPageVisits, 1);
histogram_tester_->ExpectBucketCount("Blink.UseCounter.WebDXFeatures",
WebDXFeature::kWebAnimations, 1);
}
class PageLoadMetricsBrowserTestWithDecoupleComputedBorderWidthFromStyle
: public PageLoadMetricsBrowserTest,
public testing::WithParamInterface<DecoupleComputedWidthCase> {
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
PageLoadMetricsBrowserTest::SetUpCommandLine(command_line);
feature_list_.InitAndEnableFeature(
blink::features::kDecoupleComputedBorderWidthFromStyle);
}
private:
base::test::ScopedFeatureList feature_list_;
};
INSTANTIATE_TEST_SUITE_P(
All,
PageLoadMetricsBrowserTestWithDecoupleComputedBorderWidthFromStyle,
testing::ValuesIn(kDecoupleComputedWidthCases));
IN_PROC_BROWSER_TEST_P(
PageLoadMetricsBrowserTestWithDecoupleComputedBorderWidthFromStyle,
UseCounterForSingleProperty) {
ASSERT_TRUE(embedded_test_server()->Start());
// Expect 0 hits before we query for the property.
histogram_tester_->ExpectBucketCount("Blink.UseCounter.Features",
GetParam().feature, 0);
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/page_load_metrics/use_counter_features.html")));
waiter->Wait();
content::EvalJsResult result =
EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
base::StringPrintf("document.getElementById('width-no-style')."
"computedStyleMap().get('%s');",
GetParam().property_name));
EXPECT_TRUE(result.is_ok());
NavigateToUntrackedUrl();
histogram_tester_->ExpectBucketCount("Blink.UseCounter.Features",
GetParam().feature, 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterFeaturesInNonSecureMainFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"non-secure.test", "/page_load_metrics/use_counter_features.html")));
MakeComponentFullscreen("testvideo");
waiter->Wait();
NavigateToUntrackedUrl();
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kTextWholeText), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.MainFrame.Features",
static_cast<int32_t>(WebFeature::kTextWholeText), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.MainFrame.Features",
static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kNavigatorVibrate), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.MainFrame.Features",
static_cast<int32_t>(WebFeature::kNavigatorVibrate), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kPageVisits), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.MainFrame.Features",
static_cast<int32_t>(WebFeature::kPageVisits), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kFullscreenInsecureOrigin), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.MainFrame.Features",
static_cast<int32_t>(WebFeature::kFullscreenInsecureOrigin), 1);
}
// Test UseCounter UKM features observed.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterUkmFeaturesLogged) {
ASSERT_TRUE(embedded_test_server()->Start());
// Ensure that the previous page won't be stored in the back/forward cache, so
// that the histogram will be recorded when the previous page is unloaded.
// UKM/UMA logging after BFCache eviction is checked by
// PageLoadMetricsBrowserTestWithBackForwardCache's
// UseCounterUkmFeaturesLoggedOnBFCacheEviction test.
browser()
->tab_strip_model()
->GetActiveWebContents()
->GetController()
.GetBackForwardCache()
.DisableForTesting(content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
GURL url = embedded_test_server()->GetURL(
"/page_load_metrics/use_counter_features.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
MakeComponentFullscreen("testvideo");
waiter->Wait();
NavigateToUntrackedUrl();
const auto& entries = test_ukm_recorder_->GetEntriesByName(
ukm::builders::Blink_UseCounter::kEntryName);
EXPECT_THAT(entries, SizeIs(3));
std::vector<int64_t> ukm_features;
for (const ukm::mojom::UkmEntry* entry : entries) {
test_ukm_recorder_->ExpectEntrySourceHasUrl(entry, url);
test_ukm_recorder_->ExpectEntryMetric(
entry, ukm::builders::Blink_UseCounter::kIsMainFrameFeatureName, 1);
const auto* metric = test_ukm_recorder_->GetEntryMetric(
entry, ukm::builders::Blink_UseCounter::kFeatureName);
DCHECK(metric);
ukm_features.push_back(*metric);
}
EXPECT_THAT(ukm_features,
UnorderedElementsAre(
static_cast<int64_t>(WebFeature::kPageVisits),
static_cast<int64_t>(WebFeature::kFullscreenSecureOrigin),
static_cast<int64_t>(WebFeature::kNavigatorVibrate)));
}
// Test UseCounter UKM mixed content features observed.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestWithAutoupgradesDisabled,
UseCounterUkmMixedContentFeaturesLogged) {
// As with UseCounterFeaturesMixedContent, load on a real HTTPS server to
// trigger mixed content.
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.AddDefaultHandlers(GetChromeTestDataDir());
ASSERT_TRUE(https_server.Start());
// Ensure that the previous page won't be stored in the back/forward cache, so
// that the histogram will be recorded when the previous page is unloaded.
// UKM/UMA logging after BFCache eviction is checked by
// PageLoadMetricsBrowserTestWithBackForwardCache's
// UseCounterUkmFeaturesLoggedOnBFCacheEviction test.
browser()
->tab_strip_model()
->GetActiveWebContents()
->GetController()
.GetBackForwardCache()
.DisableForTesting(content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
GURL url =
https_server.GetURL("/page_load_metrics/use_counter_features.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
MakeComponentFullscreen("testvideo");
waiter->Wait();
NavigateToUntrackedUrl();
const auto& entries = test_ukm_recorder_->GetEntriesByName(
ukm::builders::Blink_UseCounter::kEntryName);
EXPECT_THAT(entries, SizeIs(6));
std::vector<int64_t> ukm_features;
for (const ukm::mojom::UkmEntry* entry : entries) {
test_ukm_recorder_->ExpectEntrySourceHasUrl(entry, url);
test_ukm_recorder_->ExpectEntryMetric(
entry, ukm::builders::Blink_UseCounter::kIsMainFrameFeatureName, 1);
const auto* metric = test_ukm_recorder_->GetEntryMetric(
entry, ukm::builders::Blink_UseCounter::kFeatureName);
DCHECK(metric);
ukm_features.push_back(*metric);
}
EXPECT_THAT(ukm_features,
UnorderedElementsAre(
static_cast<int64_t>(WebFeature::kPageVisits),
static_cast<int64_t>(WebFeature::kFullscreenSecureOrigin),
static_cast<int64_t>(WebFeature::kNavigatorVibrate),
static_cast<int64_t>(WebFeature::kMixedContentImage),
static_cast<int64_t>(WebFeature::kMixedContentAudio),
static_cast<int64_t>(WebFeature::kMixedContentVideo)));
}
// Test UseCounter Features observed in a child frame are recorded, exactly
// once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, UseCounterFeaturesInIframe) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"/page_load_metrics/use_counter_features_in_iframe.html")));
waiter->Wait();
NavigateToUntrackedUrl();
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kTextWholeText), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kNavigatorVibrate), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kPageVisits), 1);
// No feature but page visits should get counted.
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.MainFrame.Features",
static_cast<int32_t>(WebFeature::kTextWholeText), 0);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.MainFrame.Features",
static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 0);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.MainFrame.Features",
static_cast<int32_t>(WebFeature::kNavigatorVibrate), 0);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.MainFrame.Features",
static_cast<int32_t>(WebFeature::kPageVisits), 1);
}
// Test UseCounter Features observed in multiple child frames are recorded,
// exactly once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterFeaturesInMultipleIframes) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"/page_load_metrics/use_counter_features_in_iframes.html")));
waiter->Wait();
NavigateToUntrackedUrl();
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kTextWholeText), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kNavigatorVibrate), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kPageVisits), 1);
// No feature but page visits should get counted.
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.MainFrame.Features",
static_cast<int32_t>(WebFeature::kTextWholeText), 0);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.MainFrame.Features",
static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 0);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.MainFrame.Features",
static_cast<int32_t>(WebFeature::kNavigatorVibrate), 0);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.MainFrame.Features",
static_cast<int32_t>(WebFeature::kPageVisits), 1);
}
// Test UseCounter CSS properties observed in a child frame are recorded,
// exactly once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterCSSPropertiesInIframe) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"/page_load_metrics/use_counter_features_in_iframe.html")));
waiter->Wait();
NavigateToUntrackedUrl();
// CSSPropertyID::kFontFamily
histogram_tester_->ExpectBucketCount("Blink.UseCounter.CSSProperties", 6, 1);
// CSSPropertyID::kFontSize
histogram_tester_->ExpectBucketCount("Blink.UseCounter.CSSProperties", 7, 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.CSSProperties",
blink::mojom::CSSSampleId::kTotalPagesMeasured, 1);
}
// Test UseCounter CSS Properties observed in multiple child frames are
// recorded, exactly once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterCSSPropertiesInMultipleIframes) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"/page_load_metrics/use_counter_features_in_iframes.html")));
waiter->Wait();
NavigateToUntrackedUrl();
// CSSPropertyID::kFontFamily
histogram_tester_->ExpectBucketCount("Blink.UseCounter.CSSProperties", 6, 1);
// CSSPropertyID::kFontSize
histogram_tester_->ExpectBucketCount("Blink.UseCounter.CSSProperties", 7, 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.CSSProperties",
blink::mojom::CSSSampleId::kTotalPagesMeasured, 1);
}
// Test UseCounter CSS properties observed in a child frame are recorded,
// exactly once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterAnimatedCSSPropertiesInIframe) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"/page_load_metrics/use_counter_features_in_iframe.html")));
waiter->Wait();
NavigateToUntrackedUrl();
// CSSPropertyID::kWidth
histogram_tester_->ExpectBucketCount("Blink.UseCounter.AnimatedCSSProperties",
161, 1);
// CSSPropertyID::kMarginLeft
histogram_tester_->ExpectBucketCount("Blink.UseCounter.AnimatedCSSProperties",
91, 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.AnimatedCSSProperties",
blink::mojom::CSSSampleId::kTotalPagesMeasured, 1);
}
// Test UseCounter CSS Properties observed in multiple child frames are
// recorded, exactly once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterAnimatedCSSPropertiesInMultipleIframes) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"/page_load_metrics/use_counter_features_in_iframes.html")));
waiter->Wait();
NavigateToUntrackedUrl();
// CSSPropertyID::kWidth
histogram_tester_->ExpectBucketCount("Blink.UseCounter.AnimatedCSSProperties",
161, 1);
// CSSPropertyID::kMarginLeft
histogram_tester_->ExpectBucketCount("Blink.UseCounter.AnimatedCSSProperties",
91, 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.AnimatedCSSProperties",
blink::mojom::CSSSampleId::kTotalPagesMeasured, 1);
}
// Test UseCounter WebDX Features observed in a child frame are recorded,
// exactly once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterWebDXFeaturesInIframe) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"/page_load_metrics/use_counter_features_in_iframe.html")));
waiter->Wait();
NavigateToUntrackedUrl();
histogram_tester_->ExpectBucketCount("Blink.UseCounter.WebDXFeatures",
WebDXFeature::kPageVisits, 1);
histogram_tester_->ExpectBucketCount("Blink.UseCounter.WebDXFeatures",
WebDXFeature::kWebAnimations, 1);
}
// Test UseCounter WebDX Features observed in multiple child frames are
// recorded, exactly once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterWebDXFeaturesInMultipleIframes) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"/page_load_metrics/use_counter_features_in_iframes.html")));
waiter->Wait();
NavigateToUntrackedUrl();
histogram_tester_->ExpectBucketCount("Blink.UseCounter.WebDXFeatures",
WebDXFeature::kPageVisits, 1);
histogram_tester_->ExpectBucketCount("Blink.UseCounter.WebDXFeatures",
WebDXFeature::kWebAnimations, 1);
}
// Test UseCounter Features observed for SVG pages.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterObserveSVGImagePage) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("/page_load_metrics/circle.svg")));
NavigateToUntrackedUrl();
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kPageVisits), 1);
}
// Test UseCounter Permissions Policy Usages in main frame.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterPermissionsPolicyUsageInMainFrame) {
auto test_feature = static_cast<blink::UseCounterFeature::EnumValue>(
network::mojom::PermissionsPolicyFeature::kFullscreen);
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddUseCounterFeatureExpectation({
blink::mojom::UseCounterFeatureType::kPermissionsPolicyViolationEnforce,
test_feature,
});
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/page_load_metrics/use_counter_features.html")));
waiter->Wait();
NavigateToUntrackedUrl();
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.PermissionsPolicy.Violation.Enforce", test_feature, 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.PermissionsPolicy.Header2", test_feature, 1);
}
// Test UseCounter Permissions Policy Usages observed in child frame
// are recorded, exactly once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterPermissionsPolicyUsageInIframe) {
auto test_feature = static_cast<blink::UseCounterFeature::EnumValue>(
network::mojom::PermissionsPolicyFeature::kFullscreen);
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddUseCounterFeatureExpectation({
blink::mojom::UseCounterFeatureType::kPermissionsPolicyViolationEnforce,
test_feature,
});
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"/page_load_metrics/use_counter_features_in_iframe.html")));
waiter->Wait();
NavigateToUntrackedUrl();
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.PermissionsPolicy.Violation.Enforce", test_feature, 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.PermissionsPolicy.Header2", test_feature, 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.PermissionsPolicy.Allow2", test_feature, 1);
}
// Test UseCounter Permissions Policy Usages observed in multiple child frames
// are recorded, exactly once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterPermissionsPolicyUsageInMultipleIframes) {
auto test_feature = static_cast<blink::UseCounterFeature::EnumValue>(
network::mojom::PermissionsPolicyFeature::kFullscreen);
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddUseCounterFeatureExpectation({
blink::mojom::UseCounterFeatureType::kPermissionsPolicyViolationEnforce,
test_feature,
});
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"/page_load_metrics/use_counter_features_in_iframes.html")));
waiter->Wait();
NavigateToUntrackedUrl();
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.PermissionsPolicy.Violation.Enforce", test_feature, 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.PermissionsPolicy.Header2", test_feature, 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.PermissionsPolicy.Allow2", test_feature, 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, LoadingMetrics) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadTimingInfo);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/title1.html")));
// Waits until nonzero loading metrics are seen.
waiter->Wait();
}
class SessionRestorePageLoadMetricsBrowserTest
: public PageLoadMetricsBrowserTest {
public:
SessionRestorePageLoadMetricsBrowserTest() = default;
SessionRestorePageLoadMetricsBrowserTest(
const SessionRestorePageLoadMetricsBrowserTest&) = delete;
SessionRestorePageLoadMetricsBrowserTest& operator=(
const SessionRestorePageLoadMetricsBrowserTest&) = delete;
// PageLoadMetricsBrowserTest:
void SetUpOnMainThread() override {
PageLoadMetricsBrowserTest::SetUpOnMainThread();
ASSERT_TRUE(embedded_test_server()->Start());
}
Browser* QuitBrowserAndRestore(Browser* browser) {
Profile* profile = browser->profile();
SessionStartupPref::SetStartupPref(
profile, SessionStartupPref(SessionStartupPref::LAST));
#if BUILDFLAG(IS_CHROMEOS)
SessionServiceTestHelper helper(profile);
helper.SetForceBrowserNotAliveWithNoWindows(true);
#endif
auto keep_alive = std::make_unique<ScopedKeepAlive>(
KeepAliveOrigin::SESSION_RESTORE, KeepAliveRestartOption::DISABLED);
auto profile_keep_alive = std::make_unique<ScopedProfileKeepAlive>(
profile, ProfileKeepAliveOrigin::kBrowserWindow);
CloseBrowserSynchronously(browser);
// Create a new window, which should trigger session restore.
chrome::NewEmptyWindow(profile);
SessionRestoreTestHelper().Wait();
return BrowserList::GetInstance()->GetLastActive();
}
void WaitForTabsToLoad(Browser* browser) {
for (int i = 0; i < browser->tab_strip_model()->count(); ++i) {
content::WebContents* contents =
browser->tab_strip_model()->GetWebContentsAt(i);
contents->GetController().LoadIfNecessary();
ASSERT_TRUE(content::WaitForLoadStop(contents));
}
}
// The PageLoadMetricsTestWaiter can observe first meaningful paints on these
// test pages while not on other simple pages such as /title1.html.
GURL GetTestURL() const {
return embedded_test_server()->GetURL(
"/page_load_metrics/main_frame_with_iframe.html");
}
GURL GetTestURL2() const {
return embedded_test_server()->GetURL("/title2.html");
}
};
IN_PROC_BROWSER_TEST_F(SessionRestorePageLoadMetricsBrowserTest,
InitialVisibilityOfSingleRestoredTab) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestURL()));
Browser* new_browser = QuitBrowserAndRestore(browser());
ASSERT_NO_FATAL_FAILURE(WaitForTabsToLoad(new_browser));
}
IN_PROC_BROWSER_TEST_F(SessionRestorePageLoadMetricsBrowserTest,
InitialVisibilityOfMultipleRestoredTabs) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestURL()));
ui_test_utils::NavigateToURLWithDisposition(
browser(), GetTestURL(), WindowOpenDisposition::NEW_BACKGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
Browser* new_browser = QuitBrowserAndRestore(browser());
ASSERT_NO_FATAL_FAILURE(WaitForTabsToLoad(new_browser));
TabStripModel* tab_strip = new_browser->tab_strip_model();
ASSERT_TRUE(tab_strip);
ASSERT_EQ(2, tab_strip->count());
}
enum class ReduceTransferSizeUpdatedIPCTestCase {
kEnabled,
kDisabled,
};
class PageLoadMetricsResourceLoadBrowserTest
: public PageLoadMetricsBrowserTest,
public ::testing::WithParamInterface<
ReduceTransferSizeUpdatedIPCTestCase> {
public:
PageLoadMetricsResourceLoadBrowserTest() {
if (IsReduceTransferSizeUpdatedIPCEnabled()) {
feature_list_.InitAndEnableFeature(
network::features::kReduceTransferSizeUpdatedIPC);
} else {
feature_list_.InitAndDisableFeature(
network::features::kReduceTransferSizeUpdatedIPC);
}
}
~PageLoadMetricsResourceLoadBrowserTest() override = default;
protected:
bool IsReduceTransferSizeUpdatedIPCEnabled() const {
return GetParam() == ReduceTransferSizeUpdatedIPCTestCase::kEnabled;
}
private:
base::test::ScopedFeatureList feature_list_;
};
INSTANTIATE_TEST_SUITE_P(
All,
PageLoadMetricsResourceLoadBrowserTest,
testing::ValuesIn({ReduceTransferSizeUpdatedIPCTestCase::kDisabled,
ReduceTransferSizeUpdatedIPCTestCase::kEnabled}),
[](const testing::TestParamInfo<ReduceTransferSizeUpdatedIPCTestCase>&
info) {
switch (info.param) {
case ReduceTransferSizeUpdatedIPCTestCase::kEnabled:
return "ReduceTransferSizeUpdatedIPCEnabled";
case ReduceTransferSizeUpdatedIPCTestCase::kDisabled:
return "ReduceTransferSizeUpdatedIPCDisabled";
}
});
// TODO(crbug.com/41412649) Disabled due to flaky timeouts on all platforms.
IN_PROC_BROWSER_TEST_P(PageLoadMetricsResourceLoadBrowserTest,
DISABLED_ReceivedAggregateResourceDataLength) {
embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"foo.com", "/cross_site_iframe_factory.html?foo")));
waiter->Wait();
base::ByteCount one_frame_page_size = waiter->current_network_bytes();
waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b,c,d(e,f,g))")));
// Verify that 7 iframes are fetched, with some amount of tolerance since
// favicon is fetched only once.
waiter->AddMinimumNetworkBytesExpectation(
7 * (one_frame_page_size - base::ByteCount(100)));
waiter->Wait();
}
IN_PROC_BROWSER_TEST_P(PageLoadMetricsResourceLoadBrowserTest,
ChunkedResponse_OverheadDoesNotCountForBodyBytes) {
const char kHttpResponseHeader[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n";
const int kChunkSize = 5;
const int kNumChunks = 5;
auto main_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/mock_page.html",
true /*relative_url_is_prefix*/);
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
browser()->OpenURL(
content::OpenURLParams(embedded_test_server()->GetURL("/mock_page.html"),
content::Referrer(),
WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_TYPED, false),
/*navigation_handle_callback=*/{});
main_response->WaitForRequest();
main_response->Send(kHttpResponseHeader);
for (int i = 0; i < kNumChunks; i++) {
main_response->Send(base::NumberToString(kChunkSize));
main_response->Send("\r\n");
main_response->Send(std::string(kChunkSize, '*'));
main_response->Send("\r\n");
}
main_response->Done();
waiter->AddMinimumCompleteResourcesExpectation(1);
waiter->Wait();
// Verify that overheads for each chunk are not reported as body bytes.
EXPECT_EQ(waiter->current_network_body_bytes().InBytes(),
kChunkSize * kNumChunks);
}
IN_PROC_BROWSER_TEST_P(PageLoadMetricsResourceLoadBrowserTest,
ReceivedCompleteResources) {
const char kHttpResponseHeader[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"\r\n";
auto main_html_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/mock_page.html",
true /*relative_url_is_prefix*/);
auto script_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/script.js",
true /*relative_url_is_prefix*/);
auto iframe_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/iframe.html",
true /*relative_url_is_prefix*/);
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
browser()->OpenURL(
content::OpenURLParams(embedded_test_server()->GetURL("/mock_page.html"),
content::Referrer(),
WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_TYPED, false),
/*navigation_handle_callback=*/{});
main_html_response->WaitForRequest();
main_html_response->Send(kHttpResponseHeader);
main_html_response->Send(
"<html><body></body><script src=\"script.js\"></script></html>");
main_html_response->Send(std::string(1000, ' '));
main_html_response->Done();
waiter->AddMinimumCompleteResourcesExpectation(1);
waiter->AddMinimumNetworkBytesExpectation(base::ByteCount(1000));
waiter->Wait();
script_response->WaitForRequest();
script_response->Send(kHttpResponseHeader);
script_response->Send(
"var iframe = document.createElement(\"iframe\");"
"iframe.src =\"iframe.html\";"
"document.body.appendChild(iframe);");
script_response->Send(std::string(1000, ' '));
// Data received but resource not complete
waiter->AddMinimumCompleteResourcesExpectation(1);
waiter->AddMinimumNetworkBytesExpectation(base::ByteCount(2000));
if (!IsReduceTransferSizeUpdatedIPCEnabled()) {
// When ReduceTransferSizeUpdatedIPC is disabled, network bytes information
// is sent almost every time when the body data is received. So we can call
// Wait() before finishing `script_response`,
waiter->Wait();
script_response->Done();
} else {
// But when ReduceTransferSizeUpdatedIPC is enabled, network bytes
// information is sent only when the resource is complete. So we need to
// call Wait() after finishing `script_response`.
script_response->Done();
waiter->Wait();
}
waiter->AddMinimumCompleteResourcesExpectation(2);
waiter->Wait();
// Make sure main resources are loaded correctly
iframe_response->WaitForRequest();
iframe_response->Send(kHttpResponseHeader);
iframe_response->Send(std::string(2000, ' '));
iframe_response->Done();
waiter->AddMinimumCompleteResourcesExpectation(3);
waiter->AddMinimumNetworkBytesExpectation(base::ByteCount(4000));
waiter->Wait();
}
// TODO(crbug.com/334416161): Re-enable this test on Windows.
#if BUILDFLAG(IS_WIN)
#define MAYBE_InputEventsForClick DISABLED_InputEventsForClick
#else
#define MAYBE_InputEventsForClick InputEventsForClick
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, MAYBE_InputEventsForClick) {
embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
GURL url = embedded_test_server()->GetURL("/page_load_metrics/link.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
waiter->Wait();
content::SimulateMouseClickAt(
browser()->tab_strip_model()->GetActiveWebContents(), 0,
blink::WebMouseEvent::Button::kLeft, gfx::Point(100, 100));
waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
waiter->Wait();
histogram_tester_->ExpectTotalCount(internal::kHistogramInputToNavigation, 1);
histogram_tester_->ExpectTotalCount(
internal::kHistogramInputToNavigationLinkClick, 1);
histogram_tester_->ExpectTotalCount(
internal::kHistogramInputToFirstContentfulPaint, 1);
// Force navigation to another page, which should force logging of histograms
// persisted at the end of the page load lifetime.
NavigateToUntrackedUrl();
// Navigation should record the metrics twice because of the initial pageload
// and the second pageload ("/title1.html") initiated by the link click.
VerifyNavigationMetrics(
{url, embedded_test_server()->GetURL("/title1.html")});
}
class SoftNavigationBrowserTest : public PageLoadMetricsBrowserTest {
public:
void TestSoftNavigation(bool soft_navs_is_web_exposed) {
embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
waiter->AddPageExpectation(TimingField::kLargestContentfulPaint);
GURL url = embedded_test_server()->GetURL(
"/page_load_metrics/soft_navigation.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
waiter->Wait();
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::WaitForHitTestData(web_contents->GetPrimaryMainFrame());
waiter->AddPageExpectation(TimingField::kSoftNavigationCountUpdated);
waiter->AddPageExpectation(TimingField::kLargestContentfulPaint);
const std::string get_lcp_startTime = R"(
(() => new Promise(resolve => {
new PerformanceObserver(list => {
resolve(list.getEntries().at(-1).startTime);
}).observe({type: 'largest-contentful-paint', buffered: true});
}))();
)";
// Get the web exposed LCP value before the click.
int lcp_startTime = EvalJs(web_contents, get_lcp_startTime).ExtractDouble();
content::SimulateMouseClickAt(
browser()->tab_strip_model()->GetActiveWebContents(), 0,
blink::WebMouseEvent::Button::kLeft, gfx::Point(100, 100));
// Get the web exposed ICP value only if the feature flag for exposing to
// performance timeline is enabled.
if (soft_navs_is_web_exposed) {
const std::string get_icp_startTime = R"(
(() => new Promise(resolve => {
new PerformanceObserver(list => {
resolve(list.getEntries().at(-1).startTime);
}).observe({
type: 'interaction-contentful-paint',
buffered: true,
includeSoftNavigationObservations: true
});
}))();
)";
int icp_startTime =
EvalJs(web_contents, get_icp_startTime).ExtractDouble();
ASSERT_GE(icp_startTime, lcp_startTime);
}
// Wait for a soft navigation count update.
waiter->Wait();
// Force navigation to another page, which should force logging of
// histograms persisted at the end of the page load lifetime.
NavigateToUntrackedUrl();
VerifyNavigationMetrics({url});
int64_t soft_navigation_count =
GetUKMPageLoadMetric(PageLoad::kSoftNavigationCountName);
ASSERT_EQ(soft_navigation_count, 1);
auto lcp_value_bucket_start =
histogram_tester_
->GetAllSamples(internal::kHistogramLargestContentfulPaint)[0]
.min;
// The histogram value represents the low end of the bucket, not the actual
// value. Therefore it is lower or equal to the web exposed value.
ASSERT_LE(lcp_value_bucket_start, lcp_startTime);
}
};
class SoftNavigationBrowserTestWithSoftNavigationHeuristicsFlag
: public SoftNavigationBrowserTest {
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
PageLoadMetricsBrowserTest::SetUpCommandLine(command_line);
features_list_.InitWithFeatures({blink::features::kSoftNavigationHeuristics},
{});
}
private:
base::test::ScopedFeatureList features_list_;
};
IN_PROC_BROWSER_TEST_F(SoftNavigationBrowserTest, SoftNavigation) {
TestSoftNavigation(/*soft_navs_is_web_exposed=*/false);
}
IN_PROC_BROWSER_TEST_F(
SoftNavigationBrowserTestWithSoftNavigationHeuristicsFlag, SoftNavigation) {
TestSoftNavigation(/*soft_navs_is_web_exposed=*/true);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, InputEventsForOmniboxMatch) {
embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
GURL url = embedded_test_server()->GetURL("/title1.html");
ui_test_utils::SendToOmniboxAndSubmit(browser(), url.spec(),
base::TimeTicks::Now());
waiter->Wait();
histogram_tester_->ExpectTotalCount(internal::kHistogramInputToNavigation, 1);
histogram_tester_->ExpectTotalCount(
internal::kHistogramInputToFirstContentfulPaint, 1);
// Force navigation to another page, which should force logging of histograms
// persisted at the end of the page load lifetime.
NavigateToUntrackedUrl();
VerifyNavigationMetrics({url});
}
// TODO(crbug.com/334416161): Re-enable this test on Windows.
#if BUILDFLAG(IS_WIN)
#define MAYBE_InputEventsForJavaScriptHref DISABLED_InputEventsForJavaScriptHref
#else
#define MAYBE_InputEventsForJavaScriptHref InputEventsForJavaScriptHref
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
MAYBE_InputEventsForJavaScriptHref) {
embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
GURL url =
embedded_test_server()->GetURL("/page_load_metrics/javascript_href.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
waiter->Wait();
waiter = CreatePageLoadMetricsTestWaiter("waiter");
content::SimulateMouseClickAt(
browser()->tab_strip_model()->GetActiveWebContents(), 0,
blink::WebMouseEvent::Button::kLeft, gfx::Point(100, 100));
waiter->AddPageExpectation(TimingField::kLoadEvent);
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
waiter->Wait();
histogram_tester_->ExpectTotalCount(internal::kHistogramInputToNavigation, 1);
histogram_tester_->ExpectTotalCount(
internal::kHistogramInputToNavigationLinkClick, 1);
histogram_tester_->ExpectTotalCount(
internal::kHistogramInputToFirstContentfulPaint, 1);
// Force navigation to another page, which should force logging of histograms
// persisted at the end of the page load lifetime.
NavigateToUntrackedUrl();
// Navigation should record the metrics twice because of the initial pageload
// and the second pageload ("/title1.html") initiated by the link click.
VerifyNavigationMetrics(
{url, embedded_test_server()->GetURL("/title1.html")});
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
InputEventsForJavaScriptWindowOpen) {
embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
GURL url = embedded_test_server()->GetURL(
"/page_load_metrics/javascript_window_open.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
waiter->Wait();
ui_test_utils::AllBrowserTabAddedWaiter tab_added_waiter;
content::SimulateMouseClickAt(
browser()->tab_strip_model()->GetActiveWebContents(), 0,
blink::WebMouseEvent::Button::kLeft, gfx::Point(100, 100));
// Wait for new window to open.
auto* web_contents = tab_added_waiter.Wait();
waiter = std::make_unique<PageLoadMetricsTestWaiter>(web_contents);
waiter->AddPageExpectation(TimingField::kLoadEvent);
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
waiter->Wait();
histogram_tester_->ExpectTotalCount(internal::kHistogramInputToNavigation, 1);
histogram_tester_->ExpectTotalCount(
internal::kHistogramInputToNavigationLinkClick, 1);
histogram_tester_->ExpectTotalCount(
internal::kHistogramInputToFirstContentfulPaint, 1);
// Close all pages, which should force logging of histograms persisted at the
// end of the page load lifetime.
browser()->tab_strip_model()->CloseAllTabs();
// Navigation should record the metrics twice because of the initial pageload
// and the second pageload ("/title1.html") initiated by the link click.
VerifyNavigationMetrics(
{url, embedded_test_server()->GetURL("/title1.html")});
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, FirstInputFromScroll) {
embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("/page_load_metrics/scroll.html")));
waiter->Wait();
content::SimulateGestureScrollSequence(
browser()->tab_strip_model()->GetActiveWebContents(),
gfx::Point(100, 100), gfx::Vector2dF(0, 15));
NavigateToUntrackedUrl();
// First Input Delay should not be reported from a scroll!
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstInputDelay, 0);
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstInputTimestamp,
0);
}
// Does a navigation to a page controlled by a service worker and verifies
// that service worker page load metrics are logged.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, ServiceWorkerMetrics) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kFirstPaint);
// Load a page that registers a service worker.
GURL url = embedded_test_server()->GetURL(
"/service_worker/create_service_worker.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
EXPECT_EQ("DONE", EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
"register('fetch_event_pass_through.js');"));
waiter->Wait();
// The first load was not controlled, so service worker metrics should not be
// logged.
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 1);
histogram_tester_->ExpectTotalCount(
internal::kHistogramServiceWorkerFirstPaint, 0);
waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kFirstPaint);
// Load a controlled page.
GURL controlled_url = url;
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), controlled_url));
waiter->Wait();
// Service worker metrics should be logged.
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 2);
histogram_tester_->ExpectTotalCount(
internal::kHistogramServiceWorkerFirstPaint, 1);
// Force navigation to another page, which should force logging of histograms
// persisted at the end of the page load lifetime.
NavigateToUntrackedUrl();
// Navigation should record the metrics twice because of the initial pageload
// to register a service worker and the page load controlled by the service
// worker.
VerifyNavigationMetrics({url, controlled_url});
}
// Does a navigation to a page controlled by a skippable service worker
// fetch handler and verifies that the page load metrics are logged.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
ServiceWorkerSkippableFetchHandlerMetrics) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kFirstPaint);
// Load a page that registers a service worker.
GURL url = embedded_test_server()->GetURL(
"/service_worker/create_service_worker.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
EXPECT_EQ("DONE", EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
"register('empty_fetch_event.js');"));
waiter->Wait();
// The first load was not controlled, so service worker metrics should not be
// logged.
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 1);
histogram_tester_->ExpectTotalCount(
internal::kHistogramServiceWorkerFirstPaint, 0);
waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kFirstPaint);
// Load a controlled page.
GURL controlled_url = url;
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), controlled_url));
waiter->Wait();
// The metrics should be logged.
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 2);
histogram_tester_->ExpectTotalCount(
internal::kHistogramServiceWorkerFirstPaint, 1);
histogram_tester_->ExpectTotalCount(
internal::
kHistogramServiceWorkerFirstContentfulPaintSkippableFetchHandler,
1);
// Force navigation to another page, which should force logging of histograms
// persisted at the end of the page load lifetime.
NavigateToUntrackedUrl();
// Navigation should record the metrics twice because of the initial pageload
// to register a service worker and the page load controlled by the service
// worker.
VerifyNavigationMetrics({url, controlled_url});
}
// Does a navigation to a page which records a WebFeature before commit.
// Regression test for https://crbug.com/1043018.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PreCommitWebFeature) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/title1.html")));
waiter->Wait();
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kSecureContextCheckPassed), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kSecureContextCheckFailed), 0);
}
// TODO(crbug.com/40916877): Fix this test on Mac.
#if BUILDFLAG(IS_MAC)
#define MAYBE_MainFrameIntersectionsMainFrame \
DISABLED_MainFrameIntersectionsMainFrame
#else
#define MAYBE_MainFrameIntersectionsMainFrame MainFrameIntersectionsMainFrame
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
MAYBE_MainFrameIntersectionsMainFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Evaluate the height and width of the page as the browser_test can
// vary the dimensions.
int document_height =
EvalJs(web_contents, "document.body.scrollHeight").ExtractInt();
int document_width =
EvalJs(web_contents, "document.body.scrollWidth").ExtractInt();
// Expectation is before NavigateToUrl for this test as the expectation can be
// met after NavigateToUrl and before the Wait.
waiter->AddMainFrameIntersectionExpectation(
gfx::Rect(0, 0, document_width,
document_height)); // Initial main frame rect.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"/page_load_metrics/blank_with_positioned_iframe_writer.html")));
waiter->Wait();
// Create a |document_width|x|document_height| frame at 100,100, increasing
// the page width and height by 100.
waiter->AddMainFrameIntersectionExpectation(
gfx::Rect(0, 0, document_width + 100, document_height + 100));
EXPECT_TRUE(ExecJs(
web_contents,
content::JsReplace("createIframeAtRect(\"test\", 100, 100, $1, $2);",
document_width, document_height)));
waiter->Wait();
}
// TODO(crbug.com/40916877): Fix this test on Mac.
#if BUILDFLAG(IS_MAC)
#define MAYBE_MainFrameIntersectionSingleFrame \
DISABLED_MainFrameIntersectionSingleFrame
#else
#define MAYBE_MainFrameIntersectionSingleFrame MainFrameIntersectionSingleFrame
#endif
// Creates a single frame within the main frame and verifies the intersection
// with the main frame.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
MAYBE_MainFrameIntersectionSingleFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"/page_load_metrics/blank_with_positioned_iframe_writer.html")));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
waiter->AddMainFrameIntersectionExpectation(gfx::Rect(100, 100, 200, 200));
// Create a 200x200 iframe at 100,100.
EXPECT_TRUE(ExecJs(web_contents,
"createIframeAtRect(\"test\", 100, 100, 200, 200);"));
waiter->Wait();
}
// TODO(crbug.com/40916877): Fix this test on Mac.
#if BUILDFLAG(IS_MAC)
#define MAYBE_MainFrameIntersectionSameOrigin \
DISABLED_MainFrameIntersectionSameOrigin
#else
#define MAYBE_MainFrameIntersectionSameOrigin MainFrameIntersectionSameOrigin
#endif
// Creates a set of nested frames within the main frame and verifies
// their intersections with the main frame.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
MAYBE_MainFrameIntersectionSameOrigin) {
EXPECT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"/page_load_metrics/blank_with_positioned_iframe_writer.html")));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
waiter->AddMainFrameIntersectionExpectation(gfx::Rect(100, 100, 200, 200));
// Create a 200x200 iframe at 100,100.
EXPECT_TRUE(ExecJs(web_contents,
"createIframeAtRect(\"test\", 100, 100, 200, 200);"));
waiter->Wait();
NavigateIframeToURL(
web_contents, "test",
embedded_test_server()->GetURL(
"/page_load_metrics/blank_with_positioned_iframe_writer.html"));
// Creates the grandchild iframe within the child frame at 10, 10 with
// dimensions 300x300. This frame is clipped by 110 pixels in the bottom and
// right. This translates to an intersection of 110, 110, 190, 190 with the
// main frame.
waiter->AddMainFrameIntersectionExpectation(gfx::Rect(110, 110, 190, 190));
content::RenderFrameHost* child_frame =
content::ChildFrameAt(web_contents->GetPrimaryMainFrame(), 0);
EXPECT_TRUE(
ExecJs(child_frame, "createIframeAtRect(\"test2\", 10, 10, 300, 300);"));
waiter->Wait();
}
// TODO(crbug.com/40916877): Fix this test on Mac.
#if BUILDFLAG(IS_MAC)
#define MAYBE_MainFrameIntersectionCrossOrigin \
DISABLED_MainFrameIntersectionCrossOrigin
#else
#define MAYBE_MainFrameIntersectionCrossOrigin MainFrameIntersectionCrossOrigin
#endif
// Creates a set of nested frames, with a cross origin subframe, within the
// main frame and verifies their intersections with the main frame.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
MAYBE_MainFrameIntersectionCrossOrigin) {
EXPECT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"/page_load_metrics/blank_with_positioned_iframe_writer.html")));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
waiter->AddMainFrameIntersectionExpectation(gfx::Rect(100, 100, 200, 200));
// Create a 200x200 iframe at 100,100.
EXPECT_TRUE(ExecJs(web_contents,
"createIframeAtRect(\"test\", 100, 100, 200, 200);"));
NavigateIframeToURL(
web_contents, "test",
embedded_test_server()->GetURL(
"b.com",
"/page_load_metrics/blank_with_positioned_iframe_writer.html"));
// Wait for the main frame intersection after we have navigated the frame
// to a cross-origin url.
waiter->Wait();
// Change the size of the frame to 150, 150. This tests the cross origin
// code path as the previous wait can flakily pass due to receiving the
// correct intersection before the frame transitions to cross-origin without
// checking that the final computation is consistent.
waiter->AddMainFrameIntersectionExpectation(gfx::Rect(100, 100, 150, 150));
EXPECT_TRUE(ExecJs(web_contents,
"let frame = document.getElementById('test'); "
"frame.width = 150; "
"frame.height = 150; "));
waiter->Wait();
// Creates the grandchild iframe within the child frame at 10, 10 with
// dimensions 300x300. This frame is clipped by 110 pixels in the bottom and
// right. This translates to an intersection of 110, 110, 190, 190 with the
// main frame.
waiter->AddMainFrameIntersectionExpectation(gfx::Rect(110, 110, 140, 140));
content::RenderFrameHost* child_frame =
content::ChildFrameAt(web_contents->GetPrimaryMainFrame(), 0);
EXPECT_TRUE(
ExecJs(child_frame, "createIframeAtRect(\"test2\", 10, 10, 300, 300);"));
waiter->Wait();
}
// TODO(crbug.com/40916877): Fix this test on Mac.
#if BUILDFLAG(IS_MAC)
#define MAYBE_MainFrameIntersectionCrossOriginOutOfView \
DISABLED_MainFrameIntersectionCrossOriginOutOfView
#else
#define MAYBE_MainFrameIntersectionCrossOriginOutOfView \
MainFrameIntersectionCrossOriginOutOfView
#endif
// Creates a set of nested frames, with a cross origin subframe that is out of
// view within the main frame and verifies their intersections with the main
// frame.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
MAYBE_MainFrameIntersectionCrossOriginOutOfView) {
EXPECT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"/page_load_metrics/blank_with_positioned_iframe_writer.html")));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
waiter->AddMainFrameIntersectionExpectation(gfx::Rect(100, 100, 200, 200));
// Create a 200x200 iframe at 100,100.
EXPECT_TRUE(ExecJs(web_contents,
"createIframeAtRect(\"test\", 100, 100, 200, 200);"));
NavigateIframeToURL(
web_contents, "test",
embedded_test_server()->GetURL(
"b.com",
"/page_load_metrics/blank_with_positioned_iframe_writer.html"));
// Wait for the main frame intersection after we have navigated the frame
// to a cross-origin url.
waiter->Wait();
// Creates the grandchild iframe within the child frame outside the parent
// frame's viewport.
waiter->AddMainFrameIntersectionExpectation(gfx::Rect(0, 0, 0, 0));
content::RenderFrameHost* child_frame =
content::ChildFrameAt(web_contents->GetPrimaryMainFrame(), 0);
EXPECT_TRUE(ExecJs(child_frame,
"createIframeAtRect(\"test2\", 5000, 5000, 190, 190);"));
waiter->Wait();
}
// TODO(crbug.com/40916877): Fix this test on Mac.
#if BUILDFLAG(IS_MAC)
#define MAYBE_MainFrameIntersectionCrossOriginScrolled \
DISABLED_MainFrameIntersectionCrossOriginScrolled
#else
#define MAYBE_MainFrameIntersectionCrossOriginScrolled \
MainFrameIntersectionCrossOriginScrolled
#endif
// Creates a set of nested frames, with a cross origin subframe that is out of
// view within the main frame and verifies their intersections with the main
// frame. The out of view frame is then scrolled back into view and the
// intersection is verified.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
MAYBE_MainFrameIntersectionCrossOriginScrolled) {
EXPECT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL(
"/page_load_metrics/blank_with_positioned_iframe_writer.html")));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
waiter->AddMainFrameIntersectionExpectation(gfx::Rect(100, 100, 200, 200));
// Create a 200x200 iframe at 100,100.
EXPECT_TRUE(ExecJs(web_contents,
"createIframeAtRect(\"test\", 100, 100, 200, 200);"));
NavigateIframeToURL(
web_contents, "test",
embedded_test_server()->GetURL(
"b.com",
"/page_load_metrics/blank_with_positioned_iframe_writer.html"));
// Wait for the main frame intersection after we have navigated the frame
// to a cross-origin url.
waiter->Wait();
// Creates the grandchild iframe within the child frame outside the parent
// frame's viewport.
waiter->AddMainFrameIntersectionExpectation(gfx::Rect(0, 0, 0, 0));
content::RenderFrameHost* child_frame =
content::ChildFrameAt(web_contents->GetPrimaryMainFrame(), 0);
EXPECT_TRUE(ExecJs(child_frame,
"createIframeAtRect(\"test2\", 5000, 5000, 190, 190);"));
waiter->Wait();
// Scroll the child frame and verify the grandchild frame's intersection.
// The parent frame is at position 100,100 with dimensions 200x200. The
// child frame after scrolling is positioned at 100,100 within the parent
// frame and is clipped to 100x100. The grand child's main frame document
// position is then 200,200 after the child frame is scrolled.
waiter->AddMainFrameIntersectionExpectation(gfx::Rect(200, 200, 100, 100));
EXPECT_TRUE(ExecJs(child_frame, "window.scroll(4900, 4900); "));
waiter->Wait();
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PageLCPStopsUponInput) {
embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
// Waiter to ensure main content is loaded.
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
waiter->AddPageExpectation(TimingField::kLargestContentfulPaint);
// Waiter to ensure that iframe content is loaded.
auto waiter2 = CreatePageLoadMetricsTestWaiter("waiter2");
waiter2->AddPageExpectation(TimingField::kLoadEvent);
waiter2->AddSubFrameExpectation(TimingField::kLoadEvent);
waiter2->AddPageExpectation(TimingField::kFirstContentfulPaint);
waiter2->AddSubFrameExpectation(TimingField::kFirstContentfulPaint);
waiter2->AddPageExpectation(TimingField::kLargestContentfulPaint);
waiter2->AddSubFrameExpectation(TimingField::kLargestContentfulPaint);
waiter2->AddPageExpectation(TimingField::kFirstInputOrScroll);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/page_load_metrics/click_to_create_iframe.html")));
waiter->Wait();
// Tap in the middle of the button.
content::SimulateMouseClickOrTapElementWithId(web_contents(), "button");
waiter2->Wait();
// LCP is collected only at the end of the page lifecycle. Navigate to flush.
NavigateToUntrackedUrl();
histogram_tester_->ExpectTotalCount(
internal::kHistogramLargestContentfulPaint, 1);
auto all_frames_value =
histogram_tester_
->GetAllSamples(internal::kHistogramLargestContentfulPaint)[0]
.min;
histogram_tester_->ExpectTotalCount(
internal::kHistogramLargestContentfulPaintMainFrame, 1);
auto main_frame_value =
histogram_tester_
->GetAllSamples(
internal::kHistogramLargestContentfulPaintMainFrame)[0]
.min;
// Even though the content on the iframe is larger, the all_frames LCP value
// should match the main frame value because the iframe content was created
// after input in the main frame.
ASSERT_EQ(all_frames_value, main_frame_value);
}
// Regression test for crbug.com/383189046. This test asserts that background
// tabs marked as needs-reload correctly report foreground paint metrics
// when activated and brought to the foreground.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
ActivatedNeedsReloadBackgroundTabsEmitCorrectMetrics) {
ASSERT_TRUE(embedded_test_server()->Start());
// Create a new active tab.
content::WebContents* target_contents = browser()->OpenURL(
{embedded_test_server()->GetURL("/title1.html"), content::Referrer(),
WindowOpenDisposition::NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_TYPED,
false},
/*navigation_handle_callback=*/{});
auto fcp_waiter =
CreatePageLoadMetricsTestWaiter("fcp_waiter", target_contents);
fcp_waiter->AddPageExpectation(page_load_metrics::PageLoadMetricsTestWaiter::
TimingField::kFirstContentfulPaint);
auto navigation_observer =
std::make_unique<content::TestNavigationObserver>(target_contents);
navigation_observer->Wait();
fcp_waiter->Wait();
// Verify foreground fcp metrics are emitted.
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstContentfulPaint,
1);
histogram_tester_->ExpectTotalCount(
internal::kBackgroundHistogramFirstContentfulPaint, 0);
// Activate the original tab, backgrounding the target tab.
browser()->tab_strip_model()->ActivateTabAt(0);
EXPECT_NE(target_contents,
browser()->tab_strip_model()->GetActiveWebContents());
EXPECT_EQ(content::Visibility::HIDDEN, target_contents->GetVisibility());
// Shutdown the target tab's process and tag it as needs-reload.
content::RenderProcessHost* target_process =
target_contents->GetPrimaryMainFrame()->GetProcess();
target_contents->GetController().SetNeedsReload();
EXPECT_TRUE(target_process->FastShutdownIfPossible());
EXPECT_FALSE(target_process->IsInitializedAndNotDead());
EXPECT_TRUE(target_contents->GetController().NeedsReload());
// Activate the target tab.
fcp_waiter = CreatePageLoadMetricsTestWaiter("fcp_waiter", target_contents);
fcp_waiter->AddPageExpectation(page_load_metrics::PageLoadMetricsTestWaiter::
TimingField::kFirstContentfulPaint);
navigation_observer =
std::make_unique<content::TestNavigationObserver>(target_contents);
browser()->tab_strip_model()->ActivateTabAt(1);
navigation_observer->Wait();
fcp_waiter->Wait();
// Verify the appropriate foreground metrics are emitted.
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstContentfulPaint,
2);
histogram_tester_->ExpectTotalCount(
internal::kBackgroundHistogramFirstContentfulPaint, 0);
}
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_MAC)
// The LinkPreview feature is implemented only on desktops, and window
// implementation assumes the Aura for now.
// TODO(crbug.com/305004651): Implement the feature for other platforms and
// enable the following tests on the remaining platforms.
class PageLoadMetricsPreviewBrowserTest : public PageLoadMetricsBrowserTest {
public:
PageLoadMetricsPreviewBrowserTest() {
helper_ = std::make_unique<test::PreviewTestHelper>(
base::BindRepeating(&PageLoadMetricsPreviewBrowserTest::web_contents,
base::Unretained(this)));
}
protected:
std::unique_ptr<test::PreviewTestHelper> helper_;
};
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_MAC)
class PageLoadMetricsBrowserTestTerminatedPage
: public PageLoadMetricsBrowserTest {
protected:
void SetUpOnMainThread() override {
PageLoadMetricsBrowserTest::SetUpOnMainThread();
ASSERT_TRUE(embedded_test_server()->Start());
}
public:
content::WebContents* OpenTabAndNavigate() {
content::OpenURLParams page(embedded_test_server()->GetURL("/title1.html"),
content::Referrer(),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui::PAGE_TRANSITION_TYPED, false);
content::WebContents* contents =
browser()->OpenURL(page, /*navigation_handle_callback=*/{});
std::unique_ptr<PageLoadMetricsTestWaiter> waiter =
CreatePageLoadMetricsTestWaiter("lcp_waiter", contents);
waiter->AddPageExpectation(page_load_metrics::PageLoadMetricsTestWaiter::
TimingField::kLargestContentfulPaint);
content::TestNavigationObserver observer(contents);
observer.set_expected_initial_url(page.url);
observer.Wait();
// This is to wait for LCP to be observed on browser side.
waiter->Wait();
return contents;
}
double GetLCPTimeFromEmittedLCPEntry(content::WebContents* contents) {
content::EvalJsResult lcp_time =
EvalJs(contents, ScriptForGettingLCPTimeFromEmittedLCPEntry());
return lcp_time.ExtractDouble();
}
void AddNewTab() {
std::unique_ptr<content::WebContents> web_contents_to_add =
content::WebContents::Create(
content::WebContents::CreateParams(browser()->profile()));
web_contents_to_add->GetController().LoadURL(
embedded_test_server()->GetURL("/title1.html"), content::Referrer(),
::ui::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string());
auto* tab_strip_model = browser()->tab_strip_model();
tab_strip_model->AddWebContents(std::move(web_contents_to_add), -1,
::ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
AddTabTypes::ADD_ACTIVE);
}
void DiscardTab(content::WebContents* contents) {
resource_coordinator::TabLifecycleUnitExternal::FromWebContents(contents)
->DiscardTab(mojom::LifecycleUnitDiscardReason::URGENT);
}
void CloseTab(content::WebContents* contents) {
auto* tab_strip_model = browser()->tab_strip_model();
// Get the total count of tabs.
int tab_count = tab_strip_model->count();
// Get the tab index of the given WebContents.
int tab_index = tab_strip_model->GetIndexOfWebContents(contents);
// Expect the tab index of the given WebContents is found.
EXPECT_NE(tab_index, TabStripModel::kNoTab);
// Close the tab corresponding to the given WebContents.
tab_strip_model->CloseWebContentsAt(tab_index,
TabCloseTypes::CLOSE_USER_GESTURE);
// Verify tab is closed.
EXPECT_EQ(tab_strip_model->count(), tab_count - 1);
}
std::string ScriptForGettingLCPTimeFromEmittedLCPEntry() {
return R"(
(async () => {
return await new Promise(resolve => {
(new PerformanceObserver(list => {
const entries = list.getEntries();
for (let entry of entries) {
if (entry) {
resolve(entry.startTime);
}
}
}))
.observe({
type: 'largest-contentful-paint',
buffered: true
});
})
})())";
}
};
class PageLoadMetricsBrowserTestDiscardedPage
: public PageLoadMetricsBrowserTestTerminatedPage,
public ::testing::WithParamInterface<bool> {};
IN_PROC_BROWSER_TEST_P(PageLoadMetricsBrowserTestDiscardedPage,
UkmIsRecordedForDiscardedTabPage) {
if (base::FeatureList::IsEnabled(features::kWebContentsDiscard)) {
GTEST_SKIP() << "Page load metrics are reported when the tab is closed.";
}
// Open a new foreground tab and navigate.
content::WebContents* contents = OpenTabAndNavigate();
// Wait for LCP emission and observation.
double lcp_time = GetLCPTimeFromEmittedLCPEntry(contents);
// Background current tab by adding a new tab if provided param is true.
if (GetParam()) {
// Add a new tab.
AddNewTab();
// Verify the first tab is backgrounded.
EXPECT_NE(contents, browser()->tab_strip_model()->GetActiveWebContents());
}
// Discard tab.
DiscardTab(contents);
// Verify tab is discarded.
EXPECT_TRUE(
browser()->tab_strip_model()->GetWebContentsAt(1)->WasDiscarded());
// Verify page load metric is recorded.
EXPECT_NEAR(
GetUKMPageLoadMetric(
PageLoad::kPaintTiming_NavigationToLargestContentfulPaint2Name),
lcp_time, 10);
}
INSTANTIATE_TEST_SUITE_P(DiscardedPages,
PageLoadMetricsBrowserTestDiscardedPage,
testing::Bool());
class PageLoadMetricsBrowserTestClosedPage
: public PageLoadMetricsBrowserTestTerminatedPage,
public ::testing::WithParamInterface<bool> {};
IN_PROC_BROWSER_TEST_P(PageLoadMetricsBrowserTestClosedPage,
UkmIsRecordedForClosedTabPage) {
// Open a new foreground tab and navigate. The new tab would be of index 1
// which would be used below in verifying the tab is discarded.
content::WebContents* contents = OpenTabAndNavigate();
// Wait for LCP emission and observation. This is to ensure there is an LCP
// entry to report at the time of closing the page.
double lcp_time = GetLCPTimeFromEmittedLCPEntry(contents);
// Background current tab by adding a new tab if provided param is true.
if (GetParam()) {
// Add a new tab.
AddNewTab();
// Verify the tab is backgrounded.
EXPECT_NE(contents, browser()->tab_strip_model()->GetActiveWebContents());
}
// close tab.
CloseTab(contents);
// Verify page load metric is recorded.
EXPECT_NEAR(
GetUKMPageLoadMetric(
PageLoad::kPaintTiming_NavigationToLargestContentfulPaint2Name),
lcp_time, 10);
}
INSTANTIATE_TEST_SUITE_P(ClosedPages,
PageLoadMetricsBrowserTestClosedPage,
testing::Bool());
// This test is to verify page load metrics are recorded in case when the
// render process is shut down.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestTerminatedPage,
UkmIsRecordedWhenRenderProcessShutsDown) {
content::WebContents* contents = OpenTabAndNavigate();
// Wait for LCP emission and observation.
double lcp_time = GetLCPTimeFromEmittedLCPEntry(contents);
content::RenderProcessHost* process = RenderFrameHost()->GetProcess();
// Shut down render process.
content::RenderProcessHostWatcher crash_observer(
process, content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
EXPECT_TRUE(process->Shutdown(content::RESULT_CODE_KILLED));
crash_observer.Wait();
EXPECT_FALSE(RenderFrameHost()->IsRenderFrameLive());
// Verify page load metric is recorded.
EXPECT_NEAR(
GetUKMPageLoadMetric(
PageLoad::kPaintTiming_NavigationToLargestContentfulPaint2Name),
lcp_time, 10);
}
// This class is used to verify page load metrics are recorded in case of
// crashes of different kinds. These crashes are simulated by navigating to the
// chrome debug urls.
class PageLoadMetricsBrowserTestRendererCrashedPage
: public PageLoadMetricsBrowserTestTerminatedPage,
public ::testing::WithParamInterface<const char*> {};
IN_PROC_BROWSER_TEST_P(PageLoadMetricsBrowserTestRendererCrashedPage,
UkmIsRecordedForCrashedTabPage) {
// Open a new foreground tab and navigate.
content::WebContents* contents = OpenTabAndNavigate();
// The back/forward cache is disabled because page load metrics can also be
// recorded when entering into the bfcache. We want to test that page load
// metrics are recorded via the PageLoadTracker destructor which is called in
// all crash cases.
content::DisableBackForwardCacheForTesting(
contents, content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
// Wait for LCP emission and observation. This is to ensure there is an LCP
// entry to report at the time of killing the page.
double lcp_time = GetLCPTimeFromEmittedLCPEntry(contents);
// Crash the page.
content::RenderProcessHostWatcher crash_observer(
RenderFrameHost()->GetProcess(),
content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
browser()->OpenURL(
content::OpenURLParams(GURL(GetParam()), content::Referrer(),
WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_TYPED, false),
/*navigation_handle_callback=*/{});
crash_observer.Wait();
EXPECT_FALSE(crash_observer.did_exit_normally());
EXPECT_TRUE(web_contents()->IsCrashed());
// Verify page load metric is recorded.
EXPECT_NEAR(
GetUKMPageLoadMetric(
PageLoad::kPaintTiming_NavigationToLargestContentfulPaint2Name),
lcp_time, 10);
}
INSTANTIATE_TEST_SUITE_P(RendererCrashCases,
PageLoadMetricsBrowserTestRendererCrashedPage,
testing::ValuesIn({blink::kChromeUIKillURL,
blink::kChromeUICrashURL}));
// Similar to the crashes above, this test verifies page load metrics are
// recorded in case severe errors that don't actually crash the
// renderer process (e.g. only the GPU process may crash), but rather cause
// the renderer to be terminated.
class PageLoadMetricsBrowserTestNoRendererCrashedPage
: public PageLoadMetricsBrowserTestTerminatedPage,
public ::testing::WithParamInterface<const char*> {};
IN_PROC_BROWSER_TEST_P(PageLoadMetricsBrowserTestNoRendererCrashedPage,
UkmIsRecordedForCrashedTabPage) {
base::test::ScopedCommandLine scoped_command_line;
if (GetParam() == blink::kChromeUIGpuCrashURL) {
// This flag must be enabled to log metrics for blink::kChromeUIGpuCrashURL.
scoped_command_line.GetProcessCommandLine()->AppendSwitch(
switches::kEnableGpuBenchmarking);
}
// Open a new foreground tab and navigate.
content::WebContents* contents = OpenTabAndNavigate();
// The back/forward cache is disabled because page load metrics can also be
// recorded when entering into the bfcache. We want to test that page load
// metrics are recorded via the PageLoadTracker destructor which is called in
// all crash cases.
content::DisableBackForwardCacheForTesting(
contents, content::BackForwardCache::TEST_REQUIRES_NO_CACHING);
// Wait for LCP emission and observation. This is to ensure there is an LCP
// entry to report at the time of killing the page.
double lcp_time = GetLCPTimeFromEmittedLCPEntry(contents);
// Wait for the destruction of the RenderProcessHost, which is triggered by
// the navigation to the chrome debug url; then assert that we've navigated
// to the correct URL.
content::RenderProcessHostWatcher destruction_observer(contents,
content::RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);
browser()->OpenURL(
content::OpenURLParams(GURL(GetParam()), content::Referrer(),
WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_TYPED, false),
/*navigation_handle_callback=*/{});
destruction_observer.Wait();
EXPECT_TRUE(web_contents() == contents);
EXPECT_FALSE(contents->IsCrashed());
EXPECT_EQ(GURL(GetParam()), contents->GetLastCommittedURL());
EXPECT_FALSE(contents->HasUncommittedNavigationInPrimaryMainFrame());
// Verify page load metric is recorded.
EXPECT_NEAR(
GetUKMPageLoadMetric(
PageLoad::kPaintTiming_NavigationToLargestContentfulPaint2Name),
lcp_time, 10);
}
INSTANTIATE_TEST_SUITE_P(
NoRendererCrashCases,
PageLoadMetricsBrowserTestNoRendererCrashedPage,
testing::ValuesIn({blink::kChromeUIGpuCrashURL,
blink::kChromeUINetworkErrorURL,
blink::kChromeUIProcessInternalsURL}));
// Test is flaky. https://crbug.com/1260953
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) || \
BUILDFLAG(IS_WIN)
#define MAYBE_PageLCPAnimatedImage DISABLED_PageLCPAnimatedImage
#else
#define MAYBE_PageLCPAnimatedImage PageLCPAnimatedImage
#endif
// Tests that an animated image's reported LCP values are smaller than its load
// times, when the feature flag for animated image reporting is enabled.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestAnimatedLCP,
MAYBE_PageLCPAnimatedImage) {
test_animated_image_lcp(/*smaller=*/true, /*animated=*/true);
}
// Tests that a non-animated image's reported LCP values are larger than its
// load times, when the feature flag for animated image reporting is enabled.
// TODO(crbug.com/40218474): Flaky on Mac/Linux.
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
#define MAYBE_PageLCPNonAnimatedImage DISABLED_PageLCPNonAnimatedImage
#else
#define MAYBE_PageLCPNonAnimatedImage PageLCPNonAnimatedImage
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestAnimatedLCP,
MAYBE_PageLCPNonAnimatedImage) {
test_animated_image_lcp(/*smaller=*/false, /*animated=*/false);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, FirstInputDelayFromClick) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
auto waiter2 = CreatePageLoadMetricsTestWaiter("waiter2");
waiter2->AddPageExpectation(TimingField::kLoadEvent);
waiter2->AddPageExpectation(TimingField::kFirstContentfulPaint);
waiter2->AddPageExpectation(TimingField::kFirstInputDelay);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("/page_load_metrics/click.html")));
waiter->Wait();
content::SimulateMouseClickAt(
browser()->tab_strip_model()->GetActiveWebContents(), 0,
blink::WebMouseEvent::Button::kLeft, gfx::Point(100, 100));
waiter2->Wait();
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstInputDelay, 1);
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstInputTimestamp,
1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, SameOriginNavigation) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL kUrl1 = embedded_test_server()->GetURL("a.com", "/title1.html");
GURL kUrl2 = embedded_test_server()->GetURL("a.com", "/title2.html");
auto waiter1 = CreatePageLoadMetricsTestWaiter("waiter1");
waiter1->AddPageExpectation(TimingField::kLargestContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kUrl1));
waiter1->Wait();
auto waiter2 = CreatePageLoadMetricsTestWaiter("waiter2");
waiter2->AddPageExpectation(TimingField::kLargestContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kUrl2));
waiter2->Wait();
NavigateToUntrackedUrl();
VerifyNavigationMetrics({kUrl1, kUrl2});
// Navigation from about:blank to kUrl1 is a cross origin navigation.
histogram_tester_->ExpectTotalCount(
"PageLoad.Clients.CrossOrigin.FirstContentfulPaint", 1);
histogram_tester_->ExpectTotalCount(
"PageLoad.Clients.CrossOrigin.LargestContentfulPaint", 1);
// Navigation from kUrl1 to kUrl2 is a same origin navigation.
histogram_tester_->ExpectTotalCount(
"PageLoad.Clients.SameOrigin.FirstContentfulPaint", 1);
histogram_tester_->ExpectTotalCount(
"PageLoad.Clients.SameOrigin.LargestContentfulPaint", 1);
}
// TODO(crbug.com/334416161): Re-enable this test on Windows.
#if BUILDFLAG(IS_WIN)
#define MAYBE_CrossOriginNavigation DISABLED_CrossOriginNavigation
#else
#define MAYBE_CrossOriginNavigation CrossOriginNavigation
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
MAYBE_CrossOriginNavigation) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL kUrl1 = embedded_test_server()->GetURL("a.com", "/title1.html");
GURL kUrl2 = embedded_test_server()->GetURL("b.com", "/title1.html");
auto waiter1 = CreatePageLoadMetricsTestWaiter("waiter1");
waiter1->AddPageExpectation(TimingField::kFirstContentfulPaint);
waiter1->AddPageExpectation(TimingField::kLargestContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kUrl1));
waiter1->Wait();
auto waiter2 = CreatePageLoadMetricsTestWaiter("waiter2");
waiter2->AddPageExpectation(TimingField::kFirstContentfulPaint);
waiter1->AddPageExpectation(TimingField::kLargestContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kUrl2));
waiter2->Wait();
NavigateToUntrackedUrl();
VerifyNavigationMetrics({kUrl1, kUrl2});
// Navigation from about:blank to kUrl1 and navigation from kUrl1 to kUrl2 are
// cross origin navigations.
histogram_tester_->ExpectTotalCount(
"PageLoad.Clients.CrossOrigin.FirstContentfulPaint", 2);
histogram_tester_->ExpectTotalCount(
"PageLoad.Clients.CrossOrigin.LargestContentfulPaint", 2);
}
class PageLoadMetricsBrowserTestWithFencedFrames
: public PageLoadMetricsBrowserTest {
public:
PageLoadMetricsBrowserTestWithFencedFrames()
: https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
https_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
https_server_.AddDefaultHandlers(GetChromeTestDataDir());
}
~PageLoadMetricsBrowserTestWithFencedFrames() override = default;
protected:
net::EmbeddedTestServer& https_server() { return https_server_; }
private:
net::EmbeddedTestServer https_server_;
content::test::FencedFrameTestHelper helper_;
};
// TODO(crbug.com/334416161): Re-enable this test on Windows.
#if BUILDFLAG(IS_WIN)
#define MAYBE_PageLoadPrivacySandboxAdsFencedFramesMetrics \
DISABLED_PageLoadPrivacySandboxAdsFencedFramesMetrics
#else
#define MAYBE_PageLoadPrivacySandboxAdsFencedFramesMetrics \
PageLoadPrivacySandboxAdsFencedFramesMetrics
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestWithFencedFrames,
MAYBE_PageLoadPrivacySandboxAdsFencedFramesMetrics) {
ASSERT_TRUE(https_server().Start());
static constexpr char
kHistogramPrivacySandboxAdsNavigationToFirstContentfulPaint[] =
"PageLoad.Clients.PrivacySandboxAds.PaintTiming."
"NavigationToFirstContentfulPaint.FencedFrames";
// Not recorded as fenced frame is not created.
auto waiter1 = CreatePageLoadMetricsTestWaiter("waiter1");
waiter1->AddPageExpectation(TimingField::kFirstContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), https_server().GetURL("a.test", "/title1.html")));
waiter1->Wait();
histogram_tester_->ExpectTotalCount(
kHistogramPrivacySandboxAdsNavigationToFirstContentfulPaint, 0);
// Recorded as fenced frame is created.
auto waiter2 = CreatePageLoadMetricsTestWaiter("waiter2");
waiter2->AddPageExpectation(TimingField::kFirstContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
https_server().GetURL("c.test", "/fenced_frames/basic_title.html")));
waiter2->Wait();
histogram_tester_->ExpectTotalCount(
kHistogramPrivacySandboxAdsNavigationToFirstContentfulPaint, 1);
}
class PageLoadMetricsBrowserTestWithBackForwardCache
: public PageLoadMetricsBrowserTest {
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
PageLoadMetricsBrowserTest::SetUpCommandLine(command_line);
feature_list.InitWithFeaturesAndParameters(
content::GetDefaultEnabledBackForwardCacheFeaturesForTesting(),
content::GetDefaultDisabledBackForwardCacheFeaturesForTesting());
}
private:
base::test::ScopedFeatureList feature_list;
};
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestWithBackForwardCache,
BackForwardCacheEvent) {
ASSERT_TRUE(embedded_test_server()->Start());
auto url1 = embedded_test_server()->GetURL("a.com", "/title1.html");
auto url2 = embedded_test_server()->GetURL("b.com", "/title1.html");
// Go to URL1.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url1));
histogram_tester_->ExpectBucketCount(
internal::kHistogramBackForwardCacheEvent,
internal::PageLoadBackForwardCacheEvent::kEnterBackForwardCache, 0);
histogram_tester_->ExpectBucketCount(
internal::kHistogramBackForwardCacheEvent,
internal::PageLoadBackForwardCacheEvent::kRestoreFromBackForwardCache, 0);
// Go to URL2. The previous page (URL1) is put into the back-forward cache.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url2));
histogram_tester_->ExpectBucketCount(
internal::kHistogramBackForwardCacheEvent,
internal::PageLoadBackForwardCacheEvent::kEnterBackForwardCache, 1);
histogram_tester_->ExpectBucketCount(
internal::kHistogramBackForwardCacheEvent,
internal::PageLoadBackForwardCacheEvent::kRestoreFromBackForwardCache, 0);
// Go back to URL1. The previous page (URL2) is put into the back-forward
// cache.
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
web_contents->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(web_contents));
histogram_tester_->ExpectBucketCount(
internal::kHistogramBackForwardCacheEvent,
internal::PageLoadBackForwardCacheEvent::kEnterBackForwardCache, 2);
// For now UmaPageLoadMetricsObserver::OnEnterBackForwardCache returns
// STOP_OBSERVING, OnRestoreFromBackForward is never reached.
//
// TODO(hajimehoshi): Update this when the UmaPageLoadMetricsObserver
// continues to observe after entering to back-forward cache.
histogram_tester_->ExpectBucketCount(
internal::kHistogramBackForwardCacheEvent,
internal::PageLoadBackForwardCacheEvent::kRestoreFromBackForwardCache, 0);
}
// Test UseCounter UKM features observed when a page is in the BFCache and is
// evicted from it.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestWithBackForwardCache,
UseCounterUkmFeaturesLoggedOnBFCacheEviction) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url = embedded_test_server()->GetURL(
"/page_load_metrics/use_counter_features.html");
{
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
MakeComponentFullscreen("testvideo");
waiter->Wait();
}
NavigateToUntrackedUrl();
// Force the BFCache to evict all entries. This should cause the
// UseCounter histograms to be logged.
browser()
->tab_strip_model()
->GetActiveWebContents()
->GetController()
.GetBackForwardCache()
.Flush();
// Navigate to a new URL. This gives the various page load tracking
// mechanisms time to process the BFCache evictions.
auto url1 = embedded_test_server()->GetURL("a.com", "/title1.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url1));
const auto& entries = test_ukm_recorder_->GetEntriesByName(
ukm::builders::Blink_UseCounter::kEntryName);
EXPECT_THAT(entries, SizeIs(4));
std::vector<int64_t> ukm_features;
for (const ukm::mojom::UkmEntry* entry : entries) {
test_ukm_recorder_->ExpectEntryMetric(
entry, ukm::builders::Blink_UseCounter::kIsMainFrameFeatureName, 1);
const auto* metric = test_ukm_recorder_->GetEntryMetric(
entry, ukm::builders::Blink_UseCounter::kFeatureName);
DCHECK(metric);
ukm_features.push_back(*metric);
}
EXPECT_THAT(ukm_features,
UnorderedElementsAre(
static_cast<int64_t>(WebFeature::kPageVisits),
static_cast<int64_t>(WebFeature::kFullscreenSecureOrigin),
static_cast<int64_t>(WebFeature::kNavigatorVibrate),
static_cast<int64_t>(WebFeature::kPageVisits)));
// Check histogram counts.
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kPageVisits), 2);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kFullscreenSecureOrigin), 1);
histogram_tester_->ExpectBucketCount(
"Blink.UseCounter.Features",
static_cast<int32_t>(WebFeature::kNavigatorVibrate), 1);
}
class NavigationPageLoadMetricsBrowserTest
: public PageLoadMetricsBrowserTest,
public ::testing::WithParamInterface<std::string> {
public:
NavigationPageLoadMetricsBrowserTest() = default;
~NavigationPageLoadMetricsBrowserTest() override = default;
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
// TODO(crbug.com/40188113): This test used an experiment param (which no
// longer exists) to suppress the metrics send timer. If and when the test
// is re-enabled, it should be updated to use a different mechanism.
PageLoadMetricsBrowserTest::SetUpCommandLine(command_line);
}
};
IN_PROC_BROWSER_TEST_P(NavigationPageLoadMetricsBrowserTest, FirstInputDelay) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url1(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url2(embedded_test_server()->GetURL(
(GetParam() == "SameSite") ? "a.com" : "b.com", "/title2.html"));
EXPECT_THAT(histogram_tester_->GetAllSamples(
internal::kHistogramFirstContentfulPaint),
testing::IsEmpty());
auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
waiter->AddPageExpectation(TimingField::kFirstInputDelay);
// 1) Navigate to url1.
EXPECT_TRUE(content::NavigateToURL(web_contents(), url1));
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstInputDelay, 0);
content::RenderFrameHost* rfh_a = RenderFrameHost();
content::RenderProcessHost* rfh_a_process = rfh_a->GetProcess();
// We should wait for the main frame's hit-test data to be ready before
// sending the click event below to avoid flakiness.
content::WaitForHitTestData(web_contents()->GetPrimaryMainFrame());
// Ensure the compositor thread is ready for mouse events.
content::MainThreadFrameObserver frame_observer(
web_contents()->GetRenderWidgetHostView()->GetRenderWidgetHost());
frame_observer.Wait();
// Simulate mouse click. FirstInputDelay won't get updated immediately.
content::SimulateMouseClickAt(web_contents(), 0,
blink::WebMouseEvent::Button::kLeft,
gfx::Point(100, 100));
// Run a Performance Observer to ensure the renderer receives the click
EXPECT_TRUE(content::ExecJs(web_contents(), R"(
(async () => {
await new Promise(resolve => {
new PerformanceObserver(e => {
e.getEntries().forEach(entry => {
resolve(true);
})
}).observe({type: 'first-input', buffered: true});
})})())"));
base::RunLoop().RunUntilIdle();
content::FetchHistogramsFromChildProcesses();
waiter->Wait();
// 2) Immediately navigate to url2.
if (GetParam() == "CrossSiteRendererInitiated") {
EXPECT_TRUE(content::NavigateToURLFromRenderer(web_contents(), url2));
} else {
EXPECT_TRUE(content::NavigateToURL(web_contents(), url2));
}
content::FetchHistogramsFromChildProcesses();
if (GetParam() != "CrossSiteBrowserInitiated" ||
rfh_a_process == RenderFrameHost()->GetProcess()) {
// - For "SameSite" case, since the old and new RenderFrame either share a
// process (with RenderDocument/back-forward cache) or the RenderFrame is
// reused the metrics update will be sent to the browser during commit and
// won't get ignored, successfully updating the FirstInputDelay histogram.
// - For "CrossSiteRendererInitiated" case, FirstInputDelay was sent when
// the renderer-initiated navigation started on the old frame.
// - For "CrossSiteBrowserInitiated" case, if the old and new RenderFrame
// share a process, the metrics update will be sent to the browser during
// commit and won't get ignored, successfully updating the histogram.
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstInputDelay, 1);
} else {
// Note that in some cases the metrics might flakily get updated in time,
// before the browser changed the current RFH. So, we can neither expect it
// to be 0 all the time or 1 all the time.
// TODO(crbug.com/40157795): Support updating metrics consistently on
// cross-RFH cross-process navigations.
}
}
std::vector<std::string> NavigationPageLoadMetricsBrowserTestTestValues() {
return {"SameSite", "CrossSiteRendererInitiated",
"CrossSiteBrowserInitiated"};
}
INSTANTIATE_TEST_SUITE_P(
All,
NavigationPageLoadMetricsBrowserTest,
testing::ValuesIn(NavigationPageLoadMetricsBrowserTestTestValues()));
class PrerenderPageLoadMetricsBrowserTest : public PageLoadMetricsBrowserTest {
public:
PrerenderPageLoadMetricsBrowserTest()
: prerender_helper_(base::BindRepeating(
&PrerenderPageLoadMetricsBrowserTest::web_contents,
base::Unretained(this))) {}
void SetUp() override {
prerender_helper_.RegisterServerRequestMonitor(embedded_test_server());
PageLoadMetricsBrowserTest::SetUp();
}
protected:
content::test::PrerenderTestHelper prerender_helper_;
};
IN_PROC_BROWSER_TEST_F(PrerenderPageLoadMetricsBrowserTest, PrerenderEvent) {
using page_load_metrics::internal::kPageLoadPrerender2Event;
using page_load_metrics::internal::PageLoadPrerenderEvent;
ASSERT_TRUE(embedded_test_server()->Start());
// Navigate to an initial page.
auto initial_url = embedded_test_server()->GetURL("/empty.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
histogram_tester_->ExpectBucketCount(
kPageLoadPrerender2Event,
PageLoadPrerenderEvent::kNavigationInPrerenderedMainFrame, 0);
histogram_tester_->ExpectBucketCount(
kPageLoadPrerender2Event,
PageLoadPrerenderEvent::kPrerenderActivationNavigation, 0);
// Start a prerender.
GURL prerender_url = embedded_test_server()->GetURL("/title2.html");
prerender_helper_.AddPrerender(prerender_url);
histogram_tester_->ExpectBucketCount(
kPageLoadPrerender2Event,
PageLoadPrerenderEvent::kNavigationInPrerenderedMainFrame, 1);
histogram_tester_->ExpectBucketCount(
kPageLoadPrerender2Event,
PageLoadPrerenderEvent::kPrerenderActivationNavigation, 0);
// Activate.
prerender_helper_.NavigatePrimaryPage(prerender_url);
histogram_tester_->ExpectBucketCount(
kPageLoadPrerender2Event,
PageLoadPrerenderEvent::kNavigationInPrerenderedMainFrame, 1);
histogram_tester_->ExpectBucketCount(
kPageLoadPrerender2Event,
PageLoadPrerenderEvent::kPrerenderActivationNavigation, 1);
}
IN_PROC_BROWSER_TEST_F(PrerenderPageLoadMetricsBrowserTest,
PrerenderingDoNotRecordUKM) {
ASSERT_TRUE(embedded_test_server()->Start());
// Navigate to an initial page.
auto initial_url = embedded_test_server()->GetURL("/empty.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
// Load a page in the prerender.
GURL prerender_url = embedded_test_server()->GetURL("/title2.html");
content::FrameTreeNodeId host_id =
prerender_helper_.AddPrerender(prerender_url);
content::test::PrerenderHostObserver host_observer(*web_contents(), host_id);
EXPECT_FALSE(host_observer.was_activated());
auto entries =
test_ukm_recorder_->GetMergedEntriesByName(PageLoad::kEntryName);
EXPECT_EQ(0u, entries.size());
// Activate.
prerender_helper_.NavigatePrimaryPage(prerender_url);
EXPECT_TRUE(host_observer.was_activated());
entries = test_ukm_recorder_->GetMergedEntriesByName(PageLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
}
enum BackForwardCacheStatus { kDisabled = 0, kEnabled = 1 };
class PageLoadMetricsBackForwardCacheBrowserTest
: public PageLoadMetricsBrowserTest,
public testing::WithParamInterface<BackForwardCacheStatus> {
public:
PageLoadMetricsBackForwardCacheBrowserTest() {
if (GetParam() == BackForwardCacheStatus::kEnabled) {
// Enable BackForwardCache.
feature_list_.InitWithFeaturesAndParameters(
content::GetBasicBackForwardCacheFeatureForTesting(),
content::GetDefaultDisabledBackForwardCacheFeaturesForTesting());
} else {
feature_list_.InitAndDisableFeature(features::kBackForwardCache);
DCHECK(!content::BackForwardCache::IsBackForwardCacheFeatureEnabled());
}
}
static std::string DescribeParams(
const testing::TestParamInfo<ParamType>& info) {
return info.param ? "BFCacheEnabled" : "BFCacheDisabled";
}
void VerifyPageEndReasons(const std::vector<PageEndReason>& reasons,
const GURL& url,
bool is_bfcache_enabled);
int64_t CountForMetricForURL(std::string_view entry_name,
std::string_view metric_name,
const GURL& url);
void ExpectNewForegroundDuration(const GURL& url, bool expect_bfcache);
private:
int64_t expected_page_load_foreground_durations_ = 0;
int64_t expected_bfcache_foreground_durations_ = 0;
base::test::ScopedFeatureList feature_list_;
};
// Verifies the page end reasons are as we expect. This means that the first
// page end reason is always recorded in Navigation.PageEndReason3, and
// subsequent reasons are recorded in HistoryNavigation.PageEndReason if bfcache
// is enabled, or Navigation.PageEndReason3 if not.
void PageLoadMetricsBackForwardCacheBrowserTest::VerifyPageEndReasons(
const std::vector<PageEndReason>& reasons,
const GURL& url,
bool is_bfcache_enabled) {
unsigned int reason_index = 0;
for (const ukm::mojom::UkmEntry* entry :
test_ukm_recorder_->GetEntriesByName(PageLoad::kEntryName)) {
auto* source = test_ukm_recorder_->GetSourceForSourceId(entry->source_id);
if (source->url() != url)
continue;
if (test_ukm_recorder_->EntryHasMetric(
entry, PageLoad::kNavigation_PageEndReason3Name)) {
if (is_bfcache_enabled) {
// If bfcache is on then only one of these should exist, so the index
// should be zero.
EXPECT_EQ(reason_index, 0U);
}
ASSERT_LT(reason_index, reasons.size());
test_ukm_recorder_->ExpectEntryMetric(
entry, PageLoad::kNavigation_PageEndReason3Name,
reasons[reason_index++]);
}
}
if (is_bfcache_enabled) {
EXPECT_EQ(reason_index, 1U);
} else {
EXPECT_EQ(reason_index, reasons.size());
}
for (const ukm::mojom::UkmEntry* entry :
test_ukm_recorder_->GetEntriesByName(HistoryNavigation::kEntryName)) {
auto* source = test_ukm_recorder_->GetSourceForSourceId(entry->source_id);
if (source->url() != url)
continue;
if (test_ukm_recorder_->EntryHasMetric(
entry, HistoryNavigation::
kPageEndReasonAfterBackForwardCacheRestoreName)) {
EXPECT_TRUE(is_bfcache_enabled);
ASSERT_LT(reason_index, reasons.size());
test_ukm_recorder_->ExpectEntryMetric(
entry,
HistoryNavigation::kPageEndReasonAfterBackForwardCacheRestoreName,
reasons[reason_index++]);
}
}
// Should have been through all the reasons.
EXPECT_EQ(reason_index, reasons.size());
}
int64_t PageLoadMetricsBackForwardCacheBrowserTest::CountForMetricForURL(
std::string_view entry_name,
std::string_view metric_name,
const GURL& url) {
int64_t count = 0;
for (const ukm::mojom::UkmEntry* entry :
test_ukm_recorder_->GetEntriesByName(entry_name)) {
auto* source = test_ukm_recorder_->GetSourceForSourceId(entry->source_id);
if (source->url() != url)
continue;
if (test_ukm_recorder_->EntryHasMetric(entry, metric_name)) {
count++;
}
}
return count;
}
IN_PROC_BROWSER_TEST_P(PageLoadMetricsBackForwardCacheBrowserTest,
LogsPageEndReasons) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
bool back_forward_cache_enabled = GetParam() == kEnabled;
// Navigate to A.
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url_a));
content::RenderFrameHostWrapper rfh_a(web_contents()->GetPrimaryMainFrame());
// Navigate to B.
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));
if (back_forward_cache_enabled) {
ASSERT_EQ(rfh_a->GetLifecycleState(),
content::RenderFrameHost::LifecycleState::kInBackForwardCache);
}
std::vector<PageEndReason> expected_reasons_a;
expected_reasons_a.push_back(page_load_metrics::END_NEW_NAVIGATION);
VerifyPageEndReasons(expected_reasons_a, url_a, back_forward_cache_enabled);
// Go back to A, restoring it from the back-forward cache (if enabled)
web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(web_contents()));
// Navigate to B again - this should trigger the
// BackForwardCachePageLoadMetricsObserver for A (if enabled)
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));
expected_reasons_a.push_back(page_load_metrics::END_NEW_NAVIGATION);
VerifyPageEndReasons(expected_reasons_a, url_a, back_forward_cache_enabled);
// Go back to A, restoring it from the back-forward cache (again)
web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(web_contents()));
// Navigate to B using GoForward() to verify the correct page end reason
// is stored for A.
web_contents()->GetController().GoForward();
EXPECT_TRUE(WaitForLoadStop(web_contents()));
expected_reasons_a.push_back(page_load_metrics::END_FORWARD_BACK);
VerifyPageEndReasons(expected_reasons_a, url_a, back_forward_cache_enabled);
}
void PageLoadMetricsBackForwardCacheBrowserTest::ExpectNewForegroundDuration(
const GURL& url,
bool expect_bfcache) {
if (expect_bfcache) {
expected_bfcache_foreground_durations_++;
} else {
expected_page_load_foreground_durations_++;
}
int64_t bf_count = CountForMetricForURL(
HistoryNavigation::kEntryName,
HistoryNavigation::kForegroundDurationAfterBackForwardCacheRestoreName,
url);
int64_t pl_count = CountForMetricForURL(
PageLoad::kEntryName, PageLoad::kPageTiming_ForegroundDurationName, url);
EXPECT_EQ(bf_count, expected_bfcache_foreground_durations_);
EXPECT_EQ(pl_count, expected_page_load_foreground_durations_);
}
IN_PROC_BROWSER_TEST_P(PageLoadMetricsBackForwardCacheBrowserTest,
LogsBasicPageForegroundDuration) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
bool back_forward_cache_enabled = GetParam() == kEnabled;
// Navigate to A.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_a));
content::RenderFrameHostWrapper rfh_a(web_contents()->GetPrimaryMainFrame());
// Navigate to B.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));
if (back_forward_cache_enabled) {
ASSERT_EQ(rfh_a->GetLifecycleState(),
content::RenderFrameHost::LifecycleState::kInBackForwardCache);
}
// Verify a new foreground duration - this one shouldn't be logged by the
// bfcache metrics regardless of bfcache being enabled or not.
ExpectNewForegroundDuration(url_a, /*expect_bfcache=*/false);
// Go back to A, restoring it from the back-forward cache (if enabled)
web_contents()->GetController().GoBack();
ASSERT_TRUE(WaitForLoadStop(web_contents()));
// Navigate to B again - this should trigger the
// BackForwardCachePageLoadMetricsObserver for A (if enabled)
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));
ExpectNewForegroundDuration(url_a, back_forward_cache_enabled);
// Go back to A, restoring it from the back-forward cache (again)
web_contents()->GetController().GoBack();
ASSERT_TRUE(WaitForLoadStop(web_contents()));
web_contents()->GetController().GoForward();
ASSERT_TRUE(WaitForLoadStop(web_contents()));
// Verify another foreground duration was logged.
ExpectNewForegroundDuration(url_a, back_forward_cache_enabled);
}
IN_PROC_BROWSER_TEST_P(PageLoadMetricsBackForwardCacheBrowserTest,
LogsPageForegroundDurationOnHide) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
bool back_forward_cache_enabled = GetParam() == kEnabled;
// Navigate to A.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_a));
content::RenderFrameHostWrapper rfh_a(web_contents()->GetPrimaryMainFrame());
// Navigate to B.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));
if (back_forward_cache_enabled) {
ASSERT_EQ(rfh_a->GetLifecycleState(),
content::RenderFrameHost::LifecycleState::kInBackForwardCache);
}
// Verify a new foreground duration - this one shouldn't be logged by the
// bfcache metrics regardless of bfcache being enabled or not.
ExpectNewForegroundDuration(url_a, /*expect_bfcache=*/false);
// Go back to A, restoring it from the back-forward cache (if enabled)
web_contents()->GetController().GoBack();
ASSERT_TRUE(WaitForLoadStop(web_contents()));
// Open and move to a new tab. This hides A, which should log a foreground
// duration.
ui_test_utils::NavigateToURLWithDisposition(
browser(), url_b, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
// The new tab opening should cause a foreground duration for the original
// tab, since it's been hidden.
ExpectNewForegroundDuration(url_a, back_forward_cache_enabled);
// From this point no more foreground durations are expected to be logged, so
// stash the current counts.
int64_t bf_count = CountForMetricForURL(
HistoryNavigation::kEntryName,
HistoryNavigation::kForegroundDurationAfterBackForwardCacheRestoreName,
url_a);
int64_t pl_count =
CountForMetricForURL(PageLoad::kEntryName,
PageLoad::kPageTiming_ForegroundDurationName, url_a);
// Switch back to the tab for url_a.
ui_test_utils::NavigateToURLWithDisposition(
browser(), url_a, WindowOpenDisposition::SWITCH_TO_TAB,
ui_test_utils::BROWSER_TEST_NO_WAIT);
// And then switch back to url_b's tab. This should call OnHidden for the
// url_a tab again, but no new foreground duration should be logged.
ui_test_utils::NavigateToURLWithDisposition(
browser(), url_b, WindowOpenDisposition::SWITCH_TO_TAB,
ui_test_utils::BROWSER_TEST_NO_WAIT);
int64_t bf_count_after_switch = CountForMetricForURL(
HistoryNavigation::kEntryName,
HistoryNavigation::kForegroundDurationAfterBackForwardCacheRestoreName,
url_a);
int64_t pl_count_after_switch =
CountForMetricForURL(PageLoad::kEntryName,
PageLoad::kPageTiming_ForegroundDurationName, url_a);
EXPECT_EQ(bf_count, bf_count_after_switch);
EXPECT_EQ(pl_count, pl_count_after_switch);
// Switch back to the tab for url_a, then close the browser. This should cause
// OnComplete to be called on the BFCache observer, but this should not cause
// a new foreground duration to be logged.
ui_test_utils::NavigateToURLWithDisposition(
browser(), url_a, WindowOpenDisposition::SWITCH_TO_TAB,
ui_test_utils::BROWSER_TEST_NO_WAIT);
CloseBrowserSynchronously(browser());
// Neither of the metrics for url_a should have moved.
int64_t bf_count_after_close = CountForMetricForURL(
HistoryNavigation::kEntryName,
HistoryNavigation::kForegroundDurationAfterBackForwardCacheRestoreName,
url_a);
int64_t pl_count_after_close =
CountForMetricForURL(PageLoad::kEntryName,
PageLoad::kPageTiming_ForegroundDurationName, url_a);
EXPECT_EQ(bf_count, bf_count_after_close);
EXPECT_EQ(pl_count, pl_count_after_close);
}
INSTANTIATE_TEST_SUITE_P(
All,
PageLoadMetricsBackForwardCacheBrowserTest,
testing::ValuesIn({BackForwardCacheStatus::kDisabled,
BackForwardCacheStatus::kEnabled}),
PageLoadMetricsBackForwardCacheBrowserTest::DescribeParams);