// 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.
//
// This test creates a fake safebrowsing service, where we can inject known-
// threat urls.  It then uses a real browser to go to these urls, and sends
// "goback" or "proceed" commands and verifies they work.

#include "components/safe_browsing/content/browser/safe_browsing_blocking_page.h"

#include <algorithm>
#include <map>
#include <memory>
#include <optional>
#include <string_view>
#include <utility>

#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/i18n/language_tag.h"
#include "base/i18n/tag_converters.h"
#include "base/i18n/test/scoped_icu_locale.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_clock.h"
#include "base/test/test_future.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/browser_process.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/password_manager/password_manager_test_base.h"
#include "chrome/browser/password_manager/passwords_navigation_observer.h"
#include "chrome/browser/policy/dm_token_utils.h"
#include "chrome/browser/policy/policy_test_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/renderer_context_menu/render_view_context_menu_test_util.h"
#include "chrome/browser/safe_browsing/safe_browsing_blocking_page_platform_test_helper.h"
#include "chrome/browser/safe_browsing/safe_browsing_metrics_collector_factory.h"
#include "chrome/browser/safe_browsing/test_safe_browsing_service.h"
#include "chrome/browser/safe_browsing/url_lookup_service_factory.h"
#include "chrome/browser/safe_browsing/user_interaction_observer.h"
#include "chrome/browser/safe_browsing/v5_search_hashes_cache_factory.h"
#include "chrome/browser/safe_browsing/verdict_cache_manager_factory.h"
#include "chrome/browser/ssl/cert_verifier_browser_test.h"
#include "chrome/browser/ssl/chrome_security_state_util.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/hats/mock_trust_safety_sentiment_service.h"
#include "chrome/browser/ui/hats/trust_safety_sentiment_service_factory.h"
#include "chrome/browser/ui/safety_hub/safety_hub_util.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/location_bar/location_icon_view.h"
#include "chrome/browser/ui/views/page_info/page_info_bubble_view.h"
#include "chrome/browser/ui/views/page_info/page_info_bubble_view_base.h"
#include "chrome/browser/ui/views/page_info/page_info_view_factory.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/enterprise/connectors/core/common.h"
#include "components/enterprise/connectors/core/connectors_prefs.h"
#include "components/google/core/common/google_util.h"
#include "components/grit/components_resources.h"
#include "components/omnibox/browser/omnibox_prefs.h"
#include "components/page_info/core/features.h"
#include "components/permissions/permission_util.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/pref_service.h"
#include "components/safe_browsing/content/browser/async_check_tracker.h"
#include "components/safe_browsing/content/browser/content_unsafe_resource_util.h"
#include "components/safe_browsing/content/browser/safe_browsing_blocking_page_factory.h"
#include "components/safe_browsing/content/browser/threat_details.h"
#include "components/safe_browsing/content/browser/ui_manager.h"
#include "components/safe_browsing/content/common/safe_browsing.mojom.h"
#include "components/safe_browsing/core/browser/db/database_manager.h"
#include "components/safe_browsing/core/browser/db/fake_database_manager.h"
#include "components/safe_browsing/core/browser/db/util.h"
#include "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h"
#include "components/safe_browsing/core/browser/db/v5_search_hashes_cache.h"
#include "components/safe_browsing/core/browser/safe_browsing_metrics_collector.h"
#include "components/safe_browsing/core/browser/verdict_cache_manager.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/safe_browsing/core/common/hashprefix_realtime/hash_realtime_utils.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/safe_browsing/core/common/web_ui_constants.h"
#include "components/security_interstitials/content/security_interstitial_controller_client.h"
#include "components/security_interstitials/content/security_interstitial_tab_helper.h"
#include "components/security_interstitials/content/ssl_blocking_page.h"
#include "components/security_interstitials/core/controller_client.h"
#include "components/security_interstitials/core/metrics_helper.h"
#include "components/security_interstitials/core/unsafe_resource.h"
#include "components/security_interstitials/core/unsafe_resource_locator.h"
#include "components/security_interstitials/core/urls.h"
#include "components/security_state/core/security_state.h"
#include "components/strings/grit/components_strings.h"
#include "components/ukm/test_ukm_recorder.h"
#include "components/unified_consent/pref_names.h"
#include "components/webui/chrome_urls/pref_names.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/disallow_activation_reason.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.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/web_contents.h"
#include "content/public/common/isolated_world_ids.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/render_view_test.h"
#include "content/public/test/test_frame_navigation_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "net/cert/cert_verify_result.h"
#include "net/cert/mock_cert_verifier.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/url_request/url_request_mock_http_job.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "third_party/blink/public/common/features.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/window_open_disposition.h"
#include "ui/events/test/test_event.h"
#include "ui/views/controls/styled_label.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/metrics/desktop_session_duration/desktop_session_duration_tracker.h"
#endif

using chrome_browser_interstitials::SecurityInterstitialIDNTest;
using content::BrowserThread;
using content::NavigationController;
using content::RenderFrameHost;
using content::WebContents;
using security_interstitials::BaseSafeBrowsingErrorUI;

namespace safe_browsing {

namespace {
const char kEmptyPage[] = "/empty.html";
const char kHTTPSPage[] = "/ssl/google.html";
const char kMaliciousPage[] = "/safe_browsing/malware.html";
const char kCrossSiteMaliciousPage[] = "/safe_browsing/malware2.html";
const char kMaliciousIframe[] = "/safe_browsing/malware_iframe.html";
const char kRedirectToMalware[] = "/safe_browsing/redirect_to_malware.html";
const char kUnrelatedUrl[] = "https://www.google.com";
const char kEnhancedProtectionUrl[] = "chrome://settings/security?q=enhanced";
const char kMaliciousJsPage[] = "/safe_browsing/malware_js.html";
const char kMaliciousJs[] = "/safe_browsing/script.js";

const char kInterstitialCloseHistogram[] = "interstitial.CloseReason";
const char kInterstitialPreCommitPageHistogramSuffix[] = ".before_page_shown";

std::string GetHistogramPrefix(const SBThreatType& threat_type) {
  if (threat_type == SBThreatType::SB_THREAT_TYPE_URL_MALWARE) {
    return "malware";
  } else if (threat_type == SBThreatType::SB_THREAT_TYPE_URL_PHISHING) {
    return "phishing";
  } else if (threat_type == SBThreatType::SB_THREAT_TYPE_URL_UNWANTED) {
    return "harmful";
  } else {
    NOTREACHED();
  }
}

}  // namespace

enum Visibility { VISIBILITY_ERROR = -1, HIDDEN = 0, VISIBLE = 1 };

bool IsShowingInterstitial(WebContents* contents) {
  security_interstitials::SecurityInterstitialTabHelper* helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          contents);
  return helper &&
         (helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting() !=
          nullptr);
}

content::RenderFrameHost* GetRenderFrameHost(BrowserWindowInterface* browser) {
  return browser->GetTabStripModel()
      ->GetActiveWebContents()
      ->GetPrimaryMainFrame();
}

views::BubbleDialogDelegateView* OpenPageInfo(BrowserWindowInterface* browser) {
  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
  LocationIconView* location_icon_view =
      browser_view->toolbar()->location_bar_view()->location_icon_view();
  ui::test::TestEvent event;
  location_icon_view->ShowBubble(event);
  views::BubbleDialogDelegateView* page_info =
      PageInfoBubbleViewBase::GetPageInfoBubbleForTesting();
  page_info->set_close_on_deactivate(false);
  return page_info;
}

bool WaitForReady(BrowserWindowInterface* browser) {
  WebContents* contents = browser->GetTabStripModel()->GetActiveWebContents();
  if (!content::WaitForRenderFrameReady(contents->GetPrimaryMainFrame())) {
    return false;
  }
  return chrome_browser_interstitials::IsShowingInterstitial(contents);
}

Visibility GetVisibility(BrowserWindowInterface* browser,
                         const std::string& node_id) {
  content::RenderFrameHost* rfh = GetRenderFrameHost(browser);
  if (!rfh) {
    return VISIBILITY_ERROR;
  }

  // clang-format off
  std::string jsFindVisibility = R"(
    (function isNodeVisible(node) {
      if (!node) return 'node not found';
      if (node.offsetWidth === 0 || node.offsetHeight === 0) return false;
      // Do not check opacity, since the css transition may actually leave
      // opacity at 0 after it's been unhidden
      if (node.classList.contains('hidden')) return false;
      // Otherwise, we must check all parent nodes
      var parentVisibility = isNodeVisible(node.parentElement);
      if (parentVisibility === 'node not found') {
        return true; // none of the parents are set invisible
      }
      return parentVisibility;
    }(document.getElementById(')" + node_id + R"(')));)";
  // clang-format on

  content::EvalJsResult result = content::EvalJs(rfh, jsFindVisibility);

  if (result != true && result != false) {
    return VISIBILITY_ERROR;
  }

  return result == true ? VISIBLE : HIDDEN;
}

bool Click(BrowserWindowInterface* browser, const std::string& node_id) {
  DCHECK(node_id == "primary-button" || node_id == "proceed-link" ||
         node_id == "whitepaper-link" || node_id == "details-button" ||
         node_id == "opt-in-checkbox" || node_id == "enhanced-protection-link")
      << "Unexpected node_id: " << node_id;
  content::RenderFrameHost* rfh = GetRenderFrameHost(browser);
  if (!rfh) {
    return false;
  }
  // We don't use EvalJs for this one, since clicking
  // the button/link may navigate away before the injected javascript can
  // reply, hanging the test.
  rfh->ExecuteJavaScriptForTests(
      u"document.getElementById('" + base::ASCIIToUTF16(node_id) +
          u"').click();\n",
      base::NullCallback(), content::ISOLATED_WORLD_ID_GLOBAL);
  return true;
}

bool ClickAndWaitForDetach(BrowserWindowInterface* browser,
                           const std::string& node_id) {
  // We wait for interstitial_detached rather than nav_entry_committed, as
  // going back from a main-frame safe browsing interstitial page will not
  // cause a nav entry committed event.
  content::TestNavigationObserver observer(
      browser->GetTabStripModel()->GetActiveWebContents());
  if (!Click(browser, node_id)) {
    return false;
  }
  observer.WaitForNavigationFinished();
  return true;
}

void ExpectSecurityIndicatorDowngrade(content::WebContents* tab,
                                      net::CertStatus cert_status) {
  EXPECT_EQ(security_state::DANGEROUS,
            chrome_security_state::GetSecurityLevel(tab));
  EXPECT_NE(security_state::MALICIOUS_CONTENT_STATUS_NONE,
            chrome_security_state::GetVisibleSecurityState(tab)
                ->malicious_content_status);
  // TODO(felt): Restore this check when https://crbug.com/40085203 is fixed.
  // EXPECT_EQ(cert_status, helper->GetSecurityInfo().cert_status);
}

void ExpectNoSecurityIndicatorDowngrade(content::WebContents* tab) {
  EXPECT_EQ(security_state::NONE, chrome_security_state::GetSecurityLevel(tab));
  EXPECT_EQ(security_state::MALICIOUS_CONTENT_STATUS_NONE,
            chrome_security_state::GetVisibleSecurityState(tab)
                ->malicious_content_status);
}

class TestThreatDetailsFactory : public ThreatDetailsFactory {
 public:
  std::unique_ptr<ThreatDetails> CreateThreatDetails(
      BaseUIManager* delegate,
      WebContents* web_contents,
      const security_interstitials::UnsafeResource& unsafe_resource,
      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
      history::HistoryService* history_service,
      ReferrerChainProvider* referrer_chain_provider,
      bool trim_to_ad_tags,
      ThreatDetailsDoneCallback done_callback) override {
    auto details = base::WrapUnique(new ThreatDetails(
        delegate, web_contents, unsafe_resource, url_loader_factory,
        history_service, referrer_chain_provider, trim_to_ad_tags,
        std::move(done_callback)));
    details_ = details.get();
    details->StartCollection();
    return details;
  }

  ThreatDetails* get_details() { return details_; }

 private:
  raw_ptr<ThreatDetails, AcrossTasksDanglingUntriaged> details_ = nullptr;
};

void AssertNoInterstitial(BrowserWindowInterface* browser) {
  WebContents* contents = browser->GetTabStripModel()->GetActiveWebContents();
  ASSERT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(contents));
  return;
}

