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

#include "content/browser/preloading/prerenderer_impl.h"

#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "content/browser/preloading/prefetch/prefetch_document_manager.h"
#include "content/browser/preloading/prefetch/prefetch_features.h"
#include "content/browser/preloading/prefetch/prefetch_match_resolver.h"
#include "content/browser/preloading/prefetch/prefetch_service.h"
#include "content/browser/preloading/prefetch/prefetch_status.h"
#include "content/browser/preloading/prefetch/prefetch_test_util_internal.h"
#include "content/browser/preloading/preload_serving_metrics_holder.h"
#include "content/browser/preloading/preloading.h"
#include "content/browser/preloading/preloading_confidence.h"
#include "content/browser/preloading/preloading_decider.h"
#include "content/browser/preloading/prerender/prerender_features.h"
#include "content/browser/preloading/prerender/prerender_host_registry.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/isolated_world_ids.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/prefetch_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_content_browser_client.h"
#include "content/public/test/test_renderer_host.h"
#include "content/shell/browser/shell.h"
#include "content/test/test_web_contents.h"
#include "net/dns/mock_host_resolver.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/navigation/preloading_headers.h"
#include "third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom.h"

namespace content {
namespace {

struct RequestPathAndSecPurposeHeader {
  std::string path;
  std::string sec_purpose_header_value;

  bool operator==(const RequestPathAndSecPurposeHeader& other) const = default;
};

std::ostream& operator<<(std::ostream& ostream,
                         const RequestPathAndSecPurposeHeader& x) {
  return ostream << "RequestPathAndSecPurposeHeader {.path = \"" << x.path
                 << "\", .sec_purpose_header_value = \""
                 << x.sec_purpose_header_value << "\"}";
}

class PrerendererImplBrowserTestBase : public ContentBrowserTest {
 public:
  PrerendererImplBrowserTestBase() = default;
  ~PrerendererImplBrowserTestBase() override = default;

  void SetUp() override {
    prerender_helper_ =
        std::make_unique<test::PrerenderTestHelper>(base::BindRepeating(
            [](PrerendererImplBrowserTestBase* that) {
              return &that->web_contents();
            },
            base::Unretained(this)));

    ContentBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    https_server_ = std::make_unique<net::EmbeddedTestServer>(
        net::EmbeddedTestServer::TYPE_HTTPS);
    histogram_tester_ = std::make_unique<base::HistogramTester>();

    host_resolver()->AddRule("*", "127.0.0.1");

    https_server_->SetSSLConfig(
        net::test_server::EmbeddedTestServer::CERT_TEST_NAMES);
    https_server_->RegisterRequestMonitor(
        base::BindRepeating(&PrerendererImplBrowserTestBase::OnResourceRequest,
                            base::Unretained(this)));
    https_server_->AddDefaultHandlers(GetTestDataFilePath());
    ASSERT_TRUE(https_server_->Start());

    embedded_test_server()->RegisterRequestMonitor(
        base::BindRepeating(&PrerendererImplBrowserTestBase::OnResourceRequest,
                            base::Unretained(this)));
    embedded_test_server()->AddDefaultHandlers(GetTestDataFilePath());
    ASSERT_TRUE(embedded_test_server()->Start());

    PreloadServingMetricsHolder::SetDestructorCallbackForTesting(
        base::BindRepeating(&PrerendererImplBrowserTestBase::
                                OnPreloadServingMetricsHolderDestructor,
                            base::Unretained(this)));
  }

  void TearDownOnMainThread() override {
    PreloadServingMetricsHolder::SetDestructorCallbackForTesting({});

    ASSERT_TRUE(https_server_->ShutdownAndWaitUntilComplete());
    ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
  }

  GURL GetUrl(const std::string& path) {
    return https_server_->GetURL("a.test", path);
  }

  GURL GetCrossSiteUrl(const std::string& path) {
    return https_server_->GetURL("b.test", path);
  }

  GURL GetUrlHttp(const std::string& path) {
    return embedded_test_server()->GetURL("a.test", path);
  }

  Prefetcher& GetPrefetcher() {
    return PreloadingDecider::GetOrCreateForCurrentDocument(
               web_contents_impl().GetPrimaryMainFrame())
        ->GetPrefetcherForTesting();
  }

  PrerendererImpl& GetPrerendererImpl() {
    return static_cast<PrerendererImpl&>(
        PreloadingDecider::GetOrCreateForCurrentDocument(
            web_contents_impl().GetPrimaryMainFrame())
            ->GetPrerendererForTesting());
  }

  blink::mojom::SpeculationCandidatePtr CreateSpeculationCandidate(
      const GURL& url,
      blink::mojom::SpeculationAction prerender_action_type =
          blink::mojom::SpeculationAction::kPrerender) {
    return blink::mojom::SpeculationCandidate::New(
        /*url=*/url,
        /*action=*/prerender_action_type,
        /*referrer=*/blink::mojom::Referrer::New(),
        /*requires_anonymous_client_ip_when_cross_origin=*/false,
        /*target_browsing_context_name_hint=*/
        blink::mojom::SpeculationTargetHint::kNoHint,
        /*eagerness=*/blink::mojom::SpeculationEagerness::kImmediate,
        /*no_vary_search_hint=*/nullptr,
        /*injection_type=*/blink::mojom::SpeculationInjectionType::kNone,
        /*tags=*/std::vector<std::optional<std::string>>{std::nullopt},
        /*form_submission=*/false);
  }

  std::vector<RequestPathAndSecPurposeHeader> GetObservedRequests() {
    CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

    base::AutoLock auto_lock(lock_);

    std::vector<RequestPathAndSecPurposeHeader> ret;
    for (auto request : requests_) {
      ret.push_back(RequestPathAndSecPurposeHeader{
          .path = request.GetURL().PathForRequest(),
          .sec_purpose_header_value =
              request.headers[blink::kSecPurposeHeaderName],
      });
    }
    return ret;
  }

  void SetResponseDelay(base::TimeDelta duration) {
    response_delay_ = duration;
  }

  void OnPreloadServingMetricsHolderDestructor(
      std::unique_ptr<PreloadServingMetrics> log) {
    preload_serving_metrics_list_.push_back(std::move(log));
  }

  RenderFrameHostImpl& GetPrimaryMainFrameHost() {
    return static_cast<WebContentsImpl*>(shell()->web_contents())
        ->GetPrimaryPage()
        .GetMainDocument();
  }

  net::EmbeddedTestServer& https_server() { return *https_server_.get(); }
  base::HistogramTester& histogram_tester() { return *histogram_tester_.get(); }
  test::PrerenderTestHelper& prerender_helper() {
    return *prerender_helper_.get();
  }
  WebContents& web_contents() { return *shell()->web_contents(); }
  WebContentsImpl& web_contents_impl() {
    return static_cast<WebContentsImpl&>(web_contents());
  }
  std::vector<std::unique_ptr<PreloadServingMetrics>>&
  preload_serving_metrics_list() {
    return preload_serving_metrics_list_;
  }

 protected:
  base::test::ScopedFeatureList feature_list_;

 private:
  void OnResourceRequest(const net::test_server::HttpRequest& request) {
    // Called from a thread for EmbeddedTestServer.
    CHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI) &&
          !BrowserThread::CurrentlyOn(BrowserThread::IO));

    base::PlatformThread::Sleep(response_delay_);

    // So, we guard the field with lock.
    base::AutoLock auto_lock(lock_);

    requests_.push_back(request);
  }

  std::unique_ptr<net::EmbeddedTestServer> https_server_;
  std::unique_ptr<base::HistogramTester> histogram_tester_;
  std::unique_ptr<test::PrerenderTestHelper> prerender_helper_;

  base::TimeDelta response_delay_ = base::Seconds(0);
  base::Lock lock_;
  std::vector<net::test_server::HttpRequest> requests_ GUARDED_BY(lock_);

  std::vector<std::unique_ptr<PreloadServingMetrics>>
      preload_serving_metrics_list_;
};

class PrerendererImplBrowserTestNoPrefetchAhead
    : public PrerendererImplBrowserTestBase {
 public:
  PrerendererImplBrowserTestNoPrefetchAhead() {
    feature_list_.InitWithFeatures(
        {}, {features::kPrerender2FallbackPrefetchSpecRules,
             blink::features::kLCPTimingPredictorPrerender2});
  }
};

class PrerendererBackForwardCacheTest : public PrerendererImplBrowserTestBase {
 public:
  PrerendererBackForwardCacheTest() {
    feature_list_.InitWithFeatures(
        {features::kBackForwardCache,
         features::kPrerender2FallbackPrefetchSpecRules},
        {features::kBackForwardCacheMemoryControls,
         blink::features::kLCPTimingPredictorPrerender2});
  }
};

