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

#include <algorithm>
#include <memory>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/simple_test_clock.h"
#include "build/build_config.h"
#include "chrome/browser/captive_portal/captive_portal_service_factory.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/extensions/api/settings_private/generated_prefs.h"
#include "chrome/browser/interstitials/security_interstitial_page_test_utils.h"
#include "chrome/browser/preloading/scoped_prewarm_feature_list.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/advanced_protection_status_manager.h"
#include "chrome/browser/safe_browsing/advanced_protection_status_manager_factory.h"
#include "chrome/browser/safe_browsing/test_safe_browsing_service.h"
#include "chrome/browser/ssl/ask_before_http_dialog_controller.h"
#include "chrome/browser/ssl/chrome_security_blocking_page_factory.h"
#include "chrome/browser/ssl/chrome_security_state_util.h"
#include "chrome/browser/ssl/generated_https_first_mode_pref.h"
#include "chrome/browser/ssl/https_first_mode_settings_tracker.h"
#include "chrome/browser/ssl/https_upgrades_interceptor.h"
#include "chrome/browser/ssl/https_upgrades_navigation_throttle.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/location_bar/location_bar.h"
#include "chrome/browser/ui/omnibox/omnibox_controller.h"
#include "chrome/browser/ui/omnibox/omnibox_view.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/captive_portal/content/captive_portal_service.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/embedder_support/pref_names.h"
#include "components/omnibox/browser/omnibox_client.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/core/browser/db/fake_database_manager.h"
#include "components/safe_browsing/core/common/threat_enums.h"
#include "components/security_interstitials/content/stateful_ssl_host_state_delegate.h"
#include "components/security_interstitials/core/features.h"
#include "components/security_interstitials/core/https_only_mode_metrics.h"
#include "components/security_interstitials/core/metrics_helper.h"
#include "components/site_engagement/content/site_engagement_service.h"
#include "components/strings/grit/components_strings.h"
#include "components/tabs/public/tab_interface.h"
#include "components/ukm/test_ukm_recorder.h"
#include "components/variations/active_field_trials.h"
#include "components/variations/hashing.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/content_mock_cert_verifier.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/url_loader_interceptor.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_util.h"
#include "net/test/cert_test_util.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "net/test/test_data_directory.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_source.h"
#include "services/network/public/cpp/ip_address_space_overrides_test_utils.h"
#include "services/network/public/cpp/network_switches.h"
#include "services/network/public/cpp/url_loader_completion_status.h"
#include "services/network/public/mojom/ip_address_space.mojom.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/url_constants.h"

namespace {

[[nodiscard]] bool NavigateToURLWithLinkTransition(
    content::WebContents* web_contents,
    const GURL& url,
    const GURL& expected_commit_url) {
  content::TestNavigationObserver nav_observer(web_contents, 1);
  content::NavigationController::LoadURLParams params(url);
  params.transition_type = ui::PAGE_TRANSITION_LINK;
  web_contents->GetController().LoadURLWithParams(params);
  nav_observer.Wait();
  return nav_observer.last_navigation_succeeded() &&
         web_contents->GetLastCommittedURL() == expected_commit_url;
}

[[nodiscard]] bool NavigateToURLWithLinkTransition(
    content::WebContents* web_contents,
    const GURL& url) {
  return NavigateToURLWithLinkTransition(web_contents, url, url);
}

void NavigateToURLWithLinkTransitionBlockUntilNavigationsComplete(
    content::WebContents* web_contents,
    const GURL& url,
    int number_of_navigations) {
  content::NavigationController::LoadURLParams params(url);
  params.transition_type = ui::PAGE_TRANSITION_LINK;
  content::NavigateToURLBlockUntilNavigationsComplete(web_contents, params,
                                                      number_of_navigations);
}

}  // namespace

using chrome_browser_interstitials::
    DontProceedThroughHttpsFirstModeInterstitial;
using chrome_browser_interstitials::GetHFMInterstitialType;
using chrome_browser_interstitials::HFMInterstitialType;
using chrome_browser_interstitials::IsInterstitialDisplayingText;
using chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial;
using chrome_browser_interstitials::IsShowingInterstitial;
using chrome_browser_interstitials::ProceedThroughHttpsFirstModeInterstitial;
using security_interstitials::https_only_mode::BlockingResult;
using security_interstitials::https_only_mode::Event;
using security_interstitials::https_only_mode::FallbackReason;
using security_interstitials::https_only_mode::InterstitialReason;
using security_interstitials::https_only_mode::kEventHistogram;
using security_interstitials::https_only_mode::
    kEventHistogramWithEngagementHeuristic;
using security_interstitials::https_only_mode::kInterstitialReasonHistogram;
using security_interstitials::https_only_mode::
    kNavigationRequestSecurityLevelHistogram;
using security_interstitials::https_only_mode::
    kSiteEngagementHeuristicAccumulatedHostCountHistogram;
using security_interstitials::https_only_mode::
    kSiteEngagementHeuristicEnforcementDurationHistogram;
using security_interstitials::https_only_mode::
    kSiteEngagementHeuristicHostCountHistogram;
using security_interstitials::https_only_mode::
    kSiteEngagementHeuristicStateHistogram;
using security_interstitials::https_only_mode::NavigationRequestSecurityLevel;
using security_interstitials::https_only_mode::SiteEngagementHeuristicState;
using UkmEntry = ukm::builders::HttpsFirstMode_Event;

// Many of the following tests have only minor variations for HTTPS-First Mode
// vs. HTTPS-Upgrades. These get parameterized so the tests run under both
// versions on their own as well as when both HTTPS Upgrades and HTTPS-First
// Mode are enabled (to test any interactions between the two upgrade modes).
// HTTPS-Upgrades is now enabled by default, so all of these variations build
// on top of that baseline.
//
// Quick summary of all features tested here:
// * HTTPS-Upgrades:
//     Automatically upgrades main frame navigations to HTTPS. Silently falls
//     back to HTTP on failure.
// * HTTPS First Mode:
//     Automatically upgrades main frame navigations to HTTPS. Shows an
//     interstitial on failure.
// * HTTPS First Mode With Site Engagement:
//     Automatically enables HTTPS First Mode for sites that are visited mainly
//     over HTTPS.
// * HTTPS First Mode for Typically Secure Users
//     Automatically enables HTTPS First Mode for users that mainly visit HTTPS
//     sites.
// * HTTPS First Mode in Incognito:
//     Automatically enables HTTPS First Mode in Incognito windows.
// * HTTPS First Balanced Mode:
//     Enables HTTPS First Mode like full HFM, but exempt navigations that are
//     likely to fail.
// * HTTPS First Balanced Mode with old UI:
//     Same as Balanced Mode, but uses the old UI for HTTPS-First
//     interstitials (all other variations use the default UI).
//
enum class HttpsUpgradesTestType {
  // Enables the HFM pref.
  kHttpsFirstModeOnly,

  // Enables HFM with Site Engagement heuristic.
  kHttpsFirstModeWithSiteEngagement,

  // Enables HFM for Typically Secure Users.
  kHttpsFirstModeForTypicallySecureUsers,

  // Enables HFM with Site Engagement and HFM for Typically Secure Users (both
  // automatically enable HFM). Disables BalancedModeByDefault as that case is
  // already tested by kHttpsFirstBalancedMode.
  kAllAutoHFM,

  // Enables HFM in Incognito mode. Runs testcases inside an Incognito
  // window.
  kHttpsFirstModeIncognito,

  // Enables HFM in balanced mode.
  kHttpsFirstBalancedMode,

  // Enables HFM for Advanced Protection users.
  kHttpsFirstModeAdvancedProtection,

  // Enables HFM pref, HFM with Site Engagement heuristic, HFM for typically
  // secure users, HFM in incognito, and balanced HFM feature flags.
  kAll,

  // Enables HFM in balanced mode but with the old interstitial UI.
  kHttpsFirstBalancedModeWithOldUi,

  // Disables HFM pref, HFM with Site Engagement heuristic, the HFM for
  // typically secure users feature, and the HFM in Incognito feature.
  kNone,

  // HttpsUpgradesBrowserTest tests don't run in these modes. They are used to
  // instantiate individual tests instead:

  // Enables HFM with Site Engagement heuristic without Balanced Mode. This
  // should be a no-op.
  kHttpsFirstModeWithSiteEngagementWithoutBalancedMode,
};

// Stores the number of times the HTTPS-First Mode interstitial is shown for the
// given reason.
struct ExpectedInterstitialReasons {
  // The number of times the interstitial was shown because the HFM pref was
  // enabled.
  size_t pref = 0;
  // The number of times the interstitial was shown because of the Typically
  // Secure User heuristic.
  size_t typically_secure_user = 0;
  // The number of times the interstitial was shown because of being in balanced
  // mode.
  size_t balanced = 0;
  // The number of times the interstitial was shown because of Advanced
  // Protection.
  size_t advanced_protection = 0;
};

// A very low site engagement score.
constexpr int kLowSiteEngagementScore = 1;
// A very high site engagement score.
constexpr int kHighSiteEnagementScore = 99;

// Tests for HTTPS-Upgrades and the v2 implementation of HTTPS-First Mode.
class HttpsUpgradesBrowserTest
    : public testing::WithParamInterface<HttpsUpgradesTestType>,
      public InProcessBrowserTest {
 public:
  HttpsUpgradesBrowserTest() = default;
  ~HttpsUpgradesBrowserTest() override = default;

  void SetUp() override {
    // HFM heuristics are disabled on enterprise managed machines, so some of
    // the tests may fail on bots. Explicitly set management status to false.
    // Some of the tests check enterprise policies, so this configuration is
    // unusual because non-enterprise machines are unlikely to have an
    // enterprise allowlist, but it's good for test coverage.
    ChromeSecurityBlockingPageFactory::SetEnterpriseManagedForTesting(false);

    // HFM is controlled by a pref (configured in SetUpOnMainThread).
    switch (https_upgrades_test_type()) {
      case HttpsUpgradesTestType::kHttpsFirstModeOnly:
        feature_list_.InitWithFeatures(
            /*enabled_features=*/{security_interstitials::features::
                                      kHttpsFirstDialogUi},
            /*disabled_features=*/{
                features::kHttpsFirstModeV2ForEngagedSites,
                features::kHttpsFirstModeV2ForTypicallySecureUsers});
        break;

      case HttpsUpgradesTestType::kHttpsFirstModeWithSiteEngagement:
        // HFM pref is disabled in SetUpOnMainThread.
        feature_list_.InitWithFeatures(
            /*enabled_features=*/{features::kHttpsFirstModeV2ForEngagedSites,
                                  features::kHttpsFirstBalancedMode,
                                  security_interstitials::features::
                                      kHttpsFirstDialogUi},
            /*disabled_features=*/{
                features::kHttpsFirstModeV2ForTypicallySecureUsers,
                features::kHttpsFirstBalancedModeAutoEnable});
        break;

      case HttpsUpgradesTestType::
          kHttpsFirstModeWithSiteEngagementWithoutBalancedMode:
        // HFM pref is disabled in SetUpOnMainThread.
        feature_list_.InitWithFeatures(
            /*enabled_features=*/{features::kHttpsFirstModeV2ForEngagedSites,
                                  security_interstitials::features::
                                      kHttpsFirstDialogUi},
            /*disabled_features=*/{
                features::kHttpsFirstModeV2ForTypicallySecureUsers,
                features::kHttpsFirstBalancedMode});
        break;

      case HttpsUpgradesTestType::kHttpsFirstModeForTypicallySecureUsers:
        // HFM pref is disabled in SetUpOnMainThread.
        feature_list_.InitWithFeatures(
            /*enabled_features=*/
            {features::kHttpsFirstModeV2ForTypicallySecureUsers,
             features::kHttpsFirstBalancedMode,
             security_interstitials::features::kHttpsFirstDialogUi},
            /*disabled_features=*/{
                features::kHttpsFirstModeV2ForEngagedSites,
                features::kHttpsFirstBalancedModeAutoEnable});
        break;

      case HttpsUpgradesTestType::kAllAutoHFM:
        // HFM pref is disabled in SetUpOnMainThread.
        feature_list_.InitWithFeatures(
            /*enabled_features=*/
            {features::kHttpsFirstModeV2ForTypicallySecureUsers,
             features::kHttpsFirstModeV2ForEngagedSites,
             features::kHttpsFirstBalancedMode,
             security_interstitials::features::kHttpsFirstDialogUi},
            /*disabled_features=*/{
                features::kHttpsFirstBalancedModeAutoEnable});
        break;

      case HttpsUpgradesTestType::kHttpsFirstModeIncognito:
        feature_list_.InitWithFeatures(
            /*enabled_features=*/{features::kHttpsFirstModeIncognito,
                                  security_interstitials::features::
                                      kHttpsFirstDialogUi},
            /*disabled_features=*/{});
        break;

      case HttpsUpgradesTestType::kHttpsFirstBalancedMode:
        feature_list_.InitWithFeatures(
            /*enabled_features=*/{features::kHttpsFirstBalancedMode,
                                  features::kHttpsFirstBalancedModeAutoEnable,
                                  security_interstitials::features::
                                      kHttpsFirstDialogUi},
            /*disabled_features=*/{
                features::kHttpsFirstModeV2ForTypicallySecureUsers,
                features::kHttpsFirstModeV2ForEngagedSites});
        break;

      case HttpsUpgradesTestType::kHttpsFirstModeAdvancedProtection:
        feature_list_.InitWithFeatures(
            /*enabled_features=*/
            {features::kHttpsFirstModeForAdvancedProtectionUsers,
             features::kHttpsFirstBalancedModeAutoEnable,
             security_interstitials::features::kHttpsFirstDialogUi},
            /*disabled_features=*/{
                features::kHttpsFirstModeV2ForTypicallySecureUsers,
                features::kHttpsFirstModeV2ForEngagedSites});
        break;

      // Enable HFM, HFM with Site Engagement heuristic, HFM for typically
      // secure users, and HFM in Incognito.
      case HttpsUpgradesTestType::kAll:
        // HFM pref is enabled in SetUpOnMainThread.
        feature_list_.InitWithFeatures(
            /*enabled_features=*/
            {
                features::kHttpsFirstModeV2ForEngagedSites,
                features::kHttpsFirstModeV2ForTypicallySecureUsers,
                features::kHttpsFirstModeForAdvancedProtectionUsers,
                features::kHttpsFirstModeIncognito,
                features::kHttpsFirstBalancedMode,
                features::kHttpsFirstBalancedModeAutoEnable,
                security_interstitials::features::kHttpsFirstDialogUi,
            },
            /*disabled_features=*/{});
        break;

      case HttpsUpgradesTestType::kHttpsFirstBalancedModeWithOldUi:
        feature_list_.InitWithFeatures(
            /*enabled_features=*/{features::kHttpsFirstBalancedMode,
                                  features::kHttpsFirstBalancedModeAutoEnable},
            /*disabled_features=*/{
                features::kHttpsFirstModeV2ForTypicallySecureUsers,
                features::kHttpsFirstModeV2ForEngagedSites,
                security_interstitials::features::kHttpsFirstDialogUi});
        break;

      // Disable HFM, HFM with Site Engagement heuristic, HFM for Typically
      // Secure Users, and HFM in Incognito. (HFM pref is disabled in
      // SetUpOnMainThread.) This is equivalent to the baseline default of
      // HTTPS-Upgrades.
      case HttpsUpgradesTestType::kNone:
        feature_list_.InitWithFeatures(
            /*enabled_features=*/{},
            /*disabled_features=*/{
                features::kHttpsFirstModeV2ForEngagedSites,
                features::kHttpsFirstModeV2ForTypicallySecureUsers,
                features::kHttpsFirstBalancedMode,
                features::kHttpsFirstBalancedModeAutoEnable,
                features::kHttpsFirstModeForAdvancedProtectionUsers,
                security_interstitials::features::kHttpsFirstDialogUi});
        break;
    }

    InProcessBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    // By default allow all hosts on HTTPS.
    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
    host_resolver()->AddRule("*", "127.0.0.1");

    // Set up "bad-https.com", "bad-https2.com", "nonunique-hostname-bad-https"
    // and "nonunique-hostname-bad-https2" as hostnames with an SSL error.
    // HTTPS upgrades to these hosts will fail.
    scoped_refptr<net::X509Certificate> cert(https_server_.GetCertificate());
    net::CertVerifyResult verify_result;
    verify_result.is_issued_by_known_root = false;
    verify_result.verified_cert = cert;
    verify_result.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;
    mock_cert_verifier_.mock_cert_verifier()->AddResultForCertAndHost(
        cert, "bad-https.com", verify_result,
        net::ERR_CERT_COMMON_NAME_INVALID);
    mock_cert_verifier_.mock_cert_verifier()->AddResultForCertAndHost(
        cert, "www.bad-https.com", verify_result,
        net::ERR_CERT_COMMON_NAME_INVALID);
    mock_cert_verifier_.mock_cert_verifier()->AddResultForCertAndHost(
        cert, "bad-https2.com", verify_result,
        net::ERR_CERT_COMMON_NAME_INVALID);
    mock_cert_verifier_.mock_cert_verifier()->AddResultForCertAndHost(
        cert, "nonunique-hostname-bad-https", verify_result,
        net::ERR_CERT_COMMON_NAME_INVALID);
    mock_cert_verifier_.mock_cert_verifier()->AddResultForCertAndHost(
        cert, "nonunique-hostname-bad-https2", verify_result,
        net::ERR_CERT_COMMON_NAME_INVALID);

    http_server_.AddDefaultHandlers(GetChromeTestDataDir());
    https_server_.AddDefaultHandlers(GetChromeTestDataDir());
    ASSERT_TRUE(http_server_.Start());
    ASSERT_TRUE(https_server_.Start());

    HttpsUpgradesInterceptor::SetHttpsPortForTesting(https_server()->port());
    HttpsUpgradesInterceptor::SetHttpPortForTesting(http_server()->port());

    // Incognito tests swap out the default Browser instance for an Incognito
    // window, and then should behave like kHttpsFirstMode type tests but
    // without enabling the full HFM pref.
    if (https_upgrades_test_type() ==
        HttpsUpgradesTestType::kHttpsFirstModeIncognito) {
      UseIncognitoBrowser();
      SetPref(false);
    }

    if (https_upgrades_test_type() ==
        HttpsUpgradesTestType::kHttpsFirstModeAdvancedProtection) {
      safe_browsing::AdvancedProtectionStatusManagerFactory::GetForProfile(
          browser()->GetProfile())
          ->SetAdvancedProtectionStatusForTesting(true);
    }

    // Only enable the HTTPS-First Mode pref when the test config calls for it.
    // Some of the HFM heuristics check that the preference wasn't set so as
    // not to override user preference (e.g. if the user changed the pref by
    // turning it off from the UI, we don't want to override it).
    if (IsHttpsFirstModePrefEnabled()) {
      SetPref(true);
    }

    if (InBalancedMode()) {
      SetBalancedPref(true);
    }

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

  void TearDownOnMainThread() override {
    browser()->GetProfile()->GetPrefs()->ClearPref(
        prefs::kHttpsOnlyModeEnabled);
    browser()->GetProfile()->GetPrefs()->ClearPref(
        prefs::kHttpsOnlyModeAutoEnabled);
    browser()->GetProfile()->GetPrefs()->ClearPref(
        prefs::kHttpsUpgradeFallbacks);
    browser()->GetProfile()->GetPrefs()->ClearPref(
        prefs::kHttpsUpgradeNavigations);
    browser()->GetProfile()->GetPrefs()->ClearPref(
        prefs::kHttpsFirstBalancedMode);

    HttpsUpgradesInterceptor::SetHttpsPortForTesting(0);
    HttpsUpgradesInterceptor::SetHttpPortForTesting(0);
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    mock_cert_verifier_.SetUpCommandLine(command_line);
  }

  void SetUpInProcessBrowserTestFixture() override {
    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
  }

  void TearDownInProcessBrowserTestFixture() override {
    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
  }

  // Incognito testing support
  //
  // Returns the active Browser for the test type being run.
  Browser* GetBrowser() const {
    return incognito_browser_ ? incognito_browser_.get() : browser();
  }
  // Call to use an Incognito browser rather than the default.
  void UseIncognitoBrowser() {
    ASSERT_EQ(nullptr, incognito_browser_.get());
    incognito_browser_ = CreateIncognitoBrowser();
  }
  bool IsIncognito() const { return incognito_browser_ != nullptr; }
  bool OnlyInBalancedMode() const {
    return https_upgrades_test_type() ==
               HttpsUpgradesTestType::kHttpsFirstBalancedMode ||
           https_upgrades_test_type() ==
               HttpsUpgradesTestType::kHttpsFirstBalancedModeWithOldUi;
  }
  bool InBalancedMode() const {
    return https_upgrades_test_type() ==
               HttpsUpgradesTestType::kHttpsFirstBalancedMode ||
           https_upgrades_test_type() ==
               HttpsUpgradesTestType::kHttpsFirstBalancedModeWithOldUi ||
           https_upgrades_test_type() == HttpsUpgradesTestType::kAll;
  }

 protected:
  HttpsUpgradesTestType https_upgrades_test_type() const { return GetParam(); }

  void SetPref(bool enabled) {
    auto* prefs = browser()->GetProfile()->GetPrefs();
    prefs->SetBoolean(prefs::kHttpsOnlyModeEnabled, enabled);
  }

  bool GetPref() const {
    auto* prefs = browser()->GetProfile()->GetPrefs();
    return prefs->GetBoolean(prefs::kHttpsOnlyModeEnabled);
  }

  void SetBalancedPref(bool enabled) {
    auto* prefs = browser()->GetProfile()->GetPrefs();
    prefs->SetBoolean(prefs::kHttpsFirstBalancedMode, enabled);
  }

  bool GetBalancedPref() const {
    auto* prefs = browser()->GetProfile()->GetPrefs();
    return prefs->GetBoolean(prefs::kHttpsFirstBalancedMode);
  }

  void NavigateAndWaitForFallback(content::WebContents* tab, const GURL& url) {
    // TODO(crbug.com/40248833): With fallback as part of the same navigation,
    // this helper is no longer particularly useful. Consider updating callers.
    NavigateToURLWithLinkTransitionBlockUntilNavigationsComplete(tab, url, 1);
  }

  // Whether HFM is enabled by the UI setting.
  bool IsHttpsFirstModePrefEnabled() const {
    return https_upgrades_test_type() ==
               HttpsUpgradesTestType::kHttpsFirstModeOnly ||
           https_upgrades_test_type() == HttpsUpgradesTestType::kAll;
  }

  // Whether HFM strict mode is enabled.
  bool IsStrictInterstitialEnabledForTest() const {
    return IsHttpsFirstModePrefEnabled() ||
           safe_browsing::AdvancedProtectionStatusManagerFactory::GetForProfile(
               browser()->GetProfile())
               ->IsUnderAdvancedProtection();
  }

  // Whether HFM is enabled for many sites, and thus the tests should run steps
  // that assume the HTTP interstitial will trigger (i.e., for fallback HTTP
  // navigations when HTTPS-First Mode is enabled).
  bool IsHttpsFirstModeInterstitialEnabledAcrossSites() const {
    return IsHttpsFirstModePrefEnabled() || InBalancedMode() || IsIncognito() ||
           safe_browsing::AdvancedProtectionStatusManagerFactory::GetForProfile(
               browser()->GetProfile())
               ->IsUnderAdvancedProtection();
  }

  // Whether HTTPS-First Mode with Site Engagement Heuristic is enabled. When
  // enabled, this feature will enable HFM on sites that have high Site
  // Engagement scores on their HTTPS URLs. HFM with Site Engagement requires
  // HTTPS-Upgrades to be enabled.
  bool IsSiteEngagementHeuristicEnabled() const {
    bool enabled =
        https_upgrades_test_type() ==
            HttpsUpgradesTestType::kHttpsFirstModeWithSiteEngagement ||
        https_upgrades_test_type() == HttpsUpgradesTestType::kAllAutoHFM ||
        https_upgrades_test_type() == HttpsUpgradesTestType::kAll;
    return enabled;
  }

  // Whether automatic HTTPS-First Mode for typically secure users is enabled.
  // When enabled, this feature will enable HFM for users who would see HFM
  // warnings very rarely. HFM for typically secure users requires
  // HTTPS-Upgrades to be enabled.
  bool IsTypicallySecureUserFeatureEnabled() const {
    bool enabled =
        https_upgrades_test_type() ==
            HttpsUpgradesTestType::kHttpsFirstModeForTypicallySecureUsers ||
        https_upgrades_test_type() == HttpsUpgradesTestType::kAllAutoHFM ||
        https_upgrades_test_type() == HttpsUpgradesTestType::kAll;
    return enabled;
  }

  void SetSiteEngagementScore(const GURL& url, double score) {
    site_engagement::SiteEngagementService* service =
        site_engagement::SiteEngagementService::Get(browser()->GetProfile());
    service->ResetBaseScoreForURL(url, score);
    ASSERT_EQ(score, service->GetScore(url));
  }

  // Checks that the HTTPS-First Mode interstitial has been shown for the
  // correct reasons.
  void CheckInterstitialReasonHistogram(
      const ExpectedInterstitialReasons& expected_reasons) {
    histograms()->ExpectTotalCount(
        kInterstitialReasonHistogram,
        expected_reasons.pref + expected_reasons.typically_secure_user +
            expected_reasons.balanced + expected_reasons.advanced_protection);
    histograms()->ExpectBucketCount(kInterstitialReasonHistogram,
                                    static_cast<int>(InterstitialReason::kPref),
                                    expected_reasons.pref);
    histograms()->ExpectBucketCount(
        kInterstitialReasonHistogram,
        static_cast<int>(InterstitialReason::kBalanced),
        expected_reasons.balanced);
    histograms()->ExpectBucketCount(
        kInterstitialReasonHistogram,
        static_cast<int>(InterstitialReason::kTypicallySecureUserHeuristic),
        expected_reasons.typically_secure_user);
    histograms()->ExpectBucketCount(
        kInterstitialReasonHistogram,
        static_cast<int>(InterstitialReason::kAdvancedProtection),
        expected_reasons.advanced_protection);
  }

  // Verifies that an HFM interstitial is shown.
  void ExpectInterstitial(content::WebContents* contents) {
    EXPECT_EQ(HFMInterstitialType::kStandard, GetHFMInterstitialType(contents));
  }

  // Verifies that an HFM interstitial is shown only if the HFM-pref is enabled
  // or we're in balanced mode.
  void ExpectInterstitialOnlyIfPrefIsSetOrInBalancedMode(
      content::WebContents* contents) {
    if (IsHttpsFirstModePrefEnabled() || InBalancedMode()) {
      ExpectInterstitial(contents);
    } else {
      EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
    }
  }

  // Verifies that an HFM interstitial is shown either due to prefs being
  // enabled or typically secure heuristic.
  void MaybeExpectTypicallySecureInterstitial(content::WebContents* contents) {
    if (IsHttpsFirstModePrefEnabled() || InBalancedMode() ||
        IsTypicallySecureUserFeatureEnabled()) {
      // Typically secure interstitial should only be shown iff HFM is not
      // enabled by prefs.
      if (IsTypicallySecureUserFeatureEnabled() &&
          !IsHttpsFirstModePrefEnabled() && !InBalancedMode()) {
        EXPECT_EQ(HFMInterstitialType::kTypicallySecure,
                  GetHFMInterstitialType(contents));
      } else {
        // Otherwise, interstitial is enabled by prefs.
        EXPECT_EQ(HFMInterstitialType::kStandard,
                  GetHFMInterstitialType(contents));
      }
      return;
    }
    // Interstitial isn't enabled by the prefs or heuristic.
    EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
  }

  base::SimpleTestClock* GetTestClock() { return &test_clock_; }

  // Prepare the profile so that HFM can be automatically enabled.
  void SatisfyTypicallySecureHeuristicRequirements() {
    base::SimpleTestClock* clock = GetTestClock();

    // The total engagement score of all sites must be over a certain threshold.
    SetSiteEngagementScore(GURL("https://google.com:12345"), 90);

    // Profile must be old enough.
    browser()->GetProfile()->SetCreationTimeForTesting(clock->Now() -
                                                       base::Days(30));
    hfm_service()->SetClockForTesting(clock);

    // There must be a lot of recorded navigations.
    for (size_t i = 0; i < 1500; i++) {
      hfm_service()->IncrementRecentNavigationCount();
    }
    clock->Advance(base::Days(15));

    // Navigate to an HTTP URL that will upgrade and fall back to HTTP.
    // This will start Typically Secure observation.
    GURL http_url("http://bad-https2.com/simple.html");
    content::WebContents* contents =
        GetBrowser()->tab_strip_model()->GetActiveWebContents();
    NavigateToURLWithLinkTransitionBlockUntilNavigationsComplete(
        contents, http_url, /*number_of_navigations=*/1);
    ExpectInterstitialOnlyIfPrefIsSetOrInBalancedMode(contents);

    // Advance the clock and navigate to an HTTP URL again. This will drop the
    // first fallback event.
    clock->Advance(base::Days(35));
  }

  net::EmbeddedTestServer* http_server() { return &http_server_; }
  net::EmbeddedTestServer* https_server() { return &https_server_; }
  base::HistogramTester* histograms() { return &histograms_; }
  base::test::ScopedFeatureList* feature_list() { return &feature_list_; }

  HttpsFirstModeService* hfm_service() const {
    return HttpsFirstModeServiceFactory::GetForProfile(browser()->GetProfile());
  }

  // Checks that the interstitial UKM has an entry for `url` and `result`.
  void ExpectUKMEntry(
      const GURL& url,
      security_interstitials::https_only_mode::BlockingResult result) {
    auto entries = test_ukm_recorder_->GetEntriesByName(UkmEntry::kEntryName);
    EXPECT_EQ(1u, entries.size());

    test_ukm_recorder_->ExpectEntrySourceHasUrl(entries[0], url);
    test_ukm_recorder_->ExpectEntryMetric(entries[0], "Result",
                                          static_cast<int>(result));
  }

  // Checks that the interstitial UKM has an entry for `url`, `result` and
  // `fallback_reason`.
  void ExpectUKMEntryWithReason(
      const GURL& url,
      security_interstitials::https_only_mode::BlockingResult result,
      security_interstitials::https_only_mode::FallbackReason fallback_reason) {
    auto entries = test_ukm_recorder_->GetEntriesByName(UkmEntry::kEntryName);
    EXPECT_EQ(1u, entries.size());

    test_ukm_recorder_->ExpectEntrySourceHasUrl(entries[0], url);
    test_ukm_recorder_->ExpectEntryMetric(entries[0], "Result",
                                          static_cast<int>(result));
    test_ukm_recorder_->ExpectEntryMetric(entries[0], "FallbackReason",
                                          static_cast<int>(fallback_reason));
  }

  // Checks that the interstitial UKM has no entry.
  void ExpectEmptyUKM() {
    auto entries = test_ukm_recorder_->GetEntriesByName(UkmEntry::kEntryName);
    EXPECT_EQ(0u, entries.size());
  }

  void EnableCaptivePortalDetection(BrowserWindowInterface* browser);

 private:
  // TODO(https://crbug.com/423465927): Explore a better approach to make the
  // existing tests run with the prewarm feature enabled.
  test::ScopedPrewarmFeatureList prewarm_feature_list_{
      test::ScopedPrewarmFeatureList::PrewarmState::kDisabled};
  base::test::ScopedFeatureList feature_list_;
  net::EmbeddedTestServer http_server_{net::EmbeddedTestServer::TYPE_HTTP};
  net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
  content::ContentMockCertVerifier mock_cert_verifier_;
  base::HistogramTester histograms_;
  raw_ptr<Browser, AcrossTasksDanglingUntriaged> incognito_browser_ = nullptr;
  std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;

  // A clock that can be installed with
  // HttpsFirstModeService::SetClockForTesting(). This must outlive the test
  // because HttpsFirstModeService can access it from shutdown tasks that run
  // during PostRunTestOnMainThread().
  base::SimpleTestClock test_clock_;
};

// HttpsUpgradesBrowserTest is NOT instantiated for unusual configurations like
// kHttpsFirstModeWithSiteEngagementWithoutBalancedMode.
INSTANTIATE_TEST_SUITE_P(
    /* no prefix */,
    HttpsUpgradesBrowserTest,
    ::testing::Values(
        HttpsUpgradesTestType::kHttpsFirstModeOnly,
        HttpsUpgradesTestType::kHttpsFirstModeWithSiteEngagement,
        HttpsUpgradesTestType::kHttpsFirstModeForTypicallySecureUsers,
        HttpsUpgradesTestType::kAllAutoHFM,
        HttpsUpgradesTestType::kHttpsFirstModeIncognito,
        HttpsUpgradesTestType::kHttpsFirstBalancedMode,
        HttpsUpgradesTestType::kHttpsFirstModeAdvancedProtection,
        HttpsUpgradesTestType::kAll,
        HttpsUpgradesTestType::kHttpsFirstBalancedModeWithOldUi,
        HttpsUpgradesTestType::kNone),
    // Map param to a human-readable string for better test output.
    [](testing::TestParamInfo<HttpsUpgradesTestType> input_type)
        -> std::string {
      switch (input_type.param) {
        case HttpsUpgradesTestType::kHttpsFirstModeOnly:
          return "HttpsFirstModeOnly";
        case HttpsUpgradesTestType::kHttpsFirstModeWithSiteEngagement:
          return "HttpsFirstModeWithSiteEngagement";
        case HttpsUpgradesTestType::kHttpsFirstModeForTypicallySecureUsers:
          return "HttpsFirstModeForTypicallySecureUsers";
        case HttpsUpgradesTestType::kAllAutoHFM:
          return "AllAutoHFM";
        case HttpsUpgradesTestType::kHttpsFirstModeIncognito:
          return "HttpsFirstModeIncognito";
        case HttpsUpgradesTestType::kHttpsFirstBalancedMode:
          return "HttpsFirstBalancedMode";
        case HttpsUpgradesTestType::kHttpsFirstModeAdvancedProtection:
          return "HttpsFirstModeAdvancedProtection";
        case HttpsUpgradesTestType::kAll:
          return "AllFeatures";
        case HttpsUpgradesTestType::kHttpsFirstBalancedModeWithOldUi:
          return "HttpsFirstBalancedModeWithOldUi";
        case HttpsUpgradesTestType::kNone:
          return "None";
        case HttpsUpgradesTestType::
            kHttpsFirstModeWithSiteEngagementWithoutBalancedMode:
          return "kHttpsFirstModeWithSiteEngagementWithoutBalancedMode";
      }
    });

// If the user navigates to an HTTP URL for a site that supports HTTPS, the
// navigation should end up on the HTTPS version of the URL if upgrading is
// enabled.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       UrlWithHttpScheme_ShouldUpgrade) {
  GURL http_url = http_server()->GetURL("foo.com", "/simple.html");
  GURL https_url = https_server()->GetURL("foo.com", "/simple.html");

  auto* contents = browser()->GetTabStripModel()->GetActiveWebContents();
  EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, http_url, https_url));
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(contents));

  EXPECT_EQ(https_url, contents->GetLastCommittedURL());
  histograms()->ExpectTotalCount(kEventHistogram, 2);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeSucceeded, 1);

  // Also record general request metrics.
  histograms()->ExpectTotalCount(kNavigationRequestSecurityLevelHistogram, 2);
  histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                  NavigationRequestSecurityLevel::kSecure, 1);
  histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                  NavigationRequestSecurityLevel::kUpgraded, 1);

  ExpectEmptyUKM();
}