// Tests the safe browsing blocking page in a browser.
class SafeBrowsingBlockingPageBrowserTest
    : public CertVerifierBrowserTest,
      public testing::WithParamInterface<
          testing::tuple<SBThreatType, bool, bool, bool>> {
 public:
  SafeBrowsingBlockingPageBrowserTest()
      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
    // NOTE: Value copied from the renderer-side threat_dom_details.cc, as
    // threat_dom_details.h can't be depended on from this browser-side code.
    const char kTagAndAttributeParamName[] = "tag_attribute_csv";
    std::map<std::string, std::string> parameters = {
        {kTagAndAttributeParamName, "div,foo,div,baz"}};
    base::test::FeatureRefAndParams tag_and_attribute(
        safe_browsing::kThreatDomDetailsTagAndAttributeFeature, parameters);
    base::test::FeatureRefAndParams add_warning_shown_timestamp_csbrrs(
        safe_browsing::kAddWarningShownTSToClientSafeBrowsingReport, {});
    base::test::FeatureRefAndParams create_warning_shown_csbrrs(
        safe_browsing::kCreateWarningShownClientSafeBrowsingReports, {});
    std::vector<base::test::FeatureRefAndParams> enabled_features = {
        tag_and_attribute, add_warning_shown_timestamp_csbrrs,
        create_warning_shown_csbrrs};
    std::vector<base::test::FeatureRef> disabled_features = {
        safe_browsing::kHashPrefixRealTimeLookupsSamplePing};
    if (IsSberDeprecated()) {
      enabled_features.push_back(base::test::FeatureRefAndParams(
          safe_browsing::kExtendedReportingRemovePrefDependency, {}));
    } else {
      disabled_features.push_back(
          safe_browsing::kExtendedReportingRemovePrefDependency);
    }
    if (UseV5()) {
      enabled_features.push_back(base::test::FeatureRefAndParams(
          safe_browsing::kLocalListsUseSBv5, {}));
    } else {
      disabled_features.push_back(safe_browsing::kLocalListsUseSBv5);
    }
    scoped_feature_list_.InitWithFeaturesAndParameters(enabled_features,
                                                       disabled_features);
  }

  bool IsSberDeprecated() const { return std::get<2>(GetParam()); }
  bool UseV5() const { return std::get<3>(GetParam()); }

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

  ~SafeBrowsingBlockingPageBrowserTest() override = default;

  void CreatedBrowserMainParts(
      content::BrowserMainParts* browser_main_parts) override {
    CertVerifierBrowserTest::CreatedBrowserMainParts(browser_main_parts);
    // Test UI manager and test database manager should be set before
    // the browser is started but after threads are created.
    auto blocking_page_factory =
        std::make_unique<TestSafeBrowsingBlockingPageFactory>();
    raw_blocking_page_factory_ = blocking_page_factory.get();
    factory_.SetTestUIManager(
        new FakeSafeBrowsingUIManager(std::move(blocking_page_factory)));
    factory_.SetTestDatabaseManager(new FakeSafeBrowsingDatabaseManager(
        content::GetUIThreadTaskRunner({})));
    SafeBrowsingService::RegisterFactory(&factory_);
    ThreatDetails::RegisterFactory(&details_factory_);
  }

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

  void SetUpCommandLine(base::CommandLine* command_line) override {
    CertVerifierBrowserTest::SetUpCommandLine(command_line);
    if (IsSiteIsolationEnabled()) {
      content::IsolateAllSitesForTesting(command_line);
    }
    // TODO(crbug.com/40285326): This fails with the field trial testing config.
    command_line->AppendSwitch("disable-field-trial-config");
  }

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    content::SetupCrossSiteRedirector(embedded_test_server());
    ASSERT_TRUE(embedded_test_server()->Start());
    // The tests expect to load chrome://safe-browsing, which is an
    // internal debugging page.
    g_browser_process->local_state()->SetBoolean(
        chrome_urls::kInternalOnlyUisEnabled, true);
  }

  SBThreatType GetThreatType() const { return std::get<0>(GetParam()); }
  bool IsSiteIsolationEnabled() const { return std::get<1>(GetParam()); }

  void SetURLThreatType(const GURL& url, SBThreatType threat_type) {
    TestSafeBrowsingService* service = factory_.test_safe_browsing_service();
    ASSERT_TRUE(service);

    static_cast<FakeSafeBrowsingDatabaseManager*>(
        service->database_manager().get())
        ->AddDangerousUrl(url, threat_type);
  }

  void ClearBadURL(const GURL& url) {
    TestSafeBrowsingService* service = factory_.test_safe_browsing_service();
    ASSERT_TRUE(service);

    static_cast<FakeSafeBrowsingDatabaseManager*>(
        service->database_manager().get())
        ->ClearDangerousUrl(url);
  }

  // The basic version of this method, which uses an HTTP test URL.
  GURL SetupWarningAndNavigate(BrowserWindowInterface* browser) {
    return SetupWarningAndNavigateToURL(
        embedded_test_server()->GetURL(kEmptyPage), browser);
  }

  // The basic version of this method, which uses an HTTP test URL.
  GURL SetupWarningAndNavigateInNewTab(BrowserWindowInterface* browser) {
    return SetupWarningAndNavigateToURLInNewTab(
        embedded_test_server()->GetURL(kEmptyPage), browser);
  }

  // Navigates to a warning on a valid HTTPS website.
  GURL SetupWarningAndNavigateToValidHTTPS() {
    EXPECT_TRUE(https_server_.Start());
    scoped_refptr<net::X509Certificate> cert(https_server_.GetCertificate());
    net::CertVerifyResult verify_result;
    verify_result.verified_cert = cert;
    verify_result.cert_status = 0;
    mock_cert_verifier()->AddResultForCert(cert.get(), verify_result, net::OK);
    GURL url = https_server_.GetURL(kHTTPSPage);
    return SetupWarningAndNavigateToURL(url, browser());
  }

  // Navigates through an HTTPS interstitial, then opens up a SB warning on that
  // same URL.
  GURL SetupWarningAndNavigateToInvalidHTTPS() {
    https_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED);
    EXPECT_TRUE(https_server_.Start());
    GURL url = https_server_.GetURL(kHTTPSPage);

    // Proceed through the HTTPS interstitial.
    EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));

    content::WebContents* contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    security_interstitials::SecurityInterstitialPage* ssl_blocking_page;

    EXPECT_TRUE(WaitForRenderFrameReady(contents->GetPrimaryMainFrame()));
    security_interstitials::SecurityInterstitialTabHelper* helper =
        security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
            contents);
    EXPECT_TRUE(helper);
    ssl_blocking_page =
        helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting();

    EXPECT_EQ(SSLBlockingPage::kTypeForTesting,
              ssl_blocking_page->GetTypeForTesting());
    content::TestNavigationObserver observer(
        browser()->tab_strip_model()->GetActiveWebContents());
    ssl_blocking_page->CommandReceived(base::NumberToString(
        security_interstitials::SecurityInterstitialCommand::CMD_PROCEED));
    // When both SB and SSL interstitials are committed navigations, we need
    // to wait for two navigations here, one is from the SSL interstitial to
    // the blocked site (which does not complete since SB blocks it) and the
    // second one is to the actual SB interstitial.
    observer.WaitForNavigationFinished();

    return SetupWarningAndNavigateToURL(url, browser());
  }

  // Adds a safebrowsing threat results to the fake safebrowsing service,
  // navigates to a page with a subresource containing the threat site, and
  // returns the url of the parent page.
  GURL SetupThreatOnSubresourceAndNavigate(std::string_view main_frame_url,
                                           std::string_view subresource_url) {
    GURL url = embedded_test_server()->GetURL(main_frame_url);
    GURL embedded_url = embedded_test_server()->GetURL(subresource_url);
    SetURLThreatType(embedded_url, GetThreatType());

    EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
    WebContents* contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    EXPECT_TRUE(
        content::WaitForRenderFrameReady(contents->GetPrimaryMainFrame()));
    return url;
  }

  GURL GetWhitePaperUrl() {
    return google_util::AppendGoogleLocaleParam(
        GURL(security_interstitials::kSafeBrowsingWhitePaperUrl),
        factory_.test_safe_browsing_service()
            ->ui_manager()
            .get()
            ->app_locale());
  }

  void EnableExtendedReporting(bool enable) {
    if (IsSberDeprecated()) {
      SetSafeBrowsingState(browser()->GetProfile()->GetPrefs(),
                           enable ? SafeBrowsingState::ENHANCED_PROTECTION
                                  : SafeBrowsingState::STANDARD_PROTECTION);
    } else {
      SetExtendedReportingPrefForTests(browser()->GetProfile()->GetPrefs(),
                                       enable);
    }
  }

  bool IsExtendedReportingEnabled() {
    if (IsSberDeprecated()) {
      return IsEnhancedProtectionEnabled(*browser()->GetProfile()->GetPrefs());
    }
    return ::safe_browsing::IsExtendedReportingEnabled(
        *browser()->GetProfile()->GetPrefs());
  }

  void SendCommand(
      security_interstitials::SecurityInterstitialCommand command) {
    WebContents* contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    SafeBrowsingBlockingPage* interstitial_page;
    security_interstitials::SecurityInterstitialTabHelper* helper =
        security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
            contents);
    ASSERT_TRUE(helper);
    interstitial_page = static_cast<SafeBrowsingBlockingPage*>(
        helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting());
    ASSERT_TRUE(interstitial_page);
    ASSERT_EQ(SafeBrowsingBlockingPage::kTypeForTesting,
              interstitial_page->GetTypeForTesting());
    interstitial_page->CommandReceived(base::NumberToString(command));
  }

  void SetReportSentCallback(base::OnceClosure callback) {
    static_cast<FakeSafeBrowsingUIManager*>(
        factory_.test_safe_browsing_service()->ui_manager().get())
        ->set_threat_details_done_callback(std::move(callback));
  }

  std::string GetReportSent() {
    return static_cast<FakeSafeBrowsingUIManager*>(
               factory_.test_safe_browsing_service()->ui_manager().get())
        ->GetReport();
  }

  void SetExpectEmptyReportForHats(bool expect_empty_report_for_hats) {
    static_cast<FakeSafeBrowsingUIManager*>(
        factory_.test_safe_browsing_service()->ui_manager().get())
        ->SetExpectEmptyReportForHats(expect_empty_report_for_hats);
  }

  void SetExpectReportUrlForHats(bool expect_report_url_for_hats) {
    static_cast<FakeSafeBrowsingUIManager*>(
        factory_.test_safe_browsing_service()->ui_manager().get())
        ->SetExpectReportUrlForHats(expect_report_url_for_hats);
  }

  void SetExpectInterstitialInteractions(
      bool expect_interstitial_interactions) {
    static_cast<FakeSafeBrowsingUIManager*>(
        factory_.test_safe_browsing_service()->ui_manager().get())
        ->SetExpectInterstitialInteractions(expect_interstitial_interactions);
  }

  FakeSafeBrowsingUIManager* GetSafeBrowsingUiManager() {
    return static_cast<FakeSafeBrowsingUIManager*>(
        factory_.test_safe_browsing_service()->ui_manager().get());
  }

  content::RenderFrameHost* GetRenderFrameHost() {
    return ::safe_browsing::GetRenderFrameHost(browser());
  }

  Visibility GetVisibility(const std::string& node_id) {
    return ::safe_browsing::GetVisibility(browser(), node_id);
  }

  bool Click(const std::string& node_id) {
    return ::safe_browsing::Click(browser(), node_id);
  }

  bool ClickAndWaitForDetach(const std::string& node_id) {
    return ::safe_browsing::ClickAndWaitForDetach(browser(), node_id);
  }

  void AssertNoInterstitial() {
    return ::safe_browsing::AssertNoInterstitial(browser());
  }

  void TestReportingDisabledAndDontProceed(const GURL& url) {
    SetURLThreatType(url, GetThreatType());
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
    ASSERT_TRUE(WaitForReady(browser()));

    EXPECT_EQ(HIDDEN, GetVisibility("extended-reporting-opt-in"));
    EXPECT_EQ(HIDDEN, GetVisibility("opt-in-checkbox"));
    EXPECT_EQ(HIDDEN, GetVisibility("proceed-link"));
    EXPECT_EQ(VISIBLE, GetVisibility("learn-more-link"));
    EXPECT_TRUE(Click("details-button"));
    EXPECT_EQ(VISIBLE, GetVisibility("proceed-link"));

    EXPECT_TRUE(ClickAndWaitForDetach("primary-button"));
    AssertNoInterstitial();               // Assert the interstitial is gone
    EXPECT_EQ(GURL(url::kAboutBlankURL),  // Back to "about:blank"
              browser()
                  ->tab_strip_model()
                  ->GetActiveWebContents()
                  ->GetLastCommittedURL());
  }

  void VerifyResource(
      const ClientSafeBrowsingReportRequest& report,
      const ClientSafeBrowsingReportRequest::Resource& actual_resource,
      const std::string& expected_url,
      const std::string& expected_parent,
      int expected_child_size,
      const std::string& expected_tag_name) {
    EXPECT_EQ(expected_url, actual_resource.url());
    // Finds the parent url by comparing resource ids.
    for (auto resource : report.resources()) {
      if (actual_resource.parent_id() == resource.id()) {
        EXPECT_EQ(expected_parent, resource.url());
        break;
      }
    }
    EXPECT_EQ(expected_child_size, actual_resource.child_ids_size());
    EXPECT_EQ(expected_tag_name, actual_resource.tag_name());
  }

  void VerifyInteractionOccurrenceCount(
      const ClientSafeBrowsingReportRequest& report,
      const ClientSafeBrowsingReportRequest::InterstitialInteraction&
          actual_interaction,
      const ClientSafeBrowsingReportRequest::InterstitialInteraction::
          SecurityInterstitialInteraction& expected_interaction_type,
      const int& expected_occurrence_count) {
    // Find the interaction within the report by comparing
    // security_interstitial_interaction.
    for (auto interaction : report.interstitial_interactions()) {
      if (actual_interaction.security_interstitial_interaction() ==
          interaction.security_interstitial_interaction()) {
        EXPECT_EQ(expected_interaction_type,
                  interaction.security_interstitial_interaction());
        EXPECT_EQ(expected_occurrence_count, interaction.occurrence_count());
        break;
      }
    }
  }

  void VerifyElement(
      const ClientSafeBrowsingReportRequest& report,
      const HTMLElement& actual_element,
      const std::string& expected_tag_name,
      int expected_child_ids_size,
      const std::vector<mojom::AttributeNameValuePtr>& expected_attributes) {
    EXPECT_EQ(expected_tag_name, actual_element.tag());
    EXPECT_EQ(expected_child_ids_size, actual_element.child_ids_size());
    ASSERT_EQ(static_cast<int>(expected_attributes.size()),
              actual_element.attribute_size());
    for (size_t i = 0; i < expected_attributes.size(); ++i) {
      const mojom::AttributeNameValue& expected_attribute =
          *expected_attributes[i];
      const HTMLElement::Attribute& actual_attribute_pb =
          actual_element.attribute(i);
      EXPECT_EQ(expected_attribute.name, actual_attribute_pb.name());
      EXPECT_EQ(expected_attribute.value, actual_attribute_pb.value());
    }
  }

  void ExpectSecurityIndicatorDowngrade(content::WebContents* tab,
                                        net::CertStatus cert_status) {
    ::safe_browsing::ExpectSecurityIndicatorDowngrade(tab, cert_status);
  }

  void ExpectNoSecurityIndicatorDowngrade(content::WebContents* tab) {
    ::safe_browsing::ExpectNoSecurityIndicatorDowngrade(tab);
  }

  bool report_sent() {
    return static_cast<FakeSafeBrowsingUIManager*>(
               factory_.test_safe_browsing_service()->ui_manager().get())
        ->report_sent();
  }

  // Helper method for LearnMore test below. Implemented as a test fixture
  // method instead of in the test below because the whole test fixture class
  // is friended by SafeBrowsingBlockingPage.
  void MockHelpCenterUrl(SafeBrowsingBlockingPage* sb_interstitial) {
    ASSERT_TRUE(https_server_.Start());
    scoped_refptr<net::X509Certificate> cert(https_server_.GetCertificate());
    net::CertVerifyResult verify_result;
    verify_result.verified_cert = cert;
    verify_result.cert_status = 0;
    mock_cert_verifier()->AddResultForCert(cert.get(), verify_result, net::OK);

    security_interstitials::SecurityInterstitialControllerClient* client =
        sb_interstitial->controller();

    client->SetBaseHelpCenterUrlForTesting(
        https_server_.GetURL("/title1.html"));
  }

  void SetAlwaysShowBackToSafety(bool val) {
    raw_blocking_page_factory_->SetAlwaysShowBackToSafety(val);
  }

  MockTrustSafetySentimentService* mock_sentiment_service() {
    return raw_blocking_page_factory_->GetMockSentimentService();
  }

  HostContentSettingsMap* hcsm() {
    return HostContentSettingsMapFactory::GetForProfile(
        browser()->GetProfile());
  }

 protected:
  TestThreatDetailsFactory details_factory_;

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

 private:
  // Adds a safebrowsing result of the current test threat to the fake
  // safebrowsing service, navigates to that page, and returns the url.
  // The various wrappers supply different URLs.
  GURL SetupWarningAndNavigateToURL(GURL url, BrowserWindowInterface* browser) {
    SetURLThreatType(url, GetThreatType());
    EXPECT_TRUE(ui_test_utils::NavigateToURL(browser, url));
    EXPECT_TRUE(WaitForReady(browser));
    return url;
  }
  // Adds a safebrowsing result of the current test threat to the fake
  // safebrowsing service, navigates to that page, and returns the url.
  // The various wrappers supply different URLs.
  GURL SetupWarningAndNavigateToURLInNewTab(GURL url,
                                            BrowserWindowInterface* browser) {
    SetURLThreatType(url, GetThreatType());
    ui_test_utils::NavigateToURLWithDisposition(
        browser, url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
        ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB);
    content::TestNavigationObserver observer(
        browser->GetTabStripModel()->GetActiveWebContents());
    observer.WaitForNavigationFinished();
    EXPECT_TRUE(WaitForReady(browser));
    return url;
  }

  base::test::ScopedFeatureList scoped_feature_list_;
  TestSafeBrowsingServiceFactory factory_;
  raw_ptr<TestSafeBrowsingBlockingPageFactory, DanglingUntriaged>
      raw_blocking_page_factory_;
  net::EmbeddedTestServer https_server_;
};

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, HardcodedUrls) {
  const GURL urls[] = {GURL(kChromeUISafeBrowsingMatchMalwareUrl),
                       GURL(kChromeUISafeBrowsingMatchPhishingUrl),
                       GURL(kChromeUISafeBrowsingMatchUnwantedUrl)};

  for (const GURL& url : urls) {
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
    EXPECT_TRUE(WaitForReady(browser()));

    EXPECT_EQ(VISIBLE, GetVisibility("primary-button"));
    EXPECT_EQ(HIDDEN, GetVisibility("details"));
    EXPECT_EQ(HIDDEN, GetVisibility("proceed-link"));
    EXPECT_EQ(HIDDEN, GetVisibility("error-code"));
    EXPECT_TRUE(Click("details-button"));
    EXPECT_EQ(VISIBLE, GetVisibility("details"));
    EXPECT_EQ(VISIBLE, GetVisibility("proceed-link"));
    EXPECT_EQ(HIDDEN, GetVisibility("error-code"));
    EXPECT_TRUE(ClickAndWaitForDetach("primary-button"));

    AssertNoInterstitial();               // Assert the interstitial is gone
    EXPECT_EQ(GURL(url::kAboutBlankURL),  // Back to "about:blank"
              browser()
                  ->tab_strip_model()
                  ->GetActiveWebContents()
                  ->GetLastCommittedURL());
  }
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, DontProceed) {
  SetupWarningAndNavigate(browser());

  EXPECT_EQ(VISIBLE, GetVisibility("primary-button"));
  EXPECT_EQ(HIDDEN, GetVisibility("details"));
  EXPECT_EQ(HIDDEN, GetVisibility("proceed-link"));
  EXPECT_EQ(HIDDEN, GetVisibility("error-code"));
  EXPECT_TRUE(Click("details-button"));
  EXPECT_EQ(VISIBLE, GetVisibility("details"));
  EXPECT_EQ(VISIBLE, GetVisibility("proceed-link"));
  EXPECT_EQ(HIDDEN, GetVisibility("error-code"));
  EXPECT_TRUE(ClickAndWaitForDetach("primary-button"));

  AssertNoInterstitial();               // Assert the interstitial is gone
  EXPECT_EQ(GURL(url::kAboutBlankURL),  // Back to "about:blank"
            browser()
                ->tab_strip_model()
                ->GetActiveWebContents()
                ->GetLastCommittedURL());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, DontProceed_RTL) {
  base::i18n::ScopedDefaultIcuLocale scoped_locale(
      base::i18n::GetKnownLanguageTag("ar"));
  ASSERT_TRUE(base::i18n::IsRTL());

  SetupWarningAndNavigate(browser());

  EXPECT_EQ(VISIBLE, GetVisibility("primary-button"));
  EXPECT_EQ(HIDDEN, GetVisibility("details"));
  EXPECT_EQ(HIDDEN, GetVisibility("proceed-link"));
  EXPECT_EQ(HIDDEN, GetVisibility("error-code"));
  EXPECT_TRUE(Click("details-button"));
  EXPECT_EQ(VISIBLE, GetVisibility("details"));
  EXPECT_EQ(VISIBLE, GetVisibility("proceed-link"));
  EXPECT_EQ(HIDDEN, GetVisibility("error-code"));
  EXPECT_TRUE(ClickAndWaitForDetach("primary-button"));

  AssertNoInterstitial();               // Assert the interstitial is gone
  EXPECT_EQ(GURL(url::kAboutBlankURL),  // Back to "about:blank"
            browser()
                ->tab_strip_model()
                ->GetActiveWebContents()
                ->GetLastCommittedURL());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, Proceed) {
  GURL url = SetupWarningAndNavigate(browser());

  EXPECT_TRUE(ClickAndWaitForDetach("proceed-link"));
  AssertNoInterstitial();  // Assert the interstitial is gone.
  EXPECT_EQ(url, browser()
                     ->tab_strip_model()
                     ->GetActiveWebContents()
                     ->GetLastCommittedURL());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, Proceed_RTL) {
  base::i18n::ScopedDefaultIcuLocale scoped_locale(
      base::i18n::GetKnownLanguageTag("ar"));
  ASSERT_TRUE(base::i18n::IsRTL());

  GURL url = SetupWarningAndNavigate(browser());

  EXPECT_TRUE(ClickAndWaitForDetach("proceed-link"));
  AssertNoInterstitial();  // Assert the interstitial is gone.
  EXPECT_EQ(url, browser()
                     ->tab_strip_model()
                     ->GetActiveWebContents()
                     ->GetLastCommittedURL());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, IframeNoWarning) {
  SetupThreatOnSubresourceAndNavigate(kCrossSiteMaliciousPage,
                                      kMaliciousIframe);
  AssertNoInterstitial();
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, JsNoWarning) {
  SetupThreatOnSubresourceAndNavigate(kMaliciousJsPage, kMaliciousJs);
  AssertNoInterstitial();
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       MainFrameBlockedShouldHaveNoDOMDetailsWhenDontProceed) {
  EnableExtendedReporting(true);
  const bool expect_threat_details =
      SafeBrowsingBlockingPage::ShouldReportThreatDetails(GetThreatType());

  base::RunLoop threat_report_sent_loop;
  if (expect_threat_details) {
    SetReportSentCallback(threat_report_sent_loop.QuitClosure());
  }

  // Navigate to a safe page which contains multiple potential DOM details.
  // (Despite the name, kMaliciousPage is not the page flagged as bad in this
  // test.)
  GURL safe_url(embedded_test_server()->GetURL(kMaliciousPage));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), safe_url));

  EXPECT_EQ(nullptr, details_factory_.get_details());

  // Start navigation to bad page (kEmptyPage), which will be blocked before it
  // is committed.
  content::TestNavigationObserver observer(
      browser()->tab_strip_model()->GetActiveWebContents());
  GURL url = SetupWarningAndNavigate(browser());
  observer.WaitForNavigationFinished();
  ThreatDetails* threat_details = details_factory_.get_details();
  EXPECT_EQ(expect_threat_details, threat_details != nullptr);

  // Go back.
  EXPECT_TRUE(ClickAndWaitForDetach("primary-button"));
  AssertNoInterstitial();  // Assert the interstitial is gone

  EXPECT_TRUE(IsExtendedReportingEnabled());
  EXPECT_EQ(safe_url, browser()
                          ->tab_strip_model()
                          ->GetActiveWebContents()
                          ->GetLastCommittedURL());

  if (expect_threat_details) {
    threat_report_sent_loop.Run();
    std::string serialized = GetReportSent();
    ClientSafeBrowsingReportRequest report;
    ASSERT_TRUE(report.ParseFromString(serialized));
    // Verify the report is complete.
    EXPECT_TRUE(report.complete());
    EXPECT_EQ(url.spec(), report.page_url());
    EXPECT_EQ(url.spec(), report.url());
    ASSERT_EQ(1, report.resources_size());
    EXPECT_EQ(url.spec(), report.resources(0).url());
  }
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       MainFrameBlockedShouldHaveNoDOMDetailsWhenProceeding) {
  EnableExtendedReporting(true);
  const bool expect_threat_details =
      SafeBrowsingBlockingPage::ShouldReportThreatDetails(GetThreatType());

  base::RunLoop threat_report_sent_loop;
  if (expect_threat_details) {
    SetReportSentCallback(threat_report_sent_loop.QuitClosure());
  }

  // Navigate to a safe page which contains multiple potential DOM details.
  // (Despite the name, kMaliciousPage is not the page flagged as bad in this
  // test.)
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL(kMaliciousPage)));

  EXPECT_EQ(nullptr, details_factory_.get_details());

  // Start navigation to bad page (kEmptyPage), which will be blocked before it
  // is committed.
  GURL url = SetupWarningAndNavigate(browser());

  ThreatDetails* threat_details = details_factory_.get_details();
  EXPECT_EQ(expect_threat_details, threat_details != nullptr);

  // Proceed through the warning.
  EXPECT_TRUE(ClickAndWaitForDetach("proceed-link"));
  AssertNoInterstitial();  // Assert the interstitial is gone

  EXPECT_TRUE(IsExtendedReportingEnabled());
  EXPECT_EQ(url, browser()
                     ->tab_strip_model()
                     ->GetActiveWebContents()
                     ->GetLastCommittedURL());

  if (expect_threat_details) {
    threat_report_sent_loop.Run();
    std::string serialized = GetReportSent();
    ClientSafeBrowsingReportRequest report;
    ASSERT_TRUE(report.ParseFromString(serialized));
    // Verify the report is complete.
    EXPECT_TRUE(report.complete());
    EXPECT_EQ(url.spec(), report.page_url());
    EXPECT_EQ(url.spec(), report.url());
    ASSERT_EQ(1, report.resources_size());
    EXPECT_EQ(url.spec(), report.resources(0).url());
  }
}

// Verifies that the "proceed anyway" link isn't available when it is disabled
// by the corresponding policy. Also verifies that sending the "proceed"
// command anyway doesn't advance to the unsafe site.
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, ProceedDisabled) {
  // Simulate a policy disabling the "proceed anyway" link.
  browser()->GetProfile()->GetPrefs()->SetBoolean(
      prefs::kSafeBrowsingProceedAnywayDisabled, true);

  SetupWarningAndNavigate(browser());

  EXPECT_EQ(VISIBLE, GetVisibility("primary-button"));
  EXPECT_EQ(HIDDEN, GetVisibility("details"));
  EXPECT_EQ(HIDDEN, GetVisibility("proceed-link"));
  EXPECT_EQ(HIDDEN, GetVisibility("final-paragraph"));
  EXPECT_TRUE(Click("details-button"));
  EXPECT_EQ(HIDDEN, GetVisibility("proceed-link"));
  EXPECT_EQ(HIDDEN, GetVisibility("final-paragraph"));
  content::TestNavigationObserver observer(
      browser()->tab_strip_model()->GetActiveWebContents());
  SendCommand(security_interstitials::CMD_PROCEED);
  observer.WaitForNavigationFinished();

  // The "proceed" command should go back instead, if proceeding is disabled.
  AssertNoInterstitial();
  EXPECT_EQ(GURL(url::kAboutBlankURL),  // Back to "about:blank"
            browser()
                ->tab_strip_model()
                ->GetActiveWebContents()
                ->GetLastCommittedURL());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, NoBackToSafety) {
  SetAlwaysShowBackToSafety(false);
  SetupWarningAndNavigateInNewTab(browser());

  EXPECT_EQ(HIDDEN, GetVisibility("primary-button"));
  EXPECT_EQ(HIDDEN, GetVisibility("details"));
  EXPECT_EQ(HIDDEN, GetVisibility("proceed-link"));
  EXPECT_EQ(HIDDEN, GetVisibility("error-code"));
  EXPECT_TRUE(Click("details-button"));
  EXPECT_EQ(VISIBLE, GetVisibility("details"));
  EXPECT_EQ(VISIBLE, GetVisibility("proceed-link"));
  EXPECT_EQ(HIDDEN, GetVisibility("error-code"));
}

// Verifies that the reporting checkbox is hidden when opt-in is
// disabled by policy. However, reports can still be sent if extended
// reporting is enabled (eg: by its own policy).
// Note: this combination will be deprecated along with the OptInAllowed
// policy, to be replaced by a policy on the SBER setting itself.
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       ReportingDisabledByPolicy) {
  EnableExtendedReporting(true);
  browser()->GetProfile()->GetPrefs()->SetBoolean(
      prefs::kSafeBrowsingExtendedReportingOptInAllowed, false);

  base::RunLoop threat_report_sent_loop;
  SetReportSentCallback(threat_report_sent_loop.QuitClosure());

  TestReportingDisabledAndDontProceed(
      embedded_test_server()->GetURL(kEmptyPage));
}

// Verifies that the enhanced protection message is still shown if the page is
// reloaded while the interstitial is showing.
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       ReloadWhileInterstitialShowing) {
  safe_browsing::SetSafeBrowsingState(
      browser()->GetProfile()->GetPrefs(),
      safe_browsing::SafeBrowsingState::STANDARD_PROTECTION);
  browser()->GetProfile()->GetPrefs()->SetBoolean(
      prefs::kSafeBrowsingSurveysEnabled, false);
  // Start navigation to bad page (kEmptyPage), which will be blocked before it
  // is committed.
  const GURL url = SetupWarningAndNavigate(browser());

  // Checkbox should be showing.
  EXPECT_EQ(VISIBLE, GetVisibility("enhanced-protection-message"));

  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(tab);
  // Security indicator should be showing.
  ExpectSecurityIndicatorDowngrade(tab, 0u);

  // Check navigation entry state.
  NavigationController& controller = tab->GetController();
  ASSERT_TRUE(controller.GetVisibleEntry());
  EXPECT_EQ(url, controller.GetVisibleEntry()->GetURL());

  // "Reload" the tab.
  SetupWarningAndNavigate(browser());

  // Checkbox should be showing.
  EXPECT_EQ(VISIBLE, GetVisibility("enhanced-protection-message"));

  // Security indicator should be showing.
  ExpectSecurityIndicatorDowngrade(tab, 0u);
  // Check navigation entry state.
  ASSERT_TRUE(controller.GetVisibleEntry());
  EXPECT_EQ(url, controller.GetVisibleEntry()->GetURL());
}

