Create SSLErrorNavigationThrottle.

The committed interstitials project uses SSLErrorNavigationThrottle to watch for
failing requests that are certificate errors. It defers such requests, and then
cancels with the appropriate error code and error page content HTML based on the
blocking page it receives from calling SSLErrorHandler::HandleSSLError, via a
"blocking page ready callback".

To support this, HandleSSLError is updated to pass its constructed blocking page
to the blocking page ready callback when the callback is present, *instead* of
calling Show() on the blocking page. Since this passes ownership to the
SSLErrorNavigationThrottle, we also create a new SSLErrorTabHelper that
SSLErrorNavigationThrottle uses to associate a blocking page with its web
contents for as long as it'sr needed (i.e. until its navigation has committed
and the tab has subsequently navigated away).

Bug: 752370
Cq-Include-Trybots: master.tryserver.chromium.linux:linux_site_isolation
Change-Id: I6525912a28e48b9268aa0f9798b5e1b276cb25a6
Reviewed-on: https://chromium-review.googlesource.com/621236
Commit-Queue: Lucas Garron <[email protected]>
Reviewed-by: Emily Stark <[email protected]>
Reviewed-by: Camille Lamy <[email protected]>
Cr-Commit-Position: refs/heads/master@{#515789}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 0081691..3231dd92 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1375,6 +1375,10 @@
     "ssl/ssl_error_assistant.h",
     "ssl/ssl_error_handler.cc",
     "ssl/ssl_error_handler.h",
+    "ssl/ssl_error_navigation_throttle.cc",
+    "ssl/ssl_error_navigation_throttle.h",
+    "ssl/ssl_error_tab_helper.cc",
+    "ssl/ssl_error_tab_helper.h",
     "status_icons/status_icon.cc",
     "status_icons/status_icon.h",
     "status_icons/status_icon_menu_model.cc",
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index c96cd64..1917b033 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -97,6 +97,7 @@
 #include "chrome/browser/ssl/ssl_cert_reporter.h"
 #include "chrome/browser/ssl/ssl_client_certificate_selector.h"
 #include "chrome/browser/ssl/ssl_error_handler.h"
+#include "chrome/browser/ssl/ssl_error_navigation_throttle.h"
 #include "chrome/browser/subresource_filter/chrome_subresource_filter_client.h"
 #include "chrome/browser/sync_file_system/local/sync_file_system_backend.h"
 #include "chrome/browser/tab_contents/tab_util.h"
@@ -639,8 +640,9 @@
 class CertificateReportingServiceCertReporter : public SSLCertReporter {
  public:
   explicit CertificateReportingServiceCertReporter(
-      CertificateReportingService* service)
-      : service_(service) {}
+      content::WebContents* web_contents)
+      : service_(CertificateReportingServiceFactory::GetForBrowserContext(
+            web_contents->GetBrowserContext())) {}
   ~CertificateReportingServiceCertReporter() override {}
 
   // SSLCertReporter implementation
@@ -2270,17 +2272,14 @@
   }
 
   // Otherwise, display an SSL blocking page. The interstitial page takes
-  // ownership of ssl_blocking_page.
-
-  CertificateReportingService* cert_reporting_service =
-      CertificateReportingServiceFactory::GetForBrowserContext(
-          web_contents->GetBrowserContext());
-  std::unique_ptr<CertificateReportingServiceCertReporter> cert_reporter(
-      new CertificateReportingServiceCertReporter(cert_reporting_service));
-
+  // ownership of ssl_blocking_page. We pass a null BlockingPageReadyCallback()
+  // to indicate that we don't want SSLErrorHandler to take the committed
+  // interstitials code path.
   SSLErrorHandler::HandleSSLError(
       web_contents, cert_error, ssl_info, request_url, strict_enforcement,
-      expired_previous_decision, std::move(cert_reporter), callback);
+      expired_previous_decision,
+      std::make_unique<CertificateReportingServiceCertReporter>(web_contents),
+      callback, SSLErrorHandler::BlockingPageReadyCallback());
 }
 
 void ChromeContentBrowserClient::SelectClientCertificate(
@@ -3417,6 +3416,14 @@
   if (tab_under_throttle)
     throttles.push_back(std::move(tab_under_throttle));
 
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kCommittedInterstitials)) {
+    throttles.push_back(std::make_unique<SSLErrorNavigationThrottle>(
+        handle,
+        std::make_unique<CertificateReportingServiceCertReporter>(web_contents),
+        base::Bind(&SSLErrorHandler::HandleSSLError)));
+  }
+
   return throttles;
 }
 
diff --git a/chrome/browser/ssl/ssl_blocking_page.h b/chrome/browser/ssl/ssl_blocking_page.h
index b5c1ff3..aabe97bf 100644
--- a/chrome/browser/ssl/ssl_blocking_page.h
+++ b/chrome/browser/ssl/ssl_blocking_page.h
@@ -77,19 +77,6 @@
   friend class policy::PolicyTest_SSLErrorOverridingDisallowed_Test;
   friend class SSLUITest;
 
-  // InterstitialPageDelegate implementation.
-  void CommandReceived(const std::string& command) override;
-  void OverrideEntry(content::NavigationEntry* entry) override;
-  void OverrideRendererPrefs(content::RendererPreferences* prefs) override;
-  void OnProceed() override;
-  void OnDontProceed() override;
-
-  // SecurityInterstitialPage implementation:
-  bool ShouldCreateNewNavigation() const override;
-  void PopulateInterstitialStrings(
-      base::DictionaryValue* load_time_data) override;
-
- private:
   SSLBlockingPage(
       content::WebContents* web_contents,
       int cert_error,
@@ -104,6 +91,19 @@
       const base::Callback<void(content::CertificateRequestResultType)>&
           callback);
 
+  // InterstitialPageDelegate implementation.
+  void CommandReceived(const std::string& command) override;
+  void OverrideEntry(content::NavigationEntry* entry) override;
+  void OverrideRendererPrefs(content::RendererPreferences* prefs) override;
+  void OnProceed() override;
+  void OnDontProceed() override;
+
+  // SecurityInterstitialPage implementation:
+  bool ShouldCreateNewNavigation() const override;
+  void PopulateInterstitialStrings(
+      base::DictionaryValue* load_time_data) override;
+
+ private:
   void NotifyDenyCertificate();
 
   base::Callback<void(content::CertificateRequestResultType)> callback_;
diff --git a/chrome/browser/ssl/ssl_browser_tests.cc b/chrome/browser/ssl/ssl_browser_tests.cc
index 18a3f95..1d979a3 100644
--- a/chrome/browser/ssl/ssl_browser_tests.cc
+++ b/chrome/browser/ssl/ssl_browser_tests.cc
@@ -253,6 +253,18 @@
   AuthState::Check(*entry, expected_authentication_state);
 }
 
+void CheckProceedLinkExists(content::RenderViewHost* rvh) {
+  int result = security_interstitials::CMD_ERROR;
+  const std::string javascript = base::StringPrintf(
+      "domAutomationController.send("
+      "(document.querySelector(\"#proceed-link\") === null) "
+      "? (%d) : (%d))",
+      security_interstitials::CMD_TEXT_NOT_FOUND,
+      security_interstitials::CMD_TEXT_FOUND);
+  ASSERT_TRUE(content::ExecuteScriptAndExtractInt(rvh, javascript, &result));
+  EXPECT_EQ(security_interstitials::CMD_TEXT_FOUND, result);
+}
+
 // This observer waits for the SSLErrorHandler to start an interstitial timer
 // for the given web contents.
 class SSLInterstitialTimerObserver {
@@ -3509,7 +3521,7 @@
 }
 
 // Verifies that an overridable interstitial has a proceed link.
