blob: 1baa0550212e97938f71cf1e83f38d1d1347d619 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <iterator>
#include <memory>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/check_op.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.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/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/page_load_metrics/observers/aborts_page_load_metrics_observer.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/observers/service_worker_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/session_restore_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/page_load_metrics_initialize.h"
#include "chrome/browser/prefetch/no_state_prefetch/no_state_prefetch_manager_factory.h"
#include "chrome/browser/prefetch/no_state_prefetch/prerender_test_utils.h"
#include "chrome/browser/prefs/session_startup_pref.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/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/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_manager.h"
#include "components/no_state_prefetch/browser/prerender_histograms.h"
#include "components/no_state_prefetch/common/prerender_origin.h"
#include "components/page_load_metrics/browser/observers/core/uma_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/sessions/core/serialized_navigation_entry.h"
#include "components/sessions/core/serialized_navigation_entry_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/render_process_host.h"
#include "content/public/browser/render_view_host.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/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
#include "content/public/test/navigation_handle_observer.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/features.h"
#include "third_party/blink/public/mojom/use_counter/css_property_id.mojom.h"
#include "third_party/blink/public/mojom/web_feature/web_feature.mojom.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 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;
namespace {
constexpr char kCacheablePathPrefix[] = "/cacheable";
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);
}
} // namespace
class PageLoadMetricsBrowserTest : public InProcessBrowserTest {
public:
PageLoadMetricsBrowserTest() {
scoped_feature_list_.InitWithFeatures(
{ukm::kUkmFeature, blink::features::kPortals,
blink::features::kPortalsCrossOrigin},
{});
}
PageLoadMetricsBrowserTest(const PageLoadMetricsBrowserTest&) = delete;
PageLoadMetricsBrowserTest& operator=(const PageLoadMetricsBrowserTest&) =
delete;
~PageLoadMetricsBrowserTest() override {}
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::ExecuteScript(
browser()->tab_strip_model()->GetActiveWebContents(),
"document.getElementById(\"" + id + "\").webkitRequestFullscreen();"));
}
std::string GetRecordedPageLoadMetricNames() {
auto entries = histogram_tester_->GetTotalCountsForPrefix("PageLoad.");
std::vector<std::string> names;
std::transform(
entries.begin(), entries.end(), std::back_inserter(names),
[](const std::pair<std::string, base::HistogramBase::Count>& entry) {
return entry.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.
size_t total_pageload_histograms =
histogram_tester_->GetTotalCountsForPrefix("PageLoad.").size();
size_t total_internal_histograms =
histogram_tester_->GetTotalCountsForPrefix("PageLoad.Internal.").size();
DCHECK_GE(total_pageload_histograms, total_internal_histograms);
return total_pageload_histograms - total_internal_histograms == 0;
}
std::unique_ptr<PageLoadMetricsTestWaiter> CreatePageLoadMetricsTestWaiter() {
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
return std::make_unique<PageLoadMetricsTestWaiter>(web_contents);
}
// 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->StartPrefetchingFromOmnibox(
url, storage_namespace, gfx::Size(640, 480));
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()->GetMainFrame();
}
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<base::HistogramTester> histogram_tester_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
};
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->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));
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, 50));
const timestamp = performance.now();
await new Promise(r => setTimeout(r, 50));
return timestamp;
})();)");
EXPECT_EQ("", result.error);
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_EQ("", result2.error);
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);
}
}
};
class PageLoadMetricsBrowserTestWithAnimatedLCPFlag
: public PageLoadMetricsBrowserTestAnimatedLCP {
public:
PageLoadMetricsBrowserTestWithAnimatedLCPFlag() {
scoped_feature_list_.Reset();
scoped_feature_list_.InitWithFeatures(
{blink::features::kLCPAnimatedImagesReporting}, {});
}
};
class PageLoadMetricsBrowserTestWithRuntimeAnimatedLCPFlag
: public PageLoadMetricsBrowserTestAnimatedLCP {
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
"LCPAnimatedImagesWebExposed");
}
};
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, NoNavigation) {
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_TRUE(NoPageLoadMetricsRecorded())
<< "Recorded metrics: " << GetRecordedPageLoadMetricNames();
}
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->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::kHistogramParseDuration, 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::kHistogramPageLoadTotalBytes,
1);
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->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::kHistogramParseDuration, 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::kHistogramPageLoadTotalBytes,
1);
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});
}
// Triggers nostate prefetch, and verifies that the UKM metrics related to
// nostate prefetch are recorded correctly.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, NoStatePrefetchMetrics) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url = embedded_test_server()->GetURL("/title1.html");
TriggerNoStatePrefetch(url);
auto waiter = CreatePageLoadMetricsTestWaiter();
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::kHistogramPageLoadTotalBytes,
1);
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});
}
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->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/986642): 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->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->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->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->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);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, 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->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->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->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->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();
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/1223288): Test flakes on Chrome OS.
#if BUILDFLAG(IS_CHROMEOS_ASH)
#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->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->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, DocumentWriteReload) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
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->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->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->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->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->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/712935): Flaky on Linux dbg.
// TODO(crbug.com/738235): 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);
}
// Test code that aborts provisional navigations.
// TODO(csharrison): Move these to unit tests once the navigation API in content
// properly calls NavigationHandle/NavigationThrottle methods.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, AbortNewNavigation) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("/title1.html"));
NavigateParams params(browser(), url, ui::PAGE_TRANSITION_LINK);
content::TestNavigationManager manager(
browser()->tab_strip_model()->GetActiveWebContents(), url);
Navigate(&params);
EXPECT_TRUE(manager.WaitForRequestStart());
GURL url2(embedded_test_server()->GetURL("/title2.html"));
NavigateParams params2(browser(), url2, ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
auto waiter = CreatePageLoadMetricsTestWaiter();
waiter->AddPageExpectation(TimingField::kLoadEvent);
Navigate(&params2);
waiter->Wait();
histogram_tester_->ExpectTotalCount(
internal::kHistogramAbortNewNavigationBeforeCommit, 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, AbortReload) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("/title1.html"));
NavigateParams params(browser(), url, ui::PAGE_TRANSITION_LINK);
content::TestNavigationManager manager(
browser()->tab_strip_model()->GetActiveWebContents(), url);
Navigate(&params);
EXPECT_TRUE(manager.WaitForRequestStart());
NavigateParams params2(browser(), url, ui::PAGE_TRANSITION_RELOAD);
auto waiter = CreatePageLoadMetricsTestWaiter();
waiter->AddPageExpectation(TimingField::kLoadEvent);
Navigate(&params2);
waiter->Wait();
histogram_tester_->ExpectTotalCount(
internal::kHistogramAbortReloadBeforeCommit, 1);
}
// TODO(crbug.com/675061): Flaky on Win7 dbg.
#if BUILDFLAG(IS_WIN)
#define MAYBE_AbortClose DISABLED_AbortClose
#else
#define MAYBE_AbortClose AbortClose
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, MAYBE_AbortClose) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("/title1.html"));
NavigateParams params(browser(), url, ui::PAGE_TRANSITION_LINK);
content::TestNavigationManager manager(
browser()->tab_strip_model()->GetActiveWebContents(), url);
Navigate(&params);
EXPECT_TRUE(manager.WaitForRequestStart());
browser()->tab_strip_model()->GetActiveWebContents()->Close();
manager.WaitForNavigationFinished();
histogram_tester_->ExpectTotalCount(
internal::kHistogramAbortCloseBeforeCommit, 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, AbortMultiple) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("/title1.html"));
NavigateParams params(browser(), url, ui::PAGE_TRANSITION_LINK);
content::TestNavigationManager manager(
browser()->tab_strip_model()->GetActiveWebContents(), url);
Navigate(&params);
EXPECT_TRUE(manager.WaitForRequestStart());
GURL url2(embedded_test_server()->GetURL("/title2.html"));
NavigateParams params2(browser(), url2, ui::PAGE_TRANSITION_TYPED);
content::TestNavigationManager manager2(
browser()->tab_strip_model()->GetActiveWebContents(), url2);
Navigate(&params2);
EXPECT_TRUE(manager2.WaitForRequestStart());
manager.WaitForNavigationFinished();
GURL url3(embedded_test_server()->GetURL("/title3.html"));
NavigateParams params3(browser(), url3, ui::PAGE_TRANSITION_TYPED);
auto waiter = CreatePageLoadMetricsTestWaiter();
waiter->AddPageExpectation(TimingField::kLoadEvent);
Navigate(&params3);
waiter->Wait();
manager2.WaitForNavigationFinished();
histogram_tester_->ExpectTotalCount(
internal::kHistogramAbortNewNavigationBeforeCommit, 2);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
NoAbortMetricsOnClientRedirect) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL first_url(embedded_test_server()->GetURL("/title1.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), first_url));
GURL second_url(embedded_test_server()->GetURL("/title2.html"));
NavigateParams params(browser(), second_url, ui::PAGE_TRANSITION_LINK);
content::TestNavigationManager manager(
browser()->tab_strip_model()->GetActiveWebContents(), second_url);
Navigate(&params);
EXPECT_TRUE(manager.WaitForRequestStart());
{
auto waiter = CreatePageLoadMetricsTestWaiter();
waiter->AddPageExpectation(TimingField::kLoadEvent);
EXPECT_TRUE(content::ExecuteScript(
browser()->tab_strip_model()->GetActiveWebContents(),
"window.location.reload();"));
waiter->Wait();
}
manager.WaitForNavigationFinished();
EXPECT_TRUE(
histogram_tester_
->GetTotalCountsForPrefix("PageLoad.Experimental.AbortTiming.")
.empty());
}
// TODO(crbug.com/1009885): Flaky on Linux MSan builds.
#if defined(MEMORY_SANITIZER) && (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS))
#define MAYBE_FirstMeaningfulPaintRecorded DISABLED_FirstMeaningfulPaintRecorded
#else
#define MAYBE_FirstMeaningfulPaintRecorded FirstMeaningfulPaintRecorded
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
MAYBE_FirstMeaningfulPaintRecorded) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
waiter->AddPageExpectation(TimingField::kFirstMeaningfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/title1.html")));
waiter->Wait();
histogram_tester_->ExpectUniqueSample(
internal::kHistogramFirstMeaningfulPaintStatus,
internal::FIRST_MEANINGFUL_PAINT_RECORDED, 1);
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstMeaningfulPaint,
1);
histogram_tester_->ExpectTotalCount(
internal::kHistogramParseStartToFirstMeaningfulPaint, 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
FirstMeaningfulPaintNotRecorded) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/page_load_metrics/page_with_active_connections.html")));
waiter->Wait();
// Navigate away before a FMP is reported.
NavigateToUntrackedUrl();
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstContentfulPaint,
1);
histogram_tester_->ExpectUniqueSample(
internal::kHistogramFirstMeaningfulPaintStatus,
internal::FIRST_MEANINGFUL_PAINT_DID_NOT_REACH_NETWORK_STABLE, 1);
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstMeaningfulPaint,
0);
histogram_tester_->ExpectTotalCount(
internal::kHistogramParseStartToFirstMeaningfulPaint, 0);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PayloadSize) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
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();
histogram_tester_->ExpectTotalCount(internal::kHistogramPageLoadTotalBytes,
1);
// Verify that there is a single sample recorded in the 10kB bucket (the size
// of the main HTML response).
histogram_tester_->ExpectBucketCount(internal::kHistogramPageLoadTotalBytes,
10, 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PayloadSizeChildFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
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();
histogram_tester_->ExpectTotalCount(internal::kHistogramPageLoadTotalBytes,
1);
// Verify that there is a single sample recorded in the 10kB bucket (the size
// of the iframe response).
histogram_tester_->ExpectBucketCount(internal::kHistogramPageLoadTotalBytes,
10, 1);
}
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();
histogram_tester_->ExpectUniqueSample(internal::kHistogramPageLoadTotalBytes,
0, 1);
}
// 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->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(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kTextWholeText), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramMainFrameName,
static_cast<int32_t>(WebFeature::kTextWholeText), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramMainFrameName,
static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kNavigatorVibrate), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramMainFrameName,
static_cast<int32_t>(WebFeature::kNavigatorVibrate), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kPageVisits), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramMainFrameName,
static_cast<int32_t>(WebFeature::kPageVisits), 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterCSSPropertiesInMainFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
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(internal::kCssPropertiesHistogramName, 6,
1);
// CSSPropertyID::kFontSize
histogram_tester_->ExpectBucketCount(internal::kCssPropertiesHistogramName, 7,
1);
histogram_tester_->ExpectBucketCount(
internal::kCssPropertiesHistogramName,
blink::mojom::CSSSampleId::kTotalPagesMeasured, 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterAnimatedCSSPropertiesInMainFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
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(
internal::kAnimatedCssPropertiesHistogramName, 161, 1);
// CSSPropertyID::kMarginLeft
histogram_tester_->ExpectBucketCount(
internal::kAnimatedCssPropertiesHistogramName, 91, 1);
histogram_tester_->ExpectBucketCount(
internal::kAnimatedCssPropertiesHistogramName,
blink::mojom::CSSSampleId::kTotalPagesMeasured, 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->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(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kMixedContentAudio), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kMixedContentImage), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kMixedContentVideo), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramName,
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->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(internal::kCssPropertiesHistogramName, 6,
1);
// CSSPropertyID::kFontSize
histogram_tester_->ExpectBucketCount(internal::kCssPropertiesHistogramName, 7,
1);
histogram_tester_->ExpectBucketCount(
internal::kCssPropertiesHistogramName,
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->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(
internal::kAnimatedCssPropertiesHistogramName, 161, 1);
// CSSPropertyID::kMarginLeft
histogram_tester_->ExpectBucketCount(
internal::kAnimatedCssPropertiesHistogramName, 91, 1);
histogram_tester_->ExpectBucketCount(
internal::kAnimatedCssPropertiesHistogramName,
blink::mojom::CSSSampleId::kTotalPagesMeasured, 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
UseCounterFeaturesInNonSecureMainFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
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(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kTextWholeText), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramMainFrameName,
static_cast<int32_t>(WebFeature::kTextWholeText), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramMainFrameName,
static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kNavigatorVibrate), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramMainFrameName,
static_cast<int32_t>(WebFeature::kNavigatorVibrate), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kPageVisits), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramMainFrameName,
static_cast<int32_t>(WebFeature::kPageVisits), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kFullscreenInsecureOrigin), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramMainFrameName,
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_ASSUMES_NO_CACHING);
auto waiter = CreatePageLoadMetricsTestWaiter();
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 auto* 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_ASSUMES_NO_CACHING);
auto waiter = CreatePageLoadMetricsTestWaiter();
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 auto* 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->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(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kTextWholeText), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kNavigatorVibrate), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kPageVisits), 1);
// No feature but page visits should get counted.
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramMainFrameName,
static_cast<int32_t>(WebFeature::kTextWholeText), 0);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramMainFrameName,
static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 0);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramMainFrameName,
static_cast<int32_t>(WebFeature::kNavigatorVibrate), 0);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramMainFrameName,
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->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(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kTextWholeText), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kNavigatorVibrate), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kPageVisits), 1);
// No feature but page visits should get counted.
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramMainFrameName,
static_cast<int32_t>(WebFeature::kTextWholeText), 0);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramMainFrameName,
static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 0);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramMainFrameName,
static_cast<int32_t>(WebFeature::kNavigatorVibrate), 0);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramMainFrameName,
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->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(internal::kCssPropertiesHistogramName, 6,
1);
// CSSPropertyID::kFontSize
histogram_tester_->ExpectBucketCount(internal::kCssPropertiesHistogramName, 7,
1);
histogram_tester_->ExpectBucketCount(
internal::kCssPropertiesHistogramName,
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->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(internal::kCssPropertiesHistogramName, 6,
1);
// CSSPropertyID::kFontSize
histogram_tester_->ExpectBucketCount(internal::kCssPropertiesHistogramName, 7,
1);
histogram_tester_->ExpectBucketCount(
internal::kCssPropertiesHistogramName,
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->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(
internal::kAnimatedCssPropertiesHistogramName, 161, 1);
// CSSPropertyID::kMarginLeft
histogram_tester_->ExpectBucketCount(
internal::kAnimatedCssPropertiesHistogramName, 91, 1);
histogram_tester_->ExpectBucketCount(
internal::kAnimatedCssPropertiesHistogramName,
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->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(
internal::kAnimatedCssPropertiesHistogramName, 161, 1);
// CSSPropertyID::kMarginLeft
histogram_tester_->ExpectBucketCount(
internal::kAnimatedCssPropertiesHistogramName, 91, 1);
histogram_tester_->ExpectBucketCount(
internal::kAnimatedCssPropertiesHistogramName,
blink::mojom::CSSSampleId::kTotalPagesMeasured, 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(
internal::kFeaturesHistogramName,
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>(
blink::mojom::PermissionsPolicyFeature::kFullscreen);
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
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(
internal::kPermissionsPolicyViolationHistogramName, test_feature, 1);
histogram_tester_->ExpectBucketCount(
internal::kPermissionsPolicyHeaderHistogramName, 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>(
blink::mojom::PermissionsPolicyFeature::kFullscreen);
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
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(
internal::kPermissionsPolicyViolationHistogramName, test_feature, 1);
histogram_tester_->ExpectBucketCount(
internal::kPermissionsPolicyHeaderHistogramName, test_feature, 1);
histogram_tester_->ExpectBucketCount(
internal::kPermissionsPolicyIframeAttributeHistogramName, 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>(
blink::mojom::PermissionsPolicyFeature::kFullscreen);
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
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(
internal::kPermissionsPolicyViolationHistogramName, test_feature, 1);
histogram_tester_->ExpectBucketCount(
internal::kPermissionsPolicyHeaderHistogramName, test_feature, 1);
histogram_tester_->ExpectBucketCount(
internal::kPermissionsPolicyIframeAttributeHistogramName, test_feature,
1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, LoadingMetrics) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
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() {}
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_ASH)
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");
}
void ExpectFirstPaintMetricsTotalCount(int expected_total_count) const {
histogram_tester_->ExpectTotalCount(
internal::kHistogramSessionRestoreForegroundTabFirstPaint,
expected_total_count);
histogram_tester_->ExpectTotalCount(
internal::kHistogramSessionRestoreForegroundTabFirstContentfulPaint,
expected_total_count);
histogram_tester_->ExpectTotalCount(
internal::kHistogramSessionRestoreForegroundTabFirstMeaningfulPaint,
expected_total_count);
}
};
class SessionRestorePaintWaiter : public SessionRestoreObserver {
public:
SessionRestorePaintWaiter() { SessionRestore::AddObserver(this); }
SessionRestorePaintWaiter(const SessionRestorePaintWaiter&) = delete;
SessionRestorePaintWaiter& operator=(const SessionRestorePaintWaiter&) =
delete;
~SessionRestorePaintWaiter() { SessionRestore::RemoveObserver(this); }
// SessionRestoreObserver implementation:
void OnWillRestoreTab(content::WebContents* contents) override {
chrome::InitializePageLoadMetricsForWebContents(contents);
auto waiter = std::make_unique<PageLoadMetricsTestWaiter>(contents);
waiter->AddPageExpectation(TimingField::kFirstPaint);
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
waiter->AddPageExpectation(TimingField::kFirstMeaningfulPaint);
waiters_[contents] = std::move(waiter);
}
// First meaningful paints occur only on foreground tabs.
void WaitForForegroundTabs(size_t num_expected_foreground_tabs) {
size_t num_actual_foreground_tabs = 0;
for (auto iter = waiters_.begin(); iter != waiters_.end(); ++iter) {
if (iter->first->GetVisibility() == content::Visibility::HIDDEN)
continue;
iter->second->Wait();
++num_actual_foreground_tabs;
}
EXPECT_EQ(num_expected_foreground_tabs, num_actual_foreground_tabs);
}
private:
std::unordered_map<content::WebContents*,
std::unique_ptr<PageLoadMetricsTestWaiter>>
waiters_;
};
IN_PROC_BROWSER_TEST_F(SessionRestorePageLoadMetricsBrowserTest,
InitialVisibilityOfSingleRestoredTab) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestURL()));
histogram_tester_->ExpectTotalCount(
page_load_metrics::internal::kPageLoadStartedInForeground, 1);
histogram_tester_->ExpectBucketCount(
page_load_metrics::internal::kPageLoadStartedInForeground, true, 1);
Browser* new_browser = QuitBrowserAndRestore(browser());
ASSERT_NO_FATAL_FAILURE(WaitForTabsToLoad(new_browser));
histogram_tester_->ExpectTotalCount(
page_load_metrics::internal::kPageLoadStartedInForeground, 2);
histogram_tester_->ExpectBucketCount(
page_load_metrics::internal::kPageLoadStartedInForeground, true, 2);
}
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);
histogram_tester_->ExpectTotalCount(
page_load_metrics::internal::kPageLoadStartedInForeground, 2);
histogram_tester_->ExpectBucketCount(
page_load_metrics::internal::kPageLoadStartedInForeground, false, 1);
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());
histogram_tester_->ExpectTotalCount(
page_load_metrics::internal::kPageLoadStartedInForeground, 4);
histogram_tester_->ExpectBucketCount(
page_load_metrics::internal::kPageLoadStartedInForeground, true, 2);
histogram_tester_->ExpectBucketCount(
page_load_metrics::internal::kPageLoadStartedInForeground, false, 2);
}
IN_PROC_BROWSER_TEST_F(SessionRestorePageLoadMetricsBrowserTest,
NoSessionRestore) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestURL()));
ExpectFirstPaintMetricsTotalCount(0);
}
IN_PROC_BROWSER_TEST_F(SessionRestorePageLoadMetricsBrowserTest,
SingleTabSessionRestore) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestURL()));
SessionRestorePaintWaiter session_restore_paint_waiter;
QuitBrowserAndRestore(browser());
session_restore_paint_waiter.WaitForForegroundTabs(1);
ExpectFirstPaintMetricsTotalCount(1);
}
IN_PROC_BROWSER_TEST_F(SessionRestorePageLoadMetricsBrowserTest,
MultipleTabsSessionRestore) {
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);
SessionRestorePaintWaiter session_restore_paint_waiter;
Browser* new_browser = QuitBrowserAndRestore(browser());
TabStripModel* tab_strip = new_browser->tab_strip_model();
ASSERT_TRUE(tab_strip);
ASSERT_EQ(2, tab_strip->count());
// Only metrics of the initial foreground tab are recorded.
session_restore_paint_waiter.WaitForForegroundTabs(1);
ASSERT_NO_FATAL_FAILURE(WaitForTabsToLoad(new_browser));
ExpectFirstPaintMetricsTotalCount(1);
}
IN_PROC_BROWSER_TEST_F(SessionRestorePageLoadMetricsBrowserTest,
NavigationDuringSessionRestore) {
NavigateToUntrackedUrl();
Browser* new_browser = QuitBrowserAndRestore(browser());
auto waiter = std::make_unique<PageLoadMetricsTestWaiter>(
new_browser->tab_strip_model()->GetActiveWebContents());
waiter->AddPageExpectation(TimingField::kFirstMeaningfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(new_browser, GetTestURL()));
waiter->Wait();
// No metrics recorded for the second navigation because the tab navigated
// away during session restore.
ExpectFirstPaintMetricsTotalCount(0);
}
IN_PROC_BROWSER_TEST_F(SessionRestorePageLoadMetricsBrowserTest,
LoadingAfterSessionRestore) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestURL()));
Browser* new_browser = nullptr;
{
SessionRestorePaintWaiter session_restore_paint_waiter;
new_browser = QuitBrowserAndRestore(browser());
session_restore_paint_waiter.WaitForForegroundTabs(1);
ExpectFirstPaintMetricsTotalCount(1);
}
// Load a new page after session restore.
auto waiter = std::make_unique<PageLoadMetricsTestWaiter>(
new_browser->tab_strip_model()->GetActiveWebContents());
waiter->AddPageExpectation(TimingField::kFirstMeaningfulPaint);
ASSERT_TRUE(ui_test_utils::NavigateToURL(new_browser, GetTestURL()));
waiter->Wait();
// No more metrics because the navigation is after session restore.
ExpectFirstPaintMetricsTotalCount(1);
}
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#define MAYBE_InitialForegroundTabChanged DISABLED_InitialForegroundTabChanged
#else
#define MAYBE_InitialForegroundTabChanged InitialForegroundTabChanged
#endif
IN_PROC_BROWSER_TEST_F(SessionRestorePageLoadMetricsBrowserTest,
MAYBE_InitialForegroundTabChanged) {
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);
SessionRestorePaintWaiter session_restore_paint_waiter;
Browser* new_browser = QuitBrowserAndRestore(browser());
// Change the foreground tab before the first meaningful paint.
TabStripModel* tab_strip = new_browser->tab_strip_model();
ASSERT_TRUE(tab_strip);
ASSERT_EQ(2, tab_strip->count());
ASSERT_EQ(0, tab_strip->active_index());
tab_strip->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
session_restore_paint_waiter.WaitForForegroundTabs(1);
// No metrics were recorded because initial foreground tab was switched away.
ExpectFirstPaintMetricsTotalCount(0);
}
IN_PROC_BROWSER_TEST_F(SessionRestorePageLoadMetricsBrowserTest,
MultipleSessionRestores) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestURL()));
Browser* current_browser = browser();
const int num_session_restores = 3;
for (int i = 1; i <= num_session_restores; ++i) {
SessionRestorePaintWaiter session_restore_paint_waiter;
current_browser = QuitBrowserAndRestore(current_browser);
session_restore_paint_waiter.WaitForForegroundTabs(1);
ExpectFirstPaintMetricsTotalCount(i);
}
}
IN_PROC_BROWSER_TEST_F(SessionRestorePageLoadMetricsBrowserTest,
RestoreForeignTab) {
sessions::SessionTab tab;
tab.tab_visual_index = 0;
tab.current_navigation_index = 1;
tab.navigations.push_back(sessions::ContentTestHelper::CreateNavigation(
GetTestURL().spec(), "one"));
tab.navigations.back().set_encoded_page_state("");
ASSERT_EQ(1, browser()->tab_strip_model()->count());
// Restore in the current tab.
content::WebContents* tab_contents = nullptr;
{
SessionRestorePaintWaiter session_restore_paint_waiter;
tab_contents = SessionRestore::RestoreForeignSessionTab(
browser()->tab_strip_model()->GetActiveWebContents(), tab,
WindowOpenDisposition::CURRENT_TAB);
ASSERT_EQ(1, browser()->tab_strip_model()->count());
ASSERT_TRUE(tab_contents);
ASSERT_EQ(GetTestURL(), tab_contents->GetURL());
session_restore_paint_waiter.WaitForForegroundTabs(1);
ExpectFirstPaintMetricsTotalCount(1);
}
// Restore in a new foreground tab.
{
SessionRestorePaintWaiter session_restore_paint_waiter;
tab_contents = SessionRestore::RestoreForeignSessionTab(
browser()->tab_strip_model()->GetActiveWebContents(), tab,
WindowOpenDisposition::NEW_FOREGROUND_TAB);
ASSERT_EQ(2, browser()->tab_strip_model()->count());
ASSERT_EQ(1, browser()->tab_strip_model()->active_index());
ASSERT_TRUE(tab_contents);
ASSERT_EQ(GetTestURL(), tab_contents->GetURL());
session_restore_paint_waiter.WaitForForegroundTabs(1);
ExpectFirstPaintMetricsTotalCount(2);
}
// Restore in a new background tab.
{
tab_contents = SessionRestore::RestoreForeignSessionTab(
browser()->tab_strip_model()->GetActiveWebContents(), tab,
WindowOpenDisposition::NEW_BACKGROUND_TAB);
ASSERT_EQ(3, browser()->tab_strip_model()->count());
ASSERT_EQ(1, browser()->tab_strip_model()->active_index());
ASSERT_TRUE(tab_contents);
ASSERT_EQ(GetTestURL(), tab_contents->GetURL());
ASSERT_NO_FATAL_FAILURE(WaitForTabsToLoad(browser()));
// Do not record timings of initially background tabs.
ExpectFirstPaintMetricsTotalCount(2);
}
}
// TODO(crbug.com/1242284): Flaky on Linux.
#if BUILDFLAG(IS_LINUX)
#define MAYBE_RestoreForeignSession DISABLED_RestoreForeignSession
#else
#define MAYBE_RestoreForeignSession RestoreForeignSession
#endif
IN_PROC_BROWSER_TEST_F(SessionRestorePageLoadMetricsBrowserTest,
MAYBE_RestoreForeignSession) {
Profile* profile = browser()->profile();
// Set up the restore data: one window with two tabs.
std::vector<const sessions::SessionWindow*> session;
sessions::SessionWindow window;
{
auto tab1 = std::make_unique<sessions::SessionTab>();
tab1->tab_visual_index = 0;
tab1->current_navigation_index = 0;
tab1->pinned = true;
tab1->navigations.push_back(sessions::ContentTestHelper::CreateNavigation(
GetTestURL().spec(), "one"));
tab1->navigations.back().set_encoded_page_state("");
window.tabs.push_back(std::move(tab1));
}
{
auto tab2 = std::make_unique<sessions::SessionTab>();
tab2->tab_visual_index = 1;
tab2->current_navigation_index = 0;
tab2->pinned = false;
tab2->navigations.push_back(sessions::ContentTestHelper::CreateNavigation(
GetTestURL2().spec(), "two"));
tab2->navigations.back().set_encoded_page_state("");
window.tabs.push_back(std::move(tab2));
}
// Restore the session window with 2 tabs.
session.push_back(static_cast<const sessions::SessionWindow*>(&window));
SessionRestorePaintWaiter session_restore_paint_waiter;
SessionRestore::RestoreForeignSessionWindows(profile, session.begin(),
session.end());
session_restore_paint_waiter.WaitForForegroundTabs(1);
Browser* new_browser = BrowserList::GetInstance()->GetLastActive();
ASSERT_TRUE(new_browser);
ASSERT_EQ(2, new_browser->tab_strip_model()->count());
ASSERT_NO_FATAL_FAILURE(WaitForTabsToLoad(new_browser));
ExpectFirstPaintMetricsTotalCount(1);
}
// TODO(crbug.com/882077) Disabled due to flaky timeouts on all platforms.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
DISABLED_ReceivedAggregateResourceDataLength) {
embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
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();
int64_t one_frame_page_size = waiter->current_network_bytes();
waiter = CreatePageLoadMetricsTestWaiter();
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 - 100));
waiter->Wait();
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
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();
browser()->OpenURL(content::OpenURLParams(
embedded_test_server()->GetURL("/mock_page.html"), content::Referrer(),
WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_TYPED, false));
main_response->WaitForRequest();
main_response->Send(kHttpResponseHeader);
for (int i = 0; i < kNumChunks; i++) {
main_response->Send(std::to_string(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(), kChunkSize * kNumChunks);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, 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();
browser()->OpenURL(content::OpenURLParams(
embedded_test_server()->GetURL("/mock_page.html"), content::Referrer(),
WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_TYPED, false));
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(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(2000);
waiter->Wait();
script_response->Done();
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(4000);
waiter->Wait();
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
MemoryCacheResource_Recorded) {
const char kHttpResponseHeader[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Cache-Control: max-age=60\r\n"
"\r\n";
embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
content::SetupCrossSiteRedirector(embedded_test_server());
auto cached_response =
std::make_unique<net::test_server::ControllableHttpResponse>(
embedded_test_server(), "/cachetime",
true /*relative_url_is_prefix*/);
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
browser()->OpenURL(content::OpenURLParams(
embedded_test_server()->GetURL("/page_with_cached_subresource.html"),
content::Referrer(), WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_TYPED, false));
// Load a resource large enough to record a nonzero number of kilobytes.
cached_response->WaitForRequest();
cached_response->Send(kHttpResponseHeader);
cached_response->Send(std::string(10 * 1024, ' '));
cached_response->Done();
waiter->AddMinimumCompleteResourcesExpectation(3);
waiter->Wait();
// Re-navigate the page to the same url with a different query string so the
// main resource is not loaded from the disk cache. The subresource will be
// loaded from the memory cache.
waiter = CreatePageLoadMetricsTestWaiter();
browser()->OpenURL(content::OpenURLParams(
embedded_test_server()->GetURL("/page_with_cached_subresource.html?xyz"),
content::Referrer(), WindowOpenDisposition::CURRENT_TAB,
ui::PAGE_TRANSITION_TYPED, false));
// Favicon is not fetched this time.
waiter->AddMinimumCompleteResourcesExpectation(2);
waiter->Wait();
// Verify no resources were cached for the first load.
histogram_tester_->ExpectBucketCount(internal::kHistogramPageLoadCacheBytes,
0, 1);
// Force histograms to record.
NavigateToUntrackedUrl();
// Verify that the cached resource from the memory cache is recorded
// correctly.
histogram_tester_->ExpectBucketCount(internal::kHistogramPageLoadCacheBytes,
10, 1);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, InputEventsForClick) {
embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
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->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::kHistogramInputToFirstPaint, 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, InputEventsForOmniboxMatch) {
embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
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::kHistogramInputToFirstPaint, 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});
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
InputEventsForJavaScriptHref) {
embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
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();
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::kHistogramInputToFirstPaint, 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->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();
content::WebContentsAddedObserver web_contents_added_observer;
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 = web_contents_added_observer.GetWebContents();
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::kHistogramInputToFirstPaint, 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->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->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->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 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->AddPageExpectation(TimingField::kLoadEvent);
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/title1.html")));
waiter->Wait();
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kSecureContextCheckPassed), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kSecureContextCheckFailed), 0);
}
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
MainFrameIntersectionsMainFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
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();
}
// Creates a single frame within the main frame and verifies the intersection
// with the main frame.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
MainFrameIntersectionSingleFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
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();
}
// Creates a set of nested frames within the main frame and verifies
// their intersections with the main frame.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
MainFrameIntersectionSameOrigin) {
EXPECT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
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->GetMainFrame(), 0);
EXPECT_TRUE(
ExecJs(child_frame, "createIframeAtRect(\"test2\", 10, 10, 300, 300);"));
waiter->Wait();
}
// 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,
MainFrameIntersectionCrossOrigin) {
EXPECT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
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->GetMainFrame(), 0);
EXPECT_TRUE(
ExecJs(child_frame, "createIframeAtRect(\"test2\", 10, 10, 300, 300);"));
waiter->Wait();
}
// 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,
MainFrameIntersectionCrossOriginOutOfView) {
EXPECT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
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->GetMainFrame(), 0);
EXPECT_TRUE(ExecJs(child_frame,
"createIframeAtRect(\"test2\", 5000, 5000, 190, 190);"));
waiter->Wait();
}
// 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,
MainFrameIntersectionCrossOriginScrolled) {
EXPECT_TRUE(embedded_test_server()->Start());
auto waiter = CreatePageLoadMetricsTestWaiter();
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->GetMainFrame(), 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->AddPageExpectation(TimingField::kLoadEvent);
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
waiter->AddPageExpectation(TimingField::kLargestContentfulPaint);
// Waiter to ensure that iframe content is loaded.
auto waiter2 = CreatePageLoadMetricsTestWaiter();
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);
}
#if BUILDFLAG(IS_MAC) // crbug.com/1277391
#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(PageLoadMetricsBrowserTestWithAnimatedLCPFlag,
MAYBE_PageLCPAnimatedImage) {
test_animated_image_lcp(/*smaller=*/true, /*animated=*/true);
}
// Tests that an animated image's reported LCP values are larger than its load
// times, when only the feature flag for animated image web exposure is enabled.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestWithRuntimeAnimatedLCPFlag,
PageLCPAnimatedImageOnlyRuntimeFlag) {
test_animated_image_lcp(/*smaller=*/false, /*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.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestWithAnimatedLCPFlag,
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->AddPageExpectation(TimingField::kLoadEvent);
waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
auto waiter2 = CreatePageLoadMetricsTestWaiter();
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);
}
// Flaky on all platforms: https://crbug.com/1211028.
// Tests that a portal activation records metrics.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, DISABLED_PortalActivation) {
// We only record metrics for portals when the time is consistent across
// processes.
if (!base::TimeTicks::IsConsistentAcrossProcesses())
return;
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("portal.test", "/title1.html")));
content::WebContents* outer_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Create a portal to a.com.
GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
std::string script = R"(
var portal = document.createElement('portal');
portal.src = '%s';
document.body.appendChild(portal);
)";
content::WebContentsAddedObserver contents_observer;
content::TestNavigationObserver portal_nav_observer(a_url);
portal_nav_observer.StartWatchingNewWebContents();
EXPECT_TRUE(ExecJs(outer_contents,
base::StringPrintf(script.c_str(), a_url.spec().c_str())));
portal_nav_observer.WaitForNavigationFinished();
content::WebContents* portal_contents = contents_observer.GetWebContents();
{
// The portal is not activated, so no page end metrics should be recorded
// (although the outer contents may have recorded FCP).
auto entries = test_ukm_recorder_->GetMergedEntriesByName(
ukm::builders::PageLoad::kEntryName);
for (const auto& kv : entries) {
EXPECT_FALSE(ukm::TestUkmRecorder::EntryHasMetric(
kv.second.get(),
ukm::builders::PageLoad::kNavigation_PageEndReason3Name));
}
}
// Activate the portal.
std::string activated_listener = R"(
activatePromise = new Promise(resolve => {
window.addEventListener('portalactivate', resolve(true));
});
)";
EXPECT_TRUE(ExecJs(portal_contents, activated_listener));
EXPECT_TRUE(
ExecJs(outer_contents, "document.querySelector('portal').activate()"));
EXPECT_EQ(true, content::EvalJs(portal_contents,
"activatePromise.then(r => { "
" window.domAutomationController.send(r);"
"});",
content::EXECUTE_SCRIPT_USE_MANUAL_REPLY));
// The activated portal contents should be the currently active contents.
EXPECT_EQ(portal_contents,
browser()->tab_strip_model()->GetActiveWebContents());
EXPECT_NE(portal_contents, outer_contents);
{
// The portal is activated, so there should be a PageLoad entry showing
// that the outer contents was closed.
auto entries = test_ukm_recorder_->GetMergedEntriesByName(
ukm::builders::PageLoad::kEntryName);
EXPECT_EQ(1u, entries.size());
for (const auto& kv : entries) {
ukm::TestUkmRecorder::ExpectEntryMetric(
kv.second.get(),
ukm::builders::PageLoad::kNavigation_PageEndReason3Name,
PageEndReason::END_CLOSE);
}
}
{
// The portal is activated, also check the portal entry.
auto entries = test_ukm_recorder_->GetMergedEntriesByName(
ukm::builders::Portal_Activate::kEntryName);
EXPECT_EQ(1u, entries.size());
}
}
class PageLoadMetricsBrowserTestWithBackForwardCache
: public PageLoadMetricsBrowserTest {
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
PageLoadMetricsBrowserTest::SetUpCommandLine(command_line);
feature_list.InitWithFeaturesAndParameters(
{{features::kBackForwardCache,
// Set a very long TTL before expiration (longer than the test
// timeout) so tests that are expecting deletion don't pass when they
// shouldn't.
//
// TODO(hajimehoshi): This value is used in various places. Define a
// constant and use it.
//
// Some features like the outstanding network requests are expected to
// appear in almost any output. Filter them out to make the tests
// simpler.
{{"TimeToLiveInBackForwardCacheInSeconds", "3600"},
{"ignore_outstanding_network_request_for_testing", "true"}}}},
// Allow BackForwardCache for all devices regardless of their memory.
{features::kBackForwardCacheMemoryControls});
}
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->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 auto* 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(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kPageVisits), 2);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramName,
static_cast<int32_t>(WebFeature::kFullscreenSecureOrigin), 1);
histogram_tester_->ExpectBucketCount(
internal::kFeaturesHistogramName,
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/1224780): 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);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Flaky. See https://crbug.com/1224780.
IN_PROC_BROWSER_TEST_P(NavigationPageLoadMetricsBrowserTest,
DISABLED_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());
// 1) Navigate to url1.
EXPECT_TRUE(content::NavigateToURL(web_contents(), url1));
content::RenderFrameHost* rfh_a = RenderFrameHost();
content::RenderProcessHost* rfh_a_process = rfh_a->GetProcess();
// Simulate mouse click. FirstInputDelay won't get updated immediately.
content::SimulateMouseClickAt(web_contents(), 0,
blink::WebMouseEvent::Button::kLeft,
gfx::Point(100, 100));
// Run arbitrary script and run tasks in the brwoser to ensure the input is
// processed in the renderer.
EXPECT_TRUE(content::ExecJs(rfh_a, "var foo = 42;"));
base::RunLoop().RunUntilIdle();
content::FetchHistogramsFromChildProcesses();
histogram_tester_->ExpectTotalCount(internal::kHistogramFirstInputDelay, 0);
// 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/1150242): 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))) {
feature_list_.InitAndEnableFeature(blink::features::kPrerender2);
}
void SetUp() override {
prerender_helper_.SetUp(embedded_test_server());
PageLoadMetricsBrowserTest::SetUp();
}
protected:
content::test::PrerenderTestHelper prerender_helper_;
base::test::ScopedFeatureList feature_list_;
};
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);
histogram_tester_->ExpectBucketCount(
page_load_metrics::internal::kPageLoadStartedInForeground, true, 1);
histogram_tester_->ExpectBucketCount(
page_load_metrics::internal::kPageLoadStartedInForeground, false, 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);
histogram_tester_->ExpectBucketCount(
page_load_metrics::internal::kPageLoadStartedInForeground, true, 1);
histogram_tester_->ExpectBucketCount(
page_load_metrics::internal::kPageLoadStartedInForeground, false, 1);
// Activate.
prerender_helper_.NavigatePrimaryPage(prerender_url);
histogram_tester_->ExpectBucketCount(
kPageLoadPrerender2Event,
PageLoadPrerenderEvent::kNavigationInPrerenderedMainFrame, 1);
histogram_tester_->ExpectBucketCount(
kPageLoadPrerender2Event,
PageLoadPrerenderEvent::kPrerenderActivationNavigation, 1);
histogram_tester_->ExpectBucketCount(
page_load_metrics::internal::kPageLoadStartedInForeground, true, 1);
histogram_tester_->ExpectBucketCount(
page_load_metrics::internal::kPageLoadStartedInForeground, false, 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");
int 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(
{{features::kBackForwardCache, {{"enable_same_site", "true"}}}},
// Allow BackForwardCache for all devices regardless of their memory.
{features::kBackForwardCacheMemoryControls});
} 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(base::StringPiece entry_name,
base::StringPiece 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 (auto* 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 (auto* 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(
base::StringPiece entry_name,
base::StringPiece metric_name,
const GURL& url) {
int64_t count = 0;
for (auto* 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()->GetMainFrame());
// 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()->GetMainFrame());
// 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()->GetMainFrame());
// 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_NONE);
// 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_NONE);
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_NONE);
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);