// If the user navigates to an HTTPS URL for a site that supports HTTPS, the
// navigation should end up on that exact URL.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       UrlWithHttpsScheme_ShouldLoad) {
  GURL https_url = https_server()->GetURL("foo.com", "/simple.html");
  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, https_url));

  // Verify that navigation event metrics were not recorded as the navigation
  // was not upgraded.
  histograms()->ExpectTotalCount(kEventHistogram, 0);

  // General navigation metrics should still be recorded.
  histograms()->ExpectTotalCount(kNavigationRequestSecurityLevelHistogram, 1);
  histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                  NavigationRequestSecurityLevel::kSecure, 1);

  ExpectEmptyUKM();
}

// If the user navigates to a localhost URL, the navigation should end up on
// that exact URL.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, Localhost_ShouldNotUpgrade) {
  GURL localhost_url = http_server()->GetURL("localhost", "/simple.html");
  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, localhost_url));

  // Verify that navigation event metrics were not recorded as the navigation
  // was not upgraded.
  histograms()->ExpectTotalCount(kEventHistogram, 0);

  // Verify that general navigation request metrics were recorded.
  histograms()->ExpectTotalCount(kNavigationRequestSecurityLevelHistogram, 1);
  histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                  NavigationRequestSecurityLevel::kLocalhost,
                                  1);

  ExpectEmptyUKM();
}

// Test that HTTPS Upgrades are skipped for non-unique hostnames, such as
// non-publicly routable (RFC1918/4193) IP addresses, but HTTPS-First Mode
// should still apply.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       NonRoutableIPAddress_ShouldNotUpgrade) {
  // Disable the testing port configuration, as this test doesn't use the
  // EmbeddedTestServer.
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(0);
  HttpsUpgradesInterceptor::SetHttpPortForTesting(0);

  // Set up an interceptor because the test server can't listen on private IPs.
  GURL local_ip_url("http://192.168.0.1/simple.html");
  auto url_loader_interceptor =
      content::URLLoaderInterceptor::ServeFilesFromDirectoryAtOrigin(
          GetChromeTestDataDir().MaybeAsASCII(),
          local_ip_url.GetWithEmptyPath());

  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();

  if (IsStrictInterstitialEnabledForTest()) {
    // HFM should attempt the upgrade, fail, and fallback to the interstitial.
    EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, local_ip_url));
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));

    // Verify that upgrade events were recorded because an upgrade was attempted
    // and failed.
    histograms()->ExpectTotalCount(kEventHistogram, 3);
    histograms()->ExpectBucketCount(
        kEventHistogram,
        security_interstitials::https_only_mode::Event::kUpgradeAttempted, 1);
    histograms()->ExpectBucketCount(
        kEventHistogram,
        security_interstitials::https_only_mode::Event::kUpgradeFailed, 1);
    histograms()->ExpectBucketCount(
        kEventHistogram,
        security_interstitials::https_only_mode::Event::kUpgradeTimedOut, 1);
    histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                    NavigationRequestSecurityLevel::kUpgraded,
                                    1);
  } else {
    // If HFM strict mode is not enabled, we should not attempt to upgrade the
    // navigation.
    EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, local_ip_url));
    histograms()->ExpectTotalCount(kEventHistogram, 0);
    histograms()->ExpectBucketCount(
        kNavigationRequestSecurityLevelHistogram,
        NavigationRequestSecurityLevel::kNonUniqueHostname, 1);
  }

  ExpectEmptyUKM();
}

// Test that unique single-label hostnames (e.g. gTLDs) are only upgraded and
// warned on in strict mode.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       UniqueSingleLabel_NoWarnInBalancedMode) {
  // Set an HTTPS testing port that does not match the test server to have all
  // attempted upgrades fail.
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(0);

  GURL singlelabel_url = http_server()->GetURL("cl", "/simple.html");

  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();

  if (IsStrictInterstitialEnabledForTest()) {
    // HFM should attempt the upgrade, fail, and fallback to the interstitial.
    EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, singlelabel_url));
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
    histograms()->ExpectTotalCount(kNavigationRequestSecurityLevelHistogram, 2);
  } else {
    // Otherwise, the request should not be upgraded and just navigate to HTTP.
    EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, singlelabel_url));
    EXPECT_EQ(singlelabel_url, contents->GetLastCommittedURL());
    EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
    histograms()->ExpectBucketCount(
        kNavigationRequestSecurityLevelHistogram,
        NavigationRequestSecurityLevel::kSingleLabelHostname, 1);
    histograms()->ExpectTotalCount(kNavigationRequestSecurityLevelHistogram, 1);
  }

  // If in Strict Mode, verify that upgrade events were recorded because an
  // upgrade was attempted and failed.
  if (IsStrictInterstitialEnabledForTest()) {
    histograms()->ExpectTotalCount(kEventHistogram, 3);
    histograms()->ExpectBucketCount(
        kEventHistogram,
        security_interstitials::https_only_mode::Event::kUpgradeAttempted, 1);
    histograms()->ExpectBucketCount(
        kEventHistogram,
        security_interstitials::https_only_mode::Event::kUpgradeFailed, 1);
    histograms()->ExpectBucketCount(
        kEventHistogram,
        security_interstitials::https_only_mode::Event::kUpgradeTimedOut, 1);

    histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                    NavigationRequestSecurityLevel::kUpgraded,
                                    1);
    histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                    NavigationRequestSecurityLevel::kSecure, 1);
  }

  ExpectEmptyUKM();
}

// If the user navigates to a non-unique hostname, the navigation should be
// upgraded only if strict mode is enabled. If we skip upgrading we should
// record that the reason was the non-unique hostname.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, NonUniqueHost_RecordsMetrics) {
  GURL nonunique_url1 = http_server()->GetURL("test.local", "/simple.html");
  GURL nonunique_url2 = http_server()->GetURL("test", "/simple.html");
  // Note that we don't test with an RFC1918 IP because the test server
  // wouldn't receive the traffic (since it relies on DNS).

  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  if (IsStrictInterstitialEnabledForTest()) {
    EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, nonunique_url1));
    EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, nonunique_url2));
    // Other histograms are still recorded.
    histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                    NavigationRequestSecurityLevel::kUpgraded,
                                    2);
    histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                    NavigationRequestSecurityLevel::kSecure, 2);
  } else {
    // When HFM strict mode is not enabled, Chrome does NOT upgrade, so other
    // histograms are not recorded.
    EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, nonunique_url1));
    EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, nonunique_url2));
    histograms()->ExpectUniqueSample(
        kNavigationRequestSecurityLevelHistogram,
        NavigationRequestSecurityLevel::kNonUniqueHostname, 2);
  }

  ExpectEmptyUKM();
}

// Test that non-default ports (e.g. not HTTP80) are only upgraded and warned on
// in strict mode and Incognito.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       NonDefaultPorts_NoWarnInBalancedMode) {
  // Disable the testing port configuration, as this test doesn't use the
  // EmbeddedTestServer.
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(0);
  HttpsUpgradesInterceptor::SetHttpPortForTesting(0);

  // Set up an interceptor so we can test non-default (and non-testing) ports.
  GURL non_default_http_url = GURL("http://example.com:8080/simple.html");
  auto url_loader_interceptor =
      content::URLLoaderInterceptor::ServeFilesFromDirectoryAtOrigin(
          GetChromeTestDataDir().MaybeAsASCII(),
          non_default_http_url.GetWithEmptyPath());

  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();

  if (IsStrictInterstitialEnabledForTest()) {
    // HFM should attempt the upgrade, fail, and fallback to the interstitial.
    EXPECT_FALSE(
        NavigateToURLWithLinkTransition(contents, non_default_http_url));
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
    histograms()->ExpectTotalCount(kNavigationRequestSecurityLevelHistogram, 2);
  } else {
    // Otherwise, the request should not be upgraded and just navigate to HTTP.
    EXPECT_TRUE(
        NavigateToURLWithLinkTransition(contents, non_default_http_url));
    EXPECT_EQ(non_default_http_url, contents->GetLastCommittedURL());
    EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
    histograms()->ExpectBucketCount(
        kNavigationRequestSecurityLevelHistogram,
        NavigationRequestSecurityLevel::kNonDefaultPorts, 1);
    histograms()->ExpectTotalCount(kNavigationRequestSecurityLevelHistogram, 1);
  }

  // If in Strict Mode, verify that upgrade events were recorded
  // because an upgrade was attempted and failed.
  if (IsStrictInterstitialEnabledForTest()) {
    histograms()->ExpectTotalCount(kEventHistogram, 3);
    histograms()->ExpectBucketCount(
        kEventHistogram,
        security_interstitials::https_only_mode::Event::kUpgradeAttempted, 1);
    histograms()->ExpectBucketCount(
        kEventHistogram,
        security_interstitials::https_only_mode::Event::kUpgradeFailed, 1);
    histograms()->ExpectBucketCount(
        kEventHistogram,
        security_interstitials::https_only_mode::Event::kUpgradeNetError, 1);

    histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                    NavigationRequestSecurityLevel::kUpgraded,
                                    1);
    histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                    NavigationRequestSecurityLevel::kSecure, 1);
  }

  ExpectEmptyUKM();
}

// If the user navigates to an HTTPS URL, the navigation should end up on that
// exact URL, even if the site has an SSL error.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       UrlWithHttpsScheme_BrokenSSL_ShouldNotFallback) {
  GURL https_url = https_server()->GetURL("bad-https.com", "/simple.html");

  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, https_url));
  EXPECT_EQ(https_url, contents->GetLastCommittedURL());

  // The SSL error should show regardless of the HFM state.
  EXPECT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(contents));

  // Verify that navigation event metrics were not recorded as the navigation
  // was not upgraded.
  histograms()->ExpectTotalCount(kEventHistogram, 0);

  ExpectEmptyUKM();
}

// If the user navigates to an HTTP URL for a site with broken HTTPS, the
// navigation should end up on the HTTPS URL and show the HTTPS-Only Mode
// interstitial.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       UrlWithHttpScheme_BrokenSSL_ShouldInterstitial) {
  GURL http_url = http_server()->GetURL("bad-https.com", "/simple.html");
  GURL https_url = https_server()->GetURL("bad-https.com", "/simple.html");

  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateAndWaitForFallback(contents, http_url);
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
  }

  // Verify that navigation event metrics were correctly recorded.
  histograms()->ExpectTotalCount(kEventHistogram, 3);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeFailed, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeCertError, 1);

  // The user hasn't taken action yet, so this should be empty.
  ExpectEmptyUKM();
}

// HTTPS-First Mode in Incognito should customize the interstitial.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       IncognitoInterstitialVariation) {
  // This test only applies to fully-enabled HFM and HFM-in-Incognito.
  if (!IsHttpsFirstModePrefEnabled() && !IsIncognito()) {
    return;
  }

  GURL http_url = http_server()->GetURL("bad-https.com", "/simple.html");
  GURL https_url = https_server()->GetURL("bad-https.com", "/simple.html");

  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateAndWaitForFallback(contents, http_url);
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());

  if (IsHttpsFirstModePrefEnabled()) {
    EXPECT_EQ(HFMInterstitialType::kStandard, GetHFMInterstitialType(contents));
  } else if (IsIncognito()) {
    // Test that HFM-in-Incognito overrides the default interstitial text.
    EXPECT_EQ(HFMInterstitialType::kIncognito,
              GetHFMInterstitialType(contents));
  }

  // The user hasn't taken action yet, so this should be empty.
  ExpectEmptyUKM();
}

void MaybeEnableHttpsFirstModeForEngagedSitesAndWait(
    HttpsFirstModeService* hfm_service) {
  base::RunLoop run_loop;
  hfm_service->MaybeEnableHttpsFirstModeForEngagedSites(run_loop.QuitClosure());
  run_loop.Run();
}

// Returns a URL loader interceptor that responds to HTTPS URLs with a cert
// error and to HTTP URLs with a good response.
std::unique_ptr<content::URLLoaderInterceptor>
MakeInterceptorForSiteEngagementHeuristic() {
  return std::make_unique<content::URLLoaderInterceptor>(
      base::BindLambdaForTesting(
          [](content::URLLoaderInterceptor::RequestParams* params) {
            if (params->url_request.url.SchemeIs("https")) {
              // Fail with an SSL error.
              network::URLLoaderCompletionStatus status;
              status.error_code = net::ERR_CERT_COMMON_NAME_INVALID;
              status.ssl_info = net::SSLInfo();
              status.ssl_info->cert_status =
                  net::CERT_STATUS_COMMON_NAME_INVALID;
              // The cert doesn't matter.
              status.ssl_info->cert = net::ImportCertFromFile(
                  net::GetTestCertsDirectory(), "ok_cert.pem");
              status.ssl_info->unverified_cert = status.ssl_info->cert;
              params->client->OnComplete(status);
              return true;
            }
            content::URLLoaderInterceptor::WriteResponse(
                "HTTP/1.1 200 OK\nContent-type: text/html\n\n",
                "<html>Done</html>", params->client.get());
            return true;
          }));
}

// TODO(crbug.com/40904694): Fails on the linux-wayland-rel bot.
#if defined(OZONE_PLATFORM_WAYLAND)
#define MAYBE_UrlWithHttpScheme_BrokenSSL_SiteEngagementHeuristic_ShouldInterstitial \
  DISABLED_UrlWithHttpScheme_BrokenSSL_SiteEngagementHeuristic_ShouldInterstitial
#else
#define MAYBE_UrlWithHttpScheme_BrokenSSL_SiteEngagementHeuristic_ShouldInterstitial \
  UrlWithHttpScheme_BrokenSSL_SiteEngagementHeuristic_ShouldInterstitial