class PrerendererImplBrowserTestPrefetchAhead
    : public PrerendererImplBrowserTestBase,
      public ::testing::WithParamInterface<
          features::Prerender2FallbackPrefetchSchedulerPolicy> {
 public:
  PrerendererImplBrowserTestPrefetchAhead() {
    const char* prefetch_scheduler_policy = [&]() {
      switch (GetParam()) {
        case features::Prerender2FallbackPrefetchSchedulerPolicy::kNotUse:
          return "NotUse";
        case features::Prerender2FallbackPrefetchSchedulerPolicy::kPrioritize:
          return "Prioritize";
        case features::Prerender2FallbackPrefetchSchedulerPolicy::kBurst:
          return "Burst";
      }
    }();
    feature_list_.InitWithFeaturesAndParameters(
        {{
             features::kPrerender2FallbackPrefetchSpecRules,
             {
                 {"kPrerender2FallbackPrefetchSchedulerPolicy",
                  prefetch_scheduler_policy},
                 {"kPrerender2FallbackPrefetchUseBlockUntilHeadTimetout",
                  "false"},
             },
         },
         {
             features::kPrefetchUseContentRefactor,
             {
                 {"prefetch_timeout_ms", "1500"},
                 {"block_until_head_timeout_moderate_prefetch", "500"},
             },
         },
         {blink::features::kPrerenderUntilScript, {}}},
        {
            blink::features::kLCPTimingPredictorPrerender2,
            // `kPrefetchServiceWorker` is disabled to make the prefetch fail
            // due to ServiceWorker-related ineligibility.
            features::kPrefetchServiceWorker,
        });
  }
};

INSTANTIATE_TEST_SUITE_P(
    /* no prefix */,
    PrerendererImplBrowserTestPrefetchAhead,
    testing::Values(
        features::Prerender2FallbackPrefetchSchedulerPolicy::kNotUse,
        features::Prerender2FallbackPrefetchSchedulerPolicy::kPrioritize,
        features::Prerender2FallbackPrefetchSchedulerPolicy::kBurst));

IN_PROC_BROWSER_TEST_F(PrerendererImplBrowserTestNoPrefetchAhead,
                       PrefetchNotTriggeredPrerenderSuccess) {
  ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));

  const GURL prerender_url = GetUrl("/title1.html");
  blink::mojom::SpeculationCandidatePtr candidate =
      CreateSpeculationCandidate(prerender_url);
  PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
      PreloadingTriggerType::kSpeculationRule);
  GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
                                      PreloadingConfidence{100});
  prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);

  prerender_helper().NavigatePrimaryPage(prerender_url);

  histogram_tester().ExpectTotalCount(
      "Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome", 0);
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kSuccess, 1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
      PrerenderFinalStatus::kActivated, 1);

  histogram_tester().ExpectTotalCount(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.Count",
      0);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/empty.html", .sec_purpose_header_value = ""},
      {.path = "/title1.html",
       .sec_purpose_header_value =
           blink::kSecPurposePrefetchPrerenderHeaderValue}};
  ASSERT_EQ(expected, GetObservedRequests());
}

IN_PROC_BROWSER_TEST_F(PrerendererImplBrowserTestNoPrefetchAhead,
                       PrefetchNotTriggeredPrerenderFailure) {
  ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));

  const GURL prerender_url = GetUrl("/title1.html");
  blink::mojom::SpeculationCandidatePtr candidate =
      CreateSpeculationCandidate(prerender_url);
  PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
      PreloadingTriggerType::kSpeculationRule);
  GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
                                      PreloadingConfidence{100});
  prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);

  // Make prerender failure by calling a forbidden API.
  test::PrerenderHostObserver observer(web_contents(), prerender_url);
  RenderFrameHost* rfh =
      prerender_helper().GetPrerenderedMainFrameHost(prerender_url);
  ASSERT_TRUE(rfh);
  const char* script = "navigator.getGamepads();";
  rfh->ExecuteJavaScriptForTests(base::UTF8ToUTF16(script),
                                 base::NullCallback(),
                                 ISOLATED_WORLD_ID_GLOBAL);
  observer.WaitForDestroyed();

  prerender_helper().NavigatePrimaryPage(prerender_url);

  histogram_tester().ExpectTotalCount(
      "Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome", 0);
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kFailure, 1);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/empty.html", .sec_purpose_header_value = ""},
      {.path = "/title1.html",
       .sec_purpose_header_value =
           blink::kSecPurposePrefetchPrerenderHeaderValue},
      {.path = "/title1.html", .sec_purpose_header_value = ""},
  };
  ASSERT_EQ(expected, GetObservedRequests());
}

IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
                       PrefetchSuccessPrerenderSuccess) {
  ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
  preload_serving_metrics_list().clear();

  const GURL prerender_url = GetUrl("/title1.html");
  blink::mojom::SpeculationCandidatePtr candidate =
      CreateSpeculationCandidate(prerender_url);
  PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
      PreloadingTriggerType::kSpeculationRule);
  GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
                                      PreloadingConfidence{100});
  prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);

  prerender_helper().NavigatePrimaryPage(prerender_url);

  // TODO(https://crbug.com/342089066): Record prefetch as
  // kTriggeredButUpgradedToPrerender.
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kSuccess, 1);
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kSuccess, 1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
      PrerenderFinalStatus::kActivated, 1);

  histogram_tester().ExpectTotalCount(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.Count",
      0);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/empty.html", .sec_purpose_header_value = ""},
      {.path = "/title1.html",
       .sec_purpose_header_value =
           blink::kSecPurposePrefetchPrerenderHeaderValue}};
  ASSERT_EQ(expected, GetObservedRequests());

  ASSERT_EQ(2u, preload_serving_metrics_list().size());

  auto& prerender_initial_preload_serving_metrics =
      preload_serving_metrics_list()[0];
  // Taken and used to record UMAs.
  ASSERT_FALSE(prerender_initial_preload_serving_metrics);

  auto& preload_serving_metrics = preload_serving_metrics_list()[1];
  ASSERT_TRUE(preload_serving_metrics);
  ASSERT_EQ(0u, preload_serving_metrics->prefetch_match_metrics_list.size());
  ASSERT_TRUE(
      preload_serving_metrics->prerender_initial_preload_serving_metrics);
  ASSERT_EQ(1u,
            preload_serving_metrics->prerender_initial_preload_serving_metrics
                ->prefetch_match_metrics_list.size());
  ASSERT_TRUE(preload_serving_metrics->prerender_initial_preload_serving_metrics
                  ->prefetch_match_metrics_list[0]);
  ASSERT_EQ(1,
            preload_serving_metrics->prerender_initial_preload_serving_metrics
                ->prefetch_match_metrics_list[0]
                ->n_initial_candidates);
  ASSERT_EQ(1,
            preload_serving_metrics->prerender_initial_preload_serving_metrics
                ->prefetch_match_metrics_list[0]
                ->n_initial_candidates_block_until_head);
  ASSERT_TRUE(preload_serving_metrics->prerender_initial_preload_serving_metrics
                  ->prefetch_match_metrics_list[0]
                  ->prefetch_container_metrics);
  ASSERT_TRUE(preload_serving_metrics->prerender_initial_preload_serving_metrics
                  ->prefetch_match_metrics_list[0]
                  ->prefetch_container_metrics
                  ->time_header_determined_successfully.has_value());
  ASSERT_TRUE(preload_serving_metrics->prerender_initial_preload_serving_metrics
                  ->prefetch_match_metrics_list[0]
                  ->prefetch_container_metrics_ahead_of_prerender);
  ASSERT_TRUE(
      preload_serving_metrics->prerender_initial_preload_serving_metrics
          ->prefetch_match_metrics_list[0]
          ->prefetch_potential_candidate_serving_result_ahead_of_prerender);
}

IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
                       PrefetchSuccessPrerenderNotEligible) {
  ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));

  test::TestPrefetchWatcher watcher;
  const GURL prerender_url = GetCrossSiteUrl("/title1.html");
  blink::mojom::SpeculationCandidatePtr candidate =
      CreateSpeculationCandidate(prerender_url);
  PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
      PreloadingTriggerType::kSpeculationRule);
  GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
                                      PreloadingConfidence{100});
  watcher.WaitUntilPrefetchResponseCompleted(
      web_contents_impl().GetPrimaryMainFrame()->GetDocumentToken(),
      prerender_url);

  prerender_helper().NavigatePrimaryPage(prerender_url);

  histogram_tester().ExpectUniqueSample(
      "Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kSuccess, 1);
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kUnspecified, 1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
      PrerenderFinalStatus::kCrossSiteNavigationInInitialNavigation, 1);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/empty.html", .sec_purpose_header_value = ""},
      {.path = "/title1.html",
       .sec_purpose_header_value =
           blink::kSecPurposePrefetchPrerenderHeaderValue},
  };
  ASSERT_EQ(expected, GetObservedRequests());
}

IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
                       PrefetchSuccessPrerenderFailure) {
  ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
  preload_serving_metrics_list().clear();

  const GURL prerender_url = GetUrl("/title1.html");
  blink::mojom::SpeculationCandidatePtr candidate =
      CreateSpeculationCandidate(prerender_url);
  PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
      PreloadingTriggerType::kSpeculationRule);
  GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
                                      PreloadingConfidence{100});
  prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);

  // Make prerender failure by calling a forbidden API.
  test::PrerenderHostObserver observer(web_contents(), prerender_url);
  RenderFrameHost* rfh =
      prerender_helper().GetPrerenderedMainFrameHost(prerender_url);
  ASSERT_TRUE(rfh);
  const char* script = "navigator.getGamepads();";
  rfh->ExecuteJavaScriptForTests(base::UTF8ToUTF16(script),
                                 base::NullCallback(),
                                 ISOLATED_WORLD_ID_GLOBAL);
  observer.WaitForDestroyed();

  prerender_helper().NavigatePrimaryPage(prerender_url);

  // TODO(https://crbug.com/342089066): Record prefetch as
  // kTriggeredButUpgradedToPrerender.
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kSuccess, 1);
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kFailure, 1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
      PrerenderFinalStatus::kMojoBinderPolicy, 1);

  histogram_tester().ExpectUniqueSample(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.Count",
      1, 1);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/empty.html", .sec_purpose_header_value = ""},
      {.path = "/title1.html",
       .sec_purpose_header_value =
           blink::kSecPurposePrefetchPrerenderHeaderValue},
  };
  ASSERT_EQ(expected, GetObservedRequests());

  ASSERT_EQ(2u, preload_serving_metrics_list().size());

  auto& prerender_initial_preload_serving_metrics =
      preload_serving_metrics_list()[0];
  // Taken and used to record UMAs.
  ASSERT_FALSE(prerender_initial_preload_serving_metrics);

  auto& preload_serving_metrics = preload_serving_metrics_list()[1];
  ASSERT_TRUE(preload_serving_metrics);
  ASSERT_EQ(1u, preload_serving_metrics->prefetch_match_metrics_list.size());
  ASSERT_EQ(1u, preload_serving_metrics->prefetch_match_metrics_list[0]
                    ->n_initial_candidates);
  ASSERT_EQ(0u, preload_serving_metrics->prefetch_match_metrics_list[0]
                    ->n_initial_candidates_block_until_head);
  ASSERT_TRUE(preload_serving_metrics->prefetch_match_metrics_list[0]
                  ->prefetch_container_metrics);
  ASSERT_TRUE(preload_serving_metrics->prefetch_match_metrics_list[0]
                  ->prefetch_container_metrics
                  ->time_header_determined_successfully.has_value());
  ASSERT_FALSE(preload_serving_metrics->prefetch_match_metrics_list[0]
                   ->prefetch_container_metrics_ahead_of_prerender);
  ASSERT_FALSE(
      preload_serving_metrics->prefetch_match_metrics_list[0]
          ->prefetch_potential_candidate_serving_result_ahead_of_prerender);
  ASSERT_FALSE(
      preload_serving_metrics->prerender_initial_preload_serving_metrics);
}

IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
                       PrefetchNotEligiblePrerenderFailure) {
  // Make eligibility check fail with `kHostIsNonUnique`.
  auto& prefetch_service = *PrefetchService::GetFromFrameTreeNodeId(
      web_contents().GetPrimaryMainFrame()->GetFrameTreeNodeId());
  prefetch_service.SetInjectedEligibilityCheckForTesting(base::BindRepeating(
      [](PrefetchService::InjectedEligibilityCheckResultCallbackForTesting
             callback) {
        std::move(callback).Run(PreloadingEligibility::kHostIsNonUnique);
      }));

  ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));

  const GURL prerender_url = GetUrl("/title1.html");
  blink::mojom::SpeculationCandidatePtr candidate =
      CreateSpeculationCandidate(prerender_url);
  PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
      PreloadingTriggerType::kSpeculationRule);
  GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
                                      PreloadingConfidence{100});

  // Here we shouldn't call
  // `prerender_helper().WaitForPrerenderLoadCompletion(prerender_url)` since
  // this eligibility check of prefetch synchronously fails and the call first
  // tries to get `PrerenderHost`, which has been already destructed.

  ASSERT_TRUE(NavigateToURL(shell(), prerender_url));

  prefetch_service.SetInjectedEligibilityCheckForTesting(base::NullCallback());

  histogram_tester().ExpectUniqueSample(
      "Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kUnspecified, 1);
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kFailure, 1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
      PrerenderFinalStatus::kPrerenderFailedDuringPrefetch, 1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrefetchAheadOfPrerenderFailed.PrefetchStatus."
      "SpeculationRule",
      PrefetchStatus::kPrefetchIneligibleHostIsNonUnique, 1);

  histogram_tester().ExpectUniqueSample(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.Count",
      1, 1);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/empty.html", .sec_purpose_header_value = ""},
      {.path = "/title1.html", .sec_purpose_header_value = ""},
  };
  ASSERT_EQ(expected, GetObservedRequests());
}

IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
                       PrefetchNotEligibleNonHttpsPrerenderSuccess) {
  ASSERT_TRUE(NavigateToURL(shell(), GetUrlHttp("/empty.html")));

  const GURL prerender_url = GetUrlHttp("/title1.html");
  blink::mojom::SpeculationCandidatePtr candidate =
      CreateSpeculationCandidate(prerender_url);
  PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
      PreloadingTriggerType::kSpeculationRule);
  GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
                                      PreloadingConfidence{100});
  prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);

  prerender_helper().NavigatePrimaryPage(prerender_url);

  histogram_tester().ExpectUniqueSample(
      "Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kUnspecified, 1);
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kSuccess, 1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
      PrerenderFinalStatus::kActivated, 1);

  histogram_tester().ExpectTotalCount(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.Count",
      0);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/empty.html", .sec_purpose_header_value = ""},
      {.path = "/title1.html",
       .sec_purpose_header_value =
           blink::kSecPurposePrefetchPrerenderHeaderValue},
  };
  ASSERT_EQ(expected, GetObservedRequests());
}

IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
                       PrefetchNotEligibleNonHttpsPrerenderSuccessWithDelay) {
  ASSERT_TRUE(NavigateToURL(shell(), GetUrlHttp("/empty.html")));

  PrefetchServiceInjectedEligibilityCheckFuture
      eligibility_check_callback_future(
          *PrefetchService::GetFromFrameTreeNodeId(
              web_contents().GetPrimaryMainFrame()->GetFrameTreeNodeId()));

  const GURL prerender_url = GetUrlHttp("/title1.html");
  blink::mojom::SpeculationCandidatePtr candidate =
      CreateSpeculationCandidate(prerender_url);
  PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
      PreloadingTriggerType::kSpeculationRule);
  GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
                                      PreloadingConfidence{100});

  base::PlatformThread::Sleep(base::Milliseconds(101));

  // Proceed to the eligibility check.
  eligibility_check_callback_future->Take().Run(
      PreloadingEligibility::kEligible);

  prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);

  prerender_helper().NavigatePrimaryPage(prerender_url);

  histogram_tester().ExpectUniqueSample(
      "Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kUnspecified, 1);
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kSuccess, 1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
      PrerenderFinalStatus::kActivated, 1);

  histogram_tester().ExpectTotalCount(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.Count",
      0);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/empty.html", .sec_purpose_header_value = ""},
      {.path = "/title1.html",
       .sec_purpose_header_value =
           blink::kSecPurposePrefetchPrerenderHeaderValue},
  };
  ASSERT_EQ(expected, GetObservedRequests());
}

