// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <algorithm>
#include <iterator>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_set>
#include <utility>
#include <vector>

#include "base/byte_size.h"
#include "base/check_op.h"
#include "base/containers/to_vector.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/gtest_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/tracing/trace_event_analyzer.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "cc/base/switches.h"
#include "chrome/browser/after_startup_task_utils.h"
#include "chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/observers/document_write_page_load_metrics_observer.h"
#include "chrome/browser/page_load_metrics/page_load_metrics_initialize.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/preloading/prefetch/no_state_prefetch/no_state_prefetch_manager_factory.h"
#include "chrome/browser/preloading/prefetch/no_state_prefetch/no_state_prefetch_test_utils.h"
#include "chrome/browser/preloading/scoped_prewarm_feature_list.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/resource_coordinator/tab_lifecycle_unit_external.h"
#include "chrome/browser/sessions/session_restore.h"
#include "chrome/browser/sessions/session_restore_test_helper.h"
#include "chrome/browser/sessions/session_service_factory.h"
#include "chrome/browser/sessions/session_service_test_helper.h"
#include "chrome/browser/sessions/tab_restore_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_live_tab_context.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
#include "chrome/browser/ui/navigator/browser_navigator.h"
#include "chrome/browser/ui/navigator/browser_navigator_params.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/embedder_support/user_agent_utils.h"
#include "components/keep_alive_registry/keep_alive_types.h"
#include "components/keep_alive_registry/scoped_keep_alive.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_handle.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_histograms.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_manager.h"
#include "components/no_state_prefetch/common/no_state_prefetch_origin.h"
#include "components/page_load_metrics/browser/observers/abandoned_page_load_metrics_observer.h"
#include "components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer.h"
#include "components/page_load_metrics/browser/observers/service_worker_page_load_metrics_observer.h"
#include "components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.h"
#include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h"
#include "components/page_load_metrics/browser/page_load_tracker.h"
#include "components/prefs/pref_service.h"
#include "components/sessions/content/content_test_helper.h"
#include "components/sessions/core/tab_restore_service.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/tracing_controller.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_paths.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/referrer.h"
#include "content/public/common/result_codes.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/hit_test_region_observer.h"
#include "content/public/test/navigation_handle_observer.h"
#include "content/public/test/no_renderer_crashes_assertion.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.h"
#include "net/base/net_errors.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/network/public/cpp/features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/chrome_debug_urls.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/use_counter/metrics/css_property_id.mojom.h"
#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h"
#include "third_party/blink/public/mojom/use_counter/metrics/webdx_feature.mojom-shared.h"
#include "ui/gfx/geometry/size.h"
#include "url/gurl.h"

using page_load_metrics::PageEndReason;
using page_load_metrics::PageLoadMetricsTestWaiter;
using TimingField = page_load_metrics::PageLoadMetricsTestWaiter::TimingField;
using WebDXFeature = blink::mojom::WebDXFeature;
using WebFeature = blink::mojom::WebFeature;
using testing::SizeIs;
using testing::UnorderedElementsAre;
using NoStatePrefetch = ukm::builders::NoStatePrefetch;
using PageLoad = ukm::builders::PageLoad;
using HistoryNavigation = ukm::builders::HistoryNavigation;
using trace_analyzer::Query;
using trace_analyzer::TraceAnalyzer;
using trace_analyzer::TraceEventVector;

namespace {

constexpr char kCacheablePathPrefix[] = "/cacheable";

const char kResponseWithNoStore[] =
    "HTTP/1.1 200 OK\r\n"
    "Content-Type: text/html; charset=utf-8\r\n"
    "Cache-Control: no-store\r\n"
    "\r\n"
    "The server speaks HTTP!";

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},
        // TODO(crbug.com/40248833): Use HTTPS URLs in tests to avoid having to
        // disable this feature.
        {features::kHttpsUpgrades});
  }

  PageLoadMetricsBrowserTest(const PageLoadMetricsBrowserTest&) = delete;
  PageLoadMetricsBrowserTest& operator=(const PageLoadMetricsBrowserTest&) =
      delete;

  ~PageLoadMetricsBrowserTest() override = default;

 protected:
  void SetUpOnMainThread() override {
    InProcessBrowserTest::SetUpOnMainThread();
    host_resolver()->AddRule("*", "127.0.0.1");
    embedded_test_server()->RegisterRequestHandler(
        base::BindRepeating(&HandleCachableRequestHandler));
  }

  void PreRunTestOnMainThread() override {
    InProcessBrowserTest::PreRunTestOnMainThread();

    histogram_tester_ = std::make_unique<base::HistogramTester>();
    test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
  }

  // Force navigation to a new page, so the currently tracked page load runs its
  // OnComplete callback. You should prefer to use PageLoadMetricsTestWaiter,
  // and only use NavigateToUntrackedUrl for cases where the waiter isn't
  // sufficient.
  void NavigateToUntrackedUrl() {
    ASSERT_TRUE(
        ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
  }

  int64_t GetUKMPageLoadMetric(std::string metric_name) {
    std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries =
        test_ukm_recorder_->GetMergedEntriesByName(PageLoad::kEntryName);

    EXPECT_EQ(1ul, merged_entries.size());
    const auto& kv = merged_entries.begin();
    const int64_t* recorded =
        ukm::TestUkmRecorder::GetEntryMetric(kv->second.get(), metric_name);
    EXPECT_TRUE(recorded != nullptr);
    return (*recorded);
  }

  void MakeComponentFullscreen(const std::string& id) {
    EXPECT_TRUE(content::ExecJs(
        browser()->GetTabStripModel()->GetActiveWebContents(),
        "document.getElementById(\"" + id + "\").webkitRequestFullscreen();"));
  }

  std::string GetRecordedPageLoadMetricNames() {
    auto entries = histogram_tester_->GetTotalCountsForPrefix("PageLoad.");
    std::vector<std::string> names = base::ToVector(
        entries, &base::HistogramTester::CountsMap::value_type::first);
    return base::JoinString(names, ",");
  }

  bool NoPageLoadMetricsRecorded() {
    // Determine whether any 'public' page load metrics are recorded. We exclude
    // 'internal' metrics as these may be recorded for debugging purposes, and
    // abandonment-related metrics, which are expected to be recorded for all
    // kinds of navigations.
    size_t total_pageload_histograms = 0;

    // We filter out WebUI and NonTabWebUI metrics because the browser may load
    // background WebUIs (e.g., side panels, hidden WebUIs) during test
    // execution independent of the explicit test navigations. These background
    // loads shouldn't fail tests that assert no standard page load metrics are
    // recorded. Examples of such metrics include:
    //  - PageLoad.PaintTiming.NavigationToFirstContentfulPaint.WebUI
    //  - PageLoad.PaintTiming.NavigationToLargestContentfulPaint2.NonTabWebUI
    auto is_webui_metric = [](const std::string& name) {
      static constexpr const char* kWebUIPrefixes[] = {
          "PageLoad.PaintTiming.NavigationToFirstContentfulPaint.WebUI",
          "PageLoad.PaintTiming.NavigationToLargestContentfulPaint.WebUI",
          "PageLoad.PaintTiming.NavigationToFirstContentfulPaint.NonTabWebUI",
          ("PageLoad.PaintTiming.NavigationToLargestContentfulPaint2."
           "NonTabWebUI"),
      };
      for (const char* prefix : kWebUIPrefixes) {
        if (base::StartsWith(name, prefix, base::CompareCase::SENSITIVE)) {
          return true;
        }
      }
      return false;
    };

    for (const auto& entry :
         histogram_tester_->GetTotalCountsForPrefix("PageLoad.")) {
      if (!base::StartsWith(entry.first, "PageLoad.Internal.",
                            base::CompareCase::SENSITIVE) &&
          !base::StartsWith(entry.first,
                            internal::kAbandonedPageLoadMetricsHistogramPrefix,
                            base::CompareCase::SENSITIVE) &&
          !is_webui_metric(entry.first)) {
        LOG(INFO) << "UNEXPECTED METRIC: " << entry.first;
        total_pageload_histograms++;
      }
    }
    return total_pageload_histograms == 0;
  }

  std::unique_ptr<PageLoadMetricsTestWaiter> CreatePageLoadMetricsTestWaiter(
      const char* observer_name,
      content::WebContents* web_contents = nullptr) {
    if (!web_contents)
      web_contents = browser()->GetTabStripModel()->GetActiveWebContents();
    return std::make_unique<PageLoadMetricsTestWaiter>(web_contents,
                                                       observer_name);
  }

  // Triggers nostate prefetch of |url|.
  void TriggerNoStatePrefetch(const GURL& url) {
    prerender::NoStatePrefetchManager* no_state_prefetch_manager =
        prerender::NoStatePrefetchManagerFactory::GetForBrowserContext(
            browser()->GetProfile());
    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);

    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->StartPrefetchingFromLinkRelPrerender(
            /*process_id=*/-1, /*route_id=*/-1, url,
            blink::mojom::PrerenderTriggerType::kLinkRelPrerender,
            content::Referrer(), url::Origin::Create(url), 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);
    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::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);
    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));
      }
    }
  }

  Profile* profile() const { return browser()->GetProfile(); }

  content::WebContents* web_contents() const {
    return browser()->GetTabStripModel()->GetActiveWebContents();
  }

  content::RenderFrameHost* RenderFrameHost() const {
    return web_contents()->GetPrimaryMainFrame();
  }
  test::ScopedPrewarmFeatureList scoped_prewarm_feature_list_{
      test::ScopedPrewarmFeatureList::PrewarmState::kDisabled};
  base::test::ScopedFeatureList scoped_feature_list_;
  std::unique_ptr<base::HistogramTester> histogram_tester_;
  std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
};

// Verifies that the first navigation in a new window is classified as
// kNewWindow.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, NewWindowClassification) {
  ASSERT_TRUE(embedded_test_server()->Start());
  const GURL url = embedded_test_server()->GetURL("/title1.html");

  // Ensure browser startup is complete.
  ASSERT_TRUE(AfterStartupTaskUtils::IsBrowserStartupComplete());

  base::HistogramTester histogram_tester;
  NavigateParams nav_params(browser(), url, ui::PAGE_TRANSITION_LINK);
  nav_params.disposition = WindowOpenDisposition::NEW_WINDOW;
  Navigate(&nav_params);

  BrowserWindowInterface* new_browser =
      GetLastActiveBrowserWindowInterfaceWithAnyProfile();
  PageLoadMetricsTestWaiter waiter(
      new_browser->GetTabStripModel()->GetActiveWebContents());
  waiter.AddPageExpectation(TimingField::kFirstContentfulPaint);
  waiter.AddPageExpectation(TimingField::kLargestContentfulPaint);
  waiter.AddPageExpectation(TimingField::kLoadEvent);
  waiter.Wait();

  // Navigate away to force LCP logging.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(new_browser, GURL(url::kAboutBlankURL)));

  // Verify NewWindow suffix histograms are logged.
  histogram_tester.ExpectTotalCount(
      "PageLoad.PaintTiming.NavigationToFirstContentfulPaint.NewWindow", 1);
  histogram_tester.ExpectTotalCount(
      "PageLoad.PaintTiming.NavigationToLargestContentfulPaint2.NewWindow", 1);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PageLCPImagePriority) {
  // Waiter to ensure main content is loaded.
  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  waiter->AddPageExpectation(TimingField::kLargestContentfulPaint);

  const char kHtmlHttpResponseHeader[] =
      "HTTP/1.1 200 OK\r\n"
      "Content-Type: text/html; charset=utf-8\r\n"
      "\r\n";
  const char kImgHttpResponseHeader[] =
      "HTTP/1.1 200 OK\r\n"
      "Content-Type: image/png\r\n"
      "\r\n";
  auto main_html_response =
      std::make_unique<net::test_server::ControllableHttpResponse>(
          embedded_test_server(), "/mock_page.html",
          false /*relative_url_is_prefix*/);
  auto img_response =
      std::make_unique<net::test_server::ControllableHttpResponse>(
          embedded_test_server(), "/images/lcp.jpg",
          false /*relative_url_is_prefix*/);

  ASSERT_TRUE(embedded_test_server()->Start());

  // File is under content/test/data/
  std::string file_contents;
  {
    base::ScopedAllowBlockingForTesting allow_io;
    base::FilePath test_dir;
    ASSERT_TRUE(base::PathService::Get(content::DIR_TEST_DATA, &test_dir));
    base::FilePath file_name = test_dir.AppendASCII("single_face.jpg");
    ASSERT_TRUE(base::ReadFileToString(file_name, &file_contents));
  }

  browser()->OpenURL(
      content::OpenURLParams(embedded_test_server()->GetURL("/mock_page.html"),
                             content::Referrer(),
                             WindowOpenDisposition::CURRENT_TAB,
                             ui::PAGE_TRANSITION_TYPED, false),
      /*navigation_handle_callback=*/{});

  main_html_response->WaitForRequest();
  main_html_response->Send(kHtmlHttpResponseHeader);
  main_html_response->Send(
      "<html><body><img src=\"/images/lcp.jpg\"></body></html>");
  main_html_response->Done();

  img_response->WaitForRequest();

  // Force layout and thus the visibility-based priority to be set, before the
  // loading is finished.
  content::EvalJsResult result =
      EvalJs(browser()->GetTabStripModel()->GetActiveWebContents(), R"(
      new Promise(resolve => {
        const forceLayout = () => {
          document.querySelector('img').offsetTop;
          resolve();
        };
        if (document.querySelector('img')) {
          forceLayout();
        } else {
          // Wait for DOMContentLoaded to ensure <img> is inserted.
          document.addEventListener('DOMContentLoaded', forceLayout);
        }
      })
  )");
  EXPECT_TRUE(result.is_ok());

  img_response->Send(kImgHttpResponseHeader);
  img_response->Send(file_contents);
  img_response->Done();

  // Wait on an LCP entry to make sure we have one to report when navigating
  // away.
  content::EvalJsResult result2 =
      EvalJs(browser()->GetTabStripModel()->GetActiveWebContents(), R"(
 (async () => {
   await new Promise(resolve => {
     (new PerformanceObserver(list => {
       const entries = list.getEntries();
       for (let entry of entries) {
         if (entry.url.includes('images')) {resolve()}
       }
     }))
     .observe({type: 'largest-contentful-paint', buffered: true});
 })})())");
  EXPECT_TRUE(result2.is_ok());
  waiter->Wait();

  // LCP is collected only at the end of the page lifecycle. Navigate to
  // flush.
  NavigateToUntrackedUrl();

  // Image should be loaded with `net::MEDIUM` priority because the image is
  // visible.
  int64_t value = GetUKMPageLoadMetric(
      PageLoad::PageLoad::
          kPaintTiming_LargestContentfulPaintRequestPriorityName);
  ASSERT_EQ(value, static_cast<int>(net::MEDIUM));
}

class PageLoadMetricsBrowserTestAnimatedLCP
    : public PageLoadMetricsBrowserTest {
 protected:
  void test_animated_image_lcp(bool smaller, bool animated) {
    // Waiter to ensure main content is loaded.
    auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
    waiter->AddPageExpectation(TimingField::kLoadEvent);
    waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
    waiter->AddPageExpectation(TimingField::kLargestContentfulPaint);

    const char kHtmlHttpResponseHeader[] =
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html; charset=utf-8\r\n"
        "\r\n";
    const char kImgHttpResponseHeader[] =
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: image/png\r\n"
        "\r\n";
    auto main_html_response =
        std::make_unique<net::test_server::ControllableHttpResponse>(
            embedded_test_server(), "/mock_page.html",
            false /*relative_url_is_prefix*/);
    auto img_response =
        std::make_unique<net::test_server::ControllableHttpResponse>(
            embedded_test_server(),
            animated ? "/images/animated-delayed.png" : "/images/delayed.jpg",
            false /*relative_url_is_prefix*/);

    ASSERT_TRUE(embedded_test_server()->Start());

    // File is under content/test/data/
    const std::string file_name_string =
        animated ? "animated.png" : "single_face.jpg";
    std::string file_contents;
    // The first_frame_size number for the animated case (262), represents the
    // first frame of the animated PNG + an extra chunk enabling the decoder to
    // understand the first frame is done and decode it.
    // For the non-animated case (5000), it's an arbitrary number that
    // represents a part of the JPEG's frame.
    const unsigned first_frame_size = animated ? 262 : 5000;

    // Read the animated image into two frames.
    {
      base::ScopedAllowBlockingForTesting allow_io;
      base::FilePath test_dir;
      ASSERT_TRUE(base::PathService::Get(content::DIR_TEST_DATA, &test_dir));
      base::FilePath file_name = test_dir.AppendASCII(file_name_string);
      ASSERT_TRUE(base::ReadFileToString(file_name, &file_contents));
    }
    // Split the contents into 2 frames
    std::string first_frame = file_contents.substr(0, first_frame_size);
    std::string second_frame = file_contents.substr(first_frame_size);

    browser()->OpenURL(
        content::OpenURLParams(
            embedded_test_server()->GetURL("/mock_page.html"),
            content::Referrer(), WindowOpenDisposition::CURRENT_TAB,
            ui::PAGE_TRANSITION_TYPED, false),
        /*navigation_handle_callback=*/{});

    main_html_response->WaitForRequest();
    main_html_response->Send(kHtmlHttpResponseHeader);
    main_html_response->Send(
        animated ? "<html><body></body><img "
                   "src=\"/images/animated-delayed.png\"></script></html>"
                 : "<html><body></body><img "
                   "src=\"/images/delayed.jpg\"></script></html>");
    main_html_response->Done();

    img_response->WaitForRequest();
    img_response->Send(kImgHttpResponseHeader);
    img_response->Send(first_frame);

    // Trigger a double rAF and wait a bit, then take a timestamp that's after
    // the presentation time of the first frame.
    // Then wait some more to ensure the timestamp is not too close to the point
    // where the second frame is sent.
    content::EvalJsResult result =
        EvalJs(browser()->GetTabStripModel()->GetActiveWebContents(), R"(
(async () => {
  const double_raf = () => {
    return new Promise(r => {
      requestAnimationFrame(()=>requestAnimationFrame(r));
    })
  };
  await double_raf();
  await new Promise(r => setTimeout(r, 500));
  const timestamp = performance.now();
  await new Promise(r => setTimeout(r, 50));
  return timestamp;
})();)");
    EXPECT_TRUE(result.is_ok());
    double timestamp = result.ExtractDouble();

    img_response->Send(second_frame);
    img_response->Done();

    // Wait on an LCP entry to make sure we have one to report when navigating
    // away.
    content::EvalJsResult result2 =
        EvalJs(browser()->GetTabStripModel()->GetActiveWebContents(), R"(
 (async () => {
   await new Promise(resolve => {
     (new PerformanceObserver(list => {
       const entries = list.getEntries();
       for (let entry of entries) {
         if (entry.url.includes('images')) {resolve()}
       }
     }))
     .observe({type: 'largest-contentful-paint', buffered: true});
 })})())");
    EXPECT_TRUE(result2.is_ok());
    waiter->Wait();

    // LCP is collected only at the end of the page lifecycle. Navigate to
    // flush.
    NavigateToUntrackedUrl();

    int64_t value = GetUKMPageLoadMetric(
        PageLoad::kPaintTiming_NavigationToLargestContentfulPaint2Name);

    if (smaller) {
      ASSERT_LT(value, timestamp);
    } else {
      ASSERT_GT(value, timestamp);
    }
  }
};

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, NoNavigation) {
  ASSERT_TRUE(embedded_test_server()->Start());
  EXPECT_TRUE(NoPageLoadMetricsRecorded())
      << "Recorded metrics: " << GetRecordedPageLoadMetricNames();
}