#endif
// Test for Site Engagement Heuristic, a feature that enables HFM on specific
// sites based on their site engagement scores.
// If the user navigates to an HTTP URL for a site with broken HTTPS, the
// navigation should end up on the HTTPS URL and show the HTTPS-Only Mode
// interstitial. It should also record a separate histogram for Site Engagement
// Heuristic if the interstitial isn't enabled.
IN_PROC_BROWSER_TEST_P(
    HttpsUpgradesBrowserTest,
    MAYBE_UrlWithHttpScheme_BrokenSSL_SiteEngagementHeuristic_ShouldInterstitial) {
  // HFM+SE is not enabled in Incognito.
  if (IsIncognito()) {
    return;
  }
  // Disable the testing port configuration, as this test doesn't use the
  // EmbeddedTestServer.
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(0);
  HttpsUpgradesInterceptor::SetHttpPortForTesting(0);
  auto url_loader_interceptor = MakeInterceptorForSiteEngagementHeuristic();

  content::WebContents* contents =
      GetBrowser()->tab_strip_model()->GetActiveWebContents();
  Profile* profile = GetBrowser()->GetProfile();
  content::SSLHostStateDelegate* state = profile->GetSSLHostStateDelegate();

  // Set test clock.
  auto clock = std::make_unique<base::SimpleTestClock>();
  auto* clock_ptr = clock.get();
  StatefulSSLHostStateDelegate* chrome_state =
      static_cast<StatefulSSLHostStateDelegate*>(state);
  chrome_state->SetClockForTesting(std::move(clock));

  // Start the clock at standard system time.
  clock_ptr->SetNow(base::Time::NowFromSystemTime());

  GURL http_url("http://bad-https.com");
  GURL https_url("https://bad-https.com");
  SetSiteEngagementScore(http_url, kLowSiteEngagementScore);
  SetSiteEngagementScore(https_url, kHighSiteEnagementScore);
  HttpsFirstModeService* hfm_service =
      HttpsFirstModeServiceFactory::GetForProfile(profile);
  MaybeEnableHttpsFirstModeForEngagedSitesAndWait(hfm_service);
  const bool is_interstitial_due_to_se_heuristic =
      IsSiteEngagementHeuristicEnabled() && !IsHttpsFirstModePrefEnabled() &&
      !InBalancedMode();

  NavigateAndWaitForFallback(contents, http_url);
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites() ||
      IsSiteEngagementHeuristicEnabled()) {
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
    EXPECT_EQ(is_interstitial_due_to_se_heuristic
                  ? HFMInterstitialType::kSiteEngagement
                  : HFMInterstitialType::kStandard,
              GetHFMInterstitialType(contents));
  } else {
    EXPECT_EQ(HFMInterstitialType::kNone, GetHFMInterstitialType(contents));
  }

  // Verify that navigation event metrics were correctly recorded.
  histograms()->ExpectTotalCount(kEventHistogram, 3);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeFailed, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeCertError, 1);

  // Check engagement heuristic metrics. These are only recorded when the
  // site engagement heuristic is enabled and the interstitial is due to this
  // heuristic and not because of prefs.
  if (is_interstitial_due_to_se_heuristic) {
    histograms()->ExpectTotalCount(kEventHistogramWithEngagementHeuristic, 3);
    histograms()->ExpectBucketCount(kEventHistogramWithEngagementHeuristic,
                                    Event::kUpgradeAttempted, 1);
    histograms()->ExpectBucketCount(kEventHistogramWithEngagementHeuristic,
                                    Event::kUpgradeFailed, 1);
    histograms()->ExpectBucketCount(kEventHistogramWithEngagementHeuristic,
                                    Event::kUpgradeCertError, 1);
    // Check the heuristic state.
    histograms()->ExpectTotalCount(kSiteEngagementHeuristicStateHistogram, 1);
    histograms()->ExpectBucketCount(kSiteEngagementHeuristicStateHistogram,
                                    SiteEngagementHeuristicState::kDisabled, 0);
    histograms()->ExpectBucketCount(kSiteEngagementHeuristicStateHistogram,
                                    SiteEngagementHeuristicState::kEnabled, 1);
    // Check host count.
    histograms()->ExpectTotalCount(kSiteEngagementHeuristicHostCountHistogram,
                                   1);
    histograms()->ExpectBucketCount(kSiteEngagementHeuristicHostCountHistogram,
                                    0,
                                    /*expected_count=*/0);
    histograms()->ExpectBucketCount(kSiteEngagementHeuristicHostCountHistogram,
                                    1,
                                    /*expected_count=*/1);
    // Check accumulated host count.
    histograms()->ExpectTotalCount(
        kSiteEngagementHeuristicAccumulatedHostCountHistogram, 1);
    histograms()->ExpectBucketCount(
        kSiteEngagementHeuristicAccumulatedHostCountHistogram, 0,
        /*expected_count=*/0);
    histograms()->ExpectBucketCount(
        kSiteEngagementHeuristicAccumulatedHostCountHistogram, 1,
        /*expected_count=*/1);
    // Check enforcement duration. Since the host isn't removed from HFM
    // enforcement list, no duration should be recorded yet.
    histograms()->ExpectTotalCount(
        kSiteEngagementHeuristicEnforcementDurationHistogram, 0);

    histograms()->ExpectBucketCount(
        kNavigationRequestSecurityLevelHistogram,
        NavigationRequestSecurityLevel::kHttpsEnforcedOnHostname, 1);
  } else {
    histograms()->ExpectTotalCount(kEventHistogramWithEngagementHeuristic, 0);

    histograms()->ExpectBucketCount(
        kNavigationRequestSecurityLevelHistogram,
        NavigationRequestSecurityLevel::kHttpsEnforcedOnHostname, 0);
  }

  // Lower HTTPS engagement score. This disables HFM on the site. Also advance
  // the clock.
  SetSiteEngagementScore(https_url, 5);
  clock_ptr->Advance(base::Hours(1));
  MaybeEnableHttpsFirstModeForEngagedSitesAndWait(hfm_service);

  NavigateAndWaitForFallback(contents, http_url);
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());

  // Should only show the interstitial if the HFM pref is enabled. Site
  // engagement heuristic alone will no longer cause an interstitial.
  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));

    // Proceed through the interstitial, which will add the host to the
    // allowlist and navigate to the HTTP fallback URL.
    ProceedThroughHttpsFirstModeInterstitial(contents);

    // Verify that the interstitial metrics were correctly recorded. The
    // interstitial was shown twice, once clicked through and once not.
    histograms()->ExpectTotalCount("interstitial.https_first_mode.decision", 4);
    histograms()->ExpectBucketCount(
        "interstitial.https_first_mode.decision",
        security_interstitials::MetricsHelper::Decision::SHOW, 2);
    histograms()->ExpectBucketCount(
        "interstitial.https_first_mode.decision",
        security_interstitials::MetricsHelper::Decision::PROCEED, 1);
    histograms()->ExpectBucketCount(
        "interstitial.https_first_mode.decision",
        security_interstitials::MetricsHelper::Decision::DONT_PROCEED, 1);
  } else {
    EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
    if (IsSiteEngagementHeuristicEnabled()) {
      // Verify that the interstitial metrics were correctly recorded. The
      // interstitial was shown once and navigated away from.
      histograms()->ExpectTotalCount("interstitial.https_first_mode.decision",
                                     2);
      histograms()->ExpectBucketCount(
          "interstitial.https_first_mode.decision",
          security_interstitials::MetricsHelper::Decision::SHOW, 1);
      histograms()->ExpectBucketCount(
          "interstitial.https_first_mode.decision",
          security_interstitials::MetricsHelper::Decision::DONT_PROCEED, 1);
    } else {
      histograms()->ExpectTotalCount("interstitial.https_first_mode.decision",
                                     0);
    }
  }

  // Check engagement heuristic metrics. These are only recorded when the
  // site engagement heuristic is enabled and the interstitial is due to this
  // heuristic and not because of prefs.
  if (IsSiteEngagementHeuristicEnabled() &&
      !IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    // Event histogram shouldn't change because Site Engagement heuristic didn't
    // kick in.
    histograms()->ExpectTotalCount(kEventHistogramWithEngagementHeuristic, 3);

    // Check host count.
    histograms()->ExpectTotalCount(kSiteEngagementHeuristicHostCountHistogram,
                                   2);
    histograms()->ExpectBucketCount(kSiteEngagementHeuristicHostCountHistogram,
                                    0,
                                    /*expected_count=*/1);
    histograms()->ExpectBucketCount(kSiteEngagementHeuristicHostCountHistogram,
                                    1,
                                    /*expected_count=*/1);
    // Check accumulated host count.
    histograms()->ExpectTotalCount(
        kSiteEngagementHeuristicAccumulatedHostCountHistogram, 2);
    histograms()->ExpectBucketCount(
        kSiteEngagementHeuristicAccumulatedHostCountHistogram, 0,
        /*expected_count=*/0);
    histograms()->ExpectBucketCount(
        kSiteEngagementHeuristicAccumulatedHostCountHistogram, 1,
        /*expected_count=*/2);
    // Check enforcement duration. The host is now removed from HFM
    // enforcement list, so its HFM enforcement duration should be recorded now.
    histograms()->ExpectTotalCount(
        kSiteEngagementHeuristicEnforcementDurationHistogram, 1);
    histograms()->ExpectTimeBucketCount(
        kSiteEngagementHeuristicEnforcementDurationHistogram, base::Hours(1),
        1);

    // This bucket was recorded once previously, shouldn't be recorded again.
    histograms()->ExpectBucketCount(
        kNavigationRequestSecurityLevelHistogram,
        NavigationRequestSecurityLevel::kHttpsEnforcedOnHostname, 1);

  } else {
    // Event histogram shouldn't change because Site Engagement heuristic didn't
    // kick in.
    histograms()->ExpectTotalCount(kEventHistogramWithEngagementHeuristic, 0);

    // If HFM pref was enabled, no SE metrics should be recorded because HFM
    // won't be auto-enabled.
    histograms()->ExpectTotalCount(kSiteEngagementHeuristicHostCountHistogram,
                                   0);
    histograms()->ExpectTotalCount(
        kSiteEngagementHeuristicAccumulatedHostCountHistogram, 0);
    histograms()->ExpectTotalCount(
        kSiteEngagementHeuristicEnforcementDurationHistogram, 0);

    histograms()->ExpectBucketCount(
        kNavigationRequestSecurityLevelHistogram,
        NavigationRequestSecurityLevel::kHttpsEnforcedOnHostname, 0);
  }
}

// Test that Site Engagement Heuristic doesn't enforce HTTPS on URLs with
// non-default ports.
IN_PROC_BROWSER_TEST_P(
    HttpsUpgradesBrowserTest,
    UrlWithHttpScheme_BrokenSSL_SiteEngagementHeuristic_ShouldIgnoreUrlsWithNonDefaultPorts) {
  // HFM+SE is not enabled in Incognito.
  if (IsIncognito()) {
    return;
  }
  // Advanced Protection users have HFM enabled unconditionally, which does not
  // exempt non-default ports. This test assumes the heuristic decides whether
  // to upgrade, which is not the case when AP is active.
  if (https_upgrades_test_type() ==
      HttpsUpgradesTestType::kHttpsFirstModeAdvancedProtection) {
    return;
  }
  // Disable the testing port configuration, as this test doesn't use the
  // EmbeddedTestServer.
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(0);
  HttpsUpgradesInterceptor::SetHttpPortForTesting(0);
  auto url_loader_interceptor = MakeInterceptorForSiteEngagementHeuristic();

  content::WebContents* contents =
      GetBrowser()->tab_strip_model()->GetActiveWebContents();
  Profile* profile = GetBrowser()->GetProfile();
  content::SSLHostStateDelegate* state = profile->GetSSLHostStateDelegate();

  // Set test clock.
  auto clock = std::make_unique<base::SimpleTestClock>();
  auto* clock_ptr = clock.get();
  StatefulSSLHostStateDelegate* chrome_state =
      static_cast<StatefulSSLHostStateDelegate*>(state);
  chrome_state->SetClockForTesting(std::move(clock));

  // Start the clock at standard system time.
  clock_ptr->SetNow(base::Time::NowFromSystemTime());

  GURL http_url("http://bad-https.com");
  GURL https_url("https://bad-https.com");
  GURL navigated_url("http://bad-https.com:8080");

  // Set engagement for the HTTP and HTTPS origins with default ports.
  SetSiteEngagementScore(http_url, kLowSiteEngagementScore);
  SetSiteEngagementScore(https_url, kHighSiteEnagementScore);
  HttpsFirstModeService* hfm_service =
      HttpsFirstModeServiceFactory::GetForProfile(profile);
  MaybeEnableHttpsFirstModeForEngagedSitesAndWait(hfm_service);

  // Navigate to a non-default port version of the URL.
  NavigateAndWaitForFallback(contents, navigated_url);
  EXPECT_EQ(navigated_url, contents->GetLastCommittedURL());

  // Non-strict modes should not upgrade because `navigated_url` has a
  // non-default port, regardless of whether the hostname is on the enforcelist.
  if (IsHttpsFirstModePrefEnabled()) {
    EXPECT_EQ(HFMInterstitialType::kStandard, GetHFMInterstitialType(contents));
  } else {
    EXPECT_EQ(HFMInterstitialType::kNone, GetHFMInterstitialType(contents));
  }

  // Strict mode should have upgraded and fallen back to HTTP.
  if (IsHttpsFirstModePrefEnabled()) {
    // Verify that navigation event metrics were correctly recorded.
    histograms()->ExpectTotalCount(kEventHistogram, 3);
    histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeAttempted,
                                    1);
    histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeFailed, 1);
    histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeCertError,
                                    1);
  } else {
    histograms()->ExpectTotalCount(kEventHistogram, 0);
  }

  // Engagement heuristic shouldn't handle any navigation events because we
  // didn't navigate to bad-https.com:80.
  histograms()->ExpectTotalCount(kEventHistogramWithEngagementHeuristic, 0);

  // Check engagement heuristic metrics. These are only recorded when the
  // site engagement interstitial is enabled.
  if (IsSiteEngagementHeuristicEnabled() &&
      !IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    // Check the heuristic state. The heuristic should enable HFM for
    // example.com
    histograms()->ExpectTotalCount(kSiteEngagementHeuristicStateHistogram, 1);
    histograms()->ExpectBucketCount(kSiteEngagementHeuristicStateHistogram,
                                    SiteEngagementHeuristicState::kDisabled, 0);
    histograms()->ExpectBucketCount(kSiteEngagementHeuristicStateHistogram,
                                    SiteEngagementHeuristicState::kEnabled, 1);
    // Check host count.
    histograms()->ExpectTotalCount(kSiteEngagementHeuristicHostCountHistogram,
                                   1);
    histograms()->ExpectBucketCount(kSiteEngagementHeuristicHostCountHistogram,
                                    0,
                                    /*expected_count=*/0);
    histograms()->ExpectBucketCount(kSiteEngagementHeuristicHostCountHistogram,
                                    1,
                                    /*expected_count=*/1);
    // Check accumulated host count.
    histograms()->ExpectTotalCount(
        kSiteEngagementHeuristicAccumulatedHostCountHistogram, 1);
    histograms()->ExpectBucketCount(
        kSiteEngagementHeuristicAccumulatedHostCountHistogram, 0,
        /*expected_count=*/0);
    histograms()->ExpectBucketCount(
        kSiteEngagementHeuristicAccumulatedHostCountHistogram, 1,
        /*expected_count=*/1);
    // Check enforcement duration. Since the host isn't removed from HFM
    // enforcement list, no duration should be recorded yet.
    histograms()->ExpectTotalCount(
        kSiteEngagementHeuristicEnforcementDurationHistogram, 0);
  } else {
    histograms()->ExpectTotalCount(kEventHistogramWithEngagementHeuristic, 0);
  }
}

IN_PROC_BROWSER_TEST_P(
    HttpsUpgradesBrowserTest,
    PRE_UrlWithHttpScheme_BrokenSSL_ShouldInterstitial_TypicallySecureUser) {
  // HFM-for-Typically-Secure-Users is not enabled in Incognito.
  if (IsIncognito()) {
    return;
  }
  if (https_upgrades_test_type() ==
      HttpsUpgradesTestType::kHttpsFirstModeAdvancedProtection) {
    return;
  }

  content::WebContents* contents =
      GetBrowser()->tab_strip_model()->GetActiveWebContents();
  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());

  if (!IsHttpsFirstModePrefEnabled() && !InBalancedMode()) {
    // When HFM is not enabled via pref, these should never be set in this test.
    EXPECT_FALSE(
        profile->GetPrefs()->HasPrefPath(prefs::kHttpsOnlyModeEnabled));
    EXPECT_FALSE(
        profile->GetPrefs()->HasPrefPath(prefs::kHttpsOnlyModeAutoEnabled));
  }

  // Typically Secure User heuristic requires a minimum total site engagement
  // score.
  SetSiteEngagementScore(GURL("https://google.com"), 90);

  base::Time now;
  EXPECT_TRUE(base::Time::FromUTCString("2023-10-15T06:00:00Z", &now));
  // Start the clock at standard system time.
  GetTestClock()->SetNow(now);

  profile->SetCreationTimeForTesting(GetTestClock()->Now() - base::Days(30));

  HttpsFirstModeService* hfm_service =
      HttpsFirstModeServiceFactory::GetForProfile(profile);
  hfm_service->SetClockForTesting(GetTestClock());

  GURL http_url = http_server()->GetURL("bad-https.com", "/simple.html");
  GURL https_url = https_server()->GetURL("bad-https.com", "/simple.html");

  // Visit the HTTP URL. Profile age is old enough but we haven't been observing
  // navigations for long enough, so Typically Secure Users feature won't show
  // an interstitial here.
  NavigateAndWaitForFallback(contents, http_url);
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
  ExpectedInterstitialReasons expected_reasons;

  if (IsHttpsFirstModePrefEnabled()) {
    ExpectInterstitial(contents);
    expected_reasons.pref++;
  } else if (InBalancedMode()) {
    ExpectInterstitial(contents);
    expected_reasons.balanced++;
  } else if (safe_browsing::AdvancedProtectionStatusManagerFactory::
                 GetForProfile(browser()->GetProfile())
                     ->IsUnderAdvancedProtection()) {
    ExpectInterstitial(contents);
    expected_reasons.advanced_protection++;
  } else {
    EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
  }
  CheckInterstitialReasonHistogram(expected_reasons);

  // Move the clock forward and revisit HTTP. Profile is old enough now, but
  // Typically Secure Users feature will only auto-enable HFM after a restart
  // and show an interstitial.
  GetTestClock()->Advance(base::Days(15));
  NavigateAndWaitForFallback(contents, http_url);
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());

  if (IsHttpsFirstModePrefEnabled()) {
    ExpectInterstitial(contents);
    expected_reasons.pref++;
  } else if (InBalancedMode()) {
    ExpectInterstitial(contents);
    expected_reasons.balanced++;
  } else if (safe_browsing::AdvancedProtectionStatusManagerFactory::
                 GetForProfile(browser()->GetProfile())
                     ->IsUnderAdvancedProtection()) {
    ExpectInterstitial(contents);
    expected_reasons.advanced_protection++;
  } else {
    EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
  }
  CheckInterstitialReasonHistogram(expected_reasons);

  if (!IsHttpsFirstModePrefEnabled() && !InBalancedMode()) {
    EXPECT_FALSE(
        profile->GetPrefs()->HasPrefPath(prefs::kHttpsOnlyModeEnabled));
    EXPECT_FALSE(
        profile->GetPrefs()->HasPrefPath(prefs::kHttpsOnlyModeAutoEnabled));
  }
}

// TODO(crbug.com/40925331): Fails on the linux-wayland-rel bot.
#if defined(OZONE_PLATFORM_WAYLAND)
#define MAYBE_UrlWithHttpScheme_BrokenSSL_ShouldInterstitial_TypicallySecureUser \
  DISABLED_UrlWithHttpScheme_BrokenSSL_ShouldInterstitial_TypicallySecureUser
#else
#define MAYBE_UrlWithHttpScheme_BrokenSSL_ShouldInterstitial_TypicallySecureUser \
  UrlWithHttpScheme_BrokenSSL_ShouldInterstitial_TypicallySecureUser
#endif
IN_PROC_BROWSER_TEST_P(
    HttpsUpgradesBrowserTest,
    MAYBE_UrlWithHttpScheme_BrokenSSL_ShouldInterstitial_TypicallySecureUser) {
  // HFM-for-Typically-Secure-Users is not enabled in Incognito.
  if (IsIncognito()) {
    return;
  }
  if (https_upgrades_test_type() ==
      HttpsUpgradesTestType::kHttpsFirstModeAdvancedProtection) {
    return;
  }

  // Advance the clock to one day after the last fallback event, which happened
  // on the 15th day.
  GetTestClock()->SetNow(base::Time::NowFromSystemTime() + base::Days(16));

  content::WebContents* contents =
      GetBrowser()->tab_strip_model()->GetActiveWebContents();
  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());

  HttpsFirstModeService* hfm_service =
      HttpsFirstModeServiceFactory::GetForProfile(profile);
  // Do lots of navigations so that Typically Secure User can kick in.
  for (size_t i = 0; i < 1500; i++) {
    hfm_service->IncrementRecentNavigationCount();
  }

  hfm_service->SetClockForTesting(GetTestClock());
  // HFM service runs this on startup, but we can't set the test clock before it
  // runs, and we need to move the clock forward for this to work. So call it
  // explicitly again here.
  hfm_service->CheckUserIsTypicallySecureAndMaybeEnableHttpsFirstBalancedMode();
  size_t initial_navigation_count = hfm_service->GetRecentNavigationCount();

  // Use a different hostname than the PRE_ test so that we don't hit the
  // allowlist.
  GURL http_url = http_server()->GetURL("bad-https2.com", "/simple.html");
  GURL https_url = https_server()->GetURL("bad-https2.com", "/simple.html");
  NavigateAndWaitForFallback(contents, http_url);
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
  EXPECT_EQ(initial_navigation_count + 1u,
            hfm_service->GetRecentNavigationCount());

  bool expect_interstitial = IsHttpsFirstModeInterstitialEnabledAcrossSites() ||
                             IsTypicallySecureUserFeatureEnabled();
  // Expect typically secure text only when HFM is auto-enabled, so exclude
  // HttpsUpgradesTestType::kAll where HFM is enabled via pref).
  bool expect_typically_secure_user_interstitial_text =
      https_upgrades_test_type() ==
          HttpsUpgradesTestType::kHttpsFirstModeForTypicallySecureUsers ||
      https_upgrades_test_type() == HttpsUpgradesTestType::kAllAutoHFM;

  ExpectedInterstitialReasons expected_reasons;
  if (expect_interstitial) {
    EXPECT_EQ(expect_typically_secure_user_interstitial_text
                  ? HFMInterstitialType::kTypicallySecure
                  : HFMInterstitialType::kStandard,
              GetHFMInterstitialType(contents));

    if (expect_typically_secure_user_interstitial_text) {
      expected_reasons.typically_secure_user++;
    } else if (IsHttpsFirstModePrefEnabled()) {
      expected_reasons.pref++;
    } else if (InBalancedMode()) {
      expected_reasons.balanced++;
    } else if (safe_browsing::AdvancedProtectionStatusManagerFactory::
                   GetForProfile(browser()->GetProfile())
                       ->IsUnderAdvancedProtection()) {
      expected_reasons.advanced_protection++;
    } else {
      NOTREACHED();
    }
  } else {
    EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
  }
  CheckInterstitialReasonHistogram(expected_reasons);

  // Move the clock forward a day and revisit HTTP. Should still show HFM
  // interstitial.
  GetTestClock()->Advance(base::Days(1));
  NavigateAndWaitForFallback(contents, http_url);
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
  EXPECT_EQ(initial_navigation_count + 2u,
            hfm_service->GetRecentNavigationCount());

  if (expect_interstitial) {
    EXPECT_EQ(expect_typically_secure_user_interstitial_text
                  ? HFMInterstitialType::kTypicallySecure
                  : HFMInterstitialType::kStandard,
              GetHFMInterstitialType(contents));

    if (expect_typically_secure_user_interstitial_text) {
      expected_reasons.typically_secure_user++;
    } else if (IsHttpsFirstModePrefEnabled()) {
      expected_reasons.pref++;
    } else if (InBalancedMode()) {
      expected_reasons.balanced++;
    } else if (safe_browsing::AdvancedProtectionStatusManagerFactory::
                   GetForProfile(browser()->GetProfile())
                       ->IsUnderAdvancedProtection()) {
      expected_reasons.advanced_protection++;
    } else {
      NOTREACHED();
    }

  } else {
    EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
  }
  CheckInterstitialReasonHistogram(expected_reasons);

  // Disable HFM and HF-balanced-mode. Should no longer auto-enable it.
  SetPref(false);
  auto* state = static_cast<StatefulSSLHostStateDelegate*>(
      profile->GetSSLHostStateDelegate());
  state->SetHttpsFirstBalancedModeSuppressedForTesting(true);
  NavigateAndWaitForFallback(contents, http_url);
  EXPECT_EQ(initial_navigation_count + 3u,
            hfm_service->GetRecentNavigationCount());
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
  EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));

  // Re-enable HFM. Should now show HFM interstitial without the auto-enabled
  // text.
  SetPref(true);
  state->SetHttpsFirstBalancedModeSuppressedForTesting(false);
  NavigateAndWaitForFallback(contents, http_url);
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
  EXPECT_EQ(initial_navigation_count + 4u,
            hfm_service->GetRecentNavigationCount());

  EXPECT_EQ(HFMInterstitialType::kStandard, GetHFMInterstitialType(contents));
  expected_reasons.pref++;

  CheckInterstitialReasonHistogram(expected_reasons);
}

// Checks that navigation to a non-unique hostname doesn't display a typically
// secure interstitial.
IN_PROC_BROWSER_TEST_P(
    HttpsUpgradesBrowserTest,
    TypicallySecure_NonUniqueHostname_ShouldNotShowInterstitial) {
  // HFM-for-Typically-Secure-Users is not enabled in Incognito.
  if (IsIncognito()) {
    return;
  }
  if (https_upgrades_test_type() ==
      HttpsUpgradesTestType::kHttpsFirstModeAdvancedProtection) {
    return;
  }
  // Disable the testing port configuration, as this test doesn't use the
  // EmbeddedTestServer.
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(0);
  HttpsUpgradesInterceptor::SetHttpPortForTesting(0);
  auto url_loader_interceptor = MakeInterceptorForSiteEngagementHeuristic();

  SatisfyTypicallySecureHeuristicRequirements();
  // This should auto-enable HFM now:
  hfm_service()
      ->CheckUserIsTypicallySecureAndMaybeEnableHttpsFirstBalancedMode();

  // Check that a bad HTTPS URL should show an interstitial due to the
  // heuristic.
  GURL http_url("http://bad-https.com/simple.html");
  content::WebContents* contents =
      GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateToURLWithLinkTransitionBlockUntilNavigationsComplete(
      contents, http_url, /*number_of_navigations=*/1);
  MaybeExpectTypicallySecureInterstitial(contents);

  // Check that a non-unique hostname shouldn't show an interstitial due to the
  // heuristic.
  GURL nonunique_url("http://nonunique-hostname-bad-https/simple.html");
  NavigateToURLWithLinkTransitionBlockUntilNavigationsComplete(
      contents, nonunique_url, /*number_of_navigations=*/1);
  if (IsStrictInterstitialEnabledForTest()) {
    // Non-unique hostnames should only show an interstitial in strict mode.
    EXPECT_EQ(HFMInterstitialType::kStandard, GetHFMInterstitialType(contents));
  } else {
    EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
  }
}

// Same as TypicallySecure_NonUniqueHostname_ShouldNotShowInterstitial, but
// also navigates to a non-unique URL before checking the heuristic. The
// non-unique URL should not count as a fallback navigation and should not
// disable the Typically Secure heuristic.
IN_PROC_BROWSER_TEST_P(
    HttpsUpgradesBrowserTest,
    TypicallySecure_NonUniqueHostnameFallbackShouldNotDisableTypicallySecureHeuristic) {
  // HFM-for-Typically-Secure-Users is not enabled in Incognito.
  if (IsIncognito()) {
    return;
  }
  // Disable the testing port configuration, as this test doesn't use the
  // EmbeddedTestServer.
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(0);
  HttpsUpgradesInterceptor::SetHttpPortForTesting(0);
  auto url_loader_interceptor = MakeInterceptorForSiteEngagementHeuristic();

  if (https_upgrades_test_type() ==
      HttpsUpgradesTestType::kHttpsFirstModeAdvancedProtection) {
    return;
  }
  SatisfyTypicallySecureHeuristicRequirements();

  // Before running the heuristic checks, also navigate to a non-unique
  // hostname. This will result in an interstitial iff strict mode is enabled.
  content::WebContents* contents =
      browser()->GetTabStripModel()->GetActiveWebContents();
  NavigateToURLWithLinkTransitionBlockUntilNavigationsComplete(
      contents, GURL("http://nonunique-hostname-bad-https2/simple.html"),
      /*number_of_navigations=*/1);
  if (IsStrictInterstitialEnabledForTest()) {
    // Non-unique hostnames should only show an interstitial in strict mode.
    EXPECT_EQ(HFMInterstitialType::kStandard, GetHFMInterstitialType(contents));
  } else {
    EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
  }

  // This should still auto-enable HFM despite the interstitial for the
  // non-unique hostname because Typically Secure heuristic ignores fallbacks
  // for non-unique hostnames.
  hfm_service()
      ->CheckUserIsTypicallySecureAndMaybeEnableHttpsFirstBalancedMode();

  // Check that a bad HTTPS URL should show an interstitial due to the
  // heuristic.
  GURL http_url("http://bad-https.com/simple.html");
  NavigateToURLWithLinkTransitionBlockUntilNavigationsComplete(
      contents, http_url, /*number_of_navigations=*/1);
  MaybeExpectTypicallySecureInterstitial(contents);

  // Check that a non-unique hostname shouldn't show an interstitial due to the
  // heuristic.
  GURL nonunique_url("http://nonunique-hostname-bad-https/simple.html");
  NavigateToURLWithLinkTransitionBlockUntilNavigationsComplete(
      contents, nonunique_url, /*number_of_navigations=*/1);
  if (IsHttpsFirstModePrefEnabled()) {
    // Non-unique hostnames should only show an interstitial in strict mode.
    EXPECT_EQ(HFMInterstitialType::kStandard, GetHFMInterstitialType(contents));
  } else {
    EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
  }
}

// Regression test for crbug.com/40909795. Sequence of events:
// 1. Loads http://example.com. This gets upgraded to https://example.com.
// 2. https://example.com has an iframe for https://nonexistentsite.com. It
//    navigates away immediately to http://example.com.
// 3. This causes a crash in
//    HttpsUpgradesInterceptor::MaybeCreateLoaderForResponse() for
//    nonexistentsite.com.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       LoadIFrameAndNavigateAway_ShouldNotCrash) {
  // Disable the testing port configuration, as this test doesn't use the
  // EmbeddedTestServer.
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(0);
  HttpsUpgradesInterceptor::SetHttpPortForTesting(0);

  bool navigated_once = false;
  auto url_loader_interceptor = std::make_unique<content::URLLoaderInterceptor>(
      base::BindLambdaForTesting(
          [&navigated_once](
              content::URLLoaderInterceptor::RequestParams* params) {
            if (params->url_request.url == GURL("https://example.com")) {
              if (!navigated_once) {
                // Load an iframe that will result in an error and immediately
                // navigate away.
                content::URLLoaderInterceptor::WriteResponse(
                    "HTTP/1.1 200 OK\nContent-type: text/html\n\n",
                    "<html>"
                    "<iframe src='https://nonexistentsite.com'></iframe>"
                    "<script>window.location.href = "
                    "'http://example.com';</script></html>",
                    params->client.get());
                navigated_once = true;
                return true;
              }
              // Return a normal response the second time this is called,
              // otherwise the test will timeout due to navigating back and
              // forth between http and https URLs.
              content::URLLoaderInterceptor::WriteResponse(
                  "HTTP/1.1 200 OK\nContent-type: text/html\n\n",
                  "<html>Done</html>", params->client.get());
              return true;
            }

            if (params->url_request.url == GURL("http://example.com")) {
              content::URLLoaderInterceptor::WriteResponse(
                  "HTTP/1.1 200 OK\nContent-type: text/html\n\n",
                  "<html>Test</html>", params->client.get());
              return true;
            }

            if (params->url_request.url.GetHost() == "nonexistentsite.com") {
              // This request must fail for the bug to trigger.
              params->client->OnComplete(network::URLLoaderCompletionStatus(
                  net::ERR_CONNECTION_RESET));
              return true;
            }
            return false;
          }));

  GURL http_url("http://example.com");
  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateAndWaitForFallback(contents, http_url);
}

