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

#include <limits>
#include <string>

#include "base/functional/bind.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h"
#include "chrome/browser/battery/battery_saver.h"
#include "chrome/browser/preloading/chrome_preloading.h"
#include "chrome/browser/preloading/prefetch/search_prefetch/field_trial_settings.h"
#include "chrome/browser/preloading/prefetch/search_prefetch/search_preload_test_response_utils.h"
#include "chrome/browser/preloading/preloading_features.h"
#include "chrome/browser/preloading/prerender/prerender_manager.h"
#include "chrome/browser/preloading/prerender/search_preload_progress_service.h"
#include "chrome/browser/preloading/prerender/search_preload_progress_service_factory.h"
#include "chrome/browser/preloading/scoped_prewarm_feature_list.h"
#include "chrome/browser/preloading/search_preload/search_preload_features.h"
#include "chrome/browser/preloading/search_preload/search_preload_service.h"
#include "chrome/browser/preloading/search_preload/search_preload_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "chrome/test/base/platform_browser_test.h"
#include "chrome/test/base/search_test_utils.h"
#include "chrome/test/base/testing_profile.h"
#include "components/omnibox/browser/autocomplete_input.h"
#include "components/omnibox/browser/autocomplete_result.h"
#include "components/omnibox/browser/omnibox.mojom.h"
#include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h"
#include "components/search_engines/template_url_data.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/browser/preload_pipeline_info.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/prefetch_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "extensions/common/extension_features.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/navigation/preloading_headers.h"

namespace {

// Holds //content data to avoid disallowed import.
namespace alternative_content {

// Minimal copy of content/browser/preloading/prefetch/prefetch_status.h
enum class PrefetchStatus {
  kPrefetchNotFinishedInTime = 10,
};

// Minimal copy of content/browser/preloading/prerender/prerender_final_status.h
enum class PrerenderFinalStatus {
  kActivated = 0,
  kPrerenderFailedDuringPrefetch = 86,
};

}  // namespace alternative_content

class HistogramTesterWrapper {
 public:
  HistogramTesterWrapper() = default;
  ~HistogramTesterWrapper() = default;

  template <typename T>
  void ExpectUma(std::string_view name,
                 std::vector<T> values,
                 const base::Location& location = FROM_HERE) {
    std::map<T, size_t> counts;
    for (auto& value : values) {
      counts[value]++;
    }

    histogram_tester_.ExpectTotalCount(name, values.size(), location);
    for (auto& [value, count] : counts) {
      histogram_tester_.ExpectBucketCount(name, value, count, location);
    }
  }

  template <typename T>
  void ExpectUma(std::string_view name,
                 std::initializer_list<T> values,
                 const base::Location& location = FROM_HERE) {
    ExpectUma(name, std::vector<T>(values), location);
  }

  // Special case for an empty initializer `{}`.
  void ExpectUma(std::string_view name,
                 void* values,
                 const base::Location& location = FROM_HERE) {
    ExpectUma(name, std::vector<int>({}), location);
  }

  void ExpectTotalCount(std::string_view name,
                        int count,
                        const base::Location& location = FROM_HERE) {
    histogram_tester_.ExpectTotalCount(name, count, location);
  }

 private:
  base::HistogramTester histogram_tester_;
};

constexpr static char kSearchTerms_502OnPrefetch[] = "502-on-prefetch";

std::optional<net::HttpNoVarySearchData> ParseNoVarySearchData(std::string s) {
  auto maybe_no_vary_search_data =
      net::HttpNoVarySearchData::ParseFromHeaderValue(s);
  if (!maybe_no_vary_search_data.has_value()) {
    return std::nullopt;
  }
  return maybe_no_vary_search_data.value();
}

// Collects requests to `EmbeddedTestServer` via RequestMonitor.
class EmbeddedTestServerRequestCollector {
 public:
  EmbeddedTestServerRequestCollector() = default;
  ~EmbeddedTestServerRequestCollector() = default;

  void Reset() {
    base::AutoLock auto_lock(lock_);

    requests_.clear();
  }

  base::RepeatingCallback<void(const net::test_server::HttpRequest&)>
  GetOnResourceRequest() {
    return base::BindRepeating(
        &EmbeddedTestServerRequestCollector::OnResourceRequest,
        base::Unretained(this));
  }

  int CountByPath(const GURL& url) {
    base::AutoLock auto_lock(lock_);

    int i = 0;

    for (const auto& request : requests_) {
      if (request.GetURL().PathForRequest() == url.PathForRequest()) {
        i += 1;
      }
    }

    return i;
  }

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

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

    requests_.push_back(request);
  }

  base::Lock lock_;
  std::vector<net::test_server::HttpRequest> requests_ GUARDED_BY(lock_);
};

// Injects delay for each response of `EmbeddedTestServer` via RequestMonitor.
class EmbeddedTestServerDelayInjector {
 public:
  EmbeddedTestServerDelayInjector() = default;
  ~EmbeddedTestServerDelayInjector() = default;

  base::RepeatingCallback<void(const net::test_server::HttpRequest&)>
  GetOnResourceRequest() {
    return base::BindRepeating(
        &EmbeddedTestServerDelayInjector::OnResourceRequest,
        base::Unretained(this));
  }

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

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

    base::PlatformThread::Sleep(response_delay_);
  }

  base::TimeDelta response_delay_ = base::Seconds(0);
};