// TODO(crbug.com/40839280): Re-enable this test
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       DISABLED_MainFrameViewportRect) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url = embedded_test_server()->GetURL(
      "a.com", "/scroll/scrollable_page_with_content.html");

  auto main_frame_viewport_rect_expectation_waiter =
      CreatePageLoadMetricsTestWaiter("waiter");

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();

  int side_scrollbar_width =
      EvalJs(web_contents,
             "window.innerWidth - document.documentElement.clientWidth")
          .ExtractInt();
  int bottom_scrollbar_height =
      EvalJs(web_contents,
             "window.innerHeight - document.documentElement.clientHeight")
          .ExtractInt();

  content::RenderWidgetHostView* guest_host_view =
      web_contents->GetRenderWidgetHostView();
  gfx::Size viewport_size = guest_host_view->GetVisibleViewportSize();
  viewport_size -= gfx::Size(side_scrollbar_width, bottom_scrollbar_height);

  main_frame_viewport_rect_expectation_waiter
      ->AddMainFrameViewportRectExpectation(
          gfx::Rect(5000, 5000, viewport_size.width(), viewport_size.height()));

  ASSERT_TRUE(ExecJs(web_contents, "window.scrollTo(5000, 5000)"));

  main_frame_viewport_rect_expectation_waiter->Wait();
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, NewPage) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url = embedded_test_server()->GetURL("/title1.html");

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kFirstPaint);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(internal::kHistogramDomContentLoaded, 1);
  histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 1);

  // Force navigation to another page, which should force logging of histograms
  // persisted at the end of the page load lifetime.
  NavigateToUntrackedUrl();
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramPageTimingForegroundDuration, 1);

  VerifyBasicPageLoadUkms(url);

  const auto& nostate_prefetch_entries =
      test_ukm_recorder_->GetMergedEntriesByName(NoStatePrefetch::kEntryName);
  EXPECT_EQ(0u, nostate_prefetch_entries.size());

  VerifyNavigationMetrics({url});
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, Redirect) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL final_url = embedded_test_server()->GetURL("/title1.html");
  GURL first_url =
      embedded_test_server()->GetURL("/server-redirect?" + final_url.spec());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kFirstPaint);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), first_url));
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(internal::kHistogramDomContentLoaded, 1);
  histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 1);

  // Force navigation to another page, which should force logging of histograms
  // persisted at the end of the page load lifetime.
  NavigateToUntrackedUrl();
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramPageTimingForegroundDuration, 1);

  VerifyBasicPageLoadUkms(final_url);

  const auto& nostate_prefetch_entries =
      test_ukm_recorder_->GetMergedEntriesByName(NoStatePrefetch::kEntryName);
  EXPECT_EQ(0u, nostate_prefetch_entries.size());

  VerifyNavigationMetrics({final_url});
}

// 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");
  waiter->AddPageExpectation(TimingField::kFirstPaint);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  waiter->Wait();

  // Force navigation to another page, which should force logging of histograms
  // persisted at the end of the page load lifetime.
  NavigateToUntrackedUrl();
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramPageTimingForegroundDuration, 1);

  const auto& entries =
      test_ukm_recorder_->GetMergedEntriesByName(NoStatePrefetch::kEntryName);
  EXPECT_EQ(1u, entries.size());
  for (const auto& kv : entries) {
    test_ukm_recorder_->ExpectEntrySourceHasUrl(kv.second.get(), url);
    // UKM metrics related to attempted nostate prefetch should be recorded.
    EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
        kv.second.get(), NoStatePrefetch::kPrefetchedRecently_FinalStatusName));
    EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
        kv.second.get(), NoStatePrefetch::kPrefetchedRecently_OriginName));
    EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(
        kv.second.get(), NoStatePrefetch::kPrefetchedRecently_PrefetchAgeName));
  }

  VerifyNavigationMetrics({url});
}

class PageLoadMetricsBrowserTestWithInitialWebUIParam
    : public PageLoadMetricsBrowserTest,
      public ::testing::WithParamInterface<bool> {
 public:
  PageLoadMetricsBrowserTestWithInitialWebUIParam() {
    if (GetParam()) {
      sub_feature_list_.InitWithFeatures(
          {features::kInitialWebUI, features::kWebUIReloadButton}, {});
    } else {
      sub_feature_list_.InitWithFeatures(
          {}, {features::kInitialWebUI, features::kWebUIReloadButton});
    }
  }

 private:
  base::test::ScopedFeatureList sub_feature_list_;
};

IN_PROC_BROWSER_TEST_P(PageLoadMetricsBrowserTestWithInitialWebUIParam,
                       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());

    const auto& kv = entries.begin();
    EXPECT_FALSE(test_ukm_recorder_->EntryHasMetric(kv->second.get(),
                                                    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();

  {
    auto entries =
        test_ukm_recorder_->GetMergedEntriesByName(PageLoad::kEntryName);
    EXPECT_EQ(1u, entries.size());

    const auto& kv = entries.begin();
    EXPECT_TRUE(test_ukm_recorder_->EntryHasMetric(kv->second.get(),
                                                   PageLoad::kWasCachedName));
  }

  VerifyNavigationMetrics({url});
}

INSTANTIATE_TEST_SUITE_P(All,
                         PageLoadMetricsBrowserTestWithInitialWebUIParam,
                         ::testing::Bool());

// Test that we log kMainFrameResource_RequestHasNoStore when response has
// cache-control:no-store response header.
IN_PROC_BROWSER_TEST_P(PageLoadMetricsBrowserTestWithInitialWebUIParam,
                       MainFrameHasNoStore) {
  // Create a HTTP response to control main-frame navigation to send no-store
  // response.
  net::test_server::ControllableHttpResponse response(embedded_test_server(),
                                                      "/main_document");

  ASSERT_TRUE(embedded_test_server()->Start());

  const GURL kUrl = embedded_test_server()->GetURL("/main_document");

  // Load the document and specify no-store for the main resource.
  content::TestNavigationManager navigation_manager(web_contents(), kUrl);
  browser()->OpenURL(content::OpenURLParams(kUrl, content::Referrer(),
                                            WindowOpenDisposition::CURRENT_TAB,
                                            ui::PAGE_TRANSITION_TYPED, false),
                     /*navigation_handle_callback=*/{});

  // The navigation starts.
  EXPECT_TRUE(navigation_manager.WaitForRequestStart());
  navigation_manager.ResumeNavigation();

  // The response's headers are received.
  response.WaitForRequest();
  response.Send(kResponseWithNoStore);
  response.Done();
  EXPECT_TRUE(navigation_manager.WaitForResponse());
  navigation_manager.ResumeNavigation();
  ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
  NavigateToUntrackedUrl();

  auto entries =
      test_ukm_recorder_->GetMergedEntriesByName(PageLoad::kEntryName);
  if (GetParam()) {
    EXPECT_GE(entries.size(), 1u);
  } else {
    EXPECT_EQ(1u, entries.size());
  }

  size_t count = 0;
  for (const auto& kv : entries) {
    auto* entry = kv.second.get();
    const int64_t* metric_value = ukm::TestUkmRecorder::GetEntryMetric(
        entry, PageLoad::kMainFrameResource_RequestHasNoStoreName);
    if (metric_value && *metric_value == 1) {
      count++;
    }
  }
  EXPECT_EQ(1u, count);
}

// Test that we set kMainFrameResource_RequestHasNoStore to false when response
// has no cache-control:no-store header.
IN_PROC_BROWSER_TEST_P(PageLoadMetricsBrowserTestWithInitialWebUIParam,
                       MainFrameDoesnotHaveNoStore) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // Navigate to an URL to see if metrics are recorded.
  const GURL kUrl = embedded_test_server()->GetURL("/title1.html");

  // Navigate to the |kUrl| with no cache-control: no store header.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kUrl));
  NavigateToUntrackedUrl();

  auto entries =
      test_ukm_recorder_->GetMergedEntriesByName(PageLoad::kEntryName);
  if (GetParam()) {
    EXPECT_GE(entries.size(), 1u);
  } else {
    EXPECT_EQ(1u, entries.size());
  }

  size_t count = 0;
  for (const auto& kv : entries) {
    auto* entry = kv.second.get();
    const int64_t* metric_value = ukm::TestUkmRecorder::GetEntryMetric(
        entry, PageLoad::kMainFrameResource_RequestHasNoStoreName);
    if (metric_value && *metric_value == 0) {
      count++;
    }
  }
  EXPECT_EQ(1u, count);
}

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/40522104, with browser side navigation enabled,
  // navigations in new tabs were recorded as starting in the background. Here
  // we verify that navigations initiated in a new tab are recorded as happening
  // in the foreground.
  histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
  histogram_tester_->ExpectTotalCount(internal::kBackgroundHistogramLoad, 0);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, NoPaintForEmptyDocument) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/empty.html")));
  waiter->Wait();
  EXPECT_FALSE(waiter->DidObserveInPage(TimingField::kFirstPaint));

  histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 0);
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstContentfulPaint,
                                      0);
}

// TODO(crbug.com/41472183): Flaky on Win and Linux.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#define MAYBE_NoPaintForEmptyDocumentInChildFrame \
  DISABLED_NoPaintForEmptyDocumentInChildFrame
#else
#define MAYBE_NoPaintForEmptyDocumentInChildFrame \
  NoPaintForEmptyDocumentInChildFrame
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       MAYBE_NoPaintForEmptyDocumentInChildFrame) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL a_url(
      embedded_test_server()->GetURL("/page_load_metrics/empty_iframe.html"));

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  waiter->AddSubFrameExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), a_url));
  waiter->Wait();
  EXPECT_FALSE(waiter->DidObserveInPage(TimingField::kFirstPaint));

  histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 0);
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstContentfulPaint,
                                      0);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PaintInChildFrame) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL a_url(embedded_test_server()->GetURL("/page_load_metrics/iframe.html"));
  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  waiter->AddPageExpectation(TimingField::kFirstPaint);
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  waiter->AddSubFrameExpectation(TimingField::kFirstPaint);
  waiter->AddSubFrameExpectation(TimingField::kFirstContentfulPaint);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), a_url));
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 1);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PaintInDynamicChildFrame) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  waiter->AddPageExpectation(TimingField::kFirstPaint);
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  waiter->AddSubFrameExpectation(TimingField::kFirstPaint);
  waiter->AddSubFrameExpectation(TimingField::kFirstContentfulPaint);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(
                     "/page_load_metrics/dynamic_iframe.html")));
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 1);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PaintInMultipleChildFrames) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL a_url(embedded_test_server()->GetURL("/page_load_metrics/iframes.html"));

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);

  waiter->AddSubFrameExpectation(TimingField::kFirstPaint);
  waiter->AddPageExpectation(TimingField::kFirstPaint);

  waiter->AddSubFrameExpectation(TimingField::kFirstContentfulPaint);
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), a_url));
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 1);
}

// TODO(crbug.com/334416161): Re-enable this test on Windows.
#if BUILDFLAG(IS_WIN)
#define MAYBE_PaintInMainAndChildFrame DISABLED_PaintInMainAndChildFrame
#else
#define MAYBE_PaintInMainAndChildFrame PaintInMainAndChildFrame
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       MAYBE_PaintInMainAndChildFrame) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL a_url(embedded_test_server()->GetURL(
      "/page_load_metrics/main_frame_with_iframe.html"));

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  waiter->AddPageExpectation(TimingField::kFirstPaint);
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  waiter->AddSubFrameExpectation(TimingField::kFirstPaint);
  waiter->AddSubFrameExpectation(TimingField::kFirstContentfulPaint);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), a_url));
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 1);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, SameDocumentNavigation) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  GURL url = embedded_test_server()->GetURL("/title1.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(internal::kHistogramDomContentLoaded, 1);
  histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);

  // Perform a same-document navigation. No additional metrics should be logged.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/title1.html#hash")));
  NavigateToUntrackedUrl();

  histogram_tester_->ExpectTotalCount(internal::kHistogramDomContentLoaded, 1);
  histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);

  VerifyNavigationMetrics({url});
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, SameUrlNavigation) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  GURL url = embedded_test_server()->GetURL("/title1.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(internal::kHistogramDomContentLoaded, 1);
  histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 1);

  waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  waiter->Wait();

  VerifyNavigationMetrics({url});

  // We expect one histogram sample for each navigation to title1.html.
  histogram_tester_->ExpectTotalCount(internal::kHistogramDomContentLoaded, 2);
  histogram_tester_->ExpectTotalCount(internal::kHistogramLoad, 2);

  // Force navigation to another page, which should force logging of histograms
  // persisted at the end of the page load lifetime.
  NavigateToUntrackedUrl();

  VerifyNavigationMetrics({url, url});
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       DocWriteAbortsSubframeNavigation) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(
                     "/page_load_metrics/doc_write_aborts_subframe.html")));
  waiter->AddMinimumCompleteResourcesExpectation(4);
  waiter->Wait();
  EXPECT_FALSE(waiter->DidObserveInPage(TimingField::kFirstPaint));
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, NonHtmlMainResource) {
  ASSERT_TRUE(embedded_test_server()->Start());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/circle.svg")));
  NavigateToUntrackedUrl();
  EXPECT_TRUE(NoPageLoadMetricsRecorded())
      << "Recorded metrics: " << GetRecordedPageLoadMetricNames();
}

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()->GetTabStripModel()->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()->GetProfile()->GetDownloadManager(),
      1,  // == wait_count (only waiting for "download-test3.gif").
      content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/download-test3.gif")));
  downloads_observer.WaitForFinished();

  NavigateToUntrackedUrl();
  EXPECT_TRUE(NoPageLoadMetricsRecorded())
      << "Recorded metrics: " << GetRecordedPageLoadMetricNames();
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, NoDocumentWrite) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/title1.html")));
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstContentfulPaint,
                                      1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramActualNavigationStartToNavigationCommitSent, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramActualNavigationStartToParseStart, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramActualNavigationStartToDOMContentLoaded, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramActualNavigationStartToFirstContentfulPaint, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramNavigationCommitSentToParseStart, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramParseStartToDOMContentLoaded, 1);

  histogram_tester_->ExpectTotalCount(
      internal::kHistogramDocWriteBlockParseStartToFirstContentfulPaint, 0);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, DocumentWriteBlock) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(
                     "/page_load_metrics/document_write_script_block.html")));
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(
      internal::kHistogramDocWriteBlockParseStartToFirstContentfulPaint, 1);
}

// TODO(crbug.com/334416161): Re-enable this test on Windows.
#if BUILDFLAG(IS_WIN)
#define MAYBE_DocumentWriteReload DISABLED_DocumentWriteReload
#else
#define MAYBE_DocumentWriteReload DocumentWriteReload
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, MAYBE_DocumentWriteReload) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(
                     "/page_load_metrics/document_write_script_block.html")));
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(
      internal::kHistogramDocWriteBlockParseStartToFirstContentfulPaint, 1);

  // Reload should not log the histogram as the script is not blocked.
  waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(
                     "/page_load_metrics/document_write_script_block.html")));
  waiter->Wait();

  waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(
                     "/page_load_metrics/document_write_script_block.html")));
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(
      internal::kHistogramDocWriteBlockParseStartToFirstContentfulPaint, 1);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, DocumentWriteAsync) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(
                     "/page_load_metrics/document_write_async_script.html")));
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstContentfulPaint,
                                      1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramDocWriteBlockParseStartToFirstContentfulPaint, 0);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, DocumentWriteSameDomain) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL(
          "/page_load_metrics/document_write_external_script.html")));
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstContentfulPaint,
                                      1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramDocWriteBlockParseStartToFirstContentfulPaint, 0);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, NoDocumentWriteScript) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(
                     "/page_load_metrics/document_write_no_script.html")));
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstContentfulPaint,
                                      1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramDocWriteBlockParseStartToFirstContentfulPaint, 0);
}