#if (BUILDFLAG(IS_MAC) && !defined(NDEBUG)) || defined(MEMORY_SANITIZER)
// TODO(crbug.com/40721886): Address flaky timeout.
#define MAYBE_LearnMore DISABLED_LearnMore
#else
#define MAYBE_LearnMore LearnMore
#endif
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, MAYBE_LearnMore) {
  SetupWarningAndNavigate(browser());

  SafeBrowsingBlockingPage* sb_interstitial;
  WebContents* interstitial_tab =
      browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(interstitial_tab);

  security_interstitials::SecurityInterstitialTabHelper* helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          interstitial_tab);
  ASSERT_TRUE(helper);
  sb_interstitial = static_cast<SafeBrowsingBlockingPage*>(
      helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting());

  MockHelpCenterUrl(sb_interstitial);

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

  content::TestNavigationObserver nav_observer(
      browser()->tab_strip_model()->GetActiveWebContents());
  SendCommand(security_interstitials::CMD_OPEN_HELP_CENTER);
  nav_observer.WaitForNavigationFinished();

  // A new tab has not been opened.
  EXPECT_EQ(1, browser()->tab_strip_model()->count());

  // Interstitial does not display in the foreground tab.
  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
  EXPECT_EQ(interstitial_tab,
            browser()->tab_strip_model()->GetActiveWebContents());
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(
      browser()->tab_strip_model()->GetActiveWebContents()));
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       Histograms_DontProceed) {
  ukm::TestAutoSetUkmRecorder test_ukm_recorder;
  WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  SafeBrowsingMetricsCollector* metrics_collector =
      SafeBrowsingMetricsCollectorFactory::GetForProfile(
          Profile::FromBrowserContext(web_contents->GetBrowserContext()));
  EXPECT_EQ(std::nullopt,
            metrics_collector->GetLatestEventTimestamp(
                SafeBrowsingMetricsCollector::EventType::
                    SECURITY_SENSITIVE_SAFE_BROWSING_INTERSTITIAL));
  base::HistogramTester histograms;
  SBThreatType threat_type = GetThreatType();
  std::string prefix = GetHistogramPrefix(threat_type);
  const std::string decision_histogram = "interstitial." + prefix + ".decision";
  const std::string interaction_histogram =
      "interstitial." + prefix + ".interaction";
  const std::string delay_histogram = "interstitial." + prefix + ".show_delay";
  const std::string delay_long_range_histogram =
      "interstitial." + prefix + ".show_delay_long_range";
  const std::string threat_source =
      UseV5() ? ".from_local_blocklist_v5" : ".from_device_v4";

  // TODO(nparker): Check for *.from_device as well.

  // 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.
  const GURL url = SetupWarningAndNavigate(browser());
  histograms.ExpectTotalCount(decision_histogram, 1);
  histograms.ExpectBucketCount(decision_histogram,
                               security_interstitials::MetricsHelper::SHOW, 1);
  histograms.ExpectBucketCount(
      decision_histogram + kInterstitialPreCommitPageHistogramSuffix,
      security_interstitials::MetricsHelper::SHOW, 1);
  histograms.ExpectTimeBucketCount(delay_histogram, base::TimeDelta::Min(), 1);
  histograms.ExpectTimeBucketCount(delay_histogram + threat_source,
                                   base::TimeDelta::Min(), 1);
  histograms.ExpectTimeBucketCount(delay_long_range_histogram,
                                   base::TimeDelta::Min(), 1);
  histograms.ExpectTimeBucketCount(delay_long_range_histogram + threat_source,
                                   base::TimeDelta::Min(), 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);

  // Check if security sensitive event is added to prefs.
  EXPECT_NE(std::nullopt,
            metrics_collector->GetLatestEventTimestamp(
                SafeBrowsingMetricsCollector::EventType::
                    SECURITY_SENSITIVE_SAFE_BROWSING_INTERSTITIAL));

  // Decision should be recorded.
  EXPECT_TRUE(ClickAndWaitForDetach("primary-button"));
  AssertNoInterstitial();  // Assert the interstitial is gone
  histograms.ExpectTotalCount(decision_histogram, 2);
  histograms.ExpectBucketCount(
      decision_histogram, security_interstitials::MetricsHelper::DONT_PROCEED,
      1);
  histograms.ExpectBucketCount(
      decision_histogram + kInterstitialPreCommitPageHistogramSuffix,
      security_interstitials::MetricsHelper::DONT_PROCEED, 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);

  // CloseReason histograms.
  histograms.ExpectTotalCount(kInterstitialCloseHistogram, 2);
  histograms.ExpectBucketCount(
      kInterstitialCloseHistogram,
      security_interstitials::SecurityInterstitialTabHelper::
          InterstitialCloseReason::INTERSTITIAL_SHOWN,
      1);
  histograms.ExpectBucketCount(
      kInterstitialCloseHistogram,
      security_interstitials::SecurityInterstitialTabHelper::
          InterstitialCloseReason::NAVIGATE_AWAY,
      1);

  // Check that we are recording the UKM when interstitial is shown and we do
  // not record for the interstitial bypassed.
  auto ukm_entries =
      test_ukm_recorder.GetEntriesByName("SafeBrowsingInterstitial");
  EXPECT_EQ(1u, ukm_entries.size());
  test_ukm_recorder.ExpectEntrySourceHasUrl(ukm_entries[0], url);
  test_ukm_recorder.ExpectEntryMetric(ukm_entries[0], "Shown", true);
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       Histograms_Proceed) {
  ukm::TestAutoSetUkmRecorder test_ukm_recorder;
  base::HistogramTester histograms;
  SBThreatType threat_type = GetThreatType();
  std::string prefix = GetHistogramPrefix(threat_type);
  const std::string decision_histogram = "interstitial." + prefix + ".decision";
  const std::string interaction_histogram =
      "interstitial." + prefix + ".interaction";
  const std::string delay_histogram = "interstitial." + prefix + ".show_delay";
  const std::string delay_long_range_histogram =
      "interstitial." + prefix + ".show_delay_long_range";
  const std::string threat_source =
      UseV5() ? ".from_local_blocklist_v5" : ".from_device_v4";

  // 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.
  GURL url = SetupWarningAndNavigate(browser());
  histograms.ExpectTotalCount(decision_histogram, 1);
  histograms.ExpectBucketCount(decision_histogram,
                               security_interstitials::MetricsHelper::SHOW, 1);
  histograms.ExpectBucketCount(
      decision_histogram + kInterstitialPreCommitPageHistogramSuffix,
      security_interstitials::MetricsHelper::SHOW, 1);
  histograms.ExpectTimeBucketCount(delay_histogram, base::TimeDelta::Min(), 1);
  histograms.ExpectTimeBucketCount(delay_histogram + threat_source,
                                   base::TimeDelta::Min(), 1);
  histograms.ExpectTimeBucketCount(delay_long_range_histogram,
                                   base::TimeDelta::Min(), 1);
  histograms.ExpectTimeBucketCount(delay_long_range_histogram + threat_source,
                                   base::TimeDelta::Min(), 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.
  EXPECT_TRUE(ClickAndWaitForDetach("proceed-link"));
  AssertNoInterstitial();  // Assert the interstitial is gone.
  histograms.ExpectTotalCount(decision_histogram, 2);
  histograms.ExpectBucketCount(
      decision_histogram, security_interstitials::MetricsHelper::PROCEED, 1);
  histograms.ExpectBucketCount(
      decision_histogram + kInterstitialPreCommitPageHistogramSuffix,
      security_interstitials::MetricsHelper::PROCEED, 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);
  histograms.ExpectBucketCount(
      interaction_histogram,
      security_interstitials::MetricsHelper::CLOSE_INTERSTITIAL_WITHOUT_UI, 0);

  // CloseReason histograms.
  histograms.ExpectTotalCount(kInterstitialCloseHistogram, 2);
  histograms.ExpectBucketCount(
      kInterstitialCloseHistogram,
      security_interstitials::SecurityInterstitialTabHelper::
          InterstitialCloseReason::INTERSTITIAL_SHOWN,
      1);
  histograms.ExpectBucketCount(
      kInterstitialCloseHistogram,
      security_interstitials::SecurityInterstitialTabHelper::
          InterstitialCloseReason::NAVIGATE_AWAY,
      1);

  // Check that we are recording the UKM when interstitial is shown and we do
  // not record for the interstitial bypassed.
  auto ukm_entries =
      test_ukm_recorder.GetEntriesByName("SafeBrowsingInterstitial");
  EXPECT_EQ(2u, ukm_entries.size());
  test_ukm_recorder.ExpectEntrySourceHasUrl(ukm_entries[0], url);
  test_ukm_recorder.ExpectEntrySourceHasUrl(ukm_entries[1], url);
  test_ukm_recorder.ExpectEntryMetric(ukm_entries[0], "Shown", true);
  test_ukm_recorder.ExpectEntryMetric(ukm_entries[1], "Bypassed", true);
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       Histograms_UserMadeNoDecision) {
  base::HistogramTester histograms;
  SBThreatType threat_type = GetThreatType();
  std::string prefix = GetHistogramPrefix(threat_type);
  const std::string interaction_histogram =
      "interstitial." + prefix + ".interaction";

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

  // Navigate to the page and show warning.
  GURL url = SetupWarningAndNavigate(browser());

  // Close tab without making an explicit choice on interstitial.
  chrome::CloseTab(browser());
  histograms.ExpectBucketCount(
      interaction_histogram,
      security_interstitials::MetricsHelper::CLOSE_INTERSTITIAL_WITHOUT_UI, 1);

  // CloseReason histograms.
  histograms.ExpectTotalCount(kInterstitialCloseHistogram, 2);
  histograms.ExpectBucketCount(
      kInterstitialCloseHistogram,
      security_interstitials::SecurityInterstitialTabHelper::
          InterstitialCloseReason::INTERSTITIAL_SHOWN,
      1);
  histograms.ExpectBucketCount(
      kInterstitialCloseHistogram,
      security_interstitials::SecurityInterstitialTabHelper::
          InterstitialCloseReason::CLOSE_TAB,
      1);
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, AllowlistRevisit) {
  GURL url = SetupWarningAndNavigate(browser());

  EXPECT_TRUE(ClickAndWaitForDetach("proceed-link"));
  AssertNoInterstitial();  // Assert the interstitial is gone.
  EXPECT_EQ(url, browser()
                     ->tab_strip_model()
                     ->GetActiveWebContents()
                     ->GetLastCommittedURL());

  // Unrelated pages should not be allowlisted now.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(kUnrelatedUrl)));
  AssertNoInterstitial();

  // The allowlisted page should remain allowlisted.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  AssertNoInterstitial();
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest, AllowlistUnsaved) {
  GURL url = SetupWarningAndNavigate(browser());

  // Navigate without making a decision.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(kUnrelatedUrl)));
  AssertNoInterstitial();

  // The non-allowlisted page should now show an interstitial.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  EXPECT_TRUE(WaitForReady(browser()));
  EXPECT_TRUE(ClickAndWaitForDetach("proceed-link"));
  AssertNoInterstitial();
}

#if (BUILDFLAG(IS_MAC) && !defined(NDEBUG)) || defined(MEMORY_SANITIZER)
// TODO(crbug.com/40721886): Address flay failure.
#define MAYBE_VerifyClientReportSentOnSBERAndNotIncognito \
  DISABLED_VerifyClientReportSentOnSBERAndNotIncognito
#else
#define MAYBE_VerifyClientReportSentOnSBERAndNotIncognito \
  VerifyClientReportSentOnSBERAndNotIncognito
#endif
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       MAYBE_VerifyClientReportSentOnSBERAndNotIncognito) {
  // This test verifies that client reports are sent under the legacy SBER
  // reporting mechanism. With the deprecation of SBER, this functionality is
  // being replaced by Enhanced Safe Browsing (ESB). The ESB equivalent of this
  // test can be found in the VerifyHistogramsAndClientReport test within the
  // SafeBrowsingBlockingPageAsyncChecksTimingTest suite.
  if (IsSberDeprecated()) {
    GTEST_SKIP() << "This test only applies to SBER logic.";
  }
  // The extended reporting opt-in is presented in the interstitial for malware,
  // phishing, and UwS threats.
  const bool expect_threat_details =
      SafeBrowsingBlockingPage::ShouldReportThreatDetails(GetThreatType());

  base::RunLoop threat_report_sent_loop;
  if (expect_threat_details) {
    SetReportSentCallback(threat_report_sent_loop.QuitClosure());
  }

  EnableExtendedReporting(true);
  GURL url = SetupWarningAndNavigate(browser());  // not incognito
  EXPECT_TRUE(report_sent());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       VerifyClientReportNotSentOnIncognito) {
  browser()->GetProfile()->GetPrefs()->SetBoolean(
      prefs::kSafeBrowsingSurveysEnabled, false);
  // The extended reporting opt-in is presented in the interstitial for malware,
  // phishing, and UwS threats.
  const bool expect_threat_details =
      SafeBrowsingBlockingPage::ShouldReportThreatDetails(GetThreatType());

  base::RunLoop threat_report_sent_loop;
  if (expect_threat_details) {
    SetReportSentCallback(threat_report_sent_loop.QuitClosure());
  }

  BrowserWindowInterface* incognito_browser = CreateIncognitoBrowser();
  incognito_browser->GetProfile()->GetPrefs()->SetBoolean(
      prefs::kSafeBrowsingScoutReportingEnabled, true);   // set up SBER
  GURL url = SetupWarningAndNavigate(incognito_browser);  // incognito
  // Check enhanced protection message is not shown.
  EXPECT_EQ(HIDDEN, ::safe_browsing::GetVisibility(
                        incognito_browser, "enhanced-protection-message"));

  EXPECT_FALSE(report_sent());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       VerifyClientReportNotSentWithoutSBER) {
  // The extended reporting opt-in is presented in the interstitial for malware,
  // phishing, and UwS threats.
  const bool expect_threat_details =
      SafeBrowsingBlockingPage::ShouldReportThreatDetails(GetThreatType());

  base::RunLoop threat_report_sent_loop;
  if (expect_threat_details) {
    SetReportSentCallback(threat_report_sent_loop.QuitClosure());
  }

  browser()->GetProfile()->GetPrefs()->SetBoolean(
      prefs::kSafeBrowsingScoutReportingEnabled, false);  // set up SBER
  GURL url = SetupWarningAndNavigate(browser());          // not incognito
  EXPECT_FALSE(report_sent());
}

namespace {

class SecurityStyleTestObserver : public content::WebContentsObserver {
 public:
  explicit SecurityStyleTestObserver(content::WebContents* web_contents)
      : content::WebContentsObserver(web_contents) {}

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

  std::optional<security_state::SecurityLevel> latest_security_level() const {
    return latest_security_level_;
  }

  // WebContentsObserver:
  void DidChangeVisibleSecurityState() override {
    latest_security_level_ =
        chrome_security_state::GetSecurityLevel(web_contents());
  }

 private:
  std::optional<security_state::SecurityLevel> latest_security_level_;
};

}  // namespace

// Test that the security indicator does not stay downgraded after
// clicking back from a Safe Browsing interstitial. Regression test for
// https://crbug.com/41283177.
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       SecurityStateGoBack) {
  // Navigate to a page so that there is somewhere to go back to.
  GURL start_url = GURL(kUnrelatedUrl);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), start_url));

  // The security indicator should be downgraded while the interstitial shows.
  GURL bad_url = embedded_test_server()->GetURL(kEmptyPage);
  SetupWarningAndNavigate(browser());
  WebContents* error_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(error_tab);
  ExpectSecurityIndicatorDowngrade(error_tab, 0u);
  content::NavigationEntry* entry =
      error_tab->GetController().GetVisibleEntry();
  ASSERT_TRUE(entry);
  ASSERT_EQ(bad_url, entry->GetURL());

  // Go back.
  EXPECT_EQ(VISIBLE, GetVisibility("primary-button"));
  EXPECT_EQ(HIDDEN, GetVisibility("details"));
  EXPECT_EQ(HIDDEN, GetVisibility("proceed-link"));
  EXPECT_EQ(HIDDEN, GetVisibility("error-code"));
  EXPECT_TRUE(Click("details-button"));
  EXPECT_EQ(VISIBLE, GetVisibility("details"));
  EXPECT_EQ(VISIBLE, GetVisibility("proceed-link"));
  EXPECT_EQ(HIDDEN, GetVisibility("error-code"));
  EXPECT_TRUE(ClickAndWaitForDetach("primary-button"));

  // The security indicator should *not* still be downgraded after going back.
  AssertNoInterstitial();
  WebContents* post_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(post_tab);
  entry = post_tab->GetController().GetVisibleEntry();
  ASSERT_TRUE(entry);
  EXPECT_EQ(start_url, entry->GetURL());
  ExpectNoSecurityIndicatorDowngrade(post_tab);

  ClearBadURL(bad_url);
  // Navigate to the URL that the interstitial was on, and check that it
  // is no longer marked as dangerous.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), bad_url));
  ExpectNoSecurityIndicatorDowngrade(
      browser()->tab_strip_model()->GetActiveWebContents());
}

// Test that the security indicator is downgraded after clicking through a
// Safe Browsing interstitial.
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       SecurityState_HTTP) {
  // The security indicator should be downgraded while the interstitial shows.
  SetupWarningAndNavigate(browser());
  WebContents* error_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(error_tab);
  ExpectSecurityIndicatorDowngrade(error_tab, 0u);

  // The security indicator should still be downgraded post-interstitial.
  EXPECT_TRUE(ClickAndWaitForDetach("proceed-link"));
  AssertNoInterstitial();
  WebContents* post_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(post_tab);
  ExpectSecurityIndicatorDowngrade(post_tab, 0u);
}

// Test that the security indicator is downgraded even if the website has valid
// HTTPS (meaning that the SB state overrides the HTTPS state).
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       SecurityState_ValidHTTPS) {
  WebContents* error_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(error_tab);
  SecurityStyleTestObserver observer(error_tab);

  // The security indicator should be downgraded while the interstitial shows.
  SetupWarningAndNavigateToValidHTTPS();
  ExpectSecurityIndicatorDowngrade(error_tab, 0u);

  // The security indicator should still be downgraded post-interstitial.
  EXPECT_TRUE(ClickAndWaitForDetach("proceed-link"));
  AssertNoInterstitial();
  WebContents* post_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(post_tab);
  ExpectSecurityIndicatorDowngrade(post_tab, 0u);
}

// Test that the security indicator is still downgraded after two interstitials
// are shown in a row (one for Safe Browsing, one for invalid HTTPS).
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       SecurityState_InvalidHTTPS) {
  // The security indicator should be downgraded while the interstitial shows.
  SetupWarningAndNavigateToInvalidHTTPS();
  WebContents* error_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(error_tab);
  ExpectSecurityIndicatorDowngrade(error_tab, 0u);

  // The security indicator should still be downgraded post-interstitial.
  EXPECT_TRUE(ClickAndWaitForDetach("proceed-link"));
  AssertNoInterstitial();
  WebContents* post_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(post_tab);
  // TODO(felt): Sometimes the cert status here is 0u, which is wrong.
  // Filed https://crbug.com/40085203 to investigate.
  ExpectSecurityIndicatorDowngrade(post_tab, net::CERT_STATUS_INVALID);
}

// Test that no safe browsing interstitial will be shown, if URL matches
// enterprise safe browsing allowlist domains.
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       VerifyEnterpriseAllowlist) {
  GURL url = embedded_test_server()->GetURL(kEmptyPage);
  // Add test server domain into the enterprise allowlist.
  base::ListValue allowlist;
  allowlist.Append(url.GetHost());
  browser()->GetProfile()->GetPrefs()->SetList(
      prefs::kSafeBrowsingAllowlistDomains, std::move(allowlist));

  SetURLThreatType(url, GetThreatType());
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  base::RunLoop().RunUntilIdle();
  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  EXPECT_TRUE(
      content::WaitForRenderFrameReady(contents->GetPrimaryMainFrame()));
  EXPECT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(contents));
}

INSTANTIATE_TEST_SUITE_P(
    SafeBrowsingBlockingPageBrowserTestWithThreatTypeAndIsolationSetting,
    SafeBrowsingBlockingPageBrowserTest,
    testing::Combine(
        testing::Values(
            SBThreatType::SB_THREAT_TYPE_URL_MALWARE,  // Threat types
            SBThreatType::SB_THREAT_TYPE_URL_PHISHING,
            SBThreatType::SB_THREAT_TYPE_URL_UNWANTED),
        testing::Bool(),
        testing::Bool(),
        testing::Bool()));  // Isolate all sites, SBER deprecated, Use V5.

// Check back and forward work correctly after clicking through an interstitial.
#if (BUILDFLAG(IS_MAC) && !defined(NDEBUG)) || defined(MEMORY_SANITIZER)
// TODO(crbug.com/40721886): Address flay failure.
#define MAYBE_NavigatingBackAndForth DISABLED_NavigatingBackAndForth
#else
#define MAYBE_NavigatingBackAndForth NavigatingBackAndForth
#endif
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       MAYBE_NavigatingBackAndForth) {
  // Load a safe page. (Despite the name, kMaliciousPage is not the page flagged
  // as bad in this test.)
  GURL safe_url(embedded_test_server()->GetURL(kMaliciousPage));
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), safe_url));
  // Navigate to a site that triggers a warning and click through it.
  const GURL bad_url = SetupWarningAndNavigate(browser());
  EXPECT_TRUE(ClickAndWaitForDetach("proceed-link"));
  AssertNoInterstitial();
  // Go back and check we are back on the safe site.
  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver back_observer(contents);
  contents->GetController().GoBack();
  back_observer.Wait();
  EXPECT_EQ(safe_url, contents->GetLastCommittedURL());
  // Check forward takes us back to the flagged site with no interstitial.
  content::TestNavigationObserver forward_observer(contents);
  contents->GetController().GoForward();
  forward_observer.Wait();
  WaitForReady(browser());
  AssertNoInterstitial();
  EXPECT_EQ(bad_url, contents->GetLastCommittedURL());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       TimestampInCSBRRClickedThroughBlockingPage) {
  EnableExtendedReporting(true);
  content::TestNavigationObserver observer(
      browser()->tab_strip_model()->GetActiveWebContents());
  SetupWarningAndNavigate(browser());

  // Proceed to unsafe site, sending CSBRR.
  EXPECT_TRUE(ClickAndWaitForDetach("proceed-link"));
  observer.WaitForNavigationFinished();

  // The "proceed" command should go back instead, if proceeding is disabled.
  AssertNoInterstitial();

  base::RunLoop threat_report_sent_loop;
  SetReportSentCallback(threat_report_sent_loop.QuitClosure());

  threat_report_sent_loop.Run();
  std::string serialized = GetReportSent();
  ClientSafeBrowsingReportRequest report;
  ASSERT_TRUE(report.ParseFromString(serialized));
  // The timstamp of the warning shown should be in CSBRRs.
  EXPECT_TRUE(report.has_warning_shown_timestamp_msec());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       FallbackCSBRRSentWithExpectedFieldsPopulated) {
  EnableExtendedReporting(true);
  content::TestNavigationObserver observer(
      browser()->tab_strip_model()->GetActiveWebContents());
  base::RunLoop threat_report_sent_loop;
  SetReportSentCallback(threat_report_sent_loop.QuitClosure());
  SetupWarningAndNavigate(browser());
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(
      browser()->tab_strip_model()->GetActiveWebContents()));

  // Send CSBRR without interactions.
  chrome::CloseTab(browser());
  observer.WaitForNavigationFinished();
  threat_report_sent_loop.Run();

  std::string serialized = GetReportSent();
  ClientSafeBrowsingReportRequest report;
  ASSERT_TRUE(report.ParseFromString(serialized));
  // The timstamp of the warning shown should be in CSBRRs.
  EXPECT_TRUE(report.has_warning_shown_timestamp_msec());

  // The `client_properties` field should be populated in fallback CSBRRs.
  EXPECT_TRUE(report.has_client_properties());
  EXPECT_TRUE(report.client_properties().has_url_api_type());
  EXPECT_TRUE(report.client_properties().has_is_async_check());
  EXPECT_EQ(report.client_properties().url_api_type(),
            UseV5()
                ? ClientSafeBrowsingReportRequest::PVER5_NATIVE_LOCAL_BLOCKLIST
                : ClientSafeBrowsingReportRequest::PVER4_NATIVE);
  EXPECT_FALSE(report.client_properties().is_async_check());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       IgnoreFutureAutoRevocation) {
  GURL url = SetupWarningAndNavigate(browser());
  EXPECT_FALSE(
      safety_hub_util::IsAbusiveNotificationRevocationIgnored(hcsm(), url));
  EXPECT_TRUE(ClickAndWaitForDetach("proceed-link"));
  AssertNoInterstitial();  // Assert the interstitial is gone.
  if (GetThreatType() == SBThreatType::SB_THREAT_TYPE_URL_PHISHING) {
    EXPECT_TRUE(
        safety_hub_util::IsAbusiveNotificationRevocationIgnored(hcsm(), url));
  } else {
    EXPECT_FALSE(
        safety_hub_util::IsAbusiveNotificationRevocationIgnored(hcsm(), url));
  }
}