// Sets up testing context for the search preloading features: search prefetch
// and search prerender.
// These features are able to coordinate with the other: A prefetched result
// might be upgraded to prerender when needed (usually when service suggests
// clients to do so), and they share the prefetched response and other
// resources, so it is a unified test designed to test the interaction between
// these two features.
class SearchPreloadBrowserTestBase : public PlatformBrowserTest,
                                     public SearchPreloadResponseController {
 public:
  SearchPreloadBrowserTestBase() = default;

  virtual void InitFeatures(
      base::test::ScopedFeatureList& scoped_feature_list) = 0;

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

    InitFeatures(scoped_feature_list_);

    PlatformBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    PlatformBrowserTest::SetUpOnMainThread();

    https_server_ = std::make_unique<net::EmbeddedTestServer>(
        net::EmbeddedTestServer::TYPE_HTTPS);
    request_collector_ = std::make_unique<EmbeddedTestServerRequestCollector>();
    delay_injector_ = std::make_unique<EmbeddedTestServerDelayInjector>();
    histogram_tester_ = std::make_unique<base::HistogramTester>();

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

    // Set up a generic server.
    embedded_test_server()->ServeFilesFromDirectory(
        base::PathService::CheckedGet(chrome::DIR_TEST_DATA));
    ASSERT_TRUE(embedded_test_server()->Start());

    // Set up server for search engine.
    https_server_->SetSSLConfig(
        net::test_server::EmbeddedTestServer::CERT_TEST_NAMES);
    https_server_->RegisterRequestMonitor(
        request_collector_->GetOnResourceRequest());
    https_server_->RegisterRequestHandler(
        base::BindRepeating(&SearchPreloadBrowserTestBase::HandleSearchRequest,
                            base::Unretained(this)));
    ASSERT_TRUE(https_server_->Start());
  }

  void SetUpTemplateURLService(bool prefetch_likely_navigations = true) {
    TemplateURLService* model =
        TemplateURLServiceFactory::GetForProfile(&GetProfile());
    ASSERT_TRUE(model);
    search_test_utils::WaitForTemplateURLServiceToLoad(model);
    ASSERT_TRUE(model->loaded());

    TemplateURLData data;
    data.SetShortName(kSearchDomain16);
    data.SetKeyword(data.short_name());
    data.SetURL(https_server_
                    ->GetURL(kSearchDomain,
                             "/search_page.html?q={searchTerms}&{google:"
                             "assistedQueryStats}{google:prefetchSource}"
                             "type=test")
                    .spec());
    data.suggestions_url =
        https_server_->GetURL(kSearchDomain, "/?q={searchTerms}").spec();
    data.prefetch_likely_navigations = prefetch_likely_navigations;

    TemplateURL* template_url = model->Add(std::make_unique<TemplateURL>(data));
    ASSERT_TRUE(template_url);
    model->SetUserSelectedDefaultSearchProvider(template_url);
  }

  struct SetUpSearchPreloadServiceArgs {
    std::optional<std::string> no_vary_search_data_cache;
  };

  void SetUpSearchPreloadService(SetUpSearchPreloadServiceArgs args) {
    auto no_vary_search_data_cache =
        [&]() -> std::optional<net::HttpNoVarySearchData> {
      if (!args.no_vary_search_data_cache.has_value()) {
        return std::nullopt;
      }

      return ParseNoVarySearchData(args.no_vary_search_data_cache.value());
    }();

    GetSearchPreloadService().SetNoVarySearchDataCacheForTesting(
        std::move(no_vary_search_data_cache));
  }

  void WaitForDuration(base::TimeDelta duration) {
    base::RunLoop run_loop;
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE, run_loop.QuitClosure(), duration);
    run_loop.Run();
  }

  std::unique_ptr<net::test_server::HttpResponse> HandleSearchRequest(
      const net::test_server::HttpRequest& request) {
    const bool is_prefetch =
        request.GetURL().GetQuery().find("pf=cs") != std::string::npos ||
        request.GetURL().GetQuery().find("pf=op") != std::string::npos;
    CHECK_EQ(is_prefetch,
             request.headers.find(blink::kSecPurposeHeaderName) !=
                     request.headers.end() &&
                 request.headers.find(blink::kSecPurposeHeaderName)->second ==
                     blink::kSecPurposePrefetchPrerenderHeaderValue);

    if (request.GetURL().spec().find(kSearchTerms_502OnPrefetch) !=
            std::string::npos &&
        is_prefetch) {
      net::HttpStatusCode code = net::HTTP_BAD_GATEWAY;
      std::string content = "<html><body>preeftch</body></html>";
      base::StringPairs headers = {
          {"Content-Length", base::NumberToString(content.length())},
          {"Content-Type", "text/html"},
          {"No-Vary-Search", R"(key-order, params, except=("q"))"},
      };
      return CreateDeferrableResponse(code, headers, content);
    }

    net::HttpStatusCode code = net::HttpStatusCode::HTTP_OK;
    std::string content = "<html><body>prefetch</body></html>";
    base::StringPairs headers = {
        {"Content-Length", base::NumberToString(content.length())},
        {"Content-Type", "text/html"},
        {"No-Vary-Search", R"(key-order, params, except=("q"))"},
    };
    return CreateDeferrableResponse(code, headers, content);
  }

  enum class PrefetchHint { kEnabled, kDisabled };
  enum class PrerenderHint { kEnabled, kDisabled };
  enum class UrlType {
    // For URLs that will be used for a real navigation.
    kReal,
    // For URLs that will be used for prefetch requests for
    // `OnAutocompleteResultChanged()`.
    kPrefetchOnSuggest,
    // For URLs that will be used for prefetch requests for
    // `OnNavigationLikely()`.
    kPrefetchOnPress,
    // For URLs that will be used for prerender requests.
    kPrerender
  };
  struct SearchUrls {
    GURL navigation;
    GURL prefetch_on_suggest;
    GURL prefetch_on_press;
    GURL prerender;
  };

  SearchUrls GetSearchUrls(const std::string& search_terms) {
    SearchUrls urls = {
        .navigation = GetSearchUrl(search_terms, UrlType::kReal),
        .prefetch_on_suggest =
            GetSearchUrl(search_terms, UrlType::kPrefetchOnSuggest),
        .prefetch_on_press =
            GetSearchUrl(search_terms, UrlType::kPrefetchOnPress),
        .prerender = GetSearchUrl(search_terms, UrlType::kPrerender),
    };
    CHECK_EQ(urls.prerender, urls.navigation);
    return urls;
  }

  GURL GetSearchUrl(const std::string& search_terms, UrlType url_type) {
    const char* pf;
    switch (url_type) {
      case UrlType::kReal:
      case UrlType::kPrerender:
        pf = "";
        break;
      case UrlType::kPrefetchOnSuggest:
        pf = "&pf=cs";
        break;
      case UrlType::kPrefetchOnPress:
        pf = "&pf=op";
        break;
    }
    std::string path = base::StrCat({
        "/search_page.html",
        "?q=",
        search_terms,
        pf,
        "&type=test",
    });
    return https_server_->GetURL(kSearchDomain, path);
  }

  void ChangeAutocompleteResult(const std::string& original_query,
                                const std::string& search_terms,
                                PrefetchHint prefetch_hint,
                                PrerenderHint prerender_hint) {
    AutocompleteInput input(base::ASCIIToUTF16(original_query),
                            metrics::OmniboxEventProto::BLANK,
                            ChromeAutocompleteSchemeClassifier(&GetProfile()));

    AutocompleteMatch autocomplete_match = CreateSearchSuggestionMatch(
        original_query, search_terms, prefetch_hint, prerender_hint);
    AutocompleteResult autocomplete_result;
    autocomplete_result.AppendMatches({autocomplete_match});

    GetSearchPreloadService().OnAutocompleteResultChanged(&GetWebContents(),
                                                          autocomplete_result);
  }

  AutocompleteMatch CreateSearchSuggestionMatch(
      const std::string& original_query,
      const std::string& search_terms,
      PrefetchHint prefetch_hint,
      PrerenderHint prerender_hint) {
    AutocompleteMatch match;
    match.search_terms_args = std::make_unique<TemplateURLRef::SearchTermsArgs>(
        base::UTF8ToUTF16(search_terms));
    match.search_terms_args->original_query = base::UTF8ToUTF16(original_query);
    match.destination_url = GetSearchUrls(search_terms).navigation;
    match.keyword = base::UTF8ToUTF16(original_query);
    match.allowed_to_be_default_match = true;

    if (prefetch_hint == PrefetchHint::kEnabled) {
      match.RecordAdditionalInfo("should_prefetch", "true");
    }
    if (prerender_hint == PrerenderHint::kEnabled) {
      match.RecordAdditionalInfo("should_prerender", "true");
    }

    return match;
  }

  void NavigateAndWaitFCP(const GURL& url) {
    page_load_metrics::PageLoadMetricsTestWaiter waiter(&GetWebContents());
    waiter.AddPageExpectation(page_load_metrics::PageLoadMetricsTestWaiter::
                                  TimingField::kFirstContentfulPaint);

    ASSERT_TRUE(content::NavigateToURL(&GetWebContents(), url));

    waiter.Wait();
  }

  // `WaitEvent::kLoadStopped` is the default value for a
  // TestNavigationObserver. Pass another event type to not wait until it
  // finishes loading.
  void NavigateToPrerenderedResult(
      const GURL& url,
      content::TestNavigationObserver::WaitEvent wait_event =
          content::TestNavigationObserver::WaitEvent::kLoadStopped) {
    content::TestNavigationObserver observer(&GetWebContents());
    observer.set_wait_event(wait_event);
    GetWebContents().OpenURL(
        content::OpenURLParams(
            url, content::Referrer(), WindowOpenDisposition::CURRENT_TAB,
            ui::PageTransitionFromInt(ui::PAGE_TRANSITION_GENERATED |
                                      ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
            /*is_renderer_initiated=*/false),
        /*navigation_handle_callback=*/{});
    observer.Wait();
  }

  void NavigateAwayToRecordHistogram() {
    CHECK(content::NavigateToURL(&GetWebContents(), GURL(url::kAboutBlankURL)));
  }

  Profile& GetProfile() { return *chrome_test_utils::GetProfile(this); }
  SearchPreloadService& GetSearchPreloadService() {
    return *SearchPreloadService::GetForProfile(&GetProfile());
  }
  content::WebContents& GetWebContents() {
    return *chrome_test_utils::GetActiveWebContents(this);
  }

  content::test::PrerenderTestHelper& prerender_helper() {
    return *prerender_helper_.get();
  }
  EmbeddedTestServerRequestCollector& request_collector() {
    return *request_collector_.get();
  }
  EmbeddedTestServerDelayInjector& delay_injector() {
    return *delay_injector_.get();
  }
  base::HistogramTester& histogram_tester() { return *histogram_tester_.get(); }

 private:
  constexpr static char kSearchDomain[] = "a.test";
  constexpr static char16_t kSearchDomain16[] = u"a.test";

  std::unique_ptr<net::test_server::EmbeddedTestServer> https_server_;
  std::unique_ptr<EmbeddedTestServerRequestCollector> request_collector_;
  std::unique_ptr<EmbeddedTestServerDelayInjector> delay_injector_;

  std::unique_ptr<base::HistogramTester> histogram_tester_;

  std::unique_ptr<content::test::PrerenderTestHelper> prerender_helper_;

  base::test::ScopedFeatureList scoped_feature_list_;
};

class SearchPreloadBrowserTest : public SearchPreloadBrowserTestBase {
 public:
  void InitFeatures(
      base::test::ScopedFeatureList& scoped_feature_list) override {
    scoped_feature_list.InitWithFeaturesAndParameters(
        {
            // Check webRequest API.
            //
            // See http://crbug.com/438935264
            {
                extensions_features::kForceWebRequestProxyForTest,
                {},
            },
            {
                features::kDsePreload2,
                {
                    {"kDsePreload2DeviceMemoryThresholdMiB", "0"},
                },
            },
            {
                features::kDsePreload2OnPress,
                {
                    {"kDsePreload2OnPressMouseDown", "true"},
                    {"kDsePreload2OnPressUpOrDownArrowButton", "true"},
                    {"kDsePreload2OnPressTouchDown", "true"},
                },
            },
        },
        /*disabled_features=*/{});
  }
};

// Scenario:
//
// - A user inputs "he".
// - Autocomplete suggests to prefetch "hello".
// - `SearchPreloadService` starts prefetch with query "?q=hello&pf=cs...".
// - A user navigates to a page with query "?q=hello&..."
// - Prefetch is used.
IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest,
                       OnAutocompleteResultChanged_TriggersPrefetch) {
  HistogramTesterWrapper uma_tester;
  SetUpTemplateURLService();
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  std::string original_query = "he";
  std::string search_terms = "hello";
  SearchUrls urls = GetSearchUrls(search_terms);

  {
    content::test::TestPrefetchWatcher watcher;

    ChangeAutocompleteResult(original_query, search_terms,
                             PrefetchHint::kEnabled, PrerenderHint::kDisabled);

    watcher.WaitUntilPrefetchResponseCompleted(std::nullopt,
                                               urls.prefetch_on_suggest);
  }

  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_EQ(0, request_collector().CountByPath(urls.prerender));

  // Navigate.
  NavigateAndWaitFCP(urls.navigation);

  NavigateAwayToRecordHistogram();

  // Prefetch is used.
  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_EQ(0, request_collector().CountByPath(urls.prerender));
  EXPECT_EQ(0, request_collector().CountByPath(urls.navigation));

  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch",
                       {SearchPreloadSignalResult::kPrefetchTriggered});
  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prerender",
                       {});
  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnPress.Prefetch", {});

  histogram_tester().ExpectBucketCount(
      "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
      "DefaultSearchEngine",
      alternative_content::PrerenderFinalStatus::kActivated, 0);

  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithoutPreload",
      0);
  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithPrefetch",
      1);
  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithPrerender",
      0);
}