// TODO(crbug.com/40516222): Flaky on Linux dbg.
// TODO(crbug.com/41328109): Now flaky on Win and Mac.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, DISABLED_BadXhtml) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // When an XHTML page contains invalid XML, it causes a paint of the error
  // message without a layout. Page load metrics currently treats this as an
  // error. Eventually, we'll fix this by special casing the handling of
  // documents with non-well-formed XML on the blink side. See
  // crbug.com/40476240 for more.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL("/page_load_metrics/badxml.xhtml")));
  NavigateToUntrackedUrl();

  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 0);

  histogram_tester_->ExpectBucketCount(
      page_load_metrics::internal::kErrorEvents,
      page_load_metrics::ERR_BAD_TIMING_IPC_INVALID_TIMING, 1);

  histogram_tester_->ExpectTotalCount(
      page_load_metrics::internal::kPageLoadTimingStatus, 1);
  histogram_tester_->ExpectBucketCount(
      page_load_metrics::internal::kPageLoadTimingStatus,
      page_load_metrics::internal::INVALID_ORDER_PARSE_START_FIRST_PAINT, 1);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PayloadSize) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL("/page_load_metrics/large.html")));
  waiter->Wait();

  // Payload histograms are only logged when a page load terminates, so force
  // navigation to another page.
  NavigateToUntrackedUrl();
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PayloadSizeChildFrame) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL("/page_load_metrics/large_iframe.html")));
  waiter->Wait();

  // Payload histograms are only logged when a page load terminates, so force
  // navigation to another page.
  NavigateToUntrackedUrl();
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       PayloadSizeIgnoresDownloads) {
  ASSERT_TRUE(embedded_test_server()->Start());

  content::DownloadTestObserverTerminal downloads_observer(
      browser()->GetProfile()->GetDownloadManager(),
      1,  // == wait_count (only waiting for "download-test1.lib").
      content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_FAIL);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(
                     "/page_load_metrics/download_anchor_click.html")));
  downloads_observer.WaitForFinished();

  NavigateToUntrackedUrl();
}

// Test UseCounter Features observed in the main frame are recorded, exactly
// once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       UseCounterFeaturesInMainFrame) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(
                     "/page_load_metrics/use_counter_features.html")));
  waiter->Wait();
  NavigateToUntrackedUrl();

  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kTextWholeText), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.MainFrame.Features",
      static_cast<int32_t>(WebFeature::kTextWholeText), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.MainFrame.Features",
      static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kNavigatorVibrate), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.MainFrame.Features",
      static_cast<int32_t>(WebFeature::kNavigatorVibrate), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kPageVisits), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.MainFrame.Features",
      static_cast<int32_t>(WebFeature::kPageVisits), 1);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       UseCounterCSSPropertiesInMainFrame) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(
                     "/page_load_metrics/use_counter_features.html")));
  waiter->Wait();
  NavigateToUntrackedUrl();

  // CSSPropertyID::kFontFamily
  histogram_tester_->ExpectBucketCount("Blink.UseCounter.CSSProperties", 6, 1);
  // CSSPropertyID::kFontSize
  histogram_tester_->ExpectBucketCount("Blink.UseCounter.CSSProperties", 7, 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.CSSProperties",
      blink::mojom::CSSSampleId::kTotalPagesMeasured, 1);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       UseCounterAnimatedCSSPropertiesInMainFrame) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(
                     "/page_load_metrics/use_counter_features.html")));
  waiter->Wait();
  NavigateToUntrackedUrl();

  // CSSPropertyID::kWidth
  histogram_tester_->ExpectBucketCount("Blink.UseCounter.AnimatedCSSProperties",
                                       161, 1);
  // CSSPropertyID::kMarginLeft
  histogram_tester_->ExpectBucketCount("Blink.UseCounter.AnimatedCSSProperties",
                                       91, 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.AnimatedCSSProperties",
      blink::mojom::CSSSampleId::kTotalPagesMeasured, 1);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       UseCounterCSSVisitedColumnRuleColorInMainFrame) {
  ASSERT_TRUE(embedded_test_server()->Start());

  histogram_tester_->ExpectBucketCount("Blink.UseCounter.Features",
                                       WebFeature::kVisitedColumnRuleColor, 0);

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(
                     "/page_load_metrics/use_counter_features.html")));
  waiter->Wait();
  NavigateToUntrackedUrl();

  histogram_tester_->ExpectBucketCount("Blink.UseCounter.Features",
                                       WebFeature::kVisitedColumnRuleColor, 1);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       UseCounterWebDXFeaturesInMainFrame) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(
                     "/page_load_metrics/use_counter_features.html")));
  waiter->Wait();
  NavigateToUntrackedUrl();

  histogram_tester_->ExpectBucketCount("Blink.UseCounter.WebDXFeatures",
                                       WebDXFeature::kPageVisits, 1);

  histogram_tester_->ExpectBucketCount("Blink.UseCounter.WebDXFeatures",
                                       WebDXFeature::kWebAnimations, 1);
}

class PageLoadMetricsBrowserTestWithAutoupgradesDisabled
    : public PageLoadMetricsBrowserTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    PageLoadMetricsBrowserTest::SetUpCommandLine(command_line);
    feature_list.InitAndDisableFeature(
        blink::features::kMixedContentAutoupgrade);
  }

 private:
  base::test::ScopedFeatureList feature_list;
};

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestWithAutoupgradesDisabled,
                       UseCounterFeaturesMixedContent) {
  // UseCounterFeaturesInMainFrame loads the test file on a loopback
  // address. Loopback is treated as a secure origin in most ways, but it
  // doesn't count as mixed content when it loads http://
  // subresources. Therefore, this test loads the test file on a real HTTPS
  // server.
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.AddDefaultHandlers(GetChromeTestDataDir());
  ASSERT_TRUE(https_server.Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      https_server.GetURL("/page_load_metrics/use_counter_features.html")));
  waiter->Wait();
  NavigateToUntrackedUrl();

  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kMixedContentAudio), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kMixedContentImage), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kMixedContentVideo), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kPageVisits), 1);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestWithAutoupgradesDisabled,
                       UseCounterCSSPropertiesMixedContent) {
  // UseCounterCSSPropertiesInMainFrame loads the test file on a loopback
  // address. Loopback is treated as a secure origin in most ways, but it
  // doesn't count as mixed content when it loads http://
  // subresources. Therefore, this test loads the test file on a real HTTPS
  // server.
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.AddDefaultHandlers(GetChromeTestDataDir());
  ASSERT_TRUE(https_server.Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      https_server.GetURL("/page_load_metrics/use_counter_features.html")));
  waiter->Wait();
  NavigateToUntrackedUrl();

  // CSSPropertyID::kFontFamily
  histogram_tester_->ExpectBucketCount("Blink.UseCounter.CSSProperties", 6, 1);
  // CSSPropertyID::kFontSize
  histogram_tester_->ExpectBucketCount("Blink.UseCounter.CSSProperties", 7, 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.CSSProperties",
      blink::mojom::CSSSampleId::kTotalPagesMeasured, 1);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestWithAutoupgradesDisabled,
                       UseCounterAnimatedCSSPropertiesMixedContent) {
  // UseCounterCSSPropertiesInMainFrame loads the test file on a loopback
  // address. Loopback is treated as a secure origin in most ways, but it
  // doesn't count as mixed content when it loads http://
  // subresources. Therefore, this test loads the test file on a real HTTPS
  // server.
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.AddDefaultHandlers(GetChromeTestDataDir());
  ASSERT_TRUE(https_server.Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      https_server.GetURL("/page_load_metrics/use_counter_features.html")));
  waiter->Wait();
  NavigateToUntrackedUrl();

  // CSSPropertyID::kWidth
  histogram_tester_->ExpectBucketCount("Blink.UseCounter.AnimatedCSSProperties",
                                       161, 1);
  // CSSPropertyID::kMarginLeft
  histogram_tester_->ExpectBucketCount("Blink.UseCounter.AnimatedCSSProperties",
                                       91, 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.AnimatedCSSProperties",
      blink::mojom::CSSSampleId::kTotalPagesMeasured, 1);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestWithAutoupgradesDisabled,
                       UseCounterWebDXFeaturesMixedContent) {
  // UseCounterWebDXFeaturesInMainFrame loads the test file on a loopback
  // address. Loopback is treated as a secure origin in most ways, but it
  // doesn't count as mixed content when it loads http://
  // subresources. Therefore, this test loads the test file on a real HTTPS
  // server.
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.AddDefaultHandlers(GetChromeTestDataDir());
  ASSERT_TRUE(https_server.Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      https_server.GetURL("/page_load_metrics/use_counter_features.html")));
  waiter->Wait();
  NavigateToUntrackedUrl();

  histogram_tester_->ExpectBucketCount("Blink.UseCounter.WebDXFeatures",
                                       WebDXFeature::kPageVisits, 1);

  histogram_tester_->ExpectBucketCount("Blink.UseCounter.WebDXFeatures",
                                       WebDXFeature::kWebAnimations, 1);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       UseCounterFeaturesInNonSecureMainFrame) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL(
          "non-secure.test", "/page_load_metrics/use_counter_features.html")));
  MakeComponentFullscreen("testvideo");
  waiter->Wait();
  NavigateToUntrackedUrl();

  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kTextWholeText), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.MainFrame.Features",
      static_cast<int32_t>(WebFeature::kTextWholeText), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.MainFrame.Features",
      static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kNavigatorVibrate), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.MainFrame.Features",
      static_cast<int32_t>(WebFeature::kNavigatorVibrate), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kPageVisits), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.MainFrame.Features",
      static_cast<int32_t>(WebFeature::kPageVisits), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kFullscreenInsecureOrigin), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.MainFrame.Features",
      static_cast<int32_t>(WebFeature::kFullscreenInsecureOrigin), 1);
}

// Test UseCounter UKM features observed.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       UseCounterUkmFeaturesLogged) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // Ensure that the previous page won't be stored in the back/forward cache, so
  // that the histogram will be recorded when the previous page is unloaded.
  // UKM/UMA logging after BFCache eviction is checked by
  // PageLoadMetricsBrowserTestWithBackForwardCache's
  // UseCounterUkmFeaturesLoggedOnBFCacheEviction test.
  browser()
      ->GetTabStripModel()
      ->GetActiveWebContents()
      ->GetController()
      .GetBackForwardCache()
      .DisableForTesting(content::BackForwardCache::TEST_REQUIRES_NO_CACHING);

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  GURL url = embedded_test_server()->GetURL(
      "/page_load_metrics/use_counter_features.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  MakeComponentFullscreen("testvideo");
  waiter->Wait();
  NavigateToUntrackedUrl();

  const auto& entries = test_ukm_recorder_->GetEntriesByName(
      ukm::builders::Blink_UseCounter::kEntryName);
  EXPECT_THAT(entries, SizeIs(3));
  std::vector<int64_t> ukm_features;
  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_ukm_recorder_->ExpectEntrySourceHasUrl(entry, url);
    test_ukm_recorder_->ExpectEntryMetric(
        entry, ukm::builders::Blink_UseCounter::kIsMainFrameFeatureName, 1);
    const auto* metric = test_ukm_recorder_->GetEntryMetric(
        entry, ukm::builders::Blink_UseCounter::kFeatureName);
    DCHECK(metric);
    ukm_features.push_back(*metric);
  }
  EXPECT_THAT(ukm_features,
              UnorderedElementsAre(
                  static_cast<int64_t>(WebFeature::kPageVisits),
                  static_cast<int64_t>(WebFeature::kFullscreenSecureOrigin),
                  static_cast<int64_t>(WebFeature::kNavigatorVibrate)));
}

// Test UseCounter UKM mixed content features observed.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestWithAutoupgradesDisabled,
                       UseCounterUkmMixedContentFeaturesLogged) {
  // As with UseCounterFeaturesMixedContent, load on a real HTTPS server to
  // trigger mixed content.
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.AddDefaultHandlers(GetChromeTestDataDir());
  ASSERT_TRUE(https_server.Start());

  // Ensure that the previous page won't be stored in the back/forward cache, so
  // that the histogram will be recorded when the previous page is unloaded.
  // UKM/UMA logging after BFCache eviction is checked by
  // PageLoadMetricsBrowserTestWithBackForwardCache's
  // UseCounterUkmFeaturesLoggedOnBFCacheEviction test.
  browser()
      ->GetTabStripModel()
      ->GetActiveWebContents()
      ->GetController()
      .GetBackForwardCache()
      .DisableForTesting(content::BackForwardCache::TEST_REQUIRES_NO_CACHING);

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  GURL url =
      https_server.GetURL("/page_load_metrics/use_counter_features.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  MakeComponentFullscreen("testvideo");
  waiter->Wait();
  NavigateToUntrackedUrl();

  const auto& entries = test_ukm_recorder_->GetEntriesByName(
      ukm::builders::Blink_UseCounter::kEntryName);
  EXPECT_THAT(entries, SizeIs(6));
  std::vector<int64_t> ukm_features;
  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_ukm_recorder_->ExpectEntrySourceHasUrl(entry, url);
    test_ukm_recorder_->ExpectEntryMetric(
        entry, ukm::builders::Blink_UseCounter::kIsMainFrameFeatureName, 1);
    const auto* metric = test_ukm_recorder_->GetEntryMetric(
        entry, ukm::builders::Blink_UseCounter::kFeatureName);
    DCHECK(metric);
    ukm_features.push_back(*metric);
  }
  EXPECT_THAT(ukm_features,
              UnorderedElementsAre(
                  static_cast<int64_t>(WebFeature::kPageVisits),
                  static_cast<int64_t>(WebFeature::kFullscreenSecureOrigin),
                  static_cast<int64_t>(WebFeature::kNavigatorVibrate),
                  static_cast<int64_t>(WebFeature::kMixedContentImage),
                  static_cast<int64_t>(WebFeature::kMixedContentAudio),
                  static_cast<int64_t>(WebFeature::kMixedContentVideo)));
}

// Test UseCounter Features observed in a child frame are recorded, exactly
// once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, UseCounterFeaturesInIframe) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL(
          "/page_load_metrics/use_counter_features_in_iframe.html")));
  waiter->Wait();
  NavigateToUntrackedUrl();

  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kTextWholeText), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kNavigatorVibrate), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kPageVisits), 1);
  // No feature but page visits should get counted.
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.MainFrame.Features",
      static_cast<int32_t>(WebFeature::kTextWholeText), 0);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.MainFrame.Features",
      static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 0);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.MainFrame.Features",
      static_cast<int32_t>(WebFeature::kNavigatorVibrate), 0);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.MainFrame.Features",
      static_cast<int32_t>(WebFeature::kPageVisits), 1);
}

// Test UseCounter Features observed in multiple child frames are recorded,
// exactly once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       UseCounterFeaturesInMultipleIframes) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL(
          "/page_load_metrics/use_counter_features_in_iframes.html")));
  waiter->Wait();
  NavigateToUntrackedUrl();

  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kTextWholeText), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kNavigatorVibrate), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kPageVisits), 1);
  // No feature but page visits should get counted.
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.MainFrame.Features",
      static_cast<int32_t>(WebFeature::kTextWholeText), 0);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.MainFrame.Features",
      static_cast<int32_t>(WebFeature::kV8Element_Animate_Method), 0);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.MainFrame.Features",
      static_cast<int32_t>(WebFeature::kNavigatorVibrate), 0);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.MainFrame.Features",
      static_cast<int32_t>(WebFeature::kPageVisits), 1);
}

// Test UseCounter CSS properties observed in a child frame are recorded,
// exactly once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       UseCounterCSSPropertiesInIframe) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL(
          "/page_load_metrics/use_counter_features_in_iframe.html")));
  waiter->Wait();
  NavigateToUntrackedUrl();

  // CSSPropertyID::kFontFamily
  histogram_tester_->ExpectBucketCount("Blink.UseCounter.CSSProperties", 6, 1);
  // CSSPropertyID::kFontSize
  histogram_tester_->ExpectBucketCount("Blink.UseCounter.CSSProperties", 7, 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.CSSProperties",
      blink::mojom::CSSSampleId::kTotalPagesMeasured, 1);
}

// Test UseCounter CSS Properties observed in multiple child frames are
// recorded, exactly once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       UseCounterCSSPropertiesInMultipleIframes) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL(
          "/page_load_metrics/use_counter_features_in_iframes.html")));
  waiter->Wait();
  NavigateToUntrackedUrl();

  // CSSPropertyID::kFontFamily
  histogram_tester_->ExpectBucketCount("Blink.UseCounter.CSSProperties", 6, 1);
  // CSSPropertyID::kFontSize
  histogram_tester_->ExpectBucketCount("Blink.UseCounter.CSSProperties", 7, 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.CSSProperties",
      blink::mojom::CSSSampleId::kTotalPagesMeasured, 1);
}

// Test UseCounter CSS properties observed in a child frame are recorded,
// exactly once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       UseCounterAnimatedCSSPropertiesInIframe) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL(
          "/page_load_metrics/use_counter_features_in_iframe.html")));
  waiter->Wait();
  NavigateToUntrackedUrl();

  // CSSPropertyID::kWidth
  histogram_tester_->ExpectBucketCount("Blink.UseCounter.AnimatedCSSProperties",
                                       161, 1);
  // CSSPropertyID::kMarginLeft
  histogram_tester_->ExpectBucketCount("Blink.UseCounter.AnimatedCSSProperties",
                                       91, 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.AnimatedCSSProperties",
      blink::mojom::CSSSampleId::kTotalPagesMeasured, 1);
}

// Test UseCounter CSS Properties observed in multiple child frames are
// recorded, exactly once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       UseCounterAnimatedCSSPropertiesInMultipleIframes) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL(
          "/page_load_metrics/use_counter_features_in_iframes.html")));
  waiter->Wait();
  NavigateToUntrackedUrl();

  // CSSPropertyID::kWidth
  histogram_tester_->ExpectBucketCount("Blink.UseCounter.AnimatedCSSProperties",
                                       161, 1);
  // CSSPropertyID::kMarginLeft
  histogram_tester_->ExpectBucketCount("Blink.UseCounter.AnimatedCSSProperties",
                                       91, 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.AnimatedCSSProperties",
      blink::mojom::CSSSampleId::kTotalPagesMeasured, 1);
}

