Move error page auto-reload to shared browser code

This CL moves Chrome's renderer-side error page auto-reload behavior
implementation to a new browser-side NetErrorAutoReloader helper within
the error_page component. Correpsonding tests are rewritten as browser
tests within components_browsertests.

Bug: 1098578
Change-Id: I42e7af0a3d23364d5065e474312f35620299ff54
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2302927
Reviewed-by: Matt Menke <[email protected]>
Reviewed-by: Avi Drissman <[email protected]>
Reviewed-by: Nasko Oskov <[email protected]>
Reviewed-by: Carlos IL <[email protected]>
Commit-Queue: Ken Rockot <[email protected]>
Cr-Commit-Position: refs/heads/master@{#791935}
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index 81935b1..9960de7 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -110,6 +110,7 @@
   "+components/embedder_support",
   "+components/encrypted_messages",
   "+components/enterprise",
+  "+components/error_page/content/browser",
   "+components/exo",
   "+components/external_intents",
   "+components/favicon_base",
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 2cfdc073..fc12a1f 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -207,6 +207,7 @@
 #include "components/dom_distiller/core/url_constants.h"
 #include "components/embedder_support/switches.h"
 #include "components/error_page/common/error_page_switches.h"
+#include "components/error_page/content/browser/net_error_auto_reloader.h"
 #include "components/feature_engagement/public/feature_constants.h"
 #include "components/feature_engagement/public/feature_list.h"
 #include "components/google/core/common/google_switches.h"
@@ -1119,6 +1120,18 @@
   }
 }
 
+bool IsErrorPageAutoReloadEnabled() {
+  const base::CommandLine& command_line =
+      *base::CommandLine::ForCurrentProcess();
+  if (command_line.HasSwitch(switches::kEnableAutomation))
+    return false;
+  if (command_line.HasSwitch(switches::kEnableAutoReload))
+    return true;
+  if (command_line.HasSwitch(switches::kDisableAutoReload))
+    return false;
+  return true;
+}
+
 }  // namespace
 
 // Generate a pseudo-random permutation of the following brand/version pairs:
@@ -2041,16 +2054,6 @@
 
 namespace {
 
-bool IsAutoReloadEnabled() {
-  const base::CommandLine& browser_command_line =
-      *base::CommandLine::ForCurrentProcess();
-  if (browser_command_line.HasSwitch(switches::kEnableAutoReload))
-    return true;
-  if (browser_command_line.HasSwitch(switches::kDisableAutoReload))
-    return false;
-  return true;
-}
-
 void MaybeAppendBlinkSettingsSwitchForFieldTrial(
     const base::CommandLine& browser_command_line,
     base::CommandLine* command_line) {
@@ -2302,9 +2305,6 @@
       }
     }
 
-    if (IsAutoReloadEnabled())
-      command_line->AppendSwitch(switches::kEnableAutoReload);
-
     MaybeAppendBlinkSettingsSwitchForFieldTrial(browser_command_line,
                                                 command_line);
 
@@ -4111,6 +4111,12 @@
               handle, std::make_unique<ChromeSecurityBlockingPageFactory>()),
       &throttles);
 
+  if (IsErrorPageAutoReloadEnabled() && handle->IsInMainFrame()) {
+    MaybeAddThrottle(
+        error_page::NetErrorAutoReloader::MaybeCreateThrottleFor(handle),
+        &throttles);
+  }
+
   return throttles;
 }
 
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 99c75f9..20e948f3 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -420,6 +420,7 @@
     "//components/embedder_support",
     "//components/encrypted_messages:encrypted_message_proto",
     "//components/enterprise",