// Scenario:
//
// - A user inputs "he".
// - Autocomplete suggests to prefetch "hello".
// - `SearchPreloadService` starts prefetch with query "?q=hello&pf=cs...".
// - A user inputs "hel".
// - Autocomplete suggests to prefetch "hello" (unchanged).
// - `SearchPreloadService` does nothing as prefetch for "hello" is already
//   triggered.
// - A user navigates to a page with query "?q=hello&..."
// - Prefetch is used.
IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest,
                       OnAutocompleteResultChanged_TriggeredPrefetchIsHeld) {
  HistogramTesterWrapper uma_tester;
  SetUpTemplateURLService();
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  std::string original_query = "he";
  std::string original_query2 = "hel";
  std::string search_terms = "hello";
  SearchUrls urls = GetSearchUrls(search_terms);

  {
    content::test::TestPrefetchWatcher watcher;

    ChangeAutocompleteResult(original_query, search_terms,
                             PrefetchHint::kEnabled, PrerenderHint::kDisabled);

    watcher.WaitUntilPrefetchResponseCompleted(std::nullopt,
                                               urls.prefetch_on_suggest);
  }

  // A user inputs anothor character and `OnAutocompleteResultChanged()` is
  // called with "hel". Prefetch is already triggered and it doesn't trigger
  // another one.
  ChangeAutocompleteResult(original_query2, search_terms,
                           PrefetchHint::kEnabled, PrerenderHint::kDisabled);

  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_EQ(0, request_collector().CountByPath(urls.prerender));

  // Navigate.
  NavigateAndWaitFCP(urls.navigation);

  NavigateAwayToRecordHistogram();

  // Prefetch is used.
  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_EQ(0, request_collector().CountByPath(urls.prerender));
  EXPECT_EQ(0, request_collector().CountByPath(urls.navigation));

  uma_tester.ExpectUma(
      "Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch",
      {SearchPreloadSignalResult::kPrefetchTriggered,
       SearchPreloadSignalResult::kNotTriggeredAlreadyTriggered});
  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prerender",
                       {});
  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnPress.Prefetch", {});

  histogram_tester().ExpectBucketCount(
      "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
      "DefaultSearchEngine",
      alternative_content::PrerenderFinalStatus::kActivated, 0);

  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithoutPreload",
      0);
  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithPrefetch",
      1);
  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithPrerender",
      0);
}

// Scenario:
//
// - A user inputs "he".
// - Autocomplete suggests to prerender "hello".
// - `SearchPreloadService` starts prefetch with query "?q=hello&pf=cs...".
// - `SearchPreloadService` starts prerender with query "?q=hello...".
// - A user navigates to a page with query "?q=hello&..."
// - Prerender is used.
IN_PROC_BROWSER_TEST_F(
    SearchPreloadBrowserTest,
    OnAutocompleteResultChanged_TriggersPrefetchAndPrerender) {
  HistogramTesterWrapper uma_tester;
  SetUpTemplateURLService();
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  std::string original_query = "he";
  std::string search_terms = "hello";
  SearchUrls urls = GetSearchUrls(search_terms);

  {
    content::test::TestPrefetchWatcher watcher;
    content::test::PrerenderHostRegistryObserver registry_observer(
        GetWebContents());

    ChangeAutocompleteResult(original_query, search_terms,
                             PrefetchHint::kEnabled, PrerenderHint::kEnabled);

    watcher.WaitUntilPrefetchResponseCompleted(std::nullopt,
                                               urls.prefetch_on_suggest);

    registry_observer.WaitForTrigger(urls.prerender);
    prerender_helper().WaitForPrerenderLoadCompletion(GetWebContents(),
                                                      urls.prerender);
  }

  // Only prefetch request went through network and prerender used the
  // prefetched response.
  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_EQ(0, request_collector().CountByPath(urls.prerender));

  // Navigate.
  {
    content::test::PrerenderHostObserver prerender_observer(GetWebContents(),
                                                            urls.prerender);
    page_load_metrics::PageLoadMetricsTestWaiter waiter(&GetWebContents());
    waiter.AddPageExpectation(page_load_metrics::PageLoadMetricsTestWaiter::
                                  TimingField::kFirstContentfulPaint);

    NavigateToPrerenderedResult(urls.navigation);

    prerender_observer.WaitForActivation();
    waiter.Wait();
  }

  NavigateAwayToRecordHistogram();

  // Prerender is used.
  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_EQ(0, request_collector().CountByPath(urls.prerender));
  EXPECT_EQ(0, request_collector().CountByPath(urls.navigation));

  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch",
                       {SearchPreloadSignalResult::kPrefetchTriggered});
  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prerender",
                       {SearchPreloadSignalResult::kPrerenderTriggered});
  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnPress.Prefetch", {});

  histogram_tester().ExpectBucketCount(
      "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
      "DefaultSearchEngine",
      alternative_content::PrerenderFinalStatus::kActivated, 1);

  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithoutPreload",
      0);
  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithPrefetch",
      0);
  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithPrerender",
      1);
}

// Scenario:
//
// - A user inputs "he".
// - Autocomplete suggests to prefetch "hello".
// - `SearchPreloadService` starts prefetch with query "?q=hello&pf=cs...".
// - A user inputs "hel".
// - Autocomplete suggests to prerender "hello".
// - `SearchPreloadService` starts prerender with query "?q=hello...".
// - A user navigates to a page with query "?q=hello&..."
// - Prerender is used.
IN_PROC_BROWSER_TEST_F(
    SearchPreloadBrowserTest,
    OnAutocompleteResultChanged_TriggersPrefetchThenPrerender) {
  HistogramTesterWrapper uma_tester;
  SetUpTemplateURLService();
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  std::string original_query = "he";
  std::string original_query2 = "hel";
  std::string search_terms = "hello";
  SearchUrls urls = GetSearchUrls(search_terms);

  {
    content::test::TestPrefetchWatcher watcher;

    ChangeAutocompleteResult(original_query, search_terms,
                             PrefetchHint::kEnabled, PrerenderHint::kDisabled);

    watcher.WaitUntilPrefetchResponseCompleted(std::nullopt,
                                               urls.prefetch_on_suggest);
  }

  {
    content::test::PrerenderHostRegistryObserver registry_observer(
        GetWebContents());

    ChangeAutocompleteResult(original_query2, search_terms,
                             PrefetchHint::kEnabled, PrerenderHint::kEnabled);

    prerender_helper().WaitForPrerenderLoadCompletion(GetWebContents(),
                                                      urls.prerender);
  }

  // Only prefetch request went through network and prerender used the
  // prefetched response.
  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_EQ(0, request_collector().CountByPath(urls.prerender));

  // Navigate.
  {
    content::test::PrerenderHostObserver prerender_observer(GetWebContents(),
                                                            urls.prerender);
    page_load_metrics::PageLoadMetricsTestWaiter waiter(&GetWebContents());
    waiter.AddPageExpectation(page_load_metrics::PageLoadMetricsTestWaiter::
                                  TimingField::kFirstContentfulPaint);

    NavigateToPrerenderedResult(urls.navigation);

    prerender_observer.WaitForActivation();
    waiter.Wait();
  }

  NavigateAwayToRecordHistogram();

  // Prerender is used.
  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_EQ(0, request_collector().CountByPath(urls.prerender));
  EXPECT_EQ(0, request_collector().CountByPath(urls.navigation));

  uma_tester.ExpectUma(
      "Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch",
      {SearchPreloadSignalResult::kPrefetchTriggered,
       SearchPreloadSignalResult::kNotTriggeredAlreadyTriggered});
  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prerender",
                       {SearchPreloadSignalResult::kPrerenderTriggered});
  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnPress.Prefetch", {});

  histogram_tester().ExpectBucketCount(
      "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
      "DefaultSearchEngine",
      alternative_content::PrerenderFinalStatus::kActivated, 1);

  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithoutPreload",
      0);
  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithPrefetch",
      0);
  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithPrerender",
      1);
}

// Scenario:
//
// - A user inputs "he".
// - A user clicks a suggestion "hello".
// - `SearchPreloadService` starts prefetch with query "?q=hello&pf=op...".
// - A user navigates to a page with query "?q=hello&..."
// - Prefetch is used.
IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest,
                       OnNavigationLikely_TriggersPrefetch) {
  HistogramTesterWrapper uma_tester;
  SetUpTemplateURLService(/*prefetch_likely_navigations=*/true);
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  std::string original_query = "he";
  std::string search_terms = "hello";
  SearchUrls urls = GetSearchUrls(search_terms);

  {
    content::test::TestPrefetchWatcher watcher;

    AutocompleteMatch autocomplete_match = CreateSearchSuggestionMatch(
        original_query, search_terms, PrefetchHint::kEnabled,
        PrerenderHint::kDisabled);

    const bool is_triggered_prefetch =
        GetSearchPreloadService().OnNavigationLikely(
            1, autocomplete_match,
            omnibox::mojom::NavigationPredictor::kMouseDown, &GetWebContents());
    ASSERT_TRUE(is_triggered_prefetch);

    watcher.WaitUntilPrefetchResponseCompleted(std::nullopt,
                                               urls.prefetch_on_press);
  }

  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_press));

  // Navigate.
  NavigateAndWaitFCP(urls.navigation);

  NavigateAwayToRecordHistogram();

  // Prefetch is used.
  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_press));
  EXPECT_EQ(0, request_collector().CountByPath(urls.navigation));

  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch",
                       {});
  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prerender",
                       {});
  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnPress.Prefetch",
                       {SearchPreloadSignalResult::kPrefetchTriggered});

  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithoutPreload",
      0);
  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithPrefetch",
      1);
  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithPrerender",
      0);
}

// `OnNavigationLikely()` doesn't trigger prefetch if default search provider
// doesn't opt in.
IN_PROC_BROWSER_TEST_F(
    SearchPreloadBrowserTest,
    OnNavigationLikely_DoesntTriggerPrefetchIfDefaultSearchProviderDoesntOptIn) {
  SetUpTemplateURLService(/*prefetch_likely_navigations=*/false);
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  std::string original_query = "he";
  std::string search_terms = "hello";

  AutocompleteMatch autocomplete_match = CreateSearchSuggestionMatch(
      original_query, search_terms, PrefetchHint::kEnabled,
      PrerenderHint::kDisabled);

  const bool is_triggered_prefetch =
      GetSearchPreloadService().OnNavigationLikely(
          1, autocomplete_match,
          omnibox::mojom::NavigationPredictor::kMouseDown, &GetWebContents());
  ASSERT_FALSE(is_triggered_prefetch);
}