class AntiPhishingTelemetryBrowserTest
    : public SafeBrowsingBlockingPageBrowserTest {};

INSTANTIATE_TEST_SUITE_P(
    AntiPhishingTelemetryBrowserTestWithThreatTypeAndIsolationSetting,
    AntiPhishingTelemetryBrowserTest,
    testing::Combine(
        testing::Values(
            SBThreatType::SB_THREAT_TYPE_URL_PHISHING,  // Threat types
            SBThreatType::SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING),
        testing::Bool(),
        testing::Bool(),
        testing::Bool()));  // Isolate all sites, SBER deprecated, Use V5.

IN_PROC_BROWSER_TEST_P(AntiPhishingTelemetryBrowserTest,
                       CheckReportListsInteractions) {
  EnableExtendedReporting(true);
  content::TestNavigationObserver observer(
      browser()->tab_strip_model()->GetActiveWebContents());
  SetupWarningAndNavigate(browser());

  // Show details 3x to make sure map records all 3 occurrences in interstitial
  // interaction map.
  EXPECT_TRUE(Click("details-button"));
  SendCommand(security_interstitials::CMD_SHOW_MORE_SECTION);
  SendCommand(security_interstitials::CMD_SHOW_MORE_SECTION);

  // Proceed to unsafe site, sending CSBRR.
  EXPECT_TRUE(ClickAndWaitForDetach("proceed-link"));
  observer.WaitForNavigationFinished();

  // The "proceed" command should go back instead, if proceeding is disabled.
  AssertNoInterstitial();

  scoped_refptr<content::MessageLoopRunner> threat_report_sent_runner(
      new content::MessageLoopRunner);
  SetReportSentCallback(threat_report_sent_runner->QuitClosure());

  threat_report_sent_runner->Run();
  std::string serialized = GetReportSent();
  ClientSafeBrowsingReportRequest report;
  ASSERT_TRUE(report.ParseFromString(serialized));

  EXPECT_EQ(report.url(), embedded_test_server()->GetURL(kEmptyPage));
  SBThreatType threat_type = GetThreatType();
  // SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING does not set the page_url because
  // its resource's navigation_url is empty.
  if (threat_type == SBThreatType::SB_THREAT_TYPE_URL_PHISHING) {
    EXPECT_EQ(report.page_url(), embedded_test_server()->GetURL(kEmptyPage));
  }
  // Create sorted vector of interstitial interactions. Sorted by
  // security_interstitial_interaction numeric value.
  std::vector<ClientSafeBrowsingReportRequest::InterstitialInteraction>
      interactions;
  for (auto interaction : report.interstitial_interactions()) {
    interactions.push_back(interaction);
  }
  std::sort(
      interactions.begin(), interactions.end(),
      [](const ClientSafeBrowsingReportRequest::InterstitialInteraction& a,
         const ClientSafeBrowsingReportRequest::InterstitialInteraction& b)
          -> bool {
        return a.security_interstitial_interaction() <
               b.security_interstitial_interaction();
      });

  // Verify the report interactions are complete and correct.
  EXPECT_EQ(report.interstitial_interactions_size(), 2);
  VerifyInteractionOccurrenceCount(
      report, interactions[0],
      ClientSafeBrowsingReportRequest::InterstitialInteraction::CMD_PROCEED, 1);
  VerifyInteractionOccurrenceCount(
      report, interactions[1],
      ClientSafeBrowsingReportRequest::InterstitialInteraction::
          CMD_SHOW_MORE_SECTION,
      3);
}

IN_PROC_BROWSER_TEST_P(AntiPhishingTelemetryBrowserTest,
                       CheckReportCloseTabOnInterstitial) {
  EnableExtendedReporting(true);
  content::TestNavigationObserver observer(
      browser()->tab_strip_model()->GetActiveWebContents());
  scoped_refptr<content::MessageLoopRunner> threat_report_sent_runner(
      new content::MessageLoopRunner);
  SetReportSentCallback(threat_report_sent_runner->QuitClosure());
  SetupWarningAndNavigate(browser());
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(
      browser()->tab_strip_model()->GetActiveWebContents()));

  // Send CSBRR without interactions.
  chrome::CloseTab(browser());
  observer.WaitForNavigationFinished();
  threat_report_sent_runner->Run();

  std::string serialized = GetReportSent();
  ClientSafeBrowsingReportRequest report;
  ASSERT_TRUE(report.ParseFromString(serialized));

  EXPECT_EQ(report.url(), embedded_test_server()->GetURL(kEmptyPage));
  // Verify the report interactions only contain interstitial interactions.
  SBThreatType threat_type = GetThreatType();
  if (threat_type == SBThreatType::SB_THREAT_TYPE_URL_PHISHING) {
    EXPECT_EQ(report.type(),
              ClientSafeBrowsingReportRequest_ReportType_URL_PHISHING);
    EXPECT_EQ(report.page_url(), embedded_test_server()->GetURL(kEmptyPage));
  }
  if (threat_type == SBThreatType::SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING) {
    EXPECT_EQ(
        report.type(),
        ClientSafeBrowsingReportRequest_ReportType_URL_CLIENT_SIDE_PHISHING);
  }
  EXPECT_EQ(report.interstitial_interactions_size(), 1);
  EXPECT_EQ(
      report.interstitial_interactions(0).security_interstitial_interaction(),
      ClientSafeBrowsingReportRequest::InterstitialInteraction::
          CMD_CLOSE_INTERSTITIAL_WITHOUT_UI);
  EXPECT_EQ(report.interstitial_interactions(0).occurrence_count(), 1);
}

IN_PROC_BROWSER_TEST_P(
    AntiPhishingTelemetryBrowserTest,
    CheckReportListsInteractionsNoExplicitInterstitialDecision) {
  EnableExtendedReporting(true);

  content::TestNavigationObserver observer(
      browser()->tab_strip_model()->GetActiveWebContents());
  scoped_refptr<content::MessageLoopRunner> threat_report_sent_runner(
      new content::MessageLoopRunner);
  SetReportSentCallback(threat_report_sent_runner->QuitClosure());

  // Navigate to the page and show warning.
  SetupWarningAndNavigate(browser());

  // Navigate away from interstitial without making an explicit choice through
  // the UI.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));

  observer.WaitForNavigationFinished();
  threat_report_sent_runner->Run();
  std::string serialized = GetReportSent();
  ClientSafeBrowsingReportRequest report;
  ASSERT_TRUE(report.ParseFromString(serialized));

  EXPECT_EQ(report.url(), embedded_test_server()->GetURL(kEmptyPage));
  SBThreatType threat_type = GetThreatType();
  if (threat_type == SBThreatType::SB_THREAT_TYPE_URL_PHISHING) {
    EXPECT_EQ(report.page_url(), embedded_test_server()->GetURL(kEmptyPage));
  }
  // Verify the report interaction only contains a
  // CMD_CLOSE_INTERSTITIAL_WITHOUT_UI interaction.
  EXPECT_EQ(report.interstitial_interactions_size(), 1);
  EXPECT_EQ(
      report.interstitial_interactions(0).security_interstitial_interaction(),
      ClientSafeBrowsingReportRequest::InterstitialInteraction::
          CMD_CLOSE_INTERSTITIAL_WITHOUT_UI);
  EXPECT_EQ(report.interstitial_interactions(0).occurrence_count(), 1);
}

class TrustSafetySentimentSurveyV2BrowserTest
    : public SafeBrowsingBlockingPageBrowserTest {
 public:
  TrustSafetySentimentSurveyV2BrowserTest() {
    scoped_feature_list_.InitAndEnableFeature(
        features::kTrustSafetySentimentSurveyV2);
  }
  ~TrustSafetySentimentSurveyV2BrowserTest() override = default;

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

INSTANTIATE_TEST_SUITE_P(
    TrustSafetySentimentSurveyV2BrowserTestWithThreatTypeAndIsolationSetting,
    TrustSafetySentimentSurveyV2BrowserTest,
    testing::Combine(
        testing::Values(
            SBThreatType::SB_THREAT_TYPE_URL_PHISHING,  // Threat types
            SBThreatType::SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING,
            SBThreatType::SB_THREAT_TYPE_URL_MALWARE,
            SBThreatType::SB_THREAT_TYPE_URL_UNWANTED),
        testing::Bool(),
        testing::Bool(),
        testing::Bool()));  // Isolate all sites, SBER deprecated, Use V5.

IN_PROC_BROWSER_TEST_P(TrustSafetySentimentSurveyV2BrowserTest,
                       TrustSafetySentimentTriggerredOnProceed) {
  GURL url = SetupWarningAndNavigate(browser());
  EXPECT_CALL(*mock_sentiment_service(),
              InteractedWithSafeBrowsingInterstitial(/*did_proceed=*/true,
                                                     GetThreatType()));
  EXPECT_TRUE(ClickAndWaitForDetach("proceed-link"));
  AssertNoInterstitial();  // Assert the interstitial is gone.
}

IN_PROC_BROWSER_TEST_P(TrustSafetySentimentSurveyV2BrowserTest,
                       TrustSafetySentimentTriggerredOnPrimaryButtonClick) {
  GURL url = SetupWarningAndNavigate(browser());
  EXPECT_CALL(*mock_sentiment_service(),
              InteractedWithSafeBrowsingInterstitial(/*did_proceed=*/false,
                                                     GetThreatType()));
  EXPECT_TRUE(ClickAndWaitForDetach("primary-button"));
  AssertNoInterstitial();  // Assert the interstitial is gone.
}

IN_PROC_BROWSER_TEST_P(TrustSafetySentimentSurveyV2BrowserTest,
                       TrustSafetySentimentTriggeredOnCloseInterstitialTab) {
  content::TestNavigationObserver observer(
      browser()->tab_strip_model()->GetActiveWebContents());
  GURL url = SetupWarningAndNavigate(browser());
  EXPECT_CALL(*mock_sentiment_service(),
              InteractedWithSafeBrowsingInterstitial(/*did_proceed=*/false,
                                                     GetThreatType()));
  chrome::CloseTab(browser());
  observer.WaitForNavigationFinished();
}

using RedInterstitialUIBrowserTest = SafeBrowsingBlockingPageBrowserTest;

INSTANTIATE_TEST_SUITE_P(
    RedInterstitialUIBrowserTestWithThreatTypeAndIsolationSetting,
    RedInterstitialUIBrowserTest,
    testing::Combine(
        testing::Values(
            SBThreatType::SB_THREAT_TYPE_URL_PHISHING,  // Threat types
            SBThreatType::SB_THREAT_TYPE_URL_MALWARE,
            SBThreatType::SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING,
            SBThreatType::SB_THREAT_TYPE_URL_UNWANTED),
        testing::Bool(),
        testing::Bool(),
        testing::Bool()));  // Isolate all sites, SBER deprecated, Use V5.

IN_PROC_BROWSER_TEST_P(RedInterstitialUIBrowserTest,
                       TestInterstitialPageStringsEnhancedEnabled) {
  safe_browsing::SetSafeBrowsingState(
      browser()->GetProfile()->GetPrefs(),
      safe_browsing::SafeBrowsingState::ENHANCED_PROTECTION);
  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  SetReportSentCallback(threat_report_sent_runner->QuitClosure());
  GURL url = SetupWarningAndNavigate(browser());

  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  SafeBrowsingBlockingPage* interstitial_page;
  security_interstitials::SecurityInterstitialTabHelper* helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          contents);
  ASSERT_TRUE(helper);
  interstitial_page = static_cast<SafeBrowsingBlockingPage*>(
      helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting());
  BaseSafeBrowsingErrorUI* temp_var = interstitial_page->sb_error_ui();
  base::DictValue load_time_data;
  temp_var->PopulateStringsForHtml(load_time_data);

  // Safe browsing blocking page should use correct heading and primary,
  // explanation, and proceed paragraph strings.
  ASSERT_EQ(
      load_time_data.Find("heading")->GetString(),
      base::UTF16ToUTF8(l10n_util::GetStringUTF16(IDS_SAFEBROWSING_HEADING)));
  SBThreatType threat_type = GetThreatType();
  if (threat_type == SBThreatType::SB_THREAT_TYPE_URL_PHISHING ||
      threat_type == SBThreatType::SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING) {
    ASSERT_EQ(load_time_data.Find("primaryParagraph")->GetString(),
              base::UTF16ToUTF8(l10n_util::GetStringUTF16(
                  IDS_PHISHING_V4_PRIMARY_PARAGRAPH)));
    ASSERT_EQ(load_time_data.Find("explanationParagraph")->GetString(),
              base::UTF16ToUTF8(l10n_util::GetStringUTF16(
                  IDS_PHISHING_V4_EXPLANATION_PARAGRAPH)));
    ASSERT_EQ(load_time_data.Find("finalParagraph")->GetString(),
              base::UTF16ToUTF8(l10n_util::GetStringUTF16(
                  IDS_PHISHING_V4_PROCEED_PARAGRAPH)));
  } else if (threat_type == SBThreatType::SB_THREAT_TYPE_URL_MALWARE) {
    ASSERT_EQ(load_time_data.Find("primaryParagraph")->GetString(),
              base::UTF16ToUTF8(
                  l10n_util::GetStringUTF16(IDS_MALWARE_V3_PRIMARY_PARAGRAPH)));
    ASSERT_EQ(load_time_data.Find("explanationParagraph")->GetString(),
              base::UTF16ToUTF8(l10n_util::GetStringUTF16(
                  IDS_MALWARE_V3_EXPLANATION_PARAGRAPH)));
    ASSERT_EQ(load_time_data.Find("finalParagraph")->GetString(),
              base::UTF16ToUTF8(
                  l10n_util::GetStringUTF16(IDS_MALWARE_V3_PROCEED_PARAGRAPH)));
  } else {
    ASSERT_EQ(load_time_data.Find("primaryParagraph")->GetString(),
              base::UTF16ToUTF8(
                  l10n_util::GetStringUTF16(IDS_HARMFUL_V3_PRIMARY_PARAGRAPH)));
    ASSERT_EQ(load_time_data.Find("explanationParagraph")->GetString(),
              base::UTF16ToUTF8(l10n_util::GetStringUTF16(
                  IDS_HARMFUL_V3_EXPLANATION_PARAGRAPH)));
    ASSERT_EQ(load_time_data.Find("finalParagraph")->GetString(),
              base::UTF16ToUTF8(
                  l10n_util::GetStringUTF16(IDS_HARMFUL_V3_PROCEED_PARAGRAPH)));
  }
}

IN_PROC_BROWSER_TEST_P(RedInterstitialUIBrowserTest,
                       TestInterstitialPageStringsStandardEnabled) {
  safe_browsing::SetSafeBrowsingState(
      browser()->GetProfile()->GetPrefs(),
      safe_browsing::SafeBrowsingState::STANDARD_PROTECTION);
  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  SetReportSentCallback(threat_report_sent_runner->QuitClosure());
  GURL url = SetupWarningAndNavigate(browser());

  WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
  SafeBrowsingBlockingPage* interstitial_page;
  security_interstitials::SecurityInterstitialTabHelper* helper =
      security_interstitials::SecurityInterstitialTabHelper::FromWebContents(
          contents);
  ASSERT_TRUE(helper);
  interstitial_page = static_cast<SafeBrowsingBlockingPage*>(
      helper->GetBlockingPageForCurrentlyCommittedNavigationForTesting());
  BaseSafeBrowsingErrorUI* temp_var = interstitial_page->sb_error_ui();
  base::DictValue load_time_data;
  temp_var->PopulateStringsForHtml(load_time_data);

  // Safe browsing blocking page should use correct header and enhanced
  // protection promo message strings.
  ASSERT_EQ(
      load_time_data.Find("heading")->GetString(),
      base::UTF16ToUTF8(l10n_util::GetStringUTF16(IDS_SAFEBROWSING_HEADING)));
  ASSERT_EQ(
      load_time_data.Find(security_interstitials::kEnhancedProtectionMessage)
          ->GetString(),
      base::UTF16ToUTF8(l10n_util::GetStringUTF16(
          IDS_SAFE_BROWSING_ENHANCED_PROTECTION_MESSAGE)));
}

class SafeBrowsingBlockingPageDelayedWarningBrowserTest
    : public InProcessBrowserTest,
      public testing::WithParamInterface<
          testing::tuple<bool /* IsolateAllSitesForTesting */,
                         bool /* Show warning on mouse click */>> {
 public:
  SafeBrowsingBlockingPageDelayedWarningBrowserTest() = default;

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

  void SetUp() override {
    std::vector<base::test::FeatureRefAndParams> enabled_features;
    if (warning_on_mouse_click_enabled()) {
      enabled_features.push_back(base::test::FeatureRefAndParams(
          kDelayedWarnings, {{"mouse", "true"}}));
    } else {
      enabled_features.push_back(
          base::test::FeatureRefAndParams(kDelayedWarnings, {}));
    }

    std::vector<base::test::FeatureRef> disabled_features;
    GetAdditionalFeatures(&enabled_features, &disabled_features);

    scoped_feature_list_.InitWithFeaturesAndParameters(enabled_features,
                                                       disabled_features);
    InProcessBrowserTest::SetUp();
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    if (testing::get<0>(GetParam())) {
      content::IsolateAllSitesForTesting(command_line);
    }
    // TODO(crbug.com/40285326): This fails with the field trial testing config.
    command_line->AppendSwitch("disable-field-trial-config");
  }

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    content::SetupCrossSiteRedirector(embedded_test_server());
    ASSERT_TRUE(embedded_test_server()->Start());
  }

  void CreatedBrowserMainParts(
      content::BrowserMainParts* browser_main_parts) override {
    InProcessBrowserTest::CreatedBrowserMainParts(browser_main_parts);
    // Test UI manager and test database manager should be set before
    // the browser is started but after threads are created.
    factory_.SetTestUIManager(new FakeSafeBrowsingUIManager(
        std::make_unique<TestSafeBrowsingBlockingPageFactory>()));
    factory_.SetTestDatabaseManager(new FakeSafeBrowsingDatabaseManager(
        content::GetUIThreadTaskRunner({})));
    SafeBrowsingService::RegisterFactory(&factory_);
    ThreatDetails::RegisterFactory(&details_factory_);
  }

  static bool TypeAndWaitForInterstitial(BrowserWindowInterface* browser) {
    // Type something. An interstitial should be shown.
    content::WebContents* contents =
        browser->GetTabStripModel()->GetActiveWebContents();
    content::TestNavigationObserver observer(contents);
    input::NativeWebKeyboardEvent event(
        blink::WebKeyboardEvent::Type::kRawKeyDown,
        blink::WebInputEvent::kNoModifiers,
        blink::WebInputEvent::GetStaticTimeStampForTests());
    event.text[0] = 'a';
    content::RenderWidgetHost* rwh =
        contents->GetPrimaryMainFrame()->GetRenderViewHost()->GetWidget();
    rwh->ForwardKeyboardEvent(event);
    observer.WaitForNavigationFinished();
    return WaitForReady(browser);
  }

  static void MouseClick(BrowserWindowInterface* browser) {
    blink::WebMouseEvent event(
        blink::WebInputEvent::Type::kMouseDown,
        blink::WebInputEvent::kNoModifiers,
        blink::WebInputEvent::GetStaticTimeStampForTests());
    event.button = blink::WebMouseEvent::Button::kLeft;
    event.SetPositionInWidget(100, 100);
    event.click_count = 1;
    content::WebContents* contents =
        browser->GetTabStripModel()->GetActiveWebContents();
    content::RenderWidgetHost* rwh =
        contents->GetPrimaryMainFrame()->GetRenderViewHost()->GetWidget();
    rwh->ForwardMouseEvent(event);
  }

  static bool MouseClickAndWaitForInterstitial(
      BrowserWindowInterface* browser) {
    content::WebContents* contents =
        browser->GetTabStripModel()->GetActiveWebContents();
    content::TestNavigationObserver observer(contents);
    MouseClick(browser);
    observer.WaitForNavigationFinished();
    return WaitForReady(browser);
  }

  static bool FullscreenAndWaitForInterstitial(
      BrowserWindowInterface* browser) {
    content::WebContents* contents =
        browser->GetTabStripModel()->GetActiveWebContents();
    content::TestNavigationObserver observer(contents);
    const char* const kScript = "document.body.webkitRequestFullscreen()";
    EXPECT_TRUE(content::ExecJs(contents, kScript));
    observer.WaitForNavigationFinished();
    return WaitForReady(browser);
  }

  static bool RequestPermissionAndWaitForInterstitial(
      BrowserWindowInterface* browser) {
    content::WebContents* contents =
        browser->GetTabStripModel()->GetActiveWebContents();
    content::TestNavigationObserver observer(contents);
    const char* const kScript = "Notification.requestPermission(function(){})";
    EXPECT_TRUE(content::ExecJs(contents, kScript));
    observer.WaitForNavigationFinished();
    return WaitForReady(browser);
  }

  static bool RequestDesktopCaptureAndWaitForInterstitial(
      BrowserWindowInterface* browser) {
    content::WebContents* contents =
        browser->GetTabStripModel()->GetActiveWebContents();
    content::TestNavigationObserver observer(contents);
    const char* const kScript = "navigator.mediaDevices.getDisplayMedia()";
    EXPECT_TRUE(content::ExecJs(contents, kScript,
                                content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
    observer.WaitForNavigationFinished();
    return WaitForReady(browser);
  }

 protected:
  // Subclasses can override to enable/disable features in SetUp().
  virtual void GetAdditionalFeatures(
      std::vector<base::test::FeatureRefAndParams>* enabled_features,
      std::vector<base::test::FeatureRef>* disabled_features) {}

  // Initiates a download and waits for it to be completed or cancelled.
  static void DownloadAndWaitForNavigation(BrowserWindowInterface* browser) {
    content::WebContents* contents =
        browser->GetTabStripModel()->GetActiveWebContents();
    content::TestNavigationObserver observer(contents);
    content::WebContentsConsoleObserver console_observer(contents);
    console_observer.SetPattern(
        "A SafeBrowsing warning is pending on this page*");

    ASSERT_TRUE(ui_test_utils::NavigateToURL(
        browser, GURL("data:application/octet-stream;base64,SGVsbG8=")));
    observer.WaitForNavigationFinished();
    ASSERT_TRUE(console_observer.Wait());

    ASSERT_EQ(1u, console_observer.messages().size());
  }

  void NavigateAndAssertNoInterstitial() {
    const GURL top_frame = embedded_test_server()->GetURL("/iframe.html");
    SetURLThreatType(top_frame, SBThreatType::SB_THREAT_TYPE_URL_PHISHING);

    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), top_frame));
    AssertNoInterstitial(browser());
  }

  bool warning_on_mouse_click_enabled() const {
    return testing::get<1>(GetParam());
  }

  void SetURLThreatType(const GURL& url, SBThreatType threat_type) {
    TestSafeBrowsingService* service = factory_.test_safe_browsing_service();
    ASSERT_TRUE(service);

    static_cast<FakeSafeBrowsingDatabaseManager*>(
        service->database_manager().get())
        ->AddDangerousUrl(url, threat_type);
  }

  std::u16string GetSecuritySummaryTextFromPageInfo() {
    auto* page_info = PageInfoBubbleView::GetPageInfoBubbleForTesting();
    auto* summary_label = page_info->GetViewByID(
        PageInfoViewFactory::VIEW_ID_PAGE_INFO_SECURITY_SUMMARY_LABEL);
    return static_cast<views::StyledLabel*>(summary_label)->GetText();
  }

 protected:
  base::test::ScopedFeatureList scoped_feature_list_;

 private:
  TestSafeBrowsingServiceFactory factory_;
  TestThreatDetailsFactory details_factory_;
};