// If the user triggers an HTTPS-Only Mode interstitial for a host and then
// clicks through the interstitial, they should end up on the HTTP URL.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       InterstitialBypassed_HttpFallbackLoaded) {
  GURL http_url = http_server()->GetURL("bad-https.com", "/simple.html");

  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateAndWaitForFallback(contents, http_url);

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));

    // Proceed through the interstitial, which will add the host to the
    // allowlist and navigate to the HTTP fallback URL.
    ProceedThroughHttpsFirstModeInterstitial(contents);

    // Verify that the interstitial metrics were correctly recorded.
    histograms()->ExpectTotalCount("interstitial.https_first_mode.decision", 2);
    histograms()->ExpectBucketCount(
        "interstitial.https_first_mode.decision",
        security_interstitials::MetricsHelper::Decision::SHOW, 1);
    histograms()->ExpectBucketCount(
        "interstitial.https_first_mode.decision",
        security_interstitials::MetricsHelper::Decision::PROCEED, 1);

    ExpectUKMEntry(http_url, BlockingResult::kInterstitialProceed);
  }

  EXPECT_EQ(http_url, contents->GetLastCommittedURL());

  // Verify that navigation event metrics were correctly recorded.
  histograms()->ExpectTotalCount(kEventHistogram, 3);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeFailed, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeCertError, 1);

  // Revisit the site. Should load without a warning, but also record another
  // UKM.
  // TODO(crbug.com/406530494): This should also record the allowlisted status.
  NavigateAndWaitForFallback(contents, http_url);
  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    ExpectUKMEntry(http_url, BlockingResult::kInterstitialProceed);
  }
}

// If the upgraded HTTPS URL is not available due to a net error, it should
// trigger the HTTPS-Only Mode interstitial and offer fallback.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       NetErrorOnUpgrade_ShouldInterstitial) {
  GURL http_url = http_server()->GetURL("foo.com", "/close-socket");
  GURL https_url = https_server()->GetURL("foo.com", "/close-socket");

  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateAndWaitForFallback(contents, http_url);
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
  }

  // Verify that navigation event metrics were correctly recorded.
  histograms()->ExpectTotalCount(kEventHistogram, 3);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeFailed, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeNetError, 1);
}

// If the upgraded HTTPS URL is not available due to a potentially-transient
// exempted net error (here a DNS resolution error), show the regular net error
// page instead of the HTTPS-First Mode interstitial. If the network conditions
// change such that the network error no longer triggers, reloading the tab
// should continue the upgraded navigation, which will fail and trigger fallback
// to HTTP. (Regression test for crbug.com/40809438.)
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       ExemptNetErrorOnUpgrade_ShouldNotFallback) {
  // This test is only interesting when HTTPS-First Mode is enabled.
  if (!IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    return;
  }

  GURL http_url = http_server()->GetURL("bad-https.com", "/simple.html");
  GURL https_url = https_server()->GetURL("bad-https.com", "/simple.html");
  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();

  {
    // Set up an interceptor that will return ERR_NAME_NOT_RESOLVED. Navigating
    // to the HTTP URL should get upgraded to HTTPS, but fail with a net error
    // page on the HTTPS URL.
    auto dns_failure_interceptor =
        std::make_unique<content::URLLoaderInterceptor>(base::BindRepeating(
            [](content::URLLoaderInterceptor::RequestParams* params) {
              params->client->OnComplete(network::URLLoaderCompletionStatus(
                  net::ERR_NAME_NOT_RESOLVED));
              return true;
            }));
    EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, http_url));
    EXPECT_EQ(https_url, contents->GetLastCommittedURL());
    EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(contents));

    // Reload the tab. The net error should still be showing as the navigation
    // still results in ERR_NAME_NOT_RESOLVED.
    content::TestNavigationObserver nav_observer(contents, 1);
    contents->GetController().Reload(content::ReloadType::NORMAL,
                                     /*check_for_repost=*/false);
    nav_observer.Wait();
    EXPECT_EQ(https_url, contents->GetLastCommittedURL());
    EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(contents));
  }

  // Interceptor is now out of scope and no longer applies. Reload the tab and
  // the upgraded navigation should continue, fail due to the bad HTTPS on the
  // server, and fall back to HTTP.
  content::TestNavigationObserver nav_observer(contents, 1);
  contents->GetController().Reload(content::ReloadType::NORMAL,
                                   /*check_for_repost=*/false);
  nav_observer.Wait();

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    ASSERT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
    ProceedThroughHttpsFirstModeInterstitial(contents);
  }

  // Should now be on the HTTP URL and it should be allowlisted.
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
  content::SSLHostStateDelegate* state = profile->GetSSLHostStateDelegate();
  EXPECT_TRUE(state->IsHttpAllowedForHost(
      http_url.GetHost(),
      contents->GetPrimaryMainFrame()->GetStoragePartition()));
}

// Test that if one site redirects to a non-existent site, that we show the
// regular net error page instead of the HTTPS-First Mode interstitial.
// (Regression test for crbug.com/40809438.)
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       RedirectToNonexistentSite_ShouldNotInterstitial) {
  // This test is only interesting when HTTPS-First Mode is enabled.
  if (!IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    return;
  }

  std::string nonexistent_domain = "nonexistentsite.com";
  GURL nonexistent_http_url =
      http_server()->GetURL(nonexistent_domain, "/simple.html");
  GURL nonexistent_https_url =
      https_server()->GetURL(nonexistent_domain, "/simple.html");
  std::string www_redirect_path =
      base::StrCat({"/server-redirect?", nonexistent_http_url.spec()});
  GURL redirecting_https_url =
      https_server()->GetURL("foo.com", www_redirect_path);
  GURL redirecting_http_url =
      http_server()->GetURL("foo.com", www_redirect_path);

  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();

  // Set up an interceptor that will return ERR_NAME_NOT_RESOLVED for
  // nonexistentsite.com.
  auto dns_failure_interceptor =
      std::make_unique<content::URLLoaderInterceptor>(
          base::BindLambdaForTesting(
              [nonexistent_domain](
                  content::URLLoaderInterceptor::RequestParams* params) {
                if (params->url_request.url.GetHost() == nonexistent_domain) {
                  params->client->OnComplete(network::URLLoaderCompletionStatus(
                      net::ERR_NAME_NOT_RESOLVED));
                  return true;
                }
                return false;
              }));

  // Navigating to the HTTP URL should get upgraded to HTTPS, but fail with a
  // net error page on the HTTPS URL.
  EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, redirecting_http_url));
  EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
  EXPECT_EQ(url::kHttpsScheme, contents->GetLastCommittedURL().GetScheme());
  EXPECT_EQ(nonexistent_domain, contents->GetLastCommittedURL().GetHost());
}

// If the upgraded HTTPS URL is not available due to a potentially-transient
// exempted net error but the hostname is non-unique, don't show the net error
// page and instead just fallback to HTTP and the HTTPS-First Mode interstitial.
// Otherwise, the user can be stuck on the net error page when the HTTP version
// of the host would have resolved, such as for corp single-label hostnames.
// (Regression test for crbug.com/40915602.)
IN_PROC_BROWSER_TEST_P(
    HttpsUpgradesBrowserTest,
    ExemptNetErrorOnUpgrade_NonUniqueHostname_ShouldFallback) {
  // This test is only interesting when HTTPS-First Mode is fully enabled.
  if (!IsHttpsFirstModePrefEnabled()) {
    return;
  }

  GURL http_url = http_server()->GetURL("blorp", "/simple.html");
  GURL https_url = https_server()->GetURL("blorp", "/simple.html");
  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();

  // Set up an interceptor that will return ERR_NAME_NOT_RESOLVED. Navigating
  // to the HTTP URL should get upgraded to HTTPS, and then fallback to HTTP
  // and the HFM interstitial.
  auto dns_failure_interceptor =
      std::make_unique<content::URLLoaderInterceptor>(base::BindRepeating(
          [](content::URLLoaderInterceptor::RequestParams* params) {
            params->client->OnComplete(
                network::URLLoaderCompletionStatus(net::ERR_NAME_NOT_RESOLVED));
            return true;
          }));
  EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, http_url));
  EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
  ProceedThroughHttpsFirstModeInterstitial(contents);

  // Should now be on the HTTP URL and it should be allowlisted.
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
  content::SSLHostStateDelegate* state = profile->GetSSLHostStateDelegate();
  EXPECT_TRUE(state->IsHttpAllowedForHost(
      http_url.GetHost(),
      contents->GetPrimaryMainFrame()->GetStoragePartition()));

  ExpectUKMEntry(http_url, BlockingResult::kInterstitialProceed);
}

// If the upgraded HTTPS URL is not available due to an exempted net error but
// is to a single-label unique hostname (i.e. a TLD) don't show the net error
// page. This is the same as
// ExemptNetErrorOnUpgrade_NonUniqueHostname_ShouldFallback except with a unique
// one-label hostname.
// (Partial regression test for impact of crrev.com/c/5507613 on b/348330182.)
IN_PROC_BROWSER_TEST_P(
    HttpsUpgradesBrowserTest,
    ExemptNetErrorOnUpgrade_UniqueSingleLabelHostname_ShouldFallback) {
  // This test is only interesting when HTTPS-First Strict Mode is enabled.
  // Balanced Mode won't try to upgrade these requests at all.
  if (!IsHttpsFirstModePrefEnabled() || IsIncognito()) {
    return;
  }

  GURL http_url = http_server()->GetURL("cl", "/simple.html");
  GURL https_url = https_server()->GetURL("cl", "/simple.html");
  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();

  // Set up an interceptor that will return ERR_NAME_NOT_RESOLVED. Navigating
  // to the HTTP URL should get upgraded to HTTPS, and then fallback to HTTP
  // and the HFM interstitial.
  auto dns_failure_interceptor =
      std::make_unique<content::URLLoaderInterceptor>(base::BindRepeating(
          [](content::URLLoaderInterceptor::RequestParams* params) {
            params->client->OnComplete(
                network::URLLoaderCompletionStatus(net::ERR_NAME_NOT_RESOLVED));
            return true;
          }));
  EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, http_url));
  EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
  ProceedThroughHttpsFirstModeInterstitial(contents);

  // Should now be on the HTTP URL and the hostname should be allowlisted.
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
  content::SSLHostStateDelegate* state = profile->GetSSLHostStateDelegate();
  EXPECT_TRUE(state->IsHttpAllowedForHost(
      http_url.GetHost(),
      contents->GetPrimaryMainFrame()->GetStoragePartition()));

  ExpectUKMEntry(http_url, BlockingResult::kInterstitialProceed);
}

// Navigations in subframes should not get upgraded by HTTPS-Only Mode. They
// should be blocked as mixed content.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       HttpsParentHttpSubframeNavigation_Blocked) {
  const GURL parent_url(
      https_server()->GetURL("foo.com", "/iframe_blank.html"));
  const GURL iframe_url(http_server()->GetURL("foo.com", "/simple.html"));

  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, parent_url));

  content::TestNavigationObserver nav_observer(contents, 1);
  EXPECT_TRUE(content::NavigateIframeToURL(contents, "test", iframe_url));
  nav_observer.Wait();
  EXPECT_NE(iframe_url, nav_observer.last_navigation_url());

  // Verify that no navigation event metrics were recorded.
  histograms()->ExpectTotalCount(kEventHistogram, 0);
}

// Navigating to an HTTP URL in a subframe of an HTTP page should not upgrade
// the subframe navigation to HTTPS (even if the subframe navigation is to a
// different host than the parent frame).
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       HttpParentHttpSubframeNavigation_NotUpgraded) {
  // The parent frame will fail to upgrade to HTTPS.
  const GURL parent_url(
      http_server()->GetURL("bad-https.com", "/iframe_blank.html"));
  const GURL iframe_url(http_server()->GetURL("bar.com", "/simple.html"));

  // Navigate to `parent_url` and bypass the HTTPS-Only Mode warning.
  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateAndWaitForFallback(contents, parent_url);

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
    // Proceeding through the interstitial will add the hostname to the
    // allowlist.
    ProceedThroughHttpsFirstModeInterstitial(contents);
  }

  // Verify that navigation event metrics were recorded for the main frame.
  histograms()->ExpectTotalCount(kEventHistogram, 3);

  // Navigate the iframe to `iframe_url`. It should successfully navigate and
  // not get upgraded to HTTPS as the hostname is now in the allowlist.
  content::TestNavigationObserver nav_observer(contents, 1);
  EXPECT_TRUE(content::NavigateIframeToURL(contents, "test", iframe_url));
  nav_observer.Wait();
  EXPECT_EQ(iframe_url, nav_observer.last_navigation_url());

  // Verify that no new navigation event metrics were recorded for the subframe.
  histograms()->ExpectTotalCount(kEventHistogram, 3);
}

// Tests that a navigation to the HTTP version of a site with an HTTPS version
// that is slow to respond gets upgraded to HTTPS but times out and shows the
// HTTPS-Only Mode interstitial.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, SlowHttps_ShouldInterstitial) {
  // Set timeout to zero so that HTTPS upgrades immediately timeout.
  HttpsUpgradesNavigationThrottle::set_timeout_for_testing(base::TimeDelta());

  // Set up a custom HTTPS server that times out without sending a response.
  net::EmbeddedTestServer timeout_server{net::EmbeddedTestServer::TYPE_HTTPS};
  timeout_server.RegisterRequestHandler(base::BindLambdaForTesting(
      [&](const net::test_server::HttpRequest& request)
          -> std::unique_ptr<net::test_server::HttpResponse> {
        // Server will hang until destroyed.
        return std::make_unique<net::test_server::HungResponse>();
      }));
  ASSERT_TRUE(timeout_server.Start());
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(timeout_server.port());

  const GURL http_url = http_server()->GetURL("foo.com", "/simple.html");
  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateAndWaitForFallback(contents, http_url);

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
  }
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
}

// Tests that an HTTP POST form navigation to "bar.com" from an HTTP page on
// "foo.com" is not upgraded to HTTPS. (HTTP form navigations from HTTPS are
// blocked by the Mixed Forms warning.)
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, HttpPageHttpPost_NotUpgraded) {
  // Point the HTTP form target to "bar.com".
  base::StringPairs replacement_text;
  replacement_text.emplace_back(make_pair(
      "REPLACE_WITH_HOST_AND_PORT",
      net::HostPortPair::FromURL(http_server()->GetURL("foo.com", "/"))
          .ToString()));
  auto replacement_path = net::test_server::GetFilePathWithReplacements(
      "/ssl/page_with_form_targeting_http_url.html", replacement_text);

  // Navigate to the page hosting the form on "foo.com".
  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateToURLWithLinkTransitionBlockUntilNavigationsComplete(
      contents, http_server()->GetURL("bad-https.com", replacement_path), 1);

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    // The HTTPS-Only Mode interstitial should trigger.
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
    // Proceed through the interstitial to add the hostname to the allowlist.
    ProceedThroughHttpsFirstModeInterstitial(contents);
  }

  // Verify that navigation event metrics were recorded for the initial page.
  histograms()->ExpectTotalCount(kEventHistogram, 3);

  // Submit the form and wait for the navigation to complete.
  content::TestNavigationObserver nav_observer(contents, 1);
  ASSERT_TRUE(
      content::ExecJs(contents, "document.getElementById('submit').click();"));
  nav_observer.Wait();

  // Check that the navigation has ended up on the HTTP target.
  EXPECT_EQ("foo.com", contents->GetLastCommittedURL().GetHost());
  EXPECT_TRUE(contents->GetLastCommittedURL().SchemeIs(url::kHttpScheme));

  // Verify that no new navigation event metrics were recorded for the POST
  // navigation.
  histograms()->ExpectTotalCount(kEventHistogram, 3);
}

// Tests that if an HTTPS navigation redirects to HTTP on a different host, it
// should upgrade to HTTPS on that new host. (A downgrade redirect on the same
// host would imply a redirect loop.)
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       HttpsToHttpRedirect_ShouldUpgrade) {
  GURL target_url = http_server()->GetURL("bar.com", "/title1.html");
  GURL url = https_server()->GetURL("foo.com",
                                    "/server-redirect?" + target_url.spec());

  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();

  // NavigateToURL() returns `false` because the final redirected URL does not
  // match `url`. Separately ensure the navigation succeeded using a navigation
  // observer.
  content::TestNavigationObserver nav_observer(contents, 1);
  EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, url));
  nav_observer.Wait();
  EXPECT_TRUE(nav_observer.last_navigation_succeeded());

  // Verify that navigation event metrics were correctly recorded.
  EXPECT_TRUE(contents->GetLastCommittedURL().SchemeIs(url::kHttpsScheme));
  histograms()->ExpectTotalCount(kEventHistogram, 2);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeSucceeded, 1);

  EXPECT_EQ("bar.com", contents->GetLastCommittedURL().GetHost());
}

// Regression test for crbug.com/41488861.
// Tests that a slow fallback load is not cancelled with timeout.
// bad-ssl.com is configured to return a slow load over http. Navigating to
// http://bad-ssl.com should upgrade and immediately fall back, then display the
// http response without cancelling it for timeout.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       CancelTimeoutForFallbackNavigations) {
  net::EmbeddedTestServer http_server;
  net::EmbeddedTestServer https_server{net::EmbeddedTestServer::TYPE_HTTPS};

  // Make the HTTP server return a slow response.
  http_server.RegisterRequestHandler(base::BindRepeating(
      [](const net::test_server::HttpRequest& request)
          -> std::unique_ptr<net::test_server::HttpResponse> {
        // The HTTP load needs to be slower than the 1 second timeout configured
        // by the the test.
        auto slow_http_response =
            std::make_unique<net::test_server::DelayedHttpResponse>(
                base::Seconds(2));
        slow_http_response->set_content_type("text/html");
        slow_http_response->set_content("hello from http");
        return std::move(slow_http_response);
      }));
  ASSERT_TRUE(http_server.Start());
  ASSERT_TRUE(https_server.Start());

  // Set the timeout short enough, but not zero. We can't set it to zero
  // because it'll cancel the HTTPS load with timeout instead of an error.
  HttpsUpgradesNavigationThrottle::set_timeout_for_testing(base::Seconds(1));

  HttpsUpgradesInterceptor::SetHttpPortForTesting(http_server.port());
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(https_server.port());

  GURL http_url(http_server.GetURL("bad-https.com", "/"));
  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateAndWaitForFallback(contents, http_url);
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
    histograms()->ExpectTotalCount("Net.ErrorCodesForMainFrame4", 1);
    histograms()->ExpectBucketCount("Net.ErrorCodesForMainFrame4",
                                    -net::ERR_ABORTED, 1);
  } else {
    // Shouldn't record any net errors.
    EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
    histograms()->ExpectTotalCount("Net.ErrorCodesForMainFrame4", 0);
  }

  histograms()->ExpectTotalCount(kEventHistogram, 3);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeFailed, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeCertError, 1);
}

// Creates a redirect response.
std::unique_ptr<net::test_server::HttpResponse> RedirectResponseHandler(
    const GURL& dest_url,
    const net::test_server::HttpRequest& request) {
  std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
      new net::test_server::BasicHttpResponse);
  http_response->set_code(net::HTTP_TEMPORARY_REDIRECT);
  http_response->AddCustomHeader("Location", dest_url.spec());
  return std::move(http_response);
}

// Creates a response that causes a redirect loop over https and returns a slow
// response over http.
std::unique_ptr<net::test_server::HttpResponse> RedirectLoopResponse(
    int& http_port,
    int& https_port,
    const net::test_server::HttpRequest& request) {
  if (request.GetURL().GetPath() == "/redirect") {
    // Over https, this URL redirects to itself.
    if (request.GetURL().SchemeIs("https")) {
      GURL url(base::StringPrintf("http://a.com:%d/redirect", http_port));
      return RedirectResponseHandler(url, request);
    }
    // Over http, it prints a slow hello. This should delay longer than the
    // HTTPS upgrade timeout which is set to 1 second.
    auto slow_http_response =
        std::make_unique<net::test_server::DelayedHttpResponse>(
            base::Seconds(2));
    slow_http_response->set_content_type("text/html");
    slow_http_response->set_content("hello from http");
    return std::move(slow_http_response);
  }
  return nullptr;
}

// Another regression test for crbug.com/41488861.
// Tests that a slow load that's detected as a redirect loop will not display
// a flash of a net error page.
//
// Assume that a.com/redirect:
// - Shows a slow loading response when loaded over http
// - Redirects to http://a.com/redirect when loaded over https.
//
// The flow of the test is as follows:
// 1. Load http://a.com/redirect.
// 2. http://a.com/redirect is upgraded to http.
// 3. https://a.com/redirect redirects to http://a.com/redirect
// 4. This triggers a redirect loop (the URL was seen at step 1).
// 5. a.com is allowlisted and http://a.com/redirect is loaded as fallback.
// 6. http://a.com/redirect prints a message after a slow load.
//
// This flow should never display a net error page with ERR_TIMED_OUT to the
// user. If the interstitial is enabled, it should be displayed at the final
// step.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       RedirectLoopWithSlowRedirect_ShouldInterstitial) {
  net::EmbeddedTestServer redirect_server_http;
  net::EmbeddedTestServer redirect_server_https{
      net::EmbeddedTestServer::TYPE_HTTPS};

  // Set the timeout short enough, but not zero. We can't set it to zero
  // because it'll cancel the HTTPS load with timeout instead of an error.
  // The HTTP load in this test needs to be slower than this timeout for the
  // test to be meaningful.
  HttpsUpgradesNavigationThrottle::set_timeout_for_testing(base::Seconds(1));

  // We don't know the ports without starting the servers and we can't start
  // the servers without registering request handlers. Pass them as refs so
  // that we can change them later.
  int http_port = 0;
  int https_port = 0;
  redirect_server_http.RegisterRequestHandler(base::BindRepeating(
      &RedirectLoopResponse, std::ref(http_port), std::ref(https_port)));
  redirect_server_https.RegisterRequestHandler(base::BindRepeating(
      &RedirectLoopResponse, std::ref(http_port), std::ref(https_port)));

  ASSERT_TRUE(redirect_server_http.Start());
  ASSERT_TRUE(redirect_server_https.Start());

  http_port = redirect_server_http.port();
  https_port = redirect_server_https.port();

  HttpsUpgradesInterceptor::SetHttpPortForTesting(redirect_server_http.port());
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(
      redirect_server_https.port());

  GURL http_url(redirect_server_http.GetURL("a.com", "/redirect"));
  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateAndWaitForFallback(contents, http_url);
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
    histograms()->ExpectTotalCount("Net.ErrorCodesForMainFrame4", 1);
    histograms()->ExpectBucketCount("Net.ErrorCodesForMainFrame4",
                                    -net::ERR_ABORTED, 1);
  } else {
    // Shouldn't record any net errors.
    EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
    histograms()->ExpectTotalCount("Net.ErrorCodesForMainFrame4", 0);
  }

  histograms()->ExpectTotalCount(kEventHistogram, 3);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeFailed, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeRedirectLoop,
                                  1);
}

// Tests that navigating to an HTTPS page that downgrades to HTTP on the same
// host will fail and trigger the HTTPS-Only Mode interstitial (due to
// interceptor detecting a redirect loop and triggering fallback).
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       RedirectLoop_ShouldInterstitial) {
  // Set up a new test server instance so it can have a custom handler.
  net::EmbeddedTestServer downgrading_server{
      net::EmbeddedTestServer::TYPE_HTTPS};
  // Downgrade by swapping the scheme for HTTP. HTTPS-Only Mode will upgrade it
  // back to HTTPS.
  downgrading_server.RegisterRequestHandler(base::BindLambdaForTesting(
      [&](const net::test_server::HttpRequest& request)
          -> std::unique_ptr<net::test_server::HttpResponse> {
        GURL::Replacements http_downgrade;
        http_downgrade.SetSchemeStr(url::kHttpScheme);
        // The HttpRequest will by default refer to the test server by the
        // loopback address rather than any hostname in the navigation (i.e.,
        // the EmbeddedTestServer has no notion of virtual hosts). This
        // explicitly sets the hostname back to the test host so that this
        // doesn't fail due to the exception for localhost.
        http_downgrade.SetHostStr("foo.com");
        auto redirect_url = request.GetURL().ReplaceComponents(http_downgrade);
        auto response = std::make_unique<net::test_server::BasicHttpResponse>();
        response->set_code(net::HTTP_TEMPORARY_REDIRECT);
        response->AddCustomHeader("Location", redirect_url.spec());
        return response;
      }));
  ASSERT_TRUE(downgrading_server.Start());
  HttpsUpgradesInterceptor::SetHttpPortForTesting(downgrading_server.port());
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(downgrading_server.port());

  GURL url = downgrading_server.GetURL("foo.com", "/");
  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateAndWaitForFallback(contents, url);

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
  }

  // Verify that navigation event metrics were correctly recorded.
  histograms()->ExpectTotalCount(kEventHistogram, 3);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeFailed, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeRedirectLoop,
                                  1);
}

// Tests that the security level is WARNING when the HTTPS-Only Mode
// interstitial is shown for a net error on HTTPS. (Without HTTPS-Only Mode, a
// net error would be a security level of NONE.)
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       NetErrorOnUpgrade_SecurityLevelWarning) {
  GURL http_url = http_server()->GetURL("foo.com", "/close-socket");
  GURL https_url = https_server()->GetURL("foo.com", "/close-socket");

  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateAndWaitForFallback(contents, http_url);
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));

    EXPECT_EQ(security_state::WARNING,
              chrome_security_state::GetSecurityLevel(contents));

    // Proceed through the interstitial to navigate to the HTTP site.
    ProceedThroughHttpsFirstModeInterstitial(contents);
  }

  // The HTTP site results in a net error, which should have security level NONE
  // (as no connection was made).
  // TODO(crbug.com/40248833): Uncomment once upgrades are tracked
  // per-navigation.
  // EXPECT_EQ(security_state::NONE,
  // chrome_security_state::GetSecurityLevel(contents));
}