// Scenario:
//
// - A user inputs "he".
// - Autocomplete suggests to prefetch "hello".
// - `SearchPreloadService` starts prefetch with query "?q=hello&pf=cs...".
// - A user clicks a suggestion "hello".
// - Prefetch is not triggered with query "?q=hello&pf=op..." as prefetch is
//   already triggered.
// - A user navigates to a page with query "?q=hello&..."
// - Prefetch is used.
IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest,
                       OnAutocompleteResultChanged_Then_OnNavigationLikely) {
  HistogramTesterWrapper uma_tester;
  SetUpTemplateURLService(/*prefetch_likely_navigations=*/true);
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  std::string original_query = "he";
  std::string search_terms = "hello";
  SearchUrls urls = GetSearchUrls(search_terms);

  {
    content::test::TestPrefetchWatcher watcher;

    ChangeAutocompleteResult(original_query, search_terms,
                             PrefetchHint::kEnabled, PrerenderHint::kDisabled);

    watcher.WaitUntilPrefetchResponseCompleted(std::nullopt,
                                               urls.prefetch_on_suggest);
  }

  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest));

  AutocompleteMatch autocomplete_match = CreateSearchSuggestionMatch(
      original_query, search_terms, PrefetchHint::kEnabled,
      PrerenderHint::kDisabled);

  const bool is_triggered_prefetch =
      GetSearchPreloadService().OnNavigationLikely(
          1, autocomplete_match,
          omnibox::mojom::NavigationPredictor::kMouseDown, &GetWebContents());
  ASSERT_FALSE(is_triggered_prefetch);

  // Navigate.
  NavigateAndWaitFCP(urls.navigation);

  NavigateAwayToRecordHistogram();

  // Prefetch is used.
  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_EQ(0, request_collector().CountByPath(urls.prefetch_on_press));
  EXPECT_EQ(0, request_collector().CountByPath(urls.navigation));

  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch",
                       {SearchPreloadSignalResult::kPrefetchTriggered});
  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prerender",
                       {});
  uma_tester.ExpectUma(
      "Omnibox.DsePreload.SignalResult.OnPress.Prefetch",
      {SearchPreloadSignalResult::kNotTriggeredAlreadyTriggered});

  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithoutPreload",
      0);
  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithPrefetch",
      1);
  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithPrerender",
      0);
}

// Scenario:
//
// - A user inputs "he".
// - Autocomplete suggests to prefetch "hello".
// - `SearchPreloadService` starts prefetch with query "?q=hello&pf=cs..."
//   without No-Vary-Search hint.
// - A user navigates to a page with query "?q=hello&..."
//   - Prefetch matching fails due to lack of No-Vary-Search hint and "pf=cs"
//     param.
// - Prefetch is not used.
// TODO(crbug.com/434918482): Re-enable this test.
IN_PROC_BROWSER_TEST_F(
    SearchPreloadBrowserTest,
    DISABLED_TriggersPrefetchButMatchingFailedDueToNoVarySearchHint) {
  HistogramTesterWrapper uma_tester;
  SetUpTemplateURLService();
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = std::nullopt,
  });
  // Inject delay to keep `PrefetchContainer` waiting for a response header, so
  // that prefetch matching fail because the prefetch has query parameter
  // "pf=cs" but navigation doesn't and No-Vary-Search hint is not set. If we
  // don't do this, No-Vary-Search header is used and prefetch matching succeed.
  delay_injector().SetResponseDelay(base::Seconds(1));

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  std::string original_query = "he";
  std::string search_terms = "hello";
  SearchUrls urls = GetSearchUrls(search_terms);

  {
    content::test::TestPrefetchWatcher watcher;

    ChangeAutocompleteResult(original_query, search_terms,
                             PrefetchHint::kEnabled, PrerenderHint::kDisabled);

    // Navigate.
    NavigateAndWaitFCP(urls.navigation);

    watcher.WaitUntilPrefetchResponseCompleted(std::nullopt,
                                               urls.prefetch_on_suggest);
  }

  NavigateAwayToRecordHistogram();

  // Prefetch isn't used.
  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_EQ(1, request_collector().CountByPath(urls.navigation));

  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch",
                       {SearchPreloadSignalResult::kPrefetchTriggered});
  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prerender",
                       {});
  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnPress.Prefetch", {});

  // No-Vary-Search data cache is updated.
  histogram_tester().ExpectUniqueSample(
      "Omnibox.DsePreload.Prefetch.NoVarySearchDataCacheUpdate",
      SearchPreloadServiceNoVarySearchDataCacheUpdate::kNullToSome, 1);

  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithoutPreload",
      1);
  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithPrefetch",
      0);
  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithPrerender",
      0);

  ASSERT_EQ(GetSearchPreloadService().GetNoVarySearchDataCacheForTesting(),
            ParseNoVarySearchData(R"(key-order, params, except=("q"))"));
}

// Scenario:
//
// - A user inputs "he".
// - Autocomplete suggests to prerender "hello".
// - `SearchPreloadService` starts prefetch with query "?q=hello&pf=cs..."
//   without No-Vary-Search hint.
// - `SearchPreloadService` starts prerender with query "?q=hello...".
//   - Prefetch matching fails due to lack of No-Vary-Search hint and "pf=cs"
//     param
//   - `PrerenderURLLoaderThrottle` cancels the prerender.
// - A user navigates to a page with query "?q=hello&..."
//   - Prefetch matching fails due to lack of No-Vary-Search hint and "pf=cs"
//     param.
// - Prefetch is not used.
//
// TODO(crbug.com/434918482): Re-enable this test.
IN_PROC_BROWSER_TEST_F(
    SearchPreloadBrowserTest,
    DISABLED_TriggersPrefetchAndPrerenderButPrerenderFailsDueToNoVarySearchHint) {
  HistogramTesterWrapper uma_tester;
  SetUpTemplateURLService();
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = std::nullopt,
  });
  // Inject delay to keep `PrefetchContainer` waiting for a response header, so
  // that prefetch matching fail because the prefetch has query parameter
  // "pf=cs" but navigation doesn't and No-Vary-Search hint is not set. If we
  // don't do this, No-Vary-Search header is used and prefetch matching succeed.
  delay_injector().SetResponseDelay(base::Seconds(1));

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  std::string original_query = "he";
  std::string search_terms = "hello";
  SearchUrls urls = GetSearchUrls(search_terms);

  {
    content::test::TestPrefetchWatcher watcher;
    content::test::PrerenderHostObserver prerender_host_observer(
        GetWebContents(), urls.prerender);

    ChangeAutocompleteResult(original_query, search_terms,
                             PrefetchHint::kEnabled, PrerenderHint::kEnabled);

    prerender_host_observer.WaitForDestroyed();

    // Navigate.
    NavigateAndWaitFCP(urls.navigation);

    watcher.WaitUntilPrefetchResponseCompleted(std::nullopt,
                                               urls.prefetch_on_suggest);
  }

  NavigateAwayToRecordHistogram();

  // Prefetch nor prerender aren't used.
  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_EQ(1, request_collector().CountByPath(urls.navigation));

  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch",
                       {SearchPreloadSignalResult::kPrefetchTriggered});
  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prerender",
                       {SearchPreloadSignalResult::kPrerenderTriggered});
  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnPress.Prefetch", {});

  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
      "DefaultSearchEngine",
      alternative_content::PrerenderFinalStatus::kPrerenderFailedDuringPrefetch,
      1);
  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrefetchAheadOfPrerenderFailed.PrefetchStatus."
      "Embedder_DefaultSearchEngine",
      alternative_content::PrefetchStatus::kPrefetchNotFinishedInTime, 1);

  // No-Vary-Search data cache is updated.
  histogram_tester().ExpectUniqueSample(
      "Omnibox.DsePreload.Prefetch.NoVarySearchDataCacheUpdate",
      SearchPreloadServiceNoVarySearchDataCacheUpdate::kNullToSome, 1);

  ASSERT_EQ(GetSearchPreloadService().GetNoVarySearchDataCacheForTesting(),
            ParseNoVarySearchData(R"(key-order, params, except=("q"))"));

  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithoutPreload",
      1);
  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithPrefetch",
      0);
  uma_tester.ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithPrerender",
      0);
}

// A pipeline is consumed by navigation.
//
// Note that this is for aligning the behavior of `SearchPrefetchService`. It
// would be nice to discuss the ideal behavior.
//
// See also
// https://docs.google.com/document/d/1NjxwlOEoBwpXojG13M85XtS8nH-S4uc0F6VOrlwIAXE/edit?pli=1&tab=t.0#heading=h.5qv0ome418fo
IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest,
                       PipelineIsConsumedByNavigation) {
  SetUpTemplateURLService();
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  std::string original_query = "hello";
  std::string search_terms = original_query;
  SearchUrls urls = GetSearchUrls(search_terms);

  {
    content::test::TestPrefetchWatcher watcher;

    ChangeAutocompleteResult(original_query, search_terms,
                             PrefetchHint::kEnabled, PrerenderHint::kDisabled);

    watcher.WaitUntilPrefetchResponseCompleted(std::nullopt,
                                               urls.prefetch_on_suggest);
  }

  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_EQ(0, request_collector().CountByPath(urls.prerender));

  // Navigate.
  ASSERT_TRUE(content::NavigateToURL(&GetWebContents(), urls.navigation));

  // Prefetch is used.
  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_EQ(0, request_collector().CountByPath(urls.navigation));

  // Prefetch was consumed by the navigation.
  ASSERT_FALSE(GetSearchPreloadService().InvalidatePipelineForTesting(
      GetWebContents(), urls.navigation));

  // Navigate.
  ASSERT_TRUE(content::NavigateToURL(&GetWebContents(), urls.navigation));

  // Prefetch is not available.
  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_EQ(1, request_collector().CountByPath(urls.navigation));
}

class SearchPreloadBrowserTest_ErrorBackoffDuration
    : public SearchPreloadBrowserTestBase {
  void InitFeatures(
      base::test::ScopedFeatureList& scoped_feature_list) override {
    scoped_feature_list.InitWithFeaturesAndParameters(
        {
            {
                features::kDsePreload2,
                {
                    {"kDsePreload2ErrorBackoffDuration", "1000ms"},
                    {"kDsePreload2DeviceMemoryThresholdMiB", "0"},
                },
            },
        },
        /*disabled_features=*/{});
  }
};