+    "//components/error_page/content/browser",
     "//components/favicon/content",
     "//components/favicon/core",
     "//components/feature_engagement",
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index 16541dd..5db905d 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -1231,14 +1231,6 @@
     content::RenderFrame* render_frame,
     const GURL& url,
     int error_code) {
-  // Unit tests for ChromeContentRendererClient pass a NULL RenderFrame here.
-  // Unfortunately it's very difficult to construct a mock RenderView, so skip
-  // this functionality in this case.
-  if (render_frame && NetErrorHelper::Get(render_frame)
-                          ->ShouldSuppressErrorPage(url, error_code)) {
-    return true;
-  }
-
   // Do not flash an error page if the Instant new tab page fails to load.
   bool is_instant_ntp = false;
 #if !defined(OS_ANDROID)
diff --git a/chrome/renderer/net/net_error_helper.cc b/chrome/renderer/net/net_error_helper.cc
index 68af1dd2..747497b6 100644
--- a/chrome/renderer/net/net_error_helper.cc
+++ b/chrome/renderer/net/net_error_helper.cc
@@ -81,12 +81,6 @@
 
 namespace {
 
-NetErrorHelperCore::PageType GetLoadingPageType(const GURL& url) {
-  if (!url.is_valid() || url.spec() != kUnreachableWebDataURL)
-    return NetErrorHelperCore::NON_ERROR_PAGE;
-  return NetErrorHelperCore::ERROR_PAGE;
-}
-
 NetErrorHelperCore::FrameType GetFrameType(RenderFrame* render_frame) {
   if (render_frame->IsMainFrame())
     return NetErrorHelperCore::MAIN_FRAME;
@@ -123,24 +117,16 @@
 NetErrorHelper::NetErrorHelper(RenderFrame* render_frame)
     : RenderFrameObserver(render_frame),
       content::RenderFrameObserverTracker<NetErrorHelper>(render_frame) {
-  RenderThread::Get()->AddObserver(this);
-  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
-  bool auto_reload_enabled =
-      command_line->HasSwitch(switches::kEnableAutoReload);
   // TODO(mmenke): Consider only creating a NetErrorHelperCore for main frames.
   // subframes don't need any of the NetErrorHelperCore's extra logic.
-  core_.reset(new NetErrorHelperCore(this,
-                                     auto_reload_enabled,
-                                     !render_frame->IsHidden()));
+  core_ = std::make_unique<NetErrorHelperCore>(this);
 
   render_frame->GetAssociatedInterfaceRegistry()->AddInterface(
       base::BindRepeating(&NetErrorHelper::OnNetworkDiagnosticsClientRequest,
                           base::Unretained(this)));
 }
 
-NetErrorHelper::~NetErrorHelper() {
-  RenderThread::Get()->RemoveObserver(this);
-}
+NetErrorHelper::~NetErrorHelper() = default;
 
 void NetErrorHelper::ButtonPressed(NetErrorHelperCore::Button button) {
   core_->ExecuteButtonPress(button);
@@ -179,12 +165,6 @@
   return interface;
 }
 
-void NetErrorHelper::DidStartNavigation(
-    const GURL& url,
-    base::Optional<blink::WebNavigationType> navigation_type) {
-  core_->OnStartLoad(GetFrameType(render_frame()), GetLoadingPageType(url));
-}
-
 void NetErrorHelper::DidCommitProvisionalLoad(ui::PageTransition transition) {
   // Invalidate weak pointers from old error page controllers. If loading a new
   // error page, the controller has not yet been attached, so this won't affect
@@ -200,26 +180,10 @@
   core_->OnFinishLoad(GetFrameType(render_frame()));
 }
 
-void NetErrorHelper::OnStop() {
-  core_->OnStop();
-}
-
-void NetErrorHelper::WasShown() {
-  core_->OnWasShown();
-}
-
-void NetErrorHelper::WasHidden() {
-  core_->OnWasHidden();
-}
-
 void NetErrorHelper::OnDestruct() {
   delete this;
 }
 
-void NetErrorHelper::NetworkStateChanged(bool enabled) {
-  core_->NetworkStateChanged(enabled);
-}
-
 void NetErrorHelper::PrepareErrorPage(const error_page::Error& error,
                                       bool is_failed_post,
                                       std::string* error_html) {
@@ -227,11 +191,6 @@
                           error_html);
 }
 
-bool NetErrorHelper::ShouldSuppressErrorPage(const GURL& url, int error_code) {
-  return core_->ShouldSuppressErrorPage(GetFrameType(render_frame()), url,
-                                        error_code);
-}
-
 std::unique_ptr<network::ResourceRequest> NetErrorHelper::CreatePostRequest(
     const GURL& url) const {
   auto resource_request = std::make_unique<network::ResourceRequest>();
diff --git a/chrome/renderer/net/net_error_helper.h b/chrome/renderer/net/net_error_helper.h
index fbc2ce1..7fde1e1 100644
--- a/chrome/renderer/net/net_error_helper.h
+++ b/chrome/renderer/net/net_error_helper.h
@@ -23,7 +23,6 @@
 #include "components/security_interstitials/core/controller_client.h"
 #include "content/public/renderer/render_frame_observer.h"
 #include "content/public/renderer/render_frame_observer_tracker.h"
-#include "content/public/renderer/render_thread_observer.h"
 #include "mojo/public/cpp/bindings/associated_receiver_set.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
@@ -44,7 +43,6 @@
 class NetErrorHelper
     : public content::RenderFrameObserver,
       public content::RenderFrameObserverTracker<NetErrorHelper>,
-      public content::RenderThreadObserver,
       public NetErrorHelperCore::Delegate,
       public NetErrorPageController::Delegate,
       public security_interstitials::SecurityInterstitialPageController::
@@ -71,19 +69,10 @@
   GetInterface() override;
 
   // RenderFrameObserver implementation.
-  void DidStartNavigation(
-      const GURL& url,
-      base::Optional<blink::WebNavigationType> navigation_type) override;
   void DidCommitProvisionalLoad(ui::PageTransition transition) override;
   void DidFinishLoad() override;
-  void OnStop() override;
-  void WasShown() override;
-  void WasHidden() override;
   void OnDestruct() override;
 
-  // RenderThreadObserver implementation.
-  void NetworkStateChanged(bool online) override;
-
   // Sets values in |pending_error_page_info_|. If |error_html| is not null, it
   // initializes |error_html| with the HTML of an error page in response to
   // |error|.  Updates internals state with the assumption the page will be
@@ -92,10 +81,6 @@
                         bool is_failed_post,
                         std::string* error_html);
 
-  // Returns whether a load for |url| with |error_code| in the |frame| the
-  // NetErrorHelper is attached to should have its error page suppressed.
-  bool ShouldSuppressErrorPage(const GURL& url, int error_code);
-
  private:
   // Returns ResourceRequest filled with |url|. It has request_initiator from
   // the frame origin and origin header with "null" for a unique origin.
diff --git a/chrome/renderer/net/net_error_helper_core.cc b/chrome/renderer/net/net_error_helper_core.cc
index 488ae78..8e5edea0 100644
--- a/chrome/renderer/net/net_error_helper_core.cc
+++ b/chrome/renderer/net/net_error_helper_core.cc
@@ -22,14 +22,6 @@
 
 namespace {
 
-base::TimeDelta GetAutoReloadTime(size_t reload_count) {
-  static const int kDelaysMs[] = {0,      5000,   30000,  60000,
-                                  300000, 600000, 1800000};
-  if (reload_count >= base::size(kDelaysMs))
-    reload_count = base::size(kDelaysMs) - 1;
-  return base::TimeDelta::FromMilliseconds(kDelaysMs[reload_count]);
-}
-
 // Returns whether |error| is a DNS-related error (and therefore whether
 // the tab helper should start a DNS probe after receiving it).
 bool IsNetDnsError(const error_page::Error& error) {
@@ -41,11 +33,7 @@
 
 struct NetErrorHelperCore::ErrorPageInfo {
   ErrorPageInfo(error_page::Error error, bool was_failed_post)
-      : error(error),
-        was_failed_post(was_failed_post),
-        needs_dns_updates(false),
-        is_finished_loading(false),
-        auto_reload_triggered(false) {}
+      : error(error), was_failed_post(was_failed_post) {}
 
   // Information about the failed page load.
   error_page::Error error;
@@ -55,70 +43,21 @@
 
   // True if a page is a DNS error page and has not yet received a final DNS
   // probe status.
-  bool needs_dns_updates;
+  bool needs_dns_updates = false;
   bool dns_probe_complete = false;
 
   // True if a page has completed loading, at which point it can receive
   // updates.
-  bool is_finished_loading;
-
-  // True if the auto-reload timer has fired and a reload is or has been in
-  // flight.
-  bool auto_reload_triggered;
+  bool is_finished_loading = false;
 
   error_page::LocalizedError::PageState page_state;
 };
 
-bool NetErrorHelperCore::IsReloadableError(
-    const NetErrorHelperCore::ErrorPageInfo& info) {
-  GURL url = info.error.url();
-  return info.error.domain() == error_page::Error::kNetErrorDomain &&
-         info.error.reason() != net::ERR_ABORTED &&
-         // For now, net::ERR_UNKNOWN_URL_SCHEME is only being displayed on
-         // Chrome for Android.
-         info.error.reason() != net::ERR_UNKNOWN_URL_SCHEME &&
-         // Do not trigger if the server rejects a client certificate.
-         // https://crbug.com/431387
-         !net::IsClientCertificateError(info.error.reason()) &&
-         // Some servers reject client certificates with a generic
-         // handshake_failure alert.
-         // https://crbug.com/431387
-         info.error.reason() != net::ERR_SSL_PROTOCOL_ERROR &&
-         // Do not trigger for blacklisted URLs.
-         // https://crbug.com/803839
-         // Do not trigger for requests that were blocked by the browser itself.
-         !net::IsRequestBlockedError(info.error.reason()) &&
-         !info.was_failed_post &&
-         // Do not trigger for this error code because it is used by Chrome
-         // while an auth prompt is being displayed.
-         info.error.reason() != net::ERR_INVALID_AUTH_CREDENTIALS &&
-         // Don't auto-reload non-http/https schemas.
-         // https://crbug.com/471713
-         url.SchemeIsHTTPOrHTTPS() &&
-         // Don't auto reload if the error was a secure DNS network error, since
-         // the reload may interfere with the captive portal probe state.
-         // TODO(crbug.com/1016164): Explore how to allow reloads for secure DNS
-         // network errors without interfering with the captive portal probe
-         // state.
-         !info.error.resolve_error_info().is_secure_network_error;
-}
-
-NetErrorHelperCore::NetErrorHelperCore(Delegate* delegate,
-                                       bool auto_reload_enabled,
-                                       bool is_visible)
+NetErrorHelperCore::NetErrorHelperCore(Delegate* delegate)
     : delegate_(delegate),
       last_probe_status_(error_page::DNS_PROBE_POSSIBLE),
       can_show_network_diagnostics_dialog_(false),
-      auto_reload_enabled_(auto_reload_enabled),
-      auto_reload_timer_(new base::OneShotTimer()),
-      auto_reload_paused_(false),
-      auto_reload_in_flight_(false),
-      uncommitted_load_started_(false),
-      online_(content::RenderThread::Get()->IsOnline()),
-      visible_(is_visible),
-      auto_reload_count_(0),
-      navigation_from_button_(NO_BUTTON),
-      custom_error_page_(false)
+      navigation_from_button_(NO_BUTTON)
 #if defined(OS_ANDROID)
       ,
       page_auto_fetcher_helper_(
@@ -129,60 +68,10 @@
 
 NetErrorHelperCore::~NetErrorHelperCore() = default;
 
-void NetErrorHelperCore::CancelPendingAutoReload() {
-  auto_reload_timer_->Stop();
-  auto_reload_paused_ = false;
-}
-
-void NetErrorHelperCore::OnStop() {
-  CancelPendingAutoReload();
-  uncommitted_load_started_ = false;
-  auto_reload_count_ = 0;
-  auto_reload_in_flight_ = false;
-}
-
-void NetErrorHelperCore::OnWasShown() {
-  visible_ = true;
-  if (auto_reload_paused_)
-    MaybeStartAutoReloadTimer();
-}
-
-void NetErrorHelperCore::OnWasHidden() {
-  visible_ = false;
-  PauseAutoReloadTimer();
-}
-
-void NetErrorHelperCore::OnStartLoad(FrameType frame_type, PageType page_type) {
-  if (frame_type != MAIN_FRAME)
-    return;
-
-  uncommitted_load_started_ = true;
-
-  // If there's no pending error page information associated with the page load,
-  // or the new page is not an error page, then reset pending error page state.
-  if (!pending_error_page_info_ || page_type != ERROR_PAGE) {
-    CancelPendingAutoReload();
-  } else {
-    // Halt auto-reload if it's currently scheduled. OnFinishLoad will trigger
-    // auto-reload if appropriate.
-    PauseAutoReloadTimer();
-  }
-}
-
 void NetErrorHelperCore::OnCommitLoad(FrameType frame_type, const GURL& url) {
   if (frame_type != MAIN_FRAME)
     return;
 
-  // If a page is committing, either it's an error page and autoreload will be
-  // started again below, or it's a success page and we need to clear autoreload
-  // state.
-  auto_reload_in_flight_ = false;
-
-  // uncommitted_load_started_ could already be false, since RenderFrameImpl
-  // calls OnCommitLoad once for each in-page navigation (like a fragment
-  // change) with no corresponding OnStartLoad.
-  uncommitted_load_started_ = false;
-
 #if defined(OS_ANDROID)
   // Don't need this state. It will be refreshed if another error page is
   // loaded.
@@ -244,24 +133,17 @@
   if (frame_type != MAIN_FRAME)
     return;
 
-  if (!committed_error_page_info_) {
-    auto_reload_count_ = 0;
+  if (!committed_error_page_info_)
     return;
-  }
+
   committed_error_page_info_->is_finished_loading = true;
 
   RecordEvent(error_page::NETWORK_ERROR_PAGE_SHOWN);
 
   delegate_->SetIsShowingDownloadButton(
       committed_error_page_info_->page_state.download_button_shown);
-
   delegate_->EnablePageHelperFunctions();
 
-  if (auto_reload_enabled_ && IsReloadableError(*committed_error_page_info_) &&
-      !custom_error_page_) {
-    MaybeStartAutoReloadTimer();
-  }
-
   DVLOG(1) << "Error page finished loading; sending saved status.";
   if (committed_error_page_info_->needs_dns_updates) {
     if (last_probe_status_ != error_page::DNS_PROBE_POSSIBLE)
@@ -278,16 +160,11 @@
   if (frame_type == MAIN_FRAME) {
     pending_error_page_info_.reset(new ErrorPageInfo(error, is_failed_post));
     PrepareErrorPageForMainFrame(pending_error_page_info_.get(), error_html);
-  } else {
-    if (error_html) {
-      custom_error_page_ = false;
-      delegate_->GenerateLocalizedErrorPage(
-          error, is_failed_post,
-          false /* No diagnostics dialogs allowed for subframes. */, nullptr,
-          error_html);
-    } else {
-      custom_error_page_ = true;
-    }
+  } else if (error_html) {
+    delegate_->GenerateLocalizedErrorPage(
+        error, is_failed_post,
+        false /* No diagnostics dialogs allowed for subframes. */, nullptr,
+        error_html);
   }
 }
 
@@ -336,12 +213,9 @@
     error = GetUpdatedError(*pending_error_page_info);
   }
   if (error_html) {
-    custom_error_page_ = false;
     pending_error_page_info->page_state = delegate_->GenerateLocalizedErrorPage(
         error, pending_error_page_info->was_failed_post,
         can_show_network_diagnostics_dialog_, nullptr, error_html);
-  } else {
-    custom_error_page_ = true;
   }
 }
 
@@ -391,110 +265,6 @@
   delegate_->ReloadFrame();
 }
 
-bool NetErrorHelperCore::MaybeStartAutoReloadTimer() {
-  // Automation tools expect to be in control of reloads.
-  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
-          switches::kEnableAutomation)) {
-    return false;
-  }
-
-  if (!committed_error_page_info_ ||
-      !committed_error_page_info_->is_finished_loading ||
-      pending_error_page_info_ || uncommitted_load_started_) {
-    return false;
-  }
-
-  StartAutoReloadTimer();
-  return true;
-}
-
-void NetErrorHelperCore::StartAutoReloadTimer() {
-  DCHECK(committed_error_page_info_);
-  DCHECK(IsReloadableError(*committed_error_page_info_));
-
-  committed_error_page_info_->auto_reload_triggered = true;
-
-  if (!online_ || !visible_) {
-    auto_reload_paused_ = true;
-    return;
-  }
-
-  auto_reload_paused_ = false;
-  base::TimeDelta delay = GetAutoReloadTime(auto_reload_count_);
-  auto_reload_timer_->Stop();
-  auto_reload_timer_->Start(
-      FROM_HERE, delay,
-      base::BindOnce(&NetErrorHelperCore::AutoReloadTimerFired,
-                     base::Unretained(this)));
-}
-
-void NetErrorHelperCore::AutoReloadTimerFired() {
-  // AutoReloadTimerFired only runs if:
-  // 1. StartAutoReloadTimer was previously called, which requires that
-  //    committed_error_page_info_ is populated;
-  // 2. No other page load has started since (1), since OnStartLoad stops the
-  //    auto-reload timer.
-  DCHECK(committed_error_page_info_);
-
-  auto_reload_count_++;
-  auto_reload_in_flight_ = true;
-  Reload();
-}
-
-void NetErrorHelperCore::PauseAutoReloadTimer() {
-  if (!auto_reload_timer_->IsRunning())
-    return;
-  DCHECK(committed_error_page_info_);
-  DCHECK(!auto_reload_paused_);
-  DCHECK(committed_error_page_info_->auto_reload_triggered);
-  auto_reload_timer_->Stop();
-  auto_reload_paused_ = true;
-}
-
-void NetErrorHelperCore::NetworkStateChanged(bool online) {
-  bool was_online = online_;
-  online_ = online;
-  if (!was_online && online) {
-    // Transitioning offline -> online
-    if (auto_reload_paused_)
-      MaybeStartAutoReloadTimer();
-  } else if (was_online && !online) {
-    // Transitioning online -> offline
-    if (auto_reload_timer_->IsRunning())
-      auto_reload_count_ = 0;
-    PauseAutoReloadTimer();
-  }
-}
-
-bool NetErrorHelperCore::ShouldSuppressErrorPage(FrameType frame_type,
-                                                 const GURL& url,
-                                                 int error_code) {
-  // Don't suppress child frame errors.
-  if (frame_type != MAIN_FRAME)
-    return false;
-
-  // If there's no auto reload attempt in flight, this error page didn't come
-  // from auto reload, so don't suppress it.
-  if (!auto_reload_in_flight_)
-    return false;
-
-  // Even with auto_reload_in_flight_ error page may not come from
-  // the auto reload when proceeding from error CERT_AUTHORITY_INVALID
-  // to error INVALID_AUTH_CREDENTIALS, so do not suppress the error page
-  // for the new error code.
-  if (committed_error_page_info_ &&
-      committed_error_page_info_->error.reason() != error_code)
-    return false;
-
-  uncommitted_load_started_ = false;
-  // This serves to terminate the auto-reload in flight attempt. If
-  // ShouldSuppressErrorPage is called, the auto-reload yielded an error, which
-  // means the request was already sent.
-  auto_reload_in_flight_ = false;
-  MaybeStartAutoReloadTimer();
-  return true;
-}
-
 #if defined(OS_ANDROID)
 void NetErrorHelperCore::SetPageAutoFetcherHelperForTesting(
     std::unique_ptr<PageAutoFetcherHelper> page_auto_fetcher_helper) {
diff --git a/chrome/renderer/net/net_error_helper_core.h b/chrome/renderer/net/net_error_helper_core.h
index e9f1d7e..b2befb0 100644
--- a/chrome/renderer/net/net_error_helper_core.h
+++ b/chrome/renderer/net/net_error_helper_core.h
@@ -121,9 +121,7 @@
     virtual ~Delegate() {}
   };
 
-  NetErrorHelperCore(Delegate* delegate,
-                     bool auto_reload_enabled,
-                     bool is_visible);
+  explicit NetErrorHelperCore(Delegate* delegate);
   ~NetErrorHelperCore();
 
   // Sets values in |pending_error_page_info_|. If |error_html| is not null, it
@@ -136,12 +134,8 @@
                         std::string* error_html);
 
   // These methods handle tracking the actual state of the page.
-  void OnStartLoad(FrameType frame_type, PageType page_type);
   void OnCommitLoad(FrameType frame_type, const GURL& url);
   void OnFinishLoad(FrameType frame_type);
-  void OnStop();
-  void OnWasShown();
-  void OnWasHidden();
 
   void CancelPendingAutoReload();
 
@@ -158,29 +152,6 @@
   // synced preferences.
   void OnEasterEggHighScoreReceived(int high_score);
 
-  // Notifies |this| that the network's online status changed.
-  // Handler for NetworkStateChanged notification from the browser process. If
-  // the network state changes to online, this method is responsible for
-  // starting the auto-reload process.
-  //
-  // Warning: if there are many tabs sitting at an error page, this handler will
-  // be run at the same time for each of their top-level renderframes, which can
-  // cause many requests to be started at the same time. There's no current
-  // protection against this kind of "reload storm".
-  //
-  // TODO(rdsmith): prevent the reload storm.
-  void NetworkStateChanged(bool online);
-
-  int auto_reload_count() const { return auto_reload_count_; }
-
-  bool ShouldSuppressErrorPage(FrameType frame_type,
-                               const GURL& url,
-                               int error_code);
-
-  void set_timer_for_testing(std::unique_ptr<base::OneShotTimer> timer) {
-    auto_reload_timer_ = std::move(timer);
-  }
-
 #if defined(OS_ANDROID)
   void SetPageAutoFetcherHelperForTesting(
       std::unique_ptr<PageAutoFetcherHelper> page_auto_fetcher_helper);
@@ -227,14 +198,8 @@
   error_page::Error GetUpdatedError(const ErrorPageInfo& error_info) const;
 
   void Reload();
-  bool MaybeStartAutoReloadTimer();
-  void StartAutoReloadTimer();
-  void AutoReloadTimerFired();
-  void PauseAutoReloadTimer();
 
-  static bool IsReloadableError(const ErrorPageInfo& info);
-
-  Delegate* delegate_;
+  Delegate* const delegate_;
 
   // The last DnsProbeStatus received from the browser.
   error_page::DnsProbeStatus last_probe_status_;
@@ -249,45 +214,11 @@
 
   bool can_show_network_diagnostics_dialog_;
 
-  // True if auto-reload is enabled at all.
-  const bool auto_reload_enabled_;
-
-  // Timer used to wait for auto-reload attempts.
-  std::unique_ptr<base::OneShotTimer> auto_reload_timer_;
-
-  // True if the auto-reload timer would be running but is waiting for an
-  // offline->online network transition.
-  bool auto_reload_paused_;
-
-  // Whether an auto-reload-initiated Reload() attempt is in flight.
-  bool auto_reload_in_flight_;
-
-  // True if there is an uncommitted-but-started load, error page or not. This
-  // is used to inhibit starting auto-reload when an error page finishes, in
-  // case this happens:
-  //   Error page starts
-  //   Error page commits
-  //   Non-error page starts
-  //   Error page finishes
-  bool uncommitted_load_started_;
-
-  // Is the browser online?
-  bool online_;
-
-  // Is the RenderFrame this object is observing visible?
-  bool visible_;
-
-  int auto_reload_count_;
-
   // This value is set only when a navigation has been initiated from
   // the error page.  It is used to detect when such navigations result
   // in errors.
   Button navigation_from_button_;
 