// Tests that the security level is WARNING when the HTTPS-Only Mode
// interstitial is shown for a cert error on HTTPS. (Without HTTPS-Only Mode, a
// a cert error would be a security level of DANGEROUS.) After clicking through
// the interstitial, the security level should still be WARNING.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       BrokenSSLOnUpgrade_SecurityLevelWarning) {
  GURL http_url = http_server()->GetURL("bad-https.com", "/simple.html");
  GURL https_url = https_server()->GetURL("bad-https.com", "/simple.html");

  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateAndWaitForFallback(contents, http_url);
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));

    EXPECT_EQ(security_state::WARNING,
              chrome_security_state::GetSecurityLevel(contents));

    // Proceed through the interstitial to navigate to the HTTP page.
    ProceedThroughHttpsFirstModeInterstitial(contents);
  }

  // The security level should still be WARNING.
  EXPECT_EQ(security_state::WARNING,
            chrome_security_state::GetSecurityLevel(contents));
}

// Regression test for crbug.com/40780767.
// Tests the case where the HTTP version of a site redirects to HTTPS, but the
// HTTPS version of the site has a cert error. If the user initially navigates
// to the HTTP URL, then HTTPS-First Mode should upgrade the navigation to HTTPS
// and trigger the HTTPS-First Mode interstitial when that fails, but if the
// user clicks through the HTTPS-First Mode interstitial and falls back into the
// HTTP->HTTPS redirect back to the cert error, then the SSL interstitial should
// be shown and the user should be able to click through the SSL interstitial to
// visit the HTTPS version of the site (but in a DANGEROUS security level
// state).
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       HttpsUpgradeWithBrokenSSL_ShouldTriggerSSLInterstitial) {
  // Set up a new test server instance so it can have a custom handler that
  // redirects to the HTTPS server.
  net::EmbeddedTestServer upgrading_server{net::EmbeddedTestServer::TYPE_HTTP};
  upgrading_server.RegisterRequestHandler(base::BindLambdaForTesting(
      [&](const net::test_server::HttpRequest& request)
          -> std::unique_ptr<net::test_server::HttpResponse> {
        auto response = std::make_unique<net::test_server::BasicHttpResponse>();
        response->set_code(net::HTTP_TEMPORARY_REDIRECT);
        response->AddCustomHeader(
            "Location",
            "https://bad-https.com:" +
                base::NumberToString(
                    HttpsUpgradesInterceptor::GetHttpsPortForTesting()) +
                "/simple.html");
        return response;
      }));
  ASSERT_TRUE(upgrading_server.Start());
  HttpsUpgradesInterceptor::SetHttpPortForTesting(upgrading_server.port());

  GURL http_url = upgrading_server.GetURL("bad-https.com", "/simple.html");
  // HTTPS server will have a cert error.
  GURL https_url = https_server()->GetURL("bad-https.com", "/simple.html");

  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateAndWaitForFallback(contents, http_url);

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    // The HTTPS-First Mode interstitial should trigger first.
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));

    // Proceeding through the HTTPS-First Mode interstitial will hit the
    // upgrading server's HTTP->HTTPS redirect. This should result in an SSL
    // interstitial (not an HTTPS-First Mode interstitial).
    ProceedThroughHttpsFirstModeInterstitial(contents);
  }

  EXPECT_EQ(https_url, contents->GetLastCommittedURL());
  EXPECT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(contents));

  // Proceeding through the SSL interstitial should navigate to the HTTPS
  // version of the site but with the DANGEROUS security level.
  ProceedThroughHttpsFirstModeInterstitial(contents);
  EXPECT_EQ(https_url, contents->GetLastCommittedURL());
  EXPECT_EQ(security_state::DANGEROUS,
            chrome_security_state::GetSecurityLevel(contents));

  // Verify that navigation event metrics were correctly recorded. They should
  // only have been recorded for the initial navigation that resulted in the
  // HTTPS-First Mode interstitial.
  histograms()->ExpectTotalCount(kEventHistogram, 3);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeFailed, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeCertError, 1);

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    // Verify that the interstitial metrics were correctly recorded.
    histograms()->ExpectBucketCount(
        "interstitial.https_first_mode.decision",
        security_interstitials::MetricsHelper::Decision::SHOW, 1);
    histograms()->ExpectBucketCount(
        "interstitial.https_first_mode.decision",
        security_interstitials::MetricsHelper::Decision::PROCEED, 1);
  }
}

// Tests that clicking the "Learn More" link in the HTTPS-First Mode
// interstitial opens a new tab for the help center article.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, InterstitialLearnMoreLink) {
  // This test is only relevant to HTTPS-First Mode.
  if (!IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    return;
  }

  GURL http_url = http_server()->GetURL("foo.com", "/close-socket");
  GURL https_url = https_server()->GetURL("foo.com", "/close-socket");

  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateAndWaitForFallback(contents, http_url);
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());

  EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));

  // Simulate clicking the learn more link.
  auto* tab_interface = tabs::TabInterface::MaybeGetFromContents(contents);
  auto* controller = tab_interface
                         ? AskBeforeHttpDialogController::From(tab_interface)
                         : nullptr;
  if (controller && controller->HasOpenDialog()) {
    controller->ClickLearnMoreForTesting();
  } else {
    ASSERT_TRUE(content::ExecJs(
        contents, "window.certificateErrorPageController.openHelpCenter();"));
  }

  // New tab should include the p-link "first_mode".
  EXPECT_EQ(GetBrowser()
                ->tab_strip_model()
                ->GetActiveWebContents()
                ->GetVisibleURL()
                .GetQuery(),
            "p=first_mode");

  // Verify that the interstitial metrics were correctly recorded.
  histograms()->ExpectBucketCount(
      "interstitial.https_first_mode.decision",
      security_interstitials::MetricsHelper::Decision::SHOW, 1);
  histograms()->ExpectBucketCount(
      "interstitial.https_first_mode.interaction",
      security_interstitials::MetricsHelper::Interaction::TOTAL_VISITS, 1);
  histograms()->ExpectBucketCount(
      "interstitial.https_first_mode.interaction",
      security_interstitials::MetricsHelper::Interaction::SHOW_LEARN_MORE, 1);
}

// Tests that if the user bypasses the HTTPS-First Mode interstitial, and then
// later the server fixes their HTTPS support and the user successfully connects
// over HTTPS, the allowlist entry is cleared (so HFM will kick in again for
// that site).
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, BadHttpsFollowedByGoodHttps) {
  // TODO(crbug.com/40248833): This test is flakey when only HTTPS Upgrades are
  // enabled.
  if (!IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    return;
  }

  GURL http_url = http_server()->GetURL("foo.com", "/close-socket");
  GURL bad_https_url = https_server()->GetURL("foo.com", "/close-socket");
  GURL good_https_url = https_server()->GetURL("foo.com", "/ssl/google.html");

  ASSERT_EQ(http_url.GetHost(), bad_https_url.GetHost());
  ASSERT_EQ(bad_https_url.GetHost(), good_https_url.GetHost());

  auto* tab = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  auto* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
  auto* state = static_cast<StatefulSSLHostStateDelegate*>(
      profile->GetSSLHostStateDelegate());

  // First check that main frame requests revoke the decision.

  // Navigate to `http_url`, which will get upgraded to `bad_https_url`.
  NavigateAndWaitForFallback(tab, http_url);

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    ASSERT_TRUE(IsShowingHttpsFirstModeInterstitial(tab));
    ProceedThroughHttpsFirstModeInterstitial(tab);
  }

  EXPECT_TRUE(state->HasAllowException(
      http_url.GetHost(), tab->GetPrimaryMainFrame()->GetStoragePartition()));

  EXPECT_TRUE(NavigateToURLWithLinkTransition(tab, good_https_url));
  EXPECT_FALSE(state->HasAllowException(
      http_url.GetHost(), tab->GetPrimaryMainFrame()->GetStoragePartition()));

  // Rarely, an open connection with the bad cert might be reused for the next
  // navigation, which is supposed to show an interstitial. Close open
  // connections to ensure a fresh connection (and certificate validation) for
  // the next navigation. See https://crbug.com/40732511. A deeper fix for this
  // issue would be to unify certificate bypass logic which is currently split
  // between the net stack and content layer; see https://crbug.com/40418163.
  // See also: SSLUITest.BadCertFollowedByGoodCert.
  state->RevokeUserAllowExceptionsHard(http_url.GetHost());

  // Now check that subresource requests revoke the decision.

  // Navigate to `http_url`, which will get upgraded to `bad_https_url`.
  NavigateAndWaitForFallback(tab, http_url);

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    ASSERT_TRUE(IsShowingHttpsFirstModeInterstitial(tab));
    ProceedThroughHttpsFirstModeInterstitial(tab);
  }

  EXPECT_TRUE(state->HasAllowException(
      http_url.GetHost(), tab->GetPrimaryMainFrame()->GetStoragePartition()));

  // Load "logo.gif" as an image on the page.
  GURL image = https_server()->GetURL("foo.com", "/ssl/google_files/logo.gif");

  // TODO(crbug.com/422956041): this fetch generates an LNA request, its unclear
  // why. Investigation is required to see if this is an LNA bug or not.
  EXPECT_EQ(
      true,
      EvalJs(tab,
             std::string("var img = document.createElement('img');img.src ='") +
                 image.spec() +
                 "';"
                 "new Promise(resolve => {"
                 "  img.onload=function() { "
                 "    resolve(true); };"
                 "  document.body.appendChild(img);"
                 "});"));

  EXPECT_FALSE(state->HasAllowException(
      http_url.GetHost(), tab->GetPrimaryMainFrame()->GetStoragePartition()));
}

// Tests that clicking the "Go back" button in the HTTPS-First Mode interstitial
// navigates back to the previous page (about:blank in this case).
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, InterstitialGoBack) {
  // This test is only relevant to HTTPS-First Mode.
  if (!IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    return;
  }

  GURL http_url = http_server()->GetURL("foo.com", "/close-socket");
  GURL https_url = https_server()->GetURL("foo.com", "/close-socket");

  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateAndWaitForFallback(contents, http_url);
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());

  EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));

  // Simulate clicking the "Go back" button.
  DontProceedThroughHttpsFirstModeInterstitial(contents);

  EXPECT_EQ(GURL("about:blank"), contents->GetLastCommittedURL());

  // Verify that the interstitial metrics were correctly recorded.
  histograms()->ExpectBucketCount(
      "interstitial.https_first_mode.decision",
      security_interstitials::MetricsHelper::Decision::SHOW, 1);
  histograms()->ExpectBucketCount(
      "interstitial.https_first_mode.decision",
      security_interstitials::MetricsHelper::Decision::DONT_PROCEED, 1);

  ExpectUKMEntry(http_url, BlockingResult::kInterstitialDontProceed);
}

// Tests that closing the tab of the HTTPS-First Mode interstitial counts as
// not proceeding through the interstitial for metrics.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, CloseInterstitialTab) {
  // This test is only relevant to HTTPS-First Mode.
  if (!IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    return;
  }

  GURL http_url = http_server()->GetURL("foo.com", "/close-socket");
  GURL https_url = https_server()->GetURL("foo.com", "/close-socket");

  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateAndWaitForFallback(contents, http_url);
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());

  EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));

  // Leave the interstitial by closing the tab.
  chrome::CloseWebContents(GetBrowser(), contents, false);

  // Verify that the interstitial metrics were correctly recorded.
  histograms()->ExpectBucketCount(
      "interstitial.https_first_mode.decision",
      security_interstitials::MetricsHelper::Decision::SHOW, 1);
  histograms()->ExpectBucketCount(
      "interstitial.https_first_mode.decision",
      security_interstitials::MetricsHelper::Decision::DONT_PROCEED, 1);

  ExpectUKMEntry(http_url, BlockingResult::kInterstitialDontProceed);
}

// Tests that if a user allowlists a host and then does not visit it again for
// seven days (the expiration period), then the interstitial will be shown again
// the next time they visit the host.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, AllowlistEntryExpires) {
  content::WebContents* contents =
      GetBrowser()->tab_strip_model()->GetActiveWebContents();
  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
  content::SSLHostStateDelegate* state = profile->GetSSLHostStateDelegate();

  // Set a testing clock on the StatefulSSLHostStateDelegate, keeping a pointer
  // to the clock object around so the test can manipulate time. `chrome_state`
  // takes ownership of `clock`.
  auto clock = std::make_unique<base::SimpleTestClock>();
  auto* clock_ptr = clock.get();
  StatefulSSLHostStateDelegate* chrome_state =
      static_cast<StatefulSSLHostStateDelegate*>(state);
  chrome_state->SetClockForTesting(std::move(clock));

  // Start the clock at standard system time.
  clock_ptr->SetNow(base::Time::NowFromSystemTime());

  // Visit a host that doesn't support HTTPS for the first time, and click
  // through the HTTPS-First Mode interstitial to allowlist the host.
  GURL http_url = http_server()->GetURL("bad-https.com", "/simple.html");
  NavigateAndWaitForFallback(contents, http_url);

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
    ProceedThroughHttpsFirstModeInterstitial(contents);
  }

  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
  EXPECT_TRUE(state->IsHttpAllowedForHost(
      http_url.GetHost(),
      contents->GetPrimaryMainFrame()->GetStoragePartition()));

  // Simulate the clock advancing by 16 days, which is past the expiration
  // point.
  clock_ptr->Advance(base::Days(16));

  // The host should no longer be allowlisted, and the interstitial should
  // trigger again.
  EXPECT_FALSE(state->IsHttpAllowedForHost(
      http_url.GetHost(),
      contents->GetPrimaryMainFrame()->GetStoragePartition()));
  NavigateAndWaitForFallback(contents, http_url);

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
  }
}

// Tests that re-visiting an allowlisted host bumps the expiration time to a new
// seven days in the future from now.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, RevisitingBumpsExpiration) {
  content::WebContents* contents =
      GetBrowser()->tab_strip_model()->GetActiveWebContents();
  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
  content::SSLHostStateDelegate* state = profile->GetSSLHostStateDelegate();

  // Set a testing clock on the StatefulSSLHostStateDelegate, keeping a pointer
  // to the clock object around so the test can manipulate time. `chrome_state`
  // takes ownership of `clock`.
  auto clock = std::make_unique<base::SimpleTestClock>();
  auto* clock_ptr = clock.get();
  StatefulSSLHostStateDelegate* chrome_state =
      static_cast<StatefulSSLHostStateDelegate*>(state);
  chrome_state->SetClockForTesting(std::move(clock));

  // Start the clock at standard system time.
  clock_ptr->SetNow(base::Time::NowFromSystemTime());

  // Visit a host that doesn't support HTTPS for the first time, and click
  // through the HTTPS-First Mode interstitial to allowlist the host.
  GURL http_url = http_server()->GetURL("bad-https.com", "/simple.html");
  NavigateAndWaitForFallback(contents, http_url);

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
    ProceedThroughHttpsFirstModeInterstitial(contents);
  }

  EXPECT_EQ(http_url, contents->GetLastCommittedURL());

  EXPECT_TRUE(state->IsHttpAllowedForHost(
      http_url.GetHost(),
      contents->GetPrimaryMainFrame()->GetStoragePartition()));

  // Simulate the clock advancing by ten days.
  clock_ptr->Advance(base::Days(10));

  // Navigate to the host again; this will reset the allowlist expiration to
  // now + 7 days.
  EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, http_url));

  // Simulate the clock advancing another ten days. This will be _after_ the
  // initial expiration date of the allowlist entry, but _before_ the bumped
  // expiration date from the second navigation.
  clock_ptr->Advance(base::Days(10));
  EXPECT_TRUE(state->IsHttpAllowedForHost(
      http_url.GetHost(),
      contents->GetPrimaryMainFrame()->GetStoragePartition()));
  EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, http_url));
  EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
}

// Tests that if a hostname has an HSTS entry registered, then HTTPS-First Mode
// should not try to upgrade it (instead allowing HSTS to handle the upgrade as
// it is more strict).
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, PreferHstsOverHttpsFirstMode) {
  content::WebContents* contents =
      GetBrowser()->tab_strip_model()->GetActiveWebContents();
  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());

  // URL for HTTPS server that will result in a certificate error.
  GURL https_url = https_server()->GetURL("bad-https.com", "/simple.html");

  // HTTP version of that URL that will get upgraded to HTTPS (but with the
  // correct port for the HTTPS server -- the test code can configure
  // HTTPS-First Mode to be aware of the different ports, but can't do that for
  // HSTS).
  GURL::Replacements downgrade_scheme_to_http;
  downgrade_scheme_to_http.SetSchemeStr(url::kHttpScheme);
  GURL http_url = https_url.ReplaceComponents(downgrade_scheme_to_http);

  // Set HTTP testing port to match `http_url`.
  HttpsUpgradesInterceptor::SetHttpPortForTesting(http_url.EffectiveIntPort());

  // Add hostname to the TransportSecurityState.
  base::Time expiry = base::Time::Now() + base::Days(100);
  bool include_subdomains = false;
  auto* network_context =
      profile->GetDefaultStoragePartition()->GetNetworkContext();
  base::RunLoop run_loop;
  network_context->AddHSTS(http_url.GetHost(), expiry, include_subdomains,
                           run_loop.QuitClosure());
  run_loop.Run();

  // Navigate to the HTTP URL. It should get upgraded to HTTPS and trigger a
  // fatal certificate error (because of HTTPS) instead of falling back to the
  // HTTPS-First Mode interstitial.
  EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, http_url));
  EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
  EXPECT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(contents));

  // Verify that no HFM event histograms were emitted (to check that HFM did not
  // trigger for this navigation at all).
  histograms()->ExpectTotalCount(kEventHistogram, 0);

  // Verify that general navigation request metrics were recorded.
  histograms()->ExpectTotalCount(kNavigationRequestSecurityLevelHistogram, 2);
  histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                  NavigationRequestSecurityLevel::kHstsUpgraded,
                                  1);
  histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                  NavigationRequestSecurityLevel::kSecure, 1);
}

// Regression test for crbug.com/40806549. Previously, performing back/forward
// navigations around the HTTPS-First Mode interstitial could cause history
// entries to dropped.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       InterstitialFallbackMaintainsHistory) {
  // This test only applies to HTTPS-First Mode.
  if (!IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    return;
  }

  GURL good_https_url = https_server()->GetURL("site1.com", "/defaultresponse");

  // Set up a new test server instance so it can have a custom handler.
  net::EmbeddedTestServer downgrading_server{
      net::EmbeddedTestServer::TYPE_HTTPS};
  // Downgrade by swapping the scheme for HTTP. HTTPS-First Mode will upgrade it
  // back to HTTPS.
  downgrading_server.RegisterRequestHandler(base::BindLambdaForTesting(
      [&](const net::test_server::HttpRequest& request)
          -> std::unique_ptr<net::test_server::HttpResponse> {
        GURL::Replacements http_downgrade;
        http_downgrade.SetSchemeStr(url::kHttpScheme);
        // The HttpRequest will by default refer to the test server by the
        // loopback address rather than any hostname in the navigation (i.e.,
        // the EmbeddedTestServer has no notion of virtual hosts). This
        // explicitly sets the hostname back to the test host so that this
        // doesn't fail due to the exception for localhost.
        http_downgrade.SetHostStr("site2.com");
        auto redirect_url = request.GetURL().ReplaceComponents(http_downgrade);
        auto response = std::make_unique<net::test_server::BasicHttpResponse>();
        response->set_code(net::HTTP_TEMPORARY_REDIRECT);
        response->AddCustomHeader("Location", redirect_url.spec());
        return response;
      }));
  ASSERT_TRUE(downgrading_server.Start());
  HttpsUpgradesInterceptor::SetHttpPortForTesting(downgrading_server.port());
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(downgrading_server.port());

  GURL downgrading_https_url = downgrading_server.GetURL("site2.com", "/");
  GURL::Replacements swap_http_scheme;
  swap_http_scheme.SetSchemeStr(url::kHttpScheme);
  GURL downgrading_http_url =
      downgrading_https_url.ReplaceComponents(swap_http_scheme);

  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();

  // Navigate to a "good" HTTPS site.
  EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, good_https_url));

  // Navigate to the HTTP version of `downgrading_https_url`, which will get
  // upgraded to HTTPS and fail, triggering the HTTPS-First Mode
  // interstitial.
  NavigateToURLWithLinkTransitionBlockUntilNavigationsComplete(
      contents, downgrading_http_url, 1);
  EXPECT_EQ(downgrading_http_url, contents->GetLastCommittedURL());
  EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));

  // Simulate clicking the browser "back" button.
  EXPECT_TRUE(content::HistoryGoBack(contents));
  EXPECT_EQ(good_https_url, contents->GetLastCommittedURL());
  EXPECT_EQ(security_state::SECURE,
            chrome_security_state::GetSecurityLevel(contents));

  // Simulate clicking the browser "forward" button. The HistoryGoForward()
  // call returns `false` because it is an error page.
  EXPECT_FALSE(content::HistoryGoForward(contents));
  EXPECT_EQ(downgrading_http_url, contents->GetLastCommittedURL());
  EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));

  // No forward entry should be present.
  EXPECT_FALSE(contents->GetController().CanGoForward());

  // Simulate clicking the browser "back" button again. Previously this would
  // result in `about:blank` being shown.
  EXPECT_TRUE(content::HistoryGoBack(contents));
  EXPECT_EQ(good_https_url, contents->GetLastCommittedURL());

  // Repeat forward one last time. (Previously the user would no longer be able
  // to go back any more as the history entries were lost.)
  EXPECT_FALSE(content::HistoryGoForward(contents));  // error page -> false
  EXPECT_EQ(downgrading_http_url, contents->GetLastCommittedURL());
  EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
  EXPECT_TRUE(contents->GetController().CanGoBack());
}

// Tests that if the HttpAllowlist enterprise policy is set, then HTTPS upgrades
// are skipped for hosts in the allowlist. Includes simple hostname, wildcard
// hostname pattern, and bare IP address cases.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       EnterpriseAllowlistDisablesUpgrades) {
  content::WebContents* contents =
      GetBrowser()->tab_strip_model()->GetActiveWebContents();

  // Without any policy allowlist, navigate to HTTP URL on foo.com. It *should*
  // get upgraded to HTTPS.
  auto http_url = http_server()->GetURL("foo.com", "/simple.html");
  auto https_url = https_server()->GetURL("foo.com", "/simple.html");
  EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, http_url));
  EXPECT_EQ(https_url, contents->GetLastCommittedURL());

  // Artificially add the pref that gets mapped from the enterprise policy.
  auto* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
  auto* prefs = profile->GetPrefs();
  base::ListValue allowlist;
  allowlist.Append("foo.com");
  allowlist.Append("[*.]bar.com");
  allowlist.Append(http_server()->GetIPLiteralString());
  // These cases should not work, but the policy->pref mapping won't immediately
  // reject them.
  allowlist.Append("[*]");
  allowlist.Append("*");
  prefs->SetList(prefs::kHttpAllowlist, std::move(allowlist));

  // Navigate to HTTP URL on foo.com. It should not get upgraded to HTTPS and
  // no interstitial should be shown.
  http_url = http_server()->GetURL("foo.com", "/simple.html");
  https_url = https_server()->GetURL("foo.com", "/simple.html");
  EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, http_url));
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
  EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));

  // Navigate to HTTP URL on bar.com. Same result.
  http_url = http_server()->GetURL("bar.com", "/simple.html");
  https_url = https_server()->GetURL("bar.com", "/simple.html");
  EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, http_url));
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
  EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));

  // Navigate to HTTP URL on bar.bar.com. Same result as subdomain wildcard
  // was specified.
  http_url = http_server()->GetURL("bar.bar.com", "/simple.html");
  https_url = https_server()->GetURL("bar.bar.com", "/simple.html");
  EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, http_url));
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
  EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));

  // Navigate to HTTP URL on foo.foo.com. Subdomains of foo.com should not be
  // considered as being in the allowlist as no wildcard was specified. This
  // should get upgraded to HTTPS.
  http_url = http_server()->GetURL("foo.foo.com", "/simple.html");
  https_url = https_server()->GetURL("foo.foo.com", "/simple.html");
  EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, http_url));
  EXPECT_EQ(https_url, contents->GetLastCommittedURL());

  // Navigate to HTTP URL on baz.com, which is not on the allowlist. Should get
  // upgraded to HTTPS.
  http_url = http_server()->GetURL("baz.com", "/simple.html");
  https_url = https_server()->GetURL("baz.com", "/simple.html");
  EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, http_url));
  EXPECT_EQ(https_url, contents->GetLastCommittedURL());

  // Navigate to HTTP URL on the HTTP test server's IP address. It should not
  // get upgraded to HTTPS and no interstitial should be shown.
  http_url = http_server()->GetURL("/simple.html");
  https_url = https_server()->GetURL("/simple.html");
  EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, http_url));
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
  EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
}

// Tests that if the HttpAllowlist enterprise policy is set, then HTTPS upgrades
// and HTTPS-First Mode For Site Engagement checks are skipped for hosts in the
// allowlist.
IN_PROC_BROWSER_TEST_P(
    HttpsUpgradesBrowserTest,
    EnterpriseAllowlistDisablesHttpsFirstModeForSiteEngagament) {
  // Skip this test when HTTPS-First Mode for Site Engagement isn't enabled.
  if (!IsSiteEngagementHeuristicEnabled()) {
    return;
  }
  // Disable the testing port configuration, as this test doesn't use the
  // EmbeddedTestServer.
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(0);
  HttpsUpgradesInterceptor::SetHttpPortForTesting(0);
  auto url_loader_interceptor = MakeInterceptorForSiteEngagementHeuristic();

  content::WebContents* contents =
      GetBrowser()->tab_strip_model()->GetActiveWebContents();
  auto* profile = Profile::FromBrowserContext(contents->GetBrowserContext());

  // Without any policy allowlist, navigate to an HTTP URL. It should show the
  // HFM+SE interstitial.
  GURL http_url("http://bad-https.com");
  GURL https_url("https://bad-https.com");
  SetSiteEngagementScore(http_url, kLowSiteEngagementScore);
  SetSiteEngagementScore(https_url, kHighSiteEnagementScore);

  HttpsFirstModeService* hfm_service =
      HttpsFirstModeServiceFactory::GetForProfile(profile);
  MaybeEnableHttpsFirstModeForEngagedSitesAndWait(hfm_service);

  EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, http_url));
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
  EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));

  // Artificially add the pref that gets mapped from the enterprise policy.
  auto* prefs = profile->GetPrefs();
  base::ListValue allowlist;
  allowlist.Append("bad-https.com");
  prefs->SetList(prefs::kHttpAllowlist, std::move(allowlist));

  // Navigate to the same URL. It should not get upgraded to HTTPS and
  // no interstitial should be shown.
  EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, http_url));
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
  EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
}

IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       EnterprisePolicyDisablesUpgrades) {
  // Disable HTTPS-Upgrades via enterprise policy.
  auto* prefs = browser()->GetProfile()->GetPrefs();
  prefs->SetBoolean(prefs::kHttpsUpgradesEnabled, false);

  content::WebContents* contents =
      GetBrowser()->tab_strip_model()->GetActiveWebContents();
  GURL http_url = http_server()->GetURL("foo.com", "/simple.html");
  GURL https_url = https_server()->GetURL("foo.com", "/simple.html");

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    // HTTPS-First Mode should supersede HTTPS-Upgrades and upgrade the
    // navigation despite the HttpsUpgradeMode policy setting.
    EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, http_url));
    EXPECT_EQ(https_url, contents->GetLastCommittedURL());
    histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                    NavigationRequestSecurityLevel::kUpgraded,
                                    1);
  } else {
    // If HTTPS-First Mode is not enabled but upgrading is, then the policy
    // should prevent the upgrade.
    EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, http_url));
    EXPECT_EQ(http_url, contents->GetLastCommittedURL());
    histograms()->ExpectBucketCount(
        kNavigationRequestSecurityLevelHistogram,
        NavigationRequestSecurityLevel::kAllowlisted, 1);
  }
}

// Test that HTTPS Upgrades are skipped if the "Insecure content" site setting
// is set to "allow".
// MIXED_SCRIPT isn't enabled as a content setting on Android.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_InsecureContentSettingDisablesUpgrades \
  DISABLED_InsecureContentSettingDisablesUpgrades
#else
#define MAYBE_InsecureContentSettingDisablesUpgrades \
  InsecureContentSettingDisablesUpgrades
#endif
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       MAYBE_InsecureContentSettingDisablesUpgrades) {
  content::WebContents* contents =
      GetBrowser()->tab_strip_model()->GetActiveWebContents();
  GURL http_url = http_server()->GetURL("foo.com", "/simple.html");
  GURL https_url = https_server()->GetURL("foo.com", "/simple.html");
  auto* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
  auto* host_content_settings_map =
      HostContentSettingsMapFactory::GetForProfile(profile);

  // Set insecure content setting to allowed for `http_url`.
  host_content_settings_map->SetContentSettingDefaultScope(
      http_url, GURL(), ContentSettingsType::MIXEDSCRIPT,
      CONTENT_SETTING_ALLOW);

  if (IsStrictInterstitialEnabledForTest()) {
    // If HTTPS-First Mode is enabled, upgrades should still be applied.
    EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, http_url));
    EXPECT_EQ(https_url, contents->GetLastCommittedURL());
    histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                    NavigationRequestSecurityLevel::kUpgraded,
                                    1);
  } else {
    // Otherwise, the upgrades should be skipped.
    EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, http_url));
    EXPECT_EQ(http_url, contents->GetLastCommittedURL());
    histograms()->ExpectBucketCount(
        kNavigationRequestSecurityLevelHistogram,
        NavigationRequestSecurityLevel::kAllowlisted, 1);
  }

  // Unset the content settings.
  host_content_settings_map->ClearSettingsForOneType(
      ContentSettingsType::MIXEDSCRIPT);

  // Set insecure content setting to allowed for `https_url`.
  HostContentSettingsMapFactory::GetForProfile(profile)
      ->SetContentSettingDefaultScope(https_url, GURL(),
                                      ContentSettingsType::MIXEDSCRIPT,
                                      CONTENT_SETTING_ALLOW);
  if (IsStrictInterstitialEnabledForTest()) {
    // If HTTPS-First Mode is enabled, upgrades should still be applied.
    EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, http_url));
    EXPECT_EQ(https_url, contents->GetLastCommittedURL());
    histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                    NavigationRequestSecurityLevel::kUpgraded,
                                    2);
  } else {
    // Otherwise, the upgrades should be skipped.
    EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, http_url));
    EXPECT_EQ(http_url, contents->GetLastCommittedURL());
    histograms()->ExpectBucketCount(
        kNavigationRequestSecurityLevelHistogram,
        NavigationRequestSecurityLevel::kAllowlisted, 2);
  }
}

// Test that HTTPS Upgrades are skipped if the "Insecure content" site setting
// is set to "allow".
// MIXED_SCRIPT isn't enabled as a content setting on Android.
// This test is identical to InsecureContentSettingDisablesUpgrades except it
// sets a high site engagement score for the https URL and checks an additional
// histogram.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_InsecureContentSettingDisablesHFMForEngagedSites \
  DISABLED_InsecureContentSettingDisablesHFMForEngagedSites
#else
#define MAYBE_InsecureContentSettingDisablesHFMForEngagedSites \
  InsecureContentSettingDisablesHFMForEngagedSites
#endif
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       MAYBE_InsecureContentSettingDisablesHFMForEngagedSites) {
  content::WebContents* contents =
      GetBrowser()->tab_strip_model()->GetActiveWebContents();
  GURL http_url = http_server()->GetURL("foo.com", "/simple.html");
  GURL https_url = https_server()->GetURL("foo.com", "/simple.html");
  auto* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
  auto* host_content_settings_map =
      HostContentSettingsMapFactory::GetForProfile(profile);

  // Setting a high engagement score on the HTTPS URL enables HFM on the site
  // if the HFM+SE feature is enabled, but an Insecure Content entry disables
  // HFM+SE on the site.
  SetSiteEngagementScore(http_url, kLowSiteEngagementScore);
  SetSiteEngagementScore(https_url, kHighSiteEnagementScore);

  // Set insecure content setting to allowed for `http_url`.
  host_content_settings_map->SetContentSettingDefaultScope(
      http_url, GURL(), ContentSettingsType::MIXEDSCRIPT,
      CONTENT_SETTING_ALLOW);

  if (IsStrictInterstitialEnabledForTest()) {
    // If HTTPS-First Mode is fully enabled, upgrades should still be applied.
    EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, http_url));
    EXPECT_EQ(https_url, contents->GetLastCommittedURL());
    histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                    NavigationRequestSecurityLevel::kUpgraded,
                                    1);
  } else {
    // Otherwise, the upgrades should be skipped.
    EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, http_url));
    EXPECT_EQ(http_url, contents->GetLastCommittedURL());
    histograms()->ExpectBucketCount(
        kNavigationRequestSecurityLevelHistogram,
        NavigationRequestSecurityLevel::kAllowlisted, 1);
  }
  // In both cases, HFM+SE events shouldn't be recorded because of the Insecure
  // content setting.
  histograms()->ExpectTotalCount(kEventHistogramWithEngagementHeuristic, 0);

  // Unset the content settings.
  host_content_settings_map->ClearSettingsForOneType(
      ContentSettingsType::MIXEDSCRIPT);

  // Set insecure content setting to allowed for `https_url`.
  HostContentSettingsMapFactory::GetForProfile(profile)
      ->SetContentSettingDefaultScope(https_url, GURL(),
                                      ContentSettingsType::MIXEDSCRIPT,
                                      CONTENT_SETTING_ALLOW);
  if (IsStrictInterstitialEnabledForTest()) {
    // If HTTPS-First Mode is enabled, upgrades should still be applied.
    EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, http_url));
    EXPECT_EQ(https_url, contents->GetLastCommittedURL());
    histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                    NavigationRequestSecurityLevel::kUpgraded,
                                    2);
  } else {
    // Otherwise, the upgrades should be skipped.
    EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, http_url));
    EXPECT_EQ(http_url, contents->GetLastCommittedURL());
    histograms()->ExpectBucketCount(
        kNavigationRequestSecurityLevelHistogram,
        NavigationRequestSecurityLevel::kAllowlisted, 2);
  }
  // In both cases, HFM+SE events shouldn't be recorded because of the Insecure
  // content setting.
  histograms()->ExpectTotalCount(kEventHistogramWithEngagementHeuristic, 0);
}

// Regression test for crbug.com/40902071. Triggers a navigation where HTTPS
// upgrades applied multiple times across redirects to different sites.
// Should not crash when DCHECKS are enabled.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, crbug1431026) {
  GURL www_bad_https_url =
      https_server()->GetURL("www.bad-https.com", "/simple.html");
  GURL www_http_url =
      http_server()->GetURL("www.bad-https.com", "/simple.html");

  // Configure HTTP and bad-HTTPS URLs which redirect to www. subdomain.
  std::string www_redirect_path =
      base::StrCat({"/server-redirect?", www_http_url.spec()});
  GURL redirecting_bad_https_url =
      https_server()->GetURL("bad-https.com", www_redirect_path);
  GURL redirecting_http_url =
      http_server()->GetURL("bad-https.com", www_redirect_path);

  // A good HTTPS URL which redirects to an HTTP URL, which also redirects.
  GURL initial_redirecting_good_https_url = https_server()->GetURL(
      "good-https.com",
      base::StrCat({"/server-redirect-301?", redirecting_http_url.spec()}));

  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  EXPECT_FALSE(NavigateToURLWithLinkTransition(
      contents, initial_redirecting_good_https_url));

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    // Should be showing interstitial on http://bad-https.com/.
    EXPECT_EQ(redirecting_http_url, contents->GetLastCommittedURL());
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
  } else {
    // Either due to no upgrades, or due to fast fallback to HTTP, this should
    // end up on http://www.bad-https.com.
    EXPECT_EQ(www_http_url, contents->GetLastCommittedURL());
    EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
  }
}

// Tests that when the HTTPS-First Mode setting is toggled on or off, the
// HTTP allowlist is cleared.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       TogglingSettingClearsAllowlist) {
  // The allowlist in an Incognito window is in-memory only, and is not cleared
  // when the main profile's pref changes.
  // TODO(crbug.com/40937027): Add a test to cover the Incognito allowlisting
  // behavior explicitly.
  if (IsIncognito()) {
    return;
  }

  // Advanced Protection users have HFM enabled regardless of the pref.
  if (https_upgrades_test_type() ==
      HttpsUpgradesTestType::kHttpsFirstModeAdvancedProtection) {
    return;
  }

  auto http_url = http_server()->GetURL("bad-https.com", "/simple.html");
  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();

  // Start by enabling HTTPS-First Mode.
  SetPref(true);

  // Navigate to a URL that will fail upgrades, and click through the
  // interstitial to add it to the allowlist.
  EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, http_url));
  EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
  ProceedThroughHttpsFirstModeInterstitial(contents);

  // Disable the HTTPS-First Mode pref. This should clear the allowlist.
  SetPref(false);

  if (InBalancedMode()) {
    EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, http_url));
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));

    // Proceed through the interstitial and add the host to the allowlist.
    ProceedThroughHttpsFirstModeInterstitial(contents);
  } else {
    // With HTTPS-Upgrades enabled, navigating again should cause the site to
    // get added back to the allowlist.
    EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, http_url));
    EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
  }

  // Re-enable the HTTPS-First Mode pref. The allowlist should be cleared again.
  SetPref(true);

  // Navigate to a URL that will fail upgrades, and the interstitial should be
  // shown again as the allowlist was cleared.
  EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, http_url));
  EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
}

// Main window HTTP allowlist should not apply to Incognito window.
// Regression test for crbug.com/40949400.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       IncognitoHasSeparateAllowlist) {
  // This test only covers the case of HFM-in-Incognito.
  if (!IsIncognito()) {
    return;
  }
  // In a regular window, add a host to the HTTP allowlist.
  // Note: This is explicitly done with HTTPS-First Mode disabled as that is the
  // specific regression case for crbug.com/40949400, but HTTPS-First Mode and
  // HTTPS-Upgrades may eventually have separate allowlists.
  SetPref(false);
  auto http_url = http_server()->GetURL("bad-https.com", "/simple.html");
  auto* normal_tab = browser()->GetTabStripModel()->GetActiveWebContents();
  EXPECT_TRUE(NavigateToURLWithLinkTransition(normal_tab, http_url));
  EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(normal_tab));

  // In an Incognito window, navigating to that same host should still trigger
  // the HTTP interstitial, as the allowlist is not inherited.
  auto* incognito_tab = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  EXPECT_FALSE(NavigateToURLWithLinkTransition(incognito_tab, http_url));
  EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(incognito_tab));
}

// Tests that URLs typed with an explicit http:// scheme are opted out from
// upgrades.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       URLsTypedWithHttpSchemeNoUpgrades) {
  GURL http_url = http_server()->GetURL("foo.com", "/simple.html");
  GURL https_url = https_server()->GetURL("foo.com", "/simple.html");
  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  OmniboxClient* omnibox_client = BrowserWindow::FromBrowser(GetBrowser())
                                      ->GetLocationBar()
                                      ->GetOmniboxController()
                                      ->client();

  // Simulate the full URL was typed with an http scheme.
  content::TestNavigationObserver nav_observer(contents, 1);
  omnibox_client->OnAutocompleteAccept(
      http_url, nullptr, WindowOpenDisposition::CURRENT_TAB,
      ui::PAGE_TRANSITION_TYPED, AutocompleteMatchType::URL_WHAT_YOU_TYPED,
      base::TimeTicks(), false, true, std::u16string(), AutocompleteMatch(),
      AutocompleteMatch());
  nav_observer.Wait();

  if (IsStrictInterstitialEnabledForTest()) {
    // Typed http URLs don't opt out of upgrades in HFM.
    EXPECT_EQ(https_url, contents->GetLastCommittedURL());
  } else {
    histograms()->ExpectTotalCount(kNavigationRequestSecurityLevelHistogram, 1);
    histograms()->ExpectBucketCount(
        kNavigationRequestSecurityLevelHistogram,
        NavigationRequestSecurityLevel::kExplicitHttpScheme, 1);
    EXPECT_EQ(http_url, contents->GetLastCommittedURL());
  }
}

// Tests that URLs with an explicit http:// scheme are upgraded if they were
// autocompleted.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       URLsAutocompletedWithHttpSchemeAreUpgraded) {
  GURL http_url = http_server()->GetURL("foo.com", "/simple.html");
  GURL https_url = https_server()->GetURL("foo.com", "/simple.html");
  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  OmniboxClient* omnibox_client = BrowserWindow::FromBrowser(GetBrowser())
                                      ->GetLocationBar()
                                      ->GetOmniboxController()
                                      ->client();

  // Simulate the full URL was autocompleted with an http scheme.
  content::TestNavigationObserver nav_observer(contents, 1);
  omnibox_client->OnAutocompleteAccept(
      http_url, nullptr, WindowOpenDisposition::CURRENT_TAB,
      ui::PAGE_TRANSITION_TYPED, AutocompleteMatchType::NAVSUGGEST,
      base::TimeTicks(), false, false, std::u16string(), AutocompleteMatch(),
      AutocompleteMatch());
  nav_observer.Wait();

  EXPECT_EQ(https_url, contents->GetLastCommittedURL());
}

// Tests that URLs typed with an explicit http:// scheme that result in an
// opt-out cause the url to be added to the allowlist.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       URLsTypedWithHttpSchemeNoUpgradesAllowlist) {
  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    return;
  }
  GURL http_url = http_server()->GetURL("foo.com", "/simple.html");
  GURL https_url = https_server()->GetURL("foo.com", "/simple.html");
  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  OmniboxClient* omnibox_client = BrowserWindow::FromBrowser(GetBrowser())
                                      ->GetLocationBar()
                                      ->GetOmniboxController()
                                      ->client();

  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
  content::SSLHostStateDelegate* state = profile->GetSSLHostStateDelegate();

  // Site should not yet be in the allowlist.
  EXPECT_FALSE(state->IsHttpAllowedForHost(
      http_url.GetHost(),
      contents->GetPrimaryMainFrame()->GetStoragePartition()));

  // Simulate the full URL was typed with an http scheme.
  content::TestNavigationObserver nav_observer(contents, 1);
  omnibox_client->OnAutocompleteAccept(
      http_url, nullptr, WindowOpenDisposition::CURRENT_TAB,
      ui::PAGE_TRANSITION_TYPED, AutocompleteMatchType::URL_WHAT_YOU_TYPED,
      base::TimeTicks(), false, true, std::u16string(), AutocompleteMatch(),
      AutocompleteMatch());
  nav_observer.Wait();

  // URL should not have been upgraded, and site should now be in the allowlist.
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
  EXPECT_TRUE(state->IsHttpAllowedForHost(
      http_url.GetHost(),
      contents->GetPrimaryMainFrame()->GetStoragePartition()));
}

// Tests that when a URL typed with an explicit http:// scheme is redirected by
// the server to other hosts, only the host of the originally typed URL is
// added to the allowlist. The exemption applies to the navigation as a whole,
// but server-chosen redirect targets must not be persistently allowlisted.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest,
                       URLsTypedWithHttpSchemeAllowlistOnlyInitialHost) {
  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    return;
  }
  GURL final_url = http_server()->GetURL("baz.com", "/simple.html");
  GURL hop_url =
      http_server()->GetURL("bar.com", "/server-redirect?" + final_url.spec());
  GURL initial_url =
      http_server()->GetURL("foo.com", "/server-redirect?" + hop_url.spec());
  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  OmniboxClient* omnibox_client = BrowserWindow::FromBrowser(GetBrowser())
                                      ->GetLocationBar()
                                      ->GetOmniboxController()
                                      ->client();

  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
  content::SSLHostStateDelegate* state = profile->GetSSLHostStateDelegate();
  auto* storage_partition =
      contents->GetPrimaryMainFrame()->GetStoragePartition();

  // None of the hosts should be in the allowlist yet.
  EXPECT_FALSE(state->IsHttpAllowedForHost("foo.com", storage_partition));
  EXPECT_FALSE(state->IsHttpAllowedForHost("bar.com", storage_partition));
  EXPECT_FALSE(state->IsHttpAllowedForHost("baz.com", storage_partition));

  // Simulate the full URL was typed with an http scheme. The server redirects
  // through bar.com and baz.com.
  content::TestNavigationObserver nav_observer(contents, 1);
  omnibox_client->OnAutocompleteAccept(
      initial_url, nullptr, WindowOpenDisposition::CURRENT_TAB,
      ui::PAGE_TRANSITION_TYPED, AutocompleteMatchType::URL_WHAT_YOU_TYPED,
      base::TimeTicks(), false, true, std::u16string(), AutocompleteMatch(),
      AutocompleteMatch());
  nav_observer.Wait();

  // None of the hops should have been upgraded.
  EXPECT_EQ(final_url, contents->GetLastCommittedURL());

  // The host the user typed should be in the allowlist, but the
  // server-selected redirect targets should not.
  EXPECT_TRUE(state->IsHttpAllowedForHost("foo.com", storage_partition));
  EXPECT_FALSE(state->IsHttpAllowedForHost("bar.com", storage_partition));
  EXPECT_FALSE(state->IsHttpAllowedForHost("baz.com", storage_partition));
}

// Returns a URL loader interceptor that responds to HTTPS URLs with a timeout
// error.
std::unique_ptr<content::URLLoaderInterceptor> MakeTimeoutInterceptor() {
  return std::make_unique<content::URLLoaderInterceptor>(
      base::BindLambdaForTesting(
          [](content::URLLoaderInterceptor::RequestParams* params) {
            if (params->url_request.url.host() == "timeout-https.com") {
              if (params->url_request.url.SchemeIs("https")) {
                network::URLLoaderCompletionStatus status;
                status.error_code = net::ERR_TIMED_OUT;
                params->client->OnComplete(status);
                return true;
              }
              content::URLLoaderInterceptor::WriteResponse(
                  "HTTP/1.1 200 OK\nContent-type: text/html\n\n",
                  "<html>Done</html>", params->client.get());
              return true;
            }
            return false;
          }));
}

// Base test fixture for HttpsUpgrades typed schemeless navigation tests.
class HttpsUpgradesTypedSchemelessNavigationTestBase
    : public InProcessBrowserTest {
 public:
  HttpsUpgradesTypedSchemelessNavigationTestBase() = default;
  ~HttpsUpgradesTypedSchemelessNavigationTestBase() override = default;

  void SetUp() override {
    ChromeSecurityBlockingPageFactory::SetEnterpriseManagedForTesting(false);
    InProcessBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
    host_resolver()->AddRule("*", "127.0.0.1");

    scoped_refptr<net::X509Certificate> cert(https_server_.GetCertificate());
    net::CertVerifyResult verify_result;
    verify_result.is_issued_by_known_root = false;
    verify_result.verified_cert = cert;
    verify_result.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;
    mock_cert_verifier_.mock_cert_verifier()->AddResultForCertAndHost(
        cert, "bad-https.com", verify_result,
        net::ERR_CERT_COMMON_NAME_INVALID);

    http_server_.AddDefaultHandlers(GetChromeTestDataDir());
    https_server_.AddDefaultHandlers(GetChromeTestDataDir());
    ASSERT_TRUE(http_server_.Start());
    ASSERT_TRUE(https_server_.Start());

    HttpsUpgradesInterceptor::SetHttpsPortForTesting(https_server()->port());
    HttpsUpgradesInterceptor::SetHttpPortForTesting(http_server()->port());
  }

  void TearDownOnMainThread() override {
    HttpsUpgradesInterceptor::SetHttpsPortForTesting(0);
    HttpsUpgradesInterceptor::SetHttpPortForTesting(0);
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    mock_cert_verifier_.SetUpCommandLine(command_line);
  }

  void SetUpInProcessBrowserTestFixture() override {
    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
  }

  void TearDownInProcessBrowserTestFixture() override {
    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
  }

  net::EmbeddedTestServer* http_server() { return &http_server_; }
  net::EmbeddedTestServer* https_server() { return &https_server_; }
  base::HistogramTester* histograms() { return &histograms_; }

 protected:
  base::test::ScopedFeatureList feature_list_;

 private:
  net::EmbeddedTestServer http_server_{net::EmbeddedTestServer::TYPE_HTTP};
  net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
  content::ContentMockCertVerifier mock_cert_verifier_;
  base::HistogramTester histograms_;
};

class HttpsUpgradesTypedSchemelessNavigationNoTimeoutFallbackBrowserTest
    : public HttpsUpgradesTypedSchemelessNavigationTestBase {
 public:
  HttpsUpgradesTypedSchemelessNavigationNoTimeoutFallbackBrowserTest() {
    feature_list_.InitWithFeatures(
        /*enabled_features=*/
        {features::kHttpsUpgrades,
         features::kHttpsUpgradesTypedSchemelessNavigationNoTimeoutFallback},
        /*disabled_features=*/{
            features::kHttpsFirstModeV2ForEngagedSites,
            features::kHttpsFirstModeV2ForTypicallySecureUsers,
            features::kHttpsFirstBalancedModeAutoEnable});
  }
};

// Tests that typed schemeless navigations are upgraded to HTTPS, but do NOT
// fallback to HTTP if the upgrade fails due to timeout, when the feature flag
// is enabled.
IN_PROC_BROWSER_TEST_F(
    HttpsUpgradesTypedSchemelessNavigationNoTimeoutFallbackBrowserTest,
    TypedSchemelessNavigationNoTimeoutFallback) {
  // Set up the interceptor to simulate timeout on HTTPS.
  auto interceptor = MakeTimeoutInterceptor();

  // Use a mock host name that will be intercepted.
  GURL http_url = http_server()->GetURL("timeout-https.com", "/simple.html");
  GURL https_url = https_server()->GetURL("timeout-https.com", "/simple.html");

  content::WebContents* contents =
      browser()->GetTabStripModel()->GetActiveWebContents();
  OmniboxClient* omnibox_client = BrowserWindow::FromBrowser(browser())
                                      ->GetLocationBar()
                                      ->GetOmniboxController()
                                      ->client();

  // Simulate a typed schemeless navigation.
  content::TestNavigationObserver nav_observer(contents, 1);
  omnibox_client->OnAutocompleteAccept(
      http_url, nullptr, WindowOpenDisposition::CURRENT_TAB,
      ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
                                ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
      AutocompleteMatchType::URL_WHAT_YOU_TYPED, base::TimeTicks(), true, false,
      std::u16string(), AutocompleteMatch(), AutocompleteMatch());
  nav_observer.Wait();

  // The navigation should have been upgraded to HTTPS, and failed there.
  // Since fallback is disabled on timeout, it should NOT have navigated back to
  // HTTP.
  EXPECT_EQ(https_url, contents->GetLastCommittedURL());

  // It should show a net error page (not HFM interstitial or SSL interstitial)
  // because HFM is disabled and it's a timeout.
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(contents));

  // Verify metrics.
  histograms()->ExpectBucketCount(
      kNavigationRequestSecurityLevelHistogram,
      NavigationRequestSecurityLevel::kTypedSchemelessUpgraded, 1);
  histograms()->ExpectBucketCount(kEventHistogram,
                                  Event::kTypedSchemelessUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram,
                                  Event::kTypedSchemelessUpgradeTimedOut, 1);

  // Standard upgrade metrics should also be recorded.
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeFailed, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeTimedOut, 1);
}

// Tests that typed schemeless navigations upgraded to HTTPS DO fallback to HTTP
// if the upgrade fails due to a cert error, even when the feature flag is
// enabled (timeout restriction).
IN_PROC_BROWSER_TEST_F(
    HttpsUpgradesTypedSchemelessNavigationNoTimeoutFallbackBrowserTest,
    TypedSchemelessNavigationFallbackOnCertError) {
  // Use bad-https.com so that HTTPS upgrade fails with a cert error.
  GURL http_url = http_server()->GetURL("bad-https.com", "/simple.html");

  content::WebContents* contents =
      browser()->GetTabStripModel()->GetActiveWebContents();
  OmniboxClient* omnibox_client = BrowserWindow::FromBrowser(browser())
                                      ->GetLocationBar()
                                      ->GetOmniboxController()
                                      ->client();

  // Simulate a typed schemeless navigation.
  content::TestNavigationObserver nav_observer(contents, 1);
  omnibox_client->OnAutocompleteAccept(
      http_url, nullptr, WindowOpenDisposition::CURRENT_TAB,
      ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
                                ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
      AutocompleteMatchType::URL_WHAT_YOU_TYPED, base::TimeTicks(), true, false,
      std::u16string(), AutocompleteMatch(), AutocompleteMatch());
  nav_observer.Wait();

  // The navigation should have been upgraded to HTTPS, failed (cert error), and
  // fell back to HTTP.
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(contents));

  // Verify metrics.
  // Should record kTypedSchemelessUpgraded (since it was eligible at start).
  histograms()->ExpectBucketCount(
      kNavigationRequestSecurityLevelHistogram,
      NavigationRequestSecurityLevel::kTypedSchemelessUpgraded, 1);

  // Standard upgrade metrics should be recorded.
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeFailed, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeCertError, 1);

  // Should NOT record kTypedSchemelessUpgradeTimedOut (since it did not fail
  // due to timeout).
  histograms()->ExpectBucketCount(kEventHistogram,
                                  Event::kTypedSchemelessUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram,
                                  Event::kTypedSchemelessUpgradeSucceeded, 0);
  histograms()->ExpectBucketCount(kEventHistogram,
                                  Event::kTypedSchemelessUpgradeTimedOut, 0);
}