IN_PROC_BROWSER_TEST_F(
    SearchPreloadBrowserTest_ErrorBackoffDuration,
    PreloadsAreNotTriggeredCertainPeriodAfterPrefetchFailed) {
  SetUpTemplateURLService();
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  auto check = [&](std::string original_query,
                   const bool is_triggered_expected) {
    HistogramTesterWrapper uma_tester;
    request_collector().Reset();

    std::string search_terms = original_query;
    SearchUrls urls = GetSearchUrls(search_terms);

    {
      content::test::TestPrefetchWatcher watcher;

      ChangeAutocompleteResult(original_query, search_terms,
                               PrefetchHint::kEnabled,
                               PrerenderHint::kDisabled);

      if (is_triggered_expected) {
        watcher.WaitUntilPrefetchResponseCompleted(std::nullopt,
                                                   urls.prefetch_on_suggest);
      }
    }

    EXPECT_EQ(is_triggered_expected,
              request_collector().CountByPath(urls.prefetch_on_suggest));
    EXPECT_EQ(0, request_collector().CountByPath(urls.prerender));
  };

  check(kSearchTerms_502OnPrefetch, true);
  check("two", false);
  WaitForDuration(base::Milliseconds(1000));
  check("three", true);
}

class SearchPreloadBrowserTest_DeviceMemoryThreshold
    : public SearchPreloadBrowserTestBase {
  void InitFeatures(
      base::test::ScopedFeatureList& scoped_feature_list) override {
    scoped_feature_list.InitWithFeaturesAndParameters(
        {
            {
                features::kDsePreload2,
                {
                    {"kDsePreload2DeviceMemoryThresholdMiB",
                     base::NumberToString(std::numeric_limits<int>::max())},
                },
            },
            {
                features::kDsePreload2OnPress,
                {
                    {"kDsePreload2OnPressMouseDown", "true"},
                    {"kDsePreload2OnPressUpOrDownArrowButton", "true"},
                    {"kDsePreload2OnPressTouchDown", "true"},
                },
            },
        },
        /*disabled_features=*/{});
  }
};

IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest_DeviceMemoryThreshold,
                       FeatureIsDisabledIfDeviceMemoryIsSmallerThanThreshold) {
  ASSERT_FALSE(features::IsDsePreload2Enabled());
}

class SearchPreloadBrowserTest_Limit : public SearchPreloadBrowserTestBase {
  void InitFeatures(
      base::test::ScopedFeatureList& scoped_feature_list) override {
    scoped_feature_list.InitWithFeaturesAndParameters(
        {
            {
                features::kDsePreload2,
                {
                    {"kDsePreload2DeviceMemoryThresholdMiB", "0"},
                    {"kDsePreload2MaxPrefetch", "2"},
                },
            },
            {
                features::kDsePreload2OnPress,
                {
                    {"kDsePreload2OnPressMouseDown", "true"},
                    {"kDsePreload2OnPressUpOrDownArrowButton", "true"},
                    {"kDsePreload2OnPressTouchDown", "true"},
                },
            },
        },
        /*disabled_features=*/{});
  }
};

// The number of prefetches are bounded by `kDsePreload2MaxPrefetch`.
IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest_Limit,
                       OnAutocompleteResultChanged_PrefetchIsLimited) {
  SetUpTemplateURLService();
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  auto check = [&](std::string original_query,
                   const bool is_triggered_expected) {
    HistogramTesterWrapper uma_tester;
    request_collector().Reset();

    std::string search_terms = original_query;
    SearchUrls urls = GetSearchUrls(search_terms);

    {
      content::test::TestPrefetchWatcher watcher;

      ChangeAutocompleteResult(original_query, search_terms,
                               PrefetchHint::kEnabled,
                               PrerenderHint::kDisabled);

      if (is_triggered_expected) {
        watcher.WaitUntilPrefetchResponseCompleted(std::nullopt,
                                                   urls.prefetch_on_suggest);
      }
    }

    EXPECT_EQ(is_triggered_expected,
              request_collector().CountByPath(urls.prefetch_on_suggest));
    EXPECT_EQ(0, request_collector().CountByPath(urls.prerender));

    if (is_triggered_expected) {
      uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch",
                           {SearchPreloadSignalResult::kPrefetchTriggered});
      uma_tester.ExpectUma(
          "Omnibox.DsePreload.SignalResult.OnSuggest.Prerender", {});
    } else {
      uma_tester.ExpectUma(
          "Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch",
          {SearchPreloadSignalResult::kNotTriggeredLimitExceeded});
      uma_tester.ExpectUma(
          "Omnibox.DsePreload.SignalResult.OnSuggest.Prerender", {});
    }
  };

  check("one", true);
  check("two", true);
  check("three", false);
  ASSERT_TRUE(GetSearchPreloadService().InvalidatePipelineForTesting(
      GetWebContents(), GetSearchUrls("one").navigation));
  check("four", true);
}

// The number of prefetches are bounded by `kDsePreload2MaxPrefetch`.
IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest_Limit,
                       OnNavigationLikely_PrefetchIsLimited) {
  SetUpTemplateURLService();
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  auto check = [&](std::string original_query,
                   const bool is_triggered_expected) {
    HistogramTesterWrapper uma_tester;
    request_collector().Reset();

    std::string search_terms = original_query;
    SearchUrls urls = GetSearchUrls(search_terms);

    {
      content::test::TestPrefetchWatcher watcher;

      AutocompleteMatch autocomplete_match = CreateSearchSuggestionMatch(
          original_query, search_terms, PrefetchHint::kEnabled,
          PrerenderHint::kDisabled);

      const bool is_triggered_prefetch =
          GetSearchPreloadService().OnNavigationLikely(
              1, autocomplete_match,
              omnibox::mojom::NavigationPredictor::kMouseDown,
              &GetWebContents());
      ASSERT_EQ(is_triggered_expected, is_triggered_prefetch);

      if (is_triggered_expected) {
        watcher.WaitUntilPrefetchResponseCompleted(std::nullopt,
                                                   urls.prefetch_on_press);
      }
    }

    EXPECT_EQ(is_triggered_expected,
              request_collector().CountByPath(urls.prefetch_on_press));
    EXPECT_EQ(0, request_collector().CountByPath(urls.prerender));

    if (is_triggered_expected) {
      uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnPress.Prefetch",
                           {SearchPreloadSignalResult::kPrefetchTriggered});
    } else {
      uma_tester.ExpectUma(
          "Omnibox.DsePreload.SignalResult.OnPress.Prefetch",
          {SearchPreloadSignalResult::kNotTriggeredLimitExceeded});
    }
  };

  check("one", true);
  check("two", true);
  check("three", false);
  ASSERT_TRUE(GetSearchPreloadService().InvalidatePipelineForTesting(
      GetWebContents(), GetSearchUrls("one").navigation));
  check("four", true);
}

// The number of prerenders are bounded by 1; the last one wins.
IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest_Limit,
                       OnAutocompleteResultChanged_PrerenderIsLimited) {
  SetUpTemplateURLService();
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  auto check = [&](std::string original_query, const bool is_triggered_expected,
                   std::vector<std::string> queries_cancelled_prerender) {
    HistogramTesterWrapper uma_tester;
    request_collector().Reset();

    std::string search_terms = original_query;
    SearchUrls urls = GetSearchUrls(search_terms);

    {
      content::test::TestPrefetchWatcher watcher;
      content::test::PrerenderHostRegistryObserver registry_observer(
          GetWebContents());

      ChangeAutocompleteResult(original_query, search_terms,
                               PrefetchHint::kEnabled, PrerenderHint::kEnabled);

      if (is_triggered_expected) {
        watcher.WaitUntilPrefetchResponseCompleted(std::nullopt,
                                                   urls.prefetch_on_suggest);

        // Check prerender is triggered even if it reached the limit.
        registry_observer.WaitForTrigger(urls.prerender);
        prerender_helper().WaitForPrerenderLoadCompletion(GetWebContents(),
                                                          urls.prerender);

        // Check other prerenderes are cancelled.
        for (const auto& query_cancelled_prerender :
             queries_cancelled_prerender) {
          SearchUrls cancelled_urls = GetSearchUrls(query_cancelled_prerender);
          ASSERT_EQ(prerender_helper().GetHostForUrl(cancelled_urls.prerender),
                    content::PrerenderHostId());
        }
      }
    }

    EXPECT_EQ(is_triggered_expected,
              request_collector().CountByPath(urls.prefetch_on_suggest));
    EXPECT_EQ(0, request_collector().CountByPath(urls.prerender));

    if (is_triggered_expected) {
      uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch",
                           {SearchPreloadSignalResult::kPrefetchTriggered});
      uma_tester.ExpectUma(
          "Omnibox.DsePreload.SignalResult.OnSuggest.Prerender",
          {SearchPreloadSignalResult::kPrerenderTriggered});
    } else {
      uma_tester.ExpectUma(
          "Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch",
          {SearchPreloadSignalResult::kNotTriggeredLimitExceeded});
      uma_tester.ExpectUma(
          "Omnibox.DsePreload.SignalResult.OnSuggest.Prerender", {});
    }
  };

  check("one", true, {});
  check("two", true, {"one"});
  check("three", false, {});
  ASSERT_TRUE(GetSearchPreloadService().InvalidatePipelineForTesting(
      GetWebContents(), GetSearchUrls("one").navigation));
  check("four", true, {"one", "two"});
}

class SearchPreloadBrowserTest_Ttl : public SearchPreloadBrowserTestBase {
  void InitFeatures(
      base::test::ScopedFeatureList& scoped_feature_list) override {
    scoped_feature_list.InitWithFeaturesAndParameters(
        {
            {
                features::kDsePreload2,
                {
                    {"kDsePreload2DeviceMemoryThresholdMiB", "0"},
                    {"kDsePreload2MaxPrefetch", "2"},
                    {"kDsePreload2PrefetchTtl", "1000ms"},
                },
            },
        },
        /*disabled_features=*/{});
  }
};

// Prefetch expires after `kDsePreload2PrefetchTtl`.
IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest_Ttl, PrefetchExpiresAfterTtl) {
  SetUpTemplateURLService();
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  std::string original_query = "hello";
  std::string search_terms = original_query;
  SearchUrls urls = GetSearchUrls(search_terms);

  {
    content::test::TestPrefetchWatcher watcher;

    ChangeAutocompleteResult(original_query, search_terms,
                             PrefetchHint::kEnabled, PrerenderHint::kDisabled);

    watcher.WaitUntilPrefetchResponseCompleted(std::nullopt,
                                               urls.prefetch_on_suggest);
  }

  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_EQ(0, request_collector().CountByPath(urls.prerender));

  WaitForDuration(base::Milliseconds(1001));

  // Navigate.
  NavigateAndWaitFCP(urls.navigation);

  NavigateAwayToRecordHistogram();

  // Prefetch is not used.
  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_EQ(1, request_collector().CountByPath(urls.navigation));

  histogram_tester().ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithoutPreload",
      1);
  histogram_tester().ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithPrefetch",
      0);
  histogram_tester().ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithPrerender",
      0);
}