-IN_PROC_BROWSER_TEST_F(SSLUITest, TestInterstitialOptionsOverridable) {
+IN_PROC_BROWSER_TEST_F(SSLUITest, ProceedLinkOverridable) {
   ASSERT_TRUE(https_server_expired_.Start());
   WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
   ui_test_utils::NavigateToURL(
@@ -3526,16 +3538,29 @@
 
   content::RenderFrameHost* rfh = interstitial_page->GetMainFrame();
   ASSERT_TRUE(content::WaitForRenderFrameReady(rfh));
-  int result = security_interstitials::CMD_ERROR;
-  const std::string javascript = base::StringPrintf(
-      "domAutomationController.send("
-      "(document.querySelector(\"#proceed-link\") === null) "
-      "? (%d) : (%d))",
-      security_interstitials::CMD_TEXT_NOT_FOUND,
-      security_interstitials::CMD_TEXT_FOUND);
-  ASSERT_TRUE(content::ExecuteScriptAndExtractInt(interstitial_rvh, javascript,
-                                                  &result));
-  EXPECT_EQ(security_interstitials::CMD_TEXT_FOUND, result);
+
+  ASSERT_NO_FATAL_FAILURE(CheckProceedLinkExists(interstitial_rvh));
+}
+
+// Verifies that an overridable committed interstitial has a proceed link.
+IN_PROC_BROWSER_TEST_F(SSLUITestCommittedInterstitials,
+                       ProceedLinkOverridable) {
+  if (!content::IsBrowserSideNavigationEnabled())
+    return;
+
+  ASSERT_TRUE(https_server_expired_.Start());
+  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
+  ui_test_utils::NavigateToURL(
+      browser(), https_server_expired_.GetURL("/ssl/google.html"));
+
+  CheckSecurityState(tab, net::CERT_STATUS_DATE_INVALID,
+                     security_state::DANGEROUS, AuthState::SHOWING_ERROR);
+
+  content::RenderFrameHost* rfh = tab->GetMainFrame();
+  ASSERT_TRUE(content::WaitForRenderFrameReady(rfh));
+
+  content::RenderViewHost* rvh = tab->GetRenderViewHost();
+  ASSERT_NO_FATAL_FAILURE(CheckProceedLinkExists(rvh));
 }
 
 // Verifies that a non-overridable interstitial does not have a proceed link.
diff --git a/chrome/browser/ssl/ssl_error_handler.cc b/chrome/browser/ssl/ssl_error_handler.cc
index 399dd7d1..cf4a2e03 100644
--- a/chrome/browser/ssl/ssl_error_handler.cc
+++ b/chrome/browser/ssl/ssl_error_handler.cc
@@ -393,7 +393,8 @@
       const GURL& request_url,
       std::unique_ptr<SSLCertReporter> ssl_cert_reporter,
       const base::Callback<void(content::CertificateRequestResultType)>&
-          callback)
+          decision_callback,
+      SSLErrorHandler::BlockingPageReadyCallback blocking_page_ready_callback)
       : web_contents_(web_contents),
         ssl_info_(ssl_info),
         profile_(profile),
@@ -402,7 +403,9 @@
         is_superfish_(is_superfish),
         request_url_(request_url),
         ssl_cert_reporter_(std::move(ssl_cert_reporter)),
-        callback_(callback) {}
+        decision_callback_(decision_callback),
+        blocking_page_ready_callback_(std::move(blocking_page_ready_callback)) {
+  }
   ~SSLErrorHandlerDelegateImpl() override;
 
   // SSLErrorHandler::Delegate methods:
@@ -423,8 +426,13 @@
                                 ssl_errors::ClockState clock_state) override;
 
  private:
+  // Calls the |blocking_page_ready_callback_| if it's not null, else calls
+  // Show() on the given interstitial.
+  void OnBlockingPageReady(
+      security_interstitials::SecurityInterstitialPage* interstitial_page);
+
   content::WebContents* web_contents_;
-  const net::SSLInfo& ssl_info_;
+  const net::SSLInfo ssl_info_;
   Profile* const profile_;
   const int cert_error_;
   const int options_mask_;
@@ -432,7 +440,9 @@
   const GURL request_url_;
   std::unique_ptr<CommonNameMismatchHandler> common_name_mismatch_handler_;
   std::unique_ptr<SSLCertReporter> ssl_cert_reporter_;
-  const base::Callback<void(content::CertificateRequestResultType)> callback_;
+  const base::Callback<void(content::CertificateRequestResultType)>
+      decision_callback_;
+  SSLErrorHandler::BlockingPageReadyCallback blocking_page_ready_callback_;
 };
 
 SSLErrorHandlerDelegateImpl::~SSLErrorHandlerDelegateImpl() {
@@ -492,39 +502,47 @@
 void SSLErrorHandlerDelegateImpl::ShowCaptivePortalInterstitial(
     const GURL& landing_url) {
   // Show captive portal blocking page. The interstitial owns the blocking page.
-  (new CaptivePortalBlockingPage(web_contents_, request_url_, landing_url,
-                                 std::move(ssl_cert_reporter_), ssl_info_,
-                                 callback_))
-      ->Show();
+  OnBlockingPageReady(new CaptivePortalBlockingPage(
+      web_contents_, request_url_, landing_url, std::move(ssl_cert_reporter_),
+      ssl_info_, decision_callback_));
 }
 
 void SSLErrorHandlerDelegateImpl::ShowMITMSoftwareInterstitial(
     const std::string& mitm_software_name,
     bool is_enterprise_managed) {
   // Show MITM software blocking page. The interstitial owns the blocking page.
-  (new MITMSoftwareBlockingPage(
-       web_contents_, cert_error_, request_url_, std::move(ssl_cert_reporter_),
-       ssl_info_, mitm_software_name, is_enterprise_managed, callback_))
-      ->Show();
+  OnBlockingPageReady(new MITMSoftwareBlockingPage(
+      web_contents_, cert_error_, request_url_, std::move(ssl_cert_reporter_),
+      ssl_info_, mitm_software_name, is_enterprise_managed,
+      decision_callback_));
 }
 
 void SSLErrorHandlerDelegateImpl::ShowSSLInterstitial() {
   // Show SSL blocking page. The interstitial owns the blocking page.
-  (SSLBlockingPage::Create(web_contents_, cert_error_, ssl_info_, request_url_,
-                           options_mask_, base::Time::NowFromSystemTime(),
-                           std::move(ssl_cert_reporter_), is_superfish_,
-                           callback_))
-      ->Show();
+  OnBlockingPageReady(SSLBlockingPage::Create(
+      web_contents_, cert_error_, ssl_info_, request_url_, options_mask_,
+      base::Time::NowFromSystemTime(), std::move(ssl_cert_reporter_),
+      is_superfish_, decision_callback_));
 }
 
 void SSLErrorHandlerDelegateImpl::ShowBadClockInterstitial(
     const base::Time& now,
     ssl_errors::ClockState clock_state) {
   // Show bad clock page. The interstitial owns the blocking page.
-  (new BadClockBlockingPage(web_contents_, cert_error_, ssl_info_, request_url_,
-                            now, clock_state, std::move(ssl_cert_reporter_),
-                            callback_))
-      ->Show();
+  OnBlockingPageReady(new BadClockBlockingPage(
+      web_contents_, cert_error_, ssl_info_, request_url_, now, clock_state,
+      std::move(ssl_cert_reporter_), decision_callback_));
+}
+
+void SSLErrorHandlerDelegateImpl::OnBlockingPageReady(
+    security_interstitials::SecurityInterstitialPage* interstitial_page) {
+  if (blocking_page_ready_callback_.is_null()) {
+    interstitial_page->Show();
+  } else {
+    std::move(blocking_page_ready_callback_)
+        .Run(std::unique_ptr<security_interstitials::SecurityInterstitialPage>(
+            interstitial_page));
+  }
 }
 
 int IsCertErrorFatal(int cert_error) {
@@ -567,7 +585,10 @@
     bool expired_previous_decision,
     std::unique_ptr<SSLCertReporter> ssl_cert_reporter,
     const base::Callback<void(content::CertificateRequestResultType)>&
-        callback) {
+        decision_callback,
+    base::OnceCallback<
+        void(std::unique_ptr<security_interstitials::SecurityInterstitialPage>)>
+        blocking_page_ready_callback) {
   DCHECK(!FromWebContents(web_contents));
 
   Profile* profile =
@@ -588,8 +609,9 @@
           new SSLErrorHandlerDelegateImpl(
               web_contents, ssl_info, profile, cert_error, options_mask,
               is_superfish, request_url, std::move(ssl_cert_reporter),
-              callback)),
-      web_contents, profile, cert_error, ssl_info, request_url, callback);
+              decision_callback, std::move(blocking_page_ready_callback))),
+      web_contents, profile, cert_error, ssl_info, request_url,
+      decision_callback);
   web_contents->SetUserData(UserDataKey(), base::WrapUnique(error_handler));
   error_handler->StartHandlingError();
 }