// Scenario:
//
// - URL U has a service worker.
// - Trigger prefetch ahead of prerender A for URL U.
// - A failed in eligibility check due to SW.
// - Trigger prerender A' for URL U.
//   - A' falls back to non-prefetch navigation.
// - Navigation is started. A' is used.
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
                       PrefetchNotEligibleServiceWorkerPrerenderSuccess) {
  ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/prerender/empty.html")));

  std::string script = R"(
    (async () => {
      await navigator.serviceWorker.register('./sw_fallback.js');
      await navigator.serviceWorker.ready;
      return true;
    })();
  )";
  EXPECT_TRUE(ExecJs(web_contents().GetPrimaryMainFrame(), script));

  const GURL prerender_url = GetUrl("/prerender/empty.html?2");
  blink::mojom::SpeculationCandidatePtr candidate =
      CreateSpeculationCandidate(prerender_url);
  PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
      PreloadingTriggerType::kSpeculationRule);
  GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
                                      PreloadingConfidence{100});
  prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);

  prerender_helper().NavigatePrimaryPage(prerender_url);

  histogram_tester().ExpectUniqueSample(
      "Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kUnspecified, 1);
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kSuccess, 1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
      PrerenderFinalStatus::kActivated, 1);

  histogram_tester().ExpectTotalCount(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.Count",
      0);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/prerender/empty.html", .sec_purpose_header_value = ""},
      {.path = "/prerender/sw_fallback.js", .sec_purpose_header_value = ""},
      {.path = "/prerender/empty.html?2",
       .sec_purpose_header_value =
           blink::kSecPurposePrefetchPrerenderHeaderValue},
  };
  ASSERT_EQ(expected, GetObservedRequests());
}

// Variant of PrefetchNotEligibleServiceWorkerPrerenderSuccess.
// Eligibility check with delay.
//
// Scenario:
//
// - URL U has a service worker.
// - Trigger prefetch ahead of prerender A for URL U.
// - Trigger prerender A' for URL U.
//   - A blocks A'.
// - A failed in eligibility check due to SW.
//   - A' falls back to non-prefetch navigation.
// - Navigation is started. A' is used.
IN_PROC_BROWSER_TEST_P(
    PrerendererImplBrowserTestPrefetchAhead,
    PrefetchNotEligibleServiceWorkerPrerenderSuccessWithDelay) {
  ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/prerender/empty.html")));

  std::string script = R"(
    (async () => {
      await navigator.serviceWorker.register('./sw_fallback.js');
      await navigator.serviceWorker.ready;
      return true;
    })();
  )";
  EXPECT_TRUE(ExecJs(web_contents().GetPrimaryMainFrame(), script));

  PrefetchServiceInjectedEligibilityCheckFuture
      eligibility_check_callback_future(
          *PrefetchService::GetFromFrameTreeNodeId(
              web_contents().GetPrimaryMainFrame()->GetFrameTreeNodeId()));

  const GURL prerender_url = GetUrl("/prerender/empty.html?2");
  blink::mojom::SpeculationCandidatePtr candidate =
      CreateSpeculationCandidate(prerender_url);
  PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
      PreloadingTriggerType::kSpeculationRule);
  GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
                                      PreloadingConfidence{100});

  base::PlatformThread::Sleep(base::Milliseconds(101));

  // Proceed to the eligibility check.
  eligibility_check_callback_future->Take().Run(
      PreloadingEligibility::kEligible);

  prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);

  prerender_helper().NavigatePrimaryPage(prerender_url);

  histogram_tester().ExpectUniqueSample(
      "Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kUnspecified, 1);
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kSuccess, 1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
      PrerenderFinalStatus::kActivated, 1);

  histogram_tester().ExpectTotalCount(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.Count",
      0);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/prerender/empty.html", .sec_purpose_header_value = ""},
      {.path = "/prerender/sw_fallback.js", .sec_purpose_header_value = ""},
      {.path = "/prerender/empty.html?2",
       .sec_purpose_header_value =
           blink::kSecPurposePrefetchPrerenderHeaderValue},
  };
  ASSERT_EQ(expected, GetObservedRequests());
}

// Scenario:
//
// - Trigger prefetch ahead of prerender A for URL U.
// - Trigger prerender A' for URL U.
//   - A blocks A'.
// - A failed due to timeout.
//   - The failure is propagated to A'.
// - Navigation is started. No preloads are used.
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
                       PrefetchTimeoutPrerenderFailure) {
  // Prefetch will fail as
  // `prefetch_timeout_ms = 1500 < response_delay_ = 1500 + 1000`.
  SetResponseDelay(base::Milliseconds(1500 + 1000));

  ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
  preload_serving_metrics_list().clear();

  const GURL prerender_url = GetUrl("/title1.html");
  blink::mojom::SpeculationCandidatePtr candidate =
      CreateSpeculationCandidate(prerender_url);
  PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
      PreloadingTriggerType::kSpeculationRule);
  GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
                                      PreloadingConfidence{100});
  prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);

  ASSERT_TRUE(NavigateToURL(shell(), prerender_url));

  histogram_tester().ExpectUniqueSample(
      "Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kFailure, 1);
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kFailure, 1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
      PrerenderFinalStatus::kPrerenderFailedDuringPrefetch, 1);
  if (base::FeatureList::IsEnabled(
          features::kPrefetchMatchResolverUnblockAsync)) {
    // If `kPrefetchMatchResolverUnblockAsync` is disabled, here is reached just
    // after `PrefetchMatchResolver::OnDeterminedHead()`. If
    // `kPrefetchMatchResolverUnblockAsync` is enabled, it allows to invoke
    // `PrefetchStreamingURLLoader::OnComplete()` (We guess that it depends on
    // platforms.) and to update prefetch status.
    EXPECT_THAT(
        histogram_tester().GetAllSamples(
            "Prerender.Experimental.PrefetchAheadOfPrerenderFailed."
            "PrefetchStatus."
            "SpeculationRule"),
        testing::AnyOf(testing::ElementsAre(base::Bucket(
                           PrefetchStatus::kPrefetchFailedNetError, 1)),
                       testing::ElementsAre(base::Bucket(
                           PrefetchStatus::kPrefetchNotFinishedInTime, 1))));

  } else {
    histogram_tester().ExpectUniqueSample(
        "Prerender.Experimental.PrefetchAheadOfPrerenderFailed.PrefetchStatus."
        "SpeculationRule",
        PrefetchStatus::kPrefetchNotFinishedInTime, 1);
  }

  histogram_tester().ExpectUniqueSample(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.Count",
      1, 1);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/empty.html", .sec_purpose_header_value = ""},
      // Prefetch and prerender, timed out and aborted.
      {.path = "/title1.html",
       .sec_purpose_header_value =
           blink::kSecPurposePrefetchPrerenderHeaderValue},
      // Normal navigation.
      {.path = "/title1.html", .sec_purpose_header_value = ""}};
  ASSERT_EQ(expected, GetObservedRequests());

  ASSERT_EQ(2u, preload_serving_metrics_list().size());

  auto& prerender_initial_preload_serving_metrics =
      preload_serving_metrics_list()[0];
  // Taken and used to record UMAs.
  ASSERT_FALSE(prerender_initial_preload_serving_metrics);

  auto& preload_serving_metrics = preload_serving_metrics_list()[1];
  ASSERT_TRUE(preload_serving_metrics);
  ASSERT_EQ(1u, preload_serving_metrics->prefetch_match_metrics_list.size());
  ASSERT_EQ(0u, preload_serving_metrics->prefetch_match_metrics_list[0]
                    ->n_initial_candidates);
  ASSERT_EQ(0u, preload_serving_metrics->prefetch_match_metrics_list[0]
                    ->n_initial_candidates_block_until_head);
  ASSERT_FALSE(preload_serving_metrics->prefetch_match_metrics_list[0]
                   ->prefetch_container_metrics);
  ASSERT_FALSE(preload_serving_metrics->prefetch_match_metrics_list[0]
                   ->prefetch_container_metrics_ahead_of_prerender);
  ASSERT_FALSE(
      preload_serving_metrics->prefetch_match_metrics_list[0]
          ->prefetch_potential_candidate_serving_result_ahead_of_prerender);
  ASSERT_FALSE(
      preload_serving_metrics->prerender_initial_preload_serving_metrics);
}