// Scenario:
//
// - A user inputs "hello".
// - Autocomplete suggests to prerender "hello".
// - `SearchPreloadService` starts prefetch with query "?q=hello&pf=cs...".
// - `SearchPreloadService` starts prerender with query "?q=hello...".
// - Prefetch is expired.
//   - Prerender is still available because it already used the prefetch result.
// - A user navigates to a page with query "?q=hello&..."
// - Prerender is used.
IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest_Ttl,
                       PrerenderIsAvailableAfterPrefetchTtl) {
  SetUpTemplateURLService();
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  std::string original_query = "hello";
  std::string search_terms = original_query;
  SearchUrls urls = GetSearchUrls(search_terms);

  {
    content::test::TestPrefetchWatcher watcher;
    content::test::PrerenderHostRegistryObserver registry_observer(
        GetWebContents());

    ChangeAutocompleteResult(original_query, search_terms,
                             PrefetchHint::kEnabled, PrerenderHint::kEnabled);

    watcher.WaitUntilPrefetchResponseCompleted(std::nullopt,
                                               urls.prefetch_on_suggest);

    registry_observer.WaitForTrigger(urls.prerender);
    prerender_helper().WaitForPrerenderLoadCompletion(GetWebContents(),
                                                      urls.prerender);
  }

  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_EQ(0, request_collector().CountByPath(urls.prerender));

  WaitForDuration(base::Milliseconds(1001));

  // Navigate.
  {
    content::test::PrerenderHostObserver prerender_observer(GetWebContents(),
                                                            urls.prerender);
    page_load_metrics::PageLoadMetricsTestWaiter waiter(&GetWebContents());
    waiter.AddPageExpectation(page_load_metrics::PageLoadMetricsTestWaiter::
                                  TimingField::kFirstContentfulPaint);

    NavigateToPrerenderedResult(urls.navigation);

    prerender_observer.WaitForActivation();
    waiter.Wait();
  }

  NavigateAwayToRecordHistogram();

  // Prerender is used.
  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_EQ(0, request_collector().CountByPath(urls.navigation));

  histogram_tester().ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
      "DefaultSearchEngine",
      alternative_content::PrerenderFinalStatus::kActivated, 1);

  histogram_tester().ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithoutPreload",
      0);
  histogram_tester().ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithPrefetch",
      0);
  histogram_tester().ExpectTotalCount(
      "PreloadServingMetrics.PageLoad.Clients.PaintTiming."
      "NavigationToFirstContentfulPaint.WithPrerender",
      1);
}

// Limit cares TTL; expired prefetch is not counted.
IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest_Ttl, LimitCaresTtl) {
  SetUpTemplateURLService();
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  auto check = [&](std::string original_query,
                   const bool is_triggered_expected) {
    HistogramTesterWrapper uma_tester;
    request_collector().Reset();

    std::string search_terms = original_query;
    SearchUrls urls = GetSearchUrls(search_terms);

    {
      content::test::TestPrefetchWatcher watcher;

      ChangeAutocompleteResult(original_query, search_terms,
                               PrefetchHint::kEnabled,
                               PrerenderHint::kDisabled);

      if (is_triggered_expected) {
        watcher.WaitUntilPrefetchResponseCompleted(std::nullopt,
                                                   urls.prefetch_on_suggest);
      }
    }

    EXPECT_EQ(is_triggered_expected,
              request_collector().CountByPath(urls.prefetch_on_suggest));
    EXPECT_EQ(0, request_collector().CountByPath(urls.prerender));
  };

  check("one", true);
  check("two", true);
  check("three", false);
  WaitForDuration(base::Milliseconds(1001));
  check("four", true);
}

class SearchPreloadBrowserTest_Throttle : public SearchPreloadBrowserTestBase {
 public:
  void InitFeatures(
      base::test::ScopedFeatureList& scoped_feature_list) override {
    scoped_feature_list.InitWithFeaturesAndParameters(
        {
            {
                features::kDsePreload2,
                {
                    {"kDsePreload2UsePreloadServingMetrics", "true"},
                    {"kDsePreload2DeviceMemoryThresholdMiB", "0"},
                },
            },
            {
                features::kDsePreload2OnPress,
                {
                    {"kDsePreload2OnPressMouseDown", "true"},
                    {"kDsePreload2OnPressUpOrDownArrowButton", "true"},
                    {"kDsePreload2OnPressTouchDown", "true"},
                },
            },
        },
        /*disabled_features=*/{});
    scoped_prewarm_feature_list_ = std::make_unique<
        test::ScopedPrewarmFeatureList>(
        test::ScopedPrewarmFeatureList::PrewarmState::kEnabledWithNoTrigger);
  }

  void SetUpOnMainThread() override {
    PrerenderManager::CreateForWebContents(&GetWebContents());
    SearchPreloadBrowserTestBase::SetUpOnMainThread();
  }

 private:
  std::unique_ptr<test::ScopedPrewarmFeatureList> scoped_prewarm_feature_list_;
};

// Tests that search prefetch/prerender are throttled during an ongoing search
// prewarm request and resume after the prewarm request finishes.
// 1. Checks that preloads are deferred when a search prewarm is in progress.
// 2. Checks that preloads are triggered once the prewarm finishes.
IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest_Throttle, ThrottlePreload) {
  SetUpTemplateURLService();
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  GURL prewarm_url = embedded_test_server()->GetURL("/prewarm.html");
  auto* prerender_manager =
      PrerenderManager::FromWebContents(&GetWebContents());
  prerender_manager->SetPrewarmUrlForTesting(prewarm_url);

  content::TestNavigationManager navigation_manager(&GetWebContents(),
                                                    prewarm_url);
  EXPECT_TRUE(prerender_manager->MaybeStartPrewarmSearchResult());
  EXPECT_TRUE(navigation_manager.WaitForRequestStart());
  auto* service = SearchPreloadProgressServiceFactory::GetForProfile(
      Profile::FromBrowserContext(GetWebContents().GetBrowserContext()));
  EXPECT_TRUE(service && service->HasOnGoingSearchPrewarm());

  std::string original_query = "he";
  std::string search_terms = "hello";
  SearchUrls urls = GetSearchUrls(search_terms);

  request_collector().Reset();
  ChangeAutocompleteResult(original_query, search_terms, PrefetchHint::kEnabled,
                           PrerenderHint::kEnabled);

  // Verify that neither prefetch nor prerender started.
  EXPECT_EQ(0, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_FALSE(prerender_helper().GetHostForUrl(urls.prerender));

  content::test::TestPrefetchWatcher watcher;

  // Release the prewarm headers.
  navigation_manager.ResumeNavigation();
  EXPECT_TRUE(navigation_manager.WaitForNavigationFinished());
  EXPECT_FALSE(service && service->HasOnGoingSearchPrewarm());

  // Now the deferred preload should start.
  watcher.WaitUntilPrefetchResponseCompleted(std::nullopt,
                                             urls.prefetch_on_suggest);

  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_TRUE(prerender_helper().GetHostForUrl(urls.prerender));
}

// Tests that `OnNavigationLikely` does not trigger prefetch when a search
// prewarm request is ongoing.
// 1. Checks that `OnNavigationLikely` is throttled by search prewarm.
// 2. Checks that the signal result records `kNotTriggeredThrottledByPrewarm`.
IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest_Throttle,
                       OnNavigationLikely_ThrottledByPrewarm) {
  HistogramTesterWrapper uma_tester;
  SetUpTemplateURLService(/*prefetch_likely_navigations=*/true);
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  GURL prewarm_url = embedded_test_server()->GetURL("/prewarm.html");
  auto* prerender_manager =
      PrerenderManager::FromWebContents(&GetWebContents());
  prerender_manager->SetPrewarmUrlForTesting(prewarm_url);

  content::TestNavigationManager navigation_manager(&GetWebContents(),
                                                    prewarm_url);
  EXPECT_TRUE(prerender_manager->MaybeStartPrewarmSearchResult());
  EXPECT_TRUE(navigation_manager.WaitForRequestStart());
  auto* service = SearchPreloadProgressServiceFactory::GetForProfile(
      Profile::FromBrowserContext(GetWebContents().GetBrowserContext()));
  EXPECT_TRUE(service && service->HasOnGoingSearchPrewarm());

  std::string original_query = "he";
  std::string search_terms = "hello";
  SearchUrls urls = GetSearchUrls(search_terms);

  request_collector().Reset();
  AutocompleteMatch autocomplete_match = CreateSearchSuggestionMatch(
      original_query, search_terms, PrefetchHint::kEnabled,
      PrerenderHint::kDisabled);

  // OnNavigationLikely should return false and not trigger prefetch due to
  // ongoing prewarm.
  const bool is_triggered_prefetch =
      GetSearchPreloadService().OnNavigationLikely(
          1, autocomplete_match,
          omnibox::mojom::NavigationPredictor::kMouseDown, &GetWebContents());
  EXPECT_FALSE(is_triggered_prefetch);

  // Verify that prefetch did not start.
  EXPECT_EQ(0, request_collector().CountByPath(urls.prefetch_on_press));

  // Release the prewarm headers.
  navigation_manager.ResumeNavigation();
  EXPECT_TRUE(navigation_manager.WaitForNavigationFinished());
  EXPECT_FALSE(service && service->HasOnGoingSearchPrewarm());

  // OnNavigationLikely doesn't have a retry mechanism for now, so it should
  // still be not triggered.
  EXPECT_EQ(0, request_collector().CountByPath(urls.prefetch_on_press));

  uma_tester.ExpectUma(
      "Omnibox.DsePreload.SignalResult.OnPress.Prefetch",
      {SearchPreloadSignalResult::kNotTriggeredThrottledByPrewarm});
}