@@ -665,7 +687,8 @@
     int cert_error,
     const net::SSLInfo& ssl_info,
     const GURL& request_url,
-    const base::Callback<void(content::CertificateRequestResultType)>& callback)
+    const base::Callback<void(content::CertificateRequestResultType)>&
+        decision_callback)
     : content::WebContentsObserver(web_contents),
       delegate_(std::move(delegate)),
       web_contents_(web_contents),
@@ -673,7 +696,7 @@
       cert_error_(cert_error),
       ssl_info_(ssl_info),
       request_url_(request_url),
-      callback_(callback),
+      decision_callback_(decision_callback),
       weak_ptr_factory_(this) {}
 
 SSLErrorHandler::~SSLErrorHandler() {
@@ -893,8 +916,8 @@
 void SSLErrorHandler::DeleteSSLErrorHandler() {
   // Need to explicity deny the certificate via the callback, otherwise memory
   // is leaked.
-  if (!callback_.is_null()) {
-    base::ResetAndReturn(&callback_)
+  if (!decision_callback_.is_null()) {
+    base::ResetAndReturn(&decision_callback_)
         .Run(content::CERTIFICATE_REQUEST_RESULT_TYPE_DENY);
   }
   delegate_.reset();
diff --git a/chrome/browser/ssl/ssl_error_handler.h b/chrome/browser/ssl/ssl_error_handler.h
index 1bcea96..a7a110cb 100644
--- a/chrome/browser/ssl/ssl_error_handler.h
+++ b/chrome/browser/ssl/ssl_error_handler.h
@@ -17,6 +17,7 @@
 #include "chrome/browser/ssl/common_name_mismatch_handler.h"
 #include "chrome/browser/ssl/ssl_cert_reporter.h"
 #include "chrome/browser/ssl/ssl_error_assistant.pb.h"
+#include "components/security_interstitials/content/security_interstitial_page.h"
 #include "components/ssl_errors/error_classification.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
@@ -55,9 +56,13 @@
 // - Check for a known captive portal certificate SPKI
 // - Wait for a name-mismatch suggested URL
 // - or Wait for a captive portal result to arrive.
+//
 // Based on the result of these checks, SSLErrorHandler will show a customized
 // interstitial, redirect to a different suggested URL, or, if all else fails,
-// show the normal SSL interstitial.
+// show the normal SSL interstitial. When --committed--interstitials is enabled,
+// it passes a constructed blocking page to the |blocking_page_ready_callback|
+// that was given to HandleSSLError(), instead of showing the interstitial
+// directly.
 //
 // This class should only be used on the UI thread because its implementation
 // uses captive_portal::CaptivePortalService which can only be accessed on the
@@ -67,6 +72,9 @@
                         public content::NotificationObserver {
  public:
   typedef base::Callback<void(content::WebContents*)> TimerStartedCallback;
+  typedef base::OnceCallback<void(
+      std::unique_ptr<security_interstitials::SecurityInterstitialPage>)>
+      BlockingPageReadyCallback;
 
   ~SSLErrorHandler() override;
 
@@ -114,8 +122,12 @@
         ssl_errors::ClockState clock_state) = 0;
   };
 
-  // Entry point for the class. The parameters are the same as SSLBlockingPage
-  // constructor.
+  // Entry point for the class. All parameters except
+  // |blocking_page_ready_callback| are the same as SSLBlockingPage constructor.
+  // |blocking_page_ready_callback| is intended for committed interstitials. If
+  // |blocking_page_ready_callback| is null, this function will create a
+  // blocking page and call Show() on it. Otherwise, this function creates an
+  // interstitial and passes it to |blocking_page_ready_callback|.
   static void HandleSSLError(
       content::WebContents* web_contents,
       int cert_error,
@@ -125,7 +137,8 @@
       bool expired_previous_decision,
       std::unique_ptr<SSLCertReporter> ssl_cert_reporter,
       const base::Callback<void(content::CertificateRequestResultType)>&
-          callback);
+          decision_callback,
+      BlockingPageReadyCallback blocking_page_ready_callback);
 
   // Sets the binary proto for SSL error assistant. The binary proto
   // can be downloaded by the component updater, or set by tests.
@@ -217,7 +230,8 @@
   const int cert_error_;
   const net::SSLInfo ssl_info_;
   const GURL request_url_;
-  base::Callback<void(content::CertificateRequestResultType)> callback_;
+  base::Callback<void(content::CertificateRequestResultType)>
+      decision_callback_;
 
   content::NotificationRegistrar registrar_;
   base::OneShotTimer timer_;
diff --git a/chrome/browser/ssl/ssl_error_navigation_throttle.cc b/chrome/browser/ssl/ssl_error_navigation_throttle.cc
new file mode 100644
index 0000000..2a835c4
--- /dev/null
+++ b/chrome/browser/ssl/ssl_error_navigation_throttle.cc
@@ -0,0 +1,83 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ssl/ssl_error_navigation_throttle.h"
+
+#include "base/bind.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/ssl/ssl_error_tab_helper.h"
+#include "chrome/common/chrome_switches.h"
+#include "components/security_interstitials/content/security_interstitial_page.h"
+#include "content/public/browser/navigation_handle.h"
+#include "net/cert/cert_status_flags.h"
+
+SSLErrorNavigationThrottle::SSLErrorNavigationThrottle(
+    content::NavigationHandle* navigation_handle,
+    std::unique_ptr<SSLCertReporter> ssl_cert_reporter,
+    SSLErrorNavigationThrottle::HandleSSLErrorCallback
+        handle_ssl_error_callback)
+    : content::NavigationThrottle(navigation_handle),
+      ssl_cert_reporter_(std::move(ssl_cert_reporter)),
+      handle_ssl_error_callback_(std::move(handle_ssl_error_callback)),
+      weak_ptr_factory_(this) {}
+
+SSLErrorNavigationThrottle::~SSLErrorNavigationThrottle() {}
+
+const char* SSLErrorNavigationThrottle::GetNameForLogging() {
+  return "SSLErrorNavigationThrottle";
+}
+
+content::NavigationThrottle::ThrottleCheckResult
+SSLErrorNavigationThrottle::WillFailRequest() {
+  DCHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
+      switches::kCommittedInterstitials));
+  content::NavigationHandle* handle = navigation_handle();
+  if (!handle->GetSSLInfo().has_value()) {
+    return content::NavigationThrottle::PROCEED;
+  }
+
+  int cert_status = handle->GetSSLInfo().value().cert_status;
+  if (!net::IsCertStatusError(cert_status) ||
+      net::IsCertStatusMinorError(cert_status)) {
+    return content::NavigationThrottle::PROCEED;
+  }
+
+  // We don't know whether SSLErrorHandler will call the ShowInterstitial()
+  // callback synchronously, so we post a task that will run after this function
+  // defers the navigation. This ensures that ShowInterstitial() can always
+  // safely call CancelDeferredNavigation().
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          std::move(handle_ssl_error_callback_), handle->GetWebContents(),
+          net::MapCertStatusToNetError(cert_status),
+          handle->GetSSLInfo().value(), handle->GetURL(),
+          handle->ShouldSSLErrorsBeFatal(), false,
+          std::move(ssl_cert_reporter_),
+          base::Callback<void(content::CertificateRequestResultType)>(),
+          base::BindOnce(&SSLErrorNavigationThrottle::ShowInterstitial,
+                         weak_ptr_factory_.GetWeakPtr())));
+
+  return content::NavigationThrottle::ThrottleCheckResult(
+      content::NavigationThrottle::DEFER);
+}
+
+void SSLErrorNavigationThrottle::ShowInterstitial(
+    std::unique_ptr<security_interstitials::SecurityInterstitialPage>
+        blocking_page) {
+  content::NavigationHandle* handle = navigation_handle();
+  int net_error =
+      net::MapCertStatusToNetError(handle->GetSSLInfo().value().cert_status);
+
+  // Get the error page content before giving up ownership of |blocking_page|.
+  std::string error_page_content = blocking_page->GetHTMLContents();
+
+  SSLErrorTabHelper::AssociateBlockingPage(handle->GetWebContents(),
+                                           handle->GetNavigationId(),
+                                           std::move(blocking_page));
+
+  CancelDeferredNavigation(content::NavigationThrottle::ThrottleCheckResult(
+      content::NavigationThrottle::CANCEL, static_cast<net::Error>(net_error),
+      error_page_content));
+}
diff --git a/chrome/browser/ssl/ssl_error_navigation_throttle.h b/chrome/browser/ssl/ssl_error_navigation_throttle.h
new file mode 100644
index 0000000..44de848
--- /dev/null
+++ b/chrome/browser/ssl/ssl_error_navigation_throttle.h
@@ -0,0 +1,67 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SSL_SSL_ERROR_NAVIGATION_THROTTLE_H_
+#define CHROME_BROWSER_SSL_SSL_ERROR_NAVIGATION_THROTTLE_H_
+
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/ssl/ssl_error_handler.h"
+#include "content/public/browser/navigation_throttle.h"
+
+class SSLCertReporter;
+
+namespace content {
+class NavigationHandle;
+}  // namespace content
+
+namespace security_interstitials {
+class SecurityInterstitialPage;
+}  // namespace security_interstitials
+
+// SSLErrorNavigationThrottle watches for failed navigations that should be
+// displayed as SSL interstitial pages. More specifically,
+// SSLErrorNavigationThrottle::WillFailRequest() will defer any navigations that
+// failed due to a certificate error. After calculating which interstitial to
+// show, it will cancel the navigation with the interstitial's custom error page
+// HTML.
+class SSLErrorNavigationThrottle : public content::NavigationThrottle {
+ public:
+  typedef base::OnceCallback<void(
+      content::WebContents* web_contents,
+      int cert_error,
+      const net::SSLInfo& ssl_info,
+      const GURL& request_url,
+      bool should_ssl_errors_be_fatal,
+      bool expired_previous_decision,
+      std::unique_ptr<SSLCertReporter> ssl_cert_reporter,
+      const base::Callback<void(content::CertificateRequestResultType)>&
+          decision_callback,
+      base::OnceCallback<void(
+          std::unique_ptr<security_interstitials::SecurityInterstitialPage>)>
+          blocking_page_ready_callback)>
+      HandleSSLErrorCallback;
+
+  explicit SSLErrorNavigationThrottle(
+      content::NavigationHandle* handle,
+      std::unique_ptr<SSLCertReporter> ssl_cert_reporter,
+      HandleSSLErrorCallback handle_ssl_error_callback);
+  ~SSLErrorNavigationThrottle() override;
+
+  // content::NavigationThrottle:
+  ThrottleCheckResult WillFailRequest() override;
+  const char* GetNameForLogging() override;
+
+ private:
+  void ShowInterstitial(
+      std::unique_ptr<security_interstitials::SecurityInterstitialPage>
+          blocking_page);
+
+  std::unique_ptr<SSLCertReporter> ssl_cert_reporter_;
+  HandleSSLErrorCallback handle_ssl_error_callback_;
+  base::WeakPtrFactory<SSLErrorNavigationThrottle> weak_ptr_factory_;
+};
+
+#endif  // CHROME_BROWSER_SSL_SSL_ERROR_NAVIGATION_THROTTLE_H_
diff --git a/chrome/browser/ssl/ssl_error_navigation_throttle_unittest.cc b/chrome/browser/ssl/ssl_error_navigation_throttle_unittest.cc
new file mode 100644
index 0000000..f17a29f
--- /dev/null
+++ b/chrome/browser/ssl/ssl_error_navigation_throttle_unittest.cc
@@ -0,0 +1,191 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ssl/ssl_error_navigation_throttle.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/run_loop.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/ssl/certificate_reporting_test_utils.cc"
+#include "chrome/browser/ssl/ssl_blocking_page.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/navigation_throttle.h"
+#include "content/public/common/browser_side_navigation_policy.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/test/cert_test_util.h"
+#include "net/test/test_data_directory.h"
+
+namespace {
+
+// Replacement for SSLErrorHandler::HandleSSLError that calls
+// |blocking_page_ready_callback|. |async| specifies whether this call should be
+// done synchronously or using PostTask().
+void MockHandleSSLError(
+    bool async,
+    content::WebContents* web_contents,
+    int cert_error,
+    const net::SSLInfo& ssl_info,
+    const GURL& request_url,
+    bool should_ssl_errors_be_fatal,
+    bool expired_previous_decision,
+    std::unique_ptr<SSLCertReporter> ssl_cert_reporter,
+    const base::Callback<void(content::CertificateRequestResultType)>&
+        decision_callback,
+    base::OnceCallback<
+        void(std::unique_ptr<security_interstitials::SecurityInterstitialPage>)>
+        blocking_page_ready_callback) {
+  std::unique_ptr<SSLBlockingPage> blocking_page(
+      SSLBlockingPage::Create(web_contents, cert_error, ssl_info, request_url,
+                              0, base::Time::NowFromSystemTime(), nullptr,
+                              false /* is superfish */, decision_callback));
+  if (async) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(blocking_page_ready_callback),
+                                  std::move(blocking_page)));
+  } else {
+    std::move(blocking_page_ready_callback).Run(std::move(blocking_page));
+  }
+
+}  // namespace
+
+class TestSSLErrorNavigationThrottle : public SSLErrorNavigationThrottle {
+ public:
+  TestSSLErrorNavigationThrottle(
+      content::NavigationHandle* handle,
+      bool async_handle_ssl_error,
+      base::OnceCallback<void(content::NavigationThrottle::ThrottleCheckResult)>
+          on_cancel_deferred_navigation)
+      : SSLErrorNavigationThrottle(
+            handle,
+            certificate_reporting_test_utils::CreateMockSSLCertReporter(
+                base::Callback<void(const std::string&,
+                                    const certificate_reporting::
+                                        CertLoggerRequest_ChromeChannel)>(),
+                certificate_reporting_test_utils::CERT_REPORT_NOT_EXPECTED),
+            base::Bind(&MockHandleSSLError, async_handle_ssl_error)),
+        on_cancel_deferred_navigation_(
+            std::move(on_cancel_deferred_navigation)) {}
+
+  // NavigationThrottle:
+  void CancelDeferredNavigation(
+      content::NavigationThrottle::ThrottleCheckResult result) override {
+    std::move(on_cancel_deferred_navigation_).Run(result);
+  }
+
+ private:
+  base::OnceCallback<void(content::NavigationThrottle::ThrottleCheckResult)>
+      on_cancel_deferred_navigation_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestSSLErrorNavigationThrottle);
+};
+
+class SSLErrorNavigationThrottleTest
+    : public ChromeRenderViewHostTestHarness,
+      public testing::WithParamInterface<bool> {
+ public:
+  SSLErrorNavigationThrottleTest() {}
+  void SetUp() override {
+    ChromeRenderViewHostTestHarness::SetUp();
+    base::CommandLine::ForCurrentProcess()->AppendSwitch(
+        switches::kCommittedInterstitials);
+
+    async_ = GetParam();
+    handle_ = content::NavigationHandle::CreateNavigationHandleForTesting(
+        GURL(), main_rfh(), true /* committed */, net::ERR_CERT_INVALID);
+
+    std::unique_ptr<TestSSLErrorNavigationThrottle> throttle =
+        std::make_unique<TestSSLErrorNavigationThrottle>(
+            handle_.get(), async_,
+            base::BindOnce(
+                &SSLErrorNavigationThrottleTest::RecordDeferredResult,
+                base::Unretained(this)));
+    handle_->RegisterThrottleForTesting(std::move(throttle));
+  }
+
+  // content::RenderViewHostTestHarness:
+  void TearDown() override {
+    handle_.reset();
+    ChromeRenderViewHostTestHarness::TearDown();
+  }
+
+  void RecordDeferredResult(
+      content::NavigationThrottle::ThrottleCheckResult result) {
+    deferred_result_ = result;
+  }
+
+ protected:
+  bool async_ = false;
+  std::unique_ptr<content::NavigationHandle> handle_;
+  content::NavigationThrottle::ThrottleCheckResult deferred_result_ =
+      content::NavigationThrottle::DEFER;
+
+  DISALLOW_COPY_AND_ASSIGN(SSLErrorNavigationThrottleTest);
+};
+
+// Tests that the throttle ignores a request without SSL info.
+TEST_P(SSLErrorNavigationThrottleTest, NoSSLInfo) {
+  if (!content::IsBrowserSideNavigationEnabled())
+    return;
+  SCOPED_TRACE(::testing::Message()
+               << "Asynchronous MockHandleSSLError: " << async_);
+
+  content::NavigationThrottle::ThrottleCheckResult result =
+      handle_->CallWillFailRequestForTesting(
+          base::nullopt, false /* should_ssl_errors_be_fatal */);
+
+  EXPECT_FALSE(handle_->GetSSLInfo().has_value());
+  EXPECT_EQ(content::NavigationThrottle::PROCEED, result);
+}
+
+// Tests that the throttle ignores a request with a cert status that is not an
+// cert error.
+TEST_P(SSLErrorNavigationThrottleTest, SSLInfoWithoutCertError) {
+  if (!content::IsBrowserSideNavigationEnabled())
+    return;
+  SCOPED_TRACE(::testing::Message()
+               << "Asynchronous MockHandleSSLError: " << async_);
+
+  net::SSLInfo ssl_info;
+  ssl_info.cert_status = net::CERT_STATUS_IS_EV;
+  content::NavigationThrottle::ThrottleCheckResult result =
+      handle_->CallWillFailRequestForTesting(
+          ssl_info, false /* should_ssl_errors_be_fatal */);
+
+  ASSERT_TRUE(handle_->GetSSLInfo().has_value());
+  EXPECT_EQ(net::CERT_STATUS_IS_EV, handle_->GetSSLInfo().value().cert_status);
+  EXPECT_EQ(content::NavigationThrottle::PROCEED, result);
+}
+
+// Tests that the throttle defers and cancels a request with a cert status that
+// is a cert error.
+TEST_P(SSLErrorNavigationThrottleTest, SSLInfoWithCertError) {
+  if (!content::IsBrowserSideNavigationEnabled())
+    return;
+  SCOPED_TRACE(::testing::Message()
+               << "Asynchronous MockHandleSSLError: " << async_);
+
+  net::SSLInfo ssl_info;
+  ssl_info.cert =
+      net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
+  ssl_info.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;
+  content::NavigationThrottle::ThrottleCheckResult synchronous_result =
+      handle_->CallWillFailRequestForTesting(
+          ssl_info, false /* should_ssl_errors_be_fatal */);
+
+  EXPECT_EQ(content::NavigationThrottle::DEFER, synchronous_result.action());
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(content::NavigationThrottle::CANCEL, deferred_result_.action());
+  EXPECT_EQ(net::ERR_CERT_COMMON_NAME_INVALID,
+            deferred_result_.net_error_code());
+  EXPECT_TRUE(deferred_result_.error_page_content().has_value());
+}
+
+INSTANTIATE_TEST_CASE_P(,
+                        SSLErrorNavigationThrottleTest,
+                        ::testing::Values(false, true));
+
+}  // namespace
diff --git a/chrome/browser/ssl/ssl_error_tab_helper.cc b/chrome/browser/ssl/ssl_error_tab_helper.cc
new file mode 100644
index 0000000..94ed063
--- /dev/null
+++ b/chrome/browser/ssl/ssl_error_tab_helper.cc
@@ -0,0 +1,58 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ssl/ssl_error_tab_helper.h"
+
+#include "components/security_interstitials/content/security_interstitial_page.h"
+#include "content/public/browser/navigation_handle.h"
+
+DEFINE_WEB_CONTENTS_USER_DATA_KEY(SSLErrorTabHelper);
+
+SSLErrorTabHelper::~SSLErrorTabHelper() {}
+
+void SSLErrorTabHelper::DidFinishNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (navigation_handle->IsSameDocument()) {
+    return;
+  }
+
+  auto it = blocking_pages_for_navigations_.find(
+      navigation_handle->GetNavigationId());
+
+  if (it == blocking_pages_for_navigations_.end()) {
+    blocking_page_for_currently_committed_navigation_.reset();
+    return;
+  }
+
+  if (navigation_handle->HasCommitted()) {
+    blocking_page_for_currently_committed_navigation_ = std::move(it->second);
+  } else {
+    blocking_page_for_currently_committed_navigation_.reset();
+  }
+  blocking_pages_for_navigations_.erase(it);
+}
+
+// static
+void SSLErrorTabHelper::AssociateBlockingPage(
+    content::WebContents* web_contents,
+    int64_t navigation_id,
+    std::unique_ptr<security_interstitials::SecurityInterstitialPage>
+        blocking_page) {
+  // CreateForWebContents() creates a tab helper if it doesn't exist for
+  // |web_contents| yet.
+  SSLErrorTabHelper::CreateForWebContents(web_contents);
+
+  SSLErrorTabHelper* helper = SSLErrorTabHelper::FromWebContents(web_contents);
+  helper->SetBlockingPage(navigation_id, std::move(blocking_page));
+}
+
+SSLErrorTabHelper::SSLErrorTabHelper(content::WebContents* web_contents)
+    : WebContentsObserver(web_contents) {}
+
+void SSLErrorTabHelper::SetBlockingPage(
+    int64_t navigation_id,
+    std::unique_ptr<security_interstitials::SecurityInterstitialPage>
+        blocking_page) {
+  blocking_pages_for_navigations_[navigation_id] = std::move(blocking_page);
+}
diff --git a/chrome/browser/ssl/ssl_error_tab_helper.h b/chrome/browser/ssl/ssl_error_tab_helper.h
new file mode 100644
index 0000000..c915ac1
--- /dev/null
+++ b/chrome/browser/ssl/ssl_error_tab_helper.h
@@ -0,0 +1,67 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SSL_SSL_ERROR_TAB_HELPER_H_
+#define CHROME_BROWSER_SSL_SSL_ERROR_TAB_HELPER_H_
+
+#include <map>
+
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+
+namespace content {
+class NavigationHandle;
+class WebContents;
+}  // namespace content
+
+namespace security_interstitials {
+class SecurityInterstitialPage;
+}  // namespace security_interstitials
+
+// Long-lived helper associated with a WebContents, for owning blocking pages.
+class SSLErrorTabHelper
+    : public content::WebContentsObserver,
+      public content::WebContentsUserData<SSLErrorTabHelper> {
+ public:
+  ~SSLErrorTabHelper() override;
+
+  // WebContentsObserver:
+  void DidFinishNavigation(
+      content::NavigationHandle* navigation_handle) override;
+
+  // Associates |blocking_page| with an SSLErrorTabHelper for the given
+  // |web_contents| and |navigation_id|, to manage the |blocking_page|'s
+  // lifetime.
+  static void AssociateBlockingPage(
+      content::WebContents* web_contents,
+      int64_t navigation_id,
+      std::unique_ptr<security_interstitials::SecurityInterstitialPage>
+          blocking_page);
+
+ private:
+  explicit SSLErrorTabHelper(content::WebContents* web_contents);
+  friend class content::WebContentsUserData<SSLErrorTabHelper>;
+
+  void SetBlockingPage(
+      int64_t navigation_id,
+      std::unique_ptr<security_interstitials::SecurityInterstitialPage>
+          blocking_page);
+
+  // Keeps track of blocking pages for navigations that have encountered
+  // certificate errors in this WebContents. When a navigation commits, the
+  // corresponding blocking page is moved out and stored in
+  // |blocking_page_for_currently_committed_navigation_|.
+  std::map<int64_t,
+           std::unique_ptr<security_interstitials::SecurityInterstitialPage>>
+      blocking_pages_for_navigations_;
+  // Keeps track of the blocking page for the current committed navigation, if
+  // there is one. The value is replaced (if the new committed navigation has a
+  // blocking page) or reset on every committed navigation.
+  std::unique_ptr<security_interstitials::SecurityInterstitialPage>
+      blocking_page_for_currently_committed_navigation_;
+
+  DISALLOW_COPY_AND_ASSIGN(SSLErrorTabHelper);
+};
+
+#endif  // CHROME_BROWSER_SSL_SSL_ERROR_TAB_HELPER_H_
diff --git a/chrome/browser/ssl/ssl_error_tab_helper_unittest.cc b/chrome/browser/ssl/ssl_error_tab_helper_unittest.cc
new file mode 100644
index 0000000..8cbf2acc
--- /dev/null
+++ b/chrome/browser/ssl/ssl_error_tab_helper_unittest.cc
@@ -0,0 +1,164 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ssl/ssl_error_tab_helper.h"
+
+#include "base/bind.h"
+#include "base/time/time.h"
+#include "chrome/browser/interstitials/chrome_metrics_helper.h"
+#include "chrome/browser/ssl/ssl_blocking_page.h"
+#include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "content/public/browser/certificate_request_result_type.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/common/browser_side_navigation_policy.h"
+#include "net/base/net_errors.h"
+#include "net/test/cert_test_util.h"
+#include "net/test/test_data_directory.h"
+#include "url/gurl.h"
+
+namespace {
+
+const char kMetricsName[] = "test_ssl_blocking_page";
+
+std::unique_ptr<ChromeMetricsHelper> CreateMetricsHelper(
+    content::WebContents* web_contents) {
+  security_interstitials::MetricsHelper::ReportDetails report_details;
+  report_details.metric_prefix = kMetricsName;
+  return base::MakeUnique<ChromeMetricsHelper>(web_contents, GURL(),
+                                               report_details, kMetricsName);
+}
+
+class TestSSLBlockingPage : public SSLBlockingPage {
+ public:
+  // |*destroyed_tracker| is set to true in the destructor.
+  TestSSLBlockingPage(content::WebContents* web_contents,
+                      GURL request_url,
+                      bool* destroyed_tracker)
+      : SSLBlockingPage(
+            web_contents,
+            net::ERR_CERT_CONTAINS_ERRORS,
+            net::SSLInfo(),
+            request_url,
+            0,
+            base::Time::NowFromSystemTime(),
+            nullptr /* ssl_cert_reporter */,
+            true /* overridable */,
+            CreateMetricsHelper(web_contents),
+            false /* is_superfish */,
+            base::Callback<void(content::CertificateRequestResultType)>()),
+        destroyed_tracker_(destroyed_tracker) {}
+
+  ~TestSSLBlockingPage() override { *destroyed_tracker_ = true; }
+
+ private:
+  bool* destroyed_tracker_;
+};
+
+class SSLErrorTabHelperTest : public ChromeRenderViewHostTestHarness {
+ protected:
+  std::unique_ptr<content::NavigationHandle> CreateHandle(
+      bool committed,
+      bool is_same_document) {
+    return content::NavigationHandle::CreateNavigationHandleForTesting(
+        GURL(), main_rfh(), committed, net::OK, is_same_document);
+  }
+
+  // The lifetime of the blocking page is managed by the SSLErrorTabHelper for
+  // the test's web_contents. |destroyed_tracker| will be set to true when the
+  // corresponding blocking page is destroyed.
+  void CreateAssociatedBlockingPage(content::NavigationHandle* handle,
+                                    bool* destroyed_tracker) {
+    SSLErrorTabHelper::AssociateBlockingPage(
+        web_contents(), handle->GetNavigationId(),
+        std::make_unique<TestSSLBlockingPage>(web_contents(), GURL(),
+                                              destroyed_tracker));
+  }
+};
+
+// Tests that the helper properly handles the lifetime of a single blocking
+// page, interleaved with other navigations.
+TEST_F(SSLErrorTabHelperTest, SingleBlockingPage) {
+  std::unique_ptr<content::NavigationHandle> blocking_page_handle =
+      CreateHandle(true, false);
+  bool blocking_page_destroyed = false;
+  CreateAssociatedBlockingPage(blocking_page_handle.get(),
+                               &blocking_page_destroyed);
+  SSLErrorTabHelper* helper =
+      SSLErrorTabHelper::FromWebContents(web_contents());
+
+  // Test that a same-document navigation doesn't destroy the blocking page if
+  // its navigation hasn't committed yet.
+  std::unique_ptr<content::NavigationHandle> same_document_handle =
+      CreateHandle(true, true);
+  helper->DidFinishNavigation(same_document_handle.get());
+  EXPECT_FALSE(blocking_page_destroyed);
+
+  // Test that a committed (non-same-document) navigation doesn't destroy the
+  // blocking page if its navigation hasn't committed yet.
+  std::unique_ptr<content::NavigationHandle> committed_handle1 =
+      CreateHandle(true, false);
+  helper->DidFinishNavigation(committed_handle1.get());
+  EXPECT_FALSE(blocking_page_destroyed);
+
+  // Simulate comitting the interstitial.
+  helper->DidFinishNavigation(blocking_page_handle.get());
+  EXPECT_FALSE(blocking_page_destroyed);
+
+  // Test that a subsequent committed navigation releases the blocking page
+  // stored for the currently committed navigation.
+  std::unique_ptr<content::NavigationHandle> committed_handle2 =
+      CreateHandle(true, false);
+  helper->DidFinishNavigation(committed_handle2.get());
+  EXPECT_TRUE(blocking_page_destroyed);
+}
+
+// Tests that the helper properly handles the lifetime of multiple blocking
+// pages, committed in a different order than they are created.
+TEST_F(SSLErrorTabHelperTest, MultipleBlockingPages) {
+  // Simulate associating the first interstitial.
+  std::unique_ptr<content::NavigationHandle> handle1 =
+      CreateHandle(true, false);
+  bool blocking_page1_destroyed = false;
+  CreateAssociatedBlockingPage(handle1.get(), &blocking_page1_destroyed);
+
+  // We can directly retrieve the helper for testing once
+  // CreateAssociatedBlockingPage() was called.
+  SSLErrorTabHelper* helper =
+      SSLErrorTabHelper::FromWebContents(web_contents());
+
+  // Simulate commiting the first interstitial.
+  helper->DidFinishNavigation(handle1.get());
+  EXPECT_FALSE(blocking_page1_destroyed);
+
+  // Associate the second interstitial.
+  std::unique_ptr<content::NavigationHandle> handle2 =
+      CreateHandle(true, false);
+  bool blocking_page2_destroyed = false;
+  CreateAssociatedBlockingPage(handle2.get(), &blocking_page2_destroyed);
+  EXPECT_FALSE(blocking_page1_destroyed);
+  EXPECT_FALSE(blocking_page2_destroyed);
+
+  // Associate the third interstitial.
+  std::unique_ptr<content::NavigationHandle> handle3 =
+      CreateHandle(true, false);
+  bool blocking_page3_destroyed = false;
+  CreateAssociatedBlockingPage(handle3.get(), &blocking_page3_destroyed);
+  EXPECT_FALSE(blocking_page1_destroyed);
+  EXPECT_FALSE(blocking_page2_destroyed);
+  EXPECT_FALSE(blocking_page3_destroyed);
+
+  // Simulate commiting the third interstitial.
+  helper->DidFinishNavigation(handle3.get());
+  EXPECT_TRUE(blocking_page1_destroyed);
+  EXPECT_FALSE(blocking_page2_destroyed);
+  EXPECT_FALSE(blocking_page3_destroyed);
+
+  // Simulate commiting the second interstitial.
+  helper->DidFinishNavigation(handle2.get());
+  EXPECT_TRUE(blocking_page1_destroyed);
+  EXPECT_FALSE(blocking_page2_destroyed);
+  EXPECT_TRUE(blocking_page3_destroyed);
+}
+
+}  // namespace
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 9a63f32..d6edb00 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3401,6 +3401,8 @@
     "../browser/ssl/security_state_tab_helper_unittest.cc",
     "../browser/ssl/ssl_error_assistant_unittest.cc",
     "../browser/ssl/ssl_error_handler_unittest.cc",