#if BUILDFLAG(IS_WIN)
// Flaky on Windows CI bots (e.g. win11-arm64-rel-tests). See
// https://crbug.com/523387896.
#define MAYBE_NoInteraction_WarningNotShown \
  DISABLED_NoInteraction_WarningNotShown
#else
#define MAYBE_NoInteraction_WarningNotShown NoInteraction_WarningNotShown
#endif
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
                       MAYBE_NoInteraction_WarningNotShown) {
  base::HistogramTester histograms;
  NavigateAndAssertNoInterstitial();

  // Navigate away without interacting with the page.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
                       NotPhishing_WarningNotDelayed) {
  base::HistogramTester histograms;

  // Navigate to a non-phishing page. The warning should not be delayed.
  const GURL url = embedded_test_server()->GetURL("/empty.html");
  SetURLThreatType(url, SBThreatType::SB_THREAT_TYPE_URL_MALWARE);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  EXPECT_TRUE(WaitForReady(browser()));

  // Navigate to about:blank to "flush" metrics, if any.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
}

// Close the tab while a user interaction observer is attached to the tab. It
// shouldn't crash.
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
                       CloseTab_ShouldNotCrash) {
  base::HistogramTester histograms;
  chrome::NewTab(browser(), NewTabTypes::kNoUserAction);
  NavigateAndAssertNoInterstitial();
  chrome::CloseTab(browser());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
                       KeyPress_WarningShown) {
  constexpr int kTimeOnPage = 10;
  base::HistogramTester histograms;
  NavigateAndAssertNoInterstitial();
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();

  // Inject a test clock to test the histogram that records the time on the
  // delayed warning page before the warning shows or the user leaves the page.
  base::SimpleTestClock clock;
  SafeBrowsingUserInteractionObserver* observer =
      SafeBrowsingUserInteractionObserver::FromWebContents(web_contents);
  ASSERT_TRUE(observer);
  clock.SetNow(observer->GetCreationTimeForTesting());
  observer->SetClockForTesting(&clock);
  clock.Advance(base::Seconds(kTimeOnPage));

  // Type something. An interstitial should be shown.
  EXPECT_TRUE(TypeAndWaitForInterstitial(browser()));

  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "primary-button"));
  AssertNoInterstitial(browser());      // Assert the interstitial is gone
  EXPECT_EQ(GURL(url::kAboutBlankURL),  // Back to "about:blank"
            browser()
                ->tab_strip_model()
                ->GetActiveWebContents()
                ->GetLastCommittedURL());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
                       KeyPress_ESC_WarningNotShown) {
  base::HistogramTester histograms;
  NavigateAndAssertNoInterstitial();

  // Press ESC key. The interstitial should not be shown.
  content::WebContents* contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  input::NativeWebKeyboardEvent event(
      blink::WebKeyboardEvent::Type::kRawKeyDown,
      blink::WebInputEvent::kNoModifiers,
      blink::WebInputEvent::GetStaticTimeStampForTests());
  event.windows_key_code = ui::VKEY_ESCAPE;
  // Browser expects a non-synthesized event to have an os_event. Make the
  // browser ignore this event instead.
  event.skip_if_unhandled = true;
  contents->GetPrimaryMainFrame()
      ->GetRenderViewHost()
      ->GetWidget()
      ->ForwardKeyboardEvent(event);
  AssertNoInterstitial(browser());

  // Navigate to about:blank twice to "flush" metrics, if any. The delayed
  // warning user interaction observer may not have been deleted after the first
  // navigation.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
                       KeyPress_ModifierKey_WarningNotShown) {
  base::HistogramTester histograms;
  NavigateAndAssertNoInterstitial();

  // Press CTRL+A key. The interstitial should not be shown because we ignore
  // the CTRL modifier unless it's CTRL+C or CTRL+V.
  content::WebContents* contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  input::NativeWebKeyboardEvent event(
      blink::WebKeyboardEvent::Type::kRawKeyDown,
      blink::WebInputEvent::kControlKey,
      blink::WebInputEvent::GetStaticTimeStampForTests());
  event.windows_key_code = ui::VKEY_A;
  // Browser expects a non-synthesized event to have an os_event. Make the
  // browser ignore this event instead.
  event.skip_if_unhandled = true;
  contents->GetPrimaryMainFrame()
      ->GetRenderViewHost()
      ->GetWidget()
      ->ForwardKeyboardEvent(event);
  AssertNoInterstitial(browser());

  // Navigate to about:blank twice to "flush" metrics, if any. The delayed
  // warning user interaction observer may not have been deleted after the first
  // navigation.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
                       KeyPress_CtrlC_WarningShown) {
  base::HistogramTester histograms;
  NavigateAndAssertNoInterstitial();

  // Press CTRL+C. The interstitial should be shown.
  content::WebContents* contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver observer(contents);

  input::NativeWebKeyboardEvent event(
      blink::WebKeyboardEvent::Type::kRawKeyDown,
      blink::WebInputEvent::kControlKey,
      blink::WebInputEvent::GetStaticTimeStampForTests());
  event.windows_key_code = ui::VKEY_C;
  event.native_key_code = ui::VKEY_C;
  // We don't set event.skip_if_unhandled = true here because the event will be
  // consumed by UserInteractionObserver and not passed to the browser.
  contents->GetPrimaryMainFrame()
      ->GetRenderViewHost()
      ->GetWidget()
      ->ForwardKeyboardEvent(event);

  observer.WaitForNavigationFinished();
  EXPECT_TRUE(WaitForReady(browser()));

  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "primary-button"));
  AssertNoInterstitial(browser());      // Assert the interstitial is gone
  EXPECT_EQ(GURL(url::kAboutBlankURL),  // Back to "about:blank"
            browser()
                ->tab_strip_model()
                ->GetActiveWebContents()
                ->GetLastCommittedURL());
}

// Similar to KeyPress_ESC_WarningNotShown, but a character key is pressed after
// ESC. The warning should be shown.
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
                       KeyPress_ESCAndCharacterKey_WarningShown) {
  base::HistogramTester histograms;
  NavigateAndAssertNoInterstitial();

  // Press ESC key. The interstitial should not be shown.
  content::WebContents* contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  input::NativeWebKeyboardEvent event(
      blink::WebKeyboardEvent::Type::kRawKeyDown,
      blink::WebInputEvent::kNoModifiers,
      blink::WebInputEvent::GetStaticTimeStampForTests());
  event.windows_key_code = ui::VKEY_ESCAPE;
  // Browser expects a non-synthesized event to have an os_event. Make the
  // browser ignore this event instead.
  event.skip_if_unhandled = true;
  contents->GetPrimaryMainFrame()
      ->GetRenderViewHost()
      ->GetWidget()
      ->ForwardKeyboardEvent(event);
  base::RunLoop().RunUntilIdle();
  AssertNoInterstitial(browser());

  // Now type something. The interstitial should be shown.
  EXPECT_TRUE(TypeAndWaitForInterstitial(browser()));
  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "primary-button"));
  AssertNoInterstitial(browser());      // Assert the interstitial is gone
  EXPECT_EQ(GURL(url::kAboutBlankURL),  // Back to "about:blank"
            browser()
                ->tab_strip_model()
                ->GetActiveWebContents()
                ->GetLastCommittedURL());
}

// Disabled due to flakiness. https://crbug.com/332097746.
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
                       DISABLED_Fullscreen_WarningShown) {
  base::HistogramTester histograms;
  NavigateAndAssertNoInterstitial();

  // Page tries to enter fullscreen. An interstitial should be shown.
  EXPECT_TRUE(FullscreenAndWaitForInterstitial(browser()));
  EXPECT_FALSE(
      browser()->tab_strip_model()->GetActiveWebContents()->IsFullscreen());

  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "primary-button"));
  AssertNoInterstitial(browser());      // Assert the interstitial is gone
  EXPECT_EQ(GURL(url::kAboutBlankURL),  // Back to "about:blank"
            browser()
                ->tab_strip_model()
                ->GetActiveWebContents()
                ->GetLastCommittedURL());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
                       PermissionRequest_WarningShown) {
  base::HistogramTester histograms;
  NavigateAndAssertNoInterstitial();

  // Page tries to request a notification permission. The prompt should be
  // cancelled and an interstitial should be shown.
  EXPECT_TRUE(RequestPermissionAndWaitForInterstitial(browser()));

  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "primary-button"));
  AssertNoInterstitial(browser());      // Assert the interstitial is gone
  EXPECT_EQ(GURL(url::kAboutBlankURL),  // Back to "about:blank"
            browser()
                ->tab_strip_model()
                ->GetActiveWebContents()
                ->GetLastCommittedURL());

  histograms.ExpectTotalCount("Permissions.Action.Notifications", 1);
  histograms.ExpectBucketCount(
      "Permissions.Action.Notifications",
      static_cast<int>(permissions::PermissionAction::DENIED), 1);
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
                       JavaScriptDialog_WarningShown) {
  base::HistogramTester histograms;
  NavigateAndAssertNoInterstitial();

  // Page tries to show a JavaScript dialog. The dialog should be
  // cancelled and an interstitial should be shown.
  content::WebContents* contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver observer(contents);
  EXPECT_TRUE(content::ExecJs(contents, "alert('test')"));
  observer.WaitForNavigationFinished();
  EXPECT_TRUE(WaitForReady(browser()));

  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "primary-button"));
  AssertNoInterstitial(browser());      // Assert the interstitial is gone
  EXPECT_EQ(GURL(url::kAboutBlankURL),  // Back to "about:blank"
            browser()
                ->tab_strip_model()
                ->GetActiveWebContents()
                ->GetLastCommittedURL());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
                       DesktopCaptureRequest_WarningShown) {
  base::HistogramTester histograms;
  NavigateAndAssertNoInterstitial();

  // Page tries to request a desktop capture permission. The request should be
  // cancelled and an interstitial should be shown.
  EXPECT_TRUE(RequestDesktopCaptureAndWaitForInterstitial(browser()));
  EXPECT_FALSE(
      browser()->tab_strip_model()->GetActiveWebContents()->IsFullscreen());

  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "primary-button"));
  AssertNoInterstitial(browser());      // Assert the interstitial is gone
  EXPECT_EQ(GURL(url::kAboutBlankURL),  // Back to "about:blank"
            browser()
                ->tab_strip_model()
                ->GetActiveWebContents()
                ->GetLastCommittedURL());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
                       Paste_WarningShown) {
  base::HistogramTester histograms;
  NavigateAndAssertNoInterstitial();

  // Create a test context menu and send a paste command through it. This
  // should show the delayed interstitial.
  content::WebContents* contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  content::TestNavigationObserver observer(contents);
  std::unique_ptr<TestRenderViewContextMenu> menu(
      TestRenderViewContextMenu::Create(contents,
                                        contents->GetLastCommittedURL()));
  menu->ExecuteCommand(IDC_CONTENT_CONTEXT_PASTE, 0);
  observer.WaitForNavigationFinished();
  EXPECT_TRUE(WaitForReady(browser()));
}

// The user clicks on the page. Feature isn't configured to show a warning on
// mouse clicks. We should record that the user interacted with the page, but
// shouldn't shown an interstitial.
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
                       MouseClick_WarningNotShown) {
  if (warning_on_mouse_click_enabled()) {
    return;
  }
  base::HistogramTester histograms;
  NavigateAndAssertNoInterstitial();

  // Click on the page. An interstitial shouldn't be shown because the feature
  // parameter is off.
  MouseClick(browser());
  AssertNoInterstitial(browser());

  // Navigate away to "flush" the metrics.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
                       MouseClick_WarningShown) {
  if (!warning_on_mouse_click_enabled()) {
    return;
  }
  base::HistogramTester histograms;
  NavigateAndAssertNoInterstitial();

  // Click on the page. An interstitial should be shown because the feature
  // parameter is on.
  EXPECT_TRUE(MouseClickAndWaitForInterstitial(browser()));

  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "primary-button"));
  AssertNoInterstitial(browser());      // Assert the interstitial is gone
  EXPECT_EQ(GURL(url::kAboutBlankURL),  // Back to "about:blank"
            browser()
                ->tab_strip_model()
                ->GetActiveWebContents()
                ->GetLastCommittedURL());
}

// This test initiates a download when a warning is delayed. The download should
// be cancelled and the interstitial should not be shown.
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
                       Download_CancelledWithNoInterstitial) {
  base::HistogramTester histograms;
  NavigateAndAssertNoInterstitial();

  DownloadAndWaitForNavigation(browser());
  AssertNoInterstitial(browser());

  // Navigate away to "flush" the metrics.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
                       InteractionAfterNonCommittingNavigation_Interstitial) {
  base::HistogramTester histograms;
  NavigateAndAssertNoInterstitial();

  const GURL url_204 = embedded_test_server()->GetURL("/page204.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url_204));
  AssertNoInterstitial(browser());

  EXPECT_TRUE(TypeAndWaitForInterstitial(browser()));

  // Navigate away to "flush" the metrics.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
}

// This test navigates to a page with password form and submits a password. The
// warning should be delayed, the "Save Password" bubble should not be shown,
// and a histogram entry for the password save should be recorded.
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageDelayedWarningBrowserTest,
                       PasswordSaveDisabled) {
  base::HistogramTester histograms;

  // This is needed for tests using BubbleObserver
  content::WebContents* contents =
      browser()->tab_strip_model()->GetActiveWebContents();

  // Navigate to the page.
  content::TestNavigationObserver observer1(contents);
  const GURL url =
      embedded_test_server()->GetURL("/password/password_form.html");
  SetURLThreatType(url, SBThreatType::SB_THREAT_TYPE_URL_PHISHING);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  observer1.Wait();

  // Submit a password.
  PasswordsNavigationObserver observer2(contents);
  BubbleObserver prompt_observer(contents);
  std::string fill_and_submit =
      "document.getElementById('retry_password_field').value = 'pw';"
      "document.getElementById('retry_submit_button').click()";
  ASSERT_TRUE(content::ExecJs(contents, fill_and_submit));
  ASSERT_TRUE(observer2.Wait());
  EXPECT_FALSE(prompt_observer.IsSavePromptShownAutomatically());
  PasswordManagerBrowserTestBase::WaitForPasswordStore(browser());
  AssertNoInterstitial(browser());

  // Navigate away to "flush" the metrics.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
}

INSTANTIATE_TEST_SUITE_P(
    SafeBrowsingBlockingPageWithDelayedWarningsBrowserTest,
    SafeBrowsingBlockingPageDelayedWarningBrowserTest,
    testing::Combine(
        testing::Values(false, true), /* IsolateAllSitesForTesting */
        testing::Values(false, true) /* Show warning on mouse click */));

// Test that SafeBrowsingBlockingPage properly decodes IDN URLs that are
// displayed.
class SafeBrowsingBlockingPageIDNTest
    : public SecurityInterstitialIDNTest,
      public testing::WithParamInterface<std::tuple<SBThreatType, bool>> {
 public:
  SafeBrowsingBlockingPageIDNTest() {
    feature_list_.InitWithFeatureState(safe_browsing::kLocalListsUseSBv5,
                                       UseV5());
  }

  SBThreatType GetThreatType() const { return std::get<0>(GetParam()); }
  bool UseV5() const { return std::get<1>(GetParam()); }

 protected:
  // SecurityInterstitialIDNTest implementation
  security_interstitials::SecurityInterstitialPage* CreateInterstitial(
      content::WebContents* contents,
      const GURL& request_url) const override {
    SafeBrowsingUIManager::CreateAllowlistForTesting(contents);
    SafeBrowsingService* sb_service =
        g_browser_process->safe_browsing_service();
    auto* primary_main_frame = contents->GetPrimaryMainFrame();
    const content::GlobalRenderFrameHostId primary_main_frame_id =
        primary_main_frame->GetGlobalId();
    SafeBrowsingBlockingPage::UnsafeResource resource;

    resource.url = request_url;
    resource.threat_type = GetThreatType();
    resource.rfh_locator = security_interstitials::UnsafeResourceLocator::
        CreateForRenderFrameToken(primary_main_frame_id.child_id.value(),
                                  primary_main_frame->GetFrameToken().value());
    resource.threat_source =
        UseV5() ? safe_browsing::ThreatSource::LOCAL_PVER5_LOCAL_BLOCKLIST
                : safe_browsing::ThreatSource::LOCAL_PVER4;

    auto* ui_manager = sb_service->ui_manager().get();
    return ui_manager->CreateBlockingPage(
        contents, request_url, {resource}, /*forward_extension_event=*/false,
        /*blocked_page_shown_timestamp=*/std::nullopt);
  }

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

// TODO(crbug.com/40666794): VerifyIDNDecoded does not work with committed
// interstitials, this test should be re-enabled once it is adapted.
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageIDNTest,
                       DISABLED_SafeBrowsingBlockingPageDecodesIDN) {
  EXPECT_TRUE(VerifyIDNDecoded());
}

INSTANTIATE_TEST_SUITE_P(
    SafeBrowsingBlockingPageIDNTestWithThreatType,
    SafeBrowsingBlockingPageIDNTest,
    testing::Combine(testing::Values(SBThreatType::SB_THREAT_TYPE_URL_MALWARE,
                                     SBThreatType::SB_THREAT_TYPE_URL_PHISHING,
                                     SBThreatType::SB_THREAT_TYPE_URL_UNWANTED),
                     testing::Bool()));

class SafeBrowsingBlockingPageEnhancedProtectionMessageTest
    : public policy::PolicyTest {
 public:
  SafeBrowsingBlockingPageEnhancedProtectionMessageTest() = default;

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

  void SetUp() override { InProcessBrowserTest::SetUp(); }

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    content::SetupCrossSiteRedirector(embedded_test_server());
    ASSERT_TRUE(embedded_test_server()->Start());
  }

  void CreatedBrowserMainParts(
      content::BrowserMainParts* browser_main_parts) override {
    policy::PolicyTest::CreatedBrowserMainParts(browser_main_parts);
    // Test UI manager and test database manager should be set before
    // the browser is started but after threads are created.
    factory_.SetTestUIManager(new FakeSafeBrowsingUIManager(
        std::make_unique<TestSafeBrowsingBlockingPageFactory>()));
    factory_.SetTestDatabaseManager(new FakeSafeBrowsingDatabaseManager(
        content::GetUIThreadTaskRunner({})));
    SafeBrowsingService::RegisterFactory(&factory_);
    ThreatDetails::RegisterFactory(&details_factory_);
  }

 protected:
  void SetupWarningAndNavigateToURL(GURL url, BrowserWindowInterface* browser) {
    TestSafeBrowsingService* service = factory_.test_safe_browsing_service();
    ASSERT_TRUE(service);

    static_cast<FakeSafeBrowsingDatabaseManager*>(
        service->database_manager().get())
        ->AddDangerousUrl(url, SBThreatType::SB_THREAT_TYPE_URL_MALWARE);

    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser, url));
    EXPECT_TRUE(WaitForReady(browser));
  }

  // A test should call this function if it is expected to trigger a threat
  // report.
  void SetReportSentCallback(base::OnceClosure callback) {
    static_cast<FakeSafeBrowsingUIManager*>(
        factory_.test_safe_browsing_service()->ui_manager().get())
        ->set_threat_details_done_callback(std::move(callback));
  }

 private:
  TestSafeBrowsingServiceFactory factory_;
  TestThreatDetailsFactory details_factory_;
  base::test::ScopedFeatureList scoped_feature_list_;
};

IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageEnhancedProtectionMessageTest,
                       VerifyEnhancedProtectionMessageShownAndClicked) {
  safe_browsing::SetExtendedReportingPrefForTests(
      browser()->GetProfile()->GetPrefs(), true);
  safe_browsing::SetSafeBrowsingState(
      browser()->GetProfile()->GetPrefs(),
      safe_browsing::SafeBrowsingState::STANDARD_PROTECTION);
  SetupWarningAndNavigateToURL(embedded_test_server()->GetURL("/empty.html"),
                               browser());

  // Check SBER opt in is not shown.
  EXPECT_EQ(HIDDEN, ::safe_browsing::GetVisibility(
                        browser(), "extended-reporting-opt-in"));
  // Check enhanced protection message is shown.
  EXPECT_EQ(VISIBLE, ::safe_browsing::GetVisibility(
                         browser(), "enhanced-protection-message"));
  WebContents* interstitial_tab =
      browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(interstitial_tab);
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(
      browser()->tab_strip_model()->GetActiveWebContents()));

  content::TestNavigationObserver nav_observer(nullptr);
  nav_observer.StartWatchingNewWebContents();
  // Click the enhanced protection link.
  EXPECT_TRUE(Click(browser(), "enhanced-protection-link"));

  nav_observer.Wait();

  // There are two tabs open.
  EXPECT_EQ(2, browser()->tab_strip_model()->count());
  // The second tab is visible.
  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());

  // Assert the interstitial is not present in the foreground tab.
  ASSERT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(
      browser()->tab_strip_model()->GetActiveWebContents()));

  // Foreground tab displays the setting page.
  WebContents* new_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(new_tab);
  EXPECT_EQ(GURL(kEnhancedProtectionUrl), new_tab->GetLastCommittedURL());

  // Interstitial should still display in the background tab.
  browser()->tab_strip_model()->ActivateTabAt(
      0, TabStripUserGestureDetails(
             TabStripUserGestureDetails::GestureType::kOther));
  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
  EXPECT_EQ(interstitial_tab,
            browser()->tab_strip_model()->GetActiveWebContents());
  EXPECT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(
      browser()->tab_strip_model()->GetActiveWebContents()));

  // Set threat report sent runner, since a report will be sent when web
  // contents are destroyed.
  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  SetReportSentCallback(threat_report_sent_runner->QuitClosure());
}

IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageEnhancedProtectionMessageTest,
                       VerifyEnhancedProtectionMessageNotShownAlreadyInEp) {
  safe_browsing::SetExtendedReportingPrefForTests(
      browser()->GetProfile()->GetPrefs(), true);
  safe_browsing::SetSafeBrowsingState(
      browser()->GetProfile()->GetPrefs(),
      safe_browsing::SafeBrowsingState::ENHANCED_PROTECTION);
  SetupWarningAndNavigateToURL(embedded_test_server()->GetURL("/empty.html"),
                               browser());
  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  SetReportSentCallback(threat_report_sent_runner->QuitClosure());

  EXPECT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(
      browser()->tab_strip_model()->GetActiveWebContents()));
  // Check enhanced protection message is not shown.
  EXPECT_EQ(HIDDEN, ::safe_browsing::GetVisibility(
                        browser(), "enhanced-protection-message"));
}

IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageEnhancedProtectionMessageTest,
                       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);
  UpdateProviderPolicy(policies);
  SetupWarningAndNavigateToURL(embedded_test_server()->GetURL("/empty.html"),
                               browser());

  EXPECT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(
      browser()->tab_strip_model()->GetActiveWebContents()));
  // Check enhanced protection message is not shown.
  EXPECT_EQ(HIDDEN, ::safe_browsing::GetVisibility(
                        browser(), "enhanced-protection-message"));
}

class SafeBrowsingBlockingPageAsyncChecksTestBase
    : public InProcessBrowserTest {
 public:
  SafeBrowsingBlockingPageAsyncChecksTestBase() = default;

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    content::SetupCrossSiteRedirector(embedded_test_server());
    ASSERT_TRUE(embedded_test_server()->Start());
    // The tests expect to load chrome://safe-browsing, which is an
    // internal debugging page.
    g_browser_process->local_state()->SetBoolean(
        chrome_urls::kInternalOnlyUisEnabled, true);
  }
  void CreatedBrowserMainParts(
      content::BrowserMainParts* browser_main_parts) override {
    InProcessBrowserTest::CreatedBrowserMainParts(browser_main_parts);
    // Test UI manager and test database manager should be set before
    // the browser is started but after threads are created.
    factory_.SetTestUIManager(new FakeSafeBrowsingUIManager(
        std::make_unique<TestSafeBrowsingBlockingPageFactory>()));
    factory_.SetTestDatabaseManager(new FakeSafeBrowsingDatabaseManager(
        content::GetUIThreadTaskRunner({})));
    SafeBrowsingService::RegisterFactory(&factory_);
  }

 protected:
  void SetupUrlRealTimeVerdictInCacheManager(
      GURL url,
      Profile* profile,
      RTLookupResponse::ThreatInfo::VerdictType verdict_type,
      std::optional<RTLookupResponse::ThreatInfo::ThreatType> threat_type) {
    safe_browsing::VerdictCacheManagerFactory::GetForProfile(profile)
        ->CacheArtificialRealTimeUrlVerdict(url.spec(), verdict_type,
                                            threat_type);
  }
  void SetUpEnterpriseUrlCheck() {
    browser()->GetProfile()->GetPrefs()->SetInteger(
        enterprise_connectors::kEnterpriseRealTimeUrlCheckMode,
        enterprise_connectors::REAL_TIME_CHECK_FOR_MAINFRAME_ENABLED);
    browser()->GetProfile()->GetPrefs()->SetInteger(
        enterprise_connectors::kEnterpriseRealTimeUrlCheckScope,
        policy::POLICY_SCOPE_MACHINE);
    SetDMTokenForTesting(policy::DMToken::CreateValidToken("dm_token"));
  }
  void NavigateToURLAndWaitForAsyncChecks(GURL url) {
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
    SafeBrowsingBlockingPageTestHelper::MaybeWaitForAsyncChecksToComplete(
        browser()->tab_strip_model()->GetActiveWebContents(),
        factory_.test_safe_browsing_service()->ui_manager().get(),
        /*wait_for_load_stop=*/true);
  }

  TestSafeBrowsingServiceFactory factory_;
};

using SafeBrowsingBlockingPageAsyncChecksTest =
    SafeBrowsingBlockingPageAsyncChecksTestBase;

IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageAsyncChecksTest,
                       EnterpriseRealTimeUrlCheck) {
  base::HistogramTester histogram_tester;
  safe_browsing::SetSafeBrowsingState(
      browser()->GetProfile()->GetPrefs(),
      safe_browsing::SafeBrowsingState::STANDARD_PROTECTION);
  SetUpEnterpriseUrlCheck();

  GURL url = embedded_test_server()->GetURL(kEmptyPage);
  SetupUrlRealTimeVerdictInCacheManager(url, browser()->GetProfile(),
                                        RTLookupResponse::ThreatInfo::SAFE,
                                        /*threat_type=*/std::nullopt);
  NavigateToURLAndWaitForAsyncChecks(url);
  ASSERT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(
      browser()->tab_strip_model()->GetActiveWebContents()));

  // Whether or not async checks are enabled, only a sync check is performed
  // (the enterprise URT check).
  histogram_tester.ExpectTotalCount(
      "SafeBrowsing.BrowserThrottle.TotalDelay2.EnterpriseFullUrlLookup",
      /*expected_count=*/1);
}

IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageAsyncChecksTest,
                       ConsumerRealTimeUrlCheck) {
  base::HistogramTester histogram_tester;
  safe_browsing::SetSafeBrowsingState(
      browser()->GetProfile()->GetPrefs(),
      safe_browsing::SafeBrowsingState::STANDARD_PROTECTION);
  browser()->GetProfile()->GetPrefs()->SetBoolean(
      unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled, true);

  GURL url = embedded_test_server()->GetURL(kEmptyPage);
  SetupUrlRealTimeVerdictInCacheManager(url, browser()->GetProfile(),
                                        RTLookupResponse::ThreatInfo::SAFE,
                                        /*threat_type=*/std::nullopt);
  NavigateToURLAndWaitForAsyncChecks(url);
  ASSERT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(
      browser()->tab_strip_model()->GetActiveWebContents()));

  // When async checks are enabled, the sync check is an HPD check.
  histogram_tester.ExpectTotalCount(
      "SafeBrowsing.BrowserThrottle.TotalDelay2.HashPrefixDatabaseCheck",
      /*expected_count=*/1);
}

class SafeBrowsingBlockingPageAsyncChecksTimingTestBase
    : public SafeBrowsingBlockingPageAsyncChecksTestBase {
 public:
  SafeBrowsingBlockingPageAsyncChecksTimingTestBase() = default;

  void SetUp() override {
    feature_list_.InitWithFeatures(
        {kCreateWarningShownClientSafeBrowsingReports}, {kRedWarningSurvey});
    SafeBrowsingBlockingPageAsyncChecksTestBase::SetUp();
  }

  void TearDown() override {
    RealTimeUrlLookupServiceFactory::GetInstance()
        ->SetURLLoaderFactoryForTesting(nullptr);
    SafeBrowsingBlockingPageAsyncChecksTestBase::TearDown();
    ThreatDetails::RegisterFactory(nullptr);
  }

  void CreatedBrowserMainParts(
      content::BrowserMainParts* browser_main_parts) override {
    SafeBrowsingBlockingPageAsyncChecksTestBase::CreatedBrowserMainParts(
        browser_main_parts);
    ThreatDetails::RegisterFactory(&details_factory_);
  }

 protected:
  // Helper struct for test cases.
  struct UrlAndIsUnsafe {
    std::string relative_url;
    bool is_unsafe;
  };

  void SetURLLoaderFactoryForTesting() {
    auto ref_counted_url_loader_factory =
        base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
            &test_url_loader_factory_);
    RealTimeUrlLookupServiceFactory::GetInstance()
        ->SetURLLoaderFactoryForTesting(ref_counted_url_loader_factory);
  }

  void EnableAsyncCheck() {
    SetURLLoaderFactoryForTesting();
    // Enable enhanced protection which enables real-time URL check which is
    // conducted asynchronously.
    safe_browsing::SetSafeBrowsingState(
        browser()->GetProfile()->GetPrefs(),
        safe_browsing::SafeBrowsingState::ENHANCED_PROTECTION);
  }

  void SetURLThreatType(const GURL& url, SBThreatType threat_type) {
    TestSafeBrowsingService* service = factory_.test_safe_browsing_service();
    ASSERT_TRUE(service);

    static_cast<FakeSafeBrowsingDatabaseManager*>(
        service->database_manager().get())
        ->AddDangerousUrl(url, threat_type);
  }

  void SetURLHighConfidenceAllowlistMatch(const GURL& url,
                                          bool match_allowlist) {
    TestSafeBrowsingService* service = factory_.test_safe_browsing_service();
    ASSERT_TRUE(service);

    static_cast<FakeSafeBrowsingDatabaseManager*>(
        service->database_manager().get())
        ->SetHighConfidenceAllowlistMatchResult(url, match_allowlist);
  }

  void ReturnUrlRealTimeVerdictInUrlLoader(GURL url, bool is_unsafe) {
    constexpr char kRealTimeLookupUrl[] =
        "https://safebrowsing.google.com/safebrowsing/clientreport/realtime";
    RTLookupResponse response;
    RTLookupResponse::ThreatInfo* new_threat_info = response.add_threat_info();
    RTLookupResponse::ThreatInfo threat_info;
    if (is_unsafe) {
      threat_info.set_verdict_type(RTLookupResponse::ThreatInfo::DANGEROUS);
      threat_info.set_threat_type(
          RTLookupResponse::ThreatInfo::SOCIAL_ENGINEERING);
    } else {
      threat_info.set_verdict_type(RTLookupResponse::ThreatInfo::SAFE);
    }
    threat_info.set_cache_duration_sec(60);
    threat_info.set_cache_expression_using_match_type(url.GetHost());
    threat_info.set_cache_expression_match_type(
        RTLookupResponse::ThreatInfo::COVERING_MATCH);
    *new_threat_info = threat_info;
    std::string expected_response_str;
    response.SerializeToString(&expected_response_str);
    test_url_loader_factory_.AddResponse(kRealTimeLookupUrl,
                                         expected_response_str);
  }

  // The following events happen in sequence:
  //   1. WillProcessResponse is called.
  //   2. Safe Browsing checks complete.
  //   3. Navigation finished.
  GURL SetupWarningShownBetweenProcessResponseAndFinishNavigationAndNavigate(
      std::vector<UrlAndIsUnsafe> url_and_server_redirects) {
    CHECK(!url_and_server_redirects.empty());
    GURL original_url = embedded_test_server()->GetURL(
        url_and_server_redirects.front().relative_url);
    GURL final_url = embedded_test_server()->GetURL(
        url_and_server_redirects.back().relative_url);
    content::TestNavigationManager navigation_manager(
        browser()->tab_strip_model()->GetActiveWebContents(), original_url);
    ui_test_utils::NavigateToURLWithDisposition(
        browser(), original_url, WindowOpenDisposition::CURRENT_TAB,
        ui_test_utils::BROWSER_TEST_NO_WAIT);
    EXPECT_TRUE(navigation_manager.WaitForResponse());

    // At this point, WillProcessResponse is called so the async checker is
    // transferred to AsyncCheckTracker.
    AsyncCheckTracker* tracker =
        safe_browsing::AsyncCheckTracker::GetOrCreateForWebContents(
            browser()->tab_strip_model()->GetActiveWebContents(),
            factory_.test_safe_browsing_service()->ui_manager().get(),
            /*should_sync_checker_check_allowlist=*/false);
    EXPECT_EQ(tracker->PendingCheckersSizeForTesting(), 1u);

    GURL interstitial_url;
    for (const auto& url_and_server_redirect : url_and_server_redirects) {
      GURL url =
          embedded_test_server()->GetURL(url_and_server_redirect.relative_url);
      ReturnUrlRealTimeVerdictInUrlLoader(url,
                                          url_and_server_redirect.is_unsafe);
      if (url_and_server_redirect.is_unsafe) {
        interstitial_url = url;
      }
    }
    SafeBrowsingBlockingPageTestHelper::MaybeWaitForAsyncChecksToComplete(
        browser()->tab_strip_model()->GetActiveWebContents(),
        factory_.test_safe_browsing_service()->ui_manager().get(),
        /*wait_for_load_stop=*/false);

    // At this point, the async check is completed, but the navigation has not
    // yet finished.
    navigation_manager.ResumeNavigation();
    EXPECT_TRUE(navigation_manager.WaitForNavigationFinished());

    // After the navigation is finished, we need to wait for the navigation of
    // the interstitial to complete.
    content::TestNavigationManager interstitial_navigation_manager(
        browser()->tab_strip_model()->GetActiveWebContents(), interstitial_url);
    EXPECT_TRUE(interstitial_navigation_manager.WaitForNavigationFinished());
    content::WaitForLoadStop(
        browser()->tab_strip_model()->GetActiveWebContents());

    EXPECT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(
        browser()->tab_strip_model()->GetActiveWebContents()));

    // Reset dangerous response so future URLs are not accidentally flagged
    // by real-time URL check.
    test_url_loader_factory_.ClearResponses();
    return final_url;
  }

  void NavigateAndAwaitNavigationFinished(std::string relative_url) {
    GURL original_url = embedded_test_server()->GetURL(relative_url);
    content::TestNavigationManager navigation_manager(
        browser()->tab_strip_model()->GetActiveWebContents(), original_url);
    ui_test_utils::NavigateToURLWithDisposition(
        browser(), original_url, WindowOpenDisposition::CURRENT_TAB,
        ui_test_utils::BROWSER_TEST_NO_WAIT);
    EXPECT_TRUE(navigation_manager.WaitForNavigationFinished());
  }

  // This is expected to be used after NavigateAndAwaitNavigationFinished has
  // completed navigating to the same URLs. This method expects that at least
  // one of the URLs is unsafe.
  GURL ReturnUrlRealTimeVerdictsForUnsafeChain(
      std::vector<UrlAndIsUnsafe> url_and_server_redirects) {
    GURL final_url = embedded_test_server()->GetURL(
        url_and_server_redirects.back().relative_url);

    // At this point, the navigation has finished but the async check has not
    // yet completed.
    AsyncCheckTracker* tracker =
        safe_browsing::AsyncCheckTracker::GetOrCreateForWebContents(
            browser()->tab_strip_model()->GetActiveWebContents(),
            factory_.test_safe_browsing_service()->ui_manager().get(),
            /*should_sync_checker_check_allowlist=*/false);
    EXPECT_EQ(tracker->PendingCheckersSizeForTesting(), 1u);

    for (const auto& url_and_server_redirect : url_and_server_redirects) {
      GURL url =
          embedded_test_server()->GetURL(url_and_server_redirect.relative_url);
      ReturnUrlRealTimeVerdictInUrlLoader(url,
                                          url_and_server_redirect.is_unsafe);
    }
    SafeBrowsingBlockingPageTestHelper::MaybeWaitForAsyncChecksToComplete(
        browser()->tab_strip_model()->GetActiveWebContents(),
        factory_.test_safe_browsing_service()->ui_manager().get(),
        /*wait_for_load_stop=*/true);

    EXPECT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(
        browser()->tab_strip_model()->GetActiveWebContents()));

    // Reset dangerous response so future URLs are not accidentally
    // flagged by real-time URL check.
    test_url_loader_factory_.ClearResponses();
    return final_url;
  }

  // The following events happen in sequence:
  //   1. Navigation finished.
  //   2. Safe Browsing checks complete.
  GURL SetupWarningShownAfterFinishNavigationAndNavigate(
      std::vector<UrlAndIsUnsafe> url_and_server_redirects) {
    CHECK(!url_and_server_redirects.empty());
    NavigateAndAwaitNavigationFinished(
        url_and_server_redirects.front().relative_url);
    return ReturnUrlRealTimeVerdictsForUnsafeChain(url_and_server_redirects);
  }

  void SetReportSentCallback(base::OnceClosure callback) {
    static_cast<FakeSafeBrowsingUIManager*>(
        factory_.test_safe_browsing_service()->ui_manager().get())
        ->set_threat_details_done_callback(std::move(callback));
  }

  std::string GetReportSent() {
    return static_cast<FakeSafeBrowsingUIManager*>(
               factory_.test_safe_browsing_service()->ui_manager().get())
        ->GetReport();
  }

  std::optional<bool> shown_report_sent_is_async_check() {
    return static_cast<FakeSafeBrowsingUIManager*>(
               factory_.test_safe_browsing_service()->ui_manager().get())
        ->report_sent_is_async_check();
  }

  base::HistogramTester histogram_tester_;
  TestThreatDetailsFactory details_factory_;

 private:
  network::TestURLLoaderFactory test_url_loader_factory_;
  base::test::ScopedFeatureList feature_list_;
};

class SafeBrowsingBlockingPageAsyncChecksTimingTest
    : public SafeBrowsingBlockingPageAsyncChecksTimingTestBase,
      public testing::WithParamInterface<bool> {
 public:
  SafeBrowsingBlockingPageAsyncChecksTimingTest() = default;

  GURL SetupPostCommitInterstitialAndNavigate(
      std::vector<UrlAndIsUnsafe> url_and_server_redirects,
      base::OnceClosure report_sent_callback) {
    // Call SetupUrlRealTimeVerdictInCacheManager with a random URL to ensure
    // RealTimeUrlLookupServiceBase::CanCheckUrl returns true so the real time
    // check is performed.
    SetupUrlRealTimeVerdictInCacheManager(GURL("https://random.url"),
                                          browser()->GetProfile(),
                                          RTLookupResponse::ThreatInfo::SAFE,
                                          /*threat_type=*/std::nullopt);
    SetReportSentCallback(std::move(report_sent_callback));
    bool check_complete_after_navigation_finish = GetParam();
    if (check_complete_after_navigation_finish) {
      return SetupWarningShownAfterFinishNavigationAndNavigate(
          url_and_server_redirects);
    } else {
      return SetupWarningShownBetweenProcessResponseAndFinishNavigationAndNavigate(
          url_and_server_redirects);
    }
  }
};

class SafeBrowsingBlockingPageAsyncChecksPrerenderingTest
    : public SafeBrowsingBlockingPageAsyncChecksTimingTestBase {
 public:
  SafeBrowsingBlockingPageAsyncChecksPrerenderingTest() = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    SafeBrowsingBlockingPageAsyncChecksTimingTestBase::SetUpCommandLine(
        command_line);
    // |prerender_helper_| has a ScopedFeatureList so we needed to delay its
    // creation until now because
    // SafeBrowsingBlockingPageAsyncChecksTimingTestBase also uses a
    // ScopedFeatureList and initialization order matters.
    prerender_helper_ = std::make_unique<
        content::test::PrerenderTestHelper>(base::BindRepeating(
        &SafeBrowsingBlockingPageAsyncChecksPrerenderingTest::GetWebContents,
        base::Unretained(this)));
  }

  void SetUpOnMainThread() override {
    prerender_helper_->RegisterServerRequestMonitor(embedded_test_server());
    SafeBrowsingBlockingPageAsyncChecksTimingTestBase::SetUpOnMainThread();
  }

  content::test::PrerenderTestHelper& prerender_helper() {
    return *prerender_helper_;
  }

  content::WebContents* GetWebContents() {
    return browser()->tab_strip_model()->GetActiveWebContents();
  }

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