// Verifies that the No-Vary-Search hint is persisted to Prefs.
//
// Scenario:
//
// 1. Trigger a prefetch request for a search suggestion.
// 2. Receive a prefetch response containing a No-Vary-Search header.
// 3. Verify that the No-Vary-Search hint is saved to the profile's
//    preferences.
IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest, PersistNoVarySearchHint) {
  SetUpTemplateURLService();

  std::string pref_val = GetProfile().GetPrefs()->GetString(
      prefs::kSearchPreloadNoVarySearchHintCache);
  EXPECT_TRUE(pref_val.empty());

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  std::string original_query = "he";
  std::string search_terms = "hello";
  SearchUrls urls = GetSearchUrls(search_terms);

  {
    content::test::TestPrefetchWatcher watcher;
    ChangeAutocompleteResult(original_query, search_terms,
                             PrefetchHint::kEnabled, PrerenderHint::kDisabled);
    watcher.WaitUntilPrefetchResponseCompleted(std::nullopt,
                                               urls.prefetch_on_suggest);
  }

  pref_val = GetProfile().GetPrefs()->GetString(
      prefs::kSearchPreloadNoVarySearchHintCache);
  EXPECT_EQ(pref_val, R"(key-order, params, except=("q"))");
}

// Verifies that the No-Vary-Search hint is loaded from Prefs.
//
// Scenario:
//
// 1. Create a new profile and set a No-Vary-Search hint in its preferences.
// 2. Initialize `SearchPreloadService` for this profile.
// 3. Verify that the No-Vary-Search hint cache is initialized with the value
//    from Prefs.
IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest,
                       LoadNoVarySearchHintFromPrefs) {
  std::string pref_val = R"(key-order, params, except=("q"))";

  TestingProfile::Builder builder;
  builder.AddTestingFactory(
      TemplateURLServiceFactory::GetInstance(),
      base::BindRepeating(&TemplateURLServiceFactory::BuildInstanceFor));
  std::unique_ptr<TestingProfile> new_profile = builder.Build();
  new_profile->GetPrefs()->SetString(prefs::kSearchPreloadNoVarySearchHintCache,
                                     pref_val);

  SearchPreloadService* new_service =
      SearchPreloadServiceFactory::GetForProfile(new_profile.get());
  ASSERT_TRUE(new_service);

  std::optional<net::HttpNoVarySearchData> expected =
      ParseNoVarySearchData(pref_val);
  ASSERT_TRUE(expected.has_value());
  EXPECT_EQ(new_service->GetNoVarySearchDataCacheForTesting(), expected);
}

// Browser tests for Search Preload with initial No-Vary-Search hint.
class SearchPreloadBrowserTest_InitialNoVarySearch
    : public SearchPreloadBrowserTestBase {
  void InitFeatures(
      base::test::ScopedFeatureList& scoped_feature_list) override {
    scoped_feature_list.InitWithFeaturesAndParameters(
        {
            {
                features::kDsePreload2,
                {
                    {"kDsePreload2DeviceMemoryThresholdMiB", "0"},
                    {"dse_preload2_initial_no_vary_search_hint",
                     "key-order, params, except=(\"q\")"},
                },
            },
        },
        /*disabled_features=*/{});
  }
};

// Verifies that the initial No-Vary-Search hint is loaded from the feature
// parameter.
IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest_InitialNoVarySearch,
                       InitialHintLoaded) {
  std::optional<net::HttpNoVarySearchData> expected =
      ParseNoVarySearchData(R"(key-order, params, except=("q"))");
  ASSERT_TRUE(expected.has_value());
  EXPECT_EQ(GetSearchPreloadService().GetNoVarySearchDataCacheForTesting(),
            expected);
}

// Verifies that the No-Vary-Search hint in Prefs is prioritized over the
// feature parameter.
IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest_InitialNoVarySearch,
                       PrefsPrioritizedOverFeatureParam) {
  std::string pref_val = R"(key-order, params, except=("a"))";

  TestingProfile::Builder builder;
  builder.AddTestingFactory(
      TemplateURLServiceFactory::GetInstance(),
      base::BindRepeating(&TemplateURLServiceFactory::BuildInstanceFor));
  std::unique_ptr<TestingProfile> new_profile = builder.Build();
  new_profile->GetPrefs()->SetString(prefs::kSearchPreloadNoVarySearchHintCache,
                                     pref_val);

  SearchPreloadService* new_service =
      SearchPreloadServiceFactory::GetForProfile(new_profile.get());
  ASSERT_TRUE(new_service);

  std::optional<net::HttpNoVarySearchData> expected =
      ParseNoVarySearchData(pref_val);
  ASSERT_TRUE(expected.has_value());
  EXPECT_EQ(new_service->GetNoVarySearchDataCacheForTesting(), expected);
}

// Verifies that the No-Vary-Search hint cache (both in memory and Prefs)
// is cleared when the Default Search Provider changes.
//
// Scenario:
//
// 1. Initialize `TemplateURLService` and Default Search Provider.
// 2. Set a No-Vary-Search hint cache in both memory and Prefs.
// 3. Change the Default Search Provider to a different one.
// 4. Verify that the No-Vary-Search hint cache is cleared.
IN_PROC_BROWSER_TEST_F(
    SearchPreloadBrowserTest,
    ClearNoVarySearchDataCacheOnDefaultSearchProviderChange) {
  SetUpTemplateURLService();

  // Set initial No-Vary-Search hint cache in Prefs and memory.
  std::string pref_val = R"(key-order, params, except=("q"))";
  auto& pref_service = *GetProfile().GetPrefs();
  pref_service.SetString(prefs::kSearchPreloadNoVarySearchHintCache, pref_val);

  SearchPreloadService& search_preload_service = GetSearchPreloadService();
  std::optional<net::HttpNoVarySearchData> nvs_data =
      ParseNoVarySearchData(pref_val);
  ASSERT_TRUE(nvs_data.has_value());
  search_preload_service.SetNoVarySearchDataCacheForTesting(nvs_data.value());

  // Verify initial state.
  EXPECT_EQ(search_preload_service.GetNoVarySearchDataCacheForTesting(),
            nvs_data);
  EXPECT_EQ(pref_service.GetString(prefs::kSearchPreloadNoVarySearchHintCache),
            pref_val);

  // Change DSP to a new one.
  TemplateURLService* model =
      TemplateURLServiceFactory::GetForProfile(&GetProfile());
  ASSERT_TRUE(model);

  TemplateURLData data;
  data.SetShortName(u"new_dse");
  data.SetKeyword(data.short_name());
  data.SetURL("https://new.dse.com/search?q={searchTerms}");
  TemplateURL* new_template_url =
      model->Add(std::make_unique<TemplateURL>(data));
  ASSERT_TRUE(new_template_url);

  // This should trigger OnTemplateURLServiceChanged.
  model->SetUserSelectedDefaultSearchProvider(new_template_url);

  // Verify that the No-Vary-Search hint cache is cleared in both memory and
  // Prefs.
  EXPECT_FALSE(
      search_preload_service.GetNoVarySearchDataCacheForTesting().has_value());
  EXPECT_TRUE(pref_service.GetString(prefs::kSearchPreloadNoVarySearchHintCache)
                  .empty());
}

class SearchPreloadBrowserTest_OnPressIgnoreSaverModesEnabled
    : public SearchPreloadBrowserTestBase {
 public:
  void InitFeatures(
      base::test::ScopedFeatureList& scoped_feature_list) override {
    scoped_feature_list.InitWithFeaturesAndParameters(
        {
            {
                extensions_features::kForceWebRequestProxyForTest,
                {},
            },
            {
                features::kDsePreload2,
                {
                    {"kDsePreload2DeviceMemoryThresholdMiB", "0"},
                },
            },
            {
                features::kDsePreload2OnPress,
                {
                    {"kDsePreload2OnPressMouseDown", "true"},
                    {"kDsePreload2OnPressUpOrDownArrowButton", "true"},
                    {"kDsePreload2OnPressTouchDown", "true"},
                    {"kDsePreload2OnPressIgnoreSaverModes", "true"},
                },
            },
        },
        /*disabled_features=*/{});
  }
};

// `OnNavigationLikely()` ignores battery saver mode if
// `kDsePreload2IgnoreSaverModesOnPress` is enabled.
IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest_OnPressIgnoreSaverModesEnabled,
                       OnNavigationLikely_IgnoreSaverModes) {
  HistogramTesterWrapper uma_tester;
  battery::OverrideIsBatterySaverEnabledForTesting(true);
  base::ScopedClosureRunner reset_battery_saver(
      base::BindOnce(&battery::ResetIsBatterySaverEnabledForTesting));

  SetUpTemplateURLService(/*prefetch_likely_navigations=*/true);
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  std::string original_query = "he";
  std::string search_terms = "hello";
  SearchUrls urls = GetSearchUrls(search_terms);

  {
    content::test::TestPrefetchWatcher watcher;

    AutocompleteMatch autocomplete_match = CreateSearchSuggestionMatch(
        original_query, search_terms, PrefetchHint::kEnabled,
        PrerenderHint::kDisabled);

    const bool is_triggered_prefetch =
        GetSearchPreloadService().OnNavigationLikely(
            1, autocomplete_match,
            omnibox::mojom::NavigationPredictor::kMouseDown, &GetWebContents());
    ASSERT_TRUE(is_triggered_prefetch);

    watcher.WaitUntilPrefetchResponseCompleted(std::nullopt,
                                               urls.prefetch_on_press);
  }

  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_press));

  // Navigate.
  NavigateAndWaitFCP(urls.navigation);
  NavigateAwayToRecordHistogram();

  // Prefetch is used.
  EXPECT_EQ(1, request_collector().CountByPath(urls.prefetch_on_press));
  EXPECT_EQ(0, request_collector().CountByPath(urls.navigation));

  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnPress.Prefetch",
                       {SearchPreloadSignalResult::kPrefetchTriggered});
}