// A variant of PrefetchTimeoutPrerenderFailure, where the navigation is started
// while prefetch is ongoing and thus the prefetch-ahead-prerender failure
// occurs during the navigation.
//
// Scenario:
//
// - Trigger prefetch ahead of prerender A for URL U.
// - Trigger prerender A' for URL U.
//   - A blocks A'.
// - Navigation is started.
//   - A' blocks it.
// - A failed due to timeout.
//   - The failure is propagated to A'.
//   - It unblocks the navigation.
// - No preloads are used.
IN_PROC_BROWSER_TEST_P(
    PrerendererImplBrowserTestPrefetchAhead,
    PrefetchTimeoutNavigationStartedWithoutWaitForPrerenderPrerenderFailure) {
  // Prefetch will fail as
  // `prefetch_timeout_ms = 1500 < response_delay_ = 1500 + 1000`.
  SetResponseDelay(base::Milliseconds(1500 + 1000));

  ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
  preload_serving_metrics_list().clear();

  const GURL prerender_url = GetUrl("/title1.html");
  blink::mojom::SpeculationCandidatePtr candidate =
      CreateSpeculationCandidate(prerender_url);
  PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
      PreloadingTriggerType::kSpeculationRule);
  GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
                                      PreloadingConfidence{100});
  // Don't `prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);`
  // here, different from PrefetchTimeoutPrerenderFailure.

  ASSERT_TRUE(NavigateToURLFromRenderer(shell(), prerender_url));

  histogram_tester().ExpectUniqueSample(
      "Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kFailure, 1);
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kFailure, 1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
      PrerenderFinalStatus::kPrerenderFailedDuringPrefetch, 1);
  if (base::FeatureList::IsEnabled(
          features::kPrefetchMatchResolverUnblockAsync)) {
    // If `kPrefetchMatchResolverUnblockAsync` is disabled, here is reached just
    // after `PrefetchMatchResolver::OnDeterminedHead()`. If
    // `kPrefetchMatchResolverUnblockAsync` is enabled, it allows to invoke
    // `PrefetchStreamingURLLoader::OnComplete()` (We guess that it depends on
    // platforms.) and to update prefetch status.
    EXPECT_THAT(
        histogram_tester().GetAllSamples(
            "Prerender.Experimental.PrefetchAheadOfPrerenderFailed."
            "PrefetchStatus."
            "SpeculationRule"),
        testing::AnyOf(testing::ElementsAre(base::Bucket(
                           PrefetchStatus::kPrefetchFailedNetError, 1)),
                       testing::ElementsAre(base::Bucket(
                           PrefetchStatus::kPrefetchNotFinishedInTime, 1))));

  } else {
    histogram_tester().ExpectUniqueSample(
        "Prerender.Experimental.PrefetchAheadOfPrerenderFailed.PrefetchStatus."
        "SpeculationRule",
        PrefetchStatus::kPrefetchNotFinishedInTime, 1);
  }

  histogram_tester().ExpectUniqueSample(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.Count",
      1, 1);
  histogram_tester().ExpectUniqueSample(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.IsPotentialMatch",
      true, 1);
  histogram_tester().ExpectUniqueSample(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.PotentialMatchThen.NumberOfInitialCandidates",
      1, 1);
  histogram_tester().ExpectUniqueSample(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.PotentialMatchThen."
      "NumberOfInitialCandidatesBlockUntilHead",
      1, 1);
  histogram_tester().ExpectUniqueSample(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.PotentialMatchThen.IsActualMatch",
      false, 1);
  histogram_tester().ExpectUniqueSample(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.IsPotentialMatch.WithAheadOfPrerender",
      true, 1);
  histogram_tester().ExpectUniqueSample(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.PotentialMatchThen.WithAheadOfPrerender."
      "PotentialCandidateServingResult",
      PrefetchPotentialCandidateServingResult::kNotServedLoadFailed, 1);

  histogram_tester().ExpectUniqueSample(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "FallbackAborted.Match0.PrefetchMatchMetrics.Count",
      1, 1);
  histogram_tester().ExpectUniqueSample(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "FallbackAborted.Match0.PrefetchMatchMetrics.IsPotentialMatch",
      true, 1);
  histogram_tester().ExpectUniqueSample(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "FallbackAborted.Match0.PrefetchMatchMetrics.ExistsPaop",
      true, 1);
  if (base::FeatureList::IsEnabled(
          features::kPrefetchMatchResolverUnblockAsync)) {
    EXPECT_THAT(
        histogram_tester().GetAllSamples(
            "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
            "FallbackAborted.Match0.PrefetchMatchMetrics.ExistsPaopThen."
            "PrefetchStatus"),
        testing::AnyOf(testing::ElementsAre(base::Bucket(
                           PrefetchStatus::kPrefetchFailedNetError, 1)),
                       testing::ElementsAre(base::Bucket(
                           PrefetchStatus::kPrefetchNotFinishedInTime, 1))));
    EXPECT_THAT(
        histogram_tester().GetAllSamples(
            "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
            "FallbackAborted.Match0.PrefetchMatchMetrics.ExistsPaopThen."
            "ServableStateAndMatcherAction"),
        testing::AnyOf(
            // 4 = PrefetchServableState::kNotServable
            // 1 = PrefetchMatchResolverAction::ActionKind::kDrop
            // 8 =
            // PrefetchMatchResolverAction::ActionReason::kFailedDeterminedHead
            // 1 = is_expired == false
            testing::ElementsAre(base::Bucket(4181, 1)),
            // 4 = PrefetchServableState::kNotServable
            // 1 = PrefetchMatchResolverAction::ActionKind::kDrop
            // 9 =
            // PrefetchMatchResolverAction::ActionReason::kFailed
            // 1 = is_expired == false
            testing::ElementsAre(base::Bucket(4191, 1))));
    EXPECT_THAT(
        histogram_tester().GetAllSamples(
            "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
            "FallbackAborted.Match0.PrefetchMatchMetrics.ExistsPaopThen."
            "PotentialCandidateServingResultAndServableStateAndMatcherAction"),
        testing::AnyOf(
            // 12 =
            // PrefetchPotentialCandidateServingResult::kNotServedLoadFailed
            // 4 = PrefetchServableState::kNotServable
            // 1 = PrefetchMatchResolverAction::ActionKind::kDrop
            // 8 =
            // PrefetchMatchResolverAction::ActionReason::kFailedDeterminedHead
            // 1 = is_expired == false
            testing::ElementsAre(base::Bucket(124181, 1)),
            // 12 =
            // PrefetchPotentialCandidateServingResult::kNotServedLoadFailed
            // 4 = PrefetchServableState::kNotServable
            // 1 = PrefetchMatchResolverAction::ActionKind::kDrop
            // 9 =
            // PrefetchMatchResolverAction::ActionReason::kFailed
            // 1 = is_expired == false
            testing::ElementsAre(base::Bucket(124191, 1))));
  } else {
    histogram_tester().ExpectUniqueSample(
        "Prerender.Experimental.PrefetchAheadOfPrerenderFailed.PrefetchStatus."
        "SpeculationRule",
        PrefetchStatus::kPrefetchNotFinishedInTime, 1);
    histogram_tester().ExpectUniqueSample(
        "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
        "FallbackAborted.Match0.PrefetchMatchMetrics.ExistsPaopThen."
        "ServableStateAndMatcherAction",
        // 4 = PrefetchServableState::kNotServable
        // 1 = PrefetchMatchResolverAction::ActionKind::kDrop
        // 8 =
        // PrefetchMatchResolverAction::ActionReason::kFailedDeterminedHead
        // 1 = is_expired == false
        4181, 1);
    histogram_tester().ExpectUniqueSample(
        "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
        "FallbackAborted.Match0.PrefetchMatchMetrics.ExistsPaopThen."
        "PotentialCandidateServingResultAndServableStateAndMatcherAction",
        // 12 =
        // PrefetchPotentialCandidateServingResult::kNotServedLoadFailed
        // 4 = PrefetchServableState::kNotServable
        // 1 = PrefetchMatchResolverAction::ActionKind::kDrop
        // 8 =
        // PrefetchMatchResolverAction::ActionReason::kFailedDeterminedHead
        // 1 = is_expired == false
        124181, 1);
  }
  histogram_tester().ExpectUniqueSample(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "FallbackAborted.Match0.PrefetchMatchMetrics.ExistsPaopThen.QueueSize",
      0, 1);
  histogram_tester().ExpectUniqueSample(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "FallbackAborted.Match0.PrefetchMatchMetrics.ExistsPaopThen."
      "QueueIndexPlus1",
      0, 1);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/empty.html", .sec_purpose_header_value = ""},
      // Prefetch and prerender, timed out and aborted.
      {.path = "/title1.html",
       .sec_purpose_header_value =
           blink::kSecPurposePrefetchPrerenderHeaderValue},
      // Normal navigation.
      {.path = "/title1.html", .sec_purpose_header_value = ""}};
  ASSERT_EQ(expected, GetObservedRequests());

  ASSERT_EQ(2u, preload_serving_metrics_list().size());

  auto& prerender_initial_preload_serving_metrics =
      preload_serving_metrics_list()[0];
  // Taken and used to record UMAs.
  ASSERT_FALSE(prerender_initial_preload_serving_metrics);

  // PreloadServingMetrics for ordinal navigation.
  auto& preload_serving_metrics = preload_serving_metrics_list()[1];
  ASSERT_EQ(1u, preload_serving_metrics->prefetch_match_metrics_list.size());
  // The navigation reaches to `PrefetchMatchResolver` after prefetch A and
  // prerender A' failed. There is no available prefetch.
  ASSERT_EQ(0u, preload_serving_metrics->prefetch_match_metrics_list[0]
                    ->n_initial_candidates);
  ASSERT_EQ(0u, preload_serving_metrics->prefetch_match_metrics_list[0]
                    ->n_initial_candidates_block_until_head);
  ASSERT_FALSE(preload_serving_metrics->prefetch_match_metrics_list[0]
                   ->prefetch_container_metrics);
  ASSERT_FALSE(preload_serving_metrics->prefetch_match_metrics_list[0]
                   ->prefetch_container_metrics_ahead_of_prerender);
  ASSERT_FALSE(
      preload_serving_metrics->prefetch_match_metrics_list[0]
          ->prefetch_potential_candidate_serving_result_ahead_of_prerender);
  ASSERT_FALSE(
      preload_serving_metrics->prerender_initial_preload_serving_metrics);
}