+    "../browser/ssl/ssl_error_navigation_throttle_unittest.cc",
+    "../browser/ssl/ssl_error_tab_helper_unittest.cc",
     "../browser/status_icons/status_icon_menu_model_unittest.cc",
     "../browser/status_icons/status_icon_unittest.cc",
     "../browser/status_icons/status_tray_unittest.cc",
diff --git a/components/security_interstitials/content/security_interstitial_page.cc b/components/security_interstitials/content/security_interstitial_page.cc
index e30827e..c5a4d156 100644
--- a/components/security_interstitials/content/security_interstitial_page.cc
+++ b/components/security_interstitials/content/security_interstitial_page.cc
@@ -65,6 +65,18 @@
   create_view_ = false;
 }
 
+std::string SecurityInterstitialPage::GetHTMLContents() {
+  base::DictionaryValue load_time_data;
+  PopulateInterstitialStrings(&load_time_data);
+  webui::SetLoadTimeDataDefaults(controller()->GetApplicationLocale(),
+                                 &load_time_data);
+  std::string html = ui::ResourceBundle::GetSharedInstance()
+                         .GetRawDataResource(GetHTMLTemplateId())
+                         .as_string();
+  webui::AppendWebUiCssTextDefaults(&html);
+  return webui::GetI18nTemplateHtml(html, &load_time_data);
+}
+
 void SecurityInterstitialPage::Show() {
   DCHECK(!interstitial_page_);
   interstitial_page_ = content::InterstitialPage::Create(
@@ -110,16 +122,4 @@
   return IDR_SECURITY_INTERSTITIAL_HTML;
 }
 
-std::string SecurityInterstitialPage::GetHTMLContents() {
-  base::DictionaryValue load_time_data;
-  PopulateInterstitialStrings(&load_time_data);
-  webui::SetLoadTimeDataDefaults(
-      controller()->GetApplicationLocale(), &load_time_data);
-  std::string html = ui::ResourceBundle::GetSharedInstance()
-                         .GetRawDataResource(GetHTMLTemplateId())
-                         .as_string();
-  webui::AppendWebUiCssTextDefaults(&html);
-  return webui::GetI18nTemplateHtml(html, &load_time_data);
-}
-
 }  // security_interstitials