// Test that prerendering doesn't affect the primary frame's threat report.
IN_PROC_BROWSER_TEST_F(
    SafeBrowsingBlockingPageAsyncChecksPrerenderingTest,
    PostCommitInterstitialReportThreatDetails_DontContainPrerenderingInfo) {
  EnableAsyncCheck();

  // Navigate to unsafe page, but don't yet return unsafe for the Safe Browsing
  // lookup.
  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  SetReportSentCallback(threat_report_sent_runner->QuitClosure());
  // Call SetupUrlRealTimeVerdictInCacheManager with a random URL to ensure
  // RealTimeUrlLookupServiceBase::CanCheckUrl returns true so the real time
  // check is performed.
  SetupUrlRealTimeVerdictInCacheManager(GURL("https://random.url"),
                                        browser()->GetProfile(),
                                        RTLookupResponse::ThreatInfo::SAFE,
                                        /*threat_type=*/std::nullopt);
  std::vector<UrlAndIsUnsafe> url_and_server_redirects = {
      {kMaliciousPage, /* is_unsafe */ true}};
  NavigateAndAwaitNavigationFinished(
      url_and_server_redirects.front().relative_url);

  // Set up prerendering.
  GURL prerender_url = embedded_test_server()->GetURL("/title1.html");
  SetupUrlRealTimeVerdictInCacheManager(prerender_url, browser()->GetProfile(),
                                        RTLookupResponse::ThreatInfo::SAFE,
                                        /*threat_type=*/std::nullopt);
  prerender_helper().AddPrerenderAsync(prerender_url);

  // Return unsafe for the Safe Browsing lookup, which displays a post-commit
  // interstitial.
  GURL url = ReturnUrlRealTimeVerdictsForUnsafeChain(url_and_server_redirects);

  ThreatDetails* threat_details = details_factory_.get_details();
  EXPECT_TRUE(threat_details != nullptr);

  // Proceed through the warning.
  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "proceed-link"));
  AssertNoInterstitial(browser());  // Assert the interstitial is gone

  EXPECT_TRUE(IsExtendedReportingEnabled(*browser()->GetProfile()->GetPrefs()));
  EXPECT_EQ(url, browser()
                     ->tab_strip_model()
                     ->GetActiveWebContents()
                     ->GetLastCommittedURL());

  threat_report_sent_runner->Run();
  std::string serialized = GetReportSent();
  ClientSafeBrowsingReportRequest report;
  ASSERT_TRUE(report.ParseFromString(serialized));
  // Verify the report is complete.
  EXPECT_TRUE(report.complete());
  // The threat report should not contain the prerender information.
  EXPECT_NE(prerender_url.spec(), report.page_url());
  EXPECT_NE(prerender_url.spec(), report.url());
  for (const auto& resource : report.resources()) {
    EXPECT_NE(prerender_url.spec(), resource.url());
  }
  // We don't check the specific size of resources here. The size can be either
  // 1 or 2 depending on whether DOM details have been collected when we
  // proceed.
  ASSERT_NE(0, report.resources_size());
}

INSTANTIATE_TEST_SUITE_P(CheckCompleteAfterNavigationFinish,
                         SafeBrowsingBlockingPageAsyncChecksTimingTest,
                         testing::Bool());

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageAsyncChecksTimingTest,
                       VerifyHistogramsAndClientReport) {
  EnableAsyncCheck();
  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  GURL url = SetupPostCommitInterstitialAndNavigate(
      {{kMaliciousPage, /* is_unsafe */ true}},
      threat_report_sent_runner->QuitClosure());

  EXPECT_TRUE(shown_report_sent_is_async_check().value());

  histogram_tester_.ExpectUniqueSample(
      "interstitial.phishing.decision.after_page_shown",
      /*sample=*/security_interstitials::MetricsHelper::SHOW,
      /*expected_bucket_count=*/1);
}

// Confirm that duplicate client reports aren't sent in the case where URT falls
// back to HPD due to a high-confidence allowlist match.
IN_PROC_BROWSER_TEST_P(
    SafeBrowsingBlockingPageAsyncChecksTimingTest,
    NoDuplicateClientReports_FallbackFromHighConfidenceAllowlistMatch) {
  EnableAsyncCheck();
  // Call SetupUrlRealTimeVerdictInCacheManager with a random URL to ensure
  // RealTimeUrlLookupServiceBase::CanCheckUrl returns true so the real time
  // check is performed.
  SetupUrlRealTimeVerdictInCacheManager(GURL("https://random.url"),
                                        browser()->GetProfile(),
                                        RTLookupResponse::ThreatInfo::SAFE,
                                        /*threat_type=*/std::nullopt);
  GURL url = embedded_test_server()->GetURL(kEmptyPage);
  SetURLHighConfidenceAllowlistMatch(url, true);
  SetURLThreatType(url, SBThreatType::SB_THREAT_TYPE_URL_PHISHING);
  NavigateToURLAndWaitForAsyncChecks(url);

  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  SetReportSentCallback(threat_report_sent_runner->QuitClosure());

  EXPECT_FALSE(shown_report_sent_is_async_check().value());
}

// Confirm that duplicate client reports aren't sent in the case where URT is
// not eligible and HPD is used instead for the async check.
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageAsyncChecksTimingTest,
                       NoDuplicateClientReports_UrlRealTimeUncheckable) {
  EnableAsyncCheck();
  // Do not call |SetupUrlRealTimeVerdictInCacheManager| with a random URL,
  // that way the real-time URL lookup will instead fall back to hash database
  // checks instead, since the URL is not eligible for real-time lookups.
  GURL url = embedded_test_server()->GetURL(kEmptyPage);
  SetURLThreatType(url, SBThreatType::SB_THREAT_TYPE_URL_PHISHING);
  NavigateToURLAndWaitForAsyncChecks(url);

  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  SetReportSentCallback(threat_report_sent_runner->QuitClosure());

  EXPECT_FALSE(shown_report_sent_is_async_check().value());
}

// Confirm that duplicate client reports aren't sent for web UI URLs.
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageAsyncChecksTimingTest,
                       NoDuplicateClientReports_WebUiUrl) {
  EnableAsyncCheck();
  NavigateToURLAndWaitForAsyncChecks(
      GURL(kChromeUISafeBrowsingMatchPhishingUrl));

  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  SetReportSentCallback(threat_report_sent_runner->QuitClosure());

  EXPECT_FALSE(shown_report_sent_is_async_check().value());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageAsyncChecksTimingTest,
                       PostCommitInterstitialDontProceed) {
  EnableAsyncCheck();
  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  SetupPostCommitInterstitialAndNavigate(
      {{kMaliciousPage, /* is_unsafe */ true}},
      threat_report_sent_runner->QuitClosure());

  EXPECT_EQ(VISIBLE, GetVisibility(browser(), "primary-button"));
  EXPECT_EQ(HIDDEN, GetVisibility(browser(), "details"));
  EXPECT_EQ(HIDDEN, GetVisibility(browser(), "proceed-link"));
  EXPECT_EQ(HIDDEN, GetVisibility(browser(), "error-code"));
  EXPECT_TRUE(Click(browser(), "details-button"));
  EXPECT_EQ(VISIBLE, GetVisibility(browser(), "details"));
  EXPECT_EQ(VISIBLE, GetVisibility(browser(), "proceed-link"));
  EXPECT_EQ(HIDDEN, GetVisibility(browser(), "error-code"));
  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "primary-button"));

  AssertNoInterstitial(browser());  // Assert the interstitial is gone

  EXPECT_EQ(GURL(url::kAboutBlankURL),  // Back to "about:blank"
            browser()
                ->tab_strip_model()
                ->GetActiveWebContents()
                ->GetLastCommittedURL());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageAsyncChecksTimingTest,
                       PostCommitInterstitialProceed) {
  EnableAsyncCheck();
  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  GURL url = SetupPostCommitInterstitialAndNavigate(
      {{kMaliciousPage, /* is_unsafe */ true}},
      threat_report_sent_runner->QuitClosure());

  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "proceed-link"));
  AssertNoInterstitial(browser());  // Assert the interstitial is gone

  EXPECT_EQ(url, browser()
                     ->tab_strip_model()
                     ->GetActiveWebContents()
                     ->GetLastCommittedURL());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageAsyncChecksTimingTest,
                       PostCommitInterstitialServerRedirect_OriginIsUnsafe) {
  EnableAsyncCheck();
  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  GURL url = SetupPostCommitInterstitialAndNavigate(
      {{kRedirectToMalware, /* is_unsafe */ true},
       {kMaliciousPage, /* is_unsafe */ false}},
      threat_report_sent_runner->QuitClosure());

  // The original URL is unsafe, so it should be displayed in the URL bar.
  GURL original_url = embedded_test_server()->GetURL(kRedirectToMalware);
  EXPECT_EQ(
      original_url,
      browser()->tab_strip_model()->GetActiveWebContents()->GetVisibleURL());

  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "proceed-link"));
  AssertNoInterstitial(browser());  // Assert the interstitial is gone

  EXPECT_EQ(url, browser()
                     ->tab_strip_model()
                     ->GetActiveWebContents()
                     ->GetLastCommittedURL());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageAsyncChecksTimingTest,
                       PostCommitInterstitialServerRedirect_RedirectIsUnsafe) {
  EnableAsyncCheck();
  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  GURL url = SetupPostCommitInterstitialAndNavigate(
      {{kRedirectToMalware, /* is_unsafe */ false},
       {kMaliciousPage, /* is_unsafe */ true}},
      threat_report_sent_runner->QuitClosure());

  // The final URL is unsafe, so it should be displayed in the URL bar.
  GURL final_url = embedded_test_server()->GetURL(kMaliciousPage);
  EXPECT_EQ(
      final_url,
      browser()->tab_strip_model()->GetActiveWebContents()->GetVisibleURL());

  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "proceed-link"));
  AssertNoInterstitial(browser());  // Assert the interstitial is gone

  EXPECT_EQ(url, browser()
                     ->tab_strip_model()
                     ->GetActiveWebContents()
                     ->GetLastCommittedURL());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageAsyncChecksTimingTest,
                       PostCommitInterstitialReportThreatDetails) {
  EnableAsyncCheck();
  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  GURL url = SetupPostCommitInterstitialAndNavigate(
      {{kMaliciousPage, /* is_unsafe */ true}},
      threat_report_sent_runner->QuitClosure());

  ThreatDetails* threat_details = details_factory_.get_details();
  EXPECT_TRUE(threat_details != nullptr);
  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "proceed-link"));
  AssertNoInterstitial(browser());  // Assert the interstitial is gone

  EXPECT_EQ(url, browser()
                     ->tab_strip_model()
                     ->GetActiveWebContents()
                     ->GetLastCommittedURL());

  threat_report_sent_runner->Run();
  std::string serialized = GetReportSent();
  ClientSafeBrowsingReportRequest report;
  ASSERT_TRUE(report.ParseFromString(serialized));
  // Verify the report is complete.
  EXPECT_TRUE(report.complete());
  // Do some basic verification of report contents.
  EXPECT_EQ(url.spec(), report.page_url());
  EXPECT_EQ(url.spec(), report.url());
  // We don't check the specific size of resources here. The size can be either
  // 1 or 2 depending on whether DOM details have been collected when we
  // proceed.
  ASSERT_NE(0, report.resources_size());
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageAsyncChecksTimingTest,
                       PostCommitInterstitialAllowlistRevisit) {
  EnableAsyncCheck();
  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  GURL url = SetupPostCommitInterstitialAndNavigate(
      {{kMaliciousPage, /* is_unsafe */ true}},
      threat_report_sent_runner->QuitClosure());

  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "proceed-link"));
  AssertNoInterstitial(browser());  // Assert the interstitial is gone.
  EXPECT_EQ(url, browser()
                     ->tab_strip_model()
                     ->GetActiveWebContents()
                     ->GetLastCommittedURL());

  // Navigate to an unrelated page and revisit the allowlisted URL.
  SetupUrlRealTimeVerdictInCacheManager(GURL(kUnrelatedUrl),
                                        browser()->GetProfile(),
                                        RTLookupResponse::ThreatInfo::SAFE,
                                        /*threat_type=*/std::nullopt);
  NavigateToURLAndWaitForAsyncChecks(GURL(kUnrelatedUrl));
  AssertNoInterstitial(browser());

  // The allowlisted page should remain allowlisted.
  NavigateToURLAndWaitForAsyncChecks(url);
  AssertNoInterstitial(browser());
}

// Test that the security indicator gets updated on a Safe Browsing
// interstitial triggered post commit. Regression test for
// https://crbug.com/41283180.
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageAsyncChecksTimingTest,
                       SecurityStateDowngradedForPostCommitInterstitial) {
  EnableAsyncCheck();
  WebContents* error_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(error_tab);
  SecurityStyleTestObserver observer(error_tab);

  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  GURL url = SetupPostCommitInterstitialAndNavigate(
      {{kMaliciousPage, /* is_unsafe */ true}},
      threat_report_sent_runner->QuitClosure());

  // The security indicator should be downgraded while the interstitial shows.
  ExpectSecurityIndicatorDowngrade(error_tab, 0u);
  EXPECT_EQ(security_state::SecurityLevel::DANGEROUS,
            observer.latest_security_level());

  // The security indicator should still be downgraded post-interstitial.
  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "proceed-link"));
  AssertNoInterstitial(browser());
  WebContents* post_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(post_tab);
  ExpectSecurityIndicatorDowngrade(post_tab, 0u);
}

// Test that the security indicator does not stay downgraded after
// clicking back from a Safe Browsing interstitial triggered post commit.
// Regression test for https://crbug.com/41283177.
IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageAsyncChecksTimingTest,
                       SecurityStateGoBackOnPostCommitInterstitial) {
  EnableAsyncCheck();

  // Navigate to a page so that there is somewhere to go back to.
  GURL start_url = embedded_test_server()->GetURL(kEmptyPage);
  NavigateToURLAndWaitForAsyncChecks(start_url);

  // The security indicator should be downgraded while the interstitial
  // shows.
  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  GURL main_url = SetupPostCommitInterstitialAndNavigate(
      {{kMaliciousPage, /* is_unsafe */ true}},
      threat_report_sent_runner->QuitClosure());
  WebContents* error_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(error_tab);
  ExpectSecurityIndicatorDowngrade(error_tab, 0u);

  // Go back.
  EXPECT_EQ(VISIBLE, GetVisibility(browser(), "primary-button"));
  EXPECT_EQ(HIDDEN, GetVisibility(browser(), "details"));
  EXPECT_EQ(HIDDEN, GetVisibility(browser(), "proceed-link"));
  EXPECT_EQ(HIDDEN, GetVisibility(browser(), "error-code"));
  EXPECT_TRUE(Click(browser(), "details-button"));
  EXPECT_EQ(VISIBLE, GetVisibility(browser(), "details"));
  EXPECT_EQ(VISIBLE, GetVisibility(browser(), "proceed-link"));
  EXPECT_EQ(HIDDEN, GetVisibility(browser(), "error-code"));
  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "primary-button"));

  // The security indicator should *not* still be downgraded after going back.
  AssertNoInterstitial(browser());
  WebContents* post_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(post_tab);
  content::NavigationEntry* entry = post_tab->GetController().GetVisibleEntry();
  ASSERT_TRUE(entry);
  EXPECT_EQ(start_url, entry->GetURL());
  ExpectNoSecurityIndicatorDowngrade(post_tab);
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageAsyncChecksTimingTest,
                       SecurityStateGoBackFlaggedByBothChecks) {
  EnableAsyncCheck();

  // Navigate to a page so that there is somewhere to go back to.
  GURL start_url = embedded_test_server()->GetURL(kEmptyPage);
  NavigateToURLAndWaitForAsyncChecks(start_url);

  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  SetReportSentCallback(threat_report_sent_runner->QuitClosure());
  GURL url = embedded_test_server()->GetURL(kMaliciousPage);

  // Mark the URL as dangerous for both checks.
  SetupUrlRealTimeVerdictInCacheManager(
      url, browser()->GetProfile(), RTLookupResponse::ThreatInfo::DANGEROUS,
      RTLookupResponse::ThreatInfo::SOCIAL_ENGINEERING);
  SetURLThreatType(url, SBThreatType::SB_THREAT_TYPE_URL_PHISHING);
  NavigateToURLAndWaitForAsyncChecks(url);

  // The security indicator should be downgraded while the interstitial
  // shows.
  WebContents* error_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(error_tab);
  ExpectSecurityIndicatorDowngrade(error_tab, 0u);

  // Go back.
  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "primary-button"));

  // The security indicator should *not* still be downgraded after going back.
  AssertNoInterstitial(browser());
  WebContents* post_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(post_tab);
  content::NavigationEntry* entry = post_tab->GetController().GetVisibleEntry();
  ASSERT_TRUE(entry);
  EXPECT_EQ(start_url, entry->GetURL());
  ExpectNoSecurityIndicatorDowngrade(post_tab);
}

// Tests that commands work in a post commit interstitial if a pre commit
// interstitial has been shown previously on the same webcontents. Regression
// test for crbug.com/40657015
#if BUILDFLAG(IS_LINUX) && defined(MEMORY_SANITIZER)
// TODO(crbug.com/325491320): re-enable test
#define MAYBE_PostCommitInterstitialProceedAfterPreCommitInterstitial \
  DISABLED_PostCommitInterstitialProceedAfterPreCommitInterstitial
#else
#define MAYBE_PostCommitInterstitialProceedAfterPreCommitInterstitial \
  PostCommitInterstitialProceedAfterPreCommitInterstitial
#endif
IN_PROC_BROWSER_TEST_P(
    SafeBrowsingBlockingPageAsyncChecksTimingTest,
    MAYBE_PostCommitInterstitialProceedAfterPreCommitInterstitial) {
  EnableAsyncCheck();
  // Trigger a pre commit interstitial and go back.
  GURL start_url = embedded_test_server()->GetURL(kEmptyPage);
  SetURLThreatType(start_url, SBThreatType::SB_THREAT_TYPE_URL_PHISHING);
  NavigateToURLAndWaitForAsyncChecks(start_url);
  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "primary-button"));
  AssertNoInterstitial(browser());

  // Trigger a post commit interstitial.
  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  GURL main_url = SetupPostCommitInterstitialAndNavigate(
      {{kMaliciousPage, /* is_unsafe */ true}},
      threat_report_sent_runner->QuitClosure());

  // Commands should work.
  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "proceed-link"));
  AssertNoInterstitial(browser());  // Assert the interstitial is gone

  EXPECT_EQ(main_url, browser()
                          ->tab_strip_model()
                          ->GetActiveWebContents()
                          ->GetLastCommittedURL());
}

IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageRealTimeUrlCheckTest,
                       WarningShown_EnhancedProtectionEnabled) {
  safe_browsing::SetSafeBrowsingState(
      browser()->GetProfile()->GetPrefs(),
      safe_browsing::SafeBrowsingState::ENHANCED_PROTECTION);
  GURL url = embedded_test_server()->GetURL("/empty.html");
  SetupUnsafeVerdict(url, browser()->GetProfile());
  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  SetReportSentCallback(threat_report_sent_runner->QuitClosure());

  NavigateToURL(url);
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(
      browser()->tab_strip_model()->GetActiveWebContents()));
}

IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageRealTimeUrlCheckTest,
                       WarningShown_MbbEnabled) {
  safe_browsing::SetSafeBrowsingState(
      browser()->GetProfile()->GetPrefs(),
      safe_browsing::SafeBrowsingState::STANDARD_PROTECTION);
  browser()->GetProfile()->GetPrefs()->SetBoolean(
      unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled, true);
  GURL url = embedded_test_server()->GetURL("/empty.html");
  SetupUnsafeVerdict(url, browser()->GetProfile());

  NavigateToURL(url);
  ASSERT_TRUE(chrome_browser_interstitials::IsShowingInterstitial(
      browser()->tab_strip_model()->GetActiveWebContents()));
}

IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageRealTimeUrlCheckTest,
                       WarningNotShown_MbbDisabled) {
  safe_browsing::SetSafeBrowsingState(
      browser()->GetProfile()->GetPrefs(),
      safe_browsing::SafeBrowsingState::STANDARD_PROTECTION);
  browser()->GetProfile()->GetPrefs()->SetBoolean(
      unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled, false);
  GURL url = embedded_test_server()->GetURL("/empty.html");
  SetupUnsafeVerdict(url, browser()->GetProfile());

  NavigateToURL(url);
  ASSERT_FALSE(chrome_browser_interstitials::IsShowingInterstitial(
      browser()->tab_strip_model()->GetActiveWebContents()));
}

IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageRealTimeUrlCheckTest,
                       EnterpriseRealTimeUrlCheck_HistogramHasDmToken) {
  base::HistogramTester histogram_tester;
  safe_browsing::SetSafeBrowsingState(
      browser()->GetProfile()->GetPrefs(),
      safe_browsing::SafeBrowsingState::STANDARD_PROTECTION);

  // Set up enterprise lookup, including DM token.
  browser()->GetProfile()->GetPrefs()->SetInteger(
      enterprise_connectors::kEnterpriseRealTimeUrlCheckMode,
      enterprise_connectors::REAL_TIME_CHECK_FOR_MAINFRAME_ENABLED);
  browser()->GetProfile()->GetPrefs()->SetInteger(
      enterprise_connectors::kEnterpriseRealTimeUrlCheckScope,
      policy::POLICY_SCOPE_MACHINE);
  SetDMTokenForTesting(policy::DMToken::CreateValidToken("dm_token"));

  GURL url = embedded_test_server()->GetURL(kEmptyPage);
  SetupUrlRealTimeVerdictInCacheManager(url, browser()->GetProfile(),
                                        RTLookupResponse::ThreatInfo::SAFE,
                                        /*threat_type=*/std::nullopt);
  NavigateToURL(url);

  histogram_tester.ExpectUniqueSample(
      "SafeBrowsing.RT.EnterpriseRealTimePolicyEnabled.HasDmToken",
      /*sample=*/true,
      /*expected_bucket_count=*/1);
}

IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageRealTimeUrlCheckTest,
                       EnterpriseRealTimeUrlCheck_HistogramHasNoDmToken) {
  base::HistogramTester histogram_tester;
  safe_browsing::SetSafeBrowsingState(
      browser()->GetProfile()->GetPrefs(),
      safe_browsing::SafeBrowsingState::STANDARD_PROTECTION);

  // Set up enterprise lookup, but no DM token.
  browser()->GetProfile()->GetPrefs()->SetInteger(
      enterprise_connectors::kEnterpriseRealTimeUrlCheckMode,
      enterprise_connectors::REAL_TIME_CHECK_FOR_MAINFRAME_ENABLED);
  browser()->GetProfile()->GetPrefs()->SetInteger(
      enterprise_connectors::kEnterpriseRealTimeUrlCheckScope,
      policy::POLICY_SCOPE_MACHINE);

  GURL url = embedded_test_server()->GetURL(kEmptyPage);
  SetupUrlRealTimeVerdictInCacheManager(url, browser()->GetProfile(),
                                        RTLookupResponse::ThreatInfo::SAFE,
                                        /*threat_type=*/std::nullopt);
  NavigateToURL(url);

  histogram_tester.ExpectUniqueSample(
      "SafeBrowsing.RT.EnterpriseRealTimePolicyEnabled.HasDmToken",
      /*sample=*/false,
      /*expected_bucket_count=*/1);
}