-  // True if the current error page is displaying custom HTML (e.g.
-  // security interstitials).
-  bool custom_error_page_;
-
 #if defined(OS_ANDROID)
   AvailableOfflineContentHelper available_content_helper_;
   std::unique_ptr<PageAutoFetcherHelper> page_auto_fetcher_helper_;
diff --git a/chrome/renderer/net/net_error_helper_core_unittest.cc b/chrome/renderer/net/net_error_helper_core_unittest.cc
index 359a67a..e9dbc64 100644
--- a/chrome/renderer/net/net_error_helper_core_unittest.cc
+++ b/chrome/renderer/net/net_error_helper_core_unittest.cc
@@ -21,8 +21,6 @@
 #include "base/test/bind_test_util.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
-#include "base/timer/mock_timer.h"
-#include "base/timer/timer.h"
 #include "build/build_config.h"
 #include "chrome/common/available_offline_content.mojom.h"
 #include "chrome/renderer/net/available_offline_content_helper.h"
@@ -90,30 +88,18 @@
                                public NetErrorHelperCore::Delegate {
  public:
   NetErrorHelperCoreTest()
-      : timer_(nullptr),
-        update_count_(0),
+      : update_count_(0),
         reload_count_(0),
         diagnose_error_count_(0),
         download_count_(0),
         list_visible_by_prefs_(true),
         enable_page_helper_functions_count_(0),
         default_url_(GURL(kFailedUrl)),
-        error_url_(GURL(content::kUnreachableWebDataURL)) {
-    SetUpCore(false, true);
-  }
+        error_url_(GURL(content::kUnreachableWebDataURL)) {}
 
   ~NetErrorHelperCoreTest() override = default;
 
-  void SetUpCore(bool auto_reload_enabled,
-                 bool visible) {
-    // The old value of timer_, if any, will be freed by the old core_ being
-    // destructed, since core_ takes ownership of the timer.
-    timer_ = new base::MockOneShotTimer();
-    core_.reset(new NetErrorHelperCore(this, auto_reload_enabled, visible));
-    core_->set_timer_for_testing(base::WrapUnique(timer_));
-  }
-
-  NetErrorHelperCore* core() { return core_.get(); }
+  NetErrorHelperCore* core() { return &core_; }
 
   int reload_count() const { return reload_count_; }
 
@@ -170,14 +156,10 @@
   }
 #endif
 
-  base::MockOneShotTimer* timer() { return timer_; }
-
   base::test::TaskEnvironment* task_environment() { return &task_environment_; }
   content::MockRenderThread* render_thread() { return &render_thread_; }
 
   void DoErrorLoadOfURL(net::Error error, const GURL& url) {
-    core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                        NetErrorHelperCore::NON_ERROR_PAGE);
     std::string html;
     core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
                              NetErrorForURL(error, url),
@@ -185,8 +167,6 @@
     EXPECT_FALSE(html.empty());
     EXPECT_EQ(NetErrorStringForURL(error, url), html);
 
-    core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                        NetErrorHelperCore::ERROR_PAGE);
     core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
     core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
   }
@@ -195,23 +175,7 @@
     DoErrorLoadOfURL(error, GURL(kFailedUrl));
   }
 
-  void DoErrorLoadWithCustomErrorPage(net::Error error) {
-    core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                        NetErrorHelperCore::NON_ERROR_PAGE);
-    // Custom error pages pass nullptr to PrepareErrorPage, since they set the
-    // error HTML on their own.
-    core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                             NetErrorForURL(error, GURL(kFailedUrl)),
-                             false /* is_failed_post */, nullptr);
-    core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                        NetErrorHelperCore::ERROR_PAGE);
-    core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
-    core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
-  }
-
   void DoSuccessLoad() {
-    core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                        NetErrorHelperCore::NON_ERROR_PAGE);
     core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, default_url());
     core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
   }
@@ -239,6 +203,8 @@
       std::string* html) const override {
     last_can_show_network_diagnostics_dialog_ =
         can_show_network_diagnostics_dialog;
+
+    CHECK_NE(html, nullptr);
     *html = ErrorToString(error, is_failed_post);
 
     return GetPageState();
@@ -290,12 +256,10 @@
 
   content::RenderFrame* GetRenderFrame() override { return nullptr; }
 
-  base::MockOneShotTimer* timer_;
-
   base::test::TaskEnvironment task_environment_;
   content::MockRenderThread render_thread_;
 
-  std::unique_ptr<NetErrorHelperCore> core_;
+  NetErrorHelperCore core_{this};
 
   // Contains the information passed to the last call to UpdateErrorPage, as a
   // string.
@@ -338,19 +302,13 @@
 TEST_F(NetErrorHelperCoreTest, Null) {}
 
 TEST_F(NetErrorHelperCoreTest, SuccessfulPageLoad) {
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
   core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, default_url());
   core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
   EXPECT_EQ(0, update_count());
 }
 
 TEST_F(NetErrorHelperCoreTest, MainFrameNonDnsError) {
-  // Original page starts loading.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-
-  // It fails, and an error page is requested.
+  // An error page is requested.
   std::string html;
   core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
                            NetError(net::ERR_CONNECTION_RESET),
@@ -361,8 +319,6 @@
 
   // Error page loads.
   EXPECT_EQ(0, enable_page_helper_functions_count());
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
   core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
   core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
   EXPECT_EQ(0, update_count());
@@ -372,13 +328,9 @@
 // Much like above tests, but with a bunch of spurious DNS status messages that
 // should have no effect.
 TEST_F(NetErrorHelperCoreTest, MainFrameNonDnsErrorSpuriousStatus) {
-  // Original page starts loading.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-  core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
-
-  // It fails, and an error page is requested.
+  // Loading fails, and an error page is requested.
   std::string html;
+  core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
   core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
                            NetError(net::ERR_CONNECTION_RESET),
                            false /* is_failed_post */, &html);
@@ -390,8 +342,6 @@
 
   // Error page loads.
 
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
   core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
 
   core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
@@ -403,12 +353,19 @@
   EXPECT_EQ(0, update_count());
 }
 