// Test UseCounter WebDX Features observed in a child frame are recorded,
// exactly once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       UseCounterWebDXFeaturesInIframe) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL(
          "/page_load_metrics/use_counter_features_in_iframe.html")));
  waiter->Wait();
  NavigateToUntrackedUrl();

  histogram_tester_->ExpectBucketCount("Blink.UseCounter.WebDXFeatures",
                                       WebDXFeature::kPageVisits, 1);

  histogram_tester_->ExpectBucketCount("Blink.UseCounter.WebDXFeatures",
                                       WebDXFeature::kWebAnimations, 1);
}

// Test UseCounter WebDX Features observed in multiple child frames are
// recorded, exactly once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       UseCounterWebDXFeaturesInMultipleIframes) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL(
          "/page_load_metrics/use_counter_features_in_iframes.html")));
  waiter->Wait();
  NavigateToUntrackedUrl();

  histogram_tester_->ExpectBucketCount("Blink.UseCounter.WebDXFeatures",
                                       WebDXFeature::kPageVisits, 1);

  histogram_tester_->ExpectBucketCount("Blink.UseCounter.WebDXFeatures",
                                       WebDXFeature::kWebAnimations, 1);
}

// Test UseCounter Features observed for SVG pages.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       UseCounterObserveSVGImagePage) {
  ASSERT_TRUE(embedded_test_server()->Start());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL("/page_load_metrics/circle.svg")));
  NavigateToUntrackedUrl();

  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kPageVisits), 1);
}

// Test UseCounter Permissions Policy Usages in main frame.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       UseCounterPermissionsPolicyUsageInMainFrame) {
  auto test_feature = static_cast<blink::UseCounterFeature::EnumValue>(
      network::mojom::PermissionsPolicyFeature::kFullscreen);

  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddUseCounterFeatureExpectation({
      blink::mojom::UseCounterFeatureType::kPermissionsPolicyViolationEnforce,
      test_feature,
  });
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(
                     "/page_load_metrics/use_counter_features.html")));
  waiter->Wait();
  NavigateToUntrackedUrl();

  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.PermissionsPolicy.Violation.Enforce", test_feature, 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.PermissionsPolicy.Header2", test_feature, 1);
}

// Test UseCounter Permissions Policy Usages observed in child frame
// are recorded, exactly once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       UseCounterPermissionsPolicyUsageInIframe) {
  auto test_feature = static_cast<blink::UseCounterFeature::EnumValue>(
      network::mojom::PermissionsPolicyFeature::kFullscreen);

  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddUseCounterFeatureExpectation({
      blink::mojom::UseCounterFeatureType::kPermissionsPolicyViolationEnforce,
      test_feature,
  });
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL(
          "/page_load_metrics/use_counter_features_in_iframe.html")));
  waiter->Wait();
  NavigateToUntrackedUrl();

  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.PermissionsPolicy.Violation.Enforce", test_feature, 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.PermissionsPolicy.Header2", test_feature, 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.PermissionsPolicy.Allow2", test_feature, 1);
}

// Test UseCounter Permissions Policy Usages observed in multiple child frames
// are recorded, exactly once per feature.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       UseCounterPermissionsPolicyUsageInMultipleIframes) {
  auto test_feature = static_cast<blink::UseCounterFeature::EnumValue>(
      network::mojom::PermissionsPolicyFeature::kFullscreen);

  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddUseCounterFeatureExpectation({
      blink::mojom::UseCounterFeatureType::kPermissionsPolicyViolationEnforce,
      test_feature,
  });
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL(
          "/page_load_metrics/use_counter_features_in_iframes.html")));
  waiter->Wait();
  NavigateToUntrackedUrl();

  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.PermissionsPolicy.Violation.Enforce", test_feature, 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.PermissionsPolicy.Header2", test_feature, 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.PermissionsPolicy.Allow2", test_feature, 1);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, LoadingMetrics) {
  ASSERT_TRUE(embedded_test_server()->Start());
  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadTimingInfo);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/title1.html")));
  // Waits until nonzero loading metrics are seen.
  waiter->Wait();
}

class SessionRestorePageLoadMetricsBrowserTest
    : public PageLoadMetricsBrowserTest {
 public:
  SessionRestorePageLoadMetricsBrowserTest() = default;

  SessionRestorePageLoadMetricsBrowserTest(
      const SessionRestorePageLoadMetricsBrowserTest&) = delete;
  SessionRestorePageLoadMetricsBrowserTest& operator=(
      const SessionRestorePageLoadMetricsBrowserTest&) = delete;

  // PageLoadMetricsBrowserTest:
  void SetUpOnMainThread() override {
    PageLoadMetricsBrowserTest::SetUpOnMainThread();
    ASSERT_TRUE(embedded_test_server()->Start());
  }

  BrowserWindowInterface* QuitBrowserAndRestore(
      BrowserWindowInterface* browser) {
    Profile* const profile = browser->GetProfile();

    SessionStartupPref::SetStartupPref(
        profile, SessionStartupPref(SessionStartupPref::LAST));
#if BUILDFLAG(IS_CHROMEOS)
    SessionServiceTestHelper helper(profile);
    helper.SetForceBrowserNotAliveWithNoWindows(true);
#endif

    auto keep_alive = std::make_unique<ScopedKeepAlive>(
        KeepAliveOrigin::SESSION_RESTORE, KeepAliveRestartOption::DISABLED);
    auto profile_keep_alive = std::make_unique<ScopedProfileKeepAlive>(
        profile, ProfileKeepAliveOrigin::kBrowserWindow);
    CloseBrowserSynchronously(browser);

    // Create a new window, which should trigger session restore.
    chrome::NewEmptyWindow(profile);
    SessionRestoreTestHelper().Wait();
    return GetLastActiveBrowserWindowInterfaceWithAnyProfile();
  }

  void WaitForTabsToLoad(TabStripModel* tab_strip_model) {
    for (int i = 0; i < tab_strip_model->count(); ++i) {
      content::WebContents* const contents =
          tab_strip_model->GetWebContentsAt(i);
      contents->GetController().LoadIfNecessary();
      ASSERT_TRUE(content::WaitForLoadStop(contents));
    }
  }

  // The PageLoadMetricsTestWaiter can observe first meaningful paints on these
  // test pages while not on other simple pages such as /title1.html.
  GURL GetTestURL() const {
    return embedded_test_server()->GetURL(
        "/page_load_metrics/main_frame_with_iframe.html");
  }

  GURL GetTestURL2() const {
    return embedded_test_server()->GetURL("/title2.html");
  }
};

IN_PROC_BROWSER_TEST_F(SessionRestorePageLoadMetricsBrowserTest,
                       InitialVisibilityOfSingleRestoredTab) {
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetTestURL()));
  ASSERT_NO_FATAL_FAILURE(
      WaitForTabsToLoad(QuitBrowserAndRestore(browser())->GetTabStripModel()));
}

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);

  BrowserWindowInterface* const new_browser = QuitBrowserAndRestore(browser());
  ASSERT_NO_FATAL_FAILURE(WaitForTabsToLoad(new_browser->GetTabStripModel()));

  TabStripModel* const tab_strip = new_browser->GetTabStripModel();
  ASSERT_TRUE(tab_strip);
  ASSERT_EQ(2, tab_strip->count());
}

using PageLoadMetricsResourceLoadBrowserTest = PageLoadMetricsBrowserTest;

// TODO(crbug.com/41412649) Disabled due to flaky timeouts on all platforms.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsResourceLoadBrowserTest,
                       DISABLED_ReceivedAggregateResourceDataLength) {
  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  content::SetupCrossSiteRedirector(embedded_test_server());
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(
                     "foo.com", "/cross_site_iframe_factory.html?foo")));
  waiter->Wait();
  base::ByteSize one_frame_page_size = waiter->current_network_bytes();

  waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL(
          "a.com", "/cross_site_iframe_factory.html?a(b,c,d(e,f,g))")));
  // Verify that 7 iframes are fetched, with some amount of tolerance since
  // favicon is fetched only once.
  waiter->AddMinimumNetworkBytesExpectation(
      7 * (one_frame_page_size - base::ByteSize(100)).AsByteSize());
  waiter->Wait();
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsResourceLoadBrowserTest,
                       ChunkedResponse_OverheadDoesNotCountForBodyBytes) {
  const char kHttpResponseHeader[] =
      "HTTP/1.1 200 OK\r\n"
      "Content-Type: text/html; charset=utf-8\r\n"
      "Transfer-Encoding: chunked\r\n"
      "\r\n";
  const int kChunkSize = 5;
  const int kNumChunks = 5;
  auto main_response =
      std::make_unique<net::test_server::ControllableHttpResponse>(
          embedded_test_server(), "/mock_page.html",
          true /*relative_url_is_prefix*/);
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");

  browser()->OpenURL(
      content::OpenURLParams(embedded_test_server()->GetURL("/mock_page.html"),
                             content::Referrer(),
                             WindowOpenDisposition::CURRENT_TAB,
                             ui::PAGE_TRANSITION_TYPED, false),
      /*navigation_handle_callback=*/{});

  main_response->WaitForRequest();
  main_response->Send(kHttpResponseHeader);
  for (int i = 0; i < kNumChunks; i++) {
    main_response->Send(base::NumberToString(kChunkSize));
    main_response->Send("\r\n");
    main_response->Send(std::string(kChunkSize, '*'));
    main_response->Send("\r\n");
  }
  main_response->Done();
  waiter->AddMinimumCompleteResourcesExpectation(1);
  waiter->Wait();

  // Verify that overheads for each chunk are not reported as body bytes.
  EXPECT_EQ(waiter->current_network_body_bytes().InBytes(),
            kChunkSize * kNumChunks);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsResourceLoadBrowserTest,
                       ReceivedCompleteResources) {
  const char kHttpResponseHeader[] =
      "HTTP/1.1 200 OK\r\n"
      "Content-Type: text/html; charset=utf-8\r\n"
      "\r\n";
  auto main_html_response =
      std::make_unique<net::test_server::ControllableHttpResponse>(
          embedded_test_server(), "/mock_page.html",
          true /*relative_url_is_prefix*/);
  auto script_response =
      std::make_unique<net::test_server::ControllableHttpResponse>(
          embedded_test_server(), "/script.js",
          true /*relative_url_is_prefix*/);
  auto iframe_response =
      std::make_unique<net::test_server::ControllableHttpResponse>(
          embedded_test_server(), "/iframe.html",
          true /*relative_url_is_prefix*/);
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");

  browser()->OpenURL(
      content::OpenURLParams(embedded_test_server()->GetURL("/mock_page.html"),
                             content::Referrer(),
                             WindowOpenDisposition::CURRENT_TAB,
                             ui::PAGE_TRANSITION_TYPED, false),
      /*navigation_handle_callback=*/{});

  main_html_response->WaitForRequest();
  main_html_response->Send(kHttpResponseHeader);
  main_html_response->Send(
      "<html><body></body><script src=\"script.js\"></script></html>");
  main_html_response->Send(std::string(1000, ' '));
  main_html_response->Done();
  waiter->AddMinimumCompleteResourcesExpectation(1);
  waiter->AddMinimumNetworkBytesExpectation(base::ByteSize(1000));
  waiter->Wait();

  script_response->WaitForRequest();
  script_response->Send(kHttpResponseHeader);
  script_response->Send(
      "var iframe = document.createElement(\"iframe\");"
      "iframe.src =\"iframe.html\";"
      "document.body.appendChild(iframe);");
  script_response->Send(std::string(1000, ' '));
  // Data received but resource not complete
  waiter->AddMinimumCompleteResourcesExpectation(1);
  waiter->AddMinimumNetworkBytesExpectation(base::ByteSize(2000));

  // Network bytes information is sent only when the resource is complete.
  // So we need to call Wait() after finishing `script_response`.
  script_response->Done();
  waiter->Wait();
  waiter->AddMinimumCompleteResourcesExpectation(2);
  waiter->Wait();

  // Make sure main resources are loaded correctly
  iframe_response->WaitForRequest();
  iframe_response->Send(kHttpResponseHeader);
  iframe_response->Send(std::string(2000, ' '));
  iframe_response->Done();
  waiter->AddMinimumCompleteResourcesExpectation(3);
  waiter->AddMinimumNetworkBytesExpectation(base::ByteSize(4000));
  waiter->Wait();
}

// TODO(crbug.com/334416161): Re-enable this test on Windows.
#if BUILDFLAG(IS_WIN)
#define MAYBE_InputEventsForClick DISABLED_InputEventsForClick
#else
#define MAYBE_InputEventsForClick InputEventsForClick
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, MAYBE_InputEventsForClick) {
  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  content::SetupCrossSiteRedirector(embedded_test_server());
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  GURL url = embedded_test_server()->GetURL("/page_load_metrics/link.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputCoverageWithUserGestureBrowserInitiated, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputCoverageWithUserGestureRendererInitiated, 0);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputCoverageWithoutUserGestureBrowserInitiated, 0);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputCoverageWithoutUserGestureRendererInitiated, 0);

  content::SimulateMouseClickAt(
      browser()->GetTabStripModel()->GetActiveWebContents(), 0,
      blink::WebMouseEvent::Button::kLeft, gfx::Point(100, 100));
  waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(internal::kHistogramInputToNavigation, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputToNavigationLinkClick, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputToFirstContentfulPaint, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputCoverageWithUserGestureBrowserInitiated, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputCoverageWithUserGestureRendererInitiated, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputCoverageWithoutUserGestureBrowserInitiated, 0);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputCoverageWithoutUserGestureRendererInitiated, 0);

  // 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,
                       InputEventsForFormSubmission) {
  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  ASSERT_TRUE(embedded_test_server()->Start());

  // Make sure that the initial implicit navigation is ended by navigating to an
  // untracked URL.
  NavigateToUntrackedUrl();

  {
    // Initial browser initiated navigation.
    base::HistogramTester histogram_tester;
    auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
    waiter->AddPageExpectation(TimingField::kLoadEvent);
    waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
    GURL url = embedded_test_server()->GetURL("a.test", "/title1.html");
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
    waiter->Wait();
    histogram_tester.ExpectTotalCount(internal::kHistogramInputToNavigation, 0);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputToNavigationLinkClick, 0);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputToNavigationFormSubmit, 0);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputToFirstContentfulPaint, 0);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputCoverageWithUserGestureBrowserInitiated, 1);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputCoverageWithUserGestureRendererInitiated, 0);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputCoverageWithoutUserGestureBrowserInitiated, 0);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputCoverageWithoutUserGestureRendererInitiated,
        0);
    histogram_tester.ExpectTotalCount("Navigation.Timeline.Total.Duration", 1);
    histogram_tester.ExpectTotalCount(
        "Navigation.Timeline.TotalExcludingBeforeUnload.Duration", 1);
    histogram_tester.ExpectTotalCount(
        "Navigation.Timeline.TotalExcludingBeforeUnload.MainFrameOnly.Duration",
        1);
    EXPECT_GT(
        histogram_tester.GetTotalSum("Navigation.Timeline.Total.Duration"), 0);
    // InteractionTo* metrics are not recorded when there is no user input.
    histogram_tester.ExpectTotalCount(
        "Navigation.Timeline.InteractionToActualNavigationStart.Duration", 0);
    histogram_tester.ExpectTotalCount(
        "Navigation.Timeline.InteractionToNavigationFinished."
        "MainFrameOnly.Duration",
        0);
    histogram_tester.ExpectTotalCount(
        "Navigation.Timeline.InteractionToNavigationFinished."
        "ExcludingBeforeUnload.MainFrameOnly.Duration",
        0);
    // Navigation metrics for before-navigation phase.
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInteractionToNavigationStart, 0);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramActualNavigationStartToNavigationStart, 1);
  }

  {
    // Renderer initiated navigation via script without a user gesture.
    base::HistogramTester histogram_tester;
    auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
    waiter->AddPageExpectation(TimingField::kLoadEvent);
    waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
    ASSERT_TRUE(ExecJs(web_contents(),
                       R"(
                         var form = document.createElement('form');
                         form.method = 'POST';
                         form.action = '/title2.html';
                         const input = document.createElement('input');
                         input.type = 'text';
                         input.name = 'q';
                         input.value = 'test';
                         form.appendChild(input);
                         document.body.appendChild(form);
                         form.submit();
                       )",
                       content::EXECUTE_SCRIPT_NO_USER_GESTURE));
    waiter->Wait();
    histogram_tester.ExpectTotalCount(internal::kHistogramInputToNavigation, 0);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputToNavigationLinkClick, 0);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputToNavigationFormSubmit, 0);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputToFirstContentfulPaint, 0);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputCoverageWithUserGestureBrowserInitiated, 0);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputCoverageWithUserGestureRendererInitiated, 0);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputCoverageWithoutUserGestureBrowserInitiated, 0);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputCoverageWithoutUserGestureRendererInitiated,
        1);
    histogram_tester.ExpectTotalCount("Navigation.Timeline.Total.Duration", 1);
    EXPECT_GT(
        histogram_tester.GetTotalSum("Navigation.Timeline.Total.Duration"), 0);
    histogram_tester.ExpectTotalCount(
        "Navigation.Timeline.TotalExcludingBeforeUnload.Duration", 1);
    histogram_tester.ExpectTotalCount(
        "Navigation.Timeline.TotalExcludingBeforeUnload.MainFrameOnly.Duration",
        1);
    // InteractionTo* metrics are not recorded when there is no user input.
    histogram_tester.ExpectTotalCount(
        "Navigation.Timeline.InteractionToActualNavigationStart.Duration", 0);
    histogram_tester.ExpectTotalCount(
        "Navigation.Timeline.InteractionToActualNavigationStart."
        "MainFrameOnly.Duration",
        0);
    histogram_tester.ExpectTotalCount(
        "Navigation.Timeline.InteractionToNavigationFinished."
        "MainFrameOnly.Duration",
        0);
    histogram_tester.ExpectTotalCount(
        "Navigation.Timeline.InteractionToNavigationFinished."
        "ExcludingBeforeUnload.MainFrameOnly.Duration",
        0);
    // Navigation metrics for before-navigation phase.
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInteractionToNavigationStart, 0);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramActualNavigationStartToNavigationStart, 1);
  }

  {
    // Renderer initiated navigation with key input.
    base::HistogramTester histogram_tester;
    auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
    waiter->AddPageExpectation(TimingField::kLoadEvent);
    waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
    ASSERT_TRUE(ExecJs(web_contents(),
                       R"(
                         var form = document.createElement('form');
                         form.method = 'POST';
                         form.action = '/title3.html';
                         input = document.createElement('input');
                         input.type = 'text';
                         input.name = 'q';
                         input.value = 'test';
                         form.appendChild(input);
                         document.body.appendChild(form);
                         input.focus();
                       )",
                       content::EXECUTE_SCRIPT_NO_USER_GESTURE));
    SimulateEndOfPaintHoldingOnPrimaryMainFrame(web_contents());
    SimulateKeyPress(web_contents(), ui::DomKey::ENTER, ui::DomCode::ENTER,
                     ui::VKEY_RETURN, false, false, false, false);
    waiter->Wait();
    histogram_tester.ExpectTotalCount(internal::kHistogramInputToNavigation, 1);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputToNavigationLinkClick, 0);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputToNavigationFormSubmit, 1);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputToFirstContentfulPaint, 1);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputCoverageWithUserGestureBrowserInitiated, 0);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputCoverageWithUserGestureRendererInitiated, 1);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputCoverageWithoutUserGestureBrowserInitiated, 0);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInputCoverageWithoutUserGestureRendererInitiated,
        0);
    histogram_tester.ExpectTotalCount("Navigation.Timeline.Total.Duration", 1);
    int64_t total_duration =
        histogram_tester.GetTotalSum("Navigation.Timeline.Total.Duration");
    EXPECT_GT(total_duration, 0);
    histogram_tester.ExpectTotalCount(
        "Navigation.Timeline.TotalExcludingBeforeUnload.Duration", 1);
    histogram_tester.ExpectTotalCount(
        "Navigation.Timeline.TotalExcludingBeforeUnload.MainFrameOnly.Duration",
        1);
    histogram_tester.ExpectTotalCount(
        "Navigation.Timeline.InteractionToActualNavigationStart.Duration", 1);
    EXPECT_GT(
        histogram_tester.GetTotalSum(
            "Navigation.Timeline.InteractionToActualNavigationStart.Duration"),
        0);
    histogram_tester.ExpectTotalCount(
        "Navigation.Timeline.InteractionToActualNavigationStart."
        "MainFrameOnly.Duration",
        1);
    EXPECT_GT(histogram_tester.GetTotalSum(
                  "Navigation.Timeline.InteractionToActualNavigationStart."
                  "MainFrameOnly.Duration"),
              0);
    histogram_tester.ExpectTotalCount(
        "Navigation.Timeline.InteractionToNavigationFinished."
        "MainFrameOnly.Duration",
        1);
    EXPECT_GT(histogram_tester.GetTotalSum(
                  "Navigation.Timeline.InteractionToNavigationFinished."
                  "MainFrameOnly.Duration"),
              total_duration);
    histogram_tester.ExpectTotalCount(
        "Navigation.Timeline.InteractionToNavigationFinished."
        "ExcludingBeforeUnload.MainFrameOnly.Duration",
        1);
    EXPECT_GT(histogram_tester.GetTotalSum(
                  "Navigation.Timeline.InteractionToNavigationFinished."
                  "ExcludingBeforeUnload.MainFrameOnly.Duration"),
              total_duration);
    // Navigation metrics for before-navigation phase.
    histogram_tester.ExpectTotalCount(
        internal::kHistogramInteractionToNavigationStart, 1);
    histogram_tester.ExpectTotalCount(
        internal::kHistogramActualNavigationStartToNavigationStart, 1);
  }
}