// `OnNavigationLikely()` with `kUpOrDownArrowButton` does not ignore battery
// saver mode even if `kDsePreload2IgnoreSaverModesOnPress` is enabled.
IN_PROC_BROWSER_TEST_F(SearchPreloadBrowserTest_OnPressIgnoreSaverModesEnabled,
                       OnNavigationLikely_ArrowButton_DoesntIgnoreSaverModes) {
  HistogramTesterWrapper uma_tester;
  battery::OverrideIsBatterySaverEnabledForTesting(true);
  base::ScopedClosureRunner reset_battery_saver(
      base::BindOnce(&battery::ResetIsBatterySaverEnabledForTesting));

  SetUpTemplateURLService(/*prefetch_likely_navigations=*/true);
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  std::string original_query = "he";
  std::string search_terms = "hello";
  SearchUrls urls = GetSearchUrls(search_terms);

  AutocompleteMatch autocomplete_match = CreateSearchSuggestionMatch(
      original_query, search_terms, PrefetchHint::kEnabled,
      PrerenderHint::kDisabled);

  const bool is_triggered_prefetch =
      GetSearchPreloadService().OnNavigationLikely(
          1, autocomplete_match,
          omnibox::mojom::NavigationPredictor::kUpOrDownArrowButton,
          &GetWebContents());
  ASSERT_TRUE(is_triggered_prefetch);

  base::RunLoop().RunUntilIdle();

  // Navigate.
  NavigateAndWaitFCP(urls.navigation);
  NavigateAwayToRecordHistogram();

  // Up or down arrow key predictions have lower confidence than mouse/touch
  // down, so saver modes are not ignored and no prefetch request is sent.
  EXPECT_EQ(0, request_collector().CountByPath(urls.prefetch_on_press));
  EXPECT_EQ(1, request_collector().CountByPath(urls.navigation));

  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnPress.Prefetch",
                       {SearchPreloadSignalResult::kPrefetchTriggered});
}

class SearchPreloadBrowserTest_OnPressIgnoreSaverModesDisabled
    : public SearchPreloadBrowserTestBase {
 public:
  void InitFeatures(
      base::test::ScopedFeatureList& scoped_feature_list) override {
    scoped_feature_list.InitWithFeaturesAndParameters(
        {
            {
                extensions_features::kForceWebRequestProxyForTest,
                {},
            },
            {
                features::kDsePreload2,
                {
                    {"kDsePreload2DeviceMemoryThresholdMiB", "0"},
                },
            },
            {
                features::kDsePreload2OnPress,
                {
                    {"kDsePreload2OnPressMouseDown", "true"},
                    {"kDsePreload2OnPressUpOrDownArrowButton", "true"},
                    {"kDsePreload2OnPressTouchDown", "true"},
                    {"kDsePreload2OnPressIgnoreSaverModes", "false"},
                },
            },
        },
        /*disabled_features=*/{});
  }
};

// `OnNavigationLikely()` does not ignore battery saver mode if
// `kDsePreload2IgnoreSaverModesOnPress` is disabled.
IN_PROC_BROWSER_TEST_F(
    SearchPreloadBrowserTest_OnPressIgnoreSaverModesDisabled,
    OnNavigationLikely_DoesntIgnoreSaverModesWhenFeatureDisabled) {
  HistogramTesterWrapper uma_tester;
  battery::OverrideIsBatterySaverEnabledForTesting(true);
  base::ScopedClosureRunner reset_battery_saver(
      base::BindOnce(&battery::ResetIsBatterySaverEnabledForTesting));

  SetUpTemplateURLService(/*prefetch_likely_navigations=*/true);
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  std::string original_query = "he";
  std::string search_terms = "hello";
  SearchUrls urls = GetSearchUrls(search_terms);

  AutocompleteMatch autocomplete_match = CreateSearchSuggestionMatch(
      original_query, search_terms, PrefetchHint::kEnabled,
      PrerenderHint::kDisabled);

  const bool is_triggered_prefetch =
      GetSearchPreloadService().OnNavigationLikely(
          1, autocomplete_match,
          omnibox::mojom::NavigationPredictor::kMouseDown, &GetWebContents());
  ASSERT_TRUE(is_triggered_prefetch);

  base::RunLoop().RunUntilIdle();

  // Navigate.
  NavigateAndWaitFCP(urls.navigation);
  NavigateAwayToRecordHistogram();

  // `OnNavigationLikely()` requested `WebContents::StartPrefetch()`. However,
  // because `kDsePreload2IgnoreSaverModesOnPress` is disabled and battery saver
  // mode is active, `PrefetchService` marks the request ineligible and does
  // not dispatch any network request.
  EXPECT_EQ(0, request_collector().CountByPath(urls.prefetch_on_press));
  EXPECT_EQ(1, request_collector().CountByPath(urls.navigation));

  // `SearchPreloadSignalResult` records that the preload API was triggered by
  // the signal rather than whether the network request was actually dispatched.
  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnPress.Prefetch",
                       {SearchPreloadSignalResult::kPrefetchTriggered});
}

// `OnSuggest` does not trigger prefetch nor prerender under battery saver mode.
//
// Note that the behavior doesn't depend on OnPressIgnoreSaverModes.
IN_PROC_BROWSER_TEST_F(
    SearchPreloadBrowserTest_OnPressIgnoreSaverModesDisabled,
    OnSuggest_DoesntTriggerPrefetchNorPrerenderUnderBatterySaver) {
  HistogramTesterWrapper uma_tester;
  battery::OverrideIsBatterySaverEnabledForTesting(true);
  base::ScopedClosureRunner reset_battery_saver(
      base::BindOnce(&battery::ResetIsBatterySaverEnabledForTesting));

  SetUpTemplateURLService(/*prefetch_likely_navigations=*/true);
  SetUpSearchPreloadService({
      .no_vary_search_data_cache = R"(key-order, params, except=("q"))",
  });

  ASSERT_TRUE(content::NavigateToURL(
      &GetWebContents(), embedded_test_server()->GetURL("/empty.html")));

  std::string original_query = "he";
  std::string search_terms = "hello";
  SearchUrls urls = GetSearchUrls(search_terms);

  ChangeAutocompleteResult(original_query, search_terms, PrefetchHint::kEnabled,
                           PrerenderHint::kEnabled);

  // Neither prefetch nor prerender dispatches network requests because battery
  // saver mode is active. `PrefetchService` marks the prefetch ineligible, and
  // the prerender navigation is aborted due to the unavailable prefetch.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(0, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_FALSE(prerender_helper().GetHostForUrl(urls.prerender));

  // Navigate.
  NavigateAndWaitFCP(urls.navigation);
  NavigateAwayToRecordHistogram();

  EXPECT_EQ(0, request_collector().CountByPath(urls.prefetch_on_suggest));
  EXPECT_EQ(1, request_collector().CountByPath(urls.navigation));

  // `SearchPreloadSignalResult` records that preload trigger requests were
  // issued for both prefetch and prerender upon the suggestion signal.
  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch",
                       {SearchPreloadSignalResult::kPrefetchTriggered});
  uma_tester.ExpectUma("Omnibox.DsePreload.SignalResult.OnSuggest.Prerender",
                       {SearchPreloadSignalResult::kPrerenderTriggered});
}

// Test suite for `kDsePreload2SuppressForUnsupportedSearchMode`.
//
// Tests that preloading is suppressed for unsupported search modes (e.g.
// udm=50, AIM).
class SearchPreloadBrowserTest_SuppressForUnsupportedSearchMode
    : public SearchPreloadBrowserTestBase {
 public:
  SearchPreloadBrowserTest_SuppressForUnsupportedSearchMode() = default;

  void InitFeatures(
      base::test::ScopedFeatureList& scoped_feature_list) override {
    scoped_feature_list.InitWithFeaturesAndParameters(
        {{features::kDsePreload2, {}},
         {features::kDsePreload2SuppressForUnsupportedSearchMode,
          {{"unsupported_search_prefetch_modes", "udm=50,foo=bar"}}}},
        {});
  }
};

// Tests that preloading is not triggered for search URLs with unsupported query
// parameters.
IN_PROC_BROWSER_TEST_F(
    SearchPreloadBrowserTest_SuppressForUnsupportedSearchMode,
    UnsupportedModeFromURLNotPreloaded) {
  SetUpTemplateURLService();

  std::string search_terms = "search";
  AutocompleteMatch match = CreateSearchSuggestionMatch(
      search_terms, search_terms, PrefetchHint::kEnabled,
      PrerenderHint::kDisabled);
  match.destination_url = GetSearchUrl(search_terms, UrlType::kReal);
  std::string new_query =
      std::string(match.destination_url.query()) + "&udm=50";
  GURL::Replacements replacements;
  replacements.SetQueryStr(new_query);
  match.destination_url = match.destination_url.ReplaceComponents(replacements);

  AutocompleteResult result;
  result.AppendMatches({match});

  GetSearchPreloadService().OnAutocompleteResultChanged(&GetWebContents(),
                                                        result);

  EXPECT_EQ(request_collector().CountByPath(match.destination_url), 0);
  histogram_tester().ExpectBucketCount(
      "Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch",
      SearchPreloadSignalResult::kNotTriggeredUnsupportedSearchMode, 1);
}

// Tests that preloading is not triggered for AIM suggestions.
IN_PROC_BROWSER_TEST_F(
    SearchPreloadBrowserTest_SuppressForUnsupportedSearchMode,
    AimSuggestionNotPreloaded) {
  SetUpTemplateURLService();

  std::string search_terms = "search";
  AutocompleteMatch match = CreateSearchSuggestionMatch(
      search_terms, search_terms, PrefetchHint::kEnabled,
      PrerenderHint::kDisabled);
  omnibox::SuggestTemplateInfo suggest_template;
  (*suggest_template.mutable_default_search_parameters())["udm"] = "50";
  match.suggest_template = suggest_template;

  AutocompleteResult result;
  result.AppendMatches({match});

  GetSearchPreloadService().OnAutocompleteResultChanged(&GetWebContents(),
                                                        result);

  EXPECT_EQ(request_collector().CountByPath(match.destination_url), 0);
  histogram_tester().ExpectBucketCount(
      "Omnibox.DsePreload.SignalResult.OnSuggest.Prefetch",
      SearchPreloadSignalResult::kNotTriggeredUnsupportedSearchMode, 1);
}

}  // namespace