// Consider a case that a site uses a SpecRules containing prefetch and
// prerender for a URL U.
//
// Scenario:
//
// - Trigger prefetch A a URL U.
// - Trigger prefetch ahead of prerender B for URL U.
//   - B is migrated into A. A inherits `PreloadPipelineInfo` and is considered
//     as `IsLikelyAheadOfPrerender`.
// - Trigger prerender B' for URL U.
//   - A blocks B'.
// - Navigation is started. B' is used.
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
                       PrefetchMigratedPrefetchSuccessPrerenderSuccess) {
  ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));

  const GURL prerender_url = GetUrl("/title1.html");
  PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
      PreloadingTriggerType::kSpeculationRule);
  {
    blink::mojom::SpeculationCandidatePtr candidate =
        CreateSpeculationCandidate(prerender_url);
    candidate->action = blink::mojom::SpeculationAction::kPrefetch;
    GetPrefetcher().MaybePrefetch(std::move(candidate), enacting_predictor);
  }
  {
    blink::mojom::SpeculationCandidatePtr candidate =
        CreateSpeculationCandidate(prerender_url);
    GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
                                        PreloadingConfidence{100});
  }

  prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);

  prerender_helper().NavigatePrimaryPage(prerender_url);

  EXPECT_THAT(
      histogram_tester().GetAllSamples(
          "Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome"),
      testing::ElementsAre(
          base::Bucket(PreloadingTriggeringOutcome::kUnspecified, 1),
          base::Bucket(PreloadingTriggeringOutcome::kSuccess, 1)));
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kSuccess, 1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
      PrerenderFinalStatus::kActivated, 1);

  histogram_tester().ExpectTotalCount(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.Count",
      0);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/empty.html", .sec_purpose_header_value = ""},
      {.path = "/title1.html",
       .sec_purpose_header_value = blink::kSecPurposePrefetchHeaderValue}};
  ASSERT_EQ(expected, GetObservedRequests());
}

// Variant of PrefetchMigratedPrefetchSuccessPrerenderSuccess.
// The order of migration and prefetch completion is reversed.
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
                       PrefetchSuccessPrefetchMigratedPrerenderSuccess) {
  ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));

  const GURL prerender_url = GetUrl("/title1.html");
  PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
      PreloadingTriggerType::kSpeculationRule);
  {
    test::TestPrefetchWatcher watcher;
    blink::mojom::SpeculationCandidatePtr candidate =
        CreateSpeculationCandidate(prerender_url);
    candidate->action = blink::mojom::SpeculationAction::kPrefetch;
    GetPrefetcher().MaybePrefetch(std::move(candidate), enacting_predictor);
    watcher.WaitUntilPrefetchResponseCompleted(
        web_contents_impl().GetPrimaryMainFrame()->GetDocumentToken(),
        prerender_url);
  }
  {
    blink::mojom::SpeculationCandidatePtr candidate =
        CreateSpeculationCandidate(prerender_url);
    GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
                                        PreloadingConfidence{100});
  }

  prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);

  prerender_helper().NavigatePrimaryPage(prerender_url);

  EXPECT_THAT(
      histogram_tester().GetAllSamples(
          "Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome"),
      testing::ElementsAre(
          base::Bucket(PreloadingTriggeringOutcome::kUnspecified, 1),
          base::Bucket(PreloadingTriggeringOutcome::kSuccess, 1)));
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kSuccess, 1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
      PrerenderFinalStatus::kActivated, 1);

  histogram_tester().ExpectTotalCount(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.Count",
      0);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/empty.html", .sec_purpose_header_value = ""},
      {.path = "/title1.html",
       .sec_purpose_header_value = blink::kSecPurposePrefetchHeaderValue}};
  ASSERT_EQ(expected, GetObservedRequests());
}

// Scenario:
//
// - Trigger prefetch A a URL U.
// - Eligibility check for A is done.
// - Trigger prefetch ahead of prerender B for URL U.
//   - B is migrated into A. A inherits `PreloadPipelineInfo` and is considered
//     as `IsLikelyAheadOfPrerender`. Eligibility of A is propagated to the
//     `PreloadPipelineInfo`.
// - Trigger prerender B' for URL U.
//   - A blocks B'.
// - A fails by timeout.
// - Navigation is started. No preloads are used.
//
// This shows the necessity of eligibility propagation in
// `PrefetchContainer::MigrateNewlyAdded()`.
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
                       PrefetchMigratedPrefetchFailurePrerenderFailure) {
  // Prefetch will fail as
  // `prefetch_timeout_ms = 1500 < response_delay_ = 1500 + 1000`.
  SetResponseDelay(base::Milliseconds(1500 + 1000));

  ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));

  const GURL prerender_url = GetUrl("/title1.html");
  PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
      PreloadingTriggerType::kSpeculationRule);
  {
    blink::mojom::SpeculationCandidatePtr candidate =
        CreateSpeculationCandidate(prerender_url);
    candidate->action = blink::mojom::SpeculationAction::kPrefetch;
    GetPrefetcher().MaybePrefetch(std::move(candidate), enacting_predictor);
  }

  // Ensure that eligibility check is done.
  base::PlatformThread::Sleep(base::Milliseconds(101));

  {
    blink::mojom::SpeculationCandidatePtr candidate =
        CreateSpeculationCandidate(prerender_url);
    GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
                                        PreloadingConfidence{100});
  }

  prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);

  ASSERT_TRUE(NavigateToURL(shell(), prerender_url));

  EXPECT_THAT(
      histogram_tester().GetAllSamples(
          "Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome"),
      testing::ElementsAre(
          base::Bucket(PreloadingTriggeringOutcome::kUnspecified, 1),
          base::Bucket(PreloadingTriggeringOutcome::kFailure, 1)));
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kFailure, 1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
      PrerenderFinalStatus::kPrerenderFailedDuringPrefetch, 1);
  if (base::FeatureList::IsEnabled(
          features::kPrefetchMatchResolverUnblockAsync)) {
    // If `kPrefetchMatchResolverUnblockAsync` is disabled, here is reached just
    // after `PrefetchMatchResolver::OnDeterminedHead()`. If
    // `kPrefetchMatchResolverUnblockAsync` is enabled, it allows to invoke
    // `PrefetchStreamingURLLoader::OnComplete()` (We guess that it depends on
    // platforms.) and to update prefetch status.
    EXPECT_THAT(
        histogram_tester().GetAllSamples(
            "Prerender.Experimental.PrefetchAheadOfPrerenderFailed."
            "PrefetchStatus."
            "SpeculationRule"),
        testing::AnyOf(testing::ElementsAre(base::Bucket(
                           PrefetchStatus::kPrefetchFailedNetError, 1)),
                       testing::ElementsAre(base::Bucket(
                           PrefetchStatus::kPrefetchNotFinishedInTime, 1))));

  } else {
    histogram_tester().ExpectUniqueSample(
        "Prerender.Experimental.PrefetchAheadOfPrerenderFailed.PrefetchStatus."
        "SpeculationRule",
        PrefetchStatus::kPrefetchNotFinishedInTime, 1);
  }

  histogram_tester().ExpectUniqueSample(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.Count",
      1, 1);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/empty.html", .sec_purpose_header_value = ""},
      {.path = "/title1.html",
       .sec_purpose_header_value = blink::kSecPurposePrefetchHeaderValue},
      {.path = "/title1.html", .sec_purpose_header_value = ""}};
  ASSERT_EQ(expected, GetObservedRequests());
}