-TEST_F(NetErrorHelperCoreTest, SubFrameDnsError) {
-  // Original page starts loading.
-  core()->OnStartLoad(NetErrorHelperCore::SUB_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
+TEST_F(NetErrorHelperCoreTest, SubFrameErrorWithCustomErrorPage) {
+  // Loading fails, and an error page is requested. |error_html| is null
+  // indicating a custom error page. Calls below should not crash.
+  core()->PrepareErrorPage(
+      NetErrorHelperCore::SUB_FRAME, NetError(net::ERR_NAME_NOT_RESOLVED),
+      false /* is_failed_post */, nullptr /* error_html */);
+  core()->OnCommitLoad(NetErrorHelperCore::SUB_FRAME, error_url());
+  core()->OnFinishLoad(NetErrorHelperCore::SUB_FRAME);
+  EXPECT_EQ(0, update_count());
+}
 
-  // It fails, and an error page is requested.
+TEST_F(NetErrorHelperCoreTest, SubFrameDnsError) {
+  // Loading fails, and an error page is requested.
   std::string html;
   core()->PrepareErrorPage(NetErrorHelperCore::SUB_FRAME,
                            NetError(net::ERR_NAME_NOT_RESOLVED),
@@ -417,8 +374,6 @@
   EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), html);
 
   // Error page loads.
-  core()->OnStartLoad(NetErrorHelperCore::SUB_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
   core()->OnCommitLoad(NetErrorHelperCore::SUB_FRAME, error_url());
   core()->OnFinishLoad(NetErrorHelperCore::SUB_FRAME);
   EXPECT_EQ(0, update_count());
@@ -427,13 +382,9 @@
 // Much like above tests, but with a bunch of spurious DNS status messages that
 // should have no effect.
 TEST_F(NetErrorHelperCoreTest, SubFrameDnsErrorSpuriousStatus) {
-  // Original page starts loading.
-  core()->OnStartLoad(NetErrorHelperCore::SUB_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-  core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
-
-  // It fails, and an error page is requested.
+  // Loading fails, and an error page is requested.
   std::string html;
+  core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
   core()->PrepareErrorPage(NetErrorHelperCore::SUB_FRAME,
                            NetError(net::ERR_NAME_NOT_RESOLVED),
                            false /* is_failed_post */, &html);
@@ -444,8 +395,6 @@
 
   // Error page loads.
 
-  core()->OnStartLoad(NetErrorHelperCore::SUB_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
   core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
 
   core()->OnCommitLoad(NetErrorHelperCore::SUB_FRAME, error_url());
@@ -464,11 +413,7 @@
 // Test case where the error page finishes loading before receiving any DNS
 // probe messages.
 TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbe) {
-  // Original page starts loading.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-
-  // It fails, and an error page is requested.
+  // Loading fails, and an error page is requested.
   std::string html;
   core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
                            NetError(net::ERR_NAME_NOT_RESOLVED),
@@ -477,8 +422,6 @@
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
   // Error page loads.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
   core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
   core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
   EXPECT_EQ(0, update_count());
@@ -500,11 +443,7 @@
 
 // Same as above, but the probe is not run.
 TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbeNotRun) {
-  // Original page starts loading.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-
-  // It fails, and an error page is requested.
+  // Loading fails, and an error page is requested.
   std::string html;
   core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
                            NetError(net::ERR_NAME_NOT_RESOLVED),
@@ -513,8 +452,6 @@
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
   // Error page loads.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
   core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
   core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
   EXPECT_EQ(0, update_count());
@@ -533,11 +470,7 @@
 
 // Same as above, but the probe result is inconclusive.
 TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbeInconclusive) {
-  // Original page starts loading.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-
-  // It fails, and an error page is requested.
+  // Loading fails, and an error page is requested.
   std::string html;
   core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
                            NetError(net::ERR_NAME_NOT_RESOLVED),
@@ -546,8 +479,6 @@
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
   // Error page loads.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
   core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
   core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
   EXPECT_EQ(0, update_count());
@@ -571,11 +502,7 @@
 TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbeNoInternet) {
   base::HistogramTester histogram_tester_;
 
-  // Original page starts loading.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-
-  // It fails, and an error page is requested.
+  // Loading fails, and an error page is requested.
   std::string html;
   core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
                            NetError(net::ERR_NAME_NOT_RESOLVED),
@@ -584,8 +511,6 @@
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
   // Error page loads.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
   core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
   core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
   EXPECT_EQ(0, update_count());
@@ -611,14 +536,10 @@
 
   // Perform a second error page load, and confirm that the previous load
   // doesn't affect the result.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
   core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
                            NetError(net::ERR_NAME_NOT_RESOLVED),
                            false /* is_failed_post */, &html);
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
   core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
   core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
   core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
@@ -634,11 +555,7 @@
 
 // Same as above, but the probe result is bad config.
 TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbeBadConfig) {
-  // Original page starts loading.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-
-  // It fails, and an error page is requested.
+  // Loading fails, and an error page is requested.
   std::string html;
   core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
                            NetError(net::ERR_NAME_NOT_RESOLVED),
@@ -647,8 +564,6 @@
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
   // Error page loads.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
   core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
   core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
   EXPECT_EQ(0, update_count());
@@ -672,11 +587,7 @@
 // Test case where the error page finishes loading after receiving the start
 // DNS probe message.
 TEST_F(NetErrorHelperCoreTest, FinishedAfterStartProbe) {
-  // Original page starts loading.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-
-  // It fails, and an error page is requested.
+  // Loading fails, and an error page is requested.
   std::string html;
   core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
                            NetError(net::ERR_NAME_NOT_RESOLVED),
@@ -685,8 +596,6 @@
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
   // Error page loads.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
   core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
 
   // Nothing should be done when a probe status comes in before loading
@@ -714,11 +623,7 @@
 // Test case where the error page finishes loading before receiving any DNS
 // probe messages and the request is a POST.
 TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbePost) {
-  // Original page starts loading.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-
-  // It fails, and an error page is requested.
+  // Loading fails, and an error page is requested.
   std::string html;
   core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
                            NetError(net::ERR_NAME_NOT_RESOLVED),
@@ -729,8 +634,6 @@
 
   // Error page loads.
   EXPECT_EQ(0, enable_page_helper_functions_count());
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
   core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
   core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
   EXPECT_EQ(0, update_count());
@@ -750,11 +653,7 @@
 
 // Test case where the probe finishes before the page is committed.
 TEST_F(NetErrorHelperCoreTest, ProbeFinishesEarly) {
-  // Original page starts loading.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-
-  // It fails, and an error page is requested.
+  // Loading fails, and an error page is requested.
   std::string html;
   core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
                            NetError(net::ERR_NAME_NOT_RESOLVED),
@@ -762,12 +661,8 @@
   // Should have returned a local error page indicating a probe may run.
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
-  // Error page starts loading.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
-
-  // Nothing should be done when the probe statuses come in before loading
-  // finishes.
+  // Error page starts loading. Nothing should be done when the probe statuses
+  // come in before loading finishes.
   core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
   core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
   EXPECT_EQ(0, update_count());
@@ -791,11 +686,7 @@
 // Test case where one error page loads completely before a new navigation
 // results in another error page.  Probes are run for both pages.
 TEST_F(NetErrorHelperCoreTest, TwoErrorsWithProbes) {
-  // Original page starts loading.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-
-  // It fails, and an error page is requested.
+  // Loading fails, and an error page is requested.
   std::string html;
   core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
                            NetError(net::ERR_NAME_NOT_RESOLVED),
@@ -804,8 +695,6 @@
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
   // Error page loads.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
   core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
   core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
 
@@ -818,11 +707,7 @@
 
   // The process starts again.
 
-  // Normal page starts loading.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-
-  // It fails, and an error page is requested.
+  // Loading fails, and an error page is requested.
   core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
                            NetError(net::ERR_NAME_NOT_RESOLVED),
                            false /* is_failed_post */, &html);
@@ -830,8 +715,6 @@
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
   // Error page loads.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
   core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
   core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
   EXPECT_EQ(2, update_count());
@@ -851,11 +734,7 @@
 // results in another error page.  Probe results for the first probe are only
 // received after the second load starts, but before it commits.
 TEST_F(NetErrorHelperCoreTest, TwoErrorsWithProbesAfterSecondStarts) {
-  // Original page starts loading.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-
-  // It fails, and an error page is requested.
+  // Loading fails, and an error page is requested.
   std::string html;
   core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
                            NetError(net::ERR_NAME_NOT_RESOLVED),
@@ -864,29 +743,20 @@
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
   // Error page loads.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
   core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
   core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
 
   // The process starts again.
 
-  // Normal page starts loading.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-
-  // It fails, and an error page is requested.
+  // Loading fails, and an error page is requested.
   core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
                            NetError(net::ERR_NAME_NOT_RESOLVED),
                            false /* is_failed_post */, &html);
   // Should have returned a local error page indicating a probe may run.
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
-  // Error page starts to load.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
-
-  // Probe results come in, and the first page is updated.
+  // Error page starts to load. Probe results come in, and the first page is
+  // updated.
   core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
   core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
   EXPECT_EQ(2, update_count());
@@ -907,11 +777,7 @@
 
 // Same as above, but a new page is loaded before the error page commits.
 TEST_F(NetErrorHelperCoreTest, ErrorPageLoadInterrupted) {
-  // Original page starts loading.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-
-  // It fails, and an error page is requested.
+  // Loading fails, and an error page is requested.
   std::string html;
   core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
                            NetError(net::ERR_NAME_NOT_RESOLVED),
@@ -919,19 +785,12 @@
   // Should have returned a local error page indicating a probe may run.
   EXPECT_EQ(ProbeErrorString(error_page::DNS_PROBE_POSSIBLE), html);
 
-  // Error page starts loading.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
-  // Probe statuses come in, but should be ignored.
+  // Error page starts loading. Probe statuses come in, but should be ignored.
   core()->OnNetErrorInfo(error_page::DNS_PROBE_STARTED);
   core()->OnNetErrorInfo(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
   EXPECT_EQ(0, update_count());
 
-  // A new navigation begins while the error page is loading.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-
-  // And fails.
+  // A new navigation fails while the error page is loading.
   core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
                            NetError(net::ERR_NAME_NOT_RESOLVED),
                            false /* is_failed_post */, &html);
@@ -953,404 +812,6 @@
             last_error_html());
 }
 
-//------------------------------------------------------------------------------
-// Autoreload tests.
-//------------------------------------------------------------------------------
-
-TEST_F(NetErrorHelperCoreTest, AutoReloadDisabled) {
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-
-  EXPECT_FALSE(timer()->IsRunning());
-  EXPECT_EQ(0, reload_count());
-}
-
-class NetErrorHelperCoreAutoReloadTest : public NetErrorHelperCoreTest {
- public:
-  void SetUp() override {
-    NetErrorHelperCoreTest::SetUp();
-    SetUpCore(true, true);
-  }
-};
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, Succeeds) {
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-
-  EXPECT_TRUE(timer()->IsRunning());
-  EXPECT_EQ(0, reload_count());
-
-  timer()->Fire();
-  EXPECT_FALSE(timer()->IsRunning());
-  EXPECT_EQ(1, reload_count());
-
-  DoSuccessLoad();
-
-  EXPECT_FALSE(timer()->IsRunning());
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, Retries) {
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-
-  EXPECT_TRUE(timer()->IsRunning());
-  base::TimeDelta first_delay = timer()->GetCurrentDelay();
-  EXPECT_EQ(0, reload_count());
-
-  timer()->Fire();
-  EXPECT_FALSE(timer()->IsRunning());
-  EXPECT_EQ(1, reload_count());
-
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-
-  EXPECT_TRUE(timer()->IsRunning());
-  EXPECT_GT(timer()->GetCurrentDelay(), first_delay);
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, StopsTimerOnStop) {
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-  EXPECT_TRUE(timer()->IsRunning());
-  core()->OnStop();
-  EXPECT_FALSE(timer()->IsRunning());
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, StopsLoadingOnStop) {
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-  EXPECT_EQ(0, core()->auto_reload_count());
-  timer()->Fire();
-  EXPECT_EQ(1, core()->auto_reload_count());
-  EXPECT_EQ(1, reload_count());
-  core()->OnStop();
-  EXPECT_FALSE(timer()->IsRunning());
-  EXPECT_EQ(0, core()->auto_reload_count());
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, StopsOnOtherLoadStart) {
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-  EXPECT_TRUE(timer()->IsRunning());
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-  EXPECT_FALSE(timer()->IsRunning());
-  EXPECT_EQ(0, core()->auto_reload_count());
-}
-
-// This is a regression test for http://crbug.com/881208.
-TEST_F(NetErrorHelperCoreAutoReloadTest, StopsOnErrorLoadCommit) {
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-  EXPECT_TRUE(timer()->IsRunning());
-
-  // Simulate manually reloading the error page while the timer is still
-  // running.
-  std::string html;
-  core()->PrepareErrorPage(
-      NetErrorHelperCore::MAIN_FRAME,
-      NetErrorForURL(net::ERR_CONNECTION_RESET, GURL(kFailedUrl)),
-      false /* is_failed_post */, &html);
-
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
-  EXPECT_FALSE(timer()->IsRunning());
-  core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
-  core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
-  EXPECT_TRUE(timer()->IsRunning());
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, ResetsCountOnSuccess) {
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-  base::TimeDelta delay = timer()->GetCurrentDelay();
-  EXPECT_EQ(0, core()->auto_reload_count());
-  timer()->Fire();
-  EXPECT_EQ(1, core()->auto_reload_count());
-  EXPECT_EQ(1, reload_count());
-  DoSuccessLoad();
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-  EXPECT_EQ(0, core()->auto_reload_count());
-  EXPECT_EQ(timer()->GetCurrentDelay(), delay);
-  timer()->Fire();
-  EXPECT_EQ(1, core()->auto_reload_count());
-  EXPECT_EQ(2, reload_count());
-  DoSuccessLoad();
-  EXPECT_EQ(0, core()->auto_reload_count());
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, RestartsOnOnline) {
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-  base::TimeDelta delay = timer()->GetCurrentDelay();
-  timer()->Fire();
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-  EXPECT_TRUE(timer()->IsRunning());
-  EXPECT_NE(delay, timer()->GetCurrentDelay());
-  core()->NetworkStateChanged(false);
-  EXPECT_FALSE(timer()->IsRunning());
-  core()->NetworkStateChanged(true);
-  EXPECT_TRUE(timer()->IsRunning());
-  EXPECT_EQ(delay, timer()->GetCurrentDelay());
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, DoesNotStartOnOnline) {
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-  timer()->Fire();
-  DoSuccessLoad();
-  EXPECT_FALSE(timer()->IsRunning());
-  core()->NetworkStateChanged(true);
-  EXPECT_FALSE(timer()->IsRunning());
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, DoesNotStartOffline) {
-  core()->NetworkStateChanged(false);
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-  EXPECT_FALSE(timer()->IsRunning());
-  core()->NetworkStateChanged(true);
-  EXPECT_TRUE(timer()->IsRunning());
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, DoesNotRestartOnOnlineAfterStop) {
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-  timer()->Fire();
-  core()->OnStop();
-  core()->NetworkStateChanged(true);
-  EXPECT_FALSE(timer()->IsRunning());
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, WithDnsProbes) {
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-  DoDnsProbe(error_page::DNS_PROBE_FINISHED_NXDOMAIN);
-  timer()->Fire();
-  EXPECT_EQ(1, reload_count());
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, ExponentialBackoffLevelsOff) {
-  base::TimeDelta previous = base::TimeDelta::FromMilliseconds(0);
-  const int kMaxTries = 50;
-  int tries = 0;
-  for (tries = 0; tries < kMaxTries; tries++) {
-    DoErrorLoad(net::ERR_CONNECTION_RESET);
-    EXPECT_TRUE(timer()->IsRunning());
-    if (previous == timer()->GetCurrentDelay())
-      break;
-    previous = timer()->GetCurrentDelay();
-    timer()->Fire();
-  }
-
-  EXPECT_LT(tries, kMaxTries);
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, SlowError) {
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-  std::string html;
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_CONNECTION_RESET),
-                           false /* is_failed_post */, &html);
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
-  core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
-  EXPECT_FALSE(timer()->IsRunning());
-  // Start a new non-error page load.
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-  EXPECT_FALSE(timer()->IsRunning());
-  // Finish the error page load.
-  core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
-  EXPECT_FALSE(timer()->IsRunning());
-  core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
-  core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
-  EXPECT_FALSE(timer()->IsRunning());
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, OnlineSlowError) {
-  core()->NetworkStateChanged(false);
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-  std::string html;
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_CONNECTION_RESET),
-                           false /* is_failed_post */, &html);
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
-  core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
-  EXPECT_FALSE(timer()->IsRunning());
-  core()->NetworkStateChanged(true);
-  EXPECT_FALSE(timer()->IsRunning());
-  core()->NetworkStateChanged(false);
-  core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
-  EXPECT_FALSE(timer()->IsRunning());
-  core()->NetworkStateChanged(true);
-  EXPECT_TRUE(timer()->IsRunning());
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, OnlinePendingError) {
-  core()->NetworkStateChanged(false);
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-  std::string html;
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_CONNECTION_RESET),
-                           false /* is_failed_post */, &html);
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
-  EXPECT_FALSE(timer()->IsRunning());
-  core()->NetworkStateChanged(true);
-  EXPECT_FALSE(timer()->IsRunning());
-  core()->NetworkStateChanged(false);
-  core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
-  core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
-  EXPECT_FALSE(timer()->IsRunning());
-  core()->NetworkStateChanged(true);
-  EXPECT_TRUE(timer()->IsRunning());
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, OnlinePartialErrorReplacement) {
-  core()->NetworkStateChanged(false);
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-  std::string html;
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_CONNECTION_RESET),
-                           false /* is_failed_post */, &html);
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
-  core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url());
-  core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME);
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::NON_ERROR_PAGE);
-  core()->PrepareErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                           NetError(net::ERR_CONNECTION_RESET),
-                           false /* is_failed_post */, &html);
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
-  EXPECT_FALSE(timer()->IsRunning());
-  core()->NetworkStateChanged(true);
-  EXPECT_FALSE(timer()->IsRunning());
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, ShouldSuppressNonReloadableErrorPage) {
-  DoErrorLoad(net::ERR_ABORTED);
-  EXPECT_FALSE(core()->ShouldSuppressErrorPage(
-      NetErrorHelperCore::MAIN_FRAME, GURL(kFailedUrl), net::ERR_ABORTED));
-
-  DoErrorLoad(net::ERR_UNKNOWN_URL_SCHEME);
-  EXPECT_FALSE(core()->ShouldSuppressErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                                               GURL(kFailedUrl),
-                                               net::ERR_UNKNOWN_URL_SCHEME));
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest,
-       ShouldSuppressErrorPageForDifferentError) {
-  DoErrorLoad(net::ERR_CERT_AUTHORITY_INVALID);
-  EXPECT_FALSE(core()->ShouldSuppressErrorPage(
-      NetErrorHelperCore::MAIN_FRAME, GURL(kFailedUrl),
-      net::ERR_INVALID_AUTH_CREDENTIALS));
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, DoesNotReload) {
-  DoErrorLoad(net::ERR_ABORTED);
-  EXPECT_FALSE(timer()->IsRunning());
-
-  DoErrorLoad(net::ERR_UNKNOWN_URL_SCHEME);
-  EXPECT_FALSE(timer()->IsRunning());
-
-  DoErrorLoad(net::ERR_SSL_PROTOCOL_ERROR);
-  EXPECT_FALSE(timer()->IsRunning());
-
-  DoErrorLoad(net::ERR_BLOCKED_BY_ADMINISTRATOR);
-  EXPECT_FALSE(timer()->IsRunning());
-
-  DoErrorLoad(net::ERR_BAD_SSL_CLIENT_AUTH_CERT);
-  EXPECT_FALSE(timer()->IsRunning());
-
-  DoErrorLoadOfURL(net::ERR_ACCESS_DENIED, GURL("data://some-data-here"));
-  EXPECT_FALSE(timer()->IsRunning());
-
-  DoErrorLoadOfURL(net::ERR_ACCESS_DENIED, GURL("chrome-extension://foo"));
-  EXPECT_FALSE(timer()->IsRunning());
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, ShouldSuppressErrorPage) {
-  // Set up the environment to test ShouldSuppressErrorPage: auto-reload is
-  // enabled, an error page is loaded, and the auto-reload callback is running.
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-  timer()->Fire();
-
-  // Sub-frame load.
-  EXPECT_FALSE(core()->ShouldSuppressErrorPage(NetErrorHelperCore::SUB_FRAME,
-                                               GURL(kFailedUrl),
-                                               net::ERR_CONNECTION_RESET));
-  EXPECT_TRUE(core()->ShouldSuppressErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                                              GURL(kFailedUrl),
-                                              net::ERR_CONNECTION_RESET));
-  // No auto-reload attempt in flight.
-  EXPECT_FALSE(core()->ShouldSuppressErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                                               GURL(kFailedUrl),
-                                               net::ERR_CONNECTION_RESET));
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, HiddenAndShown) {
-  SetUpCore(true, true);
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-  EXPECT_TRUE(timer()->IsRunning());
-  core()->OnWasHidden();
-  EXPECT_FALSE(timer()->IsRunning());
-  core()->OnWasShown();
-  EXPECT_TRUE(timer()->IsRunning());
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, HiddenWhileOnline) {
-  SetUpCore(true, true);
-  core()->NetworkStateChanged(false);
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-  EXPECT_FALSE(timer()->IsRunning());
-  core()->OnWasHidden();
-  core()->NetworkStateChanged(true);
-  EXPECT_FALSE(timer()->IsRunning());
-  core()->NetworkStateChanged(false);
-  core()->OnWasShown();
-  EXPECT_FALSE(timer()->IsRunning());
-  core()->NetworkStateChanged(true);
-  EXPECT_TRUE(timer()->IsRunning());
-  core()->NetworkStateChanged(false);
-  core()->OnWasHidden();
-  EXPECT_FALSE(timer()->IsRunning());
-  core()->NetworkStateChanged(true);
-  EXPECT_FALSE(timer()->IsRunning());
-  core()->OnWasShown();
-  EXPECT_TRUE(timer()->IsRunning());
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, ShownWhileNotReloading) {
-  SetUpCore(true, false);
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-  EXPECT_FALSE(timer()->IsRunning());
-  core()->OnWasShown();
-  EXPECT_TRUE(timer()->IsRunning());
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest, ManualReloadShowsError) {
-  SetUpCore(true, true);
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-  core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME,
-                      NetErrorHelperCore::ERROR_PAGE);
-  EXPECT_FALSE(core()->ShouldSuppressErrorPage(NetErrorHelperCore::MAIN_FRAME,
-                                               GURL(kFailedUrl),
-                                               net::ERR_CONNECTION_RESET));
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest,
-       AutoReloadDisabledForCustomErrorPages) {
-  // This error code would normally trigger auto-reloads, but shouldn't if we
-  // are showing a custom error page (e.g. an interstitial.).
-  DoErrorLoadWithCustomErrorPage(net::ERR_CONNECTION_RESET);
-
-  EXPECT_FALSE(timer()->IsRunning());
-  EXPECT_EQ(0, reload_count());
-}
-
-TEST_F(NetErrorHelperCoreAutoReloadTest,
-       AutoReloadEnabledAfterCustomErrorPage) {
-  DoErrorLoadWithCustomErrorPage(net::ERR_CONNECTION_RESET);
-  EXPECT_FALSE(timer()->IsRunning());
-  EXPECT_EQ(0, reload_count());
-  DoErrorLoad(net::ERR_CONNECTION_RESET);
-  EXPECT_TRUE(timer()->IsRunning());
-}
-
 TEST_F(NetErrorHelperCoreTest, ExplicitReloadSucceeds) {
   DoErrorLoad(net::ERR_CONNECTION_RESET);
   EXPECT_EQ(0, reload_count());
diff --git a/components/BUILD.gn b/components/BUILD.gn
index fa79211..23f86db 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -592,6 +592,7 @@
       "//components/dom_distiller/content/browser",
       "//components/dom_distiller/core",
       "//components/dom_distiller/core:test_support",
+      "//components/error_page/content/browser:browser_tests",
       "//components/metrics:content",
       "//components/offline_pages/content/renovations",
       "//components/offline_pages/core/renovations",
diff --git a/components/error_page/content/browser/BUILD.gn b/components/error_page/content/browser/BUILD.gn
new file mode 100644
index 0000000..f39ebf6
--- /dev/null
+++ b/components/error_page/content/browser/BUILD.gn
@@ -0,0 +1,43 @@
+# Copyright 2020 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.
+
+static_library("browser") {
+  public = [ "net_error_auto_reloader.h" ]
+
+  sources = [ "net_error_auto_reloader.cc" ]
+
+  public_deps = [
+    "//base",
+    "//content/public/browser",
+    "//services/network/public/cpp",
+    "//services/network/public/mojom",
+  ]
+
+  deps = [
+    "//net",
+    "//url",
+  ]
+}
+
+source_set("browser_tests") {
+  testonly = true
+
+  defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+
+  sources = [ "net_error_auto_reloader_browsertest.cc" ]
+
+  deps = [
+    ":browser",
+    "//base",
+    "//base/test:test_support",
+    "//content/public/browser",
+    "//content/shell:content_shell_lib",
+    "//content/test:browsertest_support",
+    "//content/test:test_support",
+    "//net",
+    "//services/network:test_support",
+    "//testing/gtest",
+    "//url",
+  ]
+}
diff --git a/components/error_page/content/browser/DEPS b/components/error_page/content/browser/DEPS
new file mode 100644
index 0000000..3e18f370
--- /dev/null
+++ b/components/error_page/content/browser/DEPS
@@ -0,0 +1,14 @@
+include_rules = [
+  "+content/public/browser",
+  "+net/base",
+  "+services/network/public",
+]
+
+specific_include_rules = {
+  ".*_browsertest\.cc": [
+    "+content/public/test",
+    "+content/shell/browser/shell.h",
+    "+content/shell/browser/shell_content_browser_client.h",
+    "+services/network/test",
+  ]
+}
diff --git a/components/error_page/content/browser/net_error_auto_reloader.cc b/components/error_page/content/browser/net_error_auto_reloader.cc
new file mode 100644
index 0000000..6699bbb
--- /dev/null
+++ b/components/error_page/content/browser/net_error_auto_reloader.cc
@@ -0,0 +1,318 @@
+// Copyright 2020 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 "components/error_page/content/browser/net_error_auto_reloader.h"
+
+#include <algorithm>
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/navigation_throttle.h"
+#include "content/public/browser/network_service_instance.h"
+#include "content/public/browser/web_contents.h"
+#include "net/base/net_errors.h"
+#include "url/gurl.h"
+
+namespace error_page {
+
+namespace {
+
+bool ShouldAutoReload(content::NavigationHandle* handle) {
+  DCHECK(handle->HasCommitted());
+  const int net_error = handle->GetNetErrorCode();
+  return handle->IsErrorPage() && !handle->IsCustomErrorPage() &&
+         net_error != net::OK && !handle->IsPost() &&
+         // For now, net::ERR_UNKNOWN_URL_SCHEME is only being displayed on
+         // Chrome for Android.
+         net_error != net::ERR_UNKNOWN_URL_SCHEME &&
+         // Do not trigger if the server rejects a client certificate.
+         // https://crbug.com/431387
+         !net::IsClientCertificateError(net_error) &&
+         // Some servers reject client certificates with a generic
+         // handshake_failure alert.
+         // https://crbug.com/431387
+         net_error != net::ERR_SSL_PROTOCOL_ERROR &&
+         // Do not trigger for blocklisted URLs.
+         // https://crbug.com/803839
+         // Do not trigger for requests that were blocked by the browser itself.
+         !net::IsRequestBlockedError(net_error) &&
+         // Do not trigger for this error code because it is used by Chrome
+         // while an auth prompt is being displayed.
+         net_error != net::ERR_INVALID_AUTH_CREDENTIALS &&
+         // Don't auto-reload non-http/https schemas.
+         // https://crbug.com/471713
+         handle->GetURL().SchemeIsHTTPOrHTTPS() &&
+         // Don't auto reload if the error was a secure DNS network error, since
+         // the reload may interfere with the captive portal probe state.
+         // TODO(crbug.com/1016164): Explore how to allow reloads for secure DNS
+         // network errors without interfering with the captive portal probe
+         // state.
+         !handle->GetResolveErrorInfo().is_secure_network_error;
+}
+
+base::TimeDelta GetNextReloadDelay(size_t reload_count) {
+  static const int kDelaysMs[] = {1000,   5000,   30000,  60000,
+                                  300000, 600000, 1800000};
+  return base::TimeDelta::FromMilliseconds(
+      kDelaysMs[std::min(reload_count, base::size(kDelaysMs) - 1)]);
+}
+
+// Helper to block a navigation that would result in re-committing the same
+// error page a tab is already displaying.
+class IgnoreDuplicateErrorThrottle : public content::NavigationThrottle {
+ public:
+  using ShouldSuppressCallback =
+      base::OnceCallback<bool(content::NavigationHandle*)>;
+
+  IgnoreDuplicateErrorThrottle(content::NavigationHandle* handle,
+                               ShouldSuppressCallback should_suppress)
+      : content::NavigationThrottle(handle),
+        should_suppress_(std::move(should_suppress)) {
+    DCHECK(should_suppress_);
+  }
+  IgnoreDuplicateErrorThrottle(const IgnoreDuplicateErrorThrottle&) = delete;
+  IgnoreDuplicateErrorThrottle& operator=(const IgnoreDuplicateErrorThrottle&) =
+      delete;
+  ~IgnoreDuplicateErrorThrottle() override = default;
+
+  // content::NavigationThrottle:
+  content::NavigationThrottle::ThrottleCheckResult WillFailRequest() override {
+    DCHECK(should_suppress_);
+    if (std::move(should_suppress_).Run(navigation_handle()))
+      return content::NavigationThrottle::ThrottleAction::CANCEL;
+    return content::NavigationThrottle::ThrottleAction::PROCEED;
+  }
+
+  const char* GetNameForLogging() override {
+    return "IgnoreDuplicateErrorThrottle";
+  }
+
+ private:
+  ShouldSuppressCallback should_suppress_;
+};
+
+}  // namespace
+
+NetErrorAutoReloader::ErrorPageInfo::ErrorPageInfo(const GURL& url,
+                                                   net::Error error)
+    : url(url), error(error) {}
+
+NetErrorAutoReloader::ErrorPageInfo::~ErrorPageInfo() = default;
+
+NetErrorAutoReloader::NetErrorAutoReloader(content::WebContents* web_contents)
+    : content::WebContentsObserver(web_contents),
+      connection_tracker_(content::GetNetworkConnectionTracker()) {
+  connection_tracker_->AddNetworkConnectionObserver(this);
+
+  network::mojom::ConnectionType connection_type;
+  if (connection_tracker_->GetConnectionType(
+          &connection_type,
+          base::BindOnce(&NetErrorAutoReloader::SetInitialConnectionType,
+                         weak_ptr_factory_.GetWeakPtr()))) {
+    SetInitialConnectionType(connection_type);
+  }
+}
+
+NetErrorAutoReloader::~NetErrorAutoReloader() {
+  // NOTE: Tests may call `DisableConnectionChangeObservationForTesting` to null
+  // this out.
+  if (connection_tracker_)
+    connection_tracker_->RemoveNetworkConnectionObserver(this);
+}
+
+// static
+std::unique_ptr<content::NavigationThrottle>
+NetErrorAutoReloader::MaybeCreateThrottleFor(
+    content::NavigationHandle* handle) {
+  // Note that `CreateForWebContents` is a no-op if `contents` already has a
+  // NetErrorAutoReloader. See WebContentsUserData.
+  content::WebContents* contents = handle->GetWebContents();
+  CreateForWebContents(contents);
+  return FromWebContents(contents)->MaybeCreateThrottle(handle);
+}
+
+void NetErrorAutoReloader::DidStartNavigation(
+    content::NavigationHandle* handle) {
+  if (!handle->IsInMainFrame())
+    return;
+
+  // Suppress automatic reload as long as any navigations are pending.
+  PauseAutoReloadTimerIfRunning();
+  pending_navigations_.insert(handle);
+}
+
+void NetErrorAutoReloader::DidFinishNavigation(
+    content::NavigationHandle* handle) {
+  if (!handle->IsInMainFrame())
+    return;
+
+  pending_navigations_.erase(handle);
+  if (!handle->HasCommitted()) {
+    // This navigation was cancelled and not committed. If there are still other
+    // pending navigations, or we aren't sitting on a error page which allows
+    // auto-reload, there's nothing to do.
+    if (!pending_navigations_.empty() || !current_reloadable_error_page_info_)
+      return;
+
+    // The last pending navigation was just cancelled and we're sitting on an
+    // error page which allows auto-reload. Schedule the next auto-reload
+    // attempt.
+    is_auto_reload_in_progress_ = false;
+    ScheduleNextAutoReload();
+    return;
+  }
+
+  if (!ShouldAutoReload(handle)) {
+    // We've committed something that doesn't support auto-reload. Reset
+    // all auto-reload state so nothing interesting happens until another
+    // error page navigation is committed.
+    Reset();
+    return;
+  }
+
+  // This heuristic isn't perfect but it should be good enough: if the new
+  // commit is not a reload, or if it's an error page with an error code
+  // different from what we had previously committed, we treat it as a new
+  // error and thus reset our tracking state.
+  net::Error net_error = handle->GetNetErrorCode();
+  if (handle->GetReloadType() == content::ReloadType::NONE ||
+      !current_reloadable_error_page_info_ ||
+      net_error != current_reloadable_error_page_info_->error) {
+    Reset();
+    current_reloadable_error_page_info_ =
+        ErrorPageInfo(handle->GetURL(), net_error);
+  }
+
+  // We only schedule a reload if there are no other pending navigations.
+  // If there are and they end up getting terminated without a commit, we
+  // will schedule the next auto-reload at that time.
+  if (pending_navigations_.empty())
+    ScheduleNextAutoReload();
+}
+
+void NetErrorAutoReloader::NavigationStopped() {
+  // Stopping navigation or loading cancels all pending auto-reload behavior
+  // until the next time a new error page is committed. Note that a stop during
+  // navigation will also result in a DidFinishNavigation with a failed
+  // navigation and an error code of ERR_ABORTED. However stops can also occur
+  // after an error page commits but before it finishes loading, and we want to
+  // catch those cases too.
+  Reset();
+}
+
+void NetErrorAutoReloader::OnVisibilityChanged(content::Visibility visibility) {
+  if (!IsWebContentsVisible()) {
+    PauseAutoReloadTimerIfRunning();
+  } else if (pending_navigations_.empty()) {
+    ResumeAutoReloadIfPaused();
+  }
+}
+
+void NetErrorAutoReloader::OnConnectionChanged(
+    network::mojom::ConnectionType type) {
+  is_online_ = (type != network::mojom::ConnectionType::CONNECTION_NONE);
+  if (!is_online_) {
+    PauseAutoReloadTimerIfRunning();
+  } else if (pending_navigations_.empty()) {
+    ResumeAutoReloadIfPaused();
+  }
+}
+
+// static
+base::TimeDelta NetErrorAutoReloader::GetNextReloadDelayForTesting(
+    size_t reload_count) {
+  return GetNextReloadDelay(reload_count);
+}
+
+void NetErrorAutoReloader::DisableConnectionChangeObservationForTesting() {
+  if (connection_tracker_) {
+    connection_tracker_->RemoveNetworkConnectionObserver(this);
+    connection_tracker_ = nullptr;
+  }
+}
+
+void NetErrorAutoReloader::SetInitialConnectionType(
+    network::mojom::ConnectionType type) {
+  // NOTE: Tests may call `DisableConnectionChangeObservationForTesting` to null
+  // this out.
+  if (connection_tracker_)
+    OnConnectionChanged(type);
+}
+
+bool NetErrorAutoReloader::IsWebContentsVisible() {
+  return web_contents()->GetVisibility() != content::Visibility::HIDDEN;
+}
+
+void NetErrorAutoReloader::Reset() {
+  next_reload_timer_.reset();
+  num_reloads_for_current_error_ = 0;
+  is_auto_reload_in_progress_ = false;
+  current_reloadable_error_page_info_.reset();
+}
+
+void NetErrorAutoReloader::PauseAutoReloadTimerIfRunning() {
+  next_reload_timer_.reset();
+}
+
+void NetErrorAutoReloader::ResumeAutoReloadIfPaused() {
+  if (current_reloadable_error_page_info_ && !next_reload_timer_)
+    ScheduleNextAutoReload();
+}
+
+void NetErrorAutoReloader::ScheduleNextAutoReload() {
+  DCHECK(current_reloadable_error_page_info_);
+  if (!is_online_ || !IsWebContentsVisible())
+    return;
+
+  // Note that Unretained is safe here because base::OneShotTimer will never
+  // run its callback once destructed.
+  next_reload_timer_.emplace();
+  next_reload_timer_->Start(
+      FROM_HERE, GetNextReloadDelay(num_reloads_for_current_error_),
+      base::BindOnce(&NetErrorAutoReloader::ReloadMainFrame,
+                     base::Unretained(this)));
+}
+
+void NetErrorAutoReloader::ReloadMainFrame() {
+  DCHECK(current_reloadable_error_page_info_);
+  if (!is_online_ || !IsWebContentsVisible())
+    return;
+
+  ++num_reloads_for_current_error_;
+  is_auto_reload_in_progress_ = true;
+  web_contents()->GetMainFrame()->Reload();
+}
+
+std::unique_ptr<content::NavigationThrottle>
+NetErrorAutoReloader::MaybeCreateThrottle(content::NavigationHandle* handle) {
+  DCHECK(handle->IsInMainFrame());
+  if (!current_reloadable_error_page_info_ ||
+      current_reloadable_error_page_info_->url != handle->GetURL() ||
+      !is_auto_reload_in_progress_) {
+    return nullptr;
+  }
+
+  return std::make_unique<IgnoreDuplicateErrorThrottle>(
+      handle, base::BindOnce(&NetErrorAutoReloader::ShouldSuppressErrorPage,
+                             base::Unretained(this)));
+}
+
+bool NetErrorAutoReloader::ShouldSuppressErrorPage(
+    content::NavigationHandle* handle) {
+  // We already verified these conditions when the throttle was created, but now
+  // that the throttle is about to fail its navigation, we double-check in case
+  // another navigation has committed in the interim.
+  if (!current_reloadable_error_page_info_ ||
+      current_reloadable_error_page_info_->url != handle->GetURL() ||
+      current_reloadable_error_page_info_->error != handle->GetNetErrorCode()) {
+    return false;
+  }
+
+  return true;
+}
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(NetErrorAutoReloader)
+
+}  // namespace error_page
diff --git a/components/error_page/content/browser/net_error_auto_reloader.h b/components/error_page/content/browser/net_error_auto_reloader.h
new file mode 100644
index 0000000..f7ee52a8
--- /dev/null
+++ b/components/error_page/content/browser/net_error_auto_reloader.h
@@ -0,0 +1,115 @@
+// Copyright 2020 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 COMPONENTS_ERROR_PAGE_CONTENT_BROWSER_NET_ERROR_AUTO_RELOADER_H_
+#define COMPONENTS_ERROR_PAGE_CONTENT_BROWSER_NET_ERROR_AUTO_RELOADER_H_
+
+#include <stddef.h>
+
+#include <memory>
+#include <set>
+
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+#include "net/base/net_errors.h"
+#include "services/network/public/cpp/network_connection_tracker.h"
+#include "services/network/public/mojom/network_change_manager.mojom.h"
+#include "url/gurl.h"
+
+namespace content {
+class NavigationHandle;
+class NavigationThrottle;
+class WebContents;
+}  // namespace content
+
+namespace error_page {
+
+// This class implements support for automatic reload attempts with backoff
+// whenever a WebContents' main frame lands on common network error pages. To
+// use this behavior as a Content embedder, simply call the static
+// `MaybeCreateNavigationThrottle()` method from within your implementation of
+// ContentBrowserClient::CreateThrottlesForNavigation.
+class NetErrorAutoReloader
+    : public content::WebContentsObserver,
+      public content::WebContentsUserData<NetErrorAutoReloader>,
+      public network::NetworkConnectionTracker::NetworkConnectionObserver {
+ public:
+  NetErrorAutoReloader(const NetErrorAutoReloader&) = delete;
+  NetErrorAutoReloader& operator=(const NetErrorAutoReloader&) = delete;
+  ~NetErrorAutoReloader() override;
+
+  // Maybe installs a throttle for the given navigation, lazily initializing the
+  // appropriate WebContents' NetErrorAutoReloader instance if necessary. For
+  // embedders wanting to use NetErrorAutoReload's behavior, it's sufficient to
+  // call this from ContentBrowserClient::CreateThrottlesForNavigation for each
+  // navigation processed.
+  static std::unique_ptr<content::NavigationThrottle> MaybeCreateThrottleFor(
+      content::NavigationHandle* handle);
+
+  // content::WebContentsObserver:
+  void DidStartNavigation(content::NavigationHandle* handle) override;
+  void DidFinishNavigation(content::NavigationHandle* handle) override;
+  void NavigationStopped() override;
+  void OnVisibilityChanged(content::Visibility visibility) override;
+
+  // network::NetworkConnectionTracker::NetworkConnectionObserver:
+  void OnConnectionChanged(network::mojom::ConnectionType type) override;
+
+  // Returns the delay applied when scheduling the next auto-reload of a page
+  // after it's already been auto-reloaded `reload_count` times.
+  static base::TimeDelta GetNextReloadDelayForTesting(size_t reload_count);
+
+  // Permanently unsubscribes this object from receiving OnConnectionChanged
+  // notifications. Used in tests which want to drive this behavior explicitly.
+  void DisableConnectionChangeObservationForTesting();
+
+  // Returns the timer used internally to schedule the next auto-reload task,
+  // or null if no auto-reload task is currently scheduled.
+  base::Optional<base::OneShotTimer>& next_reload_timer_for_testing() {
+    return next_reload_timer_;
+  }
+
+ private:
+  friend class content::WebContentsUserData<NetErrorAutoReloader>;
+
+  explicit NetErrorAutoReloader(content::WebContents* web_contents);
+
+  void SetInitialConnectionType(network::mojom::ConnectionType type);
+  bool IsWebContentsVisible();
+  void Reset();
+  void PauseAutoReloadTimerIfRunning();
+  void ResumeAutoReloadIfPaused();
+  void ScheduleNextAutoReload();
+  void ReloadMainFrame();
+  std::unique_ptr<content::NavigationThrottle> MaybeCreateThrottle(
+      content::NavigationHandle* handle);
+  bool ShouldSuppressErrorPage(content::NavigationHandle* handle);
+
+  struct ErrorPageInfo {
+    ErrorPageInfo(const GURL& url, net::Error error);
+    ~ErrorPageInfo();
+
+    GURL url;
+    net::Error error;
+  };
+
+  network::NetworkConnectionTracker* connection_tracker_;
+  bool is_online_ = true;
+  std::set<content::NavigationHandle*> pending_navigations_;
+  base::Optional<base::OneShotTimer> next_reload_timer_;
+  base::Optional<ErrorPageInfo> current_reloadable_error_page_info_;
+  size_t num_reloads_for_current_error_ = 0;
+  bool is_auto_reload_in_progress_ = false;
+  base::WeakPtrFactory<NetErrorAutoReloader> weak_ptr_factory_{this};
+
+  WEB_CONTENTS_USER_DATA_KEY_DECL();
+};
+
+}  // namespace error_page
+
+#endif  // COMPONENTS_ERROR_PAGE_CONTENT_BROWSER_NET_ERROR_AUTO_RELOADER_H_
diff --git a/components/error_page/content/browser/net_error_auto_reloader_browsertest.cc b/components/error_page/content/browser/net_error_auto_reloader_browsertest.cc
new file mode 100644
index 0000000..7925d93
--- /dev/null
+++ b/components/error_page/content/browser/net_error_auto_reloader_browsertest.cc
@@ -0,0 +1,538 @@
+// Copyright 2020 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 "components/error_page/content/browser/net_error_auto_reloader.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/optional.h"
+#include "base/test/bind_test_util.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/test_navigation_throttle.h"
+#include "content/public/test/test_navigation_throttle_inserter.h"
+#include "content/public/test/url_loader_interceptor.h"
+#include "content/shell/browser/shell.h"
+#include "content/shell/browser/shell_content_browser_client.h"
+#include "net/base/net_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace error_page {
+namespace {
+
+// Helper which intercepts all requests for a given URL and terminates them with
+// a given net error code. Interception affects most browser requests globally
+// (tests here are concerned only with main-frame navigation requests, which are
+// covered) and persists from construction time until destruction time.
+class NetErrorUrlInterceptor {
+ public:
+  NetErrorUrlInterceptor(GURL url, net::Error error)
+      : url_(std::move(url)),
+        interceptor_(base::BindLambdaForTesting(
+            [this,
+             error](content::URLLoaderInterceptor::RequestParams* params) {
+              if (params->url_request.url != url_)
+                return false;
+              network::URLLoaderCompletionStatus status;
+              status.error_code = error;
+              params->client->OnComplete(status);
+              return true;
+            })) {}
+  ~NetErrorUrlInterceptor() = default;
+
+ private:
+  const GURL url_;
+  const content::URLLoaderInterceptor interceptor_;
+};
+
+// Helper to intercept all navigations with a failure using custom error page
+// contents. As long as an instance of this class exists, navigations will land
+// on its custom error page.
+class CustomErrorPageThrottleInserter {
+ public:
+  CustomErrorPageThrottleInserter(content::WebContents* web_contents,
+                                  net::Error error,
+                                  std::string error_page_contents)
+      : throttle_inserter_(
+            web_contents,
+            base::BindLambdaForTesting(
+                [error, error_page_contents](content::NavigationHandle* handle)
+                    -> std::unique_ptr<content::NavigationThrottle> {
+                  auto throttle =
+                      std::make_unique<content::TestNavigationThrottle>(handle);
+                  throttle->SetResponse(
+                      content::TestNavigationThrottle::WILL_START_REQUEST,
+                      content::TestNavigationThrottle::SYNCHRONOUS,
+                      content::NavigationThrottle::ThrottleCheckResult(
+                          content::NavigationThrottle::CANCEL, error,
+                          error_page_contents));
+                  return throttle;
+                })) {}
+  ~CustomErrorPageThrottleInserter() = default;
+
+ private:
+  const content::TestNavigationThrottleInserter throttle_inserter_;
+};
+
+// Helper to intercept and defer the first navigation initiated after
+// construction. Allows a test to wait for both request start and deferral, as
+// well as request completion after cancellation.
+class DeferNextNavigationThrottleInserter
+    : public content::WebContentsObserver {
+ public:
+  class DeferringThrottle : public content::NavigationThrottle {
+   public:
+    explicit DeferringThrottle(content::NavigationHandle* handle,
+                               base::OnceClosure callback)
+        : NavigationThrottle(handle), callback_(std::move(callback)) {}
+
+    ~DeferringThrottle() override = default;
+
+    void Cancel() { CancelDeferredNavigation(CANCEL); }
+
+    // content::NavigationThrottle:
+    ThrottleCheckResult WillStartRequest() override {
+      std::move(callback_).Run();
+      return DEFER;
+    }
+    const char* GetNameForLogging() override { return "DeferringThrottle"; }
+
+   private:
+    base::OnceClosure callback_;
+  };
+
+  explicit DeferNextNavigationThrottleInserter(
+      content::WebContents* web_contents)
+      : content::WebContentsObserver(web_contents),
+        throttle_inserter_(
+            web_contents,
+            base::BindRepeating(
+                &DeferNextNavigationThrottleInserter::MaybeCreateThrottle,
+                base::Unretained(this))) {}
+  ~DeferNextNavigationThrottleInserter() override = default;
+
+  void WaitForNextNavigationToBeDeferred() { defer_wait_loop_.Run(); }
+
+  void CancelAndWaitForNavigationToFinish() {
+    throttle_->Cancel();
+    finish_wait_loop_.Run();
+  }
+
+  // content::WebContentsObserver:
+  void DidFinishNavigation(content::NavigationHandle* handle) override {
+    DCHECK(throttle_);
+    if (handle == throttle_->navigation_handle())
+      finish_wait_loop_.Quit();
+  }
+
+ private:
+  std::unique_ptr<content::NavigationThrottle> MaybeCreateThrottle(
+      content::NavigationHandle* handle) {
+    if (throttle_)
+      return nullptr;
+
+    auto throttle = std::make_unique<DeferringThrottle>(
+        handle, defer_wait_loop_.QuitClosure());
+    throttle_ = throttle.get();
+    return throttle;
+  }
+
+  const content::TestNavigationThrottleInserter throttle_inserter_;
+  DeferringThrottle* throttle_ = nullptr;
+  base::RunLoop defer_wait_loop_;
+  base::RunLoop finish_wait_loop_;
+};
+
+base::TimeDelta GetDelayForReloadCount(size_t count) {
+  return NetErrorAutoReloader::GetNextReloadDelayForTesting(count);
+}
+
+class NetErrorAutoReloaderBrowserTest : public content::ContentBrowserTest {
+ public:
+  void SetUpOnMainThread() override {
+    ASSERT_TRUE(embedded_test_server()->Start());
+    error_page::NetErrorAutoReloader::CreateForWebContents(
+        shell()->web_contents());
+
+    // Start online by default in all tests.
+    GetAutoReloader()->DisableConnectionChangeObservationForTesting();
+    GetAutoReloader()->OnConnectionChanged(
+        network::mojom::ConnectionType::CONNECTION_WIFI);
+
+    content::ShellContentBrowserClient::Get()
+        ->set_create_throttles_for_navigation_callback(base::BindRepeating(
+            [](content::NavigationHandle* handle)
+                -> std::vector<std::unique_ptr<content::NavigationThrottle>> {
+              std::vector<std::unique_ptr<content::NavigationThrottle>>
+                  throttles;
+              auto throttle =
+                  NetErrorAutoReloader::MaybeCreateThrottleFor(handle);
+              if (throttle)
+                throttles.push_back(std::move(throttle));
+              return throttles;
+            }));
+  }
+
+  NetErrorAutoReloader* GetAutoReloader() {
+    return error_page::NetErrorAutoReloader::FromWebContents(
+        shell()->web_contents());
+  }
+
+  // Returns the time-delay of the currently scheduled auto-reload task, if one
+  // is scheduled. If no auto-reload is scheduled, this returns null.
+  base::Optional<base::TimeDelta> GetCurrentAutoReloadDelay() {
+    const base::Optional<base::OneShotTimer>& timer =
+        GetAutoReloader()->next_reload_timer_for_testing();
+    if (!timer)
+      return base::nullopt;
+    return timer->GetCurrentDelay();
+  }
+
+  // Forces the currently scheduled auto-reload task to execute immediately.
+  // This allows tests to force normal forward-progress of the delayed reload
+  // logic without waiting a long time or painfully messing around with mock
+  // time in browser tests.
+  void ForceScheduledAutoReloadNow() {
+    base::Optional<base::OneShotTimer>& timer =
+        GetAutoReloader()->next_reload_timer_for_testing();
+    if (timer && timer->IsRunning())
+      timer->FireNow();
+  }
+
+  GURL GetTestUrl() { return embedded_test_server()->GetURL("/empty.html"); }
+
+  // Helper used by all tests to perform navigations, whether successful or
+  // intercepted for simulated failure. Note that this asynchronously initiates
+  // the navigation and then waits only for the *navigation* to finish; this is
+  // in contrast to common test utilities which wait for loading to finish. It
+  // matters because most of NetErrorAutoReloader's interesting behavior is
+  // triggered at navigation completion and tests may want to observe the
+  // immediate side effects, such as the scheduling of an auto-reload timer.
+  //
+  // Return true if the navigation was successful, or false if it failed.
+  bool NavigateMainFrame(const GURL& url) WARN_UNUSED_RESULT {
+    content::TestNavigationManager navigation(shell()->web_contents(), url);
+    shell()->web_contents()->GetController().LoadURL(
+        url, content::Referrer(), ui::PAGE_TRANSITION_TYPED,
+        /*extra_headers=*/std::string());
+    navigation.WaitForNavigationFinished();
+    return navigation.was_successful();
+  }
+
+  void SimulateNetworkGoingOnline() {
+    GetAutoReloader()->OnConnectionChanged(
+        network::mojom::ConnectionType::CONNECTION_WIFI);
+  }
+
+  void SimulateNetworkGoingOffline() {
+    GetAutoReloader()->OnConnectionChanged(
+        network::mojom::ConnectionType::CONNECTION_NONE);
+  }
+};
+
+// A successful navigation results in no auto-reload being scheduled.
+IN_PROC_BROWSER_TEST_F(NetErrorAutoReloaderBrowserTest, NoError) {
+  EXPECT_TRUE(NavigateMainFrame(GetTestUrl()));
+  EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+}
+
+// A normal error page triggers a scheduled reload.
+IN_PROC_BROWSER_TEST_F(NetErrorAutoReloaderBrowserTest, ErrorSchedulesReload) {
+  NetErrorUrlInterceptor interceptor(GetTestUrl(), net::ERR_CONNECTION_RESET);
+  EXPECT_FALSE(NavigateMainFrame(GetTestUrl()));
+  EXPECT_EQ(GetDelayForReloadCount(0), GetCurrentAutoReloadDelay());
+}
+
+// A successful auto-reload operation will behave like any successful navigation
+// and not schedule subsequent reloads.
+IN_PROC_BROWSER_TEST_F(NetErrorAutoReloaderBrowserTest, ErrorRecovery) {
+  auto interceptor = std::make_unique<NetErrorUrlInterceptor>(
+      GetTestUrl(), net::ERR_CONNECTION_RESET);
+  EXPECT_FALSE(NavigateMainFrame(GetTestUrl()));
+  EXPECT_EQ(GetDelayForReloadCount(0), GetCurrentAutoReloadDelay());
+  interceptor.reset();
+
+  // Force the scheduled auto-reload once interception is cancelled, and observe
+  // a successful navigation.
+  content::TestNavigationManager navigation(shell()->web_contents(),
+                                            GetTestUrl());
+  ForceScheduledAutoReloadNow();
+  navigation.WaitForNavigationFinished();
+  EXPECT_TRUE(navigation.was_successful());
+
+  // No new auto-reload scheduled.
+  EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+}
+
+// An auto-reload that fails in the same way as the original navigation will
+// result in another reload being scheduled with an increased delay.
+IN_PROC_BROWSER_TEST_F(NetErrorAutoReloaderBrowserTest, ReloadDelayBackoff) {
+  auto interceptor = std::make_unique<NetErrorUrlInterceptor>(
+      GetTestUrl(), net::ERR_CONNECTION_RESET);
+  EXPECT_FALSE(NavigateMainFrame(GetTestUrl()));
+  EXPECT_EQ(GetDelayForReloadCount(0), GetCurrentAutoReloadDelay());
+
+  // Force the scheduled auto-reload to run while still intercepting the
+  // navigation request and forcing it to fail with the same error. Observe that
+  // the navigation fails again and that a new auto-reload task has been
+  // scheduled with an appropriately increased delay. Note that these auto-
+  // reload navigations are also expected not to commit, since we suppress
+  // committing a new error page navigation when it corresponds to the same
+  // error code as the currently committed error page.
+  {
+    content::TestNavigationManager navigation(shell()->web_contents(),
+                                              GetTestUrl());
+    ForceScheduledAutoReloadNow();
+    navigation.WaitForNavigationFinished();
+    EXPECT_FALSE(navigation.was_committed());
+    EXPECT_EQ(GetDelayForReloadCount(1), GetCurrentAutoReloadDelay());
+  }
+
+  // Do that one more time, for good measure. Note that we expect the scheduled
+  // auto-reload delay to change again here, still with no new commit.
+  {
+    content::TestNavigationManager navigation(shell()->web_contents(),
+                                              GetTestUrl());
+    ForceScheduledAutoReloadNow();
+    navigation.WaitForNavigationFinished();
+    EXPECT_FALSE(navigation.was_committed());
+    EXPECT_EQ(GetDelayForReloadCount(2), GetCurrentAutoReloadDelay());
+    interceptor.reset();
+  }
+
+  // Finally, let the next reload succeed and verify successful navigation with
+  // no subsequent auto-reload scheduling.
+  {
+    content::TestNavigationManager navigation(shell()->web_contents(),
+                                              GetTestUrl());
+    ForceScheduledAutoReloadNow();
+    navigation.WaitForNavigationFinished();
+    EXPECT_TRUE(navigation.was_successful());
+  }
+}
+
+// If an auto-reload results in a different network error, it's treated as a new
+// navigation and the auto-reload delay backoff is reset.
+IN_PROC_BROWSER_TEST_F(NetErrorAutoReloaderBrowserTest,
+                       ResetOnAutoReloadWithNewError) {
+  {
+    NetErrorUrlInterceptor interceptor(GetTestUrl(), net::ERR_CONNECTION_RESET);
+    EXPECT_FALSE(NavigateMainFrame(GetTestUrl()));
+    EXPECT_EQ(GetDelayForReloadCount(0), GetCurrentAutoReloadDelay());
+  }
+
+  // Force the scheduled auto-reload to run while still intercepting the
+  // navigation request and forcing it to fail with a new error. Observe that
+  // the navigation fails again but that the new auto-reload task has been
+  // scheduled as if it was the first failure, since the error code is
+  // different.
+  {
+    NetErrorUrlInterceptor interceptor(GetTestUrl(), net::ERR_ACCESS_DENIED);
+    content::TestNavigationManager navigation(shell()->web_contents(),
+                                              GetTestUrl());
+    ForceScheduledAutoReloadNow();
+    navigation.WaitForNavigationFinished();
+    EXPECT_TRUE(navigation.was_committed());
+    EXPECT_FALSE(navigation.was_successful());
+    EXPECT_EQ(GetDelayForReloadCount(0), GetCurrentAutoReloadDelay());
+  }
+}
+
+// An explicitly stopped navigation from an error page does not trigger
+// auto-reload to restart.
+IN_PROC_BROWSER_TEST_F(NetErrorAutoReloaderBrowserTest, StopCancelsAutoReload) {
+  NetErrorUrlInterceptor interceptor(GetTestUrl(), net::ERR_CONNECTION_RESET);
+  EXPECT_FALSE(NavigateMainFrame(GetTestUrl()));
+  EXPECT_EQ(GetDelayForReloadCount(0), GetCurrentAutoReloadDelay());
+
+  // Start the navigation and simulate stoppage via user action (i.e.
+  // `WebContents::Stop`). This should cancel the previously scheduled
+  // auto-reload timer without scheduling a new one.
+  content::TestNavigationManager navigation(shell()->web_contents(),
+                                            GetTestUrl());
+  shell()->web_contents()->GetController().LoadURL(
+      GetTestUrl(), content::Referrer(), ui::PAGE_TRANSITION_TYPED,
+      /*extra_headers=*/std::string());
+  EXPECT_TRUE(navigation.WaitForRequestStart());
+  shell()->web_contents()->Stop();
+  EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+}
+
+// Various specific types of network-layer errors do not trigger auto-reload.
+IN_PROC_BROWSER_TEST_F(NetErrorAutoReloaderBrowserTest,
+                       NoAutoReloadOnUnsupportedNetworkErrors) {
+  {
+    NetErrorUrlInterceptor interceptor(GetTestUrl(),
+                                       net::ERR_UNKNOWN_URL_SCHEME);
+    EXPECT_FALSE(NavigateMainFrame(GetTestUrl()));
+    EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+  }
+  {
+    NetErrorUrlInterceptor interceptor(GetTestUrl(),
+                                       net::ERR_BAD_SSL_CLIENT_AUTH_CERT);
+    EXPECT_FALSE(NavigateMainFrame(GetTestUrl()));
+    EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+  }
+  {
+    NetErrorUrlInterceptor interceptor(GetTestUrl(),
+                                       net::ERR_SSL_PROTOCOL_ERROR);
+    EXPECT_FALSE(NavigateMainFrame(GetTestUrl()));
+    EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+  }
+  {
+    NetErrorUrlInterceptor interceptor(GetTestUrl(),
+                                       net::ERR_BLOCKED_BY_ADMINISTRATOR);
+    EXPECT_FALSE(NavigateMainFrame(GetTestUrl()));
+    EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+  }
+  {
+    NetErrorUrlInterceptor interceptor(GetTestUrl(),
+                                       net::ERR_INVALID_AUTH_CREDENTIALS);
+    EXPECT_FALSE(NavigateMainFrame(GetTestUrl()));
+    EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+  }
+}
+
+// Only HTTP and HTTPS navigation error pages activate auto-reload.
+IN_PROC_BROWSER_TEST_F(NetErrorAutoReloaderBrowserTest,
+                       NoAutoReloadWithoutHttpOrHttps) {
+  {
+    const GURL kTestDataUrl{"data://whatever"};
+    NetErrorUrlInterceptor interceptor(kTestDataUrl, net::ERR_ACCESS_DENIED);
+    EXPECT_FALSE(NavigateMainFrame(kTestDataUrl));
+    EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+  }
+
+  {
+    const GURL kTestFileUrl{"file://whatever"};
+    NetErrorUrlInterceptor interceptor(kTestFileUrl, net::ERR_ACCESS_DENIED);
+    EXPECT_FALSE(NavigateMainFrame(kTestFileUrl));
+    EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+  }
+}
+
+// Custom error pages do not activate auto-reload.
+IN_PROC_BROWSER_TEST_F(NetErrorAutoReloaderBrowserTest,
+                       NoAutoReloadWithCustomErrorPage) {
+  CustomErrorPageThrottleInserter custom_error_page(
+      shell()->web_contents(), net::ERR_ACCESS_DENIED,
+      "<html><body><strong>NONE SHALL PASS!</strong></body></html>");
+  EXPECT_FALSE(NavigateMainFrame(GetTestUrl()));
+  EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+}
+
+// Starting a new navigation cancels any pending auto-reload.
+IN_PROC_BROWSER_TEST_F(NetErrorAutoReloaderBrowserTest,
+                       NavigationCancelsAutoReload) {
+  // Force an error to initiate auto-reload.
+  auto interceptor = std::make_unique<NetErrorUrlInterceptor>(
+      GetTestUrl(), net::ERR_CONNECTION_RESET);
+  EXPECT_FALSE(NavigateMainFrame(GetTestUrl()));
+  EXPECT_EQ(GetDelayForReloadCount(0), GetCurrentAutoReloadDelay());
+  interceptor.reset();
+
+  // Start a new navigation before the reload task can run. Reload should be
+  // cancelled. Note that we wait only for the request to start and be deferred
+  // here before verifying the auto-reload cancellation.
+  DeferNextNavigationThrottleInserter deferrer(shell()->web_contents());
+  shell()->web_contents()->GetController().LoadURL(
+      GetTestUrl(), content::Referrer(), ui::PAGE_TRANSITION_TYPED,
+      /*extra_headers=*/std::string());
+  deferrer.WaitForNextNavigationToBeDeferred();
+  EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+
+  // Now cancel the deferred navigation and observe that auto-reload for the
+  // error page is rescheduled.
+  deferrer.CancelAndWaitForNavigationToFinish();
+  EXPECT_EQ(GetDelayForReloadCount(0), GetCurrentAutoReloadDelay());
+}
+
+// An error page while offline does not trigger auto-reload.
+IN_PROC_BROWSER_TEST_F(NetErrorAutoReloaderBrowserTest,
+                       NoAutoReloadWhileOffline) {
+  SimulateNetworkGoingOffline();
+
+  // This would normally schedule an auto-reload, but we're offline.
+  NetErrorUrlInterceptor interceptor(GetTestUrl(), net::ERR_CONNECTION_RESET);
+  EXPECT_FALSE(NavigateMainFrame(GetTestUrl()));
+  EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+}
+
+// If the browser comes online while sitting at an error page that supports
+// auto-reload, a new auto-reload task should be scheduled.
+IN_PROC_BROWSER_TEST_F(NetErrorAutoReloaderBrowserTest,
+                       AutoReloadWhenBrowserComesOnline) {
+  SimulateNetworkGoingOffline();
+
+  // This would normally schedule an auto-reload, but we're offline.
+  NetErrorUrlInterceptor interceptor(GetTestUrl(), net::ERR_CONNECTION_RESET);
+  EXPECT_FALSE(NavigateMainFrame(GetTestUrl()));
+  EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+
+  SimulateNetworkGoingOnline();
+  EXPECT_EQ(GetDelayForReloadCount(0), GetCurrentAutoReloadDelay());
+}
+
+// If the browser comes online while sitting at non-error page, auto-reload is
+// not scheduled.
+IN_PROC_BROWSER_TEST_F(NetErrorAutoReloaderBrowserTest,
+                       NoAutoReloadOnNonErrorPageWhenBrowserComesOnline) {
+  EXPECT_TRUE(NavigateMainFrame(GetTestUrl()));
+  EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+
+  SimulateNetworkGoingOffline();
+  EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+
+  SimulateNetworkGoingOnline();
+  EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+}
+
+// Auto-reload is not scheduled when the WebContents are hidden.
+IN_PROC_BROWSER_TEST_F(NetErrorAutoReloaderBrowserTest,
+                       NoAutoReloadWhenContentsHidden) {
+  shell()->web_contents()->WasHidden();
+
+  // This would normally schedule an auto-reload, but we're offline.
+  NetErrorUrlInterceptor interceptor(GetTestUrl(), net::ERR_CONNECTION_RESET);
+  EXPECT_FALSE(NavigateMainFrame(GetTestUrl()));
+  EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+}
+
+// If the WebContents becomes visible while sitting at an error page that
+// supports auto-reload, a new auto-reload task should be scheduled.
+IN_PROC_BROWSER_TEST_F(NetErrorAutoReloaderBrowserTest,
+                       AutoReloadWhenContentsBecomeVisible) {
+  shell()->web_contents()->WasHidden();
+
+  // This would normally schedule an auto-reload, but we're offline.
+  NetErrorUrlInterceptor interceptor(GetTestUrl(), net::ERR_CONNECTION_RESET);
+  EXPECT_FALSE(NavigateMainFrame(GetTestUrl()));
+  EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+
+  shell()->web_contents()->WasShown();
+  EXPECT_EQ(GetDelayForReloadCount(0), GetCurrentAutoReloadDelay());
+}
+
+// If the WebContents becomes visible while sitting at non-error page,
+// auto-reload is not scheduled.
+IN_PROC_BROWSER_TEST_F(NetErrorAutoReloaderBrowserTest,
+                       NoAutoReloadOnNonErrorPageWhenContentsBecomeVisible) {
+  EXPECT_TRUE(NavigateMainFrame(GetTestUrl()));
+  EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+
+  shell()->web_contents()->WasHidden();
+  EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+
+  shell()->web_contents()->WasShown();
+  EXPECT_EQ(base::nullopt, GetCurrentAutoReloadDelay());
+}
+
+}  // namespace
+}  // namespace error_page
diff --git a/content/browser/frame_host/navigation_request.cc b/content/browser/frame_host/navigation_request.cc
index 7ef7730..c4e8c37 100644
--- a/content/browser/frame_host/navigation_request.cc
+++ b/content/browser/frame_host/navigation_request.cc
@@ -1473,7 +1473,7 @@
       base::debug::DumpWithoutCrashing();
     }
 