// Tests that typed schemeless navigations upgraded to HTTPS successfully load
// over HTTPS, and record success metrics, when the feature flag is enabled.
IN_PROC_BROWSER_TEST_F(
    HttpsUpgradesTypedSchemelessNavigationNoTimeoutFallbackBrowserTest,
    TypedSchemelessNavigationSuccess) {
  // Use a host that will succeed on HTTPS (default cert result is OK).
  GURL http_url = http_server()->GetURL("example.com", "/simple.html");
  GURL https_url = https_server()->GetURL("example.com", "/simple.html");

  content::WebContents* contents =
      browser()->GetTabStripModel()->GetActiveWebContents();
  OmniboxClient* omnibox_client = BrowserWindow::FromBrowser(browser())
                                      ->GetLocationBar()
                                      ->GetOmniboxController()
                                      ->client();

  // Simulate a typed schemeless navigation.
  content::TestNavigationObserver nav_observer(contents, 1);
  omnibox_client->OnAutocompleteAccept(
      http_url, nullptr, WindowOpenDisposition::CURRENT_TAB,
      ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
                                ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
      AutocompleteMatchType::URL_WHAT_YOU_TYPED, base::TimeTicks(), true, false,
      std::u16string(), AutocompleteMatch(), AutocompleteMatch());
  nav_observer.Wait();

  // The navigation should have been upgraded to HTTPS, and succeeded.
  EXPECT_EQ(https_url, contents->GetLastCommittedURL());
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(contents));

  // Verify metrics.
  histograms()->ExpectBucketCount(
      kNavigationRequestSecurityLevelHistogram,
      NavigationRequestSecurityLevel::kTypedSchemelessUpgraded, 1);
  histograms()->ExpectBucketCount(kEventHistogram,
                                  Event::kTypedSchemelessUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram,
                                  Event::kTypedSchemelessUpgradeSucceeded, 1);
  histograms()->ExpectBucketCount(kEventHistogram,
                                  Event::kTypedSchemelessUpgradeTimedOut, 0);

  // Standard upgrade metrics.
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeSucceeded, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeFailed, 0);
}

class HttpsUpgradesTypedSchemelessNavigationWithFallbackBrowserTest
    : public HttpsUpgradesTypedSchemelessNavigationTestBase {
 public:
  HttpsUpgradesTypedSchemelessNavigationWithFallbackBrowserTest() {
    feature_list_.InitWithFeatures(
        /*enabled_features=*/{features::kHttpsUpgrades},
        /*disabled_features=*/{
            features::kHttpsUpgradesTypedSchemelessNavigationNoTimeoutFallback,
            features::kHttpsFirstModeV2ForEngagedSites,
            features::kHttpsFirstModeV2ForTypicallySecureUsers,
            features::kHttpsFirstBalancedModeAutoEnable});
  }
};

// Tests that typed schemeless navigations are upgraded to HTTPS, and DO
// fallback to HTTP if the upgrade fails due to cert error, when the feature
// flag is disabled.
IN_PROC_BROWSER_TEST_F(
    HttpsUpgradesTypedSchemelessNavigationWithFallbackBrowserTest,
    TypedSchemelessNavigationFallbackOnCertError) {
  // Use bad-https.com so that HTTPS upgrade fails with a cert error.
  GURL http_url = http_server()->GetURL("bad-https.com", "/simple.html");

  content::WebContents* contents =
      browser()->GetTabStripModel()->GetActiveWebContents();
  OmniboxClient* omnibox_client = BrowserWindow::FromBrowser(browser())
                                      ->GetLocationBar()
                                      ->GetOmniboxController()
                                      ->client();

  // Simulate a typed schemeless navigation.
  content::TestNavigationObserver nav_observer(contents, 1);
  omnibox_client->OnAutocompleteAccept(
      http_url, nullptr, WindowOpenDisposition::CURRENT_TAB,
      ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
                                ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
      AutocompleteMatchType::URL_WHAT_YOU_TYPED, base::TimeTicks(), true, false,
      std::u16string(), AutocompleteMatch(), AutocompleteMatch());
  nav_observer.Wait();

  // The navigation should have been upgraded to HTTPS, failed, and fell back to
  // HTTP.
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(contents));

  // Verify metrics.
  // Should record kUpgraded (standard upgrade).
  histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                  NavigationRequestSecurityLevel::kUpgraded, 1);
  histograms()->ExpectBucketCount(
      kNavigationRequestSecurityLevelHistogram,
      NavigationRequestSecurityLevel::kTypedSchemelessUpgraded, 0);

  // Standard upgrade metrics.
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeFailed, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeCertError, 1);

  // Should NOT record kTypedSchemelessUpgrade metrics.
  histograms()->ExpectBucketCount(kEventHistogram,
                                  Event::kTypedSchemelessUpgradeAttempted, 0);
  histograms()->ExpectBucketCount(kEventHistogram,
                                  Event::kTypedSchemelessUpgradeTimedOut, 0);
}

// Tests that typed schemeless navigations are upgraded to HTTPS, and DO
// fallback to HTTP if the upgrade fails due to timeout, when the feature flag
// is disabled.
IN_PROC_BROWSER_TEST_F(
    HttpsUpgradesTypedSchemelessNavigationWithFallbackBrowserTest,
    TypedSchemelessNavigationFallbackOnTimeout) {
  // Set up the interceptor to simulate timeout on HTTPS.
  auto interceptor = MakeTimeoutInterceptor();

  GURL http_url = http_server()->GetURL("timeout-https.com", "/simple.html");

  content::WebContents* contents =
      browser()->GetTabStripModel()->GetActiveWebContents();
  OmniboxClient* omnibox_client = BrowserWindow::FromBrowser(browser())
                                      ->GetLocationBar()
                                      ->GetOmniboxController()
                                      ->client();

  // Simulate a typed schemeless navigation.
  content::TestNavigationObserver nav_observer(contents, 1);
  omnibox_client->OnAutocompleteAccept(
      http_url, nullptr, WindowOpenDisposition::CURRENT_TAB,
      ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
                                ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
      AutocompleteMatchType::URL_WHAT_YOU_TYPED, base::TimeTicks(), true, false,
      std::u16string(), AutocompleteMatch(), AutocompleteMatch());
  nav_observer.Wait();

  // The navigation should have been upgraded to HTTPS, failed (timeout), and
  // fell back to HTTP.
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(contents));

  // Verify metrics.
  // Should record kUpgraded (standard upgrade).
  histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                  NavigationRequestSecurityLevel::kUpgraded, 1);
  histograms()->ExpectBucketCount(
      kNavigationRequestSecurityLevelHistogram,
      NavigationRequestSecurityLevel::kTypedSchemelessUpgraded, 0);

  // Standard upgrade metrics.
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeFailed, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeTimedOut, 1);

  // Should NOT record kTypedSchemelessUpgrade metrics.
  histograms()->ExpectBucketCount(kEventHistogram,
                                  Event::kTypedSchemelessUpgradeAttempted, 0);
  histograms()->ExpectBucketCount(kEventHistogram,
                                  Event::kTypedSchemelessUpgradeTimedOut, 0);
}

class HttpsUpgradesTypedSchemelessNavigationBalancedModeBrowserTest
    : public HttpsUpgradesTypedSchemelessNavigationTestBase {
 public:
  HttpsUpgradesTypedSchemelessNavigationBalancedModeBrowserTest() {
    feature_list_.InitWithFeatures(
        /*enabled_features=*/
        {features::kHttpsUpgrades,
         features::kHttpsUpgradesTypedSchemelessNavigationNoTimeoutFallback},
        /*disabled_features=*/{
            features::kHttpsFirstModeV2ForEngagedSites,
            features::kHttpsFirstModeV2ForTypicallySecureUsers,
            features::kHttpsFirstBalancedModeAutoEnable});
  }

  void SetUpOnMainThread() override {
    HttpsUpgradesTypedSchemelessNavigationTestBase::SetUpOnMainThread();
    browser()->GetProfile()->GetPrefs()->SetBoolean(
        prefs::kHttpsFirstBalancedMode, true);
  }

  void TearDownOnMainThread() override {
    browser()->GetProfile()->GetPrefs()->ClearPref(
        prefs::kHttpsFirstBalancedMode);
    HttpsUpgradesTypedSchemelessNavigationTestBase::TearDownOnMainThread();
  }
};

// Tests that typed schemeless navigations are upgraded to HTTPS, but do NOT
// fallback to HTTP if the upgrade fails due to timeout, even when HFM Balanced
// Mode is enabled.
IN_PROC_BROWSER_TEST_F(
    HttpsUpgradesTypedSchemelessNavigationBalancedModeBrowserTest,
    TypedSchemelessNavigationNoTimeoutFallback_BalancedMode) {
  // Set up the interceptor to simulate timeout on HTTPS.
  auto interceptor = MakeTimeoutInterceptor();

  GURL http_url = http_server()->GetURL("timeout-https.com", "/simple.html");
  GURL https_url = https_server()->GetURL("timeout-https.com", "/simple.html");

  content::WebContents* contents =
      browser()->GetTabStripModel()->GetActiveWebContents();
  OmniboxClient* omnibox_client = BrowserWindow::FromBrowser(browser())
                                      ->GetLocationBar()
                                      ->GetOmniboxController()
                                      ->client();

  // Simulate a typed schemeless navigation.
  content::TestNavigationObserver nav_observer(contents, 1);
  omnibox_client->OnAutocompleteAccept(
      http_url, nullptr, WindowOpenDisposition::CURRENT_TAB,
      ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
                                ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
      AutocompleteMatchType::URL_WHAT_YOU_TYPED, base::TimeTicks(), true, false,
      std::u16string(), AutocompleteMatch(), AutocompleteMatch());
  nav_observer.Wait();

  // The navigation should have been upgraded to HTTPS, and failed there.
  // Since fallback is disabled on timeout, it should NOT have navigated back to
  // HTTP (and should NOT show the Balanced Mode interstitial on fallback).
  EXPECT_EQ(https_url, contents->GetLastCommittedURL());

  // It should show a net error page (no HFM interstitial).
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(contents));

  // Verify metrics.
  histograms()->ExpectBucketCount(
      kNavigationRequestSecurityLevelHistogram,
      NavigationRequestSecurityLevel::kTypedSchemelessUpgraded, 1);
  histograms()->ExpectBucketCount(kEventHistogram,
                                  Event::kTypedSchemelessUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram,
                                  Event::kTypedSchemelessUpgradeTimedOut, 1);
}

// Tests that typed schemeless navigations upgraded to HTTPS DO fallback to HTTP
// if the upgrade fails due to a cert error, when HFM Balanced Mode is enabled,
// and then they show the HFM Balanced Mode interstitial on the fallback HTTP
// page.
IN_PROC_BROWSER_TEST_F(
    HttpsUpgradesTypedSchemelessNavigationBalancedModeBrowserTest,
    TypedSchemelessNavigationFallback_BalancedMode) {
  // Use bad-https.com so that HTTPS upgrade fails with a cert error.
  GURL http_url = http_server()->GetURL("bad-https.com", "/simple.html");

  content::WebContents* contents =
      browser()->GetTabStripModel()->GetActiveWebContents();
  OmniboxClient* omnibox_client = BrowserWindow::FromBrowser(browser())
                                      ->GetLocationBar()
                                      ->GetOmniboxController()
                                      ->client();

  // Simulate a typed schemeless navigation.
  content::TestNavigationObserver nav_observer(contents, 1);
  omnibox_client->OnAutocompleteAccept(
      http_url, nullptr, WindowOpenDisposition::CURRENT_TAB,
      ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
                                ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
      AutocompleteMatchType::URL_WHAT_YOU_TYPED, base::TimeTicks(), true, false,
      std::u16string(), AutocompleteMatch(), AutocompleteMatch());
  nav_observer.Wait();

  // The navigation should have been upgraded to HTTPS, failed (cert error), and
  // fell back to HTTP.
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());

  // Since Balanced Mode is enabled, falling back to HTTP should show the
  // Balanced Mode interstitial.
  EXPECT_TRUE(chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
      contents));

  // Verify metrics.
  // Should record kTypedSchemelessUpgraded (since it was eligible at start).
  histograms()->ExpectBucketCount(
      kNavigationRequestSecurityLevelHistogram,
      NavigationRequestSecurityLevel::kTypedSchemelessUpgraded, 1);

  // Standard upgrade metrics.
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeFailed, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeCertError, 1);

  // Should NOT record kTypedSchemelessUpgradeTimedOut (since it did not time
  // out).
  histograms()->ExpectBucketCount(kEventHistogram,
                                  Event::kTypedSchemelessUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram,
                                  Event::kTypedSchemelessUpgradeSucceeded, 0);
  histograms()->ExpectBucketCount(kEventHistogram,
                                  Event::kTypedSchemelessUpgradeTimedOut, 0);
}

// Url used to detect the presence of a captive portal.
constexpr char kCaptivePortalPingUrl[] = "http://captive-portal-ping-url.com/";
// HTTPS version of the same URL.
constexpr char kCaptivePortalPingUrlHttps[] =
    "https://captive-portal-ping-url.com/";

// Returns a URL loader interceptor that responds to HTTPS URLs with a cert
// error and to HTTP URLs with a good response.
std::unique_ptr<content::URLLoaderInterceptor> MakeCaptivePortalInterceptor(
    bool login_page_has_valid_https) {
  return std::make_unique<content::URLLoaderInterceptor>(
      base::BindLambdaForTesting(
          [login_page_has_valid_https](
              content::URLLoaderInterceptor::RequestParams* params) {
            if (params->url_request.url == GURL(kCaptivePortalPingUrl) ||
                (login_page_has_valid_https &&
                 params->url_request.url == GURL(kCaptivePortalPingUrlHttps))) {
              // Return a non-204 response for the captive portal ping URL
              // so that the portal is detected.
              content::URLLoaderInterceptor::WriteResponse(
                  "HTTP/1.1 200 OK\nContent-type: text/html\n\n",
                  "<html>Non-204 response to trigger captive portal "
                  "detection</html>",
                  params->client.get());
              return true;
            }

            if (params->url_request.url.SchemeIs("https")) {
              // Fail with an SSL error so that a fallback is triggered.
              network::URLLoaderCompletionStatus status;
              status.error_code = net::ERR_CERT_COMMON_NAME_INVALID;
              status.ssl_info = net::SSLInfo();
              status.ssl_info->cert_status =
                  net::CERT_STATUS_COMMON_NAME_INVALID;
              // The cert doesn't matter.
              status.ssl_info->cert = net::ImportCertFromFile(
                  net::GetTestCertsDirectory(), "ok_cert.pem");
              status.ssl_info->unverified_cert = status.ssl_info->cert;
              params->client->OnComplete(status);
              return true;
            }
            content::URLLoaderInterceptor::WriteResponse(
                "HTTP/1.1 200 OK\nContent-type: text/html\n\n",
                "<html>Done</html>", params->client.get());
            return true;
          }));
}

void HttpsUpgradesBrowserTest::EnableCaptivePortalDetection(
    BrowserWindowInterface* browser) {
  captive_portal::CaptivePortalService* captive_portal_service =
      CaptivePortalServiceFactory::GetForProfile(browser->GetProfile());
  captive_portal_service->set_test_url(GURL(kCaptivePortalPingUrl));

  captive_portal::CaptivePortalService::set_state_for_testing(
      captive_portal::CaptivePortalService::NOT_TESTING);
  browser->GetProfile()->GetPrefs()->SetBoolean(
      embedder_support::kAlternateErrorPagesEnabled, true);
}

// Checks that an automatically opened captive portal login page is not upgraded
// to HTTPS unless the interstitial is enabled. The captive portal's login
// page supports https.
IN_PROC_BROWSER_TEST_P(
    HttpsUpgradesBrowserTest,
    CaptivePortal_LoginPageWithValidSSL_ShouldNotUpgradeUnlessInterstitialEnabled) {
  if (https_upgrades_test_type() ==
      HttpsUpgradesTestType::kHttpsFirstModeIncognito) {
    return;
  }
  auto interceptor =
      MakeCaptivePortalInterceptor(/*login_page_has_valid_https=*/true);

  // Disable the testing port configuration, as this test doesn't use the
  // EmbeddedTestServer.
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(0);
  HttpsUpgradesInterceptor::SetHttpPortForTesting(0);
  EnableCaptivePortalDetection(browser());

  auto* tab_strip = GetBrowser()->tab_strip_model();
  auto* contents = tab_strip->GetActiveWebContents();
  size_t tab_count = tab_strip->count();

  // Go to an HTTPS URL. The navigation will fail and trigger a captive portal
  // detection.
  ui_test_utils::TabAddedWaiter waiter(browser());
  NavigateAndWaitForFallback(contents,
                             GURL("https://ssl-error-for-captive-portal.com/"));
  waiter.Wait();

  // Captive portal login page should not be upgraded.
  content::WebContents* login_page = tab_strip->GetWebContentsAt(tab_count);
  content::WaitForLoadStop(login_page);
  EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(login_page));

  if (IsStrictInterstitialEnabledForTest()) {
    // If the interstitial is enabled, captive portal login page should also be
    // upgraded to HTTPS.
    EXPECT_EQ(GURL(kCaptivePortalPingUrlHttps),
              login_page->GetLastCommittedURL());

    // Should only attempt an upgrade for the original page.
    histograms()->ExpectTotalCount(kNavigationRequestSecurityLevelHistogram, 3);
    // The original page serves bad HTTPS, but any HTTPS URL is counted as
    // secure in this histogram. Captive portal login page is valid HTTPS, so
    // it's also counted here.
    histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                    NavigationRequestSecurityLevel::kSecure, 2);
    histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                    NavigationRequestSecurityLevel::kUpgraded,
                                    1);
  } else {
    // Captive portal login page should not be upgraded to HTTPS.
    EXPECT_EQ(GURL(kCaptivePortalPingUrl), login_page->GetLastCommittedURL());

    // Should only attempt an upgrade for the original page.
    histograms()->ExpectTotalCount(kNavigationRequestSecurityLevelHistogram, 2);
    // The original page serves bad HTTPS, but any HTTPS URL is counted as
    // secure in this histogram:
    histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                    NavigationRequestSecurityLevel::kSecure, 1);
    histograms()->ExpectBucketCount(
        kNavigationRequestSecurityLevelHistogram,
        NavigationRequestSecurityLevel::kCaptivePortalLogin, 1);
  }
}

// Same as
// CaptivePortal_LoginPageWithValidSSL_ShouldNotUpgradeUnlessInterstitialEnabled
// but the captive portal's login page serves bad SSL.
IN_PROC_BROWSER_TEST_P(
    HttpsUpgradesBrowserTest,
    CaptivePortal_LoginPageWithoutValidSSL_ShouldNotUpgradeUnlessInterstitialEnabled) {
  if (https_upgrades_test_type() ==
      HttpsUpgradesTestType::kHttpsFirstModeIncognito) {
    return;
  }
  auto interceptor =
      MakeCaptivePortalInterceptor(/*login_page_has_valid_https=*/true);

  // Disable the testing port configuration, as this test doesn't use the
  // EmbeddedTestServer.
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(0);
  HttpsUpgradesInterceptor::SetHttpPortForTesting(0);
  EnableCaptivePortalDetection(browser());

  auto* tab_strip = GetBrowser()->tab_strip_model();
  auto* contents = tab_strip->GetActiveWebContents();
  size_t tab_count = tab_strip->count();

  // Go to an HTTPS URL. The navigation will fail and trigger a captive portal
  // detection.
  ui_test_utils::TabAddedWaiter waiter(browser());
  NavigateAndWaitForFallback(contents,
                             GURL("https://ssl-error-for-captive-portal.com/"));
  waiter.Wait();

  content::WebContents* login_page = tab_strip->GetWebContentsAt(tab_count);
  content::WaitForLoadStop(login_page);

  EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(login_page));

  if (IsStrictInterstitialEnabledForTest()) {
    // If the interstitial is enabled, captive portal login page should also be
    // upgraded to HTTPS.
    EXPECT_EQ(GURL(kCaptivePortalPingUrlHttps),
              login_page->GetLastCommittedURL());

    // Should only attempt an upgrade for the original page.
    histograms()->ExpectTotalCount(kNavigationRequestSecurityLevelHistogram, 3);
    // The original page serves bad HTTPS, but any HTTPS URL is counted as
    // secure in this histogram. Captive portal login page is valid HTTPS, so
    // it's also counted here.
    histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                    NavigationRequestSecurityLevel::kSecure, 2);
    histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                    NavigationRequestSecurityLevel::kUpgraded,
                                    1);
  } else {
    // Captive portal login page should not be upgraded to HTTPS.
    EXPECT_EQ(GURL(kCaptivePortalPingUrl), login_page->GetLastCommittedURL());

    // The original page serves bad HTTPS, but any HTTPS URL is counted as
    // secure in this histogram:
    histograms()->ExpectBucketCount(kNavigationRequestSecurityLevelHistogram,
                                    NavigationRequestSecurityLevel::kSecure, 1);
    histograms()->ExpectBucketCount(
        kNavigationRequestSecurityLevelHistogram,
        NavigationRequestSecurityLevel::kCaptivePortalLogin, 1);
  }
}

// Tests that UKM fallback reasons are correctly recorded as kCertError when
// the upgrade fails due to a certificate error.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, UKM_CertError) {
  GURL http_url = http_server()->GetURL("bad-https.com", "/simple.html");
  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateAndWaitForFallback(contents, http_url);

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
    ProceedThroughHttpsFirstModeInterstitial(contents);
    ExpectUKMEntryWithReason(http_url, BlockingResult::kInterstitialProceed,
                             FallbackReason::kCertError);
  }
}

// Tests that UKM fallback reasons are correctly recorded as kTimerFired when
// the upgrade times out.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, UKM_TimerFired) {
  HttpsUpgradesNavigationThrottle::set_timeout_for_testing(base::TimeDelta());
  net::EmbeddedTestServer timeout_server{net::EmbeddedTestServer::TYPE_HTTPS};
  timeout_server.RegisterRequestHandler(base::BindLambdaForTesting(
      [&](const net::test_server::HttpRequest& request)
          -> std::unique_ptr<net::test_server::HttpResponse> {
        return std::make_unique<net::test_server::HungResponse>();
      }));
  ASSERT_TRUE(timeout_server.Start());
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(timeout_server.port());

  const GURL http_url = http_server()->GetURL("foo.com", "/simple.html");
  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateAndWaitForFallback(contents, http_url);

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
    ProceedThroughHttpsFirstModeInterstitial(contents);
    ExpectUKMEntryWithReason(http_url, BlockingResult::kInterstitialProceed,
                             FallbackReason::kTimerFired);
  }
}

// Tests that UKM fallback reasons are correctly recorded as kRedirectLoop when
// the upgraded HTTPS site redirects back to HTTP.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBrowserTest, UKM_RedirectLoop) {
  net::EmbeddedTestServer downgrading_server{
      net::EmbeddedTestServer::TYPE_HTTPS};
  downgrading_server.RegisterRequestHandler(base::BindLambdaForTesting(
      [&](const net::test_server::HttpRequest& request)
          -> std::unique_ptr<net::test_server::HttpResponse> {
        GURL::Replacements http_downgrade;
        http_downgrade.SetSchemeStr(url::kHttpScheme);
        http_downgrade.SetHostStr("foo.com");
        auto redirect_url = request.GetURL().ReplaceComponents(http_downgrade);
        auto response = std::make_unique<net::test_server::BasicHttpResponse>();
        response->set_code(net::HTTP_TEMPORARY_REDIRECT);
        response->AddCustomHeader("Location", redirect_url.spec());
        return response;
      }));
  ASSERT_TRUE(downgrading_server.Start());
  HttpsUpgradesInterceptor::SetHttpPortForTesting(downgrading_server.port());
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(downgrading_server.port());

  GURL url = downgrading_server.GetURL("foo.com", "/");
  auto* contents = GetBrowser()->tab_strip_model()->GetActiveWebContents();
  NavigateAndWaitForFallback(contents, url);

  if (IsHttpsFirstModeInterstitialEnabledAcrossSites()) {
    EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
    ProceedThroughHttpsFirstModeInterstitial(contents);

    GURL::Replacements http_scheme;
    http_scheme.SetSchemeStr(url::kHttpScheme);
    GURL http_url = url.ReplaceComponents(http_scheme);

    ExpectUKMEntryWithReason(http_url, BlockingResult::kInterstitialProceed,
                             FallbackReason::kRedirectLoop);
  }
}

// A simple test fixture that constructs a HistogramTester (so that it gets
// initialized before browser startup). Used for testing pref tracking logic.
class HttpsUpgradesPrefsBrowserTest : public InProcessBrowserTest {
 public:
  HttpsUpgradesPrefsBrowserTest() {
    feature_list_.InitWithFeatures(
        /*enabled_features=*/{},
        /*disabled_features=*/{features::kHttpsFirstModeIncognito,
                               features::kHttpsFirstBalancedMode});
  }
  ~HttpsUpgradesPrefsBrowserTest() override = default;

 protected:
  void SetUISetting(HttpsFirstModeSetting setting) {
    extensions::settings_private::GeneratedPrefs prefs(browser()->GetProfile());
    prefs.SetPref(
        kGeneratedHttpsFirstModePref,
        std::make_unique<base::Value>(static_cast<int>(setting)).get());
  }

  bool GetPref() const {
    auto* prefs = browser()->GetProfile()->GetPrefs();
    return prefs->GetBoolean(prefs::kHttpsOnlyModeEnabled);
  }

  base::HistogramTester* histograms() { return &histograms_; }

 private:
  base::test::ScopedFeatureList feature_list_;
  base::HistogramTester histograms_;
};

// Tests that the HTTPS-First Mode state is recorded at startup and when
// changed. This test requires restarting the browser to test the "at startup"
// metric in order for the preference state to be set up before the
// HttpsFirstModeService is created.
IN_PROC_BROWSER_TEST_F(HttpsUpgradesPrefsBrowserTest, PRE_PrefStatesRecorded) {
  // The default pref state is `false`, which should get recorded when the
  // initial browser instance is started here.
  histograms()->ExpectUniqueSample(
      "Security.HttpsFirstMode.SettingEnabledAtStartup2",
      HttpsFirstModeSetting::kDisabled, 1);

  EXPECT_TRUE(variations::IsInSyntheticTrialGroup("HttpsFirstModeClientSetting",
                                                  "Disabled"));

  // Emulate changing the UI setting to Enabled. This should get recorded
  // in the histogram.
  SetUISetting(HttpsFirstModeSetting::kEnabledFull);
  histograms()->ExpectUniqueSample("Security.HttpsFirstMode.SettingChanged2",
                                   HttpsFirstModeSetting::kEnabledFull, 1);
  EXPECT_TRUE(variations::IsInSyntheticTrialGroup("HttpsFirstModeClientSetting",
                                                  "Enabled"));
}

IN_PROC_BROWSER_TEST_F(HttpsUpgradesPrefsBrowserTest, PrefStatesRecorded) {
  // Restarting the browser from the PRE_ test should record the startup setting
  // histogram. Checking the unique count also ensures that other profile
  // types (e.g. the ChromeOS sign-in profile) don't cause double-counting.
  EXPECT_TRUE(GetPref());
  histograms()->ExpectUniqueSample(
      "Security.HttpsFirstMode.SettingEnabledAtStartup2",
      HttpsFirstModeSetting::kEnabledFull, 1);
  EXPECT_TRUE(variations::IsInSyntheticTrialGroup("HttpsFirstModeClientSetting",
                                                  "Enabled"));

  // Open an Incognito window. Startup metrics should not get recorded.
  CreateIncognitoBrowser();
  histograms()->ExpectTotalCount(
      "Security.HttpsFirstMode.SettingEnabledAtStartup2", 1);
}