diff --git a/components/security_interstitials/content/security_interstitial_page.h b/components/security_interstitials/content/security_interstitial_page.h
index 7cf5fc2..3bca87c8 100644
--- a/components/security_interstitials/content/security_interstitial_page.h
+++ b/components/security_interstitials/content/security_interstitial_page.h
@@ -41,6 +41,9 @@
   // Prevents creating the actual interstitial view for testing.
   void DontCreateViewForTesting();
 
+  // InterstitialPageDelegate method:
+  std::string GetHTMLContents() override;
+
  protected:
   // Returns true if the interstitial should create a new navigation entry.
   virtual bool ShouldCreateNewNavigation() const = 0;
@@ -55,9 +58,6 @@
 
   virtual int GetHTMLTemplateId();
 
-  // InterstitialPageDelegate method:
-  std::string GetHTMLContents() override;
-
   // Returns the formatted host name for the request url.
   base::string16 GetFormattedHostName() const;
 
diff --git a/content/browser/frame_host/navigation_handle_impl.cc b/content/browser/frame_host/navigation_handle_impl.cc
index 26ed81c..1f63170 100644
--- a/content/browser/frame_host/navigation_handle_impl.cc
+++ b/content/browser/frame_host/navigation_handle_impl.cc
@@ -335,7 +335,7 @@
   return connection_info_;
 }
 