class SoftNavigationBrowserTest : public PageLoadMetricsBrowserTest {
 public:
  void TestSoftNavigation(bool soft_navs_is_web_exposed) {
    embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
    content::SetupCrossSiteRedirector(embedded_test_server());
    ASSERT_TRUE(embedded_test_server()->Start());

    auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
    waiter->AddPageExpectation(TimingField::kLoadEvent);
    waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
    waiter->AddPageExpectation(TimingField::kLargestContentfulPaint);
    GURL url = embedded_test_server()->GetURL(
        "/page_load_metrics/soft_navigation.html");
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
    waiter->Wait();

    content::WebContents* web_contents =
        browser()->GetTabStripModel()->GetActiveWebContents();
    content::WaitForHitTestData(web_contents->GetPrimaryMainFrame());

    waiter->AddPageExpectation(TimingField::kSoftNavigationCountUpdated);
    waiter->AddPageExpectation(TimingField::kLargestContentfulPaint);

    const std::string get_lcp_startTime = R"(
      (() => new Promise(resolve => {
        new PerformanceObserver(list => {
          resolve(list.getEntries().at(-1).startTime);
        }).observe({type: 'largest-contentful-paint', buffered: true});
      }))();
    )";
    // Get the web exposed LCP value before the click.
    int lcp_startTime = EvalJs(web_contents, get_lcp_startTime).ExtractDouble();

    content::SimulateMouseClickAt(
        browser()->GetTabStripModel()->GetActiveWebContents(), 0,
        blink::WebMouseEvent::Button::kLeft, gfx::Point(100, 100));

    // Get the web exposed ICP value only if the feature flag for exposing to
    // performance timeline is enabled.
    if (soft_navs_is_web_exposed) {
      const std::string get_icp_startTime = R"(
        (() => new Promise(resolve => {
          new PerformanceObserver(list => {
            resolve(list.getEntries().at(-1).startTime);
          }).observe({
            type: 'interaction-contentful-paint',
            buffered: true,
          });
        }))();
      )";
      int icp_startTime =
          EvalJs(web_contents, get_icp_startTime).ExtractDouble();
      ASSERT_GE(icp_startTime, lcp_startTime);
    }

    // Wait for a soft navigation count update.
    waiter->Wait();

    // Force navigation to another page, which should force logging of
    // histograms persisted at the end of the page load lifetime.
    NavigateToUntrackedUrl();

    VerifyNavigationMetrics({url});
    int64_t soft_navigation_count =
        GetUKMPageLoadMetric(PageLoad::kSoftNavigationCountName);
    ASSERT_EQ(soft_navigation_count, 1);

    auto lcp_value_bucket_start =
        histogram_tester_
            ->GetAllSamples(internal::kHistogramLargestContentfulPaint)[0]
            .min;

    // The histogram value represents the low end of the bucket, not the actual
    // value. Therefore it is lower or equal to the web exposed value.
    ASSERT_LE(lcp_value_bucket_start, lcp_startTime);
  }
};

class SoftNavigationBrowserTestWithSoftNavigationHeuristicsFlag
    : public SoftNavigationBrowserTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    PageLoadMetricsBrowserTest::SetUpCommandLine(command_line);
    features_list_.InitWithFeatures({blink::features::kSoftNavigationHeuristics},
                                    {});
  }

 private:
  base::test::ScopedFeatureList features_list_;
};

IN_PROC_BROWSER_TEST_F(SoftNavigationBrowserTest, SoftNavigation) {
  TestSoftNavigation(/*soft_navs_is_web_exposed=*/false);
}

IN_PROC_BROWSER_TEST_F(
    SoftNavigationBrowserTestWithSoftNavigationHeuristicsFlag, SoftNavigation) {
  TestSoftNavigation(/*soft_navs_is_web_exposed=*/true);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, InputEventsForOmniboxMatch) {
  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  content::SetupCrossSiteRedirector(embedded_test_server());
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  GURL url = embedded_test_server()->GetURL("/title1.html");
  ui_test_utils::SendToOmniboxAndSubmit(browser(), url.spec(),
                                        base::TimeTicks::Now());
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(internal::kHistogramInputToNavigation, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputToFirstContentfulPaint, 1);

  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputCoverageWithUserGestureBrowserInitiated, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputCoverageWithUserGestureRendererInitiated, 0);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputCoverageWithoutUserGestureBrowserInitiated, 0);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputCoverageWithoutUserGestureRendererInitiated, 0);

  // Force navigation to another page, which should force logging of histograms
  // persisted at the end of the page load lifetime.
  NavigateToUntrackedUrl();

  VerifyNavigationMetrics({url});
}

// TODO(crbug.com/334416161): Re-enable this test on Windows.
#if BUILDFLAG(IS_WIN)
#define MAYBE_InputEventsForJavaScriptHref DISABLED_InputEventsForJavaScriptHref
#else
#define MAYBE_InputEventsForJavaScriptHref InputEventsForJavaScriptHref
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       MAYBE_InputEventsForJavaScriptHref) {
  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  content::SetupCrossSiteRedirector(embedded_test_server());
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  GURL url =
      embedded_test_server()->GetURL("/page_load_metrics/javascript_href.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputCoverageWithUserGestureBrowserInitiated, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputCoverageWithUserGestureRendererInitiated, 0);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputCoverageWithoutUserGestureBrowserInitiated, 0);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputCoverageWithoutUserGestureRendererInitiated, 0);

  waiter = CreatePageLoadMetricsTestWaiter("waiter");
  content::SimulateMouseClickAt(
      browser()->GetTabStripModel()->GetActiveWebContents(), 0,
      blink::WebMouseEvent::Button::kLeft, gfx::Point(100, 100));
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(internal::kHistogramInputToNavigation, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputToNavigationLinkClick, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputToFirstContentfulPaint, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputCoverageWithUserGestureBrowserInitiated, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputCoverageWithUserGestureRendererInitiated, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputCoverageWithoutUserGestureBrowserInitiated, 0);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputCoverageWithoutUserGestureRendererInitiated, 0);

  // Force navigation to another page, which should force logging of histograms
  // persisted at the end of the page load lifetime.
  NavigateToUntrackedUrl();

  // Navigation should record the metrics twice because of the initial pageload
  // and the second pageload ("/title1.html") initiated by the link click.
  VerifyNavigationMetrics(
      {url, embedded_test_server()->GetURL("/title1.html")});
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       InputEventsForJavaScriptWindowOpen) {
  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  content::SetupCrossSiteRedirector(embedded_test_server());
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  GURL url = embedded_test_server()->GetURL(
      "/page_load_metrics/javascript_window_open.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  waiter->Wait();
  ui_test_utils::AllBrowserTabAddedWaiter tab_added_waiter;
  content::SimulateMouseClickAt(
      browser()->GetTabStripModel()->GetActiveWebContents(), 0,
      blink::WebMouseEvent::Button::kLeft, gfx::Point(100, 100));
  // Wait for new window to open.
  auto* web_contents = tab_added_waiter.Wait();
  waiter = std::make_unique<PageLoadMetricsTestWaiter>(web_contents);
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  waiter->Wait();

  histogram_tester_->ExpectTotalCount(internal::kHistogramInputToNavigation, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputToNavigationLinkClick, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramInputToFirstContentfulPaint, 1);

  // Close all pages, which should force logging of histograms persisted at the
  // end of the page load lifetime.
  browser()->GetTabStripModel()->CloseAllTabs();

  // Navigation should record the metrics twice because of the initial pageload
  // and the second pageload ("/title1.html") initiated by the link click.
  VerifyNavigationMetrics(
      {url, embedded_test_server()->GetURL("/title1.html")});
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, FirstInputFromScroll) {
  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  content::SetupCrossSiteRedirector(embedded_test_server());
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL("/page_load_metrics/scroll.html")));
  waiter->Wait();

  content::SimulateGestureScrollSequence(
      browser()->GetTabStripModel()->GetActiveWebContents(),
      gfx::Point(100, 100), gfx::Vector2dF(0, 15));
  NavigateToUntrackedUrl();

  // First Input Delay should not be reported from a scroll!
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstInputDelay, 0);
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstInputTimestamp,
                                      0);
}

// Does a navigation to a page controlled by a service worker and verifies
// that service worker page load metrics are logged.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, ServiceWorkerMetrics) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kFirstPaint);

  // Load a page that registers a service worker.
  GURL url = embedded_test_server()->GetURL(
      "/service_worker/create_service_worker.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  EXPECT_EQ("DONE",
            EvalJs(browser()->GetTabStripModel()->GetActiveWebContents(),
                   "register('fetch_event_pass_through.js');"));
  waiter->Wait();

  // The first load was not controlled, so service worker metrics should not be
  // logged.
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramServiceWorkerFirstPaint, 0);

  waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kFirstPaint);

  // Load a controlled page.
  GURL controlled_url = url;
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), controlled_url));
  waiter->Wait();

  // Service worker metrics should be logged.
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 2);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramServiceWorkerFirstPaint, 1);

  // Force navigation to another page, which should force logging of histograms
  // persisted at the end of the page load lifetime.
  NavigateToUntrackedUrl();

  // Navigation should record the metrics twice because of the initial pageload
  // to register a service worker and the page load controlled by the service
  // worker.
  VerifyNavigationMetrics({url, controlled_url});
}

// Does a navigation to a page controlled by a skippable service worker
// fetch handler and verifies that the page load metrics are logged.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       ServiceWorkerSkippableFetchHandlerMetrics) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kFirstPaint);

  // Load a page that registers a service worker.
  GURL url = embedded_test_server()->GetURL(
      "/service_worker/create_service_worker.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  EXPECT_EQ("DONE",
            EvalJs(browser()->GetTabStripModel()->GetActiveWebContents(),
                   "register('empty_fetch_event.js');"));
  waiter->Wait();

  // The first load was not controlled, so service worker metrics should not be
  // logged.
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramServiceWorkerFirstPaint, 0);

  waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kFirstPaint);

  // Load a controlled page.
  GURL controlled_url = url;
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), controlled_url));
  waiter->Wait();

  // The metrics should be logged.
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstPaint, 2);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramServiceWorkerFirstPaint, 1);
  histogram_tester_->ExpectTotalCount(
      internal::
          kHistogramServiceWorkerFirstContentfulPaintSkippableFetchHandler,
      1);

  // Force navigation to another page, which should force logging of histograms
  // persisted at the end of the page load lifetime.
  NavigateToUntrackedUrl();

  // Navigation should record the metrics twice because of the initial pageload
  // to register a service worker and the page load controlled by the service
  // worker.
  VerifyNavigationMetrics({url, controlled_url});
}

// Does a navigation to a page which records a WebFeature before commit.
// Regression test for https://crbug.com/40115086.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PreCommitWebFeature) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/title1.html")));
  waiter->Wait();

  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kSecureContextCheckPassed), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kSecureContextCheckFailed), 0);
}

// TODO(crbug.com/40916877): Fix this test on Mac.
#if BUILDFLAG(IS_MAC)
#define MAYBE_MainFrameIntersectionsMainFrame \
  DISABLED_MainFrameIntersectionsMainFrame
#else
#define MAYBE_MainFrameIntersectionsMainFrame MainFrameIntersectionsMainFrame
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       MAYBE_MainFrameIntersectionsMainFrame) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  content::WebContents* web_contents =
      browser()->GetTabStripModel()->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->AddMainFrameRectExpectation(
      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->AddMainFrameRectExpectation(
      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();
}






IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, PageLCPStopsUponInput) {
  embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
  content::SetupCrossSiteRedirector(embedded_test_server());
  ASSERT_TRUE(embedded_test_server()->Start());

  // Waiter to ensure main content is loaded.
  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  waiter->AddPageExpectation(TimingField::kLargestContentfulPaint);

  //  Waiter to ensure that iframe content is loaded.
  auto waiter2 = CreatePageLoadMetricsTestWaiter("waiter2");
  waiter2->AddPageExpectation(TimingField::kLoadEvent);
  waiter2->AddSubFrameExpectation(TimingField::kLoadEvent);
  waiter2->AddPageExpectation(TimingField::kFirstContentfulPaint);
  waiter2->AddSubFrameExpectation(TimingField::kFirstContentfulPaint);
  waiter2->AddPageExpectation(TimingField::kLargestContentfulPaint);
  waiter2->AddSubFrameExpectation(TimingField::kLargestContentfulPaint);
  waiter2->AddPageExpectation(TimingField::kFirstInputOrScroll);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(
                     "/page_load_metrics/click_to_create_iframe.html")));
  waiter->Wait();

  // Tap in the middle of the button.
  content::SimulateMouseClickOrTapElementWithId(web_contents(), "button");
  waiter2->Wait();

  // LCP is collected only at the end of the page lifecycle. Navigate to flush.
  NavigateToUntrackedUrl();

  histogram_tester_->ExpectTotalCount(
      internal::kHistogramLargestContentfulPaint, 1);
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramParseStartToLargestContentfulPaint, 1);
  auto all_frames_value =
      histogram_tester_
          ->GetAllSamples(internal::kHistogramLargestContentfulPaint)[0]
          .min;
  histogram_tester_->ExpectTotalCount(
      internal::kHistogramActualNavigationStartToLargestContentfulPaint, 1);

  histogram_tester_->ExpectTotalCount(
      internal::kHistogramLargestContentfulPaintMainFrame, 1);
  auto main_frame_value =
      histogram_tester_
          ->GetAllSamples(
              internal::kHistogramLargestContentfulPaintMainFrame)[0]
          .min;
  // Even though the content on the iframe is larger, the all_frames LCP value
  // should match the main frame value because the iframe content was created
  // after input in the main frame.
  ASSERT_EQ(all_frames_value, main_frame_value);
}