enum class BalancedModeParam {
  kNotAutoEnabled,
  kAutoEnabled,
};

// A simple test fixture that constructs a HistogramTester (so that it gets
// initialized before browser startup). Used for testing pref tracking logic.
// Variant of HttpsUpgradesPrefsBrowserTest but with the
// HttpsFirstBalancedMode feature enabled.
class HttpsUpgradesBalancedModePrefsBrowserTest
    : public testing::WithParamInterface<BalancedModeParam>,
      public InProcessBrowserTest {
 protected:
  void SetUp() override {
    // Feature flag must be enabled before SetUp() continues.
    switch (GetParam()) {
      case BalancedModeParam::kNotAutoEnabled:
        feature_list()->InitWithFeatures(
            /*enabled_features=*/{features::kHttpsFirstBalancedMode},
            /*disabled_features=*/{
                features::kHttpsFirstBalancedModeAutoEnable});
        break;

      case BalancedModeParam::kAutoEnabled:
        feature_list()->InitWithFeatures(
            /*enabled_features=*/{features::kHttpsFirstBalancedMode,
                                  features::kHttpsFirstBalancedModeAutoEnable},
            /*disabled_features=*/{});
        break;
    }
    InProcessBrowserTest::SetUp();
  }

  void SetUISetting(HttpsFirstModeSetting setting) {
    extensions::settings_private::GeneratedPrefs prefs(browser()->GetProfile());
    prefs.SetPref(
        kGeneratedHttpsFirstModePref,
        std::make_unique<base::Value>(static_cast<int>(setting)).get());
  }

  bool GetPref() const {
    auto* prefs = browser()->GetProfile()->GetPrefs();
    return prefs->GetBoolean(prefs::kHttpsFirstBalancedMode);
  }

  base::test::ScopedFeatureList* feature_list() { return &feature_list_; }
  base::HistogramTester* histograms() { return &histograms_; }

 private:
  base::test::ScopedFeatureList feature_list_;
  base::HistogramTester histograms_;
};

INSTANTIATE_TEST_SUITE_P(
    /* no prefix */,
    HttpsUpgradesBalancedModePrefsBrowserTest,
    ::testing::Values(BalancedModeParam::kNotAutoEnabled,
                      BalancedModeParam::kAutoEnabled),
    // Map param to a human-readable string for better test output.
    [](testing::TestParamInfo<BalancedModeParam> input_type) -> std::string {
      switch (input_type.param) {
        case BalancedModeParam::kNotAutoEnabled:
          return "BalancedModeNotAutoEnabled";
        case BalancedModeParam::kAutoEnabled:
          return "BalancedModeAutoEnabled";
      }
    });

// Tests that the HTTPS-First Mode setting is recorded at startup and when
// changed, when the HFM-Balanced-Mode feature flag is enabled. This test
// requires restarting the browser to test the "at startup" metric in order
// for the preference state to be set up before the HttpsFirstModeService is
// created.
IN_PROC_BROWSER_TEST_P(HttpsUpgradesBalancedModePrefsBrowserTest,
                       PRE_PrefStatesRecorded) {
  if (GetParam() == BalancedModeParam::kNotAutoEnabled) {
    // The default Balanced Mode pref state is false, which should get recorded
    // when the initial browser instance is started here.
    histograms()->ExpectUniqueSample(
        "Security.HttpsFirstMode.SettingEnabledAtStartup2",
        HttpsFirstModeSetting::kDisabled, 1);

    EXPECT_TRUE(variations::IsInSyntheticTrialGroup(
        "HttpsFirstModeClientSetting", "Disabled"));
  } else if (GetParam() == BalancedModeParam::kAutoEnabled) {
    // The default Balanced Mode pref state is false, but Balanced Mode is auto
    // enabled.
    histograms()->ExpectUniqueSample(
        "Security.HttpsFirstMode.SettingEnabledAtStartup2",
        HttpsFirstModeSetting::kEnabledBalanced, 1);

    EXPECT_TRUE(variations::IsInSyntheticTrialGroup(
        "HttpsFirstModeClientSetting", "Balanced"));
  }

  // Emulate changing the UI setting to Balanced Mode. This should get recorded
  // in the histogram.
  SetUISetting(HttpsFirstModeSetting::kEnabledBalanced);
  EXPECT_TRUE(GetPref());
  histograms()->ExpectUniqueSample("Security.HttpsFirstMode.SettingChanged2",
                                   HttpsFirstModeSetting::kEnabledBalanced, 1);
  EXPECT_TRUE(variations::IsInSyntheticTrialGroup("HttpsFirstModeClientSetting",
                                                  "Balanced"));
}

IN_PROC_BROWSER_TEST_P(HttpsUpgradesBalancedModePrefsBrowserTest,
                       PrefStatesRecorded) {
  // Restarting the browser from the PRE_ test should record the startup setting
  // histogram. Checking the unique count also ensures that other profile
  // types (e.g. the ChromeOS sign-in profile) don't cause double-counting.
  EXPECT_TRUE(GetPref());
  histograms()->ExpectUniqueSample(
      "Security.HttpsFirstMode.SettingEnabledAtStartup2",
      HttpsFirstModeSetting::kEnabledBalanced, 1);
  EXPECT_TRUE(variations::IsInSyntheticTrialGroup("HttpsFirstModeClientSetting",
                                                  "Balanced"));

  // Open an Incognito window. Startup metrics should not get recorded.
  CreateIncognitoBrowser();
  histograms()->ExpectTotalCount(
      "Security.HttpsFirstMode.SettingEnabledAtStartup2", 1);
}

using TypicallySecureUserBrowserTest = InProcessBrowserTest;

IN_PROC_BROWSER_TEST_F(TypicallySecureUserBrowserTest,
                       PRE_RestoreCountsOnStartup_OneNavigation) {
  HttpsFirstModeService* hfm_service =
      HttpsFirstModeServiceFactory::GetForProfile(browser()->GetProfile());
  hfm_service->IncrementRecentNavigationCount();
}

IN_PROC_BROWSER_TEST_F(TypicallySecureUserBrowserTest,
                       RestoreCountsOnStartup_OneNavigation) {
  HttpsFirstModeService* hfm_service =
      HttpsFirstModeServiceFactory::GetForProfile(browser()->GetProfile());
  // A single navigation will not be persisted to the pref and won't be
  // restored on startup because navigations are persisted in batches of 10.
  EXPECT_EQ(0u, hfm_service->GetRecentNavigationCount());
}

IN_PROC_BROWSER_TEST_F(TypicallySecureUserBrowserTest,
                       PRE_RestoreCountsOnStartup_TenNavigations) {
  HttpsFirstModeService* hfm_service =
      HttpsFirstModeServiceFactory::GetForProfile(browser()->GetProfile());
  // Increment repeatedly to force the counts to be persisted to the pref.
  for (size_t i = 0; i < 10; i++) {
    hfm_service->IncrementRecentNavigationCount();
  }
}

IN_PROC_BROWSER_TEST_F(TypicallySecureUserBrowserTest,
                       RestoreCountsOnStartup_TenNavigations) {
  HttpsFirstModeService* hfm_service =
      HttpsFirstModeServiceFactory::GetForProfile(browser()->GetProfile());
  EXPECT_EQ(10u, hfm_service->GetRecentNavigationCount());
}

// Tests for HFM heuristics without Balanced Mode enabled. These are unusual
// configurations and shouldn't appear in production.
// TODO(crbug.com/349860796): Remove after balanced mode is fully launched.
using HttpsUpgradesHeuristicsWithoutBalancedModeBrowserTest =
    HttpsUpgradesBrowserTest;

INSTANTIATE_TEST_SUITE_P(
    /* no prefix */,
    HttpsUpgradesHeuristicsWithoutBalancedModeBrowserTest,
    ::testing::Values(
        HttpsUpgradesTestType::
            kHttpsFirstModeWithSiteEngagementWithoutBalancedMode));

// Test that Site Engagement Heuristic without Balanced Mode is a no-op.
IN_PROC_BROWSER_TEST_P(
    HttpsUpgradesHeuristicsWithoutBalancedModeBrowserTest,
    UrlWithHttpScheme_BrokenSSL_SiteEngagementHeuristicWithoutBalancedMode_ShouldIgnore) {
  ASSERT_FALSE(IsIncognito() || IsHttpsFirstModePrefEnabled());

  // Disable the testing port configuration, as this test doesn't use the
  // EmbeddedTestServer.
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(0);
  HttpsUpgradesInterceptor::SetHttpPortForTesting(0);
  auto url_loader_interceptor = MakeInterceptorForSiteEngagementHeuristic();

  content::WebContents* contents =
      GetBrowser()->tab_strip_model()->GetActiveWebContents();
  Profile* profile = GetBrowser()->GetProfile();
  content::SSLHostStateDelegate* state = profile->GetSSLHostStateDelegate();

  // Set test clock.
  auto clock = std::make_unique<base::SimpleTestClock>();
  auto* clock_ptr = clock.get();
  StatefulSSLHostStateDelegate* chrome_state =
      static_cast<StatefulSSLHostStateDelegate*>(state);
  chrome_state->SetClockForTesting(std::move(clock));

  // Start the clock at standard system time.
  clock_ptr->SetNow(base::Time::NowFromSystemTime());

  // Set site engagement scores so that this site would have HFM enabled if
  // HFM+SE kicked in.
  GURL http_url("http://example.com");
  GURL https_url("https://example.com");

  SetSiteEngagementScore(http_url, kLowSiteEngagementScore);
  SetSiteEngagementScore(https_url, kHighSiteEnagementScore);
  HttpsFirstModeService* hfm_service =
      HttpsFirstModeServiceFactory::GetForProfile(profile);
  MaybeEnableHttpsFirstModeForEngagedSitesAndWait(hfm_service);

  // This URL should be upgraded by HTTPS-Upgrades and should fall back to HTTP,
  // but not have HFM auto-enabled on it because balanced mode isn't enabled.
  NavigateAndWaitForFallback(contents, http_url);
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());

  EXPECT_EQ(HFMInterstitialType::kNone, GetHFMInterstitialType(contents));

  // Verify that navigation event metrics were correctly recorded.
  histograms()->ExpectTotalCount(kEventHistogram, 3);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeAttempted, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeFailed, 1);
  histograms()->ExpectBucketCount(kEventHistogram, Event::kUpgradeCertError, 1);

  // Engagement heuristic shouldn't handle any navigation events.
  histograms()->ExpectTotalCount(kEventHistogramWithEngagementHeuristic, 0);

  // Security level histogram should not record kHttpsEnforcedOnHostname.
  histograms()->ExpectBucketCount(
      kNavigationRequestSecurityLevelHistogram,
      NavigationRequestSecurityLevel::kHttpsEnforcedOnHostname, 0);
}

// Minimal test fixture for testing the interaction between the
// SecureOriginAllowlist (set via the command-line switch
// `switches::kUnsafelyTreatInsecureOriginAsSecure`) and HTTPS-First Balanced
// Mode.
class HttpsUpgradesSecureOriginAllowlistBrowserTest
    : public InProcessBrowserTest {
 public:
  HttpsUpgradesSecureOriginAllowlistBrowserTest() {
    feature_list_.InitWithFeatures(
        {features::kHttpsFirstBalancedModeAutoEnable},
        // TODO(crbug.com/351990829): Update these tests to work with the new
        // native dialog UI, and then re-enable this feature.
        {security_interstitials::features::kHttpsFirstDialogUi});
  }
  ~HttpsUpgradesSecureOriginAllowlistBrowserTest() override = default;

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    embedded_test_server()->AddDefaultHandlers(GetChromeTestDataDir());
    ASSERT_TRUE(embedded_test_server()->Start());
    HttpsUpgradesInterceptor::SetHttpPortForTesting(
        embedded_test_server()->port());
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    // This sets a wildcard entry in the allowlist, because we can't specify
    // an exact origin as we don't yet have the embedded test server's port
    // (it isn't started until later).
    command_line->AppendSwitchASCII(
        network::switches::kUnsafelyTreatInsecureOriginAsSecure,
        "*.example.com");
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_F(HttpsUpgradesSecureOriginAllowlistBrowserTest,
                       HostInAllowlistExemptedFromHttpsFirstMode) {
  GURL url_in_allowlist =
      embedded_test_server()->GetURL("test.example.com", "/simple.html");
  content::WebContents* contents =
      browser()->GetTabStripModel()->GetActiveWebContents();
  EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, url_in_allowlist));
  EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
}

IN_PROC_BROWSER_TEST_F(HttpsUpgradesSecureOriginAllowlistBrowserTest,
                       HostNotInAllowlistShowWarning) {
  GURL url_not_in_allowlist =
      embedded_test_server()->GetURL("not-example.com", "/simple.html");
  content::WebContents* contents =
      browser()->GetTabStripModel()->GetActiveWebContents();
  EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, url_not_in_allowlist));
  EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
}

// A separate test fixture for Advanced Protection to ensure that HFM triggers
// properly, even if balanced mode is enabled by default.
class HttpsUpgradesAdvancedProtectionBrowserTest : public InProcessBrowserTest {
 public:
  HttpsUpgradesAdvancedProtectionBrowserTest() {
    feature_list_.InitWithFeatures(
        /*enabled_features=*/{features::
                                  kHttpsFirstModeForAdvancedProtectionUsers,
                              features::kHttpsFirstBalancedModeAutoEnable},
        /*disabled_features=*/{
            security_interstitials::features::kHttpsFirstDialogUi});
  }
  ~HttpsUpgradesAdvancedProtectionBrowserTest() override = default;

  void SetUp() override {
    ChromeSecurityBlockingPageFactory::SetEnterpriseManagedForTesting(false);
    InProcessBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    // Enable Advanced Protection for the profile via the testing API
    safe_browsing::AdvancedProtectionStatusManagerFactory::GetForProfile(
        browser()->GetProfile())
        ->SetAdvancedProtectionStatusForTesting(true);

    host_resolver()->AddRule("*", "127.0.0.1");
    http_server_.AddDefaultHandlers(GetChromeTestDataDir());
    ASSERT_TRUE(http_server_.Start());
  }

  net::EmbeddedTestServer* http_server() { return &http_server_; }

 private:
  base::test::ScopedFeatureList feature_list_;
  net::EmbeddedTestServer http_server_{net::EmbeddedTestServer::TYPE_HTTP};
};

// Verifies that Advanced Protection users get HFM warnings for non-default
// ports and it does not get incorrectly bypassed by the Balanced Mode
// exclusion. Regression test for crbug.com/501741117.
IN_PROC_BROWSER_TEST_F(HttpsUpgradesAdvancedProtectionBrowserTest,
                       UrlWithNonDefaultPort_ShouldUpgradeAndShowInterstitial) {
  GURL http_url = http_server()->GetURL("foo.com", "/simple.html");
  EXPECT_NE(http_url.IntPort(), 80);  // Ensure it's not the default port

  auto* contents = browser()->GetTabStripModel()->GetActiveWebContents();

  EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, http_url));
  EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
}

// Test fixture for testing Ask-Before-HTTP fallback delay via FeatureParams.
// Initializes ScopedFeatureList in the constructor before browser startup.
class HttpsUpgradesAskBeforeHttpDelayTest : public InProcessBrowserTest {
 public:
  HttpsUpgradesAskBeforeHttpDelayTest() {
    feature_list_.InitAndEnableFeatureWithParameters(
        features::kHttpsUpgrades, {{"ask-before-http-fallback-delay", "0ms"},
                                   {"fallback-delay", "100s"}});
  }
  ~HttpsUpgradesAskBeforeHttpDelayTest() override = default;

  void SetUpOnMainThread() override {
    // By default allow all hosts on HTTPS.
    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
    host_resolver()->AddRule("*", "127.0.0.1");

    // bad-https.com will fail HTTPS and fallback to HTTP.
    scoped_refptr<net::X509Certificate> cert(https_server_.GetCertificate());
    net::CertVerifyResult verify_result;
    verify_result.is_issued_by_known_root = false;
    verify_result.verified_cert = cert;
    verify_result.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;
    mock_cert_verifier_.mock_cert_verifier()->AddResultForCertAndHost(
        cert, "bad-https.com", verify_result,
        net::ERR_CERT_COMMON_NAME_INVALID);

    http_server_.AddDefaultHandlers(GetChromeTestDataDir());
    https_server_.AddDefaultHandlers(GetChromeTestDataDir());
    ASSERT_TRUE(http_server_.Start());
    ASSERT_TRUE(https_server_.Start());

    HttpsUpgradesInterceptor::SetHttpsPortForTesting(https_server_.port());
    HttpsUpgradesInterceptor::SetHttpPortForTesting(http_server_.port());

    // Enable HFM pref.
    auto* prefs = browser()->GetProfile()->GetPrefs();
    prefs->SetBoolean(prefs::kHttpsOnlyModeEnabled, true);
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    mock_cert_verifier_.SetUpCommandLine(command_line);
  }

  void SetUpInProcessBrowserTestFixture() override {
    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
  }

  void TearDownInProcessBrowserTestFixture() override {
    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
  }

  net::EmbeddedTestServer* http_server() { return &http_server_; }
  net::EmbeddedTestServer* https_server() { return &https_server_; }

 private:
  base::test::ScopedFeatureList feature_list_;
  net::EmbeddedTestServer http_server_{net::EmbeddedTestServer::TYPE_HTTP};
  net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
  content::ContentMockCertVerifier mock_cert_verifier_;
};

IN_PROC_BROWSER_TEST_F(HttpsUpgradesAskBeforeHttpDelayTest,
                       FallbackDelayFeatureParam_AskBeforeHttp) {
  // Set up a custom HTTPS server that times out without sending a response.
  net::EmbeddedTestServer timeout_server{net::EmbeddedTestServer::TYPE_HTTPS};
  timeout_server.RegisterRequestHandler(base::BindLambdaForTesting(
      [&](const net::test_server::HttpRequest& request)
          -> std::unique_ptr<net::test_server::HttpResponse> {
        return std::make_unique<net::test_server::HungResponse>();
      }));
  ASSERT_TRUE(timeout_server.Start());
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(timeout_server.port());

  const GURL http_url = http_server()->GetURL("foo.com", "/simple.html");
  auto* contents = browser()->GetTabStripModel()->GetActiveWebContents();

  // Navigate to HTTP. It should upgrade to HTTPS and immediately fallback to
  // HTTP and show the interstitial because ask-before-http-fallback-delay is
  // 0ms.
  EXPECT_FALSE(NavigateToURLWithLinkTransition(contents, http_url));

  EXPECT_TRUE(IsShowingHttpsFirstModeInterstitial(contents));
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
}

// Test fixture for testing Silent Fallback delay via FeatureParams.
// Initializes ScopedFeatureList in the constructor before browser startup.
// This is the same as HttpsUpgradesAskBeforeHttpDelayTest but with the
// FeatureParams swapped and the HFM pref disabled so this runs with silent
// HTTPS-Upgrades only.
class HttpsUpgradesSilentFallbackDelayTest : public InProcessBrowserTest {
 public:
  HttpsUpgradesSilentFallbackDelayTest() {
    feature_list_.InitAndEnableFeatureWithParameters(
        features::kHttpsUpgrades, {{"fallback-delay", "0ms"},
                                   {"ask-before-http-fallback-delay", "100s"}});
  }
  ~HttpsUpgradesSilentFallbackDelayTest() override = default;

  void SetUpOnMainThread() override {
    // By default allow all hosts on HTTPS.
    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
    host_resolver()->AddRule("*", "127.0.0.1");

    // bad-https.com will fail HTTPS and fallback to HTTP.
    scoped_refptr<net::X509Certificate> cert(https_server_.GetCertificate());
    net::CertVerifyResult verify_result;
    verify_result.is_issued_by_known_root = false;
    verify_result.verified_cert = cert;
    verify_result.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;
    mock_cert_verifier_.mock_cert_verifier()->AddResultForCertAndHost(
        cert, "bad-https.com", verify_result,
        net::ERR_CERT_COMMON_NAME_INVALID);

    http_server_.AddDefaultHandlers(GetChromeTestDataDir());
    https_server_.AddDefaultHandlers(GetChromeTestDataDir());
    ASSERT_TRUE(http_server_.Start());
    ASSERT_TRUE(https_server_.Start());

    HttpsUpgradesInterceptor::SetHttpsPortForTesting(https_server_.port());
    HttpsUpgradesInterceptor::SetHttpPortForTesting(http_server_.port());

    // Disable HFM pref.
    auto* prefs = browser()->GetProfile()->GetPrefs();
    prefs->SetBoolean(prefs::kHttpsOnlyModeEnabled, false);
    prefs->SetBoolean(prefs::kHttpsFirstBalancedMode, false);
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    mock_cert_verifier_.SetUpCommandLine(command_line);
  }

  void SetUpInProcessBrowserTestFixture() override {
    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
  }

  void TearDownInProcessBrowserTestFixture() override {
    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
  }

  net::EmbeddedTestServer* http_server() { return &http_server_; }
  net::EmbeddedTestServer* https_server() { return &https_server_; }

 private:
  base::test::ScopedFeatureList feature_list_;
  net::EmbeddedTestServer http_server_{net::EmbeddedTestServer::TYPE_HTTP};
  net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
  content::ContentMockCertVerifier mock_cert_verifier_;
};

IN_PROC_BROWSER_TEST_F(HttpsUpgradesSilentFallbackDelayTest,
                       FallbackDelayFeatureParam_SilentFallback) {
  // Set up a custom HTTPS server that times out without sending a response.
  net::EmbeddedTestServer timeout_server{net::EmbeddedTestServer::TYPE_HTTPS};
  timeout_server.RegisterRequestHandler(base::BindLambdaForTesting(
      [&](const net::test_server::HttpRequest& request)
          -> std::unique_ptr<net::test_server::HttpResponse> {
        return std::make_unique<net::test_server::HungResponse>();
      }));
  ASSERT_TRUE(timeout_server.Start());
  HttpsUpgradesInterceptor::SetHttpsPortForTesting(timeout_server.port());

  const GURL http_url = http_server()->GetURL("foo.com", "/simple.html");
  auto* contents = browser()->GetTabStripModel()->GetActiveWebContents();

  // Navigate to HTTP. It should upgrade to HTTPS and immediately fallback to
  // HTTP because fallback-delay is 0ms.
  EXPECT_TRUE(NavigateToURLWithLinkTransition(contents, http_url));

  EXPECT_FALSE(IsShowingHttpsFirstModeInterstitial(contents));
  EXPECT_EQ(http_url, contents->GetLastCommittedURL());
}

// Fixture for testing interactions between HTTPS-Upgrades and Safe Browsing.
class HttpsUpgradesSafeBrowsingTest : public InProcessBrowserTest {
 public:
  HttpsUpgradesSafeBrowsingTest() {
    feature_list_.InitWithFeatures(
        /*enabled_features=*/{features::kHttpsUpgrades,
                              security_interstitials::features::
                                  kHttpsFirstDialogUi},
        /*disabled_features=*/{});
  }

  void CreatedBrowserMainParts(
      content::BrowserMainParts* browser_main_parts) override {
    InProcessBrowserTest::CreatedBrowserMainParts(browser_main_parts);
    sb_factory_ =
        std::make_unique<safe_browsing::TestSafeBrowsingServiceFactory>();
    fake_db_manager_ =
        base::MakeRefCounted<safe_browsing::FakeSafeBrowsingDatabaseManager>(
            content::GetUIThreadTaskRunner({}));
    sb_factory_->SetTestDatabaseManager(fake_db_manager_.get());
    sb_factory_->SetTestUIManager(
        new safe_browsing::TestSafeBrowsingUIManager());
    safe_browsing::SafeBrowsingService::RegisterFactory(sb_factory_.get());
  }

  void TearDown() override {
    InProcessBrowserTest::TearDown();
    safe_browsing::SafeBrowsingService::RegisterFactory(nullptr);
  }

  void SetUpOnMainThread() override {
    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
    host_resolver()->AddRule("*", "127.0.0.1");

    http_server_.AddDefaultHandlers(GetChromeTestDataDir());
    https_server_.AddDefaultHandlers(GetChromeTestDataDir());
    ASSERT_TRUE(http_server_.Start());
    ASSERT_TRUE(https_server_.Start());

    HttpsUpgradesInterceptor::SetHttpsPortForTesting(https_server_.port());
    HttpsUpgradesInterceptor::SetHttpPortForTesting(http_server_.port());

    // Enable the HFM pref.
    browser()->GetProfile()->GetPrefs()->SetBoolean(
        prefs::kHttpsOnlyModeEnabled, true);
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    mock_cert_verifier_.SetUpCommandLine(command_line);
  }

  void SetUpInProcessBrowserTestFixture() override {
    mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
  }

  void TearDownInProcessBrowserTestFixture() override {
    mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
  }

 protected:
  base::test::ScopedFeatureList feature_list_;
  std::unique_ptr<safe_browsing::TestSafeBrowsingServiceFactory> sb_factory_;
  scoped_refptr<safe_browsing::FakeSafeBrowsingDatabaseManager>
      fake_db_manager_;
  net::EmbeddedTestServer http_server_{net::EmbeddedTestServer::TYPE_HTTP};
  net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
  content::ContentMockCertVerifier mock_cert_verifier_;
};

// Tests that if a navigation gets upgraded and the resulting URL is blocked by
// Safe Browsing, we don't trigger fallback to HTTP and instead show the Safe
// Browsing interstitial. Regression test for crbug.com/443741921.
IN_PROC_BROWSER_TEST_F(HttpsUpgradesSafeBrowsingTest,
                       SafeBrowsingBlock_ShouldNotTriggerHFM) {
  GURL http_url = http_server_.GetURL("malicious.com", "/simple.html");
  GURL https_url = https_server_.GetURL("malicious.com", "/simple.html");

  // Mark the HTTPS URL as dangerous (malware).
  fake_db_manager_->AddDangerousUrl(
      https_url, safe_browsing::SBThreatType::SB_THREAT_TYPE_URL_MALWARE);

  auto* contents = browser()->GetTabStripModel()->GetActiveWebContents();
  content::TestNavigationObserver nav_observer(contents, 1);

  // Navigate to the HTTP URL.
  EXPECT_FALSE(content::NavigateToURL(contents, http_url));
  nav_observer.Wait();

  // The navigation should have failed because of the SB block.
  EXPECT_FALSE(nav_observer.last_navigation_succeeded());

  // We should be showing an interstitial (the Safe Browsing one).
  EXPECT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(contents));

  // Verify that we are NOT showing the HTTPS-First Mode interstitial.
  EXPECT_FALSE(
      chrome_browser_interstitials::IsShowingHttpsFirstModeInterstitial(
          contents));

  // Verify that the browser stayed on the HTTPS URL and did not fallback to
  // HTTP.
  EXPECT_EQ(https_url, contents->GetVisibleURL());
}