// Scenario:
//
// - Trigger prefetch A.
// - Trigger prefetch ahead of prerender B for URL U.
//   - B is migrated into A. A inherits `PreloadPipelineInfo` and is considered
//     as `IsLikelyAheadOfPrerender`.
// - Trigger prerender B' for URL U.
// - A fails in eligibility check.
//   - B' fails as the ineligibility is not admissible.
// - Navigation is started. No preloads are used.
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
                       PrefetchMigratedPrefetchNotEligiblePrerenderFailure) {
  PrefetchServiceInjectedEligibilityCheckFuture
      eligibility_check_callback_future(
          *PrefetchService::GetFromFrameTreeNodeId(
              web_contents().GetPrimaryMainFrame()->GetFrameTreeNodeId()));

  ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));

  const GURL prerender_url = GetUrl("/title1.html");
  PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
      PreloadingTriggerType::kSpeculationRule);
  {
    blink::mojom::SpeculationCandidatePtr candidate =
        CreateSpeculationCandidate(prerender_url);
    candidate->action = blink::mojom::SpeculationAction::kPrefetch;
    GetPrefetcher().MaybePrefetch(std::move(candidate), enacting_predictor);
  }
  {
    blink::mojom::SpeculationCandidatePtr candidate =
        CreateSpeculationCandidate(prerender_url);
    GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
                                        PreloadingConfidence{100});
  }

  // Resume the eligibility check of the first prefetch and make it fail with
  // `kHostIsNonUnique`.
  eligibility_check_callback_future->Take().Run(
      PreloadingEligibility::kHostIsNonUnique);

  // Here we shouldn't call
  // `prerender_helper().WaitForPrerenderLoadCompletion(prerender_url)` since
  // the call first tries to get `PrerenderHost`, which has been already
  // destructed.

  prerender_helper().NavigatePrimaryPage(prerender_url);

  histogram_tester().ExpectUniqueSample(
      "Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kUnspecified, 2);
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kFailure, 1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
      PrerenderFinalStatus::kPrerenderFailedDuringPrefetch, 1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrefetchAheadOfPrerenderFailed.PrefetchStatus."
      "SpeculationRule",
      PrefetchStatus::kPrefetchIneligibleHostIsNonUnique, 1);

  histogram_tester().ExpectUniqueSample(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.Count",
      1, 1);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/empty.html", .sec_purpose_header_value = ""},
      {.path = "/title1.html", .sec_purpose_header_value = ""},
  };
  ASSERT_EQ(expected, GetObservedRequests());
}

// Consider a case that a site uses a SpecRules containing prefetch and
// prerender for a URL U.
//
// Scenario:
//
// - Trigger prefetch A a URL U.
// - Trigger prefetch ahead of prerender B for URL U.
//   - B is migrated into A. A inherits `PreloadPipelineInfo` and is considered
//     as `IsLikelyAheadOfPrerender`.
// - Trigger prerender B' for URL U.
//   - A blocks B'.
// - A failed due to timeout.
//   - The failure is propagated to B'.
// - Navigation is started. No preloads are used.
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
                       PrefetchMigratedPrefetchTimeoutPrerenderFailure) {
  // Prefetch will fail as
  // `prefetch_timeout_ms = 1500 < response_delay_ = 1500 + 1000`.
  SetResponseDelay(base::Milliseconds(1500 + 1000));

  ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));

  const GURL prerender_url = GetUrl("/title1.html");
  PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
      PreloadingTriggerType::kSpeculationRule);
  {
    blink::mojom::SpeculationCandidatePtr candidate =
        CreateSpeculationCandidate(prerender_url);
    candidate->action = blink::mojom::SpeculationAction::kPrefetch;
    GetPrefetcher().MaybePrefetch(std::move(candidate), enacting_predictor);
  }
  {
    blink::mojom::SpeculationCandidatePtr candidate =
        CreateSpeculationCandidate(prerender_url);
    GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
                                        PreloadingConfidence{100});
  }

  prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);

  ASSERT_TRUE(NavigateToURL(shell(), prerender_url));

  EXPECT_THAT(
      histogram_tester().GetAllSamples(
          "Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome"),
      testing::ElementsAre(
          base::Bucket(PreloadingTriggeringOutcome::kUnspecified, 1),
          base::Bucket(PreloadingTriggeringOutcome::kFailure, 1)));
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kFailure, 1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
      PrerenderFinalStatus::kPrerenderFailedDuringPrefetch, 1);
  if (base::FeatureList::IsEnabled(
          features::kPrefetchMatchResolverUnblockAsync)) {
    // If `kPrefetchMatchResolverUnblockAsync` is disabled, here is reached just
    // after `PrefetchMatchResolver::OnDeterminedHead()`. If
    // `kPrefetchMatchResolverUnblockAsync` is enabled, it allows to invoke
    // `PrefetchStreamingURLLoader::OnComplete()` (We guess that it depends on
    // platforms.) and to update prefetch status.
    EXPECT_THAT(
        histogram_tester().GetAllSamples(
            "Prerender.Experimental.PrefetchAheadOfPrerenderFailed."
            "PrefetchStatus."
            "SpeculationRule"),
        testing::AnyOf(testing::ElementsAre(base::Bucket(
                           PrefetchStatus::kPrefetchFailedNetError, 1)),
                       testing::ElementsAre(base::Bucket(
                           PrefetchStatus::kPrefetchNotFinishedInTime, 1))));

  } else {
    histogram_tester().ExpectUniqueSample(
        "Prerender.Experimental.PrefetchAheadOfPrerenderFailed.PrefetchStatus."
        "SpeculationRule",
        PrefetchStatus::kPrefetchNotFinishedInTime, 1);
  }

  histogram_tester().ExpectUniqueSample(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.Count",
      1, 1);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/empty.html", .sec_purpose_header_value = ""},
      // Prefetch and prerender, timed out and aborted.
      {.path = "/title1.html",
       .sec_purpose_header_value = blink::kSecPurposePrefetchHeaderValue},
      // Normal navigation.
      {.path = "/title1.html", .sec_purpose_header_value = ""}};
  ASSERT_EQ(expected, GetObservedRequests());
}

// Scenario:
//
// - Trigger prefetch ahead of prerender A for URL U.
// - Trigger prerender A' for URL U.
//   - A blocks A'.
// - Prefetch matching process ended due to timeout. A' is aborted.
// - A received response and succeeded.
// - Navigation is started. A is used.
//
// TODO(crbug.com/372851198): The scenario described does not seem to work as
// desired. A second request is made to /title1.html, and
// `PrefetchContainer::Reader::OnPrefetchProbeResult()` is not called. Before
// https://chromium-review.googlesource.com/c/chromium/src/+/6056830, the second
// request was avoided due to the HTTP cache, but after that change the overall
// brokenness of the scenario is revealed. It is likely that the /title1.html
// response is not making it into the speculation rules prefetch cache.
IN_PROC_BROWSER_TEST_P(
    PrerendererImplBrowserTestPrefetchAhead,
    DISABLED_PrefetchSuccessPrefetchMatchResolverTimeoutPrerenderFailure) {
  SetResponseDelay(base::Milliseconds(1000));

  ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));

  const GURL prerender_url = GetUrl("/title1.html");
  {
    test::TestPrefetchWatcher watcher;

    blink::mojom::SpeculationCandidatePtr candidate =
        CreateSpeculationCandidate(prerender_url);
    // Use `kModerate` to trigger `PrefetchMatchResolver::OnTimeout()`.
    // Note that `block_until_head_timeout_moderate_prefetch = 500 <
    // response_delay_ = 1000 < prefetch_timeout_ms = 1500`.
    candidate->eagerness = blink::mojom::SpeculationEagerness::kModerate;
    PreloadingPredictor enacting_predictor =
        GetPredictorForPreloadingTriggerType(
            PreloadingTriggerType::kSpeculationRule);
    GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
                                        PreloadingConfidence{100});

    prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);

    watcher.WaitUntilPrefetchResponseCompleted(
        web_contents_impl().GetPrimaryMainFrame()->GetDocumentToken(),
        prerender_url);
  }

  ASSERT_TRUE(NavigateToURL(shell(), prerender_url));

  histogram_tester().ExpectUniqueSample(
      "Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kReady, 1);
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kFailure, 1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
      PrerenderFinalStatus::kPrerenderFailedDuringPrefetch, 1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrefetchAheadOfPrerenderFailed.PrefetchStatus."
      "SpeculationRule",
      PrefetchStatus::kPrefetchNotFinishedInTime, 1);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/empty.html", .sec_purpose_header_value = ""},
      // Prerender is aborted, but prefetch is success and used.
      {.path = "/title1.html",
       .sec_purpose_header_value =
           blink::kSecPurposePrefetchPrerenderHeaderValue}};
  ASSERT_EQ(expected, GetObservedRequests());
}