-base::Optional<net::SSLInfo> NavigationHandleImpl::GetSSLInfo() {
+const base::Optional<net::SSLInfo>& NavigationHandleImpl::GetSSLInfo() {
   return ssl_info_;
 }
 
@@ -445,6 +445,19 @@
 }
 
 NavigationThrottle::ThrottleCheckResult
+NavigationHandleImpl::CallWillFailRequestForTesting(
+    base::Optional<net::SSLInfo> ssl_info,
+    bool should_ssl_errors_be_fatal) {
+  NavigationThrottle::ThrottleCheckResult result = NavigationThrottle::DEFER;
+  WillFailRequest(ssl_info, should_ssl_errors_be_fatal,
+                  base::Bind(&UpdateThrottleCheckResult, &result));
+
+  // Reset the callback to ensure it will not be called later.
+  complete_callback_.Reset();
+  return result;
+}
+
+NavigationThrottle::ThrottleCheckResult
 NavigationHandleImpl::CallWillProcessResponseForTesting(
     content::RenderFrameHost* render_frame_host,
     const std::string& raw_response_headers) {
diff --git a/content/browser/frame_host/navigation_handle_impl.h b/content/browser/frame_host/navigation_handle_impl.h
index 9bc32a55..32e9cc6 100644
--- a/content/browser/frame_host/navigation_handle_impl.h
+++ b/content/browser/frame_host/navigation_handle_impl.h
@@ -147,6 +147,8 @@
   net::HostPortPair GetSocketAddress() override;
   const net::HttpResponseHeaders* GetResponseHeaders() override;
   net::HttpResponseInfo::ConnectionInfo GetConnectionInfo() override;
+  const base::Optional<net::SSLInfo>& GetSSLInfo() override;
+  bool ShouldSSLErrorsBeFatal() override;
   void RegisterThrottleForTesting(
       std::unique_ptr<NavigationThrottle> navigation_throttle) override;
   NavigationThrottle::ThrottleCheckResult CallWillStartRequestForTesting(
@@ -160,6 +162,9 @@
       bool new_method_is_post,
       const GURL& new_referrer_url,
       bool new_is_external_protocol) override;
+  NavigationThrottle::ThrottleCheckResult CallWillFailRequestForTesting(
+      base::Optional<net::SSLInfo> ssl_info,
+      bool should_ssl_errors_be_fatal) override;
   NavigationThrottle::ThrottleCheckResult CallWillProcessResponseForTesting(
       RenderFrameHost* render_frame_host,
       const std::string& raw_response_header) override;
@@ -474,15 +479,6 @@
   // nullptr;
   NavigationThrottle* GetDeferringThrottle() const;
 
-  // Returns the SSLInfo for a request that failed due to a certificate error.
-  // In the case of other request failures, returns base::nullopt.
-  // TODO(crrev.com/c/621236): Expose and use this method outside //content.
-  base::Optional<net::SSLInfo> GetSSLInfo();
-
-  // Returns the whether failure for a certificate error should be fatal.
-  // TODO(crrev.com/c/621236): Expose and use this method outside //content.
-  bool ShouldSSLErrorsBeFatal();
-
   // See NavigationHandle for a description of those member variables.
   GURL url_;
   scoped_refptr<SiteInstance> starting_site_instance_;
diff --git a/content/public/browser/navigation_handle.h b/content/public/browser/navigation_handle.h
index 6bd4a62..e47cf64 100644
--- a/content/public/browser/navigation_handle.h
+++ b/content/public/browser/navigation_handle.h
@@ -231,6 +231,13 @@
   // encountering a server redirect).
   virtual net::HttpResponseInfo::ConnectionInfo GetConnectionInfo() = 0;
 
+  // Returns the SSLInfo for a request that failed due to a certificate error.
+  // In the case of other request failures, returns base::nullopt.
+  virtual const base::Optional<net::SSLInfo>& GetSSLInfo() = 0;
+
+  // Whether the failure for a certificate error should be fatal.
+  virtual bool ShouldSSLErrorsBeFatal() = 0;
+
   // Returns the ID of the URLRequest associated with this navigation. Can only
   // be called from NavigationThrottle::WillProcessResponse and
   // WebContentsObserver::ReadyToCommitNavigation.
@@ -281,6 +288,11 @@
                                     const GURL& new_referrer_url,
                                     bool new_is_external_protocol) = 0;
 
+  // Simulates the network request failing.
+  virtual NavigationThrottle::ThrottleCheckResult CallWillFailRequestForTesting(
+      base::Optional<net::SSLInfo> ssl_info,
+      bool should_ssl_errors_be_fatal) = 0;
+
   // Simulates the reception of the network response.
   virtual NavigationThrottle::ThrottleCheckResult
   CallWillProcessResponseForTesting(
diff --git a/content/public/browser/navigation_throttle.h b/content/public/browser/navigation_throttle.h
index dd56a95..8186e0b8 100644
--- a/content/public/browser/navigation_throttle.h
+++ b/content/public/browser/navigation_throttle.h
@@ -148,9 +148,6 @@
   // method. Failing to do so will result in use-after-free bugs. Should the
   // implementer need to destroy the WebContents, it should return CANCEL,
   // CANCEL_AND_IGNORE or DEFER and perform the destruction asynchronously.
-  //
-  // TODO(crbug.com/752370): Use this method outside //content, in
-  // SSLErrorNavigationThrottle (crrev.com/c/621236).
   virtual ThrottleCheckResult WillFailRequest();
 
   // Called when a response's headers and metadata are available.