IN_PROC_BROWSER_TEST_F(
    SafeBrowsingBlockingPageRealTimeUrlCheckTest,
    EnterpriseRealTimeUrlCheck_NoHistogramBecausePolicyDisabled) {
  base::HistogramTester histogram_tester;
  safe_browsing::SetSafeBrowsingState(
      browser()->GetProfile()->GetPrefs(),
      safe_browsing::SafeBrowsingState::STANDARD_PROTECTION);

  // Set up enterprise lookup so that the policy is disabled.
  browser()->GetProfile()->GetPrefs()->SetInteger(
      enterprise_connectors::kEnterpriseRealTimeUrlCheckMode,
      enterprise_connectors::REAL_TIME_CHECK_DISABLED);
  browser()->GetProfile()->GetPrefs()->SetInteger(
      enterprise_connectors::kEnterpriseRealTimeUrlCheckScope,
      policy::POLICY_SCOPE_MACHINE);
  SetDMTokenForTesting(policy::DMToken::CreateValidToken("dm_token"));

  GURL url = embedded_test_server()->GetURL(kEmptyPage);
  SetupUrlRealTimeVerdictInCacheManager(url, browser()->GetProfile(),
                                        RTLookupResponse::ThreatInfo::SAFE,
                                        /*threat_type=*/std::nullopt);
  NavigateToURL(url);

  histogram_tester.ExpectTotalCount(
      "SafeBrowsing.RT.EnterpriseRealTimePolicyEnabled.HasDmToken",
      /*expected_count=*/0);
}

// Tests for hash-prefix real-time check. To avoid redundant testing of the
// HashRealTimeService, this populates the local cache instead of mocking
// network requests.
class SafeBrowsingBlockingPageHashRealTimeCheckTest
    : public InProcessBrowserTest {
 public:
  SafeBrowsingBlockingPageHashRealTimeCheckTest() = default;
  SafeBrowsingBlockingPageHashRealTimeCheckTest(
      const SafeBrowsingBlockingPageHashRealTimeCheckTest&) = delete;
  SafeBrowsingBlockingPageHashRealTimeCheckTest& operator=(
      const SafeBrowsingBlockingPageHashRealTimeCheckTest&) = delete;

  void SetUp() override {
    InitFeatures();
    InProcessBrowserTest::SetUp();
  }
  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    content::SetupCrossSiteRedirector(embedded_test_server());
    ASSERT_TRUE(embedded_test_server()->Start());
  }
  void CreatedBrowserMainParts(
      content::BrowserMainParts* browser_main_parts) override {
    InProcessBrowserTest::CreatedBrowserMainParts(browser_main_parts);
    // Test UI manager and test database manager should be set before
    // the browser is started but after threads are created.
    factory_.SetTestUIManager(new FakeSafeBrowsingUIManager(
        std::make_unique<TestSafeBrowsingBlockingPageFactory>()));
    factory_.SetTestDatabaseManager(new FakeSafeBrowsingDatabaseManager(
        content::GetUIThreadTaskRunner({})));
    SafeBrowsingService::RegisterFactory(&factory_);
  }

 protected:
  virtual void InitFeatures() {
    std::vector<base::test::FeatureRef> enabled_features = {
        kHashPrefixRealTimeLookups};
    std::vector<base::test::FeatureRef> disabled_features = {};
    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
  }
  void SetUpVerdict(GURL url, Profile* profile, bool is_unsafe) {
    V5SearchHashesCacheFactory::GetForProfile(profile)
        ->CacheArtificialV5SearchHashesLookupVerdict(url.spec(), is_unsafe);
  }
  void SetUpAndNavigateToUrl(bool is_unsafe) {
    GURL url = embedded_test_server()->GetURL("/empty.html");
    SetUpVerdict(url, browser()->GetProfile(), is_unsafe);
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
    SafeBrowsingBlockingPageTestHelper::MaybeWaitForAsyncChecksToComplete(
        browser()->tab_strip_model()->GetActiveWebContents(),
        factory_.test_safe_browsing_service()->ui_manager().get(),
        /*wait_for_load_stop=*/true);
  }
  bool IsShowingInterstitial() {
    return ::safe_browsing::IsShowingInterstitial(
        browser()->tab_strip_model()->GetActiveWebContents());
  }
  std::string GetReportSent() {
    return static_cast<FakeSafeBrowsingUIManager*>(
               factory_.test_safe_browsing_service()->ui_manager().get())
        ->GetReport();
  }
  void SetReportSentCallback(base::OnceClosure callback) {
    static_cast<FakeSafeBrowsingUIManager*>(
        factory_.test_safe_browsing_service()->ui_manager().get())
        ->set_threat_details_done_callback(std::move(callback));
  }

  base::test::ScopedFeatureList scoped_feature_list_;
  TestSafeBrowsingServiceFactory factory_;

 private:
  hash_realtime_utils::GoogleChromeBrandingPretenderForTesting apply_branding_;
};
class SafeBrowsingBlockingPageHashRealTimeCheckFeatureOffTest
    : public SafeBrowsingBlockingPageHashRealTimeCheckTest {
 protected:
  void InitFeatures() override {
    std::vector<base::test::FeatureRef> enabled_features = {};
    std::vector<base::test::FeatureRef> disabled_features = {
        kHashPrefixRealTimeLookups};
    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
  }
};

IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageHashRealTimeCheckTest,
                       ShowWarning) {
  base::HistogramTester histogram_tester;
  SetUpAndNavigateToUrl(/*is_unsafe=*/true);
  ASSERT_TRUE(IsShowingInterstitial());
  // The TotalDelay2 metric is logged for whichever check is run sync. When
  // async checks are enabled, it's the hash-prefix database check, since the
  // hash-prefix real-time check is run async.
  histogram_tester.ExpectTotalCount(
      "SafeBrowsing.BrowserThrottle.TotalDelay2.HashPrefixRealTimeCheck",
      /*expected_count=*/0);
  histogram_tester.ExpectTotalCount(
      "SafeBrowsing.BrowserThrottle.TotalDelay2.HashPrefixDatabaseCheck",
      /*expected_count=*/1);
  histogram_tester.ExpectUniqueSample(
      "SafeBrowsing.HPRT.Ineligible.IneligibleForSessionOrLocation",
      /*sample=*/false,
      /*expected_bucket_count=*/1);
  histogram_tester.ExpectTotalCount(
      "interstitial.phishing.decision.from_hash_prefix_real_time_check_v5",
      /*expected_count=*/1);
}
IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageHashRealTimeCheckTest,
                       DontShowWarning_PageIsSafe) {
  base::HistogramTester histogram_tester;
  SetUpAndNavigateToUrl(/*is_unsafe=*/false);
  ASSERT_FALSE(IsShowingInterstitial());
  // The TotalDelay2 metric is logged for whichever check is run sync. When
  // async checks are enabled, it's the hash-prefix database check, since the
  // hash-prefix real-time check is run async.
  histogram_tester.ExpectTotalCount(
      "SafeBrowsing.BrowserThrottle.TotalDelay2.HashPrefixRealTimeCheck",
      /*expected_count=*/0);
  histogram_tester.ExpectTotalCount(
      "SafeBrowsing.BrowserThrottle.TotalDelay2.HashPrefixDatabaseCheck",
      /*expected_count=*/1);
  histogram_tester.ExpectUniqueSample(
      "SafeBrowsing.HPRT.Ineligible.IneligibleForSessionOrLocation",
      /*sample=*/false,
      /*expected_bucket_count=*/1);
  histogram_tester.ExpectTotalCount(
      "interstitial.phishing.decision.from_hash_prefix_real_time_check_v5",
      /*expected_count=*/0);
}
IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageHashRealTimeCheckFeatureOffTest,
                       DontShowWarning_FeatureIsOff) {
  base::HistogramTester histogram_tester;
  SetUpAndNavigateToUrl(/*is_unsafe=*/true);
  ASSERT_FALSE(IsShowingInterstitial());
  histogram_tester.ExpectTotalCount(
      "SafeBrowsing.BrowserThrottle.TotalDelay2.HashPrefixRealTimeCheck",
      /*expected_count=*/0);
  histogram_tester.ExpectTotalCount(
      "SafeBrowsing.BrowserThrottle.TotalDelay2.HashPrefixDatabaseCheck",
      /*expected_count=*/1);
  histogram_tester.ExpectUniqueSample(
      "SafeBrowsing.HPRT.Ineligible.IneligibleForSessionOrLocation",
      /*sample=*/true,
      /*expected_bucket_count=*/1);
  histogram_tester.ExpectTotalCount(
      "interstitial.phishing.decision.from_hash_prefix_real_time_check_v5",
      /*expected_count=*/0);
}
IN_PROC_BROWSER_TEST_F(SafeBrowsingBlockingPageHashRealTimeCheckTest,
                       TriggerClientSafeBrowsingReportRequest) {
  if (base::FeatureList::IsEnabled(kExtendedReportingRemovePrefDependency)) {
    // If the extended reporting pref dependency is removed, this test will not
    // be run since it is testing HPRT lookup cases.
    // TODO(crbug.com/362530516): Remove this test case and add a new test for
    // sampled HPRT lookups.
    return;
  }
  SetExtendedReportingPrefForTests(browser()->GetProfile()->GetPrefs(), true);
  SetUpAndNavigateToUrl(/*is_unsafe=*/true);
  ASSERT_TRUE(IsShowingInterstitial());

  // Verify correct CSBRR is sent.
  auto threat_report_sent_runner = std::make_unique<base::RunLoop>();
  SetReportSentCallback(threat_report_sent_runner->QuitClosure());
  EXPECT_TRUE(ClickAndWaitForDetach(browser(), "proceed-link"));
  ASSERT_FALSE(IsShowingInterstitial());
  threat_report_sent_runner->Run();
  std::string serialized_report = GetReportSent();
  ClientSafeBrowsingReportRequest report;
  ASSERT_TRUE(report.ParseFromString(serialized_report));
  EXPECT_EQ(report.type(),
            ClientSafeBrowsingReportRequest_ReportType_URL_PHISHING);
  EXPECT_EQ(
      report.client_properties().url_api_type(),
      ClientSafeBrowsingReportRequest_SafeBrowsingUrlApiType_PVER5_NATIVE_REAL_TIME);
  EXPECT_EQ(report.client_properties().is_async_check(), true);
}

class SafeBrowsingPrerenderBrowserTest
    : public SafeBrowsingBlockingPageBrowserTest {
 public:
  SafeBrowsingPrerenderBrowserTest()
      : prerender_helper_(base::BindRepeating(
            &SafeBrowsingPrerenderBrowserTest::GetWebContents,
            base::Unretained(this))) {}
  ~SafeBrowsingPrerenderBrowserTest() override = default;
  SafeBrowsingPrerenderBrowserTest(const SafeBrowsingPrerenderBrowserTest&) =
      delete;
  SafeBrowsingPrerenderBrowserTest& operator=(
      const SafeBrowsingPrerenderBrowserTest&) = delete;

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

  content::test::PrerenderTestHelper& prerender_helper() {
    return prerender_helper_;
  }

  content::WebContents* GetWebContents() {
    return browser()->tab_strip_model()->GetActiveWebContents();
  }

  // Prerenders |prerender_url|, which triggers SafeBrowsing, then verifies that
  // the prerender is cancelled and that the security state of the primary page
  // is not affected.
  void PrerenderAndExpectCancellation(const GURL& prerender_url) {
    content::test::PrerenderHostObserver observer(*GetWebContents(),
                                                  prerender_url);
    prerender_helper().AddPrerenderAsync(prerender_url);
    observer.WaitForDestroyed();

    EXPECT_FALSE(
        chrome_browser_interstitials::IsShowingInterstitial(GetWebContents()));
    ExpectNoSecurityIndicatorDowngrade(GetWebContents());
  }

 private:
  content::test::PrerenderTestHelper prerender_helper_;
};

INSTANTIATE_TEST_SUITE_P(
    All,
    SafeBrowsingPrerenderBrowserTest,
    testing::Combine(
        testing::Values(
            SBThreatType::SB_THREAT_TYPE_URL_MALWARE,  // Threat types
            SBThreatType::SB_THREAT_TYPE_URL_PHISHING,
            SBThreatType::SB_THREAT_TYPE_URL_UNWANTED),
        testing::Bool(),
        testing::Bool(),
        testing::Bool()));  // Isolate all sites, SBER deprecated, Use V5.

// Attempt to prerender an unsafe page. The prerender navigation should be
// cancelled and should not affect the security state of the primary page.
IN_PROC_BROWSER_TEST_P(SafeBrowsingPrerenderBrowserTest, UnsafePrerender) {
  base::HistogramTester histograms;
  const GURL initial_url = embedded_test_server()->GetURL("/title1.html");
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));

  const GURL prerender_url = embedded_test_server()->GetURL(kEmptyPage);
  SetURLThreatType(prerender_url, GetThreatType());

  PrerenderAndExpectCancellation(prerender_url);
  histograms.ExpectUniqueSample(
      "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
      /*PrerenderFinalStatus::kBlockedByClient=*/28, 1);
}

class SafeBrowsingBlockingPageDelayedWarningPrerenderingBrowserTest
    : public SafeBrowsingBlockingPageDelayedWarningBrowserTest {
 public:
  SafeBrowsingBlockingPageDelayedWarningPrerenderingBrowserTest() = default;
  ~SafeBrowsingBlockingPageDelayedWarningPrerenderingBrowserTest() override =
      default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    SafeBrowsingBlockingPageDelayedWarningBrowserTest::SetUpCommandLine(
        command_line);
    // |prerender_helper_| has a ScopedFeatureList so we needed to delay its
    // creation until now because
    // SafeBrowsingBlockingPageDelayedWarningBrowserTest also uses a
    // ScopedFeatureList and initialization order matters.
    prerender_helper_ = std::make_unique<content::test::PrerenderTestHelper>(
        base::BindRepeating(
            &SafeBrowsingBlockingPageDelayedWarningPrerenderingBrowserTest::
                GetWebContents,
            base::Unretained(this)));
  }

  void SetUpOnMainThread() override {
    prerender_helper_->RegisterServerRequestMonitor(embedded_test_server());
    SafeBrowsingBlockingPageDelayedWarningBrowserTest::SetUpOnMainThread();
  }

  content::test::PrerenderTestHelper& prerender_helper() {
    return *prerender_helper_;
  }

  content::WebContents* GetWebContents() {
    return browser()->tab_strip_model()->GetActiveWebContents();
  }

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

INSTANTIATE_TEST_SUITE_P(
    All,
    SafeBrowsingBlockingPageDelayedWarningPrerenderingBrowserTest,
    testing::Combine(testing::Bool(), /* IsolateAllSitesForTesting */
                     testing::Bool() /* Show warning on mouse click */));

// This test loads a page in the prerender to ensure that the prerendering
// navigation is skipped at DidFinishNavigation() from
// SafeBrowsingUserInteractionObserver.
IN_PROC_BROWSER_TEST_P(
    SafeBrowsingBlockingPageDelayedWarningPrerenderingBrowserTest,
    DoNotRecordMetricsInPrerendering) {
  base::HistogramTester histograms;
  NavigateAndAssertNoInterstitial();

  // Load a page in the prerender.
  GURL prerender_url = embedded_test_server()->GetURL("/simple.html");
  prerender_helper().AddPrerender(prerender_url);

  // Activating the prerendered page causes "flush" metrics.
  prerender_helper().NavigatePrimaryPage(prerender_url);
}

class WarningShownTimestampCSBRRDisabledBrowserTest
    : public SafeBrowsingBlockingPageBrowserTest {
 public:
  WarningShownTimestampCSBRRDisabledBrowserTest() {
    std::vector<base::test::FeatureRef> enabled_features;
    std::vector<base::test::FeatureRef> disabled_features = {
        safe_browsing::kAddWarningShownTSToClientSafeBrowsingReport};
    if (IsSberDeprecated()) {
      enabled_features.push_back(
          safe_browsing::kExtendedReportingRemovePrefDependency);
    } else {
      disabled_features.push_back(
          safe_browsing::kExtendedReportingRemovePrefDependency);
    }
    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
  }
  ~WarningShownTimestampCSBRRDisabledBrowserTest() override = default;

  void SetUp() override { SafeBrowsingBlockingPageBrowserTest::SetUp(); }

  content::WebContents* GetWebContents() {
    return browser()->tab_strip_model()->GetActiveWebContents();
  }

  void RunThreatReportSentLoop() {
    base::RunLoop threat_report_sent_loop;
    SetReportSentCallback(threat_report_sent_loop.QuitClosure());
    threat_report_sent_loop.Run();
  }

  void CheckCSBRRForTimestamp() {
    std::string serialized = GetReportSent();
    ClientSafeBrowsingReportRequest report;
    ASSERT_TRUE(report.ParseFromString(serialized));
    // The timestamp of the warning shown should not be in the report.
    EXPECT_FALSE(report.has_warning_shown_timestamp_msec());
  }

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

INSTANTIATE_TEST_SUITE_P(
    WarningShownTimestampCSBRRDisabledBrowserTestWithThreatTypeAndIsolationSetting,
    WarningShownTimestampCSBRRDisabledBrowserTest,
    testing::Combine(
        testing::Values(
            SBThreatType::SB_THREAT_TYPE_URL_PHISHING,  // Threat types
            SBThreatType::SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING),
        testing::Bool(),
        testing::Bool(),
        testing::Bool()));  // Isolate all sites, SBER deprecated, Use V5.

IN_PROC_BROWSER_TEST_P(WarningShownTimestampCSBRRDisabledBrowserTest,
                       TimestampNotInCSBRRClickedThroughBlockingPage) {
  EnableExtendedReporting(true);
  content::TestNavigationObserver observer(
      browser()->tab_strip_model()->GetActiveWebContents());
  SetupWarningAndNavigate(browser());

  // Proceed to unsafe site, sending CSBRR.
  EXPECT_TRUE(ClickAndWaitForDetach("proceed-link"));

  observer.WaitForNavigationFinished();
  RunThreatReportSentLoop();
  CheckCSBRRForTimestamp();
}
IN_PROC_BROWSER_TEST_P(WarningShownTimestampCSBRRDisabledBrowserTest,
                       TimestampNotInFallbackCSBRRSent) {
  EnableExtendedReporting(true);
  content::TestNavigationObserver observer(
      browser()->tab_strip_model()->GetActiveWebContents());
  SetupWarningAndNavigate(browser());

  // Send CSBRR without interactions.
  chrome::CloseTab(browser());

  observer.WaitForNavigationFinished();
  RunThreatReportSentLoop();
  CheckCSBRRForTimestamp();
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       OpenHelpCenterInNewTab) {
  SetupWarningAndNavigate(browser());
  WebContents* interstitial_tab =
      browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(interstitial_tab);

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

  content::TestNavigationObserver nav_observer(nullptr);
  nav_observer.StartWatchingNewWebContents();
  SendCommand(security_interstitials::CMD_OPEN_HELP_CENTER_IN_NEW_TAB);
  nav_observer.Wait();

  // A new tab has been opened.
  EXPECT_EQ(2, browser()->tab_strip_model()->count());
  // The new tab is active.
  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
  WebContents* new_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_NE(new_tab, interstitial_tab);

  // Interstitial does not display in the new tab.
  EXPECT_FALSE(IsShowingInterstitial(new_tab));

  // Interstitial should still display in the background tab.
  EXPECT_TRUE(IsShowingInterstitial(interstitial_tab));
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       OpenDiagnosticInNewTab) {
  SetupWarningAndNavigate(browser());
  WebContents* interstitial_tab =
      browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(interstitial_tab);

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

  content::TestNavigationObserver nav_observer(nullptr);
  nav_observer.StartWatchingNewWebContents();
  SendCommand(security_interstitials::CMD_OPEN_DIAGNOSTIC_IN_NEW_TAB);
  nav_observer.Wait();

  // A new tab has been opened.
  EXPECT_EQ(2, browser()->tab_strip_model()->count());
  // The new tab is active.
  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
  WebContents* new_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_NE(new_tab, interstitial_tab);

  // Interstitial does not display in the new tab.
  EXPECT_FALSE(IsShowingInterstitial(new_tab));

  // Interstitial should still display in the background tab.
  EXPECT_TRUE(IsShowingInterstitial(interstitial_tab));
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       OpenReportingPrivacyInNewTab) {
  SetupWarningAndNavigate(browser());
  WebContents* interstitial_tab =
      browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(interstitial_tab);

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

  content::TestNavigationObserver nav_observer(nullptr);
  nav_observer.StartWatchingNewWebContents();
  SendCommand(security_interstitials::CMD_OPEN_REPORTING_PRIVACY_IN_NEW_TAB);
  nav_observer.Wait();

  // A new tab has been opened.
  EXPECT_EQ(2, browser()->tab_strip_model()->count());
  // The new tab is active.
  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
  WebContents* new_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_NE(new_tab, interstitial_tab);

  // Interstitial does not display in the new tab.
  EXPECT_FALSE(IsShowingInterstitial(new_tab));

  // Interstitial should still display in the background tab.
  EXPECT_TRUE(IsShowingInterstitial(interstitial_tab));
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       OpenWhitepaperInNewTab) {
  SetupWarningAndNavigate(browser());
  WebContents* interstitial_tab =
      browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(interstitial_tab);

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

  content::TestNavigationObserver nav_observer(nullptr);
  nav_observer.StartWatchingNewWebContents();
  SendCommand(security_interstitials::CMD_OPEN_WHITEPAPER_IN_NEW_TAB);
  nav_observer.Wait();

  // A new tab has been opened.
  EXPECT_EQ(2, browser()->tab_strip_model()->count());
  // The new tab is active.
  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
  WebContents* new_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_NE(new_tab, interstitial_tab);

  // Interstitial does not display in the new tab.
  EXPECT_FALSE(IsShowingInterstitial(new_tab));

  // Interstitial should still display in the background tab.
  EXPECT_TRUE(IsShowingInterstitial(interstitial_tab));
}

IN_PROC_BROWSER_TEST_P(SafeBrowsingBlockingPageBrowserTest,
                       ReportPhishingErrorInNewTab) {
  SetupWarningAndNavigate(browser());
  WebContents* interstitial_tab =
      browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_TRUE(interstitial_tab);

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

  content::TestNavigationObserver nav_observer(nullptr);
  nav_observer.StartWatchingNewWebContents();
  SendCommand(security_interstitials::CMD_REPORT_PHISHING_ERROR_IN_NEW_TAB);
  nav_observer.Wait();

  // A new tab has been opened.
  EXPECT_EQ(2, browser()->tab_strip_model()->count());
  // The new tab is active.
  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
  WebContents* new_tab = browser()->tab_strip_model()->GetActiveWebContents();
  ASSERT_NE(new_tab, interstitial_tab);

  // Interstitial does not display in the new tab.
  EXPECT_FALSE(IsShowingInterstitial(new_tab));

  // Interstitial should still display in the background tab.
  EXPECT_TRUE(IsShowingInterstitial(interstitial_tab));
}

}  // namespace safe_browsing