// Consider a case that a site inserted a SpecRules for prerender, removed it,
// and inserted again.
//
// Scenario:
//
// - Trigger prefetch ahead of prerender A for URL U.
// - Trigger prerender A' for URL U.
//   - A' uses A.
// - A' is cancelled.
// - Trigger prefetch ahead of prerender B for URL U.
//   - B is migrated into A. A inherits `PreloadPipelineInfo`.
// - Trigger prerender B' for URL U.
//   - B' uses A.
// - Navigation is started. B' is used.
IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
                       PrerenderSuccessCancelledAnotherPrerenderSuccess) {
  ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));

  const GURL prerender_url = GetUrl("/title1.html");
  PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
      PreloadingTriggerType::kSpeculationRule);
  {
    blink::mojom::SpeculationCandidatePtr candidate =
        CreateSpeculationCandidate(prerender_url);
    GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
                                        PreloadingConfidence{100});
    prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);

    GetPrerendererImpl().CancelStartedPrerendersForTesting();
  }
  {
    blink::mojom::SpeculationCandidatePtr candidate =
        CreateSpeculationCandidate(prerender_url);
    GetPrerendererImpl().MaybePrerender(candidate, enacting_predictor,
                                        PreloadingConfidence{100});
    prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);
  }

  prerender_helper().NavigatePrimaryPage(prerender_url);

  EXPECT_THAT(
      histogram_tester().GetAllSamples(
          "Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome"),
      testing::ElementsAre(
          base::Bucket(PreloadingTriggeringOutcome::kSuccess, 1)));
  EXPECT_THAT(
      histogram_tester().GetAllSamples(
          "Preloading.Prerender.Attempt.SpeculationRules.TriggeringOutcome"),
      testing::ElementsAre(
          base::Bucket(PreloadingTriggeringOutcome::kReady, 1),
          base::Bucket(PreloadingTriggeringOutcome::kSuccess, 1)));
  EXPECT_THAT(
      histogram_tester().GetAllSamples(
          "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule"),
      testing::ElementsAre(
          base::Bucket(PrerenderFinalStatus::kActivated, 1),
          base::Bucket(PrerenderFinalStatus::kTriggerDestroyed, 1)));

  histogram_tester().ExpectUniqueSample(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.Count",
      1, 1);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/empty.html", .sec_purpose_header_value = ""},
      {.path = "/title1.html",
       .sec_purpose_header_value =
           blink::kSecPurposePrefetchPrerenderHeaderValue}};
  ASSERT_EQ(expected, GetObservedRequests());
}

IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
                       PrerenderUntilScriptUsesPrefetchedResponse) {
  ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));

  const GURL prerender_url = GetUrl("/title1.html");
  blink::mojom::SpeculationCandidatePtr candidate = CreateSpeculationCandidate(
      prerender_url, blink::mojom::SpeculationAction::kPrerenderUntilScript);

  test::TestPrefetchWatcher test_prefetch_watcher;
  std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
  candidates.push_back(std::move(candidate));
  GetPrerendererImpl().ProcessCandidatesForPrerender(
      candidates, /*enable_cross_origin_prerender_iframes=*/false);
  test_prefetch_watcher.WaitUntilPrefetchResponseCompleted(
      GetPrimaryMainFrameHost().GetDocumentToken(), prerender_url);
  prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);

  // Activate
  prerender_helper().NavigatePrimaryPage(prerender_url);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/empty.html", .sec_purpose_header_value = ""},
      {.path = "/title1.html",
       .sec_purpose_header_value =
           blink::kSecPurposePrefetchPrerenderHeaderValue}};
  ASSERT_EQ(expected, GetObservedRequests());

  // TODO(https://crbug.com/342089066): Record prefetch as
  // kTriggeredButUpgradedToPrerender.
  histogram_tester().ExpectUniqueSample(
      "Preloading.Prefetch.Attempt.SpeculationRules.TriggeringOutcome",
      PreloadingTriggeringOutcome::kSuccess, 1);
  // TODO(https://crbug.com/428500219): Record preloading attempt metrics for
  // prerender-until-script.
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
      PrerenderFinalStatus::kActivated, 1);

  histogram_tester().ExpectTotalCount(
      "PreloadServingMetrics.ForPrerenderInitialNavigationFailed."
      "PrefetchMatchMetrics.Count",
      0);
}

IN_PROC_BROWSER_TEST_P(PrerendererImplBrowserTestPrefetchAhead,
                       PrefetchCancelledWhenPrerenderUntilScriptRemoved) {
  ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));

  const GURL prerender_url = GetUrl("/title1.html");
  blink::mojom::SpeculationCandidatePtr candidate = CreateSpeculationCandidate(
      prerender_url, blink::mojom::SpeculationAction::kPrerenderUntilScript);

  test::TestPrefetchWatcher test_prefetch_watcher;
  std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
  candidates.push_back(std::move(candidate));
  GetPrerendererImpl().ProcessCandidatesForPrerender(
      candidates, /*enable_cross_origin_prerender_iframes=*/false);
  test_prefetch_watcher.WaitUntilPrefetchResponseCompleted(
      GetPrimaryMainFrameHost().GetDocumentToken(), prerender_url);
  prerender_helper().WaitForPrerenderLoadCompletion(prerender_url);

  // Remove the rule to cancel prerender-until-script.
  GetPrerendererImpl().ProcessCandidatesForPrerender(
      std::vector<blink::mojom::SpeculationCandidatePtr>(),
      /*enable_cross_origin_prerender_iframes=*/false);

  prerender_helper().NavigatePrimaryPage(prerender_url);

  std::vector<RequestPathAndSecPurposeHeader> expected{
      {.path = "/empty.html", .sec_purpose_header_value = ""},
      {.path = "/title1.html",
       .sec_purpose_header_value =
           blink::kSecPurposePrefetchPrerenderHeaderValue},
      // Normal navigation.
      {.path = "/title1.html", .sec_purpose_header_value = ""}};
  ASSERT_EQ(expected, GetObservedRequests());
}

// Tests that PrerendererImpl::MaybePrerender ignores candidates from an
// inactive frame in the back/forward cache.
IN_PROC_BROWSER_TEST_F(PrerendererBackForwardCacheTest,
                       MaybePrerenderIgnoredWhenInactive) {
  EXPECT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html")));
  RenderFrameHostImpl* rfh_a = static_cast<RenderFrameHostImpl*>(
      web_contents_impl().GetPrimaryMainFrame());
  const GlobalRenderFrameHostId rfh_id = rfh_a->GetGlobalId();
  PrerendererImpl& prerenderer_a = GetPrerendererImpl();

  // Navigate away so that rfh_a enters the back/forward cache.
  EXPECT_TRUE(NavigateToURL(shell(), GetCrossSiteUrl("/empty.html")));
  rfh_a = static_cast<RenderFrameHostImpl*>(RenderFrameHost::FromID(rfh_id));
  EXPECT_TRUE(rfh_a);
  if (!rfh_a) {
    return;
  }
  EXPECT_TRUE(rfh_a->IsInLifecycleState(
      RenderFrameHost::LifecycleState::kInBackForwardCache));
  EXPECT_FALSE(rfh_a->IsActive());

  const GURL prerender_url = GetCrossSiteUrl("/title1.html");
  blink::mojom::SpeculationCandidatePtr candidate =
      CreateSpeculationCandidate(prerender_url);

  PreloadingPredictor enacting_predictor = GetPredictorForPreloadingTriggerType(
      PreloadingTriggerType::kSpeculationRule);
  EXPECT_FALSE(prerenderer_a.MaybePrerender(candidate, enacting_predictor,
                                            PreloadingConfidence{100}));

  RenderFrameHostImpl* rfh_b = static_cast<RenderFrameHostImpl*>(
      web_contents_impl().GetPrimaryMainFrame());
  EXPECT_NE(rfh_a, rfh_b);
  EXPECT_FALSE(PrefetchDocumentManager::GetForCurrentDocument(rfh_b));
}

}  // namespace
}  // namespace content