-    ReadyToCommitNavigation(false /* is_error */);
+    ReadyToCommitNavigation(CommitPageType::kNonErrorPage);
     CommitNavigation();
     return;
   }
@@ -3100,7 +3100,8 @@
   }
 
   sandbox_flags_to_commit_ = ComputeSandboxFlagsToCommit();
-  ReadyToCommitNavigation(true);
+  ReadyToCommitNavigation(error_page_content ? CommitPageType::kCustomErrorPage
+                                             : CommitPageType::kErrorPage);
   render_frame_host_->FailedNavigation(this, *common_params_, *commit_params_,
                                        has_stale_copy_in_cache_, net_error_,
                                        error_page_content);
@@ -3797,7 +3798,7 @@
     // commit. Inform observers that the navigation is now ready to commit,
     // unless it is not set to commit (204/205s/downloads).
     if (render_frame_host_)
-      ReadyToCommitNavigation(false);
+      ReadyToCommitNavigation(CommitPageType::kNonErrorPage);
 
     // The call above might block on showing a user dialog. The interaction of
     // the user with this dialog might result in the WebContents owning this
@@ -4160,10 +4161,11 @@
          !IsForMhtmlSubframe();
 }
 
-void NavigationRequest::ReadyToCommitNavigation(bool is_error) {
+void NavigationRequest::ReadyToCommitNavigation(CommitPageType type) {
   EnterChildTraceEvent("ReadyToCommitNavigation", this);
 
   SetState(READY_TO_COMMIT);
+  committed_page_type_ = type;
   ready_to_commit_time_ = base::TimeTicks::Now();
   RestartCommitTimeout();
 
@@ -4186,7 +4188,7 @@
 
   // Record metrics for the time it takes to get to this state from the
   // beginning of the navigation.
-  if (!IsSameDocument() && !is_error) {
+  if (!IsSameDocument() && type == CommitPageType::kNonErrorPage) {
     is_same_process_ =
         render_frame_host_->GetProcess()->GetID() ==
         frame_tree_node_->current_frame_host()->GetProcess()->GetID();
@@ -4408,6 +4410,11 @@
   return state_ == DID_COMMIT_ERROR_PAGE;
 }
 
+bool NavigationRequest::IsCustomErrorPage() {
+  return state_ == DID_COMMIT_ERROR_PAGE &&
+         committed_page_type_ == CommitPageType::kCustomErrorPage;
+}
+
 net::HttpResponseInfo::ConnectionInfo NavigationRequest::GetConnectionInfo() {
   return response() ? response()->connection_info
                     : net::HttpResponseInfo::ConnectionInfo();
diff --git a/content/browser/frame_host/navigation_request.h b/content/browser/frame_host/navigation_request.h
index 2bf304e..e4a57cff 100644
--- a/content/browser/frame_host/navigation_request.h
+++ b/content/browser/frame_host/navigation_request.h
@@ -294,6 +294,7 @@
   bool IsSameDocument() override;
   bool HasCommitted() override;
   bool IsErrorPage() override;
+  bool IsCustomErrorPage() override;
   bool HasSubframeNavigationEntryCommitted() override;
   bool DidReplaceEntry() override;
   bool ShouldUpdateHistory() override;
@@ -979,7 +980,12 @@
 
   // Called when the navigation is ready to be committed. This will update the
   // |state_| and inform the delegate.
-  void ReadyToCommitNavigation(bool is_error);
+  enum class CommitPageType {
+    kNonErrorPage,
+    kErrorPage,
+    kCustomErrorPage,
+  };
+  void ReadyToCommitNavigation(CommitPageType type);
 
   // Called if READY_TO_COMMIT -> COMMIT state transition takes an unusually
   // long time.
@@ -1119,6 +1125,9 @@
   int bindings_;
   bool entry_overrides_ua_ = false;
 
+  // Indicates what type of error page is about to be committed, if any.
+  CommitPageType committed_page_type_ = CommitPageType::kNonErrorPage;
+
   // Set to true if SetIsOverridingUserAgent() is called.
   bool was_set_overriding_user_agent_called_ = false;
 
diff --git a/content/public/browser/navigation_handle.h b/content/public/browser/navigation_handle.h
index 8a1d8de..7facdef 100644
--- a/content/public/browser/navigation_handle.h
+++ b/content/public/browser/navigation_handle.h
@@ -230,6 +230,11 @@
   // GetNetErrorCode will be net::OK.
   virtual bool IsErrorPage() = 0;
 
+  // Indicates whether navigation committed a custom error page (i.e. an error
+  // page that provides its own HTML.) IsErrorPage is always true if this is
+  // true, but the inverse doesn't hold.
+  virtual bool IsCustomErrorPage() = 0;
+
   // Not all committed subframe navigations (i.e., !IsInMainFrame &&
   // HasCommitted) end up causing a change of the current NavigationEntry. For
   // example, some users of NavigationHandle may want to ignore the initial
diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc
index 2b8475b..ee2a32f 100644
--- a/content/public/test/browser_test_utils.cc
+++ b/content/public/test/browser_test_utils.cc
@@ -2937,7 +2937,8 @@
 void TestNavigationManager::DidFinishNavigation(NavigationHandle* handle) {
   if (handle != request_)
     return;
-  was_successful_ = handle->HasCommitted() && !handle->IsErrorPage();
+  was_committed_ = handle->HasCommitted();
+  was_successful_ = was_committed_ && !handle->IsErrorPage();
   current_state_ = NavigationState::FINISHED;
   navigation_paused_ = false;
   request_ = nullptr;
diff --git a/content/public/test/browser_test_utils.h b/content/public/test/browser_test_utils.h
index 83514d8..0bb59f7 100644
--- a/content/public/test/browser_test_utils.h
+++ b/content/public/test/browser_test_utils.h
@@ -1496,6 +1496,9 @@
   NavigationHandle* GetNavigationHandle();
 
   // Whether the navigation successfully committed.
+  bool was_committed() const { return was_committed_; }
+
+  // Whether the navigation successfully committed and was not an error page.
   bool was_successful() const { return was_successful_; }
 
   // Allows nestable tasks when running a message loop in the Wait* functions.
@@ -1541,6 +1544,7 @@
   bool navigation_paused_;
   NavigationState current_state_;
   NavigationState desired_state_;
+  bool was_committed_ = false;
   bool was_successful_ = false;
   base::OnceClosure quit_closure_;
   base::RunLoop::Type message_loop_type_ = base::RunLoop::Type::kDefault;
diff --git a/content/public/test/mock_navigation_handle.h b/content/public/test/mock_navigation_handle.h
index 7149f13..eec48d6 100644
--- a/content/public/test/mock_navigation_handle.h
+++ b/content/public/test/mock_navigation_handle.h
@@ -70,6 +70,7 @@
   }
   bool HasCommitted() override { return has_committed_; }
   bool IsErrorPage() override { return is_error_page_; }
+  bool IsCustomErrorPage() override { return false; }
   MOCK_METHOD0(HasSubframeNavigationEntryCommitted, bool());
   MOCK_METHOD0(DidReplaceEntry, bool());
   MOCK_METHOD0(ShouldUpdateHistory, bool());