// Copyright 2012 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 <array>
#include <memory>
#include <string_view>
#include <utility>

#include "base/base64.h"
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_reader.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/field_trial.h"
#include "base/run_loop.h"
#include "base/strings/escape.h"
#include "base/strings/pattern.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/current_thread.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_clock.h"
#include "base/test/test_future.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/default_clock.h"
#include "base/time/default_tick_clock.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_content_browser_client.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/interstitials/security_interstitial_idn_test.h"
#include "chrome/browser/interstitials/security_interstitial_page_test_utils.h"
#include "chrome/browser/net/profile_network_context_service.h"
#include "chrome/browser/net/profile_network_context_service_factory.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/policy/policy_test_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ssl/cert_verifier_browser_test.h"
#include "chrome/browser/ssl/chrome_security_blocking_page_factory.h"
#include "chrome/browser/ssl/chrome_security_state_util.h"
#include "chrome/browser/ssl/https_upgrades_util.h"
#include "chrome/browser/ssl/ssl_browsertest_base.h"
#include "chrome/browser/ssl/ssl_browsertest_util.h"
#include "chrome/browser/ssl/ssl_client_certificate_selector.h"
#include "chrome/browser/ssl/ssl_error_controller_client.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/profile_browser_collection.h"
#include "chrome/browser/ui/navigator/browser_navigator.h"
#include "chrome/browser/ui/navigator/browser_navigator_params.h"
#include "chrome/browser/ui/tab_ui_helper.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/webui/certificate_viewer/certificate_viewer_webui.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/api/safe_browsing_private.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/test_launcher_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/content_settings/browser/page_specific_content_settings.h"
#include "components/content_settings/common/content_settings_agent.mojom.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/error_page/content/browser/net_error_auto_reloader.h"
#include "components/metrics/content/subprocess_metrics_provider.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/network_time/network_time_test_utils.h"
#include "components/network_time/network_time_tracker.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/testing_pref_service.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/security_interstitials/content/bad_clock_blocking_page.h"
#include "components/security_interstitials/content/captive_portal_blocking_page.h"
#include "components/security_interstitials/content/common_name_mismatch_handler.h"
#include "components/security_interstitials/content/insecure_form_blocking_page.h"
#include "components/security_interstitials/content/insecure_form_navigation_throttle.h"
#include "components/security_interstitials/content/mitm_software_blocking_page.h"
#include "components/security_interstitials/content/security_interstitial_controller_client.h"
#include "components/security_interstitials/content/security_interstitial_page.h"
#include "components/security_interstitials/content/security_interstitial_tab_helper.h"
#include "components/security_interstitials/content/ssl_blocking_page.h"
#include "components/security_interstitials/content/ssl_error_assistant.h"
#include "components/security_interstitials/content/ssl_error_assistant.pb.h"
#include "components/security_interstitials/content/ssl_error_handler.h"
#include "components/security_interstitials/content/stateful_ssl_host_state_delegate.h"
#include "components/security_interstitials/core/controller_client.h"
#include "components/security_interstitials/core/https_only_mode_metrics.h"
#include "components/security_interstitials/core/metrics_helper.h"
#include "components/security_interstitials/core/pref_names.h"
#include "components/security_state/core/security_state.h"
#include "components/strings/grit/components_strings.h"
#include "components/tabs/public/tab_interface.h"
#include "components/web_modal/web_contents_modal_dialog_manager.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/client_certificate_delegate.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_entry_restore_context.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/network_service_util.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/restore_type.h"
#include "content/public/browser/ssl_status.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_mock_cert_verifier.h"
#include "content/public/test/download_test_observer.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/url_loader_interceptor.h"
#include "crypto/hash.h"
#include "extensions/browser/event_router.h"
#include "google_apis/gaia/gaia_id.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
#include "net/base/features.h"
#include "net/base/host_port_pair.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/cert/asn1_util.h"
#include "net/cert/cert_database.h"
#include "net/cert/cert_status_flags.h"
#include "net/cert/mock_cert_verifier.h"
#include "net/cert/test_root_certs.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/http/transport_security_state_test_util.h"
#include "net/ssl/client_cert_identity_test_util.h"
#include "net/ssl/client_cert_store.h"
#include "net/ssl/ssl_config.h"
#include "net/ssl/ssl_info.h"
#include "net/ssl/ssl_private_key.h"
#include "net/ssl/ssl_server_config.h"
#include "net/test/cert_test_util.h"
#include "net/test/embedded_test_server/controllable_http_response.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/http_response.h"
#include "net/test/embedded_test_server/install_default_websocket_handlers.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "net/test/quic_simple_test_server.h"
#include "net/test/test_certificate_data.h"
#include "net/test/test_data_directory.h"
#include "net/third_party/quiche/src/quiche/quic/platform/api/quic_flags.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/network_switches.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/common/chrome_debug_urls.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/page_state/page_state.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
#include "third_party/blink/public/mojom/window_features/window_features.mojom.h"
#include "ui/base/l10n/l10n_util.h"

#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "chrome/browser/extensions/chrome_test_extension_loader.h"
#include "chrome/browser/extensions/scoped_test_mv2_enabler.h"
#include "extensions/browser/background_script_executor.h"
#include "extensions/common/extension.h"
#include "extensions/test/test_extension_dir.h"
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

#if BUILDFLAG(USE_NSS_CERTS)
#include "chrome/browser/net/nss_service.h"
#include "chrome/browser/net/nss_service_factory.h"
#include "crypto/scoped_test_nss_db.h"
#include "net/cert/nss_cert_database.h"
#include "net/cert/x509_util_nss.h"
#endif  // BUILDFLAG(USE_NSS_CERTS)

#if BUILDFLAG(IS_CHROMEOS)
#include "ash/constants/ash_switches.h"
#include "base/path_service.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/policy/profile_policy_connector_builder.h"
#include "components/policy/core/common/policy_namespace.h"
#include "components/policy/core/common/policy_service.h"
#endif  // BUILDFLAG(IS_CHROMEOS)

using content::WebContents;
namespace AuthState = ssl_test_util::AuthState;
namespace CertError = ssl_test_util::CertError;

namespace {

const int kLargeVersionId = 0xFFFFFF;

const char kHstsTestHostName[] = "hsts-example.test";

enum ProceedDecision {
  SSL_INTERSTITIAL_PROCEED,
  SSL_INTERSTITIAL_DO_NOT_PROCEED
};

// A WebContentsObserver that allows observing when the page has set a
// particular SSL content status flag. Assumes that the flag is not set when the
// observer is created.
class SSLContentStatusObserver : public content::WebContentsObserver {
 public:
  explicit SSLContentStatusObserver(content::WebContents* web_contents,
                                    content::SSLStatus::ContentStatusFlags flag)
      : content::WebContentsObserver(web_contents), flag_(flag) {
    content::NavigationEntry* entry =
        web_contents->GetController().GetVisibleEntry();
    if (entry) {
      DCHECK(!(entry->GetSSL().content_status & flag_));
    }
  }

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

  ~SSLContentStatusObserver() override = default;

  void DidChangeVisibleSecurityState() override {
    content::NavigationEntry* entry =
        web_contents()->GetController().GetVisibleEntry();
    if (entry && (entry->GetSSL().content_status & flag_)) {
      run_loop_.Quit();
    }
  }

  void WaitForSSLContentStatusFlag() { run_loop_.Run(); }

 private:
  // The content status flag of interest
  content::SSLStatus::ContentStatusFlags flag_;
  base::RunLoop run_loop_;
};

// This observer waits for the SSLErrorHandler to start an interstitial timer
// for the given web contents.
class SSLInterstitialTimerObserver {
 public:
  explicit SSLInterstitialTimerObserver(WebContents* web_contents)
      : web_contents_(web_contents), message_loop_runner_(new base::RunLoop) {
    callback_ = base::BindRepeating(
        &SSLInterstitialTimerObserver::OnTimerStarted, base::Unretained(this));
    SSLErrorHandler::SetInterstitialTimerStartedCallbackForTesting(&callback_);
  }

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

  ~SSLInterstitialTimerObserver() {
    SSLErrorHandler::SetInterstitialTimerStartedCallbackForTesting(nullptr);
  }

  // Waits until the interstitial delay timer in SSLErrorHandler is started.
  void WaitForTimerStarted() { message_loop_runner_->Run(); }

  // Returns true if the interstitial delay timer has been started.
  bool timer_started() const { return timer_started_; }

 private:
  void OnTimerStarted(WebContents* web_contents) {
    timer_started_ = true;
    if (web_contents_ == web_contents) {
      message_loop_runner_->Quit();
    }
  }

  bool timer_started_ = false;
  raw_ptr<const WebContents> web_contents_;
  SSLErrorHandler::TimerStartedCallback callback_;

  std::unique_ptr<base::RunLoop> message_loop_runner_;
};

class ChromeContentBrowserClientForMixedContentTest
    : public ChromeContentBrowserClient {
 public:
  ChromeContentBrowserClientForMixedContentTest() = default;

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

  void OverrideWebPreferences(
      content::WebContents* web_contents,
      content::SiteInstance& main_frame_site,
      blink::web_pref::WebPreferences* web_prefs) override {
    web_prefs->allow_running_insecure_content = allow_running_insecure_content_;
    web_prefs->strict_mixed_content_checking = strict_mixed_content_checking_;
    web_prefs->strictly_block_blockable_mixed_content =
        strictly_block_blockable_mixed_content_;
  }
  void SetMixedContentSettings(bool allow_running_insecure_content,
                               bool strict_mixed_content_checking,
                               bool strictly_block_blockable_mixed_content) {
    allow_running_insecure_content_ = allow_running_insecure_content;
    strict_mixed_content_checking_ = strict_mixed_content_checking;
    strictly_block_blockable_mixed_content_ =
        strictly_block_blockable_mixed_content;
  }

 private:
  bool allow_running_insecure_content_ = false;
  bool strict_mixed_content_checking_ = false;
  bool strictly_block_blockable_mixed_content_ = false;
};

// Returns the Sha256 hash of the SPKI of |cert|.
std::array<uint8_t, crypto::hash::kSha256Size> GetSPKIHash(
    const CRYPTO_BUFFER* cert) {
  std::string_view spki_bytes;
  EXPECT_TRUE(net::asn1::ExtractSPKIFromDERCert(
      net::x509_util::CryptoBufferAsStringPiece(cert), &spki_bytes));
  return crypto::hash::Sha256(base::as_byte_span(spki_bytes));
}

// Compares two SSLStatuses to check if they match up before and after an
// interstitial. To match up, they should have the same connection information
// properties, such as certificate, connection status, connection security,
// etc. Content status and user data are not compared. Returns true if the
// statuses match and false otherwise.
bool ComparePreAndPostInterstitialSSLStatuses(const content::SSLStatus& one,
                                              const content::SSLStatus& two) {
  // TODO(mattm): It feels like this should use
  // certificate->EqualsIncludingChain, but that fails on some platforms. Find
  // out why and document or fix.
  return one.initialized == two.initialized &&
         !!one.certificate == !!two.certificate &&
         (one.certificate
              ? one.certificate->EqualsExcludingChain(two.certificate.get())
              : true) &&
         one.cert_status == two.cert_status &&
         one.key_exchange_group == two.key_exchange_group &&
         // Skip comparing the peer_signature_algorithm, because it is not
         // filled in by the time of an interstitial.
         one.connection_status == two.connection_status &&
         one.pkp_bypassed == two.pkp_bypassed;
}

void ExpectInterstitialElementHidden(WebContents* tab,
                                     const std::string& element_id,
                                     bool expect_hidden) {
  content::RenderFrameHost* frame = tab->GetPrimaryMainFrame();
  // Send CMD_TEXT_FOUND to indicate that the 'hidden' class is found, and
  // CMD_TEXT_NOT_FOUND if not.
  std::string command = base::StringPrintf(
      "document.querySelector('#%s')"
      "    .classList.contains('hidden')"
      " ? %d : %d;",
      element_id.c_str(), security_interstitials::CMD_TEXT_FOUND,
      security_interstitials::CMD_TEXT_NOT_FOUND);
  int result = content::EvalJs(frame, command).ExtractInt();
  EXPECT_EQ(expect_hidden ? security_interstitials::CMD_TEXT_FOUND
                          : security_interstitials::CMD_TEXT_NOT_FOUND,
            result);
}

}  // namespace

class SSLUITest : public SSLUITestBase {
 public:
  SSLUITest() : SSLUITestBase() {
    scoped_feature_list_.InitWithFeatures(
        /*enabled_features=*/{net::features::kVerifyQWACs},
        GetDisabledFeatures());
  }

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

 protected:
  void DontProceedThroughInterstitial(WebContents* tab) override {
    content::TestNavigationObserver nav_observer(tab, 1);
    SendInterstitialCommand(tab, security_interstitials::CMD_DONT_PROCEED);
    nav_observer.Wait();
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

class SSLUITestBlock : public SSLUITest {
 public:
  SSLUITestBlock() : SSLUITest() {}

  // Browser will not run insecure content.
  void SetUpCommandLine(base::CommandLine* command_line) override {
    // By overriding SSLUITest, we won't apply the flag that allows running
    // insecure content.
  }
};

class SSLUITestIgnoreCertErrors : public SSLUITest {
 public:
  SSLUITestIgnoreCertErrors() : SSLUITest() {}

  void SetUpCommandLine(base::CommandLine* command_line) override {
    SSLUITest::SetUpCommandLine(command_line);
    // Browser will ignore certificate errors.
    command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
  }
};

static std::string MakeCertSPKIFingerprint(net::X509Certificate* cert) {
  return base::Base64Encode(GetSPKIHash(cert->cert_buffer()));
}

class SSLUITestIgnoreCertErrorsBySPKIHTTPS : public SSLUITest {
 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    SSLUITest::SetUpCommandLine(command_line);
    std::string whitelist_flag = MakeCertSPKIFingerprint(
        https_server_mismatched_.GetCertificate().get());
    // Browser will ignore certificate errors for chains matching one of the
    // public keys from the list.
    command_line->AppendSwitchASCII(
        network::switches::kIgnoreCertificateErrorsSPKIList, whitelist_flag);
  }
};

class SSLUITestIgnoreCertErrorsBySPKIWSS : public SSLUITest {
 public:
  SSLUITestIgnoreCertErrorsBySPKIWSS() : SSLUITest() {}

  void SetUpCommandLine(base::CommandLine* command_line) override {
    SSLUITest::SetUpCommandLine(command_line);
    std::string whitelist_flag =
        MakeCertSPKIFingerprint(wss_server_expired_.GetCertificate().get());
    // Browser will ignore certificate errors for chains matching one of the
    // public keys from the list.
    command_line->AppendSwitchASCII(
        network::switches::kIgnoreCertificateErrorsSPKIList, whitelist_flag);
  }
};

class SSLUITestIgnoreLocalhostCertErrors : public SSLUITest {
 public:
  SSLUITestIgnoreLocalhostCertErrors() : SSLUITest() {}

  void SetUpCommandLine(base::CommandLine* command_line) override {
    SSLUITest::SetUpCommandLine(command_line);
    // Browser will ignore certificate errors on localhost.
    command_line->AppendSwitch(switches::kAllowInsecureLocalhost);
  }
};

class SSLUITestHSTS : public SSLUITest {
 public:
  void SetUpOnMainThread() override {
    SSLUITest::SetUpOnMainThread();
    ssl_test_util::SetHSTSForHostName(browser()->GetProfile(),
                                      kHstsTestHostName);
  }
};

class SSLUITestReduceSubresourceNotifications : public SSLUITestBase {
 public:
  SSLUITestReduceSubresourceNotifications() {
    scoped_feature_list_.InitWithFeatures(
        /*enabled_features=*/{features::kReduceSubresourceResponseStartedIPC},
        GetDisabledFeatures());
  }

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

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Visits a regular page over http.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTP) {
  ASSERT_TRUE(embedded_test_server()->Start());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/ssl/google.html")));

  ssl_test_util::CheckUnauthenticatedState(
      browser()->tab_strip_model()->GetActiveWebContents(), AuthState::NONE);
}

// Visits a page over http which includes broken https resources (status should
// be OK).
// TODO(jcampan): test that bad HTTPS content is blocked (otherwise we'll give
//                the secure cookies away!).
IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTPWithBrokenHTTPSResource) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_expired_.Start());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_with_unsafe_contents.html",
      https_server_expired_.host_port_pair());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(replacement_path)));

  ssl_test_util::CheckUnauthenticatedState(
      browser()->tab_strip_model()->GetActiveWebContents(), AuthState::NONE);
}

// Tests that after loading mixed content and then making a same-document
// navigation, the mixed content security indicator remains. See
// https://crbug.com/40094866.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestMixedContentWithSamePageNavigation) {
  ASSERT_TRUE(https_server_.Start());
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  // Navigate to a secure page (no mixed content).
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/ssl/google.html")));
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);

  // Add a mixed form after a same-document navigation.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/ssl/google.html#foo")));
  ssl_test_util::SecurityStateWebContentsObserver observer(tab);
  ASSERT_NE(false, content::EvalJs(tab,
                                   "var f = document.createElement('form');"
                                   "f.action = 'http://foo.test';"
                                   "document.body.appendChild(f)"));
  observer.WaitForDidChangeVisibleSecurityState();

  // Since mixed forms trigger their own warning, we display the lock icon on
  // otherwise secure sites with an insecure form.
  security_state::SecurityLevel expected_level = security_state::SECURE;

  ssl_test_util::CheckSecurityState(
      tab, CertError::NONE, expected_level,
      AuthState::DISPLAYED_FORM_WITH_INSECURE_ACTION);

  // Go back (which should also be a same-document navigation) and test that the
  // security indicator is still downgraded because of the mixed form.
  chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
  EXPECT_TRUE(content::WaitForLoadStop(tab));
  ssl_test_util::CheckSecurityState(
      tab, CertError::NONE, expected_level,
      AuthState::DISPLAYED_FORM_WITH_INSECURE_ACTION);
}

IN_PROC_BROWSER_TEST_F(SSLUITest, TestBrokenHTTPSWithInsecureContent) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_expired_.Start());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_displays_insecure_content.html",
      embedded_test_server()->host_port_pair());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL(replacement_path)));

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);

  ProceedThroughInterstitial(tab);

  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID,
      AuthState::DISPLAYED_INSECURE_CONTENT);
}

// Tests that the NavigationEntry gets marked as active mixed content,
// even if there is a certificate error. Regression test for
// https://crbug.com/41243626.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestBrokenHTTPSWithActiveInsecureContent) {
  ASSERT_TRUE(https_server_expired_.Start());

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(tab);

  // Navigate to a page with a certificate error and click through the
  // interstitial.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/title1.html")));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
  ProceedThroughInterstitial(tab);

  // Load an insecure (http://) script. When it is loaded, check that the page
  // is marked as having run insecure content.
  SSLContentStatusObserver observer(tab,
                                    content::SSLStatus::RAN_INSECURE_CONTENT);
  ASSERT_NE(false,
            content::EvalJs(tab,
                            "var s = document.createElement('script');"
                            "s.src = 'http://does-not-exist.test/foo.js';"
                            "document.body.appendChild(s)"));
  observer.WaitForSSLContentStatusFlag();

  // Now check that the page is marked as both having a cert error and having
  // run insecure content.
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::RAN_INSECURE_CONTENT);
}

// Tests that when a subframe commits a main resource with a certificate error,
// the navigation entry is marked as insecure.
IN_PROC_BROWSER_TEST_F(SSLUITestIgnoreCertErrors, SubframeHasCertError) {
  ASSERT_TRUE(https_server_mismatched_.Start());
  // Load a page with a data: favicon URL to suppress a favicon request. A
  // favicon request can cause the navigation entry to get marked as having run
  // insecure content (favicons are treated as active content), which would
  // interfere with the test expectation below.
  GURL main_frame_url =
      https_server_mismatched_.GetURL("a.test", "/data_favicon.html");
  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(tab);
  EXPECT_FALSE(
      tab->GetController().GetLastCommittedEntry()->GetSSL().content_status &
      content::SSLStatus::RAN_CONTENT_WITH_CERT_ERRORS);

  GURL subframe_url =
      https_server_mismatched_.GetURL("b.test", "/data_favicon.html");
  content::TestNavigationObserver iframe_observer(tab);
  EXPECT_TRUE(content::ExecJs(
      tab, content::JsReplace("var i = document.createElement('iframe');"
                              "i.src = $1;"
                              "document.body.appendChild(i);",
                              subframe_url.spec())));
  iframe_observer.Wait();
  EXPECT_TRUE(
      tab->GetController().GetLastCommittedEntry()->GetSSL().content_status &
      content::SSLStatus::RAN_CONTENT_WITH_CERT_ERRORS);
}

namespace {

// A WebContentsObserver that allows the user to wait for a same-document
// navigation. Tests using this observer will fail if a non-same-document
// navigation completes after calling WaitForSameDocumentNavigation.
class SameDocumentNavigationObserver : public content::WebContentsObserver {
 public:
  explicit SameDocumentNavigationObserver(WebContents* web_contents)
      : WebContentsObserver(web_contents) {}
  ~SameDocumentNavigationObserver() override = default;

  void WaitForSameDocumentNavigation() { run_loop_.Run(); }

  // WebContentsObserver:
  void DidFinishNavigation(
      content::NavigationHandle* navigation_handle) override {
    ASSERT_TRUE(navigation_handle->IsSameDocument());
    run_loop_.Quit();
  }

 private:
  base::RunLoop run_loop_;
};

}  // namespace

// Tests that the mixed content flags are reset when going back to an existing
// navigation entry that had mixed content. Regression test for
// https://crbug.com/41336817.
IN_PROC_BROWSER_TEST_F(SSLUITest, GoBackToMixedContent) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  // Navigate to a URL and dynamically load mixed content.
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/ssl/google.html")));
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
  ssl_test_util::SecurityStateWebContentsObserver observer(tab);
  ASSERT_TRUE(content::ExecJs(tab,
                              "var i = document.createElement('img');"
                              "i.src = 'http://example.test';"
                              "document.body.appendChild(i);"));
  observer.WaitForDidChangeVisibleSecurityState();
  ssl_test_util::CheckSecurityState(tab, CertError::NONE,
                                    security_state::WARNING,
                                    AuthState::DISPLAYED_INSECURE_CONTENT);

  // Now navigate somewhere else, and then back to the page that dynamically
  // loaded mixed content.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/ssl/google.html")));
  ssl_test_util::CheckUnauthenticatedState(
      browser()->tab_strip_model()->GetActiveWebContents(), AuthState::NONE);
  chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
  EXPECT_TRUE(content::WaitForLoadStop(tab));
  // After going back, the mixed content indicator should no longer be present.
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}

// Tests that the mixed content flags are not reset for an in-page navigation.
IN_PROC_BROWSER_TEST_F(SSLUITest, MixedContentWithSameDocumentNavigation) {
  ASSERT_TRUE(https_server_.Start());

  // Navigate to a URL and dynamically load mixed content.
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/ssl/google.html")));
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
  ssl_test_util::SecurityStateWebContentsObserver security_state_observer(tab);
  ASSERT_TRUE(content::ExecJs(tab,
                              "var i = document.createElement('img');"
                              "i.src = 'http://example.test';"
                              "document.body.appendChild(i);"));
  security_state_observer.WaitForDidChangeVisibleSecurityState();
  ssl_test_util::CheckSecurityState(tab, CertError::NONE,
                                    security_state::WARNING,
                                    AuthState::DISPLAYED_INSECURE_CONTENT);

  // Initiate a same-document navigation and check that the page is still
  // marked as having displayed mixed content.
  SameDocumentNavigationObserver navigation_observer(tab);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/ssl/google.html#foo")));
  navigation_observer.WaitForSameDocumentNavigation();
  ssl_test_util::CheckSecurityState(tab, CertError::NONE,
                                    security_state::WARNING,
                                    AuthState::DISPLAYED_INSECURE_CONTENT);
}

// Tests that the WebContents's flag for displaying content with cert
// errors get cleared upon navigation.
IN_PROC_BROWSER_TEST_F(SSLUITest,
                       DisplayedContentWithCertErrorsClearedOnNavigation) {
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(https_server_expired_.Start());

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(tab);

  // Navigate to a page with a certificate error and click through the
  // interstitial.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/title1.html")));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
  ProceedThroughInterstitial(tab);

  // Add a subresource with a certificate error and check that it's recorded
  // correctly. We wait specifically for the DidDisplayContentWithCertErrors
  // event (rather than a DidChangeVisibleSecurityState event) because the
  // page's favicon is loaded as active content and the notification about that
  // can interfere with the visible security state change that we're observing
  // here.
  SSLContentStatusObserver observer(
      tab, content::SSLStatus::DISPLAYED_CONTENT_WITH_CERT_ERRORS);
  ASSERT_NE(false, content::EvalJs(tab,
                                   "var i = document.createElement('img');"
                                   "i.src = 'ssl/google_files/logo.gif';"
                                   "document.body.appendChild(i)"));
  observer.WaitForSSLContentStatusFlag();

  // Navigate away to a different page, and check that the flag gets cleared.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/ssl/google.html")));
  content::NavigationEntry* entry = tab->GetController().GetVisibleEntry();
  ASSERT_TRUE(entry);
  EXPECT_FALSE(entry->GetSSL().content_status &
               content::SSLStatus::DISPLAYED_CONTENT_WITH_CERT_ERRORS);
}

IN_PROC_BROWSER_TEST_F(SSLUITest, TestBrokenHTTPSMetricsReporting_Proceed) {
  ASSERT_TRUE(https_server_expired_.Start());
  base::HistogramTester histograms;
  const std::string decision_histogram =
      "interstitial.ssl_overridable.decision";
  const std::string interaction_histogram =
      "interstitial.ssl_overridable.interaction";

  // Histograms should start off empty.
  histograms.ExpectTotalCount(decision_histogram, 0);
  histograms.ExpectTotalCount(interaction_histogram, 0);

  // After navigating to the page, the totals should be set.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/title1.html")));
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(
      browser()->tab_strip_model()->GetActiveWebContents()));
  histograms.ExpectTotalCount(decision_histogram, 1);
  histograms.ExpectBucketCount(decision_histogram,
                               security_interstitials::MetricsHelper::SHOW, 1);
  histograms.ExpectTotalCount(interaction_histogram, 2);
  histograms.ExpectBucketCount(
      interaction_histogram,
      security_interstitials::MetricsHelper::TOTAL_VISITS, 1);
  histograms.ExpectBucketCount(
      interaction_histogram,
      security_interstitials::MetricsHelper::SHOW_ENHANCED_PROTECTION, 1);

  // Decision should be recorded.
  SendInterstitialCommand(browser()->tab_strip_model()->GetActiveWebContents(),
                          security_interstitials::CMD_PROCEED);
  histograms.ExpectTotalCount(decision_histogram, 2);
  histograms.ExpectBucketCount(
      decision_histogram, security_interstitials::MetricsHelper::PROCEED, 1);
  histograms.ExpectTotalCount(interaction_histogram, 2);
  histograms.ExpectBucketCount(
      interaction_histogram,
      security_interstitials::MetricsHelper::TOTAL_VISITS, 1);
}

IN_PROC_BROWSER_TEST_F(SSLUITest, TestBrokenHTTPSMetricsReporting_DontProceed) {
  ASSERT_TRUE(https_server_expired_.Start());
  base::HistogramTester histograms;
  const std::string decision_histogram =
      "interstitial.ssl_overridable.decision";
  const std::string interaction_histogram =
      "interstitial.ssl_overridable.interaction";

  // Histograms should start off empty.
  histograms.ExpectTotalCount(decision_histogram, 0);
  histograms.ExpectTotalCount(interaction_histogram, 0);

  // After navigating to the page, the totals should be set.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/title1.html")));
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(
      browser()->tab_strip_model()->GetActiveWebContents()));
  histograms.ExpectTotalCount(decision_histogram, 1);
  histograms.ExpectBucketCount(decision_histogram,
                               security_interstitials::MetricsHelper::SHOW, 1);
  histograms.ExpectTotalCount(interaction_histogram, 2);
  histograms.ExpectBucketCount(
      interaction_histogram,
      security_interstitials::MetricsHelper::TOTAL_VISITS, 1);
  histograms.ExpectBucketCount(
      interaction_histogram,
      security_interstitials::MetricsHelper::SHOW_ENHANCED_PROTECTION, 1);

  // Decision should be recorded.
  SendInterstitialCommand(browser()->tab_strip_model()->GetActiveWebContents(),
                          security_interstitials::CMD_DONT_PROCEED);
  histograms.ExpectTotalCount(decision_histogram, 2);
  histograms.ExpectBucketCount(
      decision_histogram, security_interstitials::MetricsHelper::DONT_PROCEED,
      1);
  histograms.ExpectTotalCount(interaction_histogram, 2);
  histograms.ExpectBucketCount(
      interaction_histogram,
      security_interstitials::MetricsHelper::TOTAL_VISITS, 1);
}

// Visits a page over OK https:
IN_PROC_BROWSER_TEST_F(SSLUITest, TestOKHTTPS) {
  ASSERT_TRUE(https_server_.Start());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/ssl/google.html")));

  ssl_test_util::CheckAuthenticatedState(
      browser()->tab_strip_model()->GetActiveWebContents(), AuthState::NONE);
}

// Visits a page with https error and proceed:
IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTPSExpiredCertAndProceed) {
  ASSERT_TRUE(https_server_expired_.Start());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);

  ProceedThroughInterstitial(tab);
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::NONE);
  EXPECT_EQ(https_server_expired_.GetURL("/ssl/google.html"),
            tab->GetVisibleURL());
}

// Visits a page with https error and checks favicon is not displayed:
IN_PROC_BROWSER_TEST_F(SSLUITest, TestNoFaviconOnInterstitial) {
  ASSERT_TRUE(https_server_expired_.Start());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));

  tabs::TabInterface* const tab_interface =
      browser()->tab_strip_model()->GetActiveTab();
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(
      tab_interface->GetContents()));
  EXPECT_FALSE(TabUIHelper::From(tab_interface)->ShouldDisplayFavicon());
}

// Visits a page with https error and don't proceed (and ensure we can still
// navigate at that point):
IN_PROC_BROWSER_TEST_F(SSLUITest, TestInterstitialCrossSiteNavigation) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(https_server_mismatched_.Start());

  // First navigate to an OK page.
  GURL initial_url = https_server_.GetURL("/ssl/google.html");
  ASSERT_EQ("127.0.0.1", initial_url.GetHost());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  // Navigate from 127.0.0.1 to localhost so it triggers a
  // cross-site navigation to make sure http://crbug.com/41235852 is gone.
  GURL cross_site_url = https_server_mismatched_.GetURL("/ssl/google.html");
  ASSERT_EQ("localhost", cross_site_url.GetHost());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), cross_site_url));
  // An SSL interstitial should be showing.
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_COMMON_NAME_INVALID,
      AuthState::SHOWING_INTERSTITIAL);
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));

  // Simulate user clicking "Take me back".
  DontProceedThroughInterstitial(tab);

  // We should be back to the original good page.
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);

  // Navigate to a new page to make sure bug 5800 is fixed.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/ssl/google.html")));
  ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);
}

// Test that localhost pages don't show an interstitial.
IN_PROC_BROWSER_TEST_F(SSLUITestIgnoreLocalhostCertErrors,
                       TestNoInterstitialOnLocalhost) {
  ASSERT_TRUE(https_server_.Start());

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  // Navigate to a localhost page.
  GURL url = https_server_.GetURL("/ssl/page_with_subresource.html");
  GURL::Replacements replacements;
  std::string new_host("localhost");
  replacements.SetHostStr(new_host);
  url = url.ReplaceComponents(replacements);

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

  // We should see no interstitial, but we should have an error
  // (red-crossed-out-https) in the URL bar.
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_COMMON_NAME_INVALID, AuthState::NONE);

  // We should see that the script tag in the page loaded and ran (and
  // wasn't blocked by the certificate error).
  std::u16string title;
  std::u16string expected_title = u"This script has loaded";
  ui_test_utils::GetCurrentTabTitle(browser(), &title);
  EXPECT_EQ(title, expected_title);
}

IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTPSErrorCausedByClockUsingBuildTime) {
  ASSERT_TRUE(https_server_expired_.Start());

  // Set up the build and current clock times to be more than a year apart.
  std::unique_ptr<base::SimpleTestClock> mock_clock(
      new base::SimpleTestClock());
  mock_clock->SetNow(base::Time::NowFromSystemTime());
  mock_clock->Advance(base::Days(367));
  SSLErrorHandler::SetClockForTesting(mock_clock.get());
  ssl_errors::SetBuildTimeForTesting(base::Time::NowFromSystemTime());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/title1.html")));
  WebContents* clock_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(
      chrome_browser_interstitials::IsShowingBadClockInterstitial(clock_tab));
  ssl_test_util::CheckSecurityState(clock_tab, net::CERT_STATUS_DATE_INVALID,
                                    security_state::DANGEROUS,
                                    AuthState::SHOWING_INTERSTITIAL);
}

IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTPSErrorCausedByClockUsingNetwork) {
  ASSERT_TRUE(https_server_expired_.Start());

  // Set network forward ten minutes, which is sufficient to trigger
  // the interstitial.
  g_browser_process->network_time_tracker()->UpdateNetworkTime(
      base::Time::Now() + base::Minutes(10),
      base::Milliseconds(1),   /* resolution */
      base::Milliseconds(500), /* latency */
      base::TimeTicks::Now() /* posting time of this update */);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/title1.html")));
  WebContents* clock_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(
      chrome_browser_interstitials::IsShowingBadClockInterstitial(clock_tab));
  ssl_test_util::CheckSecurityState(clock_tab, net::CERT_STATUS_DATE_INVALID,
                                    security_state::DANGEROUS,
                                    AuthState::SHOWING_INTERSTITIAL);
}

// Visits a page with https error and then goes back using Browser::GoBack.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTPSExpiredCertAndGoBackViaButton) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_expired_.Start());

  // First navigate to an HTTP page.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/ssl/google.html")));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  // Now go to a bad HTTPS page that shows an interstitial.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);

  // Simulate user clicking on back button (crbug.com/40373975).
  chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
  EXPECT_TRUE(content::WaitForLoadStop(tab));

  // We should be back at the original good page.
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(tab));
  ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);
}

// Visits a page with https error and then goes back using GoToOffset.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTPSExpiredCertAndGoBackViaMenu) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_expired_.Start());

  // First navigate to an HTTP page.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/ssl/google.html")));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  // Now go to a bad HTTPS page that shows an interstitial.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);

  // Simulate user clicking and holding on back button (crbug.com/41106657).
  // With committed interstitials enabled, this triggers a navigation.
  content::TestNavigationObserver nav_observer(tab);
  tab->GetController().GoToOffset(-1);
  nav_observer.Wait();

  // We should be back at the original good page.
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(tab));
  ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);
}

// Visits a page with https error and then goes back using the DONT_PROCEED
// interstitial command.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTPSExpiredCertGoBackUsingCommand) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_expired_.Start());

  // First navigate to an HTTP page.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/ssl/google.html")));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  // Now go to a bad HTTPS page that shows an interstitial.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);

  content::LoadStopObserver observer(tab);
  SendInterstitialCommand(tab, security_interstitials::CMD_DONT_PROCEED);
  observer.Wait();

  // We should be back at the original good page.
  ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);
}

// Visits a page that uses a SHA-1 leaf certificate, which should be rejected.
IN_PROC_BROWSER_TEST_F(SSLUITest, SHA1IsNotAllowed) {
  ASSERT_TRUE(https_server_sha1_.Start());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_sha1_.GetURL("/ssl/google.html")));

  ssl_test_util::CheckAuthenticationBrokenState(
      browser()->tab_strip_model()->GetActiveWebContents(),
      net::CERT_STATUS_INVALID, AuthState::SHOWING_INTERSTITIAL);
}

// Visit a HTTP page which request WSS connection to a server providing invalid
// certificate. Close the page while WSS connection waits for SSLManager's
// response from UI thread.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestWSSInvalidCertAndClose) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(wss_server_expired_.Start());

  // Setup page title observer.
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::TitleWatcher watcher(tab, u"PASS");
  watcher.AlsoWaitForTitle(u"FAIL");

  // Create GURLs to test pages.
  std::string wss_close_url_path = base::StringPrintf(
      "%s?%d",
      embedded_test_server()->GetURL("/ssl/wss_close.html").spec().c_str(),
      wss_server_expired_.port());
  GURL wss_close_url(wss_close_url_path);
  std::string wss_loop_url_path = base::StringPrintf(
      "%s?%d",
      embedded_test_server()->GetURL("/ssl/wss_close_loop.html").spec().c_str(),
      wss_server_expired_.port());
  GURL wss_loop_url(wss_loop_url_path);

  // Create tabs and visit pages which keep on creating wss connections.
  std::array<WebContents*, 16> tabs;
  for (int i = 0; i < 16; ++i) {
    tabs[i] = chrome::AddSelectedTabWithURL(browser(), wss_loop_url,
                                            ui::PAGE_TRANSITION_LINK);
  }
  chrome::SelectNextTab(browser());

  // Visit a page which waits for one TLS handshake failure.
  // The title will be changed to 'PASS'.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), wss_close_url));
  const std::u16string result = watcher.WaitAndGetTitle();
  EXPECT_TRUE(base::EqualsCaseInsensitiveASCII(result, "pass"));

  // Close tabs which contains the test page.
  for (int i = 0; i < 16; ++i) {
    chrome::CloseWebContents(browser(), tabs[i], false);
  }
  chrome::CloseWebContents(browser(), tab, false);
}

// Visit a HTTPS page and proceeds despite an invalid certificate. The page
// requests WSS connection to the same origin host to check if WSS connection
// share certificates policy with HTTPS correcly.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestWSSInvalidCert) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(wss_server_expired_.Start());

  // Setup page title observer.
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::TitleWatcher watcher(tab, u"PASS");
  watcher.AlsoWaitForTitle(u"FAIL");

  // Visit bad HTTPS page.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), wss_server_expired_.GetURL("/websocket/connect_check.html")));
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);

  // Proceed anyway.
  ProceedThroughInterstitial(tab);

  // Test page run a WebSocket wss connection test. The result will be shown
  // as page title.
  const std::u16string result = watcher.WaitAndGetTitle();
  EXPECT_TRUE(base::EqualsCaseInsensitiveASCII(result, "pass"));
}

// Data URLs should always be marked as non-secure.
IN_PROC_BROWSER_TEST_F(SSLUITest, MarkDataAsNonSecure) {
  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(contents);

  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL("data:text/plain,hello")));
  EXPECT_EQ(security_state::WARNING,
            chrome_security_state::GetSecurityLevel(contents));
}

#if BUILDFLAG(USE_NSS_CERTS)
class SSLUITestWithClientCert : public SSLUITestBase {
 public:
  SSLUITestWithClientCert() : cert_db_(nullptr) {}

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

    base::RunLoop loop;
    NssServiceFactory::GetForContext(browser()->GetProfile())
        ->UnsafelyGetNSSCertDatabaseForTesting(
            base::BindOnce(&SSLUITestWithClientCert::DidGetCertDatabase,
                           base::Unretained(this), &loop));
    loop.Run();
  }

 protected:
  void DidGetCertDatabase(base::RunLoop* loop, net::NSSCertDatabase* cert_db) {
    cert_db_ = cert_db;
    loop->Quit();
  }

  raw_ptr<net::NSSCertDatabase> cert_db_;
};

// SSL client certificate tests are only enabled when using NSS for private key
// storage, as only NSS can avoid modifying global machine state when testing.
// See http://crbug.com/41189152

// Visit a HTTPS page which requires client cert authentication. The client
// cert will be selected automatically, then a test which uses WebSocket runs.
//
// TODO(crbug.com/40811167): disabled because of race in when certs
// are incorporated.
IN_PROC_BROWSER_TEST_F(SSLUITestWithClientCert, DISABLED_TestWSSClientCert) {
  // Import a client cert for test.
  crypto::ScopedPK11Slot public_slot = cert_db_->GetPublicSlot();
  std::string pkcs12_data;
  base::FilePath cert_path = net::GetTestCertsDirectory().Append(
      FILE_PATH_LITERAL("websocket_client_cert.p12"));
  {
    base::ScopedAllowBlockingForTesting allow_blocking;
    EXPECT_TRUE(base::ReadFileToString(cert_path, &pkcs12_data));
  }
  EXPECT_EQ(net::OK,
            cert_db_->ImportFromPKCS12(public_slot.get(), pkcs12_data,
                                       std::u16string(), true, nullptr));

  // Start WebSocket test server with TLS and client cert authentication.
  net::EmbeddedTestServer wss_server(net::EmbeddedTestServer::Type::TYPE_HTTP);
  {
    net::SSLServerConfig ssl_config;
    ssl_config.client_cert_type =
        net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT;
    wss_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK, ssl_config);
  }
  net::test_server::InstallDefaultWebSocketHandlers(&wss_server);
  wss_server.AddDefaultHandlers(GetChromeTestDataDir());
  ASSERT_TRUE(wss_server.Start());
  GURL url = wss_server.GetURL("/websocket/connect_check.html");

  // Setup page title observer.
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::TitleWatcher watcher(tab, u"PASS");
  watcher.AlsoWaitForTitle(u"FAIL");

  // Add an entry into AutoSelectCertificateForUrls policy for automatic client
  // cert selection.
  Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
  DCHECK(profile);
  base::DictValue filter;
  filter.SetByDottedPath("ISSUER.CN", "Test Root CA");
  base::ListValue filters;
  filters.Append(std::move(filter));
  base::DictValue setting;
  setting.Set("filters", std::move(filters));
  HostContentSettingsMapFactory::GetForProfile(profile)
      ->SetWebsiteSettingDefaultScope(
          url, GURL(), ContentSettingsType::AUTO_SELECT_CERTIFICATE,
          base::Value(std::move(setting)));

  // Visit a HTTPS page which requires client certs.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);

  // Test page runs a WebSocket wss connection test. The result will be shown
  // as page title.
  const std::u16string result = watcher.WaitAndGetTitle();
  EXPECT_TRUE(base::EqualsCaseInsensitiveASCII(result, "pass"));
}
#endif  // BUILDFLAG(USE_NSS_CERTS)

// A stub ClientCertStore that returns a FakeClientCertIdentity.
class ClientCertStoreStub : public net::ClientCertStore {
 public:
  explicit ClientCertStoreStub(net::ClientCertIdentityList list)
      : list_(std::move(list)) {}

  ~ClientCertStoreStub() override = default;

  // net::ClientCertStore:
  void GetClientCerts(
      scoped_refptr<const net::SSLCertRequestInfo> cert_request_info,
      ClientCertListCallback callback) override {
    std::move(callback).Run(std::move(list_));
  }

 private:
  net::ClientCertIdentityList list_;
};

std::unique_ptr<net::ClientCertStore> CreateCertStore() {
  base::FilePath certs_dir = net::GetTestCertsDirectory();

  net::ClientCertIdentityList cert_identity_list;

  {
    base::ScopedAllowBlockingForTesting allow_blocking;

    std::unique_ptr<net::FakeClientCertIdentity> cert_identity =
        net::FakeClientCertIdentity::CreateFromCertAndKeyFiles(
            certs_dir, "client_1.pem", "client_1.pk8");
    EXPECT_TRUE(cert_identity.get());
    if (cert_identity) {
      cert_identity_list.push_back(std::move(cert_identity));
    }
  }

  return std::unique_ptr<net::ClientCertStore>(
      new ClientCertStoreStub(std::move(cert_identity_list)));
}

std::unique_ptr<net::ClientCertStore> CreateFailSigningCertStore() {
  base::FilePath certs_dir = net::GetTestCertsDirectory();

  net::ClientCertIdentityList cert_identity_list;

  {
    base::ScopedAllowBlockingForTesting allow_blocking;

    std::unique_ptr<net::FakeClientCertIdentity> cert_identity =
        net::FakeClientCertIdentity::CreateFromCertAndFailSigning(
            certs_dir, "client_1.pem");
    EXPECT_TRUE(cert_identity.get());
    if (cert_identity) {
      cert_identity_list.push_back(std::move(cert_identity));
    }
  }

  return std::unique_ptr<net::ClientCertStore>(
      new ClientCertStoreStub(std::move(cert_identity_list)));
}

std::unique_ptr<net::ClientCertStore> CreateEmptyCertStore() {
  return std::unique_ptr<net::ClientCertStore>(new ClientCertStoreStub({}));
}

IN_PROC_BROWSER_TEST_F(SSLUITest, TestBrowserUseClientCertStore) {
  // Make the browser use the ClientCertStoreStub instead of the regular one.
  ProfileNetworkContextServiceFactory::GetForContext(browser()->GetProfile())
      ->set_client_cert_store_factory_for_testing(
          base::BindRepeating(&CreateCertStore));

  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  net::SSLServerConfig ssl_config;
  ssl_config.client_cert_type =
      net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT;
  https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK, ssl_config);
  https_server.ServeFilesFromSourceDirectory("chrome/test/data");
  ASSERT_TRUE(https_server.Start());
  GURL https_url =
      https_server.GetURL("/ssl/browser_use_client_cert_store.html");

  // Add an entry into AutoSelectCertificateForUrls policy for automatic client
  // cert selection.
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
  DCHECK(profile);
  // This reproduces the AutoSelectCertificateForUrls policy payload: an array
  // of JSON dictionaries, each containing optional filters. An empty filter
  // dictionary means “allow any matching certificate”, which is enough for the
  // test to auto-select the single certificate offered by the stub store.
  base::ListValue filters;
  filters.Append(base::DictValue());
  base::DictValue setting;
  setting.Set("filters", std::move(filters));
  HostContentSettingsMapFactory::GetForProfile(profile)
      ->SetWebsiteSettingDefaultScope(
          https_url, GURL(), ContentSettingsType::AUTO_SELECT_CERTIFICATE,
          base::Value(std::move(setting)));

  // Visit a HTTPS page which requires client certs.
  ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(),
                                                            https_url, 1);
  EXPECT_EQ("pass", tab->GetLastCommittedURL().GetRef());
}

// Tests that dynamically updating AutoSelectCertificateForUrls content setting
// triggers the network service to pick up the change and allows automatic
// client cert selection without requiring a browser restart.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestBrowserUseClientCertStoreDynamicUpdate) {
  // 1. Setup: Full Cert Store.
  ProfileNetworkContextServiceFactory::GetForContext(browser()->GetProfile())
      ->set_client_cert_store_factory_for_testing(
          base::BindRepeating(&CreateCertStore));

  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  net::SSLServerConfig ssl_config;
  ssl_config.client_cert_type =
      net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT;
  https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK, ssl_config);
  https_server.ServeFilesFromSourceDirectory("chrome/test/data");
  ASSERT_TRUE(https_server.Start());
  GURL https_url =
      https_server.GetURL("/ssl/browser_use_client_cert_store.html");

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
  DCHECK(profile);

  // 2. Initial Navigation (No Policy) -> Fail.
  // Install a hook to auto-cancel the certificate selector prompt.
  SetShowSSLClientCertificateSelectorHookForTest(base::BindRepeating(
      [](content::WebContents* contents,
         net::SSLCertRequestInfo* cert_request_info,
         net::ClientCertIdentityList client_certs,
         std::unique_ptr<content::ClientCertificateDelegate> delegate) {
        delegate->ContinueWithCertificate(nullptr, nullptr);
        return base::OnceClosure();
      }));

  ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(),
                                                            https_url, 1);
  // Expect failure (did not reach "pass").
  EXPECT_NE("pass", tab->GetLastCommittedURL().GetRef());

  // 3. Set AutoSelect Policy.
  base::ListValue filters;
  filters.Append(base::DictValue());
  base::DictValue setting;
  setting.Set("filters", std::move(filters));
  HostContentSettingsMapFactory::GetForProfile(profile)
      ->SetWebsiteSettingDefaultScope(
          https_url, GURL(), ContentSettingsType::AUTO_SELECT_CERTIFICATE,
          base::Value(std::move(setting)));

  // 4. Retry Navigation -> Expect Success.
  // The policy should cause auto-selection, bypassing the selector hook.
  ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(),
                                                            https_url, 1);
  EXPECT_EQ("pass", tab->GetLastCommittedURL().GetRef());

  // Cleanup hook.
  SetShowSSLClientCertificateSelectorHookForTest(
      ShowSSLClientCertificateSelectorTestingHook());
}

// Tests that requests from service workers can also use certificates
// auto-selected by policy.
// https://crbug.com/40894162.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestServiceWorkerRequestsUseClientCertStore) {
  // Make the browser use the ClientCertStoreStub instead of the regular one.
  ProfileNetworkContextServiceFactory::GetForContext(browser()->GetProfile())
      ->set_client_cert_store_factory_for_testing(
          base::BindRepeating(&CreateCertStore));

  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  {
    net::SSLServerConfig ssl_config;
    ssl_config.client_cert_type =
        net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT;
    https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES,
                              ssl_config);
  }
  https_server.ServeFilesFromSourceDirectory("chrome/test/data");

  ASSERT_TRUE(https_server.Start());

  // We set up a page that installs a service worker to perform a fetch to
  // a separate, cross-origin resource. We need this to be a cross-origin
  // because visiting the first site (to install the service worker) requires
  // a cert to be present, so subsequent fetches to that site will succeed
  // without a separate certificate prompt.

  // Note: These domain names need to match those in
  // //net/data/ssl/certificates/test_names.pem.
  GURL requestor_url =
      https_server.GetURL("a.test", "/ssl/service_worker_fetch/page.html");
  GURL target_url =
      https_server.GetURL("b.test", "/ssl/service_worker_fetch/target.txt");

  // Add an entry into AutoSelectCertificateForUrls policy for automatic client
  // cert selection for both the requestor and target URLs.
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
  DCHECK(profile);
  base::ListValue filters;
  filters.Append(base::DictValue());
  base::DictValue setting;
  setting.Set("filters", std::move(filters));
  HostContentSettingsMapFactory::GetForProfile(profile)
      ->SetWebsiteSettingDefaultScope(
          requestor_url, GURL(), ContentSettingsType::AUTO_SELECT_CERTIFICATE,
          base::Value(setting.Clone()));
  HostContentSettingsMapFactory::GetForProfile(profile)
      ->SetWebsiteSettingDefaultScope(
          target_url, GURL(), ContentSettingsType::AUTO_SELECT_CERTIFICATE,
          base::Value(std::move(setting)));

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

  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  // Check the navigation succeeded. The title check verifies we didn't e.g.
  // get a privacy error.
  EXPECT_EQ(requestor_url, web_contents->GetLastCommittedURL());
  EXPECT_EQ(u"My Title", web_contents->GetTitle());

  // Perform a fetch from a worker and validate that it succeeds.
  EXPECT_EQ(
      "text content\n",
      content::EvalJs(web_contents,
                      content::JsReplace("doFetchInWorker($1);", target_url)));
}

// Tests that if an extension service worker requests a resource where a
// client cert is optional (not required) and there are no client certs, the
// request will continue without a certificate (as opposed to abort).
#if BUILDFLAG(ENABLE_EXTENSIONS) && !BUILDFLAG(IS_ANDROID)
IN_PROC_BROWSER_TEST_F(
    SSLUITest,
    TestExtensionServiceWorkerCanContinueWithoutACertificate) {
  // TODO(https://crbug.com/40804030): Remove this when updated to use MV3.
  extensions::ScopedTestMV2Enabler mv2_enabler;

  // Make the browser use the ClientCertStoreStub instead of the regular one.
  ProfileNetworkContextServiceFactory::GetForContext(browser()->GetProfile())
      ->set_client_cert_store_factory_for_testing(
          base::BindRepeating(&CreateEmptyCertStore));

  // Set up an HTTPS server with optional client certs.
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  {
    net::SSLServerConfig ssl_config;
    ssl_config.client_cert_type =
        net::SSLServerConfig::ClientCertType::OPTIONAL_CLIENT_CERT;
    https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES,
                              ssl_config);
  }
  https_server.ServeFilesFromSourceDirectory("chrome/test/data");
  ASSERT_TRUE(https_server.Start());

  // Load a test extension that will try to fetch the cross-origin resource.
  static constexpr char kManifest[] =
      R"({
           "name": "Fetching Extension",
           "manifest_version": 2,
           "version": "0.1",
           "background": {"service_worker": "background.js"}
         })";
  static constexpr char kBackgroundJs[] =
      R"(async function doFetchAndReply(url) {
           try {
             let response = await fetch(url);
             result = await response.text();
           } catch (e) {
             result = `Fetch error: ${e.toString()}`;
           }

           chrome.test.sendScriptResult(result);
         })";

  extensions::TestExtensionDir test_dir;
  test_dir.WriteManifest(kManifest);
  test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundJs);

  Profile* const profile = browser()->GetProfile();
  extensions::ChromeTestExtensionLoader extension_loader(profile);
  scoped_refptr<const extensions::Extension> extension =
      extension_loader.LoadExtension(test_dir.UnpackedPath());
  ASSERT_TRUE(extension);

  // Path to the cross-origin resource to fetch.
  // Note: This domain name matches one in
  // //net/data/ssl/certificates/test_names.pem.
  GURL target_url =
      https_server.GetURL("b.test", "/ssl/service_worker_fetch/target.txt");

  // Try to fetch the resource from the extension. We have no client certs
  // (we're using an empty cert store), so no certificates will be selected.
  // Even so, the fetch should succeed. It continues without a certificate, and
  // the certificate is optional.
  base::Value fetch_result =
      extensions::BackgroundScriptExecutor::ExecuteScript(
          profile, extension->id(),
          base::StringPrintf("doFetchAndReply('%s');",
                             target_url.spec().c_str()),
          extensions::BackgroundScriptExecutor::ResultCapture::
              kSendScriptResult);

  EXPECT_EQ(fetch_result, "text content\n");
}
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

IN_PROC_BROWSER_TEST_F(SSLUITest, TestClientAuthSigningFails) {
  // Make the browser use the ClientCertStoreStub instead of the regular one.
  ProfileNetworkContextServiceFactory::GetForContext(browser()->GetProfile())
      ->set_client_cert_store_factory_for_testing(
          base::BindRepeating(&CreateFailSigningCertStore));

  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  net::SSLServerConfig ssl_config;
  ssl_config.client_cert_type =
      net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT;
  https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK, ssl_config);
  https_server.ServeFilesFromSourceDirectory("chrome/test/data");
  ASSERT_TRUE(https_server.Start());
  GURL https_url =
      https_server.GetURL("/ssl/browser_use_client_cert_store.html");

  // Add an entry into AutoSelectCertificateForUrls policy for automatic client
  // cert selection.
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
  DCHECK(profile);
  base::ListValue filters;
  filters.Append(base::DictValue());
  base::DictValue setting;
  setting.Set("filters", std::move(filters));
  HostContentSettingsMapFactory::GetForProfile(profile)
      ->SetWebsiteSettingDefaultScope(
          https_url, GURL(), ContentSettingsType::AUTO_SELECT_CERTIFICATE,
          base::Value(std::move(setting)));

  // Visit a HTTPS page which requires client certs.
  ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(),
                                                            https_url, 1);
  // Page should not load successfully.
  EXPECT_EQ("", tab->GetLastCommittedURL().GetRef());
}

IN_PROC_BROWSER_TEST_F(SSLUITest, TestClientAuthContinueWithoutCert) {
  // Make the browser use a ClientCertStoreStub that returns no certs.
  ProfileNetworkContextServiceFactory::GetForContext(browser()->GetProfile())
      ->set_client_cert_store_factory_for_testing(
          base::BindRepeating(&CreateEmptyCertStore));

  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  net::SSLServerConfig ssl_config;
  ssl_config.client_cert_type =
      net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT;
  https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK, ssl_config);
  https_server.ServeFilesFromSourceDirectory("chrome/test/data");
  ASSERT_TRUE(https_server.Start());
  GURL https_url =
      https_server.GetURL("/ssl/browser_use_client_cert_store.html");

  // Visit a HTTPS page which requires client certs.
  // The browser should automatically continue to the site without a client
  // cert, since the ClientCertStore returns no certs.
  ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(),
                                                            https_url, 1);
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  // Page should not load successfully.
  EXPECT_EQ("", tab->GetLastCommittedURL().GetRef());
}

IN_PROC_BROWSER_TEST_F(SSLUITest, TestCertDBChangedFlushesClientAuthCache) {
  // Make the browser use the ClientCertStoreStub instead of the regular one.
  ProfileNetworkContextServiceFactory::GetForContext(browser()->GetProfile())
      ->set_client_cert_store_factory_for_testing(
          base::BindRepeating(&CreateCertStore));

  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  net::SSLServerConfig ssl_config;
  ssl_config.client_cert_type =
      net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT;
  https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK, ssl_config);
  https_server.ServeFilesFromSourceDirectory("chrome/test/data");
  ASSERT_TRUE(https_server.Start());
  GURL https_url =
      https_server.GetURL("/ssl/browser_use_client_cert_store.html");

  // Add an entry into AutoSelectCertificateForUrls policy for automatic client
  // cert selection.
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
  DCHECK(profile);
  base::ListValue filters;
  filters.Append(base::DictValue());
  base::DictValue setting;
  setting.Set("filters", std::move(filters));
  HostContentSettingsMapFactory::GetForProfile(profile)
      ->SetWebsiteSettingDefaultScope(
          https_url, GURL(), ContentSettingsType::AUTO_SELECT_CERTIFICATE,
          base::Value(std::move(setting)));

  // Visit a HTTPS page which requires client certs.
  ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(),
                                                            https_url, 1);
  EXPECT_EQ("pass", tab->GetLastCommittedURL().GetRef());

  ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
      browser(), GURL("about:blank"), 1);
  EXPECT_EQ("", tab->GetLastCommittedURL().GetRef());

  // Now use a ClientCertStoreStub that always returns an empty list.
  ProfileNetworkContextServiceFactory::GetForContext(browser()->GetProfile())
      ->set_client_cert_store_factory_for_testing(
          base::BindRepeating(&CreateEmptyCertStore));

  // Visiting the page which requires client certs should still work (either
  // due to the socket still being open, or due to the SSL client auth cache).
  ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(),
                                                            https_url, 1);
  EXPECT_EQ("pass", tab->GetLastCommittedURL().GetRef());

  ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
      browser(), GURL("about:blank"), 1);
  EXPECT_EQ("", tab->GetLastCommittedURL().GetRef());

  // Send an OnClientCertStoreChanged notification.
  net::CertDatabase::GetInstance()->NotifyObserversClientCertStoreChanged();
  content::FlushNetworkServiceInstanceForTesting();

  // Visiting the page which requires client certs should fail, as the socket
  // pool has been flushed and SSL client auth cache has been cleared due to
  // the CertDBChanged observer.
  ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(),
                                                            https_url, 1);
  EXPECT_EQ("", tab->GetLastCommittedURL().GetRef());
}

// Open a page with a HTTPS error in a tab with no prior navigation (through a
// link with a blank target).  This is to test that the lack of navigation entry
// does not cause any problems (it was causing a crasher, see
// http://crbug.com/40981931).
IN_PROC_BROWSER_TEST_F(SSLUITest, TestHTTPSErrorWithNoNavEntry) {
  ASSERT_TRUE(https_server_expired_.Start());

  const GURL url = https_server_expired_.GetURL("/ssl/google.htm");
  WebContents* tab2 =
      chrome::AddSelectedTabWithURL(browser(), url, ui::PAGE_TRANSITION_TYPED);
  content::WaitForLoadStop(tab2);

  // Verify our assumption that there was no prior navigation.
  EXPECT_FALSE(chrome::CanGoBack(browser()));

  // We should have an interstitial page showing.
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab2));
}

IN_PROC_BROWSER_TEST_F(SSLUITest, TestBadHTTPSDownload) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_expired_.Start());
  GURL url_non_dangerous = embedded_test_server()->GetURL("/title1.html");
  GURL url_dangerous =
      https_server_expired_.GetURL("/downloads/dangerous/dangerous.exe");

  // Visit a non-dangerous page.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_non_dangerous));

  // Now, start a transition to dangerous download.
  {
    WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
    content::LoadStopObserver observer(tab);
    NavigateParams navigate_params(browser(), url_dangerous,
                                   ui::PAGE_TRANSITION_TYPED);
    Navigate(&navigate_params);
    observer.Wait();
  }

  // Proceed through the SSL interstitial. This doesn't use
  // ProceedThroughInterstitial() since no page load will commit.
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(tab);
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
  {
    // Wait for the download to complete after proceeding with the download.
    // This serves to verify the download was initiated, and to let the
    // test successfully shut down and cleanup. Exiting the browser with a
    // download still in-progress can lead to test failues.
    content::DownloadTestObserverTerminal dangerous_download_observer(
        browser()->GetProfile()->GetDownloadManager(), 1,
        content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_ACCEPT);
    SendInterstitialCommand(tab, security_interstitials::CMD_PROCEED);
    dangerous_download_observer.WaitForFinished();
  }

  // There should still be an interstitial at this point. Press the
  // back button on the browser. Note that this doesn't wait for a
  // NAV_ENTRY_COMMITTED notification because going back with an
  // active interstitial simply hides the interstitial.
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
  EXPECT_TRUE(chrome::CanGoBack(browser()));
  chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
}

//
// Insecure content
//

// Visits a page that displays insecure content.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestDisplaysInsecureContent) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_displays_insecure_content.html",
      embedded_test_server()->host_port_pair());

  // Load a page that displays insecure content.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(replacement_path)));

  ssl_test_util::CheckSecurityState(
      browser()->tab_strip_model()->GetActiveWebContents(), CertError::NONE,
      security_state::WARNING, AuthState::DISPLAYED_INSECURE_CONTENT);
}

// Visits a page that displays an insecure form.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestDisplaysInsecureForm) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_displays_insecure_form.html",
      embedded_test_server()->host_port_pair());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(replacement_path)));

  // Since mixed forms trigger their own warning, we display the lock icon on
  // otherwise secure sites with an insecure form.
  security_state::SecurityLevel expected_level = security_state::SECURE;

  ssl_test_util::CheckSecurityState(
      browser()->tab_strip_model()->GetActiveWebContents(), CertError::NONE,
      expected_level, AuthState::DISPLAYED_FORM_WITH_INSECURE_ACTION);
}

// Verifies that an SSL interstitial generates SafeBrowsing extension api
// events.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestExtensionEvents) {
  class ExtensionEventObserver : public extensions::EventRouter::TestObserver {
   public:
    ExtensionEventObserver() = default;

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

    ~ExtensionEventObserver() override = default;

    // extensions::EventRouter::TestObserver:
    void OnWillDispatchEvent(const extensions::Event& event) override {
      event_names_.push_back(event.event_name);
    }

    void OnDidDispatchEventToProcess(const extensions::Event& event,
                                     int process_id) override {}

    const std::vector<std::string>& event_names() const { return event_names_; }

   private:
    std::vector<std::string> event_names_;
  };

  ExtensionEventObserver observer;
  extensions::EventRouter::Get(browser()->GetProfile())
      ->AddObserverForTesting(&observer);

  ASSERT_TRUE(https_server_expired_.Start());

  GURL request_url = https_server_expired_.GetURL("/title1.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), request_url));

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(tab != nullptr);
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);

  // Verifies that security interstitial shown event is observed.
  EXPECT_TRUE(std::ranges::contains(
      observer.event_names(), extensions::api::safe_browsing_private::
                                  OnSecurityInterstitialShown::kEventName));

  ProceedThroughInterstitial(tab);

  // Verifies that security interstitial proceeded event is observed.
  EXPECT_TRUE(std::ranges::contains(
      observer.event_names(), extensions::api::safe_browsing_private::
                                  OnSecurityInterstitialProceeded::kEventName));

  extensions::EventRouter::Get(browser()->GetProfile())
      ->RemoveObserverForTesting(&observer);
}

// Visits a page that runs insecure content and tries to suppress the insecure
// content warnings by randomizing location.hash.
// Based on http://crbug.com/40092102
IN_PROC_BROWSER_TEST_F(SSLUITest, TestRunsInsecuredContentRandomizeHash) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/ssl/page_runs_insecure_content.html")));

  ssl_test_util::CheckAuthenticationBrokenState(
      browser()->tab_strip_model()->GetActiveWebContents(), CertError::NONE,
      AuthState::RAN_INSECURE_CONTENT);
}

// Visits an SSL page twice, once with subresources served over good SSL and
// once over bad SSL.
// - For the good SSL case, the iframe and images should be properly displayed.
// - For the bad SSL case, the iframe contents shouldn't be displayed and images
//   and scripts should be filtered out entirely.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestUnsafeContents) {
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(https_server_expired_.Start());
  // Enable popups without user gesture.
  HostContentSettingsMapFactory::GetForProfile(browser()->GetProfile())
      ->SetDefaultContentSetting(ContentSettingsType::POPUPS,
                                 CONTENT_SETTING_ALLOW);
  {
    // First visit the page with its iframe and subresources served over good
    // SSL. This is a sanity check to make sure these resources aren't already
    // broken in the good case.
    std::string replacement_path = GetFilePathWithHostAndPortReplacement(
        "/ssl/page_with_unsafe_contents.html", https_server_.host_port_pair());
    ASSERT_TRUE(ui_test_utils::NavigateToURL(
        browser(), https_server_.GetURL(replacement_path)));
    WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
    // The state is expected to be authenticated.
    ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
    // The iframe should be able to open a popup.
    EXPECT_EQ(2u,
              ProfileBrowserCollection::GetForProfile(browser()->GetProfile())
                  ->GetSize());
    // In order to check that the image was loaded, check its width.
    // The actual image (Google logo) is 276 pixels wide.
    EXPECT_EQ(276, content::EvalJs(tab, "ImageWidth();"));
    // Check that variable |foo| is set.
    EXPECT_EQ(true, content::EvalJs(tab, "IsFooSet();"));
  }
  {
    // Now visit the page with its iframe and subresources served over bad
    // SSL. Iframes, images, and scripts should all be blocked.
    std::string replacement_path = GetFilePathWithHostAndPortReplacement(
        "/ssl/page_with_unsafe_contents.html",
        https_server_expired_.host_port_pair());
    ASSERT_TRUE(ui_test_utils::NavigateToURL(
        browser(), https_server_.GetURL(replacement_path)));
    WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
    // When the bad content is filtered, the state is expected to be
    // authenticated.
    ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
    // The iframe attempts to open a popup window, but it shouldn't be able to.
    // Previous popup is still open.
    EXPECT_EQ(2u,
              ProfileBrowserCollection::GetForProfile(browser()->GetProfile())
                  ->GetSize());
    // The broken image width is zero.
    EXPECT_EQ(16, content::EvalJs(tab, "ImageWidth();"));
    // Check that variable |foo| is not set.
    EXPECT_EQ(false, content::EvalJs(tab, "IsFooSet();"));
  }
}

// Visits a page with insecure content loaded by JS (after the initial page
// load).
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
// flaky http://crbug.com/40375913
#define MAYBE_TestDisplaysInsecureContentLoadedFromJS \
  DISABLED_TestDisplaysInsecureContentLoadedFromJS
#else
#define MAYBE_TestDisplaysInsecureContentLoadedFromJS \
  TestDisplaysInsecureContentLoadedFromJS
#endif
IN_PROC_BROWSER_TEST_F(SSLUITest,
                       MAYBE_TestDisplaysInsecureContentLoadedFromJS) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  net::HostPortPair replacement_pair = embedded_test_server()->host_port_pair();
  replacement_pair.set_host("example.test");

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_with_dynamic_insecure_content.html", replacement_pair);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(replacement_path)));

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);

  // Load the insecure image.
  EXPECT_EQ(true, content::EvalJs(tab, "loadBadImage();"));

  // We should now have insecure content.
  ssl_test_util::CheckSecurityState(tab, CertError::NONE,
                                    security_state::WARNING,
                                    AuthState::DISPLAYED_INSECURE_CONTENT);
}

// Visits two pages from the same origin: one that displays insecure content and
// one that doesn't.  The test checks that we do not propagate the insecure
// content state from one to the other.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestDisplaysInsecureContentTwoTabs) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/ssl/blank_page.html")));

  WebContents* tab1 = browser()->tab_strip_model()->GetActiveWebContents();

  // This tab should be fine.
  ssl_test_util::CheckAuthenticatedState(tab1, AuthState::NONE);

  // Create a new tab.
  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_displays_insecure_content.html",
      embedded_test_server()->host_port_pair());

  GURL url = https_server_.GetURL(replacement_path);
  NavigateParams params(browser(), url, ui::PAGE_TRANSITION_TYPED);
  params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
  params.tabstrip_index = 0;
  params.source_contents = tab1;
  Navigate(&params);
  WebContents* tab2 = params.navigated_or_inserted_contents;
  EXPECT_TRUE(content::WaitForLoadStop(tab2));

  // The new tab has insecure content.
  ssl_test_util::CheckSecurityState(tab2, CertError::NONE,
                                    security_state::WARNING,
                                    AuthState::DISPLAYED_INSECURE_CONTENT);

  // The original tab should not be contaminated.
  ssl_test_util::CheckAuthenticatedState(tab1, AuthState::NONE);
}

// Visits two pages from the same origin: one that runs insecure content and one
// that doesn't.  The test checks that we propagate the insecure content state
// from one to the other.
// TODO(crbug.com/40709634): Flaky
IN_PROC_BROWSER_TEST_F(SSLUITest, DISABLED_TestRunsInsecureContentTwoTabs) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/ssl/blank_page.html")));

  WebContents* tab1 = browser()->tab_strip_model()->GetActiveWebContents();

  // This tab should be fine.
  ssl_test_util::CheckAuthenticatedState(tab1, AuthState::NONE);

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_runs_insecure_content.html",
      embedded_test_server()->host_port_pair());

  // Create a new tab in the same process.  Using a NEW_FOREGROUND_TAB
  // disposition won't usually stay in the same process, but this works
  // because we are using process-per-site in SetUpCommandLine.
  GURL url = https_server_.GetURL(replacement_path);
  NavigateParams params(browser(), url, ui::PAGE_TRANSITION_TYPED);
  params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
  params.source_contents = tab1;
  Navigate(&params);
  WebContents* tab2 = params.navigated_or_inserted_contents;
  EXPECT_TRUE(content::WaitForLoadStop(tab2));

  // Both tabs should have the same process.
  EXPECT_EQ(tab1->GetPrimaryMainFrame()->GetProcess(),
            tab2->GetPrimaryMainFrame()->GetProcess());

  // The new tab has insecure content.
  ssl_test_util::CheckAuthenticationBrokenState(
      tab2, CertError::NONE, AuthState::RAN_INSECURE_CONTENT);

  // Which means the origin for the first tab has also been contaminated with
  // insecure content.
  ssl_test_util::CheckAuthenticationBrokenState(
      tab1, CertError::NONE, AuthState::RAN_INSECURE_CONTENT);
}

// Visits a page with an image over http.  Visits another page over https
// referencing that same image over http (hoping it is coming from the webcore
// memory cache).
IN_PROC_BROWSER_TEST_F(SSLUITest, TestDisplaysCachedInsecureContent) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_displays_insecure_content.html",
      embedded_test_server()->host_port_pair());

  // Load original page over HTTP.
  const GURL url_http = embedded_test_server()->GetURL(replacement_path);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_http));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);

  // Load again but over SSL.  It should be marked as displaying insecure
  // content (even though the image comes from the WebCore memory cache).
  const GURL url_https = https_server_.GetURL(replacement_path);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_https));
  ssl_test_util::CheckSecurityState(tab, CertError::NONE,
                                    security_state::WARNING,
                                    AuthState::DISPLAYED_INSECURE_CONTENT);
}

// Visits a page with script over http.  Visits another page over https
// referencing that same script over http (hoping it is coming from the webcore
// memory cache).
IN_PROC_BROWSER_TEST_F(SSLUITest, TestRunsCachedInsecureContent) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_runs_insecure_content.html",
      embedded_test_server()->host_port_pair());

  // Load original page over HTTP.
  const GURL url_http = embedded_test_server()->GetURL(replacement_path);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_http));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);

  // Load again but over SSL.  It should be marked as displaying insecure
  // content (even though the image comes from the WebCore memory cache).
  const GURL url_https = https_server_.GetURL(replacement_path);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_https));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, CertError::NONE, AuthState::RAN_INSECURE_CONTENT);
}

// This test ensures the CN invalid status does not 'stick' to a certificate
// (see bug #1044942) and that it depends on the host-name.
// Test if disabled due to flakiness http://crbug.com/41104365 .
IN_PROC_BROWSER_TEST_F(SSLUITest, DISABLED_TestCNInvalidStickiness) {
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(https_server_mismatched_.Start());

  // First we hit the server with hostname, this generates an invalid policy
  // error.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_mismatched_.GetURL("/ssl/google.html")));

  // We get an interstitial page as a result.
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_COMMON_NAME_INVALID,
      AuthState::SHOWING_INTERSTITIAL);
  ProceedThroughInterstitial(tab);
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_COMMON_NAME_INVALID, AuthState::NONE);

  // Now we try again with the right host name this time.
  GURL url(https_server_.GetURL("/ssl/google.html"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

  // Security state should be OK.
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);

  // Now try again the broken one to make sure it is still broken.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_mismatched_.GetURL("/ssl/google.html")));

  // Since we OKed the interstitial last time, we get right to the page.
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_COMMON_NAME_INVALID, AuthState::NONE);
}

// Test that navigating to a #ref does not change a bad security state.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestRefNavigation) {
  ASSERT_TRUE(https_server_expired_.Start());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/page_with_refs.html")));

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);

  ProceedThroughInterstitial(tab);

  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::NONE);
  // Now navigate to a ref in the page, the security state should not have
  // changed.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/page_with_refs.html#jp")));

  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::NONE);
}

// Tests that closing a page that opened a pop-up with an interstitial does not
// crash the browser (crbug.com/40979342).
// TODO(crbug.com/40714131, crbug.com/40848837): Test is flaky on Linux and
// Chrome OS.
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#define MAYBE_TestCloseTabWithUnsafePopup DISABLED_TestCloseTabWithUnsafePopup
#else
#define MAYBE_TestCloseTabWithUnsafePopup TestCloseTabWithUnsafePopup
#endif
IN_PROC_BROWSER_TEST_F(SSLUITest, MAYBE_TestCloseTabWithUnsafePopup) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_expired_.Start());

  // Enable popups without user gesture.
  HostContentSettingsMapFactory::GetForProfile(browser()->GetProfile())
      ->SetDefaultContentSetting(ContentSettingsType::POPUPS,
                                 CONTENT_SETTING_ALLOW);

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_with_unsafe_popup.html",
      https_server_expired_.host_port_pair());
  WebContents* tab1 = browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver nav_observer(
      https_server_expired_.GetURL("/ssl/bad_iframe.html"));
  nav_observer.StartWatchingNewWebContents();
  ASSERT_EQ(1u, ProfileBrowserCollection::GetForProfile(browser()->GetProfile())
                    ->GetSize());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(replacement_path)));
  ASSERT_EQ(2u, ProfileBrowserCollection::GetForProfile(browser()->GetProfile())
                    ->GetSize());

  // Last activated browser should be the popup.
  BrowserWindowInterface* popup_browser =
      ProfileBrowserCollection::GetForProfile(browser()->GetProfile())
          ->GetLastActiveBrowser();
  WebContents* popup =
      popup_browser->GetTabStripModel()->GetActiveWebContents();
  EXPECT_NE(popup, tab1);
  nav_observer.Wait();
  ASSERT_TRUE(popup->GetController().GetVisibleEntry());
  EXPECT_EQ(https_server_expired_.GetURL("/ssl/bad_iframe.html"),
            popup->GetController().GetVisibleEntry()->GetURL());
  // The interstitial showing is posted to the message loop and this happens
  // after the navigation, so we need to additionally wait for that to be
  // processed.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(popup));

  // Add another tab to make sure the browser does not exit when we close
  // the first tab.
  GURL url = embedded_test_server()->GetURL("/ssl/google.html");
  auto* contents =
      chrome::AddSelectedTabWithURL(browser(), url, ui::PAGE_TRANSITION_TYPED);
  EXPECT_TRUE(content::WaitForLoadStop(contents));

  // Close the first tab.
  chrome::CloseWebContents(browser(), tab1, false);
}

// Visit a page over bad https that is a redirect to a page with good https.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestRedirectBadToGoodHTTPS) {
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(https_server_expired_.Start());

  GURL url1 = https_server_expired_.GetURL("/server-redirect?");
  GURL url2 = https_server_.GetURL("/ssl/google.html");

  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(url1.spec() + url2.spec())));

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);

  ProceedThroughInterstitial(tab);

  // We have been redirected to the good page.
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}

// Visit a page over good https that is a redirect to a page with bad https.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestRedirectGoodToBadHTTPS) {
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(https_server_expired_.Start());

  GURL url1 = https_server_.GetURL("/server-redirect?");
  GURL url2 = https_server_expired_.GetURL("/ssl/google.html");
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(url1.spec() + url2.spec())));
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));

  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);

  ProceedThroughInterstitial(tab);

  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::NONE);
}

// Visit a page over http that is a redirect to a page with good HTTPS.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestRedirectHTTPToGoodHTTPS) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  // HTTP redirects to good HTTPS.
  GURL http_url = embedded_test_server()->GetURL("/server-redirect?");
  GURL good_https_url = https_server_.GetURL("/ssl/google.html");

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), GURL(http_url.spec() + good_https_url.spec())));
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}

// Visit a page over http that is a redirect to a page with bad HTTPS.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestRedirectHTTPToBadHTTPS) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_expired_.Start());

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  const GURL http_url = embedded_test_server()->GetURL("/server-redirect?");
  const GURL bad_https_url = https_server_expired_.GetURL("/ssl/google.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), GURL(http_url.spec() + bad_https_url.spec())));
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);

  ProceedThroughInterstitial(tab);

  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::NONE);
}

// Visit a page over https that is a redirect to a page with http (to make sure
// we don't keep the secure state).
IN_PROC_BROWSER_TEST_F(SSLUITest, TestRedirectHTTPSToHTTP) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  GURL https_url = https_server_.GetURL("/server-redirect?");
  GURL http_url = embedded_test_server()->GetURL("/ssl/google.html");

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), GURL(https_url.spec() + http_url.spec())));
  ssl_test_util::CheckUnauthenticatedState(
      browser()->tab_strip_model()->GetActiveWebContents(), AuthState::NONE);
}

// Visit a page over https that is a redirect to a non-existent page with http
// (to make sure we don't keep the secure state when redirecting to an error).
// Regression test for crbug.com/40735055.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestRedirectHTTPSToInvalidHTTP) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  GURL https_url = https_server_.GetURL("/server-redirect?");
  // Test runners might have servers listening in localhost, and the test
  // constructor routes all URLs to localhost, so use close-socket to make
  // sure we always get an error page.
  GURL invalid_url = embedded_test_server()->GetURL("/close-socket");

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), GURL(https_url.spec() + invalid_url.spec())));
  // Check we don't keep the previous certificate state around.
  EXPECT_FALSE(chrome_security_state::GetVisibleSecurityState(
                   browser()->tab_strip_model()->GetActiveWebContents())
                   ->certificate);
  EXPECT_EQ(chrome_security_state::GetSecurityLevel(
                browser()->tab_strip_model()->GetActiveWebContents()),
            security_state::SecurityLevel::NONE);
}

class SSLUITestWaitForDOMNotification : public SSLUITestIgnoreCertErrors,
                                        public content::WebContentsObserver {
 public:
  SSLUITestWaitForDOMNotification()
      : SSLUITestIgnoreCertErrors(), run_loop_(nullptr) {}

  SSLUITestWaitForDOMNotification(const SSLUITestWaitForDOMNotification&) =
      delete;
  SSLUITestWaitForDOMNotification& operator=(
      const SSLUITestWaitForDOMNotification&) = delete;
  ~SSLUITestWaitForDOMNotification() override = default;

  void SetUpOnMainThread() override {
    SSLUITestIgnoreCertErrors::SetUpOnMainThread();
  }

  void set_expected_notification(const std::string& expected_notification) {
    expected_notification_ = expected_notification;
  }

  void set_run_loop(base::RunLoop* run_loop) { run_loop_ = run_loop; }
  void observe(content::WebContents* web_contents) {
    content::WebContentsObserver::Observe(web_contents);
  }

  // content::WebContentsObserver
  void DomOperationResponse(content::RenderFrameHost* render_frame_host,
                            const std::string& json_string) override {
    DCHECK(run_loop_);
    if (json_string == expected_notification_) {
      run_loop_->Quit();
    }
  }

 private:
  std::string expected_notification_;
  raw_ptr<base::RunLoop> run_loop_;
};

// Tests that a mixed resource which includes HTTP in the redirect chain
// is marked as mixed content, even if the end result is HTTPS.
IN_PROC_BROWSER_TEST_F(SSLUITestWaitForDOMNotification,
                       TestMixedContentWithHTTPInRedirectChain) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/ssl/blank_page.html")));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);

  // Construct a URL which will be dynamically added to the page as an
  // image. The URL redirects through HTTP, though it ends up at an
  // HTTPS resource.
  GURL http_url = embedded_test_server()->GetURL("/server-redirect?");
  GURL::Replacements http_url_replacements;
  // Be sure to use a non-localhost name for the mixed content request,
  // since local hostnames are not considered mixed content.
  http_url_replacements.SetHostStr("example.test");
  url::UriComponentEncoder http_url_query(
      https_server_.GetURL("/ssl/google_files/logo.gif").spec());
  http_url_replacements.SetQueryStr(http_url_query.view());
  http_url = http_url.ReplaceComponents(http_url_replacements);

  GURL https_url = https_server_.GetURL("/server-redirect?");
  GURL::Replacements https_url_replacements;
  url::UriComponentEncoder https_url_query(http_url.spec());
  https_url_replacements.SetQueryStr(https_url_query.view());
  https_url = https_url.ReplaceComponents(https_url_replacements);

  base::RunLoop run_loop;

  // Load the image. It starts at |https_server_|, which redirects to an
  // embedded_test_server() HTTP URL, which redirects back to
  // |https_server_| for the final HTTPS image. Because the redirect
  // chain passes through HTTP, the page should be marked as mixed
  // content.
  set_expected_notification("\"mixed-image-loaded\"");
  observe(tab);
  set_run_loop(&run_loop);
  ASSERT_TRUE(content::ExecJs(
      tab,
      "var loaded = function () {"
      "  window.domAutomationController.send('mixed-image-loaded');"
      "};"
      "var img = document.createElement('img');"
      "img.onload = loaded;"
      "img.src = '" +
          https_url.spec() +
          "';"
          "document.body.appendChild(img);"));

  run_loop.Run();
  ssl_test_util::CheckSecurityState(tab, CertError::NONE,
                                    security_state::WARNING,
                                    AuthState::DISPLAYED_INSECURE_CONTENT);
}

// Visits a page to which we could not connect (bad port) over http and https
// and make sure the security style is correct.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestConnectToBadPort) {
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL("http://localhost:17")));
  ssl_test_util::CheckUnauthenticatedState(
      browser()->tab_strip_model()->GetActiveWebContents(),
      AuthState::SHOWING_ERROR);

  // Same thing over HTTPS.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL("https://localhost:17")));
  ssl_test_util::CheckUnauthenticatedState(
      browser()->tab_strip_model()->GetActiveWebContents(),
      AuthState::SHOWING_ERROR);
}

//
// Frame navigation
//

// From a good HTTPS top frame:
// - navigate to an OK HTTPS frame
// - navigate to a bad HTTPS (expect unsafe content and filtered frame), then
//   back
// - navigate to HTTP (expect insecure content), then back
IN_PROC_BROWSER_TEST_F(SSLUITest, TestGoodFrameNavigation) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(https_server_expired_.Start());

  // SetUpOnMainThread adds this hostname to the resolver so that it's not
  // blocked (browser_test_base.cc has a resolver that blocks all non-local
  // hostnames by default to ensure tests don't hit the network). This is
  // critical to do because the request would otherwise get cancelled in the
  // browser before the renderer sees it.

  std::string top_frame_path = GetTopFramePath(
      *embedded_test_server(), https_server_, https_server_expired_);

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(top_frame_path)));

  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);

  // Now navigate inside the frame.
  {
    content::LoadStopObserver observer(tab);
    ASSERT_EQ(true, content::EvalJs(tab, "clickLink('goodHTTPSLink');"));
    observer.Wait();
  }

  // We should still be fine.
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);

  // Now let's hit a bad page.
  {
    content::LoadStopObserver observer(tab);
    ASSERT_EQ(true, content::EvalJs(tab, "clickLink('badHTTPSLink');"));
    observer.Wait();
  }

  // The security style should still be secure.
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);

  // And the frame should be blocked.
  content::RenderFrameHost* content_frame = content::FrameMatchingPredicate(
      tab->GetPrimaryPage(),
      base::BindRepeating(&content::FrameMatchesName, "contentFrame"));
  std::string is_evil_js("document.getElementById('evilDiv') != null;");
  EXPECT_EQ(false, content::EvalJs(content_frame, is_evil_js));

  // Now go back, our state should still be OK.
  {
    content::LoadStopObserver observer(tab);
    tab->GetController().GoBack();
    observer.Wait();
  }
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);

  // Navigate to a page served over HTTP.
  {
    content::LoadStopObserver observer(tab);
    ASSERT_EQ(true, content::EvalJs(tab, "clickLink('HTTPLink');"));
    observer.Wait();
  }

  // Our state should be unauthenticated (in the ran mixed script sense). Note
  // this also displays images from the http page (google.com).
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, CertError::NONE,
      AuthState::RAN_INSECURE_CONTENT | AuthState::DISPLAYED_INSECURE_CONTENT |
          AuthState::DISPLAYED_FORM_WITH_INSECURE_ACTION);

  // Go back, our state should be unchanged.
  {
    content::LoadStopObserver observer(tab);
    tab->GetController().GoBack();
    observer.Wait();
  }

  ssl_test_util::CheckAuthenticationBrokenState(
      tab, CertError::NONE,
      AuthState::RAN_INSECURE_CONTENT | AuthState::DISPLAYED_INSECURE_CONTENT |
          AuthState::DISPLAYED_FORM_WITH_INSECURE_ACTION);
}

// From a bad HTTPS top frame:
// - navigate to an OK HTTPS frame (expected to be still authentication broken).
IN_PROC_BROWSER_TEST_F(SSLUITest, TestBadFrameNavigation) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(https_server_expired_.Start());

  std::string top_frame_path = GetTopFramePath(
      *embedded_test_server(), https_server_, https_server_expired_);

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL(top_frame_path)));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);

  ProceedThroughInterstitial(tab);

  // Navigate to a good frame.
  content::LoadStopObserver observer(tab);
  ASSERT_EQ(true, content::EvalJs(tab, "clickLink('goodHTTPSLink');"));
  observer.Wait();

  // We should still be authentication broken.
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::NONE);
}

// From an HTTP top frame, navigate to good and bad HTTPS (security state should
// stay unauthenticated).
IN_PROC_BROWSER_TEST_F(SSLUITest, TestUnauthenticatedFrameNavigation) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(https_server_expired_.Start());

  std::string top_frame_path = GetTopFramePath(
      *embedded_test_server(), https_server_, https_server_expired_);

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(top_frame_path)));
  ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);

  // Now navigate inside the frame to a secure HTTPS frame.
  {
    content::LoadStopObserver observer(tab);
    ASSERT_EQ(true, content::EvalJs(tab, "clickLink('goodHTTPSLink');"));
    observer.Wait();
  }

  // We should still be unauthenticated.
  ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);

  // Now navigate to a bad HTTPS frame.
  {
    content::LoadStopObserver observer(tab);
    ASSERT_EQ(true, content::EvalJs(tab, "clickLink('badHTTPSLink');"));
    observer.Wait();
  }

  // State should not have changed.
  ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);

  // And the frame should have been blocked (see bug #2316).
  content::RenderFrameHost* content_frame = content::FrameMatchingPredicate(
      tab->GetPrimaryPage(),
      base::BindRepeating(&content::FrameMatchesName, "contentFrame"));
  std::string is_evil_js("document.getElementById('evilDiv') != null;");
  EXPECT_EQ(false, content::EvalJs(content_frame, is_evil_js));
}

enum class OffMainThreadFetchMode { kEnabled, kDisabled };
enum class SSLUIWorkerFetchTestType { kUseFetch, kUseImportScripts };

class SSLUIWorkerFetchTest
    : public testing::WithParamInterface<SSLUIWorkerFetchTestType>,
      public SSLUITestBase {
 public:
  SSLUIWorkerFetchTest() { EXPECT_TRUE(tmp_dir_.CreateUniqueTempDir()); }

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

  ~SSLUIWorkerFetchTest() override = default;

  void SetUpOnMainThread() override { SSLUITestBase::SetUpOnMainThread(); }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    SSLUITestBase::SetUpCommandLine(command_line);
    scoped_feature_list_.InitWithFeatures({}, GetDisabledFeatures());
  }

 protected:
  void WriteFile(const base::FilePath::StringType& filename,
                 std::string_view contents) {
    base::ScopedAllowBlockingForTesting allow_blocking;
    EXPECT_TRUE(base::WriteFile(tmp_dir_.GetPath().Append(filename), contents));
  }

  void WriteTestFiles(const net::EmbeddedTestServer& remote_server,
                      const std::string& hostname) {
    WriteFile(FILE_PATH_LITERAL("worker_test.html"),
              "<script>"
              "var worker = new Worker('worker.js');"
              "worker.addEventListener("
              "    'message',"
              "    event => { document.title = event.data; });"
              "</script>");
    switch (GetParam()) {
      case SSLUIWorkerFetchTestType::kUseFetch:
        WriteFile(FILE_PATH_LITERAL("worker_test_data.txt.mock-http-headers"),
                  "HTTP/1.1 200 OK\n"
                  "Content-Type: text/plain\n"
                  "Access-Control-Allow-Origin: *");
        WriteFile(FILE_PATH_LITERAL("worker_test_data.txt"), "LOADED");
        WriteFile(FILE_PATH_LITERAL("worker.js"),
                  base::StringPrintf(
                      "fetch('%s')"
                      "  .then(res => res.text())"
                      "  .then(text => postMessage(text))"
                      "  .catch(_ => postMessage('FAILED'))",
                      remote_server.GetURL(hostname, "/worker_test_data.txt")
                          .spec()
                          .c_str()));
        break;
      case SSLUIWorkerFetchTestType::kUseImportScripts: {
        WriteFile(FILE_PATH_LITERAL("imported.js"), "data = 'LOADED';");
        WriteFile(
            FILE_PATH_LITERAL("worker.js"),
            base::StringPrintf(
                "var data = 'FAILED';"
                "try {"
                "  importScripts('%s')"
                "} catch(e) {}"
                "postMessage(data);",
                remote_server.GetURL(hostname, "/imported.js").spec().c_str()));
      } break;
    }
  }

  void RunMixedContentSettingsTest(
      ChromeContentBrowserClientForMixedContentTest* browser_client,
      bool allow_running_insecure_content,
      bool strict_mixed_content_checking,
      bool strictly_block_blockable_mixed_content,
      bool expected_load,
      bool expected_show_blocked,
      bool expected_show_dangerous,
      bool expected_load_after_allow,
      bool expected_show_blocked_after_allow,
      bool expected_show_dangerous_after_allow) {
    SCOPED_TRACE(
        ::testing::Message()
        << "RunMixedContentSettingsTest :"
        << "allow_running_insecure_content="
        << (allow_running_insecure_content ? "true " : "false ")
        << "strict_mixed_content_checking="
        << (strict_mixed_content_checking ? "true " : "false ")
        << "strictly_block_blockable_mixed_content="
        << (strictly_block_blockable_mixed_content ? "true " : "false "));

    // Run the tests in a new tab. This forces each call of
    // RunMixedContentSettingsTest in a single test case to use different tabs
    // and thus different processes, bypassing a subtle race condition where
    // processes can get re-used under Site Isolation and retain their mixed
    // content status (see crbug.com/41417895). This ensures all error state is
    // cleared.
    chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
    WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
    EXPECT_TRUE(content::WaitForLoadStop(tab));

    CheckErrorStateIsCleared();

    browser_client->SetMixedContentSettings(
        allow_running_insecure_content, strict_mixed_content_checking,
        strictly_block_blockable_mixed_content);
    tab->OnWebPreferencesChanged();
    CheckMixedContentSettings(allow_running_insecure_content,
                              strict_mixed_content_checking,
                              strictly_block_blockable_mixed_content);

    const std::u16string loaded_title = u"LOADED";
    const std::u16string failed_title = u"FAILED";

    {
      // First load.
      content::TitleWatcher watcher(tab, loaded_title);
      watcher.AlsoWaitForTitle(failed_title);
      ASSERT_TRUE(ui_test_utils::NavigateToURL(
          browser(), https_server_.GetURL("/worker_test.html")));
      EXPECT_EQ(expected_load ? loaded_title : failed_title,
                watcher.WaitAndGetTitle());
    }

    EXPECT_EQ(expected_show_blocked,
              content_settings::PageSpecificContentSettings::GetForFrame(
                  tab->GetPrimaryMainFrame())
                  ->IsContentBlocked(ContentSettingsType::MIXEDSCRIPT));
    ssl_test_util::CheckSecurityState(
        tab, CertError::NONE,
        expected_show_dangerous ? security_state::DANGEROUS
                                : security_state::SECURE,
        expected_show_dangerous ? AuthState::RAN_INSECURE_CONTENT
                                : AuthState::NONE);
    // Clears title.
    ASSERT_TRUE(
        content::ExecJs(tab->GetPrimaryMainFrame(), "document.title = \"\";"));

    {
      // SetAllowRunningInsecureContent will reload the page.
      content::TitleWatcher watcher(tab, loaded_title);
      watcher.AlsoWaitForTitle(failed_title);
      SetAllowRunningInsecureContent();
      tab->OnWebPreferencesChanged();
      EXPECT_EQ(expected_load_after_allow ? loaded_title : failed_title,
                watcher.WaitAndGetTitle());
    }

    EXPECT_EQ(expected_show_blocked_after_allow,
              content_settings::PageSpecificContentSettings::GetForFrame(
                  tab->GetPrimaryMainFrame())
                  ->IsContentBlocked(ContentSettingsType::MIXEDSCRIPT));
    ssl_test_util::CheckSecurityState(
        tab, CertError::NONE,
        expected_show_dangerous_after_allow ? security_state::DANGEROUS
                                            : security_state::SECURE,
        expected_show_dangerous_after_allow ? AuthState::RAN_INSECURE_CONTENT
                                            : AuthState::NONE);

    chrome::CloseTab(browser());
  }

  base::ScopedTempDir tmp_dir_;

 private:
  void SetAllowRunningInsecureContent() {
    content::RenderFrameHost* render_frame_host = browser()
                                                      ->tab_strip_model()
                                                      ->GetActiveWebContents()
                                                      ->GetPrimaryMainFrame();
    mojo::AssociatedRemote<content_settings::mojom::ContentSettingsAgent> agent;
    render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(&agent);
    agent->SetAllowRunningInsecureContent();
  }

  void CheckErrorStateIsCleared() {
    WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
    EXPECT_FALSE(content_settings::PageSpecificContentSettings::GetForFrame(
                     tab->GetPrimaryMainFrame())
                     ->IsContentBlocked(ContentSettingsType::MIXEDSCRIPT));
    ssl_test_util::CheckSecurityState(tab, CertError::NONE,
                                      security_state::NONE, AuthState::NONE);
    EXPECT_FALSE(
        chrome_security_state::GetVisibleSecurityState(tab)->ran_mixed_content);
  }

  void CheckMixedContentSettings(bool allow_running_insecure_content,
                                 bool strict_mixed_content_checking,
                                 bool strictly_block_blockable_mixed_content) {
    WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
    const blink::web_pref::WebPreferences& prefs =
        tab->GetOrCreateWebPreferences();
    ASSERT_EQ(prefs.strictly_block_blockable_mixed_content,
              strictly_block_blockable_mixed_content);
    ASSERT_EQ(prefs.allow_running_insecure_content,
              allow_running_insecure_content);
    ASSERT_EQ(prefs.strict_mixed_content_checking,
              strict_mixed_content_checking);
  }

  base::test::ScopedFeatureList scoped_feature_list_;
};

IN_PROC_BROWSER_TEST_P(SSLUIWorkerFetchTest,
                       TestUnsafeContentsInWorkerFiltered) {
  https_server_.ServeFilesFromDirectory(tmp_dir_.GetPath());
  https_server_expired_.ServeFilesFromDirectory(tmp_dir_.GetPath());
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(https_server_expired_.Start());
  WriteTestFiles(https_server_expired_, "localhost");

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  const std::u16string loaded_title = u"LOADED";
  const std::u16string failed_title = u"FAILED";
  content::TitleWatcher watcher(tab, loaded_title);
  watcher.AlsoWaitForTitle(failed_title);

  // This page will spawn a Worker which will try to load content from
  // BadCertServer.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/worker_test.html")));
  // Expect Worker not to load insecure content.
  EXPECT_EQ(failed_title, watcher.WaitAndGetTitle());
  // The bad content is filtered, expect the state to be authenticated.
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}

// This test, and the related test TestUnsafeContentsWithUserException, verify
// that if unsafe content is loaded but the host of that unsafe content has a
// user exception, the content runs and the security style is downgraded.
// TODO(crbug.com/40707016): Disabled due to flakiness.
IN_PROC_BROWSER_TEST_P(SSLUIWorkerFetchTest,
                       DISABLED_TestUnsafeContentsInWorkerWithUserException) {
  https_server_.ServeFilesFromDirectory(tmp_dir_.GetPath());
  https_server_mismatched_.ServeFilesFromDirectory(tmp_dir_.GetPath());
  ASSERT_TRUE(https_server_.Start());
  // Note that it is necessary to user https_server_mismatched_ here over the
  // other invalid cert servers. This is because the test relies on the two
  // servers having different hosts since SSL exceptions are per-host, not per
  // origin, and https_server_mismatched_ uses 'localhost' rather than
  // '127.0.0.1'.
  ASSERT_TRUE(https_server_mismatched_.Start());

  WriteTestFiles(https_server_mismatched_, "localhost");

  // Navigate to an unsafe site. Proceed with interstitial page to indicate
  // the user approves the bad certificate.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_mismatched_.GetURL("/ssl/blank_page.html")));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_COMMON_NAME_INVALID,
      AuthState::SHOWING_INTERSTITIAL);
  content::TestNavigationObserver nav_observer(tab, 1);
  security_interstitials::SecurityInterstitialTabHelper* helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          tab);
  helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
      ->CommandReceived(
          base::NumberToString(security_interstitials::CMD_PROCEED));
  nav_observer.Wait();

  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_COMMON_NAME_INVALID, AuthState::NONE);

  std::unique_ptr<security_state::VisibleSecurityState> visible_security_state =
      chrome_security_state::GetVisibleSecurityState(tab);
  EXPECT_FALSE(visible_security_state->ran_mixed_content);
  EXPECT_FALSE(visible_security_state->displayed_mixed_content);
  EXPECT_FALSE(visible_security_state->ran_content_with_cert_errors);
  EXPECT_FALSE(visible_security_state->displayed_content_with_cert_errors);

  const std::u16string loaded_title = u"LOADED";
  const std::u16string failed_title = u"FAILED";
  content::TitleWatcher watcher(tab, loaded_title);
  watcher.AlsoWaitForTitle(failed_title);

  // Navigate to safe page that has Worker loading unsafe content.
  // Expect content to load but be marked as auth broken due to running insecure
  // content.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/worker_test.html")));
  // Worker loads insecure content
  EXPECT_EQ(loaded_title, watcher.WaitAndGetTitle());
  ssl_test_util::CheckAuthenticationBrokenState(tab, CertError::NONE,
                                                AuthState::NONE);

  visible_security_state = chrome_security_state::GetVisibleSecurityState(tab);
  EXPECT_FALSE(visible_security_state->ran_mixed_content);
  EXPECT_FALSE(visible_security_state->displayed_mixed_content);
  EXPECT_TRUE(visible_security_state->ran_content_with_cert_errors);
  EXPECT_FALSE(visible_security_state->displayed_content_with_cert_errors);
}

// This test checks the behavior of mixed content blocking for the requests
// from a dedicated worker by changing the settings in WebPreferences
// with allow_running_insecure_content = true.
// Flaky. See https://crbug.com/40729606.
IN_PROC_BROWSER_TEST_P(
    SSLUIWorkerFetchTest,
    DISABLED_MixedContentSettings_AllowRunningInsecureContent) {
  ChromeContentBrowserClientForMixedContentTest browser_client;
  content::ScopedContentBrowserClientSetting setting(&browser_client);

  https_server_.ServeFilesFromDirectory(tmp_dir_.GetPath());
  embedded_test_server()->ServeFilesFromDirectory(tmp_dir_.GetPath());
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(embedded_test_server()->Start());

  WriteTestFiles(*embedded_test_server(), "example.com");
  for (bool strict_mixed_content_checking : {true, false}) {
    for (bool strictly_block_blockable_mixed_content : {true, false}) {
      if (strict_mixed_content_checking) {
        RunMixedContentSettingsTest(
            &browser_client, true /* allow_running_insecure_content */,
            strict_mixed_content_checking,
            strictly_block_blockable_mixed_content, false /* expected_load */,
            false /* expected_show_blocked */,
            false /* expected_show_dangerous */,
            false /* expected_load_after_allow */,
            false /* expected_show_blocked_after_allow */,
            false /* expected_show_dangerous_after_allow */);
      } else {
        RunMixedContentSettingsTest(
            &browser_client, true /* allow_running_insecure_content */,
            strict_mixed_content_checking,
            strictly_block_blockable_mixed_content, true /* expected_load */,
            false /* expected_show_blocked */,
            true /* expected_show_dangerous */,
            true /* expected_load_after_allow */,
            false /* expected_show_blocked_after_allow */,
            true /* expected_show_dangerous_after_allow */);
      }
    }
  }
}

// This test checks the behavior of mixed content blocking for the requests
// from a dedicated worker by changing the settings in WebPreferences
// with allow_running_insecure_content = false.
// Disabled due to being flaky. crbug.com/40712072
IN_PROC_BROWSER_TEST_P(
    SSLUIWorkerFetchTest,
    DISABLED_MixedContentSettings_DisallowRunningInsecureContent) {
  ChromeContentBrowserClientForMixedContentTest browser_client;
  content::ScopedContentBrowserClientSetting setting(&browser_client);

  https_server_.ServeFilesFromDirectory(tmp_dir_.GetPath());
  embedded_test_server()->ServeFilesFromDirectory(tmp_dir_.GetPath());
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(embedded_test_server()->Start());

  WriteTestFiles(*embedded_test_server(), "example.com");
  for (bool strict_mixed_content_checking : {true, false}) {
    for (bool strictly_block_blockable_mixed_content : {true, false}) {
      if (strict_mixed_content_checking) {
        RunMixedContentSettingsTest(
            &browser_client, false /* allow_running_insecure_content */,
            strict_mixed_content_checking,
            strictly_block_blockable_mixed_content, false /* expected_load */,
            false /* expected_show_blocked */,
            false /* expected_show_dangerous */,
            false /* expected_load_after_allow */,
            false /* expected_show_blocked_after_allow */,
            false /* expected_show_dangerous_after_allow */);
      } else if (strictly_block_blockable_mixed_content) {
        RunMixedContentSettingsTest(
            &browser_client, false /* allow_running_insecure_content */,
            strict_mixed_content_checking,
            strictly_block_blockable_mixed_content, false /* expected_load */,
            false /* expected_show_blocked */,
            false /* expected_show_dangerous */,
            false /* expected_load_after_allow */,
            false /* expected_show_blocked_after_allow */,
            false /* expected_show_dangerous_after_allow */);
      } else {
        RunMixedContentSettingsTest(
            &browser_client, false /* allow_running_insecure_content */,
            strict_mixed_content_checking,
            strictly_block_blockable_mixed_content, false /* expected_load */,
            true /* expected_show_blocked */,
            false /* expected_show_dangerous */,
            true /* expected_load_after_allow */,
            false /* expected_show_blocked_after_allow */,
            true /* expected_show_dangerous_after_allow */);
      }
    }
  }
}

// This test checks that all mixed content requests from a dedicated worker are
// blocked regardless of the settings in WebPreferences when
// block-all-mixed-content CSP is set with allow_running_insecure_content=true.
// TODO(crbug.com/429204994): Disabled due to flakiness.
IN_PROC_BROWSER_TEST_P(
    SSLUIWorkerFetchTest,
    DISABLED_MixedContentSettingsWithBlockingCSP_AllowRunningInsecureContent) {
  ChromeContentBrowserClientForMixedContentTest browser_client;
  content::ScopedContentBrowserClientSetting setting(&browser_client);

  https_server_.ServeFilesFromDirectory(tmp_dir_.GetPath());
  embedded_test_server()->ServeFilesFromDirectory(tmp_dir_.GetPath());
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(embedded_test_server()->Start());

  WriteTestFiles(*embedded_test_server(), "example.com");
  WriteFile(FILE_PATH_LITERAL("worker.js.mock-http-headers"),
            "HTTP/1.1 200 OK\n"
            "Content-Type: application/javascript\n"
            "Content-Security-Policy: block-all-mixed-content;");
  for (bool strict_mixed_content_checking : {true, false}) {
    for (bool strictly_block_blockable_mixed_content : {true, false}) {
      RunMixedContentSettingsTest(
          &browser_client, true /* allow_running_insecure_content */,
          strict_mixed_content_checking, strictly_block_blockable_mixed_content,
          false /* expected_load */, false /* expected_show_blocked */,
          false /* expected_show_dangerous */,
          false /* expected_load_after_allow */,
          false /* expected_show_blocked_after_allow */,
          false /* expected_show_dangerous_after_allow */);
    }
  }
}

// This test checks that all mixed content requests from a dedicated worker are
// blocked regardless of the settings in WebPreferences when
// block-all-mixed-content CSP is set with allow_running_insecure_content=false.
IN_PROC_BROWSER_TEST_P(
    SSLUIWorkerFetchTest,
    MixedContentSettingsWithBlockingCSP_DisallowRunningInsecureContent) {
  ChromeContentBrowserClientForMixedContentTest browser_client;
  content::ScopedContentBrowserClientSetting setting(&browser_client);

  https_server_.ServeFilesFromDirectory(tmp_dir_.GetPath());
  embedded_test_server()->ServeFilesFromDirectory(tmp_dir_.GetPath());
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(embedded_test_server()->Start());

  WriteTestFiles(*embedded_test_server(), "example.com");
  WriteFile(FILE_PATH_LITERAL("worker.js.mock-http-headers"),
            "HTTP/1.1 200 OK\n"
            "Content-Type: application/javascript\n"
            "Content-Security-Policy: block-all-mixed-content;");
  for (bool strict_mixed_content_checking : {true, false}) {
    for (bool strictly_block_blockable_mixed_content : {true, false}) {
      RunMixedContentSettingsTest(
          &browser_client, false /* allow_running_insecure_content */,
          strict_mixed_content_checking, strictly_block_blockable_mixed_content,
          false /* expected_load */, false /* expected_show_blocked */,
          false /* expected_show_dangerous */,
          false /* expected_load_after_allow */,
          false /* expected_show_blocked_after_allow */,
          false /* expected_show_dangerous_after_allow */);
    }
  }
}

// This test checks that all mixed content requests from a dedicated worker
// which is started from a subframe are blocked if
// allow_running_insecure_content setting is false or
// strict_mixed_content_checking setting is true.
// TODO(carlosil): Re-enable to check if this triggers flakiness due to
// committed interstitials.
IN_PROC_BROWSER_TEST_P(SSLUIWorkerFetchTest, DISABLED_MixedContentSubFrame) {
  ChromeContentBrowserClientForMixedContentTest browser_client;
  content::ScopedContentBrowserClientSetting setting(&browser_client);

  https_server_.ServeFilesFromDirectory(tmp_dir_.GetPath());
  embedded_test_server()->ServeFilesFromDirectory(tmp_dir_.GetPath());
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(embedded_test_server()->Start());

  WriteTestFiles(*embedded_test_server(), "example.com");
  WriteFile(FILE_PATH_LITERAL("worker_iframe.html"),
            "<script>"
            "var worker = new Worker('worker.js');"
            "worker.addEventListener("
            "    'message',"
            "    event => { parent.postMessage(event.data, '*'); });"
            "</script>");
  WriteFile(FILE_PATH_LITERAL("worker_test.html"),
            "<script>"
            "window.addEventListener("
            "    'message',"
            "    event => { document.title = event.data; });"
            "</script>"
            "<iframe src=\"./worker_iframe.html\" />");

  for (bool allow_running_insecure_content : {true, false}) {
    for (bool strict_mixed_content_checking : {true, false}) {
      for (bool strictly_block_blockable_mixed_content : {true, false}) {
        if (allow_running_insecure_content && !strict_mixed_content_checking) {
          RunMixedContentSettingsTest(
              &browser_client, allow_running_insecure_content,
              strict_mixed_content_checking,
              strictly_block_blockable_mixed_content, true /* expected_load */,
              false /* expected_show_blocked */,
              true /* expected_show_dangerous */,
              true /* expected_load_after_allow */,
              false /* expected_show_blocked_after_allow */,
              true /* expected_show_dangerous_after_allow */);
        } else {
          RunMixedContentSettingsTest(
              &browser_client, allow_running_insecure_content,
              strict_mixed_content_checking,
              strictly_block_blockable_mixed_content, false /* expected_load */,
              false /* expected_show_blocked */,
              false /* expected_show_dangerous */,
              false /* expected_load_after_allow */,
              false /* expected_show_blocked_after_allow */,
              false /* expected_show_dangerous_after_allow */);
        }
      }
    }
  }
}

INSTANTIATE_TEST_SUITE_P(
    /* no prefix */,
    SSLUIWorkerFetchTest,
    ::testing::Values(SSLUIWorkerFetchTestType::kUseFetch,
                      SSLUIWorkerFetchTestType::kUseImportScripts));

// Visits a page with unsafe content and makes sure that if a user exception
// to the certificate error is present, the image is loaded and script
// executes.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestUnsafeContentsWithUserException) {
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_NO_FATAL_FAILURE(SetUpUnsafeContentsWithUserException(
      "/ssl/page_with_unsafe_contents.html"));
  ssl_test_util::CheckAuthenticationBrokenState(tab, CertError::NONE,
                                                AuthState::NONE);

  std::unique_ptr<security_state::VisibleSecurityState> visible_security_state =
      chrome_security_state::GetVisibleSecurityState(tab);
  EXPECT_FALSE(visible_security_state->ran_mixed_content);
  EXPECT_FALSE(visible_security_state->displayed_mixed_content);
  EXPECT_TRUE(visible_security_state->ran_content_with_cert_errors);
  EXPECT_TRUE(visible_security_state->displayed_content_with_cert_errors);

  // In order to check that the image was loaded, we check its width.
  // The actual image (Google logo) is 114 pixels wide, so we assume a good
  // image is greater than 100.
  EXPECT_GT(content::EvalJs(tab, "ImageWidth();"), 100);

  EXPECT_EQ(true, content::EvalJs(tab, "IsFooSet();"));

  // Test that active subresources with the same certificate errors as
  // the main resources also get noted in |content_with_cert_errors_status|.
  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_with_unsafe_contents.html",
      https_server_mismatched_.host_port_pair());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_mismatched_.GetURL(replacement_path)));
  EXPECT_EQ(true, content::EvalJs(tab, "IsFooSet();"));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_COMMON_NAME_INVALID, AuthState::NONE);

  visible_security_state = chrome_security_state::GetVisibleSecurityState(tab);
  EXPECT_FALSE(visible_security_state->ran_mixed_content);
  EXPECT_FALSE(visible_security_state->displayed_mixed_content);
  EXPECT_TRUE(visible_security_state->ran_content_with_cert_errors);
  EXPECT_TRUE(visible_security_state->displayed_content_with_cert_errors);
}

// Like the test above, but only displaying inactive content (an image).
IN_PROC_BROWSER_TEST_F(SSLUITest, TestUnsafeImageWithUserException) {
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_NO_FATAL_FAILURE(
      SetUpUnsafeContentsWithUserException("/ssl/page_with_unsafe_image.html"));

  std::unique_ptr<security_state::VisibleSecurityState> visible_security_state =
      chrome_security_state::GetVisibleSecurityState(tab);
  EXPECT_FALSE(visible_security_state->ran_mixed_content);
  EXPECT_FALSE(visible_security_state->displayed_mixed_content);
  EXPECT_FALSE(visible_security_state->ran_content_with_cert_errors);
  EXPECT_TRUE(visible_security_state->displayed_content_with_cert_errors);
  EXPECT_EQ(0u, visible_security_state->cert_status);

  // In order to check that the image was loaded, we check its width.
  // The actual image (Google logo) is 114 pixels wide, so we assume a good
  // image is greater than 100.
  EXPECT_GT(content::EvalJs(tab, "ImageWidth();"), 100);
}

// Test that when the browser blocks displaying insecure content (iframes),
// the indicator shows a secure page, because the blocking made the otherwise
// unsafe page safe (the notification of this state is handled by other means)
IN_PROC_BROWSER_TEST_F(SSLUITestBlock, TestBlockDisplayingInsecureIframe) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_displays_insecure_iframe.html",
      embedded_test_server()->host_port_pair());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(replacement_path)));

  ssl_test_util::CheckAuthenticatedState(
      browser()->tab_strip_model()->GetActiveWebContents(), AuthState::NONE);
}

// Test that when the browser blocks running insecure content, the
// indicator shows a secure page, because the blocking made the otherwise
// unsafe page safe (the notification of this state is handled by other
// means).
IN_PROC_BROWSER_TEST_F(SSLUITestBlock, TestBlockRunningInsecureContent) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_runs_insecure_content.html",
      embedded_test_server()->host_port_pair());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(replacement_path)));

  ssl_test_util::CheckAuthenticatedState(
      browser()->tab_strip_model()->GetActiveWebContents(), AuthState::NONE);
}

// Visit a page and establish a WebSocket connection over bad https with
// --ignore-certificate-errors. The connection should be established without
// interstitial page showing.
IN_PROC_BROWSER_TEST_F(SSLUITestIgnoreCertErrors, TestWSS) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(wss_server_expired_.Start());

  // Setup page title observer.
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::TitleWatcher watcher(tab, u"PASS");
  watcher.AlsoWaitForTitle(u"FAIL");

  // Visit bad HTTPS page.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), wss_server_expired_.GetURL("/websocket/connect_check.html")));

  // We shouldn't have an interstitial page showing here.

  // Test page run a WebSocket wss connection test. The result will be shown
  // as page title.
  const std::u16string result = watcher.WaitAndGetTitle();
  EXPECT_TRUE(base::EqualsCaseInsensitiveASCII(result, "pass"));
}

// Visit a page and establish a WebSocket connection over bad https with
// --ignore-certificate-errors-spki-list. The connection should be established
// without interstitial page showing.
#if !BUILDFLAG(IS_CHROMEOS)  // Chrome OS does not support the flag.
IN_PROC_BROWSER_TEST_F(SSLUITestIgnoreCertErrorsBySPKIWSS, TestWSSExpired) {
  ASSERT_TRUE(wss_server_expired_.Start());

  // Setup page title observer.
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::TitleWatcher watcher(tab, u"PASS");
  watcher.AlsoWaitForTitle(u"FAIL");

  // Visit bad HTTPS page.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), wss_server_expired_.GetURL("/websocket/connect_check.html")));

  // We shouldn't have an interstitial page showing here.

  // Test page run a WebSocket wss connection test. The result will be shown
  // as page title.
  const std::u16string result = watcher.WaitAndGetTitle();
  EXPECT_TRUE(base::EqualsCaseInsensitiveASCII(result, "pass"));
}
#endif  // !BUILDFLAG(IS_CHROMEOS)

// Test that HTTPS pages with a bad certificate don't show an interstitial if
// the public key matches a value from --ignore-certificate-errors-spki-list.
#if !BUILDFLAG(IS_CHROMEOS)  // Chrome OS does not support the flag.
IN_PROC_BROWSER_TEST_F(SSLUITestIgnoreCertErrorsBySPKIHTTPS, TestHTTPS) {
  ASSERT_TRUE(https_server_mismatched_.Start());

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      https_server_mismatched_.GetURL("/ssl/page_with_subresource.html")));

  // We should see no interstitial. The script tag in the page should have
  // loaded and ran (and wasn't blocked by the certificate error).
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
  std::u16string title;
  ui_test_utils::GetCurrentTabTitle(browser(), &title);
  EXPECT_EQ(title, u"This script has loaded");
}
#endif  // !BUILDFLAG(IS_CHROMEOS)

// Test subresources from an origin with a bad certificate are loaded if the
// public key matches a value from --ignore-certificate-errors-spki-list.
#if !BUILDFLAG(IS_CHROMEOS)  // Chrome OS does not support the flag.
IN_PROC_BROWSER_TEST_F(SSLUITestIgnoreCertErrorsBySPKIHTTPS,
                       TestInsecureSubresource) {
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(https_server_mismatched_.Start());

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_with_unsafe_image.html",
      https_server_mismatched_.host_port_pair());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(replacement_path)));

  // We should see no interstitial.
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
  // In order to check that the image was loaded, check its width.
  // The actual image (Google logo) is 276 pixels wide.
  EXPECT_GT(content::EvalJs(tab, "ImageWidth();"), 200);
}
#endif  // !BUILDFLAG(IS_CHROMEOS)

// Verifies that the interstitial can proceed, even if JavaScript is disabled.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestInterstitialJavaScriptProceeds) {
  HostContentSettingsMapFactory::GetForProfile(browser()->GetProfile())
      ->SetDefaultContentSetting(ContentSettingsType::JAVASCRIPT,
                                 CONTENT_SETTING_BLOCK);

  ASSERT_TRUE(https_server_expired_.Start());
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);

  ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));

  content::LoadStopObserver observer(tab);
  const std::string javascript =
      "window.certificateErrorPageController.proceed();";
  EXPECT_TRUE(content::ExecJs(tab, javascript));
  observer.Wait();
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::NONE);
}

// Verifies that the interstitial can go back, even if JavaScript is disabled.
// http://crbug.com/41076570
IN_PROC_BROWSER_TEST_F(SSLUITest, TestInterstitialJavaScriptGoesBack) {
  HostContentSettingsMapFactory::GetForProfile(browser()->GetProfile())
      ->SetDefaultContentSetting(ContentSettingsType::JAVASCRIPT,
                                 CONTENT_SETTING_BLOCK);

  ASSERT_TRUE(https_server_expired_.Start());
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
  content::LoadStopObserver observer(tab);
  const std::string javascript =
      "window.certificateErrorPageController.dontProceed();";
  EXPECT_TRUE(content::ExecJs(tab, javascript));
  observer.Wait();
  EXPECT_EQ("about:blank", tab->GetVisibleURL().spec());
}

// Verifies that an overridable interstitial has a proceed link.
IN_PROC_BROWSER_TEST_F(SSLUITest, ProceedLinkOverridable) {
  ASSERT_TRUE(https_server_expired_.Start());
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);

  ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));

  EXPECT_TRUE(chrome_browser_interstitials::InterstitialHasProceedLink(
      tab->GetPrimaryMainFrame()));
}

IN_PROC_BROWSER_TEST_F(SSLUITest, TestLearnMoreLinkContainsErrorCode) {
  ASSERT_TRUE(https_server_expired_.Start());

  // Navigate to a site that causes an interstitial.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/title1.html")));
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(
      browser()->tab_strip_model()->GetActiveWebContents()));

  // Simulate clicking the learn more link.
  SendInterstitialCommand(browser()->tab_strip_model()->GetActiveWebContents(),
                          security_interstitials::CMD_OPEN_HELP_CENTER);
  EXPECT_EQ(browser()
                ->tab_strip_model()
                ->GetActiveWebContents()
                ->GetVisibleURL()
                .GetRef(),
            base::NumberToString(net::ERR_CERT_DATE_INVALID));
}

// Checks that interstitials are not used for subframe SSL errors. Regression
// test for https://crbug.com/41368701.
IN_PROC_BROWSER_TEST_F(SSLUITest, SubframeCertError) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_expired_.Start());
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/title1.html")));

  // Insert a broken-HTTPS iframe on the page and check that a generic net
  // error, not a certificate error page, is shown.
  content::TestNavigationObserver observer(tab, 1);
  std::string insert_frame = base::StringPrintf(
      "var i = document.createElement('iframe');"
      "i.src = '%s';"
      "document.body.appendChild(i);",
      https_server_expired_.GetURL("/ssl/google.html").spec().c_str());
  EXPECT_TRUE(content::ExecJs(tab, insert_frame));
  observer.Wait();

  content::RenderFrameHost* child =
      content::ChildFrameAt(tab->GetPrimaryMainFrame(), 0);
  ASSERT_TRUE(child);
  const std::string javascript = base::StringPrintf(
      "(document.querySelector(\"#proceed-link\") === null) "
      "? (%d) : (%d)",
      security_interstitials::CMD_TEXT_NOT_FOUND,
      security_interstitials::CMD_TEXT_FOUND);
  int result = content::EvalJs(child, javascript).ExtractInt();
  EXPECT_EQ(security_interstitials::CMD_TEXT_NOT_FOUND, result);
}

// Verifies that a non-overridable interstitial does not have a proceed link.
IN_PROC_BROWSER_TEST_F(SSLUITestHSTS, TestInterstitialOptionsNonOverridable) {
  ASSERT_TRUE(https_server_expired_.Start());
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  GURL::Replacements replacements;
  replacements.SetHostStr(kHstsTestHostName);
  GURL url = https_server_expired_.GetURL("/ssl/google.html")
                 .ReplaceComponents(replacements);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  // Since we are connecting to a different domain than the test server default,
  // we also expect CERT_STATUS_COMMON_NAME_INVALID.
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID | net::CERT_STATUS_COMMON_NAME_INVALID,
      AuthState::SHOWING_INTERSTITIAL);

  ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));

  const std::string javascript = base::StringPrintf(
      "(document.querySelector(\"#proceed-link\") === null) "
      "? (%d) : (%d)",
      security_interstitials::CMD_TEXT_NOT_FOUND,
      security_interstitials::CMD_TEXT_FOUND);
  int result =
      content::EvalJs(tab->GetPrimaryMainFrame(), javascript).ExtractInt();
  EXPECT_EQ(security_interstitials::CMD_TEXT_NOT_FOUND, result);
}

// Verifies that links in the interstitial open in a new tab.
// https://crbug.com/41316569
IN_PROC_BROWSER_TEST_F(SSLUITest, TestInterstitialLinksOpenInNewTab) {
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(https_server_expired_.Start());

  WebContents* interstitial_tab =
      browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));
  ASSERT_TRUE(
      chrome_browser_interstitials::IsShowingSSLInterstitial(interstitial_tab));
  ssl_test_util::CheckAuthenticationBrokenState(
      interstitial_tab, net::CERT_STATUS_DATE_INVALID,
      AuthState::SHOWING_INTERSTITIAL);

  content::TestNavigationObserver nav_observer(nullptr);
  nav_observer.StartWatchingNewWebContents();

  SSLBlockingPage* ssl_interstitial =
      static_cast<SSLBlockingPage*>(GetInterstitialPage(interstitial_tab));
  security_interstitials::SecurityInterstitialControllerClient* client =
      GetControllerClientFromSSLBlockingPage(ssl_interstitial);

  // Mock out the help center URL so that our test will hit the test server
  // instead of a real server.
  // NOTE: The CMD_OPEN_HELP_CENTER code in
  // components/security_interstitials/core/ssl_error_ui.cc ends up appending
  // a path to whatever URL is passed to it. Since that path doesn't exist on
  // our test server, this results in a 404. This is expected behavior, and
  // things are still working as expected so long as the test passes!
  const GURL mock_help_center_url = https_server_.GetURL("/title1.html");
  client->SetBaseHelpCenterUrlForTesting(mock_help_center_url);

  EXPECT_EQ(1, browser()->tab_strip_model()->count());

  SendInterstitialCommand(interstitial_tab,
                          security_interstitials::CMD_OPEN_HELP_CENTER);

  nav_observer.Wait();

  EXPECT_EQ(2, browser()->tab_strip_model()->count());
  WebContents* new_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(new_tab);
  EXPECT_EQ(mock_help_center_url.GetHost(),
            new_tab->GetLastCommittedURL().GetHost());
}

// Verifies that switching tabs, while showing interstitial page, will not
// affect the visibility of the interstitial.
// https://crbug.com/41112511
IN_PROC_BROWSER_TEST_F(SSLUITest, InterstitialNotAffectedByHideShow) {
  ASSERT_TRUE(https_server_expired_.Start());
  ASSERT_TRUE(https_server_.Start());

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  EXPECT_TRUE(tab->GetRenderWidgetHostView()->IsShowing());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
  EXPECT_TRUE(tab->GetRenderWidgetHostView()->IsShowing());

  ASSERT_TRUE(AddTabAtIndex(0, https_server_.GetURL("/ssl/google.html"),
                            ui::PAGE_TRANSITION_TYPED));
  EXPECT_EQ(2, browser()->tab_strip_model()->count());
  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
  EXPECT_EQ(tab, browser()->tab_strip_model()->GetWebContentsAt(1));
  EXPECT_FALSE(tab->GetRenderWidgetHostView()->IsShowing());

  browser()->tab_strip_model()->ActivateTabAt(
      1, TabStripUserGestureDetails(
             TabStripUserGestureDetails::GestureType::kOther));
  EXPECT_TRUE(tab->GetRenderWidgetHostView()->IsShowing());
}

// Verifies that if a bad certificate is seen for any host and the user proceeds
// through the interstitial, the decision to proceed is initially remembered.
// However, if this is followed by another visit, and a good certificate is seen
// for the same host, the original exception is forgotten.
IN_PROC_BROWSER_TEST_F(SSLUITestReduceSubresourceNotifications,
                       HasAllowExceptionForAnyHost) {
  ASSERT_TRUE(https_server_expired_.Start());
  ASSERT_TRUE(https_server_.Start());

  std::string https_server_expired_host =
      https_server_expired_.GetURL("/ssl/google.html").GetHost();
  std::string https_server_host =
      https_server_.GetURL("/ssl/google.html").GetHost();
  ASSERT_EQ(https_server_expired_host, https_server_host);

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

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

  // First check that frame requests revoke the decision.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));

  ProceedThroughInterstitial(tab);
  EXPECT_TRUE(state->HasAllowExceptionForAnyHost(
      tab->GetPrimaryMainFrame()->GetStoragePartition()));

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/ssl/google.html")));
  EXPECT_FALSE(state->HasAllowExceptionForAnyHost(
      tab->GetPrimaryMainFrame()->GetStoragePartition()));
}

// Verifies that if a bad certificate is seen for any host and the user proceeds
// through the interstitial, the decision to proceed is initially remembered.
// However, if this is followed by another visit, and a good certificate is seen
// for the same host, the original exception is forgotten. The state of
// send_subresource_notification does not change in the Webcontents even after a
// good certificate has been seen until the browser process is restarted.
IN_PROC_BROWSER_TEST_F(
    SSLUITestReduceSubresourceNotifications,
    PRE_BadCertFollowedByGoodCertNavigationFollowedByRestart) {
  ASSERT_TRUE(https_server_expired_.Start());
  ASSERT_TRUE(https_server_.Start());

  std::string https_server_expired_host =
      https_server_expired_.GetURL("/ssl/google.html").GetHost();
  std::string https_server_host =
      https_server_.GetURL("/ssl/google.html").GetHost();
  ASSERT_EQ(https_server_expired_host, https_server_host);

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

  // HTTPS-related warning exceptions have not been allowed by the user.
  ASSERT_FALSE(state->HasAllowExceptionForAnyHost(
      tab->GetPrimaryMainFrame()->GetStoragePartition()));
  // Not sending subresource notifications since no HTTPS-exceptions have been
  // allowed by the user.
  ASSERT_FALSE(tab->GetSendSubresourceNotification());

  // Navigate to a page with a certificate error.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);

  // Click through the interstitial.
  ProceedThroughInterstitial(tab);

  // HTTPS-related warning exception has been allowed by the user.
  EXPECT_TRUE(state->HasAllowExceptionForAnyHost(
      tab->GetPrimaryMainFrame()->GetStoragePartition()));

  // State in browser process has been updated, i.e., start renderers need to
  // start sending subresource notifications.
  EXPECT_TRUE(tab->GetSendSubresourceNotification());

  // See a good certificate for the same host. This removes the allowed
  // exception but `renderer_preferences_.send_subresource_notification_` is not
  // set to false. This is because allowing and revoking HTTPS related warning
  // exception is rare, and thus is update at the startup of the browser.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/ssl/google.html")));
  EXPECT_FALSE(state->HasAllowExceptionForAnyHost(
      tab->GetPrimaryMainFrame()->GetStoragePartition()));

  EXPECT_TRUE(tab->GetSendSubresourceNotification());
}

// Verifies that on browser restarts, we update `renderer_preferences_`
// according to state of allowed exceptions at browser start-up.
IN_PROC_BROWSER_TEST_F(SSLUITestReduceSubresourceNotifications,
                       BadCertFollowedByGoodCertNavigationFollowedByRestart) {
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());

  StatefulSSLHostStateDelegate* state =
      static_cast<StatefulSSLHostStateDelegate*>(
          profile->GetSSLHostStateDelegate());

  EXPECT_FALSE(state->HasAllowExceptionForAnyHost(
      tab->GetPrimaryMainFrame()->GetStoragePartition()));

  EXPECT_FALSE(tab->GetSendSubresourceNotification());
}

// Tests whether any certificate error exceptions made are persisted across
// sessions. This also verifies persistence of `send_subresource_notification_`.
IN_PROC_BROWSER_TEST_F(SSLUITestReduceSubresourceNotifications,
                       PRE_CertDecisionPersistsSessions) {
  ASSERT_TRUE(https_server_expired_.Start());

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

  // HTTPS-related warning exceptions have not been allowed by the user.
  ASSERT_FALSE(state->HasAllowExceptionForAnyHost(
      tab->GetPrimaryMainFrame()->GetStoragePartition()));
  // Not sending subresource notifications since no HTTPS-exceptions have been
  // allowed by the user.
  ASSERT_FALSE(tab->GetSendSubresourceNotification());

  // Navigate to a page with a certificate error.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);

  // Click through the interstitial.
  ProceedThroughInterstitial(tab);

  // HTTPS-related warning exception has been allowed by the user.
  EXPECT_TRUE(state->HasAllowExceptionForAnyHost(
      tab->GetPrimaryMainFrame()->GetStoragePartition()));

  // renderer_preferences_.send_subresource_notification_ state updated.
  EXPECT_TRUE(tab->GetSendSubresourceNotification());
}

IN_PROC_BROWSER_TEST_F(SSLUITestReduceSubresourceNotifications,
                       CertDecisionPersistsSessions) {
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
  StatefulSSLHostStateDelegate* state =
      static_cast<StatefulSSLHostStateDelegate*>(
          profile->GetSSLHostStateDelegate());

  // HTTPS-related warning exceptions has been allowed by the user in the past
  // which has not expired.
  EXPECT_TRUE(state->HasAllowExceptionForAnyHost(
      tab->GetPrimaryMainFrame()->GetStoragePartition()));

  // State in browser persists after restart.
  ASSERT_TRUE(tab->GetSendSubresourceNotification());
}

// Tests persistence of `send_subresource_notification_` when multiple bad
// certificates are allowed by the user.
IN_PROC_BROWSER_TEST_F(SSLUITestReduceSubresourceNotifications,
                       MultipleBadCertNavigations) {
  ASSERT_TRUE(https_server_expired_.Start());

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

  // HTTPS-related warning exceptions have not been allowed by the user.
  ASSERT_FALSE(state->HasAllowExceptionForAnyHost(
      tab->GetPrimaryMainFrame()->GetStoragePartition()));
  // Not sending subresource notifications since no HTTPS-exceptions have been
  // allowed by the user.
  ASSERT_FALSE(tab->GetSendSubresourceNotification());

  // Navigate to a page with a certificate error.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);

  // Click through the interstitial.
  ProceedThroughInterstitial(tab);

  // HTTPS-related warning exception has been allowed by the user.
  EXPECT_TRUE(state->HasAllowExceptionForAnyHost(
      tab->GetPrimaryMainFrame()->GetStoragePartition()));

  // renderer_preferences_.send_subresource_notification_ state updated.
  EXPECT_TRUE(tab->GetSendSubresourceNotification());

  // Navigate to a page with a certificate error, and click through the
  // interstitial so the certificate is allowlisted.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), GURL("https://site.test:" +
                      base::NumberToString(https_server_expired_.port()) +
                      "/ssl/blank_page.html")));
  ProceedThroughInterstitial(tab);

  // HTTPS-related warning exception has been allowed by the user.
  EXPECT_TRUE(state->HasAllowExceptionForAnyHost(
      tab->GetPrimaryMainFrame()->GetStoragePartition()));

  EXPECT_TRUE(tab->GetSendSubresourceNotification());
}

// Verifies that if a bad certificate is seen for a host and the user proceeds
// through the interstitial, the decision to proceed is initially remembered.
// However, if this is followed by another visit, and a good certificate
// is seen for the same host, the original exception is forgotten.
IN_PROC_BROWSER_TEST_F(SSLUITest, BadCertFollowedByGoodCertNavigation) {
  // It is necessary to use |https_server_expired_| rather than
  // |https_server_mismatched| because the former shares a host with
  // |https_server_| and cert exceptions are per host.
  ASSERT_TRUE(https_server_expired_.Start());
  ASSERT_TRUE(https_server_.Start());

  std::string https_server_expired_host =
      https_server_expired_.GetURL("/ssl/google.html").GetHost();
  std::string https_server_host =
      https_server_.GetURL("/ssl/google.html").GetHost();
  ASSERT_EQ(https_server_expired_host, https_server_host);

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

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

  // First check that frame requests revoke the decision.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));

  ProceedThroughInterstitial(tab);
  EXPECT_TRUE(state->HasAllowException(
      https_server_host, tab->GetPrimaryMainFrame()->GetStoragePartition()));

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/ssl/google.html")));
  EXPECT_FALSE(state->HasAllowException(
      https_server_host, tab->GetPrimaryMainFrame()->GetStoragePartition()));
}

// Verifies that if a bad certificate is seen for a host and the user proceeds
// through the interstitial, the decision to proceed is initially remembered.
// However, if this is followed by a subresource load, and a good certificate
// is seen for the same host via the subresource load, the original exception
// is forgotten.
IN_PROC_BROWSER_TEST_F(SSLUITest, BadCertFollowedByGoodCertSubresource) {
  // As in SSLUITest.BadCertFollowedByGoodCertNavigation, it is necessary to use
  // |https_server_expired_| rather than |https_server_mismatched| because the
  // former shares a host with |https_server_| and cert exceptions are per host.
  ASSERT_TRUE(https_server_expired_.Start());
  ASSERT_TRUE(https_server_.Start());

  std::string https_server_expired_host =
      https_server_expired_.GetURL("/ssl/google.html").GetHost();
  std::string https_server_host =
      https_server_.GetURL("/ssl/google.html").GetHost();
  ASSERT_EQ(https_server_expired_host, https_server_host);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));

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

  ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));

  ProceedThroughInterstitial(tab);
  EXPECT_TRUE(state->HasAllowException(
      https_server_host, tab->GetPrimaryMainFrame()->GetStoragePartition()));

  GURL image = https_server_.GetURL("/ssl/google_files/logo.gif");
  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(
      https_server_host, tab->GetPrimaryMainFrame()->GetStoragePartition()));
}

// Verifies that if a bad certificate is seen for a host and the user proceeds
// through the interstitial, the decision to proceed is not forgotten once blob
// URLs are loaded (blob loads never have certificate errors).  This is a
// regression test for https://crbug.com/40117754.
IN_PROC_BROWSER_TEST_F(SSLUITest, BadCertFollowedByBlobUrl) {
  ASSERT_TRUE(https_server_expired_.Start());
  std::string https_server_host =
      https_server_expired_.GetURL("/ssl/google.html").GetHost();

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

  // Proceed through the interstitial, accepting the broken cert.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));
  ProceedThroughInterstitial(tab);
  ASSERT_TRUE(state->HasAllowException(
      https_server_host, tab->GetPrimaryMainFrame()->GetStoragePartition()));

  // Load a blob URL.
  content::WebContentsConsoleObserver console_observer(tab);
  console_observer.SetPattern("hello from blob");
  const char kScript[] = R"(
      new Promise(function (resolvePromise, rejectPromise) {
          var blob = new Blob(['console.log("hello from blob")'],
                              {type : 'application/javascript'});
          script = document.createElement('script');
          script.onerror = rejectPromise;
          script.onload = () => resolvePromise('success');
          script.src = URL.createObjectURL(blob);
          document.body.appendChild(script);
      });
  )";
  ASSERT_EQ("success", content::EvalJs(tab, kScript));

  // Verify that the script from the blob has successfully run.
  ASSERT_TRUE(console_observer.Wait());

  // Verify that the decision to accept the broken cert has not been revoked
  // (this is a regression test for https://crbug.com/40117754).
  EXPECT_TRUE(state->HasAllowException(
      https_server_host, tab->GetPrimaryMainFrame()->GetStoragePartition()));
}

// Tests that the SSLStatus of a navigation entry for an SSL
// interstitial matches the navigation entry once the interstitial is
// clicked through. https://crbug.com/41200271
IN_PROC_BROWSER_TEST_F(SSLUITest,
                       SSLStatusMatchesOnInterstitialAndAfterProceed) {
  ASSERT_TRUE(https_server_expired_.Start());
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(tab);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));
  EXPECT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));

  content::NavigationEntry* entry = tab->GetController().GetVisibleEntry();
  ASSERT_TRUE(entry);
  content::SSLStatus interstitial_ssl_status = entry->GetSSL();

  ProceedThroughInterstitial(tab);
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(tab));
  entry = tab->GetController().GetLastCommittedEntry();
  ASSERT_TRUE(entry);

  content::SSLStatus after_interstitial_ssl_status = entry->GetSSL();
  EXPECT_TRUE(ComparePreAndPostInterstitialSSLStatuses(
      interstitial_ssl_status, after_interstitial_ssl_status));
}

// As above, but for a bad clock interstitial. Tests that a clock
// interstitial's SSLStatus matches the SSLStatus of the HTTPS page
// after proceeding through a normal SSL interstitial.
IN_PROC_BROWSER_TEST_F(SSLUITest,
                       SSLStatusMatchesonClockInterstitialAndAfterProceed) {
  ASSERT_TRUE(https_server_expired_.Start());
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(tab);

  // Set up the build and current clock times to be more than a year apart.
  base::SimpleTestClock mock_clock;
  mock_clock.SetNow(base::Time::NowFromSystemTime());
  mock_clock.Advance(base::Days(367));
  SSLErrorHandler::SetClockForTesting(&mock_clock);
  ssl_errors::SetBuildTimeForTesting(base::Time::NowFromSystemTime());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/title1.html")));
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingBadClockInterstitial(tab));

  // Grab the SSLStatus on the clock interstitial.
  content::NavigationEntry* entry = tab->GetController().GetVisibleEntry();
  ASSERT_TRUE(entry);
  content::SSLStatus clock_interstitial_ssl_status = entry->GetSSL();

  // Put the clock back to normal, trigger a normal SSL interstitial,
  // and proceed through it.
  mock_clock.SetNow(base::Time::NowFromSystemTime());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/title1.html")));
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
  ProceedThroughInterstitial(tab);
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(tab));

  // Grab the SSLStatus from the page and check that it is the same as
  // on the clock interstitial.
  entry = tab->GetController().GetLastCommittedEntry();
  ASSERT_TRUE(entry);
  content::SSLStatus after_interstitial_ssl_status = entry->GetSSL();
  EXPECT_TRUE(ComparePreAndPostInterstitialSSLStatuses(
      clock_interstitial_ssl_status, after_interstitial_ssl_status));
}

// A fixture for testing on-demand network time queries on SSL
// certificate date errors. It can simulate a delayed network time
// request, and it allows the user to configure the experimental
// parameters of the NetworkTimeTracker. Expects only one network time
// request to be issued during the test.
class SSLNetworkTimeBrowserTest : public SSLUITest {
 public:
  SSLNetworkTimeBrowserTest() : SSLUITest() {
    scoped_feature_list_.InitAndEnableFeatureWithParameters(
        network_time::kNetworkTimeServiceQuerying,
        {{"FetchBehavior", "on-demand-only"}});
  }

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

  ~SSLNetworkTimeBrowserTest() override = default;

  void SetUpOnMainThread() override {
    SSLUITest::SetUpOnMainThread();
    controllable_response_ =
        std::make_unique<net::test_server::ControllableHttpResponse>(
            embedded_test_server(), "/", true);
    ASSERT_TRUE(embedded_test_server()->Start());
    g_browser_process->network_time_tracker()->SetTimeServerURLForTesting(
        embedded_test_server()->GetURL("/"));
  }

 protected:
  void TriggerTimeResponse() {
    std::string response = "HTTP/1.1 200 OK\nContent-type: text/plain\n";
    response += base::StringPrintf(
        "Content-Length: %1d\n",
        static_cast<int>(strlen(network_time::kGoodTimeResponseBody[0])));
    response +=
        "x-cup-server-proof: " +
        std::string(network_time::kGoodTimeResponseServerProofHeader[0]);
    response += "\n\n";
    response += std::string(network_time::kGoodTimeResponseBody[0]);
    controllable_response_->WaitForRequest();
    controllable_response_->Send(response);
  }

  // Asserts that the first time request to the server is currently pending.
  void CheckTimeQueryPending() {
    base::Time unused_time;
    base::TimeDelta unused_uncertainty;
    ASSERT_EQ(network_time::NetworkTimeTracker::NETWORK_TIME_FIRST_SYNC_PENDING,
              g_browser_process->network_time_tracker()->GetNetworkTime(
                  &unused_time, &unused_uncertainty));
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
  std::unique_ptr<net::test_server::ControllableHttpResponse>
      controllable_response_;
};

// Tests that if an on-demand network time fetch returns that the clock
// is okay, a normal SSL interstitial is shown.
IN_PROC_BROWSER_TEST_F(SSLNetworkTimeBrowserTest, OnDemandFetchClockOk) {
  ASSERT_TRUE(https_server_expired_.Start());
  // Use a testing clock set to the time that GoodTimeResponseHandler
  // returns, to simulate the system clock matching the network time.
  base::SimpleTestClock testing_clock;
  SSLErrorHandler::SetClockForTesting(&testing_clock);
  testing_clock.SetNow(base::Time::FromMillisecondsSinceUnixEpoch(
      network_time::kGoodTimeResponseHandlerJsTime[0]));
  // Set the build time to match the testing clock, to ensure that the
  // build time heuristic doesn't fire.
  ssl_errors::SetBuildTimeForTesting(testing_clock.Now());

  // Set a long timeout to ensure that the on-demand time fetch completes.
  SSLErrorHandler::SetInterstitialDelayForTesting(base::Hours(1));

  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(contents);
  SSLInterstitialTimerObserver interstitial_timer_observer(contents);

  ui_test_utils::NavigateToURLWithDisposition(
      browser(), https_server_expired_.GetURL("/"),
      WindowOpenDisposition::CURRENT_TAB, ui_test_utils::BROWSER_TEST_NO_WAIT);

  // Once |interstitial_timer_observer| has fired, the request has been
  // sent. Override the nonce that NetworkTimeTracker expects so that
  // when the response comes back, it will validate. The nonce can only
  // be overriden for the current in-flight request, so the test must
  // call OverrideNonceForTesting() after the request has been sent and
  // before the response has been received.
  interstitial_timer_observer.WaitForTimerStarted();
  g_browser_process->network_time_tracker()->OverrideNonceForTesting(123123123);
  TriggerTimeResponse();

  EXPECT_TRUE(contents->IsLoading());
  // False, because an interstitial is not a normal load result.
  EXPECT_FALSE(content::WaitForLoadStop(contents));

  ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(contents));
}

// Tests that if an on-demand network time fetch returns that the clock
// is wrong, a bad clock interstitial is shown.
IN_PROC_BROWSER_TEST_F(SSLNetworkTimeBrowserTest, OnDemandFetchClockWrong) {
  ASSERT_TRUE(https_server_expired_.Start());
  // Use a testing clock set to a time that is different from what
  // GoodTimeResponseHandler returns, simulating a system clock that is
  // 30 days ahead of the network time.
  base::SimpleTestClock testing_clock;
  SSLErrorHandler::SetClockForTesting(&testing_clock);
  testing_clock.SetNow(base::Time::FromMillisecondsSinceUnixEpoch(
      network_time::kGoodTimeResponseHandlerJsTime[0]));
  testing_clock.Advance(base::Days(30));
  // Set the build time to match the testing clock, to ensure that the
  // build time heuristic doesn't fire.
  ssl_errors::SetBuildTimeForTesting(testing_clock.Now());

  // Set a long timeout to ensure that the on-demand time fetch completes.
  SSLErrorHandler::SetInterstitialDelayForTesting(base::Hours(1));

  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(contents);
  SSLInterstitialTimerObserver interstitial_timer_observer(contents);

  ui_test_utils::NavigateToURLWithDisposition(
      browser(), https_server_expired_.GetURL("/"),
      WindowOpenDisposition::CURRENT_TAB, ui_test_utils::BROWSER_TEST_NO_WAIT);

  // Once |interstitial_timer_observer| has fired, the request has been
  // sent. Override the nonce that NetworkTimeTracker expects so that
  // when the response comes back, it will validate. The nonce can only
  // be overriden for the current in-flight request, so the test must
  // call OverrideNonceForTesting() after the request has been sent and
  // before the response has been received.
  interstitial_timer_observer.WaitForTimerStarted();
  g_browser_process->network_time_tracker()->OverrideNonceForTesting(123123123);
  TriggerTimeResponse();

  EXPECT_TRUE(contents->IsLoading());
  // False, because an interstitial is not a normal load result.
  EXPECT_FALSE(content::WaitForLoadStop(contents));

  ASSERT_TRUE(
      chrome_browser_interstitials::IsShowingBadClockInterstitial(contents));
}

// Tests that if the timeout expires before the network time fetch
// returns, then a normal SSL interstitial is shown.
IN_PROC_BROWSER_TEST_F(SSLNetworkTimeBrowserTest,
                       TimeoutExpiresBeforeFetchCompletes) {
  ASSERT_TRUE(https_server_expired_.Start());
  // Set the timer to fire immediately.
  SSLErrorHandler::SetInterstitialDelayForTesting(base::TimeDelta());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(),
                                           https_server_expired_.GetURL("/")));
  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(contents);

  ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(contents));

  // Navigate away, and then trigger the network time response; no crash should
  // occur.
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), https_server_.GetURL("/")));
  ASSERT_NO_FATAL_FAILURE(CheckTimeQueryPending());
  TriggerTimeResponse();
}

// Tests that if the user stops the page load before either the network
// time fetch completes or the timeout expires, then there is no interstitial.
IN_PROC_BROWSER_TEST_F(SSLNetworkTimeBrowserTest, StopBeforeTimeoutExpires) {
  ASSERT_TRUE(https_server_expired_.Start());
  // Set the timer to a long delay.
  SSLErrorHandler::SetInterstitialDelayForTesting(base::Hours(1));

  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(contents);
  SSLInterstitialTimerObserver interstitial_timer_observer(contents);

  ui_test_utils::NavigateToURLWithDisposition(
      browser(), https_server_expired_.GetURL("/"),
      WindowOpenDisposition::CURRENT_TAB, ui_test_utils::BROWSER_TEST_NO_WAIT);
  interstitial_timer_observer.WaitForTimerStarted();

  EXPECT_TRUE(contents->IsLoading());
  contents->Stop();
  EXPECT_TRUE(content::WaitForLoadStop(contents));

  // Make sure that the |SSLErrorHandler| is deleted.
  EXPECT_FALSE(SSLErrorHandler::FromWebContents(contents));
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(contents));
  EXPECT_FALSE(contents->IsLoading());

  // Navigate away, and then trigger the network time response; no crash should
  // occur.
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/title1.html")));
  ASSERT_NO_FATAL_FAILURE(CheckTimeQueryPending());
  TriggerTimeResponse();
}

// Tests that if the user reloads the page before either the network
// time fetch completes or the timeout expires, then there is no interstitial.
IN_PROC_BROWSER_TEST_F(SSLNetworkTimeBrowserTest, ReloadBeforeTimeoutExpires) {
  ASSERT_TRUE(https_server_expired_.Start());
  // Set the timer to a long delay.
  SSLErrorHandler::SetInterstitialDelayForTesting(base::Hours(1));

  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  SSLInterstitialTimerObserver interstitial_timer_observer(contents);

  ui_test_utils::NavigateToURLWithDisposition(
      browser(), https_server_expired_.GetURL("/"),
      WindowOpenDisposition::CURRENT_TAB, ui_test_utils::BROWSER_TEST_NO_WAIT);
  interstitial_timer_observer.WaitForTimerStarted();

  EXPECT_TRUE(contents->IsLoading());
  content::TestNavigationObserver observer(contents);
  chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB);
  observer.Wait();

  // Make sure that the |SSLErrorHandler| is deleted.
  EXPECT_FALSE(SSLErrorHandler::FromWebContents(contents));
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(contents));
  EXPECT_FALSE(contents->IsLoading());

  // Navigate away, and then trigger the network time response and wait
  // for the response; no crash should occur.
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), https_server_.GetURL("/")));
  ASSERT_NO_FATAL_FAILURE(CheckTimeQueryPending());
  TriggerTimeResponse();
}

// Tests that if the user navigates away before either the network time
// fetch completes or the timeout expires, then there is no
// interstitial.
IN_PROC_BROWSER_TEST_F(SSLNetworkTimeBrowserTest,
                       NavigateAwayBeforeTimeoutExpires) {
  ASSERT_TRUE(https_server_expired_.Start());
  ASSERT_TRUE(https_server_.Start());
  // Set the timer to a long delay.
  SSLErrorHandler::SetInterstitialDelayForTesting(base::Hours(1));

  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  SSLInterstitialTimerObserver interstitial_timer_observer(contents);

  ui_test_utils::NavigateToURLWithDisposition(
      browser(), https_server_expired_.GetURL("/"),
      WindowOpenDisposition::CURRENT_TAB, ui_test_utils::BROWSER_TEST_NO_WAIT);
  interstitial_timer_observer.WaitForTimerStarted();

  EXPECT_TRUE(contents->IsLoading());
  content::TestNavigationObserver observer(contents, 1);
  browser()->OpenURL(
      content::OpenURLParams(https_server_.GetURL("/"), content::Referrer(),
                             WindowOpenDisposition::CURRENT_TAB,
                             ui::PAGE_TRANSITION_TYPED, false),
      /*navigation_handle_callback=*/{});
  observer.Wait();

  // Make sure that the |SSLErrorHandler| is deleted.
  EXPECT_FALSE(SSLErrorHandler::FromWebContents(contents));
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(contents));
  EXPECT_FALSE(contents->IsLoading());

  // Navigate away, and then trigger the network time response and wait
  // for the response; no crash should occur.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), https_server_.GetURL("/")));
  ASSERT_NO_FATAL_FAILURE(CheckTimeQueryPending());
  TriggerTimeResponse();
}

// Tests that if the user closes the tab before the network time fetch
// completes, it doesn't cause a crash.
IN_PROC_BROWSER_TEST_F(SSLNetworkTimeBrowserTest,
                       CloseTabBeforeNetworkFetchCompletes) {
  ASSERT_TRUE(https_server_expired_.Start());
  // Set the timer to fire immediately.
  SSLErrorHandler::SetInterstitialDelayForTesting(base::TimeDelta());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(),
                                           https_server_expired_.GetURL("/")));
  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(contents);

  ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(contents));

  // Open a second tab, close the first, and then trigger the network time
  // response and wait for the response; no crash should occur.
  ASSERT_TRUE(https_server_.Start());
  ASSERT_FALSE(
      AddTabAtIndex(1, https_server_.GetURL("/"), ui::PAGE_TRANSITION_TYPED));
  chrome::CloseWebContents(browser(), contents, false);
  ASSERT_NO_FATAL_FAILURE(CheckTimeQueryPending());
  TriggerTimeResponse();
}

class CommonNameMismatchBrowserTest : public CertVerifierBrowserTest {
 public:
  CommonNameMismatchBrowserTest() : CertVerifierBrowserTest() {
    // Enable finch experiment for SSL common name mismatch handling.
    base::FieldTrialList::CreateFieldTrial("SSLCommonNameMismatchHandling",
                                           "Enabled");
  }

  void SetUpOnMainThread() override {
    CertVerifierBrowserTest::SetUpOnMainThread();
    host_resolver()->AddRule("*", "127.0.0.1");
  }

  void TearDownOnMainThread() override {
    CertVerifierBrowserTest::TearDownOnMainThread();
  }
};

// Visit the URL www.mail.example.com on a server that presents a valid
// certificate for mail.example.com. Verify that the page navigates to
// mail.example.com.
IN_PROC_BROWSER_TEST_F(CommonNameMismatchBrowserTest,
                       ShouldShowWWWSubdomainMismatchInterstitial) {
  net::EmbeddedTestServer https_server_example_domain(
      net::EmbeddedTestServer::TYPE_HTTPS);
  https_server_example_domain.ServeFilesFromSourceDirectory(
      GetChromeTestDataDir());
  ASSERT_TRUE(https_server_example_domain.Start());

  scoped_refptr<net::X509Certificate> cert =
      https_server_example_domain.GetCertificate();

  // Use the "spdy_pooling.pem" cert which has "mail.example.com"
  // as one of its SANs.
  net::CertVerifyResult verify_result;
  verify_result.verified_cert =
      net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
  verify_result.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;

  // Request to "www.mail.example.com" should result in
  // |net::ERR_CERT_COMMON_NAME_INVALID| error.
  mock_cert_verifier()->AddResultForCertAndHost(
      cert.get(), "www.mail.example.com", verify_result,
      net::ERR_CERT_COMMON_NAME_INVALID);

  net::CertVerifyResult verify_result_valid;
  verify_result_valid.verified_cert =
      net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
  // Request to "www.mail.example.com" should not result in any error.
  mock_cert_verifier()->AddResultForCertAndHost(cert.get(), "mail.example.com",
                                                verify_result_valid, net::OK);

  // Use a complex URL to ensure the path, etc., are preserved. The path itself
  // does not matter.
  const GURL https_server_url =
      https_server_example_domain.GetURL("/ssl/google.html?a=b#anchor");
  GURL::Replacements replacements;
  replacements.SetHostStr("www.mail.example.com");
  const GURL https_server_mismatched_url =
      https_server_url.ReplaceComponents(replacements);

  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver observer(contents, 1);
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), https_server_mismatched_url));
  observer.Wait();

  ssl_test_util::CheckSecurityState(contents, CertError::NONE,
                                    security_state::SECURE, AuthState::NONE);
  replacements.SetHostStr("mail.example.com");
  GURL https_server_new_url = https_server_url.ReplaceComponents(replacements);
  // Verify that the current URL is the suggested URL.
  EXPECT_EQ(https_server_new_url.spec(),
            contents->GetLastCommittedURL().spec());
}

// Visit the URL www.mail.example.com on a server that presents an invalid
// certificate for mail.example.com. Verify that the page shows an interstitial
// for www.mail.example.com with no crash.
IN_PROC_BROWSER_TEST_F(CommonNameMismatchBrowserTest,
                       NoCrashIfBothSubdomainsHaveCommonNameErrors) {
  net::EmbeddedTestServer https_server_example_domain(
      net::EmbeddedTestServer::TYPE_HTTPS);
  https_server_example_domain.ServeFilesFromSourceDirectory(
      GetChromeTestDataDir());
  ASSERT_TRUE(https_server_example_domain.Start());

  scoped_refptr<net::X509Certificate> cert =
      https_server_example_domain.GetCertificate();

  // Use the "spdy_pooling.pem" cert which has "mail.example.com"
  // as one of its SANs.
  net::CertVerifyResult verify_result;
  verify_result.verified_cert =
      net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
  verify_result.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;

  // Request to "www.mail.example.com" should result in
  // |net::ERR_CERT_COMMON_NAME_INVALID| error.
  mock_cert_verifier()->AddResultForCertAndHost(
      cert.get(), "www.mail.example.com", verify_result,
      net::ERR_CERT_COMMON_NAME_INVALID);

  // Request to "mail.example.com" should also result in
  // |net::ERR_CERT_COMMON_NAME_INVALID| error.
  mock_cert_verifier()->AddResultForCertAndHost(
      cert.get(), "mail.example.com", verify_result,
      net::ERR_CERT_COMMON_NAME_INVALID);

  // Use a complex URL to ensure the path, etc., are preserved. The path itself
  // does not matter.
  const GURL https_server_url =
      https_server_example_domain.GetURL("/ssl/google.html?a=b#anchor");
  GURL::Replacements replacements;
  replacements.SetHostStr("www.mail.example.com");
  const GURL https_server_mismatched_url =
      https_server_url.ReplaceComponents(replacements);

  // Should simply show an interstitial, because both subdomains have common
  // name errors.
  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), https_server_mismatched_url));
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(contents));

  ssl_test_util::CheckSecurityState(
      contents, net::CERT_STATUS_COMMON_NAME_INVALID, security_state::DANGEROUS,
      AuthState::SHOWING_INTERSTITIAL);
}

// Visit the URL example.org on a server that presents a valid certificate
// for www.example.org. Verify that the page redirects to www.example.org.
IN_PROC_BROWSER_TEST_F(CommonNameMismatchBrowserTest,
                       CheckWWWSubdomainMismatchInverse) {
  net::EmbeddedTestServer https_server_example_domain(
      net::EmbeddedTestServer::TYPE_HTTPS);
  https_server_example_domain.ServeFilesFromSourceDirectory(
      GetChromeTestDataDir());
  ASSERT_TRUE(https_server_example_domain.Start());

  scoped_refptr<net::X509Certificate> cert =
      https_server_example_domain.GetCertificate();

  net::CertVerifyResult verify_result;
  verify_result.verified_cert =
      net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
  verify_result.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;

  mock_cert_verifier()->AddResultForCertAndHost(
      cert.get(), "example.org", verify_result,
      net::ERR_CERT_COMMON_NAME_INVALID);

  net::CertVerifyResult verify_result_valid;
  verify_result_valid.verified_cert =
      net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
  mock_cert_verifier()->AddResultForCertAndHost(cert.get(), "www.example.org",
                                                verify_result_valid, net::OK);

  const GURL https_server_url =
      https_server_example_domain.GetURL("/ssl/google.html?a=b");
  GURL::Replacements replacements;
  replacements.SetHostStr("example.org");
  const GURL https_server_mismatched_url =
      https_server_url.ReplaceComponents(replacements);

  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver observer(contents, 1);
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), https_server_mismatched_url));
  observer.Wait();

  ssl_test_util::CheckSecurityState(contents, CertError::NONE,
                                    security_state::SECURE, AuthState::NONE);
}

namespace {
// Redirects incoming request to http://example.org.
std::unique_ptr<net::test_server::HttpResponse> HTTPSToHTTPRedirectHandler(
    const net::EmbeddedTestServer* test_server,
    const net::test_server::HttpRequest& request) {
  GURL::Replacements replacements;
  replacements.SetHostStr("example.org");
  replacements.SetSchemeStr("http");
  const GURL redirect_url =
      test_server->base_url().ReplaceComponents(replacements);

  std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
      new net::test_server::BasicHttpResponse);
  http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
  http_response->AddCustomHeader("Location", redirect_url.spec());
  return std::move(http_response);
}
}  // namespace

// Common name mismatch handling feature should ignore redirects when pinging
// the suggested hostname. Visit the URL example.org on a server that presents a
// valid certificate for www.example.org. In this case, www.example.org
// redirects to http://example.org, and the SSL error should not be redirected
// to this URL.
IN_PROC_BROWSER_TEST_F(CommonNameMismatchBrowserTest,
                       WWWSubdomainMismatch_StopOnRedirects) {
  net::EmbeddedTestServer https_server_example_domain(
      net::EmbeddedTestServer::TYPE_HTTPS);

  // Redirect all URLs to http://example.org. Since this test will trigger only
  // one request to check the suggested URL, redirecting all requests is OK.
  // We would normally use content::SetupCrossSiteRedirector here, but that
  // function does not support https to http redirects.
  // This must be done before ServeFilesFromSourceDirectory(), otherwise the
  // test server will serve files instead of redirecting requests to them.
  https_server_example_domain.RegisterRequestHandler(base::BindRepeating(
      &HTTPSToHTTPRedirectHandler, &https_server_example_domain));

  https_server_example_domain.ServeFilesFromSourceDirectory(
      GetChromeTestDataDir());

  ASSERT_TRUE(https_server_example_domain.Start());

  scoped_refptr<net::X509Certificate> cert =
      https_server_example_domain.GetCertificate();

  net::CertVerifyResult verify_result;
  verify_result.verified_cert =
      net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
  verify_result.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;

  mock_cert_verifier()->AddResultForCertAndHost(
      cert.get(), "example.org", verify_result,
      net::ERR_CERT_COMMON_NAME_INVALID);

  net::CertVerifyResult verify_result_valid;
  verify_result_valid.verified_cert =
      net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
  mock_cert_verifier()->AddResultForCertAndHost(cert.get(), "www.example.org",
                                                verify_result_valid, net::OK);

  // The user will visit https://example.org:port/ssl/blank.html.
  GURL::Replacements replacements;
  replacements.SetHostStr("example.org");
  const GURL https_server_mismatched_url =
      https_server_example_domain.GetURL("/ssl/blank.html")
          .ReplaceComponents(replacements);

  // Should simply show an interstitial, because the suggested URL
  // (https://www.example.org) redirected to http://example.org.
  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), https_server_mismatched_url));
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(contents));

  ssl_test_util::CheckSecurityState(
      contents, net::CERT_STATUS_COMMON_NAME_INVALID, security_state::DANGEROUS,
      AuthState::SHOWING_INTERSTITIAL);
}

// Tests this scenario:
// - |CommonNameMismatchHandler| does not give a callback as it's set into the
//   state |IGNORE_REQUESTS_FOR_TESTING|. So no suggested URL check result can
//   arrive.
// - A cert error triggers an interstitial timer with a very long timeout.
// - No suggested URL check results arrive, causing the tab to appear as loading
//   indefinitely (also because the timer has a long timeout).
// - Stopping the page load shouldn't result in any interstitials.
IN_PROC_BROWSER_TEST_F(CommonNameMismatchBrowserTest,
                       InterstitialStopNavigationWhileLoading) {
  net::EmbeddedTestServer https_server_example_domain(
      net::EmbeddedTestServer::TYPE_HTTPS);
  https_server_example_domain.ServeFilesFromSourceDirectory(
      GetChromeTestDataDir());
  ASSERT_TRUE(https_server_example_domain.Start());

  scoped_refptr<net::X509Certificate> cert =
      https_server_example_domain.GetCertificate();

  net::CertVerifyResult verify_result;
  verify_result.verified_cert =
      net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
  verify_result.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;

  mock_cert_verifier()->AddResultForCertAndHost(
      cert.get(), "www.mail.example.com", verify_result,
      net::ERR_CERT_COMMON_NAME_INVALID);

  net::CertVerifyResult verify_result_valid;
  verify_result_valid.verified_cert =
      net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
  mock_cert_verifier()->AddResultForCertAndHost(cert.get(), "mail.example.com",
                                                verify_result_valid, net::OK);

  const GURL https_server_url =
      https_server_example_domain.GetURL("/ssl/google.html?a=b");
  GURL::Replacements replacements;
  replacements.SetHostStr("www.mail.example.com");
  const GURL https_server_mismatched_url =
      https_server_url.ReplaceComponents(replacements);

  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  CommonNameMismatchHandler::set_state_for_testing(
      CommonNameMismatchHandler::IGNORE_REQUESTS_FOR_TESTING);
  // Set delay long enough so that the page appears loading.
  SSLErrorHandler::SetInterstitialDelayForTesting(base::Hours(1));
  SSLInterstitialTimerObserver interstitial_timer_observer(contents);

  ui_test_utils::NavigateToURLWithDisposition(
      browser(), https_server_mismatched_url,
      WindowOpenDisposition::CURRENT_TAB, ui_test_utils::BROWSER_TEST_NO_WAIT);
  interstitial_timer_observer.WaitForTimerStarted();

  EXPECT_TRUE(contents->IsLoading());
  contents->Stop();
  EXPECT_TRUE(content::WaitForLoadStop(contents));

  SSLErrorHandler* ssl_error_handler =
      SSLErrorHandler::FromWebContents(contents);
  // Make sure that the |SSLErrorHandler| is deleted.
  EXPECT_FALSE(ssl_error_handler);
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(contents));
  EXPECT_FALSE(contents->IsLoading());
}

// Same as above, but instead of stopping, the loading page is reloaded. The end
// result is the same. (i.e. page load stops, no interstitials shown)
IN_PROC_BROWSER_TEST_F(CommonNameMismatchBrowserTest,
                       InterstitialReloadNavigationWhileLoading) {
  net::EmbeddedTestServer https_server_example_domain(
      net::EmbeddedTestServer::TYPE_HTTPS);
  https_server_example_domain.ServeFilesFromSourceDirectory(
      GetChromeTestDataDir());
  ASSERT_TRUE(https_server_example_domain.Start());

  scoped_refptr<net::X509Certificate> cert =
      https_server_example_domain.GetCertificate();

  net::CertVerifyResult verify_result;
  verify_result.verified_cert =
      net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
  verify_result.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;

  mock_cert_verifier()->AddResultForCertAndHost(
      cert.get(), "www.mail.example.com", verify_result,
      net::ERR_CERT_COMMON_NAME_INVALID);

  net::CertVerifyResult verify_result_valid;
  verify_result_valid.verified_cert =
      net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
  mock_cert_verifier()->AddResultForCertAndHost(cert.get(), "mail.example.com",
                                                verify_result_valid, net::OK);

  const GURL https_server_url =
      https_server_example_domain.GetURL("/ssl/google.html?a=b");
  GURL::Replacements replacements;
  replacements.SetHostStr("www.mail.example.com");
  const GURL https_server_mismatched_url =
      https_server_url.ReplaceComponents(replacements);

  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  CommonNameMismatchHandler::set_state_for_testing(
      CommonNameMismatchHandler::IGNORE_REQUESTS_FOR_TESTING);
  // Set delay long enough so that the page appears loading.
  SSLErrorHandler::SetInterstitialDelayForTesting(base::Hours(1));
  SSLInterstitialTimerObserver interstitial_timer_observer(contents);

  ui_test_utils::NavigateToURLWithDisposition(
      browser(), https_server_mismatched_url,
      WindowOpenDisposition::CURRENT_TAB, ui_test_utils::BROWSER_TEST_NO_WAIT);
  interstitial_timer_observer.WaitForTimerStarted();

  EXPECT_TRUE(contents->IsLoading());
  content::TestNavigationObserver observer(contents, 1);
  chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB);
  observer.Wait();

  SSLErrorHandler* ssl_error_handler =
      SSLErrorHandler::FromWebContents(contents);
  // Make sure that the |SSLErrorHandler| is deleted.
  EXPECT_FALSE(ssl_error_handler);
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(contents));
  EXPECT_FALSE(contents->IsLoading());
}

// Same as above, but instead of reloading, the page is navigated away. The
// new page should load, and no interstitials should be shown.
IN_PROC_BROWSER_TEST_F(CommonNameMismatchBrowserTest,
                       InterstitialNavigateAwayWhileLoading) {
  net::EmbeddedTestServer https_server_example_domain(
      net::EmbeddedTestServer::TYPE_HTTPS);
  https_server_example_domain.ServeFilesFromSourceDirectory(
      GetChromeTestDataDir());
  ASSERT_TRUE(https_server_example_domain.Start());

  scoped_refptr<net::X509Certificate> cert =
      https_server_example_domain.GetCertificate();

  net::CertVerifyResult verify_result;
  verify_result.verified_cert =
      net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
  verify_result.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;

  mock_cert_verifier()->AddResultForCertAndHost(
      cert.get(), "www.mail.example.com", verify_result,
      net::ERR_CERT_COMMON_NAME_INVALID);

  net::CertVerifyResult verify_result_valid;
  verify_result_valid.verified_cert =
      net::ImportCertFromFile(net::GetTestCertsDirectory(), "spdy_pooling.pem");
  mock_cert_verifier()->AddResultForCertAndHost(cert.get(), "mail.example.com",
                                                verify_result_valid, net::OK);

  const GURL https_server_url =
      https_server_example_domain.GetURL("/ssl/google.html?a=b");
  GURL::Replacements replacements;
  replacements.SetHostStr("www.mail.example.com");
  const GURL https_server_mismatched_url =
      https_server_url.ReplaceComponents(replacements);

  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  CommonNameMismatchHandler::set_state_for_testing(
      CommonNameMismatchHandler::IGNORE_REQUESTS_FOR_TESTING);
  // Set delay long enough so that the page appears loading.
  SSLErrorHandler::SetInterstitialDelayForTesting(base::Hours(1));
  SSLInterstitialTimerObserver interstitial_timer_observer(contents);

  ui_test_utils::NavigateToURLWithDisposition(
      browser(), https_server_mismatched_url,
      WindowOpenDisposition::CURRENT_TAB, ui_test_utils::BROWSER_TEST_NO_WAIT);
  interstitial_timer_observer.WaitForTimerStarted();

  EXPECT_TRUE(contents->IsLoading());
  content::TestNavigationObserver observer(contents, 1);
  browser()->OpenURL(
      content::OpenURLParams(GURL("https://google.com"), content::Referrer(),
                             WindowOpenDisposition::CURRENT_TAB,
                             ui::PAGE_TRANSITION_TYPED, false),
      /*navigation_handle_callback=*/{});
  observer.Wait();

  SSLErrorHandler* ssl_error_handler =
      SSLErrorHandler::FromWebContents(contents);
  // Make sure that the |SSLErrorHandler| is deleted.
  EXPECT_FALSE(ssl_error_handler);
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(contents));
  EXPECT_FALSE(contents->IsLoading());
}

class SSLBlockingPageIDNTest
    : public chrome_browser_interstitials::SecurityInterstitialIDNTest {
 protected:
  // chrome_browser_interstitials::SecurityInterstitialIDNTest:
  security_interstitials::SecurityInterstitialPage* CreateInterstitial(
      WebContents* contents,
      const GURL& request_url) const override {
    net::SSLInfo ssl_info;
    ssl_info.cert =
        net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
    ChromeSecurityBlockingPageFactory blocking_page_factory;
    return blocking_page_factory
        .CreateSSLPage(contents, net::ERR_CERT_CONTAINS_ERRORS, ssl_info,
                       request_url, 0, base::Time::NowFromSystemTime(), GURL())
        .release();
  }
};

// Flaky on mac OS and Windows: https://crbug.com/41300407
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
#define MAYBE_SSLBlockingPageDecodesIDN DISABLED_SSLBlockingPageDecodesIDN
#else
#define MAYBE_SSLBlockingPageDecodesIDN SSLBlockingPageDecodesIDN
#endif

IN_PROC_BROWSER_TEST_F(SSLBlockingPageIDNTest,
                       MAYBE_SSLBlockingPageDecodesIDN) {
  EXPECT_TRUE(VerifyIDNDecoded());
}

IN_PROC_BROWSER_TEST_F(CertVerifierBrowserTest, MockCertVerifierSmokeTest) {
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.ServeFilesFromSourceDirectory(GetChromeTestDataDir());
  ASSERT_TRUE(https_server.Start());

  mock_cert_verifier()->set_default_result(
      net::ERR_CERT_NAME_CONSTRAINT_VIOLATION);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server.GetURL("/ssl/google.html")));

  ssl_test_util::CheckSecurityState(
      browser()->tab_strip_model()->GetActiveWebContents(),
      net::CERT_STATUS_NAME_CONSTRAINT_VIOLATION, security_state::DANGEROUS,
      AuthState::SHOWING_INTERSTITIAL);
}

IN_PROC_BROWSER_TEST_F(SSLUITest, RestoreHasSSLState) {
  ASSERT_TRUE(https_server_.Start());
  GURL url(https_server_.GetURL("/ssl/google.html"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);

  content::NavigationEntry* entry =
      tab->GetController().GetLastCommittedEntry();
  std::unique_ptr<content::NavigationEntry> restored_entry =
      content::NavigationController::CreateNavigationEntry(
          url, content::Referrer(), /* initiator_origin= */ std::nullopt,
          /* initiator_base_url= */ std::nullopt, ui::PAGE_TRANSITION_RELOAD,
          false, std::string(), tab->GetBrowserContext(),
          nullptr /* blob_url_loader_factory */);
  std::unique_ptr<content::NavigationEntryRestoreContext> context =
      content::NavigationEntryRestoreContext::Create();
  restored_entry->SetPageState(entry->GetPageState(), context.get());

  WebContents::CreateParams params(tab->GetBrowserContext());
  std::unique_ptr<WebContents> tab2 = WebContents::Create(params);
  WebContents* raw_tab2 = tab2.get();
  tab->GetDelegate()->AddNewContents(
      nullptr, std::move(tab2), url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
      blink::mojom::WindowFeatures(), false, nullptr);
  std::vector<std::unique_ptr<content::NavigationEntry>> entries;
  entries.push_back(std::move(restored_entry));
  content::TestNavigationObserver observer(raw_tab2);
  raw_tab2->GetController().Restore(entries.size() - 1,
                                    content::RestoreType::kRestored, &entries);
  raw_tab2->GetController().LoadIfNecessary();
  observer.Wait();
  ssl_test_util::CheckAuthenticatedState(raw_tab2, AuthState::NONE);
}

void SetupRestoredTabWithNavigation(
    net::test_server::EmbeddedTestServer* https_server,
    BrowserWindowInterface* browser) {
  ASSERT_TRUE(https_server->Start());
  GURL url(https_server->GetURL("/ssl/google.html"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser, url));
  WebContents* tab = browser->GetTabStripModel()->GetActiveWebContents();

  content::TestNavigationObserver observer(tab);
  EXPECT_TRUE(ExecJs(tab, "history.pushState({}, '', '');"));
  observer.Wait();

  ui_test_utils::NavigateToURLWithDisposition(
      browser, GURL("about:blank"), WindowOpenDisposition::NEW_BACKGROUND_TAB,
      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
  chrome::CloseTab(browser);

  WebContents* blank_tab = browser->GetTabStripModel()->GetActiveWebContents();

  // Restore the tab.
  ui_test_utils::TabAddedWaiter tab_added_waiter(browser);
  chrome::RestoreTab(browser);
  tab_added_waiter.Wait();
  tab = browser->GetTabStripModel()->GetActiveWebContents();
  EXPECT_TRUE(content::WaitForLoadStop(tab));

  EXPECT_NE(tab, blank_tab);
}

// Simulate a browser-initiated in-page navigation in a restored tab.
// https://crbug.com/41284602
IN_PROC_BROWSER_TEST_F(SSLUITest,
                       BrowserInitiatedExistingPageAfterRestoreHasSSLState) {
  SetupRestoredTabWithNavigation(&https_server_, browser());
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);

  chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
  EXPECT_TRUE(content::WaitForLoadStop(tab));
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}

// Simulate a renderer-initiated in-page navigation in a restored tab.
IN_PROC_BROWSER_TEST_F(SSLUITest,
                       RendererInitiatedExistingPageAfterRestoreHasSSLState) {
  SetupRestoredTabWithNavigation(&https_server_, browser());
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);

  content::TestNavigationObserver observer(tab);
  ASSERT_TRUE(
      content::ExecJs(tab, "location.replace(window.location.href + '#1')"));
  observer.Wait();
  EXPECT_TRUE(content::WaitForLoadStop(tab));
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}

namespace {

// A handler which changes the response. The first time it's called for
// |relative_url| it'll give an empty response. The second time it'll
// redirect to |redirect_url|.
std::unique_ptr<net::test_server::HttpResponse> ChangingHandler(
    int* count,
    const std::string& relative_url,
    const GURL& redirect_url,
    const net::test_server::HttpRequest& request) {
  if (request.relative_url != relative_url) {
    return nullptr;
  }

  std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
      new net::test_server::BasicHttpResponse);
  if ((*count)++) {
    http_response->set_code(net::HTTP_MOVED_PERMANENTLY);
    http_response->AddCustomHeader("Location", redirect_url.spec());
  }
  return std::move(http_response);
}

}  // namespace

// Check that SSL state isn't stale when navigating to an existing page that
// gives a different response. This covers the case of going from http to
// https. http://crbug.com/40089799
IN_PROC_BROWSER_TEST_F(SSLUITest, ExistingPageHTTPToHTTPSSSLState) {
  ASSERT_TRUE(https_server_.Start());
  int count = 0;
  std::string relative_url = "/foo";
  GURL redirect_url = https_server_.GetURL("/simple.html");
  embedded_test_server()->RegisterRequestHandler(
      base::BindRepeating(ChangingHandler, &count, relative_url, redirect_url));
  ASSERT_TRUE(embedded_test_server()->Start());
  const GURL url = embedded_test_server()->GetURL(relative_url);

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);

  content::TestNavigationObserver observer(tab, 1);
  chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB);
  observer.Wait();

  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}

// Check that SSL state isn't stale when navigating to an existing page that
// gives a different response. This covers the case of going from https to
// http URL. http://crbug.com/40089799
IN_PROC_BROWSER_TEST_F(SSLUITest, ExistingPageHTTPSToHTTPSSLState) {
  ASSERT_TRUE(embedded_test_server()->Start());
  int count = 0;
  std::string relative_url = "/foo";
  GURL redirect_url = embedded_test_server()->GetURL("/simple.html");
  https_server_.RegisterRequestHandler(
      base::BindRepeating(ChangingHandler, &count, relative_url, redirect_url));
  ASSERT_TRUE(https_server_.Start());
  const GURL url = https_server_.GetURL(relative_url);

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);

  content::TestNavigationObserver observer(tab, 1);
  chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB);
  observer.Wait();

  ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);

  // We also manually check the cert on the NavigationEntry, since in the case
  // of http URLs GetSecurityLevelForRequest will return SecurityLevel::NONE for
  // http URLs.
  content::NavigationEntry* entry = tab->GetController().GetVisibleEntry();
  ASSERT_FALSE(entry->GetSSL().certificate);
}

// Checks that a restore followed immediately by a history navigation doesn't
// lose SSL state.
// Disabled since this is a test for bug 738177.
IN_PROC_BROWSER_TEST_F(SSLUITest, DISABLED_RestoreThenNavigateHasSSLState) {
  ASSERT_TRUE(https_server_.Start());
  GURL url1(https_server_.GetURL("/ssl/google.html"));
  GURL url2(https_server_.GetURL("/ssl/page_with_refs.html"));
  ui_test_utils::NavigateToURLWithDisposition(
      browser(), url1, WindowOpenDisposition::NEW_FOREGROUND_TAB,
      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url2));
  chrome::CloseTab(browser());

  ui_test_utils::TabAddedWaiter tab_added_waiter(browser());
  chrome::RestoreTab(browser());
  tab_added_waiter.Wait();

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationManager observer(tab, url1);
  chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
  ASSERT_TRUE(observer.WaitForNavigationFinished());
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}

// Simulate the URL changing when the user presses enter in the omnibox. This
// could happen when the user's login is expired and the server redirects them
// to a login page. This will be considered a same document navigation but we
// do want to update the SSL state.
IN_PROC_BROWSER_TEST_F(SSLUITest, SameDocumentHasSSLState) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  // Navigate to a simple page and then perform an in-page navigation.
  GURL start_url(embedded_test_server()->GetURL("/title1.html"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), start_url));

  GURL fragment_change_url(embedded_test_server()->GetURL("/title1.html#foo"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), fragment_change_url));
  ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);

  // Replace the URL of the current NavigationEntry with one that will cause
  // a server redirect when loaded.
  {
    GURL redirect_dest_url(https_server_.GetURL("/ssl/google.html"));
    content::TestNavigationObserver observer(tab);
    std::string script = "history.replaceState({}, '', '/server-redirect?" +
                         redirect_dest_url.spec() + "')";
    EXPECT_TRUE(ExecJs(tab, script));
    observer.Wait();
  }

  // Simulate the user hitting Enter in the omnibox without changing the URL.
  {
    content::TestNavigationObserver observer(tab);
    tab->GetController().LoadURL(tab->GetLastCommittedURL(),
                                 content::Referrer(), ui::PAGE_TRANSITION_LINK,
                                 std::string());
    observer.Wait();
  }

  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}

// Simulate the user revisiting a page without triggering a reload (e.g., when
// clicking a bookmark with an anchor hash twice).  As this is a same document
// navigation, the SSL state should be left intact despite not triggering a
// network request. Regression test for https://crbug.com/41409992.
IN_PROC_BROWSER_TEST_F(SSLUITest, SameDocumentHasSSLStateNoLoad) {
  ASSERT_TRUE(https_server_.Start());

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  GURL start_url(https_server_.GetURL("/ssl/google.html#foo"));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), start_url));

  // Simulate clicking on a bookmark.
  {
    content::LoadStopObserver observer(tab);
    NavigateParams navigate_params(browser(), start_url,
                                   ui::PAGE_TRANSITION_AUTO_BOOKMARK);
    Navigate(&navigate_params);
    observer.Wait();
  }

  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}

// Checks that if a client redirect occurs while the page is loading, the SSL
// state reflects the final URL.
IN_PROC_BROWSER_TEST_F(SSLUITest, ClientRedirectSSLState) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  GURL https_url = https_server_.GetURL("/ssl/redirect.html?");
  GURL http_url = embedded_test_server()->GetURL("/ssl/google.html");
  GURL url(https_url.spec() + http_url.spec());

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationManager navigation_observer_https(tab, url);
  content::TestNavigationManager navigation_observer_http(tab, http_url);

  tab->GetController().LoadURL(url, content::Referrer(),
                               ui::PAGE_TRANSITION_LINK, std::string());

  ASSERT_TRUE(navigation_observer_https.WaitForNavigationFinished());
  ASSERT_TRUE(navigation_observer_http.WaitForNavigationFinished());
  EXPECT_TRUE(content::WaitForLoadStop(tab));

  ssl_test_util::CheckUnauthenticatedState(tab, AuthState::NONE);
}

// Checks that if a redirect occurs while the page is loading from a mixed
// content to a valid HTTPS page, the SSL state reflects the final URL.
IN_PROC_BROWSER_TEST_F(SSLUITest, ClientRedirectFromMixedContentSSLState) {
  ASSERT_TRUE(https_server_.Start());

  GURL url = GURL(
      https_server_.GetURL("/ssl/redirect_with_mixed_content.html").spec() +
      "?" + https_server_.GetURL("/ssl/google.html").spec());

  // Load a page that displays insecure content.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}

// Checks that if a redirect occurs while the page is loading from a valid HTTPS
// page to a mixed content page, the SSL state reflects the final URL.
IN_PROC_BROWSER_TEST_F(SSLUITest, ClientRedirectToMixedContentSSLState) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  GURL redirect(https_server_.GetURL("/ssl/redirect.html"));
  GURL final_url(
      https_server_.GetURL("/ssl/page_displays_insecure_content.html"));
  GURL url = GURL(redirect.spec() + "?" + final_url.spec());

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  content::TestNavigationManager navigation_manager_redirect(tab, url);
  content::TestNavigationManager navigation_manager_final_url(tab, final_url);

  tab->GetController().LoadURL(url, content::Referrer(),
                               ui::PAGE_TRANSITION_LINK, std::string());

  ASSERT_TRUE(navigation_manager_redirect.WaitForNavigationFinished());
  ASSERT_TRUE(navigation_manager_final_url.WaitForNavigationFinished());
  EXPECT_TRUE(content::WaitForLoadStop(tab));

  ssl_test_util::CheckSecurityState(tab, CertError::NONE,
                                    security_state::WARNING,
                                    AuthState::DISPLAYED_INSECURE_CONTENT);
}

// Checks that same-document navigations during page load preserve SSL state.
IN_PROC_BROWSER_TEST_F(SSLUITest, SameDocumentNavigationDuringLoadSSLState) {
  ASSERT_TRUE(https_server_.Start());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      https_server_.GetURL("/ssl/same_document_navigation_during_load.html")));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}

// Checks that same-document navigations after the page load preserve SSL
// state.
IN_PROC_BROWSER_TEST_F(SSLUITest, SameDocumentNavigationAfterLoadSSLState) {
  ASSERT_TRUE(https_server_.Start());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/ssl/google.html")));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(content::ExecJs(tab, "location.hash = Math.random()"));
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}

// Checks that navigations after pushState maintain the SSL status.
// Flaky, see https://crbug.com/41406728 and https://crbug.com/41406729.
IN_PROC_BROWSER_TEST_F(SSLUITest, DISABLED_PushStateSSLState) {
  ASSERT_TRUE(https_server_.Start());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/ssl/google.html")));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);

  content::TestNavigationObserver observer(tab);
  EXPECT_TRUE(ExecJs(tab, "history.pushState({}, '', '');"));
  observer.Wait();
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);

  chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
  EXPECT_TRUE(content::WaitForLoadStop(tab));
  ssl_test_util::CheckAuthenticatedState(tab, AuthState::NONE);
}

namespace {

// A request handler returns different HTTP response codes
// to simulate server responses in browser tests.
std::unique_ptr<net::test_server::HttpResponse> SimpleHttpResponseCodeHandler(
    const net::test_server::HttpRequest& request) {
  auto response = std::make_unique<net::test_server::BasicHttpResponse>();
  if (request.relative_url == "/code-200") {
    response->set_code(net::HTTP_OK);
    response->set_content("HTTP OK");
    response->set_content_type("text/html");
  } else if (request.relative_url == "/code-500") {
    response->set_code(net::HTTP_INTERNAL_SERVER_ERROR);
    response->AddCustomHeader("Content-Length", "0");
  } else {
    response->set_code(net::HTTP_NOT_FOUND);
    response->AddCustomHeader("Content-Length", "0");
  }
  return response;
}

}  // namespace

// Checks that when navigating to the following scenarios with various HTTP
// response codes on a server with a valid SSL certificate, the SSL state
// (certificate) remains present.
IN_PROC_BROWSER_TEST_F(SSLUITest, SSLStateOnDifferentHttpResponses) {
  https_server_.RegisterRequestHandler(
      base::BindRepeating(&SimpleHttpResponseCodeHandler));
  ASSERT_TRUE(https_server_.Start());

  // 1) Navigating to a page that returns HTTP 200.
  // The SSL certificate state should remain present.
  GURL success_url = https_server_.GetURL("/code-200");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), success_url));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  EXPECT_TRUE(chrome_security_state::GetVisibleSecurityState(tab)->certificate);

  content::NavigationEntry* entry = tab->GetController().GetVisibleEntry();
  ASSERT_TRUE(entry);
  EXPECT_TRUE(entry->GetSSL().certificate);

  // 2) Navigating to a page that returns HTTP 500 with empty content.
  // The browser shows a default error page for the HTTP 500 response.
  // The SSL certificate state should still remain present.
  GURL error_url = https_server_.GetURL("/code-500");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), error_url));

  tab = browser()->tab_strip_model()->GetActiveWebContents();
  EXPECT_TRUE(chrome_security_state::GetVisibleSecurityState(tab)->certificate);

  entry = tab->GetController().GetVisibleEntry();
  ASSERT_TRUE(entry);
  EXPECT_TRUE(entry->GetSSL().certificate);

  // 3) Navigating to a page that returns HTTP 404 with empty content.
  // The browser shows a default error page for the HTTP 404 response.
  // The SSL certificate state should still remain present.
  GURL not_found_url = https_server_.GetURL("/not-found");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), not_found_url));
  tab = browser()->tab_strip_model()->GetActiveWebContents();
  EXPECT_TRUE(chrome_security_state::GetVisibleSecurityState(tab)->certificate);

  entry = tab->GetController().GetVisibleEntry();
  ASSERT_TRUE(entry);
  EXPECT_TRUE(entry->GetSSL().certificate);
}

// Regression test for http://crbug.com/40479807 (crash when a window with no
// NavigationEntry commits).
IN_PROC_BROWSER_TEST_F(SSLUITestIgnoreLocalhostCertErrors,
                       NoCrashOnLoadWithNoNavigationEntry) {
  ASSERT_TRUE(embedded_test_server()->Start());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/ssl/google.html")));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(content::ExecJs(tab, "window.open()"));
}

std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig>
MakeCaptivePortalConfig(int version_id,
                        const std::set<std::string>& spki_hashes) {
  auto config_proto =
      std::make_unique<chrome_browser_ssl::SSLErrorAssistantConfig>();
  config_proto->set_version_id(version_id);
  for (const std::string& hash : spki_hashes) {
    config_proto->add_captive_portal_cert()->set_sha256_hash(hash);
  }
  return config_proto;
}

// Tests the scenario where the OS reports a captive portal. A captive portal
// interstitial should be displayed. The test then switches OS captive portal
// status to false and reloads the page. This time, a normal SSL interstitial
// will be displayed.
IN_PROC_BROWSER_TEST_F(SSLUITest, OSReportsCaptivePortal) {
  ASSERT_TRUE(https_server_mismatched_.Start());
  base::HistogramTester histograms;
  bool netwok_connectivity_reported = false;
  SSLErrorHandler::SetOSReportsCaptivePortalForTesting(true);
  SSLErrorHandler::SetReportNetworkConnectivityCallbackForTesting(
      base::BindLambdaForTesting([&]() {
        SSLErrorHandler::SetOSReportsCaptivePortalForTesting(false);
        netwok_connectivity_reported = true;
      }));

  // Navigate to an unsafe page on the server.
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  SSLInterstitialTimerObserver interstitial_timer_observer(tab);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_mismatched_.GetURL("/ssl/blank_page.html")));

  ASSERT_TRUE(
      chrome_browser_interstitials::IsShowingCaptivePortalInterstitial(tab));
  EXPECT_FALSE(interstitial_timer_observer.timer_started());

  // Check that the histogram for the captive portal cert was recorded.
  histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 3);
  histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
                               SSLErrorHandler::HANDLE_ALL, 1);
  histograms.ExpectBucketCount(
      SSLErrorHandler::GetHistogramNameForTesting(),
      SSLErrorHandler::SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE, 1);
  histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
                               SSLErrorHandler::OS_REPORTS_CAPTIVE_PORTAL, 1);

  // Reload the URL. This time the OS should not report a captive portal.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_mismatched_.GetURL("/ssl/blank_page.html")));
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
  EXPECT_TRUE(netwok_connectivity_reported);
}

// Tests that the committed interstitial flag triggers the code path to show an
// error PageType instead of an interstitial PageType.
IN_PROC_BROWSER_TEST_F(SSLUITest, ErrorPage) {
  ASSERT_TRUE(https_server_expired_.Start());
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));

  ssl_test_util::CheckSecurityState(tab, net::CERT_STATUS_DATE_INVALID,
                                    security_state::DANGEROUS,
                                    AuthState::SHOWING_ERROR);

  content::NavigationEntry* entry = tab->GetController().GetVisibleEntry();
  EXPECT_EQ(content::PAGE_TYPE_ERROR, entry->GetPageType());
}

using security_interstitials::InsecureFormNavigationThrottle;

// Visits a page that displays an insecure form inside an iframe, attempts to
// submit the form, and checks an interstitial is not shown (submissions of
// mixed forms inside iframes are separately blocked, and that behavior is
// tested in mixed_content_navigation_throttle_unittest.cc).
IN_PROC_BROWSER_TEST_F(
    SSLUITest,
    TestDoesNotDisplayInsecureFormSubmissionWarningInIframe) {
  ChromeContentBrowserClientForMixedContentTest browser_client;
  browser_client.SetMixedContentSettings(
      false, /* allow_running_insecure_content */
      false, /* strict_mixed_content_checking */
      false /*strictly_block_blockable_mixed_content */);
  content::ScopedContentBrowserClientSetting setting(&browser_client);

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  tab->OnWebPreferencesChanged();
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_displays_insecure_form_in_iframe.html",
      https_server_.host_port_pair());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(replacement_path)));
  content::TestNavigationObserver nav_observer(tab, 1);
  content::WebContentsConsoleObserver console_observer(tab);
  console_observer.SetPattern(
      "Mixed Content: The page at * was loaded over a secure connection, but "
      "contains a form that targets an insecure endpoint "
      "'http://does-not-exist.test/ssl/google_files/logo.gif'. This endpoint "
      "should be made available over a secure connection.");
  ASSERT_TRUE(content::ExecJs(tab, "submitForm();"));
  nav_observer.Wait();

  // We shouldn't be displaying an interstitial.
  security_interstitials::SecurityInterstitialTabHelper* helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          tab);
  EXPECT_FALSE(helper);

  // Check console message was printed.
  EXPECT_EQ(console_observer.messages().size(), 1u);
}

// Checks insecure form warning works for forms that submit on a new tab.
IN_PROC_BROWSER_TEST_F(SSLUITest,
                       TestDisplaysInsecureFormSubmissionWarningTargetBlank) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_displays_insecure_form_target_blank.html",
      embedded_test_server()->host_port_pair());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(replacement_path)));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver nav_observer(tab, 1);
  nav_observer.StartWatchingNewWebContents();
  ASSERT_TRUE(content::ExecJs(tab, "submitForm();"));
  nav_observer.Wait();
  tab = browser()->tab_strip_model()->GetActiveWebContents();
  security_interstitials::SecurityInterstitialTabHelper* helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          tab);
  EXPECT_TRUE(helper->IsDisplayingInterstitial());
  EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
                ->GetTypeForTesting(),
            security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
}

// Checks reloading the interstitial is not treated as proceeding on a POST
// form.
IN_PROC_BROWSER_TEST_F(SSLUITest,
                       TestReloadInsecureFormSubmissionWarningIsNotProceed) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_displays_insecure_form.html",
      embedded_test_server()->host_port_pair());

  // Navigate to an insecure form, make sure we get a warning.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(replacement_path)));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver nav_observer(tab, 1);
  ASSERT_TRUE(content::ExecJs(tab, "submitForm();"));
  nav_observer.Wait();
  security_interstitials::SecurityInterstitialTabHelper* helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          tab);
  EXPECT_TRUE(helper->IsDisplayingInterstitial());
  EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
                ->GetTypeForTesting(),
            security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
  // Reload the interstitial.
  content::TestNavigationObserver reload_observer(tab, 1);
  tab->GetController().Reload(content::ReloadType::NORMAL, false);
  reload_observer.Wait();
  // Check we get another interstitial.
  helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          tab);
  EXPECT_TRUE(helper->IsDisplayingInterstitial());
  EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
                ->GetTypeForTesting(),
            security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
}

// Checks reloading the interstitial is not treated as proceeding on a GET form.
IN_PROC_BROWSER_TEST_F(
    SSLUITest,
    TestReloadInsecureFormSubmissionWarningIsNotProceedGetForm) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_displays_insecure_form_get.html",
      embedded_test_server()->host_port_pair());

  // Navigate to an insecure form that uses the GET method, make sure we get a
  // warning.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(replacement_path)));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver nav_observer(tab, 1);
  ASSERT_TRUE(content::ExecJs(tab, "submitForm();"));
  nav_observer.Wait();
  security_interstitials::SecurityInterstitialTabHelper* helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          tab);
  EXPECT_TRUE(helper->IsDisplayingInterstitial());
  EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
                ->GetTypeForTesting(),
            security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
  // Reload the interstitial.
  content::TestNavigationObserver reload_observer(tab, 1);
  tab->GetController().Reload(content::ReloadType::NORMAL, false);
  reload_observer.Wait();
  // Check we get another interstitial.
  helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          tab);
  EXPECT_TRUE(helper->IsDisplayingInterstitial());
  EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
                ->GetTypeForTesting(),
            security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
}

// Checks navigating back and forward from the interstitial is not treated as
// proceeding on a GET form.
IN_PROC_BROWSER_TEST_F(
    SSLUITest,
    TestBackForwardOnInsecureFormSubmissionWarningIsNotProceedGetForm) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_displays_insecure_form_get.html",
      embedded_test_server()->host_port_pair());

  // Navigate to an insecure form that uses the GET method, make sure we get a
  // warning.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(replacement_path)));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver nav_observer(tab, 1);
  ASSERT_TRUE(content::ExecJs(tab, "submitForm();"));
  nav_observer.Wait();
  security_interstitials::SecurityInterstitialTabHelper* helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          tab);
  EXPECT_TRUE(helper->IsDisplayingInterstitial());
  EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
                ->GetTypeForTesting(),
            security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
  // Navigate back, then forward.
  content::TestNavigationObserver back_observer(tab, 1);
  tab->GetController().GoBack();
  back_observer.Wait();
  content::TestNavigationObserver forward_observer(tab, 1);
  tab->GetController().GoForward();
  forward_observer.Wait();
  // Check we get another interstitial.
  helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          tab);
  // Currently the interstitial is only displayed when back/forward cache is
  // enabled, so return early when the feature is disabled.
  // TODO(crbug.com/40243001): Fix this.
  if (!content::BackForwardCache::IsBackForwardCacheFeatureEnabled()) {
    EXPECT_FALSE(helper->IsDisplayingInterstitial());
    return;
  }
  EXPECT_TRUE(helper->IsDisplayingInterstitial());
  EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
                ->GetTypeForTesting(),
            security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
}

// Check proceed works correctly on insecure form warning.
IN_PROC_BROWSER_TEST_F(SSLUITest, ProceedThroughInsecureFormWarning) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_displays_insecure_form.html",
      embedded_test_server()->host_port_pair());
  GURL form_target_url("http://does-not-exist.test/ssl/google_files/logo.gif");

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(replacement_path)));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver nav_observer(tab, 1);
  ASSERT_TRUE(content::ExecJs(tab, "submitForm();"));
  nav_observer.Wait();
  security_interstitials::SecurityInterstitialTabHelper* helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          tab);
  EXPECT_TRUE(helper->IsDisplayingInterstitial());
  EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
                ->GetTypeForTesting(),
            security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
  // After clicking Proceed, we should not be on an interstitial, and be
  // on the form target url;
  ProceedThroughInterstitial(tab);
  EXPECT_FALSE(helper->IsDisplayingInterstitial());
  EXPECT_EQ(tab->GetVisibleURL(), form_target_url);
}

// Check don't proceed works correctly on insecure form warning.
IN_PROC_BROWSER_TEST_F(SSLUITest, GoBackFromInsecureFormWarning) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_displays_insecure_form.html",
      embedded_test_server()->host_port_pair());
  GURL form_site_url = https_server_.GetURL(replacement_path);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), form_site_url));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver nav_observer(tab, 1);
  ASSERT_TRUE(content::ExecJs(tab, "submitForm();"));
  nav_observer.Wait();
  security_interstitials::SecurityInterstitialTabHelper* helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          tab);
  EXPECT_TRUE(helper->IsDisplayingInterstitial());
  EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
                ->GetTypeForTesting(),
            security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
  // After clicking Don't Proceed, we should not be on an interstitial, and be
  // back on the site containing the insecure form.
  DontProceedThroughInterstitial(tab);
  EXPECT_FALSE(helper->IsDisplayingInterstitial());
  EXPECT_EQ(tab->GetVisibleURL(), form_site_url);
}

// Checks mixed form warnings work correctly for non-redirects.
IN_PROC_BROWSER_TEST_F(SSLUITest, TestDisplaysInsecureFormSubmissionWarning) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_displays_insecure_form.html",
      embedded_test_server()->host_port_pair());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(replacement_path)));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver nav_observer(tab, 1);
  ASSERT_TRUE(content::ExecJs(tab, "submitForm();"));
  nav_observer.Wait();
  security_interstitials::SecurityInterstitialTabHelper* helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          tab);
  EXPECT_TRUE(helper->IsDisplayingInterstitial());
  EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
                ->GetTypeForTesting(),
            security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
}

// Checks interstitial is shown for mixed forms caused by a 307 POST http
// redirect, and that metrics are logged.
IN_PROC_BROWSER_TEST_F(SSLUITest,
                       TestDisplaysInsecureFormSubmissionWarningRedirect) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_displays_form_redirects_insecure.html",
      embedded_test_server()->host_port_pair());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(replacement_path)));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver nav_observer(tab, 1);
  ASSERT_TRUE(content::ExecJs(tab, "submitForm();"));
  nav_observer.Wait();
  security_interstitials::SecurityInterstitialTabHelper* helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          tab);
  EXPECT_TRUE(helper->IsDisplayingInterstitial());
  EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
                ->GetTypeForTesting(),
            security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
}

// Checks interstitial is shown for mixed forms caused by a 308 POST http
// redirect, and that metrics are logged.
IN_PROC_BROWSER_TEST_F(SSLUITest,
                       TestDisplaysInsecureFormSubmissionWarningRedirect308) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_displays_form_redirects_308_insecure.html",
      embedded_test_server()->host_port_pair());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(replacement_path)));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver nav_observer(tab, 1);
  ASSERT_TRUE(content::ExecJs(tab, "submitForm();"));
  nav_observer.Wait();
  security_interstitials::SecurityInterstitialTabHelper* helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          tab);
  EXPECT_TRUE(helper->IsDisplayingInterstitial());
  EXPECT_EQ(helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting()
                ->GetTypeForTesting(),
            security_interstitials::InsecureFormBlockingPage::kTypeForTesting);
}

// Checks no interstitial is shown for mixed forms caused for a POST form with a
// 301 redirect, and that metrics are logged correctly.
IN_PROC_BROWSER_TEST_F(SSLUITest,
                       TestDisplaysInsecureFormSubmissionWarningRedirect301) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  // This test posts to does-not-exist.test. Disable HTTPS upgrades on this
  // hostname for the test to work.
  // TODO(crbug.com/40248833): Remove the allowlist entry.
  ScopedAllowHttpForHostnamesForTesting scoped_allow_http(
      {"does-not-exist.test"}, browser()->GetProfile()->GetPrefs());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_displays_form_redirects_301_insecure.html",
      embedded_test_server()->host_port_pair());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(replacement_path)));

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver nav_observer(tab, 1);
  ASSERT_TRUE(content::ExecJs(tab, "submitForm();"));
  nav_observer.Wait();

  security_interstitials::SecurityInterstitialTabHelper* helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          tab);
  // There should have been no interstitial triggered.
  EXPECT_FALSE(helper);
}

// Checks no interstitial is shown for mixed forms caused for a POST form with a
// 302 redirect, and that metrics are logged correctly.
IN_PROC_BROWSER_TEST_F(SSLUITest,
                       TestDisplaysInsecureFormSubmissionWarningRedirect302) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  // This test posts to does-not-exist.test. Disable HTTPS upgrades on this
  // hostname for the test to work.
  // TODO(crbug.com/40248833): Remove the allowlist entry.
  ScopedAllowHttpForHostnamesForTesting scoped_allow_http(
      {"does-not-exist.test"}, browser()->GetProfile()->GetPrefs());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_displays_form_redirects_302_insecure.html",
      embedded_test_server()->host_port_pair());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(replacement_path)));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver nav_observer(tab, 1);
  ASSERT_TRUE(content::ExecJs(tab, "submitForm();"));
  nav_observer.Wait();
  security_interstitials::SecurityInterstitialTabHelper* helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          tab);
  // There should have been no interstitial triggered.
  EXPECT_FALSE(helper);
}

namespace {
// Redirects (with 307 code) requests with a redirect_to_http path to
// http://example.org. This custom handler is required for tests that include
// GET method forms to the redirect URL, since the built in /server-redirect-307
// handler takes the redirect-to URL as a query parameter, so it is not usable
// for GET method forms.
std::unique_ptr<net::test_server::HttpResponse> FormActionHTTPRedirectHandler(
    const net::EmbeddedTestServer* test_server,
    const net::test_server::HttpRequest& request) {
  GURL absolute_url = test_server->GetURL(request.relative_url);
  if (absolute_url.GetPath() != "/redirect_to_http") {
    return nullptr;
  }
  GURL::Replacements replacements;
  replacements.SetHostStr("example.org");
  replacements.SetSchemeStr("http");
  const GURL redirect_url =
      test_server->base_url().ReplaceComponents(replacements);

  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", redirect_url.spec());
  return std::move(http_response);
}
}  // namespace

// Checks no interstitial is shown for mixed forms caused for a GET form with
// a 307 redirect to http, and that metrics are logged correctly.
IN_PROC_BROWSER_TEST_F(SSLUITest,
                       TestDisplaysInsecureFormSubmissionWarningRedirectGet) {
  ASSERT_TRUE(embedded_test_server()->Start());
  https_server_.RegisterRequestHandler(
      base::BindRepeating(&FormActionHTTPRedirectHandler, &https_server_));
  ASSERT_TRUE(https_server_.Start());

  // This test redirects to example.org. Disable HTTPS upgrades on this
  // hostname for the test to work.
  // TODO(crbug.com/40248833): Remove the allowlist entry.
  ScopedAllowHttpForHostnamesForTesting scoped_allow_http(
      {"example.org"}, browser()->GetProfile()->GetPrefs());

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_displays_form_redirects_insecure_get.html",
      embedded_test_server()->host_port_pair());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(replacement_path)));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver nav_observer(tab, 1);
  ASSERT_TRUE(content::ExecJs(tab, "submitForm();"));
  nav_observer.Wait();
  security_interstitials::SecurityInterstitialTabHelper* helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          tab);
  // There should have been no interstitial triggered.
  EXPECT_FALSE(helper);
}

class MixedFormsPolicyTest : public policy::PolicyTest {};

// Check no warning is shown if disabled by policy.
IN_PROC_BROWSER_TEST_F(MixedFormsPolicyTest, NoWarningOptOutPolicy) {
  ASSERT_TRUE(embedded_test_server()->Start());
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.AddDefaultHandlers(GetChromeTestDataDir());
  ASSERT_TRUE(https_server.Start());

  // Check pref is set to true by default.
  EXPECT_TRUE(browser()->GetProfile()->GetPrefs()->GetBoolean(
      prefs::kMixedFormsWarningsEnabled));
  // Set policy to disable mixed form warnings.
  policy::PolicyMap policies;
  policies.Set(policy::key::kInsecureFormsWarningsEnabled,
               policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
               policy::POLICY_SOURCE_CLOUD, base::Value(false), nullptr);
  UpdateProviderPolicy(policies);
  // Pref should now be set to false.
  EXPECT_FALSE(browser()->GetProfile()->GetPrefs()->GetBoolean(
      prefs::kMixedFormsWarningsEnabled));

  std::string replacement_path =
      SSLUITestBase::GetFilePathWithHostAndPortReplacement(
          "/ssl/page_displays_insecure_form.html",
          embedded_test_server()->host_port_pair());
  GURL form_site_url = https_server.GetURL(replacement_path);
  GURL form_target_url("http://does-not-exist.test/ssl/google_files/logo.gif");

  // Navigate to site with insecure form and submit it.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), form_site_url));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver nav_observer(tab, 1);
  ASSERT_TRUE(content::ExecJs(tab, "submitForm();"));
  nav_observer.Wait();

  // No interstitial should be shown, and we should be in the form action URL.
  security_interstitials::SecurityInterstitialTabHelper* helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          tab);
  EXPECT_TRUE(!helper || !helper->IsDisplayingInterstitial());
  EXPECT_EQ(tab->GetVisibleURL(), form_target_url);
}

namespace {

char kTestMITMSoftwareName[] = "Misconfigured Firewall";
char16_t kTestMITMSoftwareName16[] = u"Misconfigured Firewall";

class SSLUIMITMSoftwareTest : public CertVerifierBrowserTest {
 public:
  SSLUIMITMSoftwareTest()
      : CertVerifierBrowserTest(),
        https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}

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

  ~SSLUIMITMSoftwareTest() override = default;

  void SetUpOnMainThread() override {
    CertVerifierBrowserTest::SetUpOnMainThread();
    host_resolver()->AddRule("*", "127.0.0.1");
    ssl_test_util::SetHSTSForHostName(browser()->GetProfile(),
                                      kHstsTestHostName);
  }

  // Set up the cert verifier to return the error passed in as the cert_error
  // parameter.
  void SetUpCertVerifier(net::CertStatus cert_error) {
    net::CertVerifyResult verify_result;
    verify_result.verified_cert =
        net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
    ASSERT_TRUE(verify_result.verified_cert);

    verify_result.cert_status = cert_error;
    mock_cert_verifier()->AddResultForCert(
        https_server()->GetCertificate().get(), verify_result,
        net::MapCertStatusToNetError(cert_error));
  }

  // Sets up an SSLErrorAssistantProto that lists |https_server_|'s default
  // certificate as a MITM software certificate.
  void SetUpMITMSoftwareCertList(uint32_t version_id) {
    auto config_proto =
        std::make_unique<chrome_browser_ssl::SSLErrorAssistantConfig>();
    config_proto->set_version_id(version_id);

    chrome_browser_ssl::MITMSoftware* mitm_software =
        config_proto->add_mitm_software();
    mitm_software->set_name(kTestMITMSoftwareName);
    mitm_software->set_issuer_common_name_regex(
        https_server()->GetCertificate().get()->issuer().common_name);
    mitm_software->set_issuer_organization_regex(
        https_server()->GetCertificate().get()->issuer().organization_names[0]);
    SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
    ASSERT_TRUE(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting() >
                0);
  }

  // Returns a URL which triggers an interstitial with the host name that has
  // HSTS set.
  GURL GetHSTSTestURL() const {
    GURL::Replacements replacements;
    replacements.SetHostStr(kHstsTestHostName);
    return https_server()
        ->GetURL("/ssl/blank_page.html")
        .ReplaceComponents(replacements);
  }

  void TestMITMSoftwareInterstitial() {
    base::HistogramTester histograms;
    ASSERT_TRUE(https_server()->Start());
    ASSERT_TRUE(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting() >
                0);

    // Navigate to an unsafe page on the server. Mock out the URL host name to
    // equal the one set for HSTS.
    WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
    SSLInterstitialTimerObserver interstitial_timer_observer(tab);
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetHSTSTestURL()));
    ASSERT_TRUE(chrome_browser_interstitials::IsShowingMITMInterstitial(tab));
    EXPECT_FALSE(interstitial_timer_observer.timer_started());

    // Check that the histograms for the MITM software interstitial were
    // recorded.
    histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(),
                                2);
    histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
                                 SSLErrorHandler::HANDLE_ALL, 1);
    histograms.ExpectBucketCount(
        SSLErrorHandler::GetHistogramNameForTesting(),
        SSLErrorHandler::SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE, 0);
    histograms.ExpectBucketCount(
        SSLErrorHandler::GetHistogramNameForTesting(),
        SSLErrorHandler::SHOW_MITM_SOFTWARE_INTERSTITIAL, 1);
  }

  void TestNoMITMSoftwareInterstitial() {
    base::HistogramTester histograms;
    ASSERT_TRUE(https_server()->Start());
    ASSERT_TRUE(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting() >
                0);

    // Navigate to an unsafe page on the server. Mock out the URL host name to
    // equal the one set for HSTS.
    WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
    SSLInterstitialTimerObserver interstitial_timer_observer(tab);
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetHSTSTestURL()));
    ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
    EXPECT_TRUE(interstitial_timer_observer.timer_started());

    // Check that a MITM software interstitial was not recorded in histogram.
    histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(),
                                2);
    histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
                                 SSLErrorHandler::HANDLE_ALL, 1);
    histograms.ExpectBucketCount(
        SSLErrorHandler::GetHistogramNameForTesting(),
        SSLErrorHandler::SHOW_SSL_INTERSTITIAL_OVERRIDABLE, 0);
    histograms.ExpectBucketCount(
        SSLErrorHandler::GetHistogramNameForTesting(),
        SSLErrorHandler::SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE, 1);
    histograms.ExpectBucketCount(
        SSLErrorHandler::GetHistogramNameForTesting(),
        SSLErrorHandler::SHOW_MITM_SOFTWARE_INTERSTITIAL, 0);
  }

  // Returns the https server. Guaranteed to be non-NULL.
  const net::EmbeddedTestServer* https_server() const { return &https_server_; }
  net::EmbeddedTestServer* https_server() { return &https_server_; }

 private:
  net::EmbeddedTestServer https_server_;
};

// The SSLUIMITMSoftwareEnabled and Disabled test classes exist so that the
// scoped feature list can be instantiated in the set up method of the class
// rather than in the test itself. Bug crbug.com/40516444 was causing some of
// the tests in SSLUIMITMSoftwareTest to be flaky. Refactoring these tests so
// that the scoped feature list initialization is done in the set up method
// fixes this flakiness.

class SSLUIMITMSoftwareEnabledTest : public SSLUIMITMSoftwareTest {
 public:
  SSLUIMITMSoftwareEnabledTest() {
    scoped_feature_list_.InitWithFeatures(
        {kMITMSoftwareInterstitial} /* enabled */, {} /* disabled */);
  }

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

  ~SSLUIMITMSoftwareEnabledTest() override = default;

  void SetUpOnMainThread() override {
    SSLUIMITMSoftwareTest::SetUpOnMainThread();
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

class SSLUIMITMSoftwareDisabledTest : public SSLUIMITMSoftwareTest {
 public:
  SSLUIMITMSoftwareDisabledTest() {
    scoped_feature_list_.InitWithFeatures(
        {} /* enabled */, {kMITMSoftwareInterstitial} /* disabled */);
  }

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

  ~SSLUIMITMSoftwareDisabledTest() override = default;

  void SetUpOnMainThread() override {
    SSLUIMITMSoftwareTest::SetUpOnMainThread();
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

}  // namespace

// Tests that the MITM software interstitial is not displayed when the feature
// is disabled by Finch.
IN_PROC_BROWSER_TEST_F(SSLUIMITMSoftwareDisabledTest, DisabledWithFinch) {
  SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID);
  SetUpMITMSoftwareCertList(kLargeVersionId);
  TestNoMITMSoftwareInterstitial();
}

// Tests that the MITM software interstitial is displayed when the feature is
// enabled by Finch.
IN_PROC_BROWSER_TEST_F(SSLUIMITMSoftwareEnabledTest, EnabledWithFinch) {
  SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID);
  SetUpMITMSoftwareCertList(kLargeVersionId);
  TestMITMSoftwareInterstitial();
}

// Tests that if a certificates matches the common name of a known MITM software
// cert on the list but not the organization name, the MITM software
// interstitial will not be displayed.
IN_PROC_BROWSER_TEST_F(
    SSLUIMITMSoftwareEnabledTest,
    CertificateCommonNameMatchOnly_NoMITMSoftwareInterstitial) {
  base::HistogramTester histograms;

  SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID);
  ASSERT_TRUE(https_server()->Start());

  // Set up an error assistant proto with a list of MITM software regexed that
  // the certificate issued by our server won't match.
  auto config_proto =
      std::make_unique<chrome_browser_ssl::SSLErrorAssistantConfig>();
  config_proto->set_version_id(kLargeVersionId);
  chrome_browser_ssl::MITMSoftware* mitm_software =
      config_proto->add_mitm_software();
  mitm_software->set_name(kTestMITMSoftwareName);
  mitm_software->set_issuer_common_name_regex(
      https_server()->GetCertificate().get()->issuer().common_name);
  mitm_software->set_issuer_organization_regex(
      "pattern-that-does-not-match-anything");
  SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
  ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
            kLargeVersionId);

  // Navigate to an unsafe page on the server. Mock out the URL host name to
  // equal the one set for HSTS.
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  SSLInterstitialTimerObserver interstitial_timer_observer(tab);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetHSTSTestURL()));
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
  EXPECT_TRUE(interstitial_timer_observer.timer_started());

  // Check that a MITM software interstitial was not recorded in histogram.
  histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 2);
  histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
                               SSLErrorHandler::HANDLE_ALL, 1);
  histograms.ExpectBucketCount(
      SSLErrorHandler::GetHistogramNameForTesting(),
      SSLErrorHandler::SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE, 1);
  histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
                               SSLErrorHandler::SHOW_MITM_SOFTWARE_INTERSTITIAL,
                               0);
}

// Tests that if a certificates matches the organization name of a known MITM
// software cert on the list but not the common name, the MITM software
// interstitial will not be displayed.
IN_PROC_BROWSER_TEST_F(
    SSLUIMITMSoftwareEnabledTest,
    CertificateOrganizationMatchOnly_NoMITMSoftwareInterstitial) {
  base::HistogramTester histograms;

  SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID);
  ASSERT_TRUE(https_server()->Start());

  // Set up an error assistant proto with a list of MITM software regexed that
  // the certificate issued by our server won't match.
  auto config_proto =
      std::make_unique<chrome_browser_ssl::SSLErrorAssistantConfig>();
  config_proto->set_version_id(kLargeVersionId);
  chrome_browser_ssl::MITMSoftware* mitm_software =
      config_proto->add_mitm_software();
  mitm_software->set_name(kTestMITMSoftwareName);
  mitm_software->set_issuer_common_name_regex(
      "pattern-that-does-not-match-anything");
  mitm_software->set_issuer_organization_regex(
      https_server()->GetCertificate().get()->issuer().organization_names[0]);
  SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
  ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
            kLargeVersionId);

  // Navigate to an unsafe page on the server. Mock out the URL host name to
  // equal the one set for HSTS.
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  SSLInterstitialTimerObserver interstitial_timer_observer(tab);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetHSTSTestURL()));
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
  EXPECT_TRUE(interstitial_timer_observer.timer_started());

  // Check that a MITM software interstitial was not recorded in histogram.
  histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 2);
  histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
                               SSLErrorHandler::HANDLE_ALL, 1);
  histograms.ExpectBucketCount(
      SSLErrorHandler::GetHistogramNameForTesting(),
      SSLErrorHandler::SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE, 1);
  histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
                               SSLErrorHandler::SHOW_MITM_SOFTWARE_INTERSTITIAL,
                               0);
}

// Tests that if the certificate does not match any entry on the list of known
// MITM software, the MITM software interstitial will not be displayed.
IN_PROC_BROWSER_TEST_F(SSLUIMITMSoftwareEnabledTest,
                       NonMatchingCertificate_NoMITMSoftwareInterstitial) {
  base::HistogramTester histograms;

  SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID);
  ASSERT_TRUE(https_server()->Start());

  // Set up an error assistant proto with a list of MITM software regexes that
  // the certificate issued by our server won't match.
  auto config_proto =
      std::make_unique<chrome_browser_ssl::SSLErrorAssistantConfig>();
  config_proto->set_version_id(kLargeVersionId);
  chrome_browser_ssl::MITMSoftware* mitm_software =
      config_proto->add_mitm_software();
  mitm_software->set_name("Non-Matching MITM Software");
  mitm_software->set_issuer_common_name_regex(
      "pattern-that-does-not-match-anything");
  mitm_software->set_issuer_organization_regex(
      "pattern-that-does-not-match-anything");
  SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
  ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
            kLargeVersionId);

  // Navigate to an unsafe page on the server. Mock out the URL host name to
  // equal the one set for HSTS.
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  SSLInterstitialTimerObserver interstitial_timer_observer(tab);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetHSTSTestURL()));
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
  EXPECT_TRUE(interstitial_timer_observer.timer_started());

  // Check that a MITM software interstitial was not recorded in histogram.
  histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 2);
  histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
                               SSLErrorHandler::HANDLE_ALL, 1);
  histograms.ExpectBucketCount(
      SSLErrorHandler::GetHistogramNameForTesting(),
      SSLErrorHandler::SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE, 1);
  histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
                               SSLErrorHandler::SHOW_MITM_SOFTWARE_INTERSTITIAL,
                               0);
}

// Tests that if there is more than one error on the certificate the MITM
// software interstitial will not be displayed.
IN_PROC_BROWSER_TEST_F(SSLUIMITMSoftwareEnabledTest,
                       TwoCertErrors_NoMITMSoftwareInterstitial) {
  SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID |
                    net::CERT_STATUS_COMMON_NAME_INVALID);
  SetUpMITMSoftwareCertList(kLargeVersionId);
  TestNoMITMSoftwareInterstitial();
}

// Tests that a certificate error other than |CERT_STATUS_AUTHORITY_INVALID|
// will not trigger the MITM software interstitial.
IN_PROC_BROWSER_TEST_F(SSLUIMITMSoftwareEnabledTest,
                       WrongCertError_NoMITMSoftwareInterstitial) {
  SetUpCertVerifier(net::CERT_STATUS_COMMON_NAME_INVALID);
  SetUpMITMSoftwareCertList(kLargeVersionId);
  TestNoMITMSoftwareInterstitial();
}

// Tests that if the error on the certificate served is overridable the MITM
// software interstitial will not be displayed.
IN_PROC_BROWSER_TEST_F(SSLUIMITMSoftwareEnabledTest,
                       OverridableError_NoMITMSoftwareInterstitial) {
  base::HistogramTester histograms;

  SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID);

  ASSERT_TRUE(https_server()->Start());
  SetUpMITMSoftwareCertList(kLargeVersionId);

  // Navigate to an unsafe page to trigger an interstitial, but don't replace
  // the host name with the one set for HSTS.
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  SSLInterstitialTimerObserver interstitial_timer_observer(tab);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server()->GetURL("/ssl/blank_page.html")));
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(tab));
  EXPECT_TRUE(interstitial_timer_observer.timer_started());

  // Check that the histogram for an overridable SSL interstitial was
  // recorded.
  histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 2);
  histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
                               SSLErrorHandler::HANDLE_ALL, 1);
  histograms.ExpectBucketCount(
      SSLErrorHandler::GetHistogramNameForTesting(),
      SSLErrorHandler::SHOW_SSL_INTERSTITIAL_OVERRIDABLE, 1);
  histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
                               SSLErrorHandler::SHOW_MITM_SOFTWARE_INTERSTITIAL,
                               0);
}

// Tests that the correct strings are displayed on the interstitial in the
// enterprise managed case.
IN_PROC_BROWSER_TEST_F(SSLUIMITMSoftwareEnabledTest, EnterpriseManaged) {
  SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID);
  ChromeSecurityBlockingPageFactory::SetEnterpriseManagedForTesting(true);

  SetUpMITMSoftwareCertList(kLargeVersionId);
  TestMITMSoftwareInterstitial();

  const std::string expected_primary_paragraph =
      l10n_util::GetStringFUTF8(IDS_MITM_SOFTWARE_PRIMARY_PARAGRAPH_ENTERPRISE,
                                base::EscapeForHTML(kTestMITMSoftwareName16));
  const std::string expected_explanation = l10n_util::GetStringFUTF8(
      IDS_MITM_SOFTWARE_EXPLANATION_ENTERPRISE,
      base::EscapeForHTML(kTestMITMSoftwareName16),
      l10n_util::GetStringUTF16(IDS_MITM_SOFTWARE_EXPLANATION));

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  EXPECT_TRUE(chrome_browser_interstitials::IsInterstitialDisplayingText(
      tab->GetPrimaryMainFrame(), expected_explanation));
  EXPECT_TRUE(chrome_browser_interstitials::IsInterstitialDisplayingText(
      tab->GetPrimaryMainFrame(), expected_primary_paragraph));
}

// Tests that the correct strings are displayed on the interstitial in the
// non-enterprise managed case.
IN_PROC_BROWSER_TEST_F(SSLUIMITMSoftwareEnabledTest, NotEnterpriseManaged) {
  SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID);
  ChromeSecurityBlockingPageFactory::SetEnterpriseManagedForTesting(false);

  SetUpMITMSoftwareCertList(kLargeVersionId);
  TestMITMSoftwareInterstitial();

  // Don't check the primary paragraph in the non-enterprise case, because it
  // has escaped HTML characters which throw an error.
  const std::string expected_explanation = l10n_util::GetStringFUTF8(
      IDS_MITM_SOFTWARE_EXPLANATION_NONENTERPRISE,
      base::EscapeForHTML(kTestMITMSoftwareName16),
      l10n_util::GetStringUTF16(IDS_MITM_SOFTWARE_EXPLANATION));

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  EXPECT_TRUE(chrome_browser_interstitials::IsInterstitialDisplayingText(
      tab->GetPrimaryMainFrame(), expected_explanation));
}

// Initialize MITMSoftware certificate list but set the version_id to zero. This
// less than the version_id of the local resource bundle, so the dynamic
// update will be ignored and a non-MITM interstitial will be shown.
IN_PROC_BROWSER_TEST_F(SSLUIMITMSoftwareEnabledTest,
                       IgnoreDynamicUpdateWithSmallVersionId) {
  auto config_proto =
      SSLErrorAssistant::GetErrorAssistantProtoFromResourceBundle();
  SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));

  SetUpCertVerifier(net::CERT_STATUS_AUTHORITY_INVALID);
  ChromeSecurityBlockingPageFactory::SetEnterpriseManagedForTesting(false);

  SetUpMITMSoftwareCertList(0u);
  TestNoMITMSoftwareInterstitial();
}

// Checks that SimpleURLLoader, which uses services/network/url_loader.cc, goes
// through the new NetworkServiceClient interface to deliver cert error
// notifications to the browser which then overrides the certificate error.
IN_PROC_BROWSER_TEST_F(SSLUITest, SimpleURLLoaderCertError) {
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_NO_FATAL_FAILURE(SetUpUnsafeContentsWithUserException(
      "/ssl/page_with_unsafe_contents.html"));
  ssl_test_util::CheckAuthenticationBrokenState(tab, CertError::NONE,
                                                AuthState::NONE);

  EXPECT_EQ(net::OK,
            content::LoadBasicRequest(
                tab->GetPrimaryMainFrame(),
                https_server_mismatched_.GetURL("/anchor_download_test.png")));
}

IN_PROC_BROWSER_TEST_F(SSLUITest, NetworkErrorDoesntRevokeExemptions) {
  ASSERT_TRUE(https_server_expired_.Start());
  GURL expired_url = https_server_expired_.GetURL("/title1.html");
  int server_port = expired_url.IntPort();

  // Navigate to the expired cert URL, make sure we get an interstitial.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), expired_url));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  EXPECT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));

  // Click through the interstitial.
  ProceedThroughInterstitial(tab);

  // Shut down the server and navigate again to cause a network error.
  ASSERT_TRUE(https_server_expired_.ShutdownAndWaitUntilComplete());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), expired_url));

  // Create a new server in the same url (including port), the certificate
  // should still be invalid.
  net::EmbeddedTestServer new_https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  new_https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED);
  new_https_server.AddDefaultHandlers(GetChromeTestDataDir());
  ASSERT_TRUE(new_https_server.Start(server_port));

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

  // We shouldn't get an interstitial this time.
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(tab));
}

// Checks we don't attempt to show an interstitial (or crash) when visiting an
// SSL error related page in chrome://network-errors. Regression test for
// crbug.com/41453481
IN_PROC_BROWSER_TEST_F(SSLUITest, NoInterstitialOnNetworkErrorPage) {
  GURL invalid_cert_url(blink::kChromeUINetworkErrorURL);
  GURL::Replacements replacements;
  replacements.SetPathStr("-207");
  invalid_cert_url = invalid_cert_url.ReplaceComponents(replacements);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), invalid_cert_url));
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(
      browser()->tab_strip_model()->GetActiveWebContents()));
}

IN_PROC_BROWSER_TEST_F(SSLUITest, OpenHelpCenterInNewTab) {
  ASSERT_TRUE(https_server_expired_.Start());
  WebContents* interstitial_tab =
      browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));
  ASSERT_TRUE(
      chrome_browser_interstitials::IsShowingSSLInterstitial(interstitial_tab));

  EXPECT_EQ(1, browser()->tab_strip_model()->count());

  content::TestNavigationObserver nav_observer(nullptr);
  nav_observer.StartWatchingNewWebContents();
  SendInterstitialCommand(
      interstitial_tab,
      security_interstitials::CMD_OPEN_HELP_CENTER_IN_NEW_TAB);
  nav_observer.Wait();

  EXPECT_EQ(2, browser()->tab_strip_model()->count());
  WebContents* new_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(new_tab);
  EXPECT_NE(new_tab, interstitial_tab);
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(new_tab));
}

IN_PROC_BROWSER_TEST_F(SSLUITest, OpenReportingPrivacyInNewTab) {
  ASSERT_TRUE(https_server_expired_.Start());
  WebContents* interstitial_tab =
      browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));
  ASSERT_TRUE(
      chrome_browser_interstitials::IsShowingSSLInterstitial(interstitial_tab));

  EXPECT_EQ(1, browser()->tab_strip_model()->count());

  content::TestNavigationObserver nav_observer(nullptr);
  nav_observer.StartWatchingNewWebContents();
  SendInterstitialCommand(
      interstitial_tab,
      security_interstitials::CMD_OPEN_REPORTING_PRIVACY_IN_NEW_TAB);
  nav_observer.Wait();

  EXPECT_EQ(2, browser()->tab_strip_model()->count());
  WebContents* new_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(new_tab);
  EXPECT_NE(new_tab, interstitial_tab);
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(new_tab));
}

IN_PROC_BROWSER_TEST_F(SSLUITest, OpenWhitepaperInNewTab) {
  ASSERT_TRUE(https_server_expired_.Start());
  WebContents* interstitial_tab =
      browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));
  ASSERT_TRUE(
      chrome_browser_interstitials::IsShowingSSLInterstitial(interstitial_tab));

  EXPECT_EQ(1, browser()->tab_strip_model()->count());

  content::TestNavigationObserver nav_observer(nullptr);
  nav_observer.StartWatchingNewWebContents();
  SendInterstitialCommand(
      interstitial_tab, security_interstitials::CMD_OPEN_WHITEPAPER_IN_NEW_TAB);
  nav_observer.Wait();

  EXPECT_EQ(2, browser()->tab_strip_model()->count());
  WebContents* new_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(new_tab);
  EXPECT_NE(new_tab, interstitial_tab);
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(new_tab));
}

IN_PROC_BROWSER_TEST_F(SSLUITest, ShowCertificateViewer) {
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  net::EmbeddedTestServer::ServerCertificateConfig cert_config;
  cert_config.root = net::EmbeddedTestServer::RootType::kUniqueRoot;
  https_server.SetSSLConfig(cert_config);
  ASSERT_TRUE(https_server.Start());

  base::HistogramTester histograms;
  static constexpr std::string_view interaction_histogram =
      "interstitial.ssl_overridable.interaction";

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server.GetURL("/ssl/google.html")));

  WebContents* interstitial_tab =
      browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(
      chrome_browser_interstitials::IsShowingSSLInterstitial(interstitial_tab));
  EXPECT_EQ(1, browser()->tab_strip_model()->count());

  base::test::TestFuture<std::vector<bssl::UniquePtr<CRYPTO_BUFFER>>,
                         content::WebContents*>
      future;
  CertificateViewerDialog::MockForTesting(future.GetRepeatingCallback());

  // The "view certificate" link is part of the details paragraph that isn't
  // visible until the details are expanded. (Technically finding the element
  // in the DOM and calling .click() on it doesn't actually require the element
  // to be visible, so doing this isn't strictly necessary, but it does verify
  // more of the expected behavior.)
  EXPECT_EQ(false, content::EvalJs(interstitial_tab,
                                   "document.querySelector(\"#view-certificate-"
                                   "link\").checkVisibility()"));

  EXPECT_EQ(true,
            content::EvalJs(
                interstitial_tab,
                "document.querySelector(\"#details-button\").click(); true"));

  EXPECT_EQ(true, content::EvalJs(interstitial_tab,
                                  "document.querySelector(\"#view-certificate-"
                                  "link\").checkVisibility()"));

  ASSERT_EQ(
      true,
      content::EvalJs(
          interstitial_tab,
          "document.querySelector(\"#view-certificate-link\").click(); true"));

  auto [cert_buffers, web_contents] = future.Take();
  EXPECT_EQ(1U, cert_buffers.size());
  EXPECT_TRUE(net::x509_util::CryptoBufferEqual(
      cert_buffers[0].get(), https_server.GetCertificate()->cert_buffer()));
  EXPECT_EQ(web_contents, interstitial_tab);
  histograms.ExpectBucketCount(
      interaction_histogram,
      security_interstitials::MetricsHelper::TOTAL_VISITS, 1);
  histograms.ExpectBucketCount(
      interaction_histogram,
      security_interstitials::MetricsHelper::VIEW_CERTIFICATE, 1);
}