// Regression test for crbug.com/383189046. This test asserts that background
// tabs marked as needs-reload correctly report foreground paint metrics
// when activated and brought to the foreground.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       ActivatedNeedsReloadBackgroundTabsEmitCorrectMetrics) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // Create a new active tab.
  content::WebContents* target_contents = browser()->OpenURL(
      {embedded_test_server()->GetURL("/title1.html"), content::Referrer(),
       WindowOpenDisposition::NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_TYPED,
       false},
      /*navigation_handle_callback=*/{});
  auto fcp_waiter =
      CreatePageLoadMetricsTestWaiter("fcp_waiter", target_contents);
  fcp_waiter->AddPageExpectation(page_load_metrics::PageLoadMetricsTestWaiter::
                                     TimingField::kFirstContentfulPaint);
  auto navigation_observer =
      std::make_unique<content::TestNavigationObserver>(target_contents);
  navigation_observer->Wait();
  fcp_waiter->Wait();

  // Verify foreground fcp metrics are emitted.
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstContentfulPaint,
                                      1);
  histogram_tester_->ExpectTotalCount(
      internal::kBackgroundHistogramFirstContentfulPaint, 0);

  // Activate the original tab, backgrounding the target tab.
  browser()->GetTabStripModel()->ActivateTabAt(0);
  EXPECT_NE(target_contents,
            browser()->GetTabStripModel()->GetActiveWebContents());
  EXPECT_EQ(content::Visibility::HIDDEN, target_contents->GetVisibility());

  // Shutdown the target tab's process and tag it as needs-reload.
  content::RenderProcessHost* target_process =
      target_contents->GetPrimaryMainFrame()->GetProcess();
  target_contents->GetController().SetNeedsReload();
  EXPECT_TRUE(target_process->FastShutdownIfPossible());
  EXPECT_FALSE(target_process->IsInitializedAndNotDead());
  EXPECT_TRUE(target_contents->GetController().NeedsReload());

  // Activate the target tab.
  fcp_waiter = CreatePageLoadMetricsTestWaiter("fcp_waiter", target_contents);
  fcp_waiter->AddPageExpectation(page_load_metrics::PageLoadMetricsTestWaiter::
                                     TimingField::kFirstContentfulPaint);
  navigation_observer =
      std::make_unique<content::TestNavigationObserver>(target_contents);
  browser()->GetTabStripModel()->ActivateTabAt(1);
  navigation_observer->Wait();
  fcp_waiter->Wait();

  // Verify the appropriate foreground metrics are emitted.
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstContentfulPaint,
                                      2);
  histogram_tester_->ExpectTotalCount(
      internal::kBackgroundHistogramFirstContentfulPaint, 0);
}

class PageLoadMetricsBrowserTestTerminatedPage
    : public PageLoadMetricsBrowserTest {
 protected:
  void SetUpOnMainThread() override {
    PageLoadMetricsBrowserTest::SetUpOnMainThread();
    ASSERT_TRUE(embedded_test_server()->Start());
  }

 public:
  content::WebContents* OpenTabAndNavigate() {
    content::OpenURLParams page(embedded_test_server()->GetURL("/title1.html"),
                                content::Referrer(),
                                WindowOpenDisposition::NEW_FOREGROUND_TAB,
                                ui::PAGE_TRANSITION_TYPED, false);

    content::WebContents* contents =
        browser()->OpenURL(page, /*navigation_handle_callback=*/{});
    std::unique_ptr<PageLoadMetricsTestWaiter> waiter =
        CreatePageLoadMetricsTestWaiter("lcp_waiter", contents);
    waiter->AddPageExpectation(page_load_metrics::PageLoadMetricsTestWaiter::
                                   TimingField::kLargestContentfulPaint);

    content::TestNavigationObserver observer(contents);
    observer.set_expected_initial_url(page.url);
    observer.Wait();

    // This is to wait for LCP to be observed on browser side.
    waiter->Wait();

    return contents;
  }

  double GetLCPTimeFromEmittedLCPEntry(content::WebContents* contents) {
    content::EvalJsResult lcp_time =
        EvalJs(contents, ScriptForGettingLCPTimeFromEmittedLCPEntry());
    return lcp_time.ExtractDouble();
  }

  void AddNewTab() {
    std::unique_ptr<content::WebContents> web_contents_to_add =
        content::WebContents::Create(
            content::WebContents::CreateParams(browser()->GetProfile()));

    web_contents_to_add->GetController().LoadURL(
        embedded_test_server()->GetURL("/title1.html"), content::Referrer(),
        ::ui::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string());

    auto* tab_strip_model = browser()->GetTabStripModel();
    tab_strip_model->AddWebContents(std::move(web_contents_to_add), -1,
                                    ::ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
                                    AddTabTypes::ADD_ACTIVE);
  }

  void DiscardTab(content::WebContents* contents) {
    resource_coordinator::TabLifecycleUnitExternal::FromWebContents(contents)
        ->DiscardTab(mojom::LifecycleUnitDiscardReason::URGENT);
  }

  void CloseTab(content::WebContents* contents) {
    auto* tab_strip_model = browser()->GetTabStripModel();
    // Get the total count of tabs.
    int tab_count = tab_strip_model->count();

    // Close the tab corresponding to the given WebContents.
    tab_strip_model->CloseWebContents(contents,
                                      TabCloseTypes::CLOSE_USER_GESTURE);
    // Verify tab is closed.
    EXPECT_EQ(tab_strip_model->count(), tab_count - 1);
  }

  std::string ScriptForGettingLCPTimeFromEmittedLCPEntry() {
    return R"(
   (async () => {
        return await new Promise(resolve => {
          (new PerformanceObserver(list => {
              const entries = list.getEntries();
              for (let entry of entries) {
                  if (entry) {
                      resolve(entry.startTime);
                  }
              }
          }))
          .observe({
              type: 'largest-contentful-paint',
              buffered: true
          });
      })
  })())";
  }
};

class PageLoadMetricsBrowserTestDiscardedPage
    : public PageLoadMetricsBrowserTestTerminatedPage,
      public ::testing::WithParamInterface<bool> {};

IN_PROC_BROWSER_TEST_P(PageLoadMetricsBrowserTestDiscardedPage,
                       UkmIsRecordedForDiscardedTabPage) {
  if (base::FeatureList::IsEnabled(features::kWebContentsDiscard)) {
    GTEST_SKIP() << "Page load metrics are reported when the tab is closed.";
  }

  // Open a new foreground tab and navigate.
  content::WebContents* contents = OpenTabAndNavigate();

  // Wait for LCP emission and observation.
  double lcp_time = GetLCPTimeFromEmittedLCPEntry(contents);

  // Background current tab by adding a new tab if provided param is true.
  if (GetParam()) {
    // Add a new tab.
    AddNewTab();

    // Verify the first tab is backgrounded.
    EXPECT_NE(contents, browser()->GetTabStripModel()->GetActiveWebContents());
  }

  // Discard tab.
  DiscardTab(contents);

  // Verify tab is discarded.
  EXPECT_TRUE(
      browser()->GetTabStripModel()->GetWebContentsAt(1)->WasDiscarded());

  // Verify page load metric is recorded.
  EXPECT_NEAR(
      GetUKMPageLoadMetric(
          PageLoad::kPaintTiming_NavigationToLargestContentfulPaint2Name),
      lcp_time, 10);
}

INSTANTIATE_TEST_SUITE_P(DiscardedPages,
                         PageLoadMetricsBrowserTestDiscardedPage,
                         testing::Bool());

class PageLoadMetricsBrowserTestClosedPage
    : public PageLoadMetricsBrowserTestTerminatedPage,
      public ::testing::WithParamInterface<bool> {};

IN_PROC_BROWSER_TEST_P(PageLoadMetricsBrowserTestClosedPage,
                       UkmIsRecordedForClosedTabPage) {
  // Open a new foreground tab and navigate. The new tab would be of index 1
  // which would be used below in verifying the tab is discarded.
  content::WebContents* contents = OpenTabAndNavigate();

  // Wait for LCP emission and observation. This is to ensure there is an LCP
  // entry to report at the time of closing the page.
  double lcp_time = GetLCPTimeFromEmittedLCPEntry(contents);

  // Background current tab by adding a new tab if provided param is true.
  if (GetParam()) {
    // Add a new tab.
    AddNewTab();

    // Verify the tab is backgrounded.
    EXPECT_NE(contents, browser()->GetTabStripModel()->GetActiveWebContents());
  }

  // close tab.
  CloseTab(contents);

  // Verify page load metric is recorded.
  EXPECT_NEAR(
      GetUKMPageLoadMetric(
          PageLoad::kPaintTiming_NavigationToLargestContentfulPaint2Name),
      lcp_time, 10);
}

INSTANTIATE_TEST_SUITE_P(ClosedPages,
                         PageLoadMetricsBrowserTestClosedPage,
                         testing::Bool());

// This test is to verify page load metrics are recorded in case when the
// render process is shut down.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestTerminatedPage,
                       UkmIsRecordedWhenRenderProcessShutsDown) {
  content::WebContents* contents = OpenTabAndNavigate();

  // Wait for LCP emission and observation.
  double lcp_time = GetLCPTimeFromEmittedLCPEntry(contents);
  content::RenderProcessHost* process = RenderFrameHost()->GetProcess();

  // Shut down render process.
  content::RenderProcessHostWatcher crash_observer(
      process, content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
  EXPECT_TRUE(process->Shutdown(content::RESULT_CODE_KILLED));
  crash_observer.Wait();
  EXPECT_FALSE(RenderFrameHost()->IsRenderFrameLive());

  // Verify page load metric is recorded.
  EXPECT_NEAR(
      GetUKMPageLoadMetric(
          PageLoad::kPaintTiming_NavigationToLargestContentfulPaint2Name),
      lcp_time, 10);
}

// This class is used to verify page load metrics are recorded in case of
// crashes of different kinds. These crashes are simulated by navigating to the
// chrome debug urls.
class PageLoadMetricsBrowserTestRendererCrashedPage
    : public PageLoadMetricsBrowserTestTerminatedPage,
      public ::testing::WithParamInterface<const char*> {};

IN_PROC_BROWSER_TEST_P(PageLoadMetricsBrowserTestRendererCrashedPage,
                       UkmIsRecordedForCrashedTabPage) {
  // Open a new foreground tab and navigate.
  content::WebContents* contents = OpenTabAndNavigate();

  // The back/forward cache is disabled because page load metrics can also be
  // recorded when entering into the bfcache. We want to test that page load
  // metrics are recorded via the PageLoadTracker destructor which is called in
  // all crash cases.
  content::DisableBackForwardCacheForTesting(
      contents, content::BackForwardCache::TEST_REQUIRES_NO_CACHING);

  // Wait for LCP emission and observation. This is to ensure there is an LCP
  // entry to report at the time of killing the page.
  double lcp_time = GetLCPTimeFromEmittedLCPEntry(contents);

  // Crash the page.
  content::RenderProcessHostWatcher crash_observer(
      RenderFrameHost()->GetProcess(),
      content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);

  browser()->OpenURL(
      content::OpenURLParams(GURL(GetParam()), content::Referrer(),
                             WindowOpenDisposition::CURRENT_TAB,
                             ui::PAGE_TRANSITION_TYPED, false),
      /*navigation_handle_callback=*/{});

  crash_observer.Wait();
  EXPECT_FALSE(crash_observer.did_exit_normally());
  EXPECT_TRUE(web_contents()->IsCrashed());

  // Verify page load metric is recorded.
  EXPECT_NEAR(
      GetUKMPageLoadMetric(
          PageLoad::kPaintTiming_NavigationToLargestContentfulPaint2Name),
      lcp_time, 10);
}

INSTANTIATE_TEST_SUITE_P(RendererCrashCases,
                         PageLoadMetricsBrowserTestRendererCrashedPage,
                         testing::ValuesIn({blink::kChromeUIKillURL,
                                            blink::kChromeUICrashURL}));

// Similar to the crashes above, this test verifies page load metrics are
// recorded in case severe errors that don't actually crash the
// renderer process (e.g. only the GPU process may crash), but rather cause
// the renderer to be terminated.
class PageLoadMetricsBrowserTestNoRendererCrashedPage
    : public PageLoadMetricsBrowserTestTerminatedPage,
      public ::testing::WithParamInterface<const char*> {};

IN_PROC_BROWSER_TEST_P(PageLoadMetricsBrowserTestNoRendererCrashedPage,
                       UkmIsRecordedForCrashedTabPage) {
  base::test::ScopedCommandLine scoped_command_line;
  if (GetParam() == blink::kChromeUIGpuCrashURL) {
    // This flag must be enabled to log metrics for blink::kChromeUIGpuCrashURL.
    scoped_command_line.GetProcessCommandLine()->AppendSwitch(
        switches::kEnableGpuBenchmarking);
  }

  // Open a new foreground tab and navigate.
  content::WebContents* contents = OpenTabAndNavigate();

  // The back/forward cache is disabled because page load metrics can also be
  // recorded when entering into the bfcache. We want to test that page load
  // metrics are recorded via the PageLoadTracker destructor which is called in
  // all crash cases.
  content::DisableBackForwardCacheForTesting(
      contents, content::BackForwardCache::TEST_REQUIRES_NO_CACHING);

  // Wait for LCP emission and observation. This is to ensure there is an LCP
  // entry to report at the time of killing the page.
  double lcp_time = GetLCPTimeFromEmittedLCPEntry(contents);

  // Wait for the destruction of the RenderProcessHost, which is triggered by
  // the navigation to the chrome debug url; then assert that we've navigated
  // to the correct URL.
  content::RenderProcessHostWatcher destruction_observer(contents,
      content::RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION);

  browser()->OpenURL(
      content::OpenURLParams(GURL(GetParam()), content::Referrer(),
                             WindowOpenDisposition::CURRENT_TAB,
                             ui::PAGE_TRANSITION_TYPED, false),
      /*navigation_handle_callback=*/{});

  destruction_observer.Wait();
  EXPECT_TRUE(web_contents() == contents);
  EXPECT_FALSE(contents->IsCrashed());
  // GetWithoutRef() allows chrome://process-internals/ to (sometimes) redirect
  // to chrome://process-internals/#general. See crbug.com/526654572.
  EXPECT_EQ(GURL(GetParam()), contents->GetLastCommittedURL().GetWithoutRef());
  EXPECT_FALSE(contents->HasUncommittedNavigationInPrimaryMainFrame());

  // Verify page load metric is recorded.
  EXPECT_NEAR(
      GetUKMPageLoadMetric(
          PageLoad::kPaintTiming_NavigationToLargestContentfulPaint2Name),
      lcp_time, 10);
}

INSTANTIATE_TEST_SUITE_P(
    NoRendererCrashCases,
    PageLoadMetricsBrowserTestNoRendererCrashedPage,
    testing::ValuesIn({blink::kChromeUIGpuCrashURL,
                       blink::kChromeUINetworkErrorURL,
                       blink::kChromeUIProcessInternalsURL}));

// Test is flaky. https://crbug.com/40202043
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) || \
    BUILDFLAG(IS_WIN)
#define MAYBE_PageLCPAnimatedImage DISABLED_PageLCPAnimatedImage
#else
#define MAYBE_PageLCPAnimatedImage PageLCPAnimatedImage
#endif
// Tests that an animated image's reported LCP values are smaller than its load
// times, when the feature flag for animated image reporting is enabled.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestAnimatedLCP,
                       MAYBE_PageLCPAnimatedImage) {
  test_animated_image_lcp(/*smaller=*/true, /*animated=*/true);
}

// Tests that a non-animated image's reported LCP values are larger than its
// load times, when the feature flag for animated image reporting is enabled.
// TODO(crbug.com/40218474): Flaky on Mac/Linux.
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
#define MAYBE_PageLCPNonAnimatedImage DISABLED_PageLCPNonAnimatedImage
#else
#define MAYBE_PageLCPNonAnimatedImage PageLCPNonAnimatedImage
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestAnimatedLCP,
                       MAYBE_PageLCPNonAnimatedImage) {
  test_animated_image_lcp(/*smaller=*/false, /*animated=*/false);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, FirstInputDelayFromClick) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);

  auto waiter2 = CreatePageLoadMetricsTestWaiter("waiter2");
  waiter2->AddPageExpectation(TimingField::kLoadEvent);
  waiter2->AddPageExpectation(TimingField::kFirstContentfulPaint);
  waiter2->AddPageExpectation(TimingField::kFirstInputDelay);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL("/page_load_metrics/click.html")));
  waiter->Wait();
  content::SimulateMouseClickAt(
      browser()->GetTabStripModel()->GetActiveWebContents(), 0,
      blink::WebMouseEvent::Button::kLeft, gfx::Point(100, 100));
  waiter2->Wait();

  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstInputDelay, 1);
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstInputTimestamp,
                                      1);
}

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest, SameOriginNavigation) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL kUrl1 = embedded_test_server()->GetURL("a.com", "/title1.html");
  GURL kUrl2 = embedded_test_server()->GetURL("a.com", "/title2.html");

  auto waiter1 = CreatePageLoadMetricsTestWaiter("waiter1");
  waiter1->AddPageExpectation(TimingField::kLargestContentfulPaint);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kUrl1));
  waiter1->Wait();

  auto waiter2 = CreatePageLoadMetricsTestWaiter("waiter2");
  waiter2->AddPageExpectation(TimingField::kLargestContentfulPaint);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kUrl2));
  waiter2->Wait();

  NavigateToUntrackedUrl();
  VerifyNavigationMetrics({kUrl1, kUrl2});

  // Navigation from about:blank to kUrl1 is a cross origin navigation.
  histogram_tester_->ExpectTotalCount(
      "PageLoad.Clients.CrossOrigin.FirstContentfulPaint", 1);
  histogram_tester_->ExpectTotalCount(
      "PageLoad.Clients.CrossOrigin.LargestContentfulPaint", 1);
  // Navigation from kUrl1 to kUrl2 is a same origin navigation.
  histogram_tester_->ExpectTotalCount(
      "PageLoad.Clients.SameOrigin.FirstContentfulPaint", 1);
  histogram_tester_->ExpectTotalCount(
      "PageLoad.Clients.SameOrigin.LargestContentfulPaint", 1);
}

// TODO(crbug.com/334416161): Re-enable this test on Windows.
#if BUILDFLAG(IS_WIN)
#define MAYBE_CrossOriginNavigation DISABLED_CrossOriginNavigation
#else
#define MAYBE_CrossOriginNavigation CrossOriginNavigation
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTest,
                       MAYBE_CrossOriginNavigation) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL kUrl1 = embedded_test_server()->GetURL("a.com", "/title1.html");
  GURL kUrl2 = embedded_test_server()->GetURL("b.com", "/title1.html");

  auto waiter1 = CreatePageLoadMetricsTestWaiter("waiter1");
  waiter1->AddPageExpectation(TimingField::kFirstContentfulPaint);
  waiter1->AddPageExpectation(TimingField::kLargestContentfulPaint);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kUrl1));
  waiter1->Wait();

  auto waiter2 = CreatePageLoadMetricsTestWaiter("waiter2");
  waiter2->AddPageExpectation(TimingField::kFirstContentfulPaint);
  waiter2->AddPageExpectation(TimingField::kLargestContentfulPaint);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kUrl2));
  waiter2->Wait();

  NavigateToUntrackedUrl();
  VerifyNavigationMetrics({kUrl1, kUrl2});

  // Navigation from about:blank to kUrl1 and navigation from kUrl1 to kUrl2 are
  // cross origin navigations.
  histogram_tester_->ExpectTotalCount(
      "PageLoad.Clients.CrossOrigin.FirstContentfulPaint", 2);
  histogram_tester_->ExpectTotalCount(
      "PageLoad.Clients.CrossOrigin.LargestContentfulPaint", 2);
}

class PageLoadMetricsBrowserTestWithFencedFrames
    : public PageLoadMetricsBrowserTest {
 public:
  PageLoadMetricsBrowserTestWithFencedFrames()
      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
    https_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
    https_server_.AddDefaultHandlers(GetChromeTestDataDir());
  }
  ~PageLoadMetricsBrowserTestWithFencedFrames() override = default;

 protected:
  net::EmbeddedTestServer& https_server() { return https_server_; }

 private:
  net::EmbeddedTestServer https_server_;
  content::test::FencedFrameTestHelper helper_;
};

// TODO(crbug.com/334416161): Re-enable this test on Windows.
#if BUILDFLAG(IS_WIN)
#define MAYBE_PageLoadPrivacySandboxAdsFencedFramesMetrics \
  DISABLED_PageLoadPrivacySandboxAdsFencedFramesMetrics
#else
#define MAYBE_PageLoadPrivacySandboxAdsFencedFramesMetrics \
  PageLoadPrivacySandboxAdsFencedFramesMetrics
#endif
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestWithFencedFrames,
                       MAYBE_PageLoadPrivacySandboxAdsFencedFramesMetrics) {
  ASSERT_TRUE(https_server().Start());

  static constexpr char
      kHistogramPrivacySandboxAdsNavigationToFirstContentfulPaint[] =
          "PageLoad.Clients.PrivacySandboxAds.PaintTiming."
          "NavigationToFirstContentfulPaint.FencedFrames";

  // Not recorded as fenced frame is not created.
  auto waiter1 = CreatePageLoadMetricsTestWaiter("waiter1");
  waiter1->AddPageExpectation(TimingField::kFirstContentfulPaint);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server().GetURL("a.test", "/title1.html")));
  waiter1->Wait();

  histogram_tester_->ExpectTotalCount(
      kHistogramPrivacySandboxAdsNavigationToFirstContentfulPaint, 0);

  // Recorded as fenced frame is created.
  auto waiter2 = CreatePageLoadMetricsTestWaiter("waiter2");
  waiter2->AddPageExpectation(TimingField::kFirstContentfulPaint);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      https_server().GetURL("c.test", "/fenced_frames/basic_title.html")));
  waiter2->Wait();

  histogram_tester_->ExpectTotalCount(
      kHistogramPrivacySandboxAdsNavigationToFirstContentfulPaint, 1);
}

class PageLoadMetricsBrowserTestWithBackForwardCache
    : public PageLoadMetricsBrowserTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    PageLoadMetricsBrowserTest::SetUpCommandLine(command_line);
    feature_list.InitWithFeaturesAndParameters(
        content::GetDefaultEnabledBackForwardCacheFeaturesForTesting(),
        content::GetDefaultDisabledBackForwardCacheFeaturesForTesting());
  }

 private:
  base::test::ScopedFeatureList feature_list;
};

IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestWithBackForwardCache,
                       BackForwardCacheEvent) {
  ASSERT_TRUE(embedded_test_server()->Start());

  auto url1 = embedded_test_server()->GetURL("a.com", "/title1.html");
  auto url2 = embedded_test_server()->GetURL("b.com", "/title1.html");

  // Go to URL1.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url1));

  histogram_tester_->ExpectBucketCount(
      internal::kHistogramBackForwardCacheEvent,
      internal::PageLoadBackForwardCacheEvent::kEnterBackForwardCache, 0);
  histogram_tester_->ExpectBucketCount(
      internal::kHistogramBackForwardCacheEvent,
      internal::PageLoadBackForwardCacheEvent::kRestoreFromBackForwardCache, 0);

  // Go to URL2. The previous page (URL1) is put into the back-forward cache.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url2));

  histogram_tester_->ExpectBucketCount(
      internal::kHistogramBackForwardCacheEvent,
      internal::PageLoadBackForwardCacheEvent::kEnterBackForwardCache, 1);
  histogram_tester_->ExpectBucketCount(
      internal::kHistogramBackForwardCacheEvent,
      internal::PageLoadBackForwardCacheEvent::kRestoreFromBackForwardCache, 0);

  // Go back to URL1. The previous page (URL2) is put into the back-forward
  // cache.
  content::WebContents* web_contents =
      browser()->GetTabStripModel()->GetActiveWebContents();
  web_contents->GetController().GoBack();
  EXPECT_TRUE(WaitForLoadStop(web_contents));

  histogram_tester_->ExpectBucketCount(
      internal::kHistogramBackForwardCacheEvent,
      internal::PageLoadBackForwardCacheEvent::kEnterBackForwardCache, 2);

  // For now UmaPageLoadMetricsObserver::OnEnterBackForwardCache returns
  // STOP_OBSERVING, OnRestoreFromBackForward is never reached.
  //
  // TODO(hajimehoshi): Update this when the UmaPageLoadMetricsObserver
  // continues to observe after entering to back-forward cache.
  histogram_tester_->ExpectBucketCount(
      internal::kHistogramBackForwardCacheEvent,
      internal::PageLoadBackForwardCacheEvent::kRestoreFromBackForwardCache, 0);
}

// Test UseCounter UKM features observed when a page is in the BFCache and is
// evicted from it.
IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestWithBackForwardCache,
                       UseCounterUkmFeaturesLoggedOnBFCacheEviction) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url = embedded_test_server()->GetURL(
      "/page_load_metrics/use_counter_features.html");
  {
    auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
    waiter->AddPageExpectation(TimingField::kLoadEvent);
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
    MakeComponentFullscreen("testvideo");
    waiter->Wait();
  }
  NavigateToUntrackedUrl();

  // Force the BFCache to evict all entries. This should cause the
  // UseCounter histograms to be logged.
  browser()
      ->GetTabStripModel()
      ->GetActiveWebContents()
      ->GetController()
      .GetBackForwardCache()
      .Flush();

  // Navigate to a new URL. This gives the various page load tracking
  // mechanisms time to process the BFCache evictions.
  auto url1 = embedded_test_server()->GetURL("a.com", "/title1.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url1));

  const auto& entries = test_ukm_recorder_->GetEntriesByName(
      ukm::builders::Blink_UseCounter::kEntryName);
  EXPECT_THAT(entries, SizeIs(4));
  std::vector<int64_t> ukm_features;
  for (const ukm::mojom::UkmEntry* entry : entries) {
    test_ukm_recorder_->ExpectEntryMetric(
        entry, ukm::builders::Blink_UseCounter::kIsMainFrameFeatureName, 1);
    const auto* metric = test_ukm_recorder_->GetEntryMetric(
        entry, ukm::builders::Blink_UseCounter::kFeatureName);
    DCHECK(metric);
    ukm_features.push_back(*metric);
  }
  EXPECT_THAT(ukm_features,
              UnorderedElementsAre(
                  static_cast<int64_t>(WebFeature::kPageVisits),
                  static_cast<int64_t>(WebFeature::kFullscreenSecureOrigin),
                  static_cast<int64_t>(WebFeature::kNavigatorVibrate),
                  static_cast<int64_t>(WebFeature::kPageVisits)));

  // Check histogram counts.
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kPageVisits), 2);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kFullscreenSecureOrigin), 1);
  histogram_tester_->ExpectBucketCount(
      "Blink.UseCounter.Features",
      static_cast<int32_t>(WebFeature::kNavigatorVibrate), 1);
}

class NavigationPageLoadMetricsBrowserTest
    : public PageLoadMetricsBrowserTest,
      public ::testing::WithParamInterface<std::string> {
 public:
  NavigationPageLoadMetricsBrowserTest() = default;
  ~NavigationPageLoadMetricsBrowserTest() override = default;

 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    // TODO(crbug.com/40188113): This test used an experiment param (which no
    // longer exists) to suppress the metrics send timer. If and when the test
    // is re-enabled, it should be updated to use a different mechanism.
    PageLoadMetricsBrowserTest::SetUpCommandLine(command_line);
  }
};

IN_PROC_BROWSER_TEST_P(NavigationPageLoadMetricsBrowserTest, FirstInputDelay) {
  ASSERT_TRUE(embedded_test_server()->Start());

  GURL url1(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url2(embedded_test_server()->GetURL(
      (GetParam() == "SameSite") ? "a.com" : "b.com", "/title2.html"));

  EXPECT_THAT(histogram_tester_->GetAllSamples(
                  internal::kHistogramFirstContentfulPaint),
              testing::IsEmpty());

  auto waiter = CreatePageLoadMetricsTestWaiter("waiter");
  waiter->AddPageExpectation(TimingField::kFirstInputDelay);

  // 1) Navigate to url1.
  EXPECT_TRUE(content::NavigateToURL(web_contents(), url1));
  histogram_tester_->ExpectTotalCount(internal::kHistogramFirstInputDelay, 0);
  content::RenderFrameHost* rfh_a = RenderFrameHost();
  content::RenderProcessHost* rfh_a_process = rfh_a->GetProcess();

  // We should wait for the main frame's hit-test data to be ready before
  // sending the click event below to avoid flakiness.
  content::WaitForHitTestData(web_contents()->GetPrimaryMainFrame());
  // Ensure the compositor thread is ready for mouse events.
  content::MainThreadFrameObserver frame_observer(
      web_contents()->GetRenderWidgetHostView()->GetRenderWidgetHost());
  frame_observer.Wait();

  // Simulate mouse click. FirstInputDelay won't get updated immediately.
  content::SimulateMouseClickAt(web_contents(), 0,
                                blink::WebMouseEvent::Button::kLeft,
                                gfx::Point(100, 100));

  // Run a Performance Observer to ensure the renderer receives the click
  EXPECT_TRUE(content::ExecJs(web_contents(), R"(
          (async () => {
            await new Promise(resolve => {
              new PerformanceObserver(e => {
                e.getEntries().forEach(entry => {
                  resolve(true);
                })
              }).observe({type: 'first-input', buffered: true});
          })})())"));
  base::RunLoop().RunUntilIdle();
  content::FetchHistogramsFromChildProcesses();

  waiter->Wait();
  // 2) Immediately navigate to url2.
  if (GetParam() == "CrossSiteRendererInitiated") {
    EXPECT_TRUE(content::NavigateToURLFromRenderer(web_contents(), url2));
  } else {
    EXPECT_TRUE(content::NavigateToURL(web_contents(), url2));
  }

  content::FetchHistogramsFromChildProcesses();
  if (GetParam() != "CrossSiteBrowserInitiated" ||
      rfh_a_process == RenderFrameHost()->GetProcess()) {
    // - For "SameSite" case, since the old and new RenderFrame either share a
    // process (with RenderDocument/back-forward cache) or the RenderFrame is
    // reused the metrics update will be sent to the browser during commit and
    // won't get ignored, successfully updating the FirstInputDelay histogram.
    // - For "CrossSiteRendererInitiated" case, FirstInputDelay was sent when
    // the renderer-initiated navigation started on the old frame.
    // - For "CrossSiteBrowserInitiated" case, if the old and new RenderFrame
    // share a process, the metrics update will be sent to the browser during
    // commit and won't get ignored, successfully updating the histogram.
    histogram_tester_->ExpectTotalCount(internal::kHistogramFirstInputDelay, 1);
  } else {
    // Note that in some cases the metrics might flakily get updated in time,
    // before the browser changed the current RFH. So, we can neither expect it
    // to be 0 all the time or 1 all the time.
    // TODO(crbug.com/40157795): Support updating metrics consistently on
    // cross-RFH cross-process navigations.
  }
}

std::vector<std::string> NavigationPageLoadMetricsBrowserTestTestValues() {
  return {"SameSite", "CrossSiteRendererInitiated",
          "CrossSiteBrowserInitiated"};
}

INSTANTIATE_TEST_SUITE_P(
    All,
    NavigationPageLoadMetricsBrowserTest,
    testing::ValuesIn(NavigationPageLoadMetricsBrowserTestTestValues()));

class PrerenderPageLoadMetricsBrowserTest : public PageLoadMetricsBrowserTest {
 public:
  PrerenderPageLoadMetricsBrowserTest()
      : prerender_helper_(base::BindRepeating(
            &PrerenderPageLoadMetricsBrowserTest::web_contents,
            base::Unretained(this))) {}

  void SetUp() override {
    prerender_helper_.RegisterServerRequestMonitor(embedded_test_server());
    PageLoadMetricsBrowserTest::SetUp();
  }

 protected:
  content::test::PrerenderTestHelper prerender_helper_;
};

class PrerenderPageLoadMetricsBrowserTestWithInitialWebUIParam
    : public PrerenderPageLoadMetricsBrowserTest,
      public ::testing::WithParamInterface<bool> {
 public:
  PrerenderPageLoadMetricsBrowserTestWithInitialWebUIParam() {
    if (GetParam()) {
      sub_feature_list_.InitWithFeatures(
          {features::kInitialWebUI, features::kWebUIReloadButton}, {});
    } else {
      sub_feature_list_.InitWithFeatures(
          {}, {features::kInitialWebUI, features::kWebUIReloadButton});
    }
  }

 private:
  base::test::ScopedFeatureList sub_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);

  // Start a prerender.
  GURL prerender_url = embedded_test_server()->GetURL("/title2.html");
  prerender_helper_.AddPrerender(prerender_url);

  histogram_tester_->ExpectBucketCount(
      kPageLoadPrerender2Event,
      PageLoadPrerenderEvent::kNavigationInPrerenderedMainFrame, 1);
  histogram_tester_->ExpectBucketCount(
      kPageLoadPrerender2Event,
      PageLoadPrerenderEvent::kPrerenderActivationNavigation, 0);

  // Activate.
  prerender_helper_.NavigatePrimaryPage(prerender_url);

  histogram_tester_->ExpectBucketCount(
      kPageLoadPrerender2Event,
      PageLoadPrerenderEvent::kNavigationInPrerenderedMainFrame, 1);
  histogram_tester_->ExpectBucketCount(
      kPageLoadPrerender2Event,
      PageLoadPrerenderEvent::kPrerenderActivationNavigation, 1);
}

IN_PROC_BROWSER_TEST_P(PrerenderPageLoadMetricsBrowserTestWithInitialWebUIParam,
                       PrerenderingDoNotRecordUKM) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // Navigate to an initial page.
  auto initial_url = embedded_test_server()->GetURL("/empty.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));

  // Load a page in the prerender.
  GURL prerender_url = embedded_test_server()->GetURL("/title2.html");
  content::PrerenderHostId 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);
  if (GetParam()) {
    EXPECT_GE(entries.size(), 0u);
  } else {
    EXPECT_EQ(0u, entries.size());
  }

  // Activate.
  prerender_helper_.NavigatePrimaryPage(prerender_url);
  EXPECT_TRUE(host_observer.was_activated());
  entries = test_ukm_recorder_->GetMergedEntriesByName(PageLoad::kEntryName);
  if (GetParam()) {
    EXPECT_GE(entries.size(), 1u);
  } else {
    EXPECT_EQ(1u, entries.size());
  }
}

INSTANTIATE_TEST_SUITE_P(
    All,
    PrerenderPageLoadMetricsBrowserTestWithInitialWebUIParam,
    ::testing::Bool());

enum BackForwardCacheStatus { kDisabled = 0, kEnabled = 1 };