// This SPKI hash is from a self signed certificate generated using the
// following openssl command:
//  openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes
//  openssl x509 -noout -in certificate.pem -pubkey | \
//  openssl asn1parse -noout -inform pem -out public.key;
//  openssl dgst -sha256 -binary public.key | openssl enc -base64
// The actual value of the hash doesn't matter as long it's a valid SPKI hash.
const char kMatchingDynamicInterstitialCert[] =
    "sha256/eFi0afYJLdI0YsZFu4U8ra2B5/5ynzfKkI88M94iVFA=";

namespace {

class SSLUIDynamicInterstitialTest : public CertVerifierBrowserTest {
 public:
  SSLUIDynamicInterstitialTest()
      : CertVerifierBrowserTest(),
        https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}

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

  ~SSLUIDynamicInterstitialTest() override = default;

  void SetUpCertVerifier() {
    scoped_refptr<net::X509Certificate> cert(https_server_.GetCertificate());
    net::CertVerifyResult verify_result;
    verify_result.verified_cert = cert;
    verify_result.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;

    std::optional<net::HashValue> hash =
        net::HashValue::FromString(kMatchingDynamicInterstitialCert);
    ASSERT_TRUE(hash.has_value());
    verify_result.public_key_hashes.push_back(hash->sha256hashvalue());

    mock_cert_verifier()->AddResultForCert(cert, verify_result,
                                           net::ERR_CERT_COMMON_NAME_INVALID);
  }

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

  // Creates and returns a SSLErrorAssistantConfig object.
  std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig>
  CreateSSLErrorAssistantConfig() {
    auto config_proto =
        std::make_unique<chrome_browser_ssl::SSLErrorAssistantConfig>();
    config_proto->set_version_id(kLargeVersionId);
    return config_proto;
  }

  // Returns a valid sha256 hashvalue string which does not match
  // kMatchingDynamicInterstitialCert.
  static std::string MakeSha256String(uint8_t i) {
    net::SHA256HashValue value;
    value.fill(i);
    return net::HashValue(net::HASH_VALUE_SHA256, value).ToString();
  }

  // Adds a dynamic interstitial to |config_proto|. All of the dynamic
  // interstitial's fields mismatch with |https_server_|'s SSL info.
  void AddMismatchDynamicInterstitial(
      chrome_browser_ssl::SSLErrorAssistantConfig* config_proto) {
    chrome_browser_ssl::DynamicInterstitial* filter =
        config_proto->add_dynamic_interstitial();
    filter->set_interstitial_type(
        chrome_browser_ssl::DynamicInterstitial::INTERSTITIAL_PAGE_SSL);
    filter->set_cert_error(
        chrome_browser_ssl::DynamicInterstitial::ERR_CERT_DATE_INVALID);

    filter->add_sha256_hash(MakeSha256String(1));
    filter->add_sha256_hash(MakeSha256String(2));

    filter->set_issuer_common_name_regex("beeeater");
    filter->set_issuer_organization_regex("honeycreeper");
    filter->set_mitm_software_name(kTestMITMSoftwareName);
  }

  // Adds a dynamic interstitial to |config_proto| and returns it. All of the
  // fields in the dynamic intersitial matches with |https_server_|'s
  // SSL info. Optionally set the flag for triggering dynamic interstitials
  // only on non-overridable errors.
  chrome_browser_ssl::DynamicInterstitial* AddMatchingDynamicInterstitial(
      chrome_browser_ssl::SSLErrorAssistantConfig* config_proto,
      bool show_only_for_nonoverridable_errors = false) {
    chrome_browser_ssl::DynamicInterstitial* filter =
        config_proto->add_dynamic_interstitial();
    filter->set_interstitial_type(chrome_browser_ssl::DynamicInterstitial::
                                      INTERSTITIAL_PAGE_CAPTIVE_PORTAL);
    filter->set_cert_error(
        chrome_browser_ssl::DynamicInterstitial::ERR_CERT_COMMON_NAME_INVALID);

    filter->add_sha256_hash(MakeSha256String(3));
    filter->add_sha256_hash(kMatchingDynamicInterstitialCert);
    filter->add_sha256_hash(MakeSha256String(4));

    scoped_refptr<net::X509Certificate> cert = https_server_.GetCertificate();
    filter->set_issuer_common_name_regex(cert.get()->issuer().common_name);

    if (!cert.get()->issuer().organization_names.empty()) {
      filter->set_issuer_organization_regex(
          cert.get()->issuer().organization_names[0]);
    }

    filter->set_mitm_software_name(kTestMITMSoftwareName);

    filter->set_support_url("https://google.com");

    filter->set_show_only_for_nonoverridable_errors(
        show_only_for_nonoverridable_errors);

    return filter;
  }

  security_interstitials::SecurityInterstitialPage* GetInterstitialDelegate(
      WebContents* tab) {
    security_interstitials::SecurityInterstitialTabHelper* helper =
        security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
            tab);
    if (!helper) {
      return nullptr;
    }
    return helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting();
  }

 private:
  net::EmbeddedTestServer https_server_;
};

}  // namespace

// Tests that the dynamic interstitial list is used when the feature is
// enabled via Finch. The list is passed to SSLErrorHandler via a proto.
IN_PROC_BROWSER_TEST_F(SSLUIDynamicInterstitialTest, Match) {
  ASSERT_TRUE(https_server()->Start());

  SetUpCertVerifier();

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  {
    std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> config_proto =
        CreateSSLErrorAssistantConfig();
    config_proto->set_version_id(kLargeVersionId);
    AddMismatchDynamicInterstitial(config_proto.get());
    AddMatchingDynamicInterstitial(config_proto.get());

    SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
    ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
              kLargeVersionId);

    ASSERT_TRUE(
        ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/")));
    ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));

    security_interstitials::SecurityInterstitialPage* interstitial_page =
        GetInterstitialDelegate(tab);
    ASSERT_TRUE(interstitial_page);
    ASSERT_EQ(CaptivePortalBlockingPage::kTypeForTesting,
              interstitial_page->GetTypeForTesting());
  }
}

IN_PROC_BROWSER_TEST_F(SSLUIDynamicInterstitialTest, MatchUnknownCertError) {
  ASSERT_TRUE(https_server()->Start());

  SetUpCertVerifier();

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  {
    std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> config_proto =
        CreateSSLErrorAssistantConfig();
    config_proto->set_version_id(kLargeVersionId);
    AddMismatchDynamicInterstitial(config_proto.get());

    // Add a matching dynamic interstitial with the UNKNOWN_CERT_ERROR status.
    chrome_browser_ssl::DynamicInterstitial* match =
        AddMatchingDynamicInterstitial(config_proto.get());
    match->set_cert_error(
        chrome_browser_ssl::DynamicInterstitial::UNKNOWN_CERT_ERROR);

    SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
    ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
              kLargeVersionId);

    ASSERT_TRUE(
        ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/")));
    ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));

    security_interstitials::SecurityInterstitialPage* interstitial_page =
        GetInterstitialDelegate(tab);
    ASSERT_TRUE(interstitial_page);
    ASSERT_EQ(CaptivePortalBlockingPage::kTypeForTesting,
              interstitial_page->GetTypeForTesting());
  }
}

IN_PROC_BROWSER_TEST_F(SSLUIDynamicInterstitialTest,
                       MatchEmptyCommonNameRegex) {
  ASSERT_TRUE(https_server()->Start());

  SetUpCertVerifier();

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  {
    std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> config_proto =
        CreateSSLErrorAssistantConfig();
    config_proto->set_version_id(kLargeVersionId);
    AddMismatchDynamicInterstitial(config_proto.get());

    // Add a matching dynamic interstitial with an empty issuer common name
    // regex.
    chrome_browser_ssl::DynamicInterstitial* match =
        AddMatchingDynamicInterstitial(config_proto.get());
    match->set_issuer_common_name_regex(std::string());

    SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
    ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
              kLargeVersionId);

    ASSERT_TRUE(
        ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/")));
    ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));

    security_interstitials::SecurityInterstitialPage* interstitial_page =
        GetInterstitialDelegate(tab);
    ASSERT_TRUE(interstitial_page);
    ASSERT_EQ(CaptivePortalBlockingPage::kTypeForTesting,
              interstitial_page->GetTypeForTesting());
  }
}

IN_PROC_BROWSER_TEST_F(SSLUIDynamicInterstitialTest,
                       MatchEmptyOrganizationRegex) {
  ASSERT_TRUE(https_server()->Start());

  SetUpCertVerifier();

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  {
    std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> config_proto =
        CreateSSLErrorAssistantConfig();
    config_proto->set_version_id(kLargeVersionId);
    AddMismatchDynamicInterstitial(config_proto.get());

    // Add a matching dynamic interstitial with an empty issuer organization
    // name regex.
    chrome_browser_ssl::DynamicInterstitial* match =
        AddMatchingDynamicInterstitial(config_proto.get());
    match->set_issuer_organization_regex(std::string());

    SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
    ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
              kLargeVersionId);

    ASSERT_TRUE(
        ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/")));
    ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));

    security_interstitials::SecurityInterstitialPage* interstitial_page =
        GetInterstitialDelegate(tab);
    ASSERT_TRUE(interstitial_page);
    ASSERT_EQ(CaptivePortalBlockingPage::kTypeForTesting,
              interstitial_page->GetTypeForTesting());
  }
}

IN_PROC_BROWSER_TEST_F(SSLUIDynamicInterstitialTest, MismatchHash) {
  ASSERT_TRUE(https_server()->Start());

  SetUpCertVerifier();

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  {
    std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> config_proto =
        CreateSSLErrorAssistantConfig();
    config_proto->set_version_id(kLargeVersionId);
    AddMismatchDynamicInterstitial(config_proto.get());

    // Add a dynamic interstitial with matching fields, except for the
    // certificate hashes.
    chrome_browser_ssl::DynamicInterstitial* match =
        AddMatchingDynamicInterstitial(config_proto.get());
    match->clear_sha256_hash();
    match->add_sha256_hash(MakeSha256String(5));
    match->add_sha256_hash(MakeSha256String(6));

    SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
    ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
              kLargeVersionId);

    ASSERT_TRUE(
        ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/")));
    ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));

    security_interstitials::SecurityInterstitialPage* interstitial_page =
        GetInterstitialDelegate(tab);
    ASSERT_TRUE(interstitial_page);
    EXPECT_NE(CaptivePortalBlockingPage::kTypeForTesting,
              interstitial_page->GetTypeForTesting());
  }
}

IN_PROC_BROWSER_TEST_F(SSLUIDynamicInterstitialTest, MismatchCertError) {
  ASSERT_TRUE(https_server()->Start());

  SetUpCertVerifier();

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  {
    std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> config_proto =
        CreateSSLErrorAssistantConfig();
    config_proto->set_version_id(kLargeVersionId);
    AddMismatchDynamicInterstitial(config_proto.get());

    // Add a dynamic interstitial with matching fields, except for the
    // cert error field.
    chrome_browser_ssl::DynamicInterstitial* match =
        AddMatchingDynamicInterstitial(config_proto.get());
    match->set_cert_error(
        chrome_browser_ssl::DynamicInterstitial::ERR_CERT_DATE_INVALID);

    SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
    ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
              kLargeVersionId);

    ASSERT_TRUE(
        ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/")));
    ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));

    security_interstitials::SecurityInterstitialPage* interstitial_page =
        GetInterstitialDelegate(tab);
    ASSERT_TRUE(interstitial_page);
    EXPECT_NE(CaptivePortalBlockingPage::kTypeForTesting,
              interstitial_page->GetTypeForTesting());
  }
}

IN_PROC_BROWSER_TEST_F(SSLUIDynamicInterstitialTest, MismatchCommonNameRegex) {
  ASSERT_TRUE(https_server()->Start());

  SetUpCertVerifier();

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  {
    std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> config_proto =
        CreateSSLErrorAssistantConfig();
    config_proto->set_version_id(kLargeVersionId);
    AddMismatchDynamicInterstitial(config_proto.get());

    // Add a dynamic interstitial with matching fields, except for the
    // issuer common name regex field.
    chrome_browser_ssl::DynamicInterstitial* match =
        AddMatchingDynamicInterstitial(config_proto.get());
    match->set_issuer_common_name_regex("beeeater");

    SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
    ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
              kLargeVersionId);

    ASSERT_TRUE(
        ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/")));
    ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));

    security_interstitials::SecurityInterstitialPage* interstitial_page =
        GetInterstitialDelegate(tab);
    ASSERT_TRUE(interstitial_page);
    EXPECT_NE(CaptivePortalBlockingPage::kTypeForTesting,
              interstitial_page->GetTypeForTesting());
  }
}

IN_PROC_BROWSER_TEST_F(SSLUIDynamicInterstitialTest,
                       MismatchOrganizationRegex) {
  ASSERT_TRUE(https_server()->Start());

  SetUpCertVerifier();

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  {
    std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> config_proto =
        CreateSSLErrorAssistantConfig();
    config_proto->set_version_id(kLargeVersionId);
    AddMismatchDynamicInterstitial(config_proto.get());

    // Add a dynamic interstitial with matching fields, except for the
    // issuer organization name regex field.
    chrome_browser_ssl::DynamicInterstitial* match =
        AddMatchingDynamicInterstitial(config_proto.get());
    match->set_issuer_organization_regex("honeycreeper");

    SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
    ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
              kLargeVersionId);

    ASSERT_TRUE(
        ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/")));
    ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));

    security_interstitials::SecurityInterstitialPage* interstitial_page =
        GetInterstitialDelegate(tab);
    ASSERT_TRUE(interstitial_page);
    EXPECT_NE(CaptivePortalBlockingPage::kTypeForTesting,
              interstitial_page->GetTypeForTesting());
  }
}

IN_PROC_BROWSER_TEST_F(SSLUIDynamicInterstitialTest, MismatchWhenOverridable) {
  ASSERT_TRUE(https_server()->Start());

  SetUpCertVerifier();

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  {
    std::unique_ptr<chrome_browser_ssl::SSLErrorAssistantConfig> config_proto =
        CreateSSLErrorAssistantConfig();
    config_proto->set_version_id(kLargeVersionId);
    AddMismatchDynamicInterstitial(config_proto.get());

    // Add a matching dynamic interstitial, except for the
    // show_only_for_nonoverridable_errors flag is set to true.
    chrome_browser_ssl::DynamicInterstitial* match =
        AddMatchingDynamicInterstitial(config_proto.get(), true);
    match->set_cert_error(
        chrome_browser_ssl::DynamicInterstitial::UNKNOWN_CERT_ERROR);

    SSLErrorHandler::SetErrorAssistantProto(std::move(config_proto));
    ASSERT_EQ(SSLErrorHandler::GetErrorAssistantProtoVersionIdForTesting(),
              kLargeVersionId);

    ASSERT_TRUE(
        ui_test_utils::NavigateToURL(browser(), https_server()->GetURL("/")));
    ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));

    security_interstitials::SecurityInterstitialPage* interstitial_page =
        GetInterstitialDelegate(tab);
    ASSERT_TRUE(interstitial_page);
    EXPECT_NE(CaptivePortalBlockingPage::kTypeForTesting,
              interstitial_page->GetTypeForTesting());
  }
}

// Tests that mixed content is tracked by origin hostname, not by URL. This is
// tested by checking that mixed content flags are set appropriately for
// about:blank URLs (who inherit the origin of their opener).
//
// Note: we test that mixed content flags are propagated from an opener page to
// about:blank, but not the other way around. This is because there is no way
// for a mixed content flag to propagate from about:blank to a different
// tab. Passive mixed content flags are not propagated from one tab to another,
// and for active mixed content, there's no way to bypass mixed content blocking
// on about:blank pages, so there's no way that the origin would get flagged for
// active mixed content from an about:blank page. (There's no way to bypass
// mixed content blocking on about:blank pages because the bypass is implemented
// as a content setting, which doesn't apply to about:blank.)
IN_PROC_BROWSER_TEST_F(SSLUITest, ActiveMixedContentTrackedByOrigin) {
  ASSERT_TRUE(embedded_test_server()->Start());
  ASSERT_TRUE(https_server_.Start());

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();

  std::string replacement_path = GetFilePathWithHostAndPortReplacement(
      "/ssl/page_runs_insecure_content.html",
      embedded_test_server()->host_port_pair());

  // The insecure script is allowed to load because SSLUITestBase sets the
  // --allow-running-insecure-content flag.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL(replacement_path)));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, CertError::NONE, AuthState::RAN_INSECURE_CONTENT);

  // Open a new tab from the current page. After an initial navigation,
  // navigate it to about:blank and check that the about:blank page is
  // downgraded, because it shares an origin with |tab| which ran mixed
  // content.
  //
  // Note that the security indicator is not downgraded on the initial
  // about:blank navigation in the new tab. Initial about:blank navigations
  // don't have navigation entries (yet), so there is no way to track the mixed
  // content state for these navigations. See https://crbug.com/40113310.
  ui_test_utils::TabAddedWaiter tab_waiter(browser());
  ASSERT_TRUE(content::ExecJs(tab, "w = window.open()"));
  tab_waiter.Wait();
  WebContents* opened_tab = browser()->tab_strip_model()->GetWebContentsAt(1);
  content::TestNavigationObserver first_navigation(opened_tab);
  ASSERT_TRUE(content::ExecJs(
      tab, content::JsReplace("w.location.href = $1",
                              embedded_test_server()->GetURL("/title1.html"))));
  first_navigation.Wait();

  ssl_test_util::CheckAuthenticationBrokenState(
      opened_tab, CertError::NONE, AuthState::RAN_INSECURE_CONTENT);

  content::TestNavigationObserver about_blank_navigation(opened_tab);
  ASSERT_TRUE(content::ExecJs(tab, "w.location.href = 'about:blank'"));
  about_blank_navigation.Wait();
  ssl_test_util::CheckAuthenticationBrokenState(
      opened_tab, CertError::NONE, AuthState::RAN_INSECURE_CONTENT);
}

// Tests that MixedContentShown histogram doesn't get logged when a site with
// a bad certificate loads a subresource (which also has a bad certificate).
IN_PROC_BROWSER_TEST_F(
    SSLUITest,
    MixedContentHistogramNotLoggedForSiteWithBadCertificate) {
  ASSERT_TRUE(https_server_expired_.Start());

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(tab);

  // Navigate to a page with a certificate error, and click through the
  // interstitial.
  // page_with_subresource.html loads both a script (which would count as
  // blockable mixed content), and an image (which would count as optionally
  // blockable mixed content) from the same origin as the main site.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      https_server_expired_.GetURL("/ssl/page_with_subresource.html")));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);
  ProceedThroughInterstitial(tab);
}

// Tests that MixedContentShown histogram gets logged when a site with
// a valid certificate loads a subresource with a bad certificate.
IN_PROC_BROWSER_TEST_F(
    SSLUITest,
    MixedContentHistogramLoggedForBadCertificateSubresource) {
  ASSERT_TRUE(https_server_.Start());
  ASSERT_TRUE(https_server_expired_.Start());
  GURL base_url("https://site.test");

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(tab);

  // Navigate to a page with a certificate error, and click through the
  // interstitial so the certificate is allowlisted.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), GURL("https://site.test:" +
                      base::NumberToString(https_server_expired_.port()) +
                      "/ssl/blank_page.html")));
  ProceedThroughInterstitial(tab);

  // Navigate to a page with a valid certificate, that contains subresouces from
  // the previously allowlisted bad certificate page.
  base::StringPairs replacement_text;
  replacement_text.push_back(make_pair(
      "REPLACE_WITH_HOST_AND_PORT",
      ("site.test:" + base::NumberToString(https_server_expired_.port()))));

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      https_server_.GetURL((net::test_server::GetFilePathWithReplacements(
          "/ssl/page_with_unsafe_contents.html", replacement_text)))));
}

// Tests that MixedContentShown histogram gets logged when a site with
// a valid certificate loads an insecure form.
IN_PROC_BROWSER_TEST_F(SSLUITest, MixedContentHistogramLoggedForInsecureForm) {
  ASSERT_TRUE(https_server_.Start());

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(tab);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      https_server_.GetURL("/ssl/page_displays_insecure_form.html")));
}

// Tests that MixedContentShown histogram gets logged when a site with
// a valid certificate loads an insecure blockable resource (a script).
// TODO(carlosil): This test works because SSLUITest has
// kMixedContentAutoupgrade disabled. When cleaning up the autoupgrade flag,
// this will need to be rewritten to use content settings.
IN_PROC_BROWSER_TEST_F(SSLUITest,
                       MixedContentHistogramLoggedForBlockableMixedContent) {
  ASSERT_TRUE(https_server_.Start());

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(tab);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_.GetURL("/ssl/page_runs_insecure_content.html")));
}

// Tests that MixedContentShown histogram gets logged when a site with
// a valid certificate loads an insecure optionally blockable resource (an
// image).
// TODO(carlosil): This test works because SSLUITest has
// kMixedContentAutoupgrade disabled. When cleaning up the autoupgrade flag,
// this will need to be rewritten to use content settings.
IN_PROC_BROWSER_TEST_F(
    SSLUITest,
    MixedContentHistogramLoggedForOptionallyBlockableMixedContent) {
  ASSERT_TRUE(https_server_.Start());

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(tab);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      https_server_.GetURL("/ssl/page_displays_insecure_content.html")));
}

class SSLUIAutoReloadTest : public SSLUITest {
 public:
  SSLUIAutoReloadTest() = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(switches::kEnableAutoReload);
    SSLUITest::SetUpCommandLine(command_line);
  }
};

// SSL interstitials should disable autoreload timer.
IN_PROC_BROWSER_TEST_F(SSLUIAutoReloadTest, AutoReloadDisabled) {
  ASSERT_TRUE(https_server_expired_.Start());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));
  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(tab));
  ssl_test_util::CheckAuthenticationBrokenState(
      tab, net::CERT_STATUS_DATE_INVALID, AuthState::SHOWING_INTERSTITIAL);

  auto* reloader = error_page::NetErrorAutoReloader::FromWebContents(tab);
  const std::optional<base::OneShotTimer>& timer =
      reloader->next_reload_timer_for_testing();
  EXPECT_EQ(std::nullopt, timer);
}

class SSLUITestWithEnhancedProtectionMessage : public SSLUITest {
 public:
  SSLUITestWithEnhancedProtectionMessage() = default;

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

IN_PROC_BROWSER_TEST_F(SSLUITestWithEnhancedProtectionMessage,
                       VerifyEnhancedProtectionMessageShown) {
  base::HistogramTester histograms;
  const std::string interaction_histogram =
      "interstitial.ssl_overridable.interaction";

  safe_browsing::SetExtendedReportingPrefForTests(
      browser()->GetProfile()->GetPrefs(), true);
  safe_browsing::SetSafeBrowsingState(
      browser()->GetProfile()->GetPrefs(),
      safe_browsing::SafeBrowsingState::STANDARD_PROTECTION);
  ASSERT_TRUE(https_server_expired_.Start());
  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(contents);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(contents));
  ExpectInterstitialElementHidden(contents, "enhanced-protection-message",
                                  false /* expect_hidden */);

  histograms.ExpectTotalCount(interaction_histogram, 2);
  histograms.ExpectBucketCount(
      interaction_histogram,
      security_interstitials::MetricsHelper::TOTAL_VISITS, 1);
  histograms.ExpectBucketCount(
      interaction_histogram,
      security_interstitials::MetricsHelper::SHOW_ENHANCED_PROTECTION, 1);
}

IN_PROC_BROWSER_TEST_F(SSLUITestWithEnhancedProtectionMessage,
                       VerifyEnhancedProtectionMessageNotShownAlreadyInEp) {
  safe_browsing::SetExtendedReportingPrefForTests(
      browser()->GetProfile()->GetPrefs(), true);
  safe_browsing::SetSafeBrowsingState(
      browser()->GetProfile()->GetPrefs(),
      safe_browsing::SafeBrowsingState::ENHANCED_PROTECTION);
  ASSERT_TRUE(https_server_expired_.Start());
  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(contents);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(contents));
  ExpectInterstitialElementHidden(contents, "extended-reporting-opt-in",
                                  true /* expect_hidden */);
  ExpectInterstitialElementHidden(contents, "enhanced-protection-message",
                                  true /* expect_hidden */);
}

IN_PROC_BROWSER_TEST_F(SSLUITestWithEnhancedProtectionMessage,
                       VerifyEnhancedProtectionMessageNotShownManaged) {
  policy::PolicyMap policies;
  policies.Set(policy::key::kSafeBrowsingProtectionLevel,
               policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
               policy::POLICY_SOURCE_CLOUD,
               base::Value(/* standard protection */ 1), nullptr);
  UpdateChromePolicy(policies);
  ASSERT_TRUE(https_server_expired_.Start());
  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(contents);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server_expired_.GetURL("/ssl/google.html")));
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingSSLInterstitial(contents));
  ExpectInterstitialElementHidden(contents, "enhanced-protection-message",
                                  true /* expect_hidden */);
}

class InsecureFormNavigationThrottleFencedFrameBrowserTest
    : public InProcessBrowserTest {
 public:
  InsecureFormNavigationThrottleFencedFrameBrowserTest() = default;
  ~InsecureFormNavigationThrottleFencedFrameBrowserTest() override = default;

  WebContents* GetWebContents() {
    return browser()->tab_strip_model()->GetActiveWebContents();
  }

  content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
    return fenced_frame_helper_;
  }

 private:
  content::test::FencedFrameTestHelper fenced_frame_helper_;
};

// Tests that a fenced frame doesn't create a security interstitial.
IN_PROC_BROWSER_TEST_F(InsecureFormNavigationThrottleFencedFrameBrowserTest,
                       DoNotCreateSecurityInterstitialInFencedFrame) {
  ASSERT_TRUE(embedded_test_server()->Start());
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  https_server.AddDefaultHandlers(GetChromeTestDataDir());
  ASSERT_TRUE(https_server.Start());

  GURL initial_url = https_server.GetURL("/title1.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));

  std::string replacement_path =
      SSLUITestBase::GetFilePathWithHostAndPortReplacement(
          "/ssl/page_displays_insecure_form.html",
          embedded_test_server()->host_port_pair());
  GURL form_site_url = https_server.GetURL(replacement_path);

  // Navigate to site with an insecure form and submit it in a fenced frame.
  content::RenderFrameHost* fenced_frame =
      fenced_frame_test_helper().CreateFencedFrame(browser()
                                                       ->tab_strip_model()
                                                       ->GetActiveWebContents()
                                                       ->GetPrimaryMainFrame(),
                                                   form_site_url);
  ASSERT_TRUE(fenced_frame);
  content::TestNavigationObserver observer(GetWebContents());
  content::WebContentsConsoleObserver console_observer(GetWebContents());
  console_observer.SetPattern(
      "Mixed Content: The page at * was loaded over a secure connection, but "
      "contains a form that targets an insecure endpoint "
      "'http://does-not-exist.test/ssl/google_files/logo.gif'. This endpoint "
      "should be made available over a secure connection.");
  ASSERT_TRUE(content::ExecJs(fenced_frame, "submitForm();"));
  observer.Wait();

  security_interstitials::SecurityInterstitialTabHelper* helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          GetWebContents());

  // No interstitial should be created in the fenced frame, and the the fenced
  // frame should be in |form_site_url| and primary mainframe should be in the
  // initial URL.
  EXPECT_TRUE(!helper || !helper->IsDisplayingInterstitial());
  EXPECT_EQ(fenced_frame->GetLastCommittedURL(), form_site_url);
  EXPECT_EQ(GetWebContents()->GetVisibleURL(), initial_url);

  // Check console message was printed.
  EXPECT_EQ(console_observer.messages().size(), 1u);
}

// TODO(jcampan): more tests to do below.

// Visit a page over https that contains a frame with a redirect.

// XMLHttpRequest insecure content in synchronous mode.

// XMLHttpRequest insecure content in asynchronous mode.

// XMLHttpRequest over bad ssl in synchronous mode.

// XMLHttpRequest over OK ssl in synchronous mode.

class SSLServerPaddingBrowserTest
    : public SSLUITest,
      public testing::WithParamInterface<std::optional<int>> {
 public:
  SSLServerPaddingBrowserTest() {
    if (padding_enabled()) {
      feature_list_.InitAndEnableFeatureWithParameters(
          net::features::kAddTLSServerHandshakePadding,
          {{"AddTLSServerHandshakePaddingBytes",
            base::NumberToString(GetParam().value())}});
    } else {
      feature_list_.InitAndDisableFeature(
          net::features::kAddTLSServerHandshakePadding);
    }
  }

  bool padding_enabled() { return GetParam().has_value(); }

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

using testing::Gt;
using testing::SizeIs;

IN_PROC_BROWSER_TEST_P(SSLServerPaddingBrowserTest,
                       ValidHandshakeAndHistograms) {
  base::HistogramTester histograms;
  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
  net::SSLServerConfig ssl_config;
  ssl_config.server_padding_enabled = true;
  https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK, ssl_config);
  https_server.AddDefaultHandlers(GetChromeTestDataDir());
  ASSERT_TRUE(https_server.Start());

  // Make sure there's no favicon so that only one request is sent.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), https_server.GetURL("/no-favicon.html")));

  content::FetchHistogramsFromChildProcesses();
  base::test::TestFuture<void> future;
  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(
      /*async=*/true, future.GetCallback());
  ASSERT_TRUE(future.Wait());

  if (padding_enabled()) {
    // Most of the time, there is only one handshake, and so there should be
    // only one entry. This isn't always true on all platforms, so just
    // check to make sure we have at least one entry in each histogram.
    //
    // See crbug.com/517861023
    EXPECT_THAT(
        histograms.GetAllSamples("Net.SSL_Connection_Latency_ServerPadding"),
        SizeIs(Gt(0)));
    EXPECT_THAT(
        histograms.GetAllSamples("Net.HttpTimeToFirstByte.ServerPadding"),
        SizeIs(Gt(0)));
  } else {
    histograms.ExpectTotalCount("Net.SSL_Connection_Latency_ServerPadding", 0);
    histograms.ExpectTotalCount("Net.HttpTimeToFirstByte.ServerPadding", 0);
  }
}

INSTANTIATE_TEST_SUITE_P(,
                         SSLServerPaddingBrowserTest,
                         ::testing::Values(std::optional(128),
                                           std::optional(0),
                                           std::nullopt));

class QuicSSLServerPaddingBrowserTest
    : public SSLUITest,
      public testing::WithParamInterface<std::optional<int>> {
 public:
  QuicSSLServerPaddingBrowserTest() {
    SetQuicRestartFlag(tls_server_padding_support, true);
    if (padding_enabled()) {
      feature_list_.InitAndEnableFeatureWithParameters(
          net::features::kAddTLSServerHandshakePadding,
          {{"AddTLSServerHandshakePaddingBytes",
            base::NumberToString(GetParam().value())}});
    } else {
      feature_list_.InitAndDisableFeature(
          net::features::kAddTLSServerHandshakePadding);
    }
  }

  bool padding_enabled() { return GetParam().has_value(); }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    SSLUITest::SetUpCommandLine(command_line);
    command_line->AppendSwitchASCII(switches::kOriginToForceQuicOn, "*");
    command_line->AppendSwitch(switches::kEnableQuic);
    mock_cert_verifier_.SetUpCommandLine(command_line);
  }

  void SetUpOnMainThread() override {
    SSLUITest::SetUpOnMainThread();
    ConfigureMockCertVerifier();
    ASSERT_TRUE(net::QuicSimpleTestServer::Start());
    host_resolver()->AddRule("*", "127.0.0.1");
  }

  void TearDownOnMainThread() override {
    net::QuicSimpleTestServer::Shutdown();
    SSLUITest::TearDownOnMainThread();
  }

 protected:
  void ConfigureMockCertVerifier() {
    auto test_cert =
        net::ImportCertFromFile(net::GetTestCertsDirectory(), "quic-chain.pem");
    net::CertVerifyResult verify_result;
    verify_result.verified_cert = test_cert;
    mock_cert_verifier_.mock_cert_verifier()->AddResultForCert(
        test_cert, verify_result, net::OK);
    mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
  }

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

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

 private:
  base::test::ScopedFeatureList feature_list_;
  content::ContentMockCertVerifier mock_cert_verifier_;
};

IN_PROC_BROWSER_TEST_P(QuicSSLServerPaddingBrowserTest,
                       ValidHandshakeAndHistograms) {
  base::HistogramTester histograms;

  GURL url = net::QuicSimpleTestServer::GetFileURL(
      net::QuicSimpleTestServer::GetHelloPath());

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

  content::FetchHistogramsFromChildProcesses();
  metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();

  if (padding_enabled()) {
    EXPECT_THAT(
        histograms.GetAllSamples("Net.HttpTimeToFirstByte.ServerPadding"),
        SizeIs(Gt(0)));
    EXPECT_THAT(histograms.GetAllSamples(
                    "Net.QuicSession.HandshakeConfirmedTime.ServerPadding"),
                SizeIs(Gt(0)));
  } else {
    histograms.ExpectTotalCount(
        "Net.QuicSession.HandshakeConfirmedTime.ServerPadding", 0);
    histograms.ExpectTotalCount("Net.HttpTimeToFirstByte.ServerPadding", 0);
  }
}

INSTANTIATE_TEST_SUITE_P(QUIC,
                         QuicSSLServerPaddingBrowserTest,
                         ::testing::Values(std::optional(128),
                                           std::optional(0),
                                           std::nullopt));