class PageLoadMetricsBackForwardCacheBrowserTest
    : public PageLoadMetricsBrowserTest,
      public testing::WithParamInterface<BackForwardCacheStatus> {
 public:
  PageLoadMetricsBackForwardCacheBrowserTest() {
    if (GetParam() == BackForwardCacheStatus::kEnabled) {
      // Enable BackForwardCache.
      feature_list_.InitWithFeaturesAndParameters(
          content::GetBasicBackForwardCacheFeatureForTesting(),
          content::GetDefaultDisabledBackForwardCacheFeaturesForTesting());
    } else {
      feature_list_.InitAndDisableFeature(features::kBackForwardCache);
      DCHECK(!content::BackForwardCache::IsBackForwardCacheFeatureEnabled());
    }
  }

  static std::string DescribeParams(
      const testing::TestParamInfo<ParamType>& info) {
    return info.param ? "BFCacheEnabled" : "BFCacheDisabled";
  }

  void VerifyPageEndReasons(const std::vector<PageEndReason>& reasons,
                            const GURL& url,
                            bool is_bfcache_enabled);
  int64_t CountForMetricForURL(std::string_view entry_name,
                               std::string_view metric_name,
                               const GURL& url);
  void ExpectNewForegroundDuration(const GURL& url, bool expect_bfcache);

 private:
  int64_t expected_page_load_foreground_durations_ = 0;
  int64_t expected_bfcache_foreground_durations_ = 0;

  base::test::ScopedFeatureList feature_list_;
};

// Verifies the page end reasons are as we expect. This means that the first
// page end reason is always recorded in Navigation.PageEndReason3, and
// subsequent reasons are recorded in HistoryNavigation.PageEndReason if bfcache
// is enabled, or Navigation.PageEndReason3 if not.
void PageLoadMetricsBackForwardCacheBrowserTest::VerifyPageEndReasons(
    const std::vector<PageEndReason>& reasons,
    const GURL& url,
    bool is_bfcache_enabled) {
  unsigned int reason_index = 0;
  for (const ukm::mojom::UkmEntry* entry :
       test_ukm_recorder_->GetEntriesByName(PageLoad::kEntryName)) {
    auto* source = test_ukm_recorder_->GetSourceForSourceId(entry->source_id);
    if (source->url() != url) {
      continue;
    }
    if (test_ukm_recorder_->EntryHasMetric(
            entry, PageLoad::kNavigation_PageEndReason3Name)) {
      if (is_bfcache_enabled) {
        // If bfcache is on then only one of these should exist, so the index
        // should be zero.
        EXPECT_EQ(reason_index, 0U);
      }
      ASSERT_LT(reason_index, reasons.size());
      test_ukm_recorder_->ExpectEntryMetric(
          entry, PageLoad::kNavigation_PageEndReason3Name,
          reasons[reason_index++]);
    }
  }
  if (is_bfcache_enabled) {
    EXPECT_EQ(reason_index, 1U);
  } else {
    EXPECT_EQ(reason_index, reasons.size());
  }
  for (const ukm::mojom::UkmEntry* entry :
       test_ukm_recorder_->GetEntriesByName(HistoryNavigation::kEntryName)) {
    auto* source = test_ukm_recorder_->GetSourceForSourceId(entry->source_id);
    if (source->url() != url)
      continue;
    if (test_ukm_recorder_->EntryHasMetric(
            entry, HistoryNavigation::
                       kPageEndReasonAfterBackForwardCacheRestoreName)) {
      EXPECT_TRUE(is_bfcache_enabled);
      ASSERT_LT(reason_index, reasons.size());
      test_ukm_recorder_->ExpectEntryMetric(
          entry,
          HistoryNavigation::kPageEndReasonAfterBackForwardCacheRestoreName,
          reasons[reason_index++]);
    }
  }
  // Should have been through all the reasons.
  EXPECT_EQ(reason_index, reasons.size());
}

int64_t PageLoadMetricsBackForwardCacheBrowserTest::CountForMetricForURL(
    std::string_view entry_name,
    std::string_view metric_name,
    const GURL& url) {
  int64_t count = 0;
  for (const ukm::mojom::UkmEntry* entry :
       test_ukm_recorder_->GetEntriesByName(entry_name)) {
    auto* source = test_ukm_recorder_->GetSourceForSourceId(entry->source_id);
    if (source->url() != url) {
      continue;
    }
    if (test_ukm_recorder_->EntryHasMetric(entry, metric_name)) {
      count++;
    }
  }
  return count;
}

IN_PROC_BROWSER_TEST_P(PageLoadMetricsBackForwardCacheBrowserTest,
                       LogsPageEndReasons) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  bool back_forward_cache_enabled = GetParam() == kEnabled;
  // Navigate to A.
  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url_a));
  content::RenderFrameHostWrapper rfh_a(web_contents()->GetPrimaryMainFrame());

  // Navigate to B.
  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));
  if (back_forward_cache_enabled) {
    ASSERT_EQ(rfh_a->GetLifecycleState(),
              content::RenderFrameHost::LifecycleState::kInBackForwardCache);
  }

  std::vector<PageEndReason> expected_reasons_a;
  expected_reasons_a.push_back(page_load_metrics::END_NEW_NAVIGATION);
  VerifyPageEndReasons(expected_reasons_a, url_a, back_forward_cache_enabled);

  // Go back to A, restoring it from the back-forward cache (if enabled)
  web_contents()->GetController().GoBack();
  EXPECT_TRUE(WaitForLoadStop(web_contents()));

  // Navigate to B again - this should trigger the
  // BackForwardCachePageLoadMetricsObserver for A (if enabled)
  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));
  expected_reasons_a.push_back(page_load_metrics::END_NEW_NAVIGATION);
  VerifyPageEndReasons(expected_reasons_a, url_a, back_forward_cache_enabled);

  // Go back to A, restoring it from the back-forward cache (again)
  web_contents()->GetController().GoBack();
  EXPECT_TRUE(WaitForLoadStop(web_contents()));

  // Navigate to B using GoForward() to verify the correct page end reason
  // is stored for A.
  web_contents()->GetController().GoForward();
  EXPECT_TRUE(WaitForLoadStop(web_contents()));
  expected_reasons_a.push_back(page_load_metrics::END_FORWARD_BACK);
  VerifyPageEndReasons(expected_reasons_a, url_a, back_forward_cache_enabled);
}

void PageLoadMetricsBackForwardCacheBrowserTest::ExpectNewForegroundDuration(
    const GURL& url,
    bool expect_bfcache) {
  if (expect_bfcache) {
    expected_bfcache_foreground_durations_++;
  } else {
    expected_page_load_foreground_durations_++;
  }
  int64_t bf_count = CountForMetricForURL(
      HistoryNavigation::kEntryName,
      HistoryNavigation::kForegroundDurationAfterBackForwardCacheRestoreName,
      url);
  int64_t pl_count = CountForMetricForURL(
      PageLoad::kEntryName, PageLoad::kPageTiming_ForegroundDurationName, url);
  EXPECT_EQ(bf_count, expected_bfcache_foreground_durations_);
  EXPECT_EQ(pl_count, expected_page_load_foreground_durations_);
}

IN_PROC_BROWSER_TEST_P(PageLoadMetricsBackForwardCacheBrowserTest,
                       LogsBasicPageForegroundDuration) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  bool back_forward_cache_enabled = GetParam() == kEnabled;
  // Navigate to A.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_a));
  content::RenderFrameHostWrapper rfh_a(web_contents()->GetPrimaryMainFrame());

  // Navigate to B.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));
  if (back_forward_cache_enabled) {
    ASSERT_EQ(rfh_a->GetLifecycleState(),
              content::RenderFrameHost::LifecycleState::kInBackForwardCache);
  }

  // Verify a new foreground duration - this one shouldn't be logged by the
  // bfcache metrics regardless of bfcache being enabled or not.
  ExpectNewForegroundDuration(url_a, /*expect_bfcache=*/false);

  // Go back to A, restoring it from the back-forward cache (if enabled)
  web_contents()->GetController().GoBack();
  ASSERT_TRUE(WaitForLoadStop(web_contents()));

  // Navigate to B again - this should trigger the
  // BackForwardCachePageLoadMetricsObserver for A (if enabled)
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));

  ExpectNewForegroundDuration(url_a, back_forward_cache_enabled);

  // Go back to A, restoring it from the back-forward cache (again)
  web_contents()->GetController().GoBack();
  ASSERT_TRUE(WaitForLoadStop(web_contents()));

  web_contents()->GetController().GoForward();
  ASSERT_TRUE(WaitForLoadStop(web_contents()));

  // Verify another foreground duration was logged.
  ExpectNewForegroundDuration(url_a, back_forward_cache_enabled);
}

IN_PROC_BROWSER_TEST_P(PageLoadMetricsBackForwardCacheBrowserTest,
                       LogsPageForegroundDurationOnHide) {
  ASSERT_TRUE(embedded_test_server()->Start());
  GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
  GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));

  bool back_forward_cache_enabled = GetParam() == kEnabled;
  // Navigate to A.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_a));
  content::RenderFrameHostWrapper rfh_a(web_contents()->GetPrimaryMainFrame());

  // Navigate to B.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_b));
  if (back_forward_cache_enabled) {
    ASSERT_EQ(rfh_a->GetLifecycleState(),
              content::RenderFrameHost::LifecycleState::kInBackForwardCache);
  }

  // Verify a new foreground duration - this one shouldn't be logged by the
  // bfcache metrics regardless of bfcache being enabled or not.
  ExpectNewForegroundDuration(url_a, /*expect_bfcache=*/false);

  // Go back to A, restoring it from the back-forward cache (if enabled)
  web_contents()->GetController().GoBack();
  ASSERT_TRUE(WaitForLoadStop(web_contents()));

  // Open and move to a new tab. This hides A, which should log a foreground
  // duration.
  ui_test_utils::NavigateToURLWithDisposition(
      browser(), url_b, WindowOpenDisposition::NEW_FOREGROUND_TAB,
      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
  // The new tab opening should cause a foreground duration for the original
  // tab, since it's been hidden.
  ExpectNewForegroundDuration(url_a, back_forward_cache_enabled);

  // From this point no more foreground durations are expected to be logged, so
  // stash the current counts.
  int64_t bf_count = CountForMetricForURL(
      HistoryNavigation::kEntryName,
      HistoryNavigation::kForegroundDurationAfterBackForwardCacheRestoreName,
      url_a);
  int64_t pl_count =
      CountForMetricForURL(PageLoad::kEntryName,
                           PageLoad::kPageTiming_ForegroundDurationName, url_a);

  // Switch back to the tab for url_a.
  ui_test_utils::NavigateToURLWithDisposition(
      browser(), url_a, WindowOpenDisposition::SWITCH_TO_TAB,
      ui_test_utils::BROWSER_TEST_NO_WAIT);

  // And then switch back to url_b's tab. This should call OnHidden for the
  // url_a tab again, but no new foreground duration should be logged.
  ui_test_utils::NavigateToURLWithDisposition(
      browser(), url_b, WindowOpenDisposition::SWITCH_TO_TAB,
      ui_test_utils::BROWSER_TEST_NO_WAIT);

  int64_t bf_count_after_switch = CountForMetricForURL(
      HistoryNavigation::kEntryName,
      HistoryNavigation::kForegroundDurationAfterBackForwardCacheRestoreName,
      url_a);
  int64_t pl_count_after_switch =
      CountForMetricForURL(PageLoad::kEntryName,
                           PageLoad::kPageTiming_ForegroundDurationName, url_a);
  EXPECT_EQ(bf_count, bf_count_after_switch);
  EXPECT_EQ(pl_count, pl_count_after_switch);

  // Switch back to the tab for url_a, then close the browser. This should cause
  // OnComplete to be called on the BFCache observer, but this should not cause
  // a new foreground duration to be logged.
  ui_test_utils::NavigateToURLWithDisposition(
      browser(), url_a, WindowOpenDisposition::SWITCH_TO_TAB,
      ui_test_utils::BROWSER_TEST_NO_WAIT);
  CloseBrowserSynchronously(browser());

  // Neither of the metrics for url_a should have moved.
  int64_t bf_count_after_close = CountForMetricForURL(
      HistoryNavigation::kEntryName,
      HistoryNavigation::kForegroundDurationAfterBackForwardCacheRestoreName,
      url_a);
  int64_t pl_count_after_close =
      CountForMetricForURL(PageLoad::kEntryName,
                           PageLoad::kPageTiming_ForegroundDurationName, url_a);
  EXPECT_EQ(bf_count, bf_count_after_close);
  EXPECT_EQ(pl_count, pl_count_after_close);
}

INSTANTIATE_TEST_SUITE_P(
    All,
    PageLoadMetricsBackForwardCacheBrowserTest,
    testing::ValuesIn({BackForwardCacheStatus::kDisabled,
                       BackForwardCacheStatus::kEnabled}),
    PageLoadMetricsBackForwardCacheBrowserTest::DescribeParams);

// ============================================================================
// Desktop Paint Timing Slices Test Suite
// ============================================================================
//
// Domain:
// Validates end-to-end integration of desktop paint timing metric slices
// across core post-startup navigation scenarios (NewWindow and SameWindow).
// Startup navigation and granular combinatorial edge cases are covered by unit
// tests in page_load_metrics_initialize_unittest.cc.

namespace {

constexpr char kFcpPrefix[] =
    "PageLoad.PaintTiming.NavigationToFirstContentfulPaint";
constexpr char kLcpPrefix[] =
    "PageLoad.PaintTiming.NavigationToLargestContentfulPaint2";
constexpr std::array<const char*, 3> kMetricSuffixes = {
    ".Startup", ".NewWindow", ".SameWindow"};

// Verifies that the expected slice suffix receives the specified metric count
// and all other scenario slice suffixes receive zero recordings.
void ExpectSlicedPaintMetrics(const base::HistogramTester& tester,
                              const std::string& expected_suffix,
                              size_t expected_count) {
  for (const char* suffix : kMetricSuffixes) {
    const size_t count = (expected_suffix == suffix) ? expected_count : 0;
    tester.ExpectTotalCount(std::string(kFcpPrefix) + suffix, count);
    tester.ExpectTotalCount(std::string(kLcpPrefix) + suffix, count);
  }
}

}  // namespace

using DesktopPaintTimingSliceBrowserTest = PageLoadMetricsBrowserTest;

// Validates paint timing metric slices for a single-tab new window navigation
// after startup completion.
//
// Scenario:
// - Action: Open URL in a newly created browser window.
// - Precondition: `is_startup` = false, new window target index = 0.
// - Expected Metric Suffix: `.NewWindow`
IN_PROC_BROWSER_TEST_F(DesktopPaintTimingSliceBrowserTest,
                       NewWindowNavigation) {
  ASSERT_TRUE(embedded_test_server()->Start());
  const GURL url = embedded_test_server()->GetURL("/title1.html");

  base::HistogramTester histogram_tester;
  NavigateParams nav_params(browser(), url, ui::PAGE_TRANSITION_LINK);
  nav_params.disposition = WindowOpenDisposition::NEW_WINDOW;
  Navigate(&nav_params);

  BrowserWindowInterface* new_browser =
      GetLastActiveBrowserWindowInterfaceWithAnyProfile();
  PageLoadMetricsTestWaiter waiter(
      new_browser->GetTabStripModel()->GetActiveWebContents());
  waiter.AddPageExpectation(TimingField::kFirstContentfulPaint);
  waiter.AddPageExpectation(TimingField::kLargestContentfulPaint);
  waiter.AddPageExpectation(TimingField::kLoadEvent);
  waiter.Wait();

  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(new_browser, GURL(url::kAboutBlankURL)));
  ExpectSlicedPaintMetrics(histogram_tester, ".NewWindow", 1);
}

// Validates paint timing metric slices for opening a new tab within an
// existing window after startup completion.
//
// Scenario:
// - Action: Open URL in a new foreground tab within existing window.
// - Precondition: `is_startup` = false, target index = 1.
// - Expected Metric Suffix: `.SameWindow`
IN_PROC_BROWSER_TEST_F(DesktopPaintTimingSliceBrowserTest,
                       SameWindowNewTabNavigation) {
  ASSERT_TRUE(embedded_test_server()->Start());
  const GURL url = embedded_test_server()->GetURL("/title1.html");

  base::HistogramTester histogram_tester;
  NavigateParams nav_params(browser(), url, ui::PAGE_TRANSITION_LINK);
  nav_params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
  Navigate(&nav_params);

  PageLoadMetricsTestWaiter waiter(
      browser()->GetTabStripModel()->GetActiveWebContents());
  waiter.AddPageExpectation(TimingField::kFirstContentfulPaint);
  waiter.AddPageExpectation(TimingField::kLargestContentfulPaint);
  waiter.AddPageExpectation(TimingField::kLoadEvent);
  waiter.Wait();

  NavigateToUntrackedUrl();
  ExpectSlicedPaintMetrics(histogram_tester, ".SameWindow", 1);
}

// Validates paint timing metric slices for tab reloads within an existing
// window after startup completion.
//
// Scenario:
// - Action: Reload active tab in existing window.
// - Precondition: `is_startup` = false, active undiscarded tab.
// - Expected Metric Suffix: `.SameWindow` (2 recordings: 1 for initial load, 1
// for reload)
IN_PROC_BROWSER_TEST_F(DesktopPaintTimingSliceBrowserTest,
                       TabReloadNavigation) {
  ASSERT_TRUE(embedded_test_server()->Start());
  const GURL url = embedded_test_server()->GetURL("/title1.html");

  base::HistogramTester histogram_tester;
  auto waiter = CreatePageLoadMetricsTestWaiter("initial_waiter");
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  waiter->AddPageExpectation(TimingField::kLargestContentfulPaint);
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  waiter->Wait();

  waiter = CreatePageLoadMetricsTestWaiter("reload_waiter");
  waiter->AddPageExpectation(TimingField::kFirstContentfulPaint);
  waiter->AddPageExpectation(TimingField::kLargestContentfulPaint);
  waiter->AddPageExpectation(TimingField::kLoadEvent);
  chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB);
  waiter->Wait();

  NavigateToUntrackedUrl();
  ExpectSlicedPaintMetrics(histogram_tester, ".SameWindow", 2);
}
