text: // Copyright 2013 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 <set>
  
  #include "base/bind.h"
  #include "base/memory/scoped_ptr.h"
  #include "base/message_loop/message_loop.h"
  #include "base/path_service.h"
  #include "base/prefs/pref_service.h"
  #include "base/run_loop.h"
  #include "base/threading/thread_restrictions.h"
  #include "chrome/browser/browser_process.h"
  #include "chrome/browser/io_thread.h"
  #include "chrome/browser/net/dns_probe_test_util.h"
  #include "chrome/browser/net/net_error_tab_helper.h"
  #include "chrome/browser/net/url_request_mock_util.h"
  #include "chrome/browser/profiles/profile.h"
  #include "chrome/browser/ui/browser.h"
  #include "chrome/browser/ui/browser_commands.h"
  #include "chrome/browser/ui/tabs/tab_strip_model.h"
  #include "chrome/common/chrome_paths.h"
  #include "chrome/common/net/net_error_info.h"
  #include "chrome/common/pref_names.h"
  #include "chrome/test/base/in_process_browser_test.h"
  #include "chrome/test/base/ui_test_utils.h"
  #include "components/google/core/browser/google_util.h"
  #include "content/public/browser/browser_thread.h"
  #include "content/public/browser/web_contents.h"
  #include "content/public/test/browser_test_utils.h"
  #include "content/public/test/test_navigation_observer.h"
  #include "content/test/net/url_request_failed_job.h"
  #include "content/test/net/url_request_mock_http_job.h"
  #include "net/base/net_errors.h"
  #include "net/dns/dns_test_util.h"
  #include "net/url_request/url_request_filter.h"
  #include "net/url_request/url_request_interceptor.h"
  #include "net/url_request/url_request_job.h"
  
  using base::Bind;
  using base::Callback;
  using base::Closure;
  using base::ConstRef;
  using base::FilePath;
  using base::MessageLoop;
  using base::Unretained;
  using chrome_common_net::DnsProbeStatus;
  using content::BrowserThread;
  using content::URLRequestFailedJob;
  using content::URLRequestMockHTTPJob;
  using content::WebContents;
  using google_util::LinkDoctorBaseURL;
  using net::MockDnsClientRule;
  using net::NetworkDelegate;
  using net::URLRequest;
  using net::URLRequestFilter;
  using net::URLRequestInterceptor;
  using net::URLRequestJob;
  using ui_test_utils::NavigateToURL;
  using ui_test_utils::NavigateToURLBlockUntilNavigationsComplete;
  
  namespace chrome_browser_net {
  
  namespace {
  
  // Postable function to run a Closure on the UI thread.  Since
  // BrowserThread::PostTask returns a bool, it can't directly be posted to
  // another thread.
  void RunClosureOnUIThread(const base::Closure& closure) {
    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, closure);
  }
  
  // Wraps DnsProbeService and delays callbacks until someone calls
  // CallDelayedCallbacks.  This allows the DnsProbeBrowserTest to enforce a
  // stricter ordering of events.
  class DelayingDnsProbeService : public DnsProbeService {
   public:
    DelayingDnsProbeService() {}
  
    virtual ~DelayingDnsProbeService() {
      EXPECT_TRUE(delayed_probes_.empty());
    }
  
    virtual void ProbeDns(const ProbeCallback& callback) OVERRIDE {
      delayed_probes_.push_back(callback);
    }
  
    void StartDelayedProbes() {
      CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  
      std::vector<ProbeCallback> probes;
      probes.swap(delayed_probes_);
  
      for (std::vector<ProbeCallback>::const_iterator i = probes.begin();
           i != probes.end(); ++i) {
        DnsProbeService::ProbeDns(*i);
      }
    }
  
    int delayed_probe_count() const {
      CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
      return delayed_probes_.size();
    }
  
   private:
    std::vector<ProbeCallback> delayed_probes_;
  };
  
  FilePath GetMockLinkDoctorFilePath() {
    FilePath root_http;
    PathService::Get(chrome::DIR_TEST_DATA, &root_http);
    return root_http.AppendASCII("mock-link-doctor.json");
  }
  
  // A request that can be delayed until Resume() is called.  Can also run a
  // callback if destroyed without being resumed.  Resume can be called either
  // before or after a the request is started.
  class DelayableRequest {
   public:
    // Called by a DelayableRequest if it was set to be delayed, and has been
    // destroyed without Undelay being called.
    typedef base::Callback<void(DelayableRequest* request)> DestructionCallback;
  
    virtual void Resume() = 0;
  
   protected:
    virtual ~DelayableRequest() {}
  };
  
  class DelayableURLRequestFailedJob : public URLRequestFailedJob,
                                       public DelayableRequest {
   public:
    // |destruction_callback| is only called if a delayed request is destroyed
    // without being resumed.
    DelayableURLRequestFailedJob(net::URLRequest* request,
                                 net::NetworkDelegate* network_delegate,
                                 int net_error,
                                 bool should_delay,
                                 const DestructionCallback& destruction_callback)
        : URLRequestFailedJob(request, network_delegate, net_error),
          should_delay_(should_delay),
          start_delayed_(false),
          destruction_callback_(destruction_callback) {}
  
    virtual void Start() OVERRIDE {
      if (should_delay_) {
        DCHECK(!start_delayed_);
        start_delayed_ = true;
        return;
      }
      URLRequestFailedJob::Start();
    }
  
    virtual void Resume() OVERRIDE {
      DCHECK(should_delay_);
      should_delay_ = false;
      if (start_delayed_) {
        start_delayed_ = false;
        Start();
      }
    }
  
   private:
    virtual ~DelayableURLRequestFailedJob() {
      if (should_delay_)
        destruction_callback_.Run(this);
    }
  
    bool should_delay_;
    bool start_delayed_;
    const DestructionCallback destruction_callback_;
  };
  
  class DelayableURLRequestMockHTTPJob : public URLRequestMockHTTPJob,
                                         public DelayableRequest {
   public:
    DelayableURLRequestMockHTTPJob(
        net::URLRequest* request,
        net::NetworkDelegate* network_delegate,
        const base::FilePath& file_path,
        bool should_delay,
        const DestructionCallback& destruction_callback)
        : URLRequestMockHTTPJob(request, network_delegate, file_path),
          should_delay_(should_delay),
          start_delayed_(false),
          destruction_callback_(destruction_callback) {}
  
    virtual void Start() OVERRIDE {
      if (should_delay_) {
        DCHECK(!start_delayed_);
        start_delayed_ = true;
        return;
      }
      URLRequestMockHTTPJob::Start();
    }
  
    virtual void Resume() OVERRIDE {
      DCHECK(should_delay_);
      should_delay_ = false;
      if (start_delayed_) {
        start_delayed_ = false;
        Start();
      }
    }
  
   private:
    virtual ~DelayableURLRequestMockHTTPJob() {
      if (should_delay_)
        destruction_callback_.Run(this);
    }
  
    bool should_delay_;
    bool start_delayed_;
    const DestructionCallback destruction_callback_;
  };
  
  // Interceptor for navigation correction requests.  Can cause requests to
  // fail with an error, and/or delay a request until a test allows to continue.
  // Also can run a callback when a delayed request is cancelled.
  class BreakableCorrectionInterceptor : public URLRequestInterceptor {
   public:
    explicit BreakableCorrectionInterceptor(
        const FilePath& mock_corrections_file_path)
        : mock_corrections_file_path_(mock_corrections_file_path),
          net_error_(net::OK),
          delay_requests_(false),
          on_request_destroyed_callback_(
              base::Bind(&BreakableCorrectionInterceptor::OnRequestDestroyed,
                         base::Unretained(this))) {
    }
  
    virtual ~BreakableCorrectionInterceptor() {
      // All delayed requests should have been resumed or cancelled by this point.
      EXPECT_TRUE(delayed_requests_.empty());
    }
  
    virtual URLRequestJob* MaybeInterceptRequest(
        URLRequest* request,
        NetworkDelegate* network_delegate) const OVERRIDE {
      if (net_error_ != net::OK) {
        DelayableURLRequestFailedJob* job =
            new DelayableURLRequestFailedJob(
                request, network_delegate, net_error_, delay_requests_,
                on_request_destroyed_callback_);
        if (delay_requests_)
          delayed_requests_.insert(job);
        return job;
      } else {
        DelayableURLRequestMockHTTPJob* job =
            new DelayableURLRequestMockHTTPJob(
                request, network_delegate, mock_corrections_file_path_,
                delay_requests_, on_request_destroyed_callback_);
        if (delay_requests_)
          delayed_requests_.insert(job);
        return job;
      }
    }
  
    void set_net_error(int net_error) { net_error_ = net_error; }
  
    void SetDelayRequests(bool delay_requests) {
      delay_requests_ = delay_requests;
  
      // Resume all delayed requests if no longer delaying requests.
      if (!delay_requests) {
        while (!delayed_requests_.empty()) {
          DelayableRequest* request = *delayed_requests_.begin();
          delayed_requests_.erase(request);
          request->Resume();
        }
      }
    }
  
    // Runs |callback| once all delayed requests have been destroyed.  Does not
    // wait for delayed requests that have been resumed.
    void SetRequestDestructionCallback(const base::Closure& callback) {
      ASSERT_TRUE(delayed_request_destruction_callback_.is_null());
      if (delayed_requests_.empty()) {
        callback.Run();
        return;
      }
      delayed_request_destruction_callback_ = callback;
    }
  
    void OnRequestDestroyed(DelayableRequest* request) {
      ASSERT_EQ(1u, delayed_requests_.count(request));
      delayed_requests_.erase(request);
      if (delayed_requests_.empty() &&
          !delayed_request_destruction_callback_.is_null()) {
        delayed_request_destruction_callback_.Run();
        delayed_request_destruction_callback_.Reset();
      }
    }
  
   private:
    const FilePath mock_corrections_file_path_;
    int net_error_;
    bool delay_requests_;
  
    // Called when a request is destroyed.  Memeber variable because
    // MaybeCreateJob is "const", so calling base::Bind in that function does
    // not work well.
    const DelayableRequest::DestructionCallback on_request_destroyed_callback_;
  
    // Mutable is needed because MaybeCreateJob is const.
    mutable std::set<DelayableRequest*> delayed_requests_;
  
    base::Closure delayed_request_destruction_callback_;
  };
  
  class DnsProbeBrowserTestIOThreadHelper {
   public:
    DnsProbeBrowserTestIOThreadHelper();
  
    void SetUpOnIOThread(IOThread* io_thread);
    void CleanUpOnIOThreadAndDeleteHelper();
  
    void SetMockDnsClientRules(MockDnsClientRule::Result system_good_result,
                               MockDnsClientRule::Result public_good_result);
    void SetCorrectionServiceNetError(int net_error);
    void SetCorrectionServiceDelayRequests(bool delay_requests);
    void SetRequestDestructionCallback(const base::Closure& callback);
    void StartDelayedProbes(int expected_delayed_probe_count);
  
   private:
    IOThread* io_thread_;
    DnsProbeService* original_dns_probe_service_;
    DelayingDnsProbeService* delaying_dns_probe_service_;
    BreakableCorrectionInterceptor* interceptor_;
    FilePath mock_corrections_file_path_;
  };
  
  DnsProbeBrowserTestIOThreadHelper::DnsProbeBrowserTestIOThreadHelper()
      : io_thread_(NULL),
        original_dns_probe_service_(NULL),
        delaying_dns_probe_service_(NULL),
        interceptor_(NULL),
        mock_corrections_file_path_(GetMockLinkDoctorFilePath()) {}
  
  void DnsProbeBrowserTestIOThreadHelper::SetUpOnIOThread(IOThread* io_thread) {
    CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    CHECK(io_thread);
    CHECK(!io_thread_);
    CHECK(!original_dns_probe_service_);
    CHECK(!delaying_dns_probe_service_);
    CHECK(!interceptor_);
  
    io_thread_ = io_thread;
  
    delaying_dns_probe_service_ = new DelayingDnsProbeService();
  
    IOThread::Globals* globals = io_thread_->globals();
    original_dns_probe_service_ = globals->dns_probe_service.release();
    globals->dns_probe_service.reset(delaying_dns_probe_service_);
  
    URLRequestFailedJob::AddUrlHandler();
  
    interceptor_ =
        new BreakableCorrectionInterceptor(mock_corrections_file_path_);
    URLRequestFilter::GetInstance()->AddUrlInterceptor(
        LinkDoctorBaseURL(), scoped_ptr<URLRequestInterceptor>(interceptor_));
  }
  
  void DnsProbeBrowserTestIOThreadHelper::CleanUpOnIOThreadAndDeleteHelper() {
    CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  
    URLRequestFilter::GetInstance()->ClearHandlers();
  
    IOThread::Globals* globals = io_thread_->globals();
    scoped_ptr<DnsProbeService> delaying_dns_probe_service(
        globals->dns_probe_service.release());
    globals->dns_probe_service.reset(original_dns_probe_service_);
  
    CHECK_EQ(delaying_dns_probe_service_, delaying_dns_probe_service.get());
  
    delete this;
  }
  
  void DnsProbeBrowserTestIOThreadHelper::SetMockDnsClientRules(
      MockDnsClientRule::Result system_result,
      MockDnsClientRule::Result public_result) {
    CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  
    DnsProbeService* service = io_thread_->globals()->dns_probe_service.get();
    service->SetSystemClientForTesting(
        CreateMockDnsClientForProbes(system_result));
    service->SetPublicClientForTesting(
        CreateMockDnsClientForProbes(public_result));
  }
  
  void DnsProbeBrowserTestIOThreadHelper::SetCorrectionServiceNetError(
      int net_error) {
    CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  
    interceptor_->set_net_error(net_error);
  }
  
  void DnsProbeBrowserTestIOThreadHelper::SetCorrectionServiceDelayRequests(
      bool delay_requests) {
    CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  
    interceptor_->SetDelayRequests(delay_requests);
  }
  
  void DnsProbeBrowserTestIOThreadHelper::SetRequestDestructionCallback(
      const base::Closure& callback) {
    CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  
    interceptor_->SetRequestDestructionCallback(callback);
  }
  
  void DnsProbeBrowserTestIOThreadHelper::StartDelayedProbes(
      int expected_delayed_probe_count) {
    CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  
    CHECK(delaying_dns_probe_service_);
  
    int actual_delayed_probe_count =
        delaying_dns_probe_service_->delayed_probe_count();
    EXPECT_EQ(expected_delayed_probe_count, actual_delayed_probe_count);
  
    delaying_dns_probe_service_->StartDelayedProbes();
  }
  
  class DnsProbeBrowserTest : public InProcessBrowserTest {
   public:
    DnsProbeBrowserTest();
    virtual ~DnsProbeBrowserTest();
  
    virtual void SetUpOnMainThread() OVERRIDE;
    virtual void CleanUpOnMainThread() OVERRIDE;
  
   protected:
    // Sets the browser object that other methods apply to, and that has the
    // DnsProbeStatus messages of its currently active tab monitored.
    void SetActiveBrowser(Browser* browser);
  
    void SetCorrectionServiceBroken(bool broken);
    void SetCorrectionServiceDelayRequests(bool delay_requests);
    void WaitForDelayedRequestDestruction();
    void SetMockDnsClientRules(MockDnsClientRule::Result system_result,
                               MockDnsClientRule::Result public_result);
  
    // These functions are often used to wait for two navigations because two
    // pages are loaded when navigation corrections are enabled: a blank page, so
    // the user stops seeing the previous page, and then the error page, either
    // with navigation corrections or without them (If the request failed).
    void NavigateToDnsError(int num_navigations);
    void NavigateToOtherError(int num_navigations);
  
    void StartDelayedProbes(int expected_delayed_probe_count);
    DnsProbeStatus WaitForSentStatus();
    int pending_status_count() const { return dns_probe_status_queue_.size(); }
  
    std::string Title();
    bool PageContains(const std::string& expected);
  
    // Checks that the local error page is being displayed, without navigation
    // corrections, and with the specified status text.  The status text should be
    // either a network error or DNS probe status.
    void ExpectDisplayingLocalErrorPage(const std::string& status_text);
  
    // Checks that an error page with mock navigation corrections is being
    // displayed, with the specified status text. The status text should be either
    // a network error or DNS probe status.
    void ExpectDisplayingCorrections(const std::string& status_text);
  
   private:
    void OnDnsProbeStatusSent(DnsProbeStatus dns_probe_status);
  
    DnsProbeBrowserTestIOThreadHelper* helper_;
  
    // Browser that methods apply to.
    Browser* active_browser_;
    // Helper that current has its DnsProbeStatus messages monitored.
    NetErrorTabHelper* monitored_tab_helper_;
  
    bool awaiting_dns_probe_status_;
    // Queue of statuses received but not yet consumed by WaitForSentStatus().
    std::list<DnsProbeStatus> dns_probe_status_queue_;
  };
  
  DnsProbeBrowserTest::DnsProbeBrowserTest()
      : helper_(new DnsProbeBrowserTestIOThreadHelper()),
        active_browser_(NULL),
        monitored_tab_helper_(NULL),
        awaiting_dns_probe_status_(false) {
  }
  
  DnsProbeBrowserTest::~DnsProbeBrowserTest() {
    // No tests should have any unconsumed probe statuses.
    EXPECT_EQ(0, pending_status_count());
  }
  
  void DnsProbeBrowserTest::SetUpOnMainThread() {
    NetErrorTabHelper::set_state_for_testing(
        NetErrorTabHelper::TESTING_DEFAULT);
  
    browser()->profile()->GetPrefs()->SetBoolean(
        prefs::kAlternateErrorPagesEnabled, true);
  
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        Bind(&DnsProbeBrowserTestIOThreadHelper::SetUpOnIOThread,
             Unretained(helper_),
             g_browser_process->io_thread()));
  
    SetActiveBrowser(browser());
  }
  
  void DnsProbeBrowserTest::CleanUpOnMainThread() {
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        Bind(&DnsProbeBrowserTestIOThreadHelper::CleanUpOnIOThreadAndDeleteHelper,
             Unretained(helper_)));
  
    NetErrorTabHelper::set_state_for_testing(
        NetErrorTabHelper::TESTING_DEFAULT);
  }
  
  void DnsProbeBrowserTest::SetActiveBrowser(Browser* browser) {
    // If currently watching a NetErrorTabHelper, stop doing so before start
    // watching another.
    if (monitored_tab_helper_) {
      monitored_tab_helper_->set_dns_probe_status_snoop_callback_for_testing(
          NetErrorTabHelper::DnsProbeStatusSnoopCallback());
    }
    active_browser_ = browser;
    monitored_tab_helper_ = NetErrorTabHelper::FromWebContents(
        active_browser_->tab_strip_model()->GetActiveWebContents());
    monitored_tab_helper_->set_dns_probe_status_snoop_callback_for_testing(
        Bind(&DnsProbeBrowserTest::OnDnsProbeStatusSent, Unretained(this)));
  }
  
  void DnsProbeBrowserTest::SetCorrectionServiceBroken(bool broken) {
    int net_error = broken ? net::ERR_NAME_NOT_RESOLVED : net::OK;
  
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        Bind(&DnsProbeBrowserTestIOThreadHelper::SetCorrectionServiceNetError,
             Unretained(helper_),
             net_error));
  }
  
  void DnsProbeBrowserTest::SetCorrectionServiceDelayRequests(
      bool delay_requests) {
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        Bind(&DnsProbeBrowserTestIOThreadHelper::
                 SetCorrectionServiceDelayRequests,
             Unretained(helper_),
             delay_requests));
  }
  
  void DnsProbeBrowserTest::WaitForDelayedRequestDestruction() {
    base::RunLoop run_loop;
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        Bind(&DnsProbeBrowserTestIOThreadHelper::SetRequestDestructionCallback,
             Unretained(helper_),
             base::Bind(&RunClosureOnUIThread,
                        run_loop.QuitClosure())));
    run_loop.Run();
  }
  
  void DnsProbeBrowserTest::NavigateToDnsError(int num_navigations) {
    NavigateToURLBlockUntilNavigationsComplete(
        active_browser_,
        URLRequestFailedJob::GetMockHttpUrl(net::ERR_NAME_NOT_RESOLVED),
        num_navigations);
  }
  
  void DnsProbeBrowserTest::NavigateToOtherError(int num_navigations) {
    NavigateToURLBlockUntilNavigationsComplete(
        active_browser_,
        URLRequestFailedJob::GetMockHttpUrl(net::ERR_CONNECTION_REFUSED),
        num_navigations);
  }
  
  void DnsProbeBrowserTest::SetMockDnsClientRules(
      MockDnsClientRule::Result system_result,
      MockDnsClientRule::Result public_result) {
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        Bind(&DnsProbeBrowserTestIOThreadHelper::SetMockDnsClientRules,
             Unretained(helper_),
             system_result,
             public_result));
  }
  
  void DnsProbeBrowserTest::StartDelayedProbes(
      int expected_delayed_probe_count) {
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        Bind(&DnsProbeBrowserTestIOThreadHelper::StartDelayedProbes,
             Unretained(helper_),
             expected_delayed_probe_count));
  }
  
  DnsProbeStatus DnsProbeBrowserTest::WaitForSentStatus() {
    CHECK(!awaiting_dns_probe_status_);
    while (dns_probe_status_queue_.empty()) {
      awaiting_dns_probe_status_ = true;
      MessageLoop::current()->Run();
      awaiting_dns_probe_status_ = false;
    }
  
    CHECK(!dns_probe_status_queue_.empty());
    DnsProbeStatus status = dns_probe_status_queue_.front();
    dns_probe_status_queue_.pop_front();
    return status;
  }
  
  // Check title by roundtripping to renderer, to make sure any probe results
  // sent before this have been applied.
  std::string DnsProbeBrowserTest::Title() {
    std::string title;
  
    WebContents* contents =
        active_browser_->tab_strip_model()->GetActiveWebContents();
  
    bool rv = content::ExecuteScriptAndExtractString(
        contents,
        "domAutomationController.send(document.title);",
        &title);
    if (!rv)
      return "";
  
    return title;
  }
  
  // Check text by roundtripping to renderer, to make sure any probe results
  // sent before this have been applied.
  bool DnsProbeBrowserTest::PageContains(const std::string& expected) {
    std::string text_content;
  
    bool rv = content::ExecuteScriptAndExtractString(
        active_browser_->tab_strip_model()->GetActiveWebContents(),
        "domAutomationController.send(document.body.textContent);",
        &text_content);
    if (!rv)
      return false;
  
    return text_content.find(expected) != std::string::npos;
  }
  
  void DnsProbeBrowserTest::ExpectDisplayingLocalErrorPage(
      const std::string& status_text) {
    EXPECT_FALSE(PageContains("http://correction1/"));
    EXPECT_FALSE(PageContains("http://correction2/"));
    EXPECT_TRUE(PageContains(status_text));
  }
  
  void DnsProbeBrowserTest::ExpectDisplayingCorrections(
      const std::string& status_text) {
    EXPECT_TRUE(PageContains("http://correction1/"));
    EXPECT_TRUE(PageContains("http://correction2/"));
    EXPECT_TRUE(PageContains(status_text));
  }
  
  void DnsProbeBrowserTest::OnDnsProbeStatusSent(
      DnsProbeStatus dns_probe_status) {
    dns_probe_status_queue_.push_back(dns_probe_status);
    if (awaiting_dns_probe_status_)
      MessageLoop::current()->Quit();
  }
  
  // Make sure probes don't break non-DNS error pages when corrections load.
  IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, OtherErrorWithCorrectionsSuccess) {
    SetCorrectionServiceBroken(false);
  
    NavigateToOtherError(2);
    ExpectDisplayingCorrections("ERR_CONNECTION_REFUSED");
  }
  
  // Make sure probes don't break non-DNS error pages when corrections failed to
  // load.
  IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, OtherErrorWithCorrectionsFailure) {
    SetCorrectionServiceBroken(true);
  
    NavigateToOtherError(2);
    ExpectDisplayingLocalErrorPage("ERR_CONNECTION_REFUSED");
  }
  
  // Make sure probes don't break DNS error pages when corrections load.
  IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest,
                         NxdomainProbeResultWithWorkingCorrections) {
    SetCorrectionServiceBroken(false);
    SetMockDnsClientRules(MockDnsClientRule::OK, MockDnsClientRule::OK);
  
    NavigateToDnsError(2);
    ExpectDisplayingCorrections("ERR_NAME_NOT_RESOLVED");
  
    // One status for committing a blank page before the corrections, and one for
    // when the error page with corrections is committed.
    EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, WaitForSentStatus());
    EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, WaitForSentStatus());
    EXPECT_EQ(0, pending_status_count());
    ExpectDisplayingCorrections("ERR_NAME_NOT_RESOLVED");
  
    StartDelayedProbes(1);
  
    EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN,
              WaitForSentStatus());
    EXPECT_EQ(0, pending_status_count());
    ExpectDisplayingCorrections("ERR_NAME_NOT_RESOLVED");
  }
  
  // Make sure probes don't break corrections when probes complete before the
  // corrections load.
  IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest,
                         NxdomainProbeResultWithWorkingSlowCorrections) {
    SetCorrectionServiceBroken(false);
    SetCorrectionServiceDelayRequests(true);
    SetMockDnsClientRules(MockDnsClientRule::OK, MockDnsClientRule::OK);
  
    NavigateToDnsError(1);
    // A blank page should be displayed while the corrections are loaded.
    EXPECT_EQ("", Title());
  
    // A single probe should be triggered by the error page load, and it should
    // be ignored.
    EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, WaitForSentStatus());
    EXPECT_EQ(0, pending_status_count());
    EXPECT_EQ("", Title());
  
    StartDelayedProbes(1);
    EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN,
              WaitForSentStatus());
    EXPECT_EQ(0, pending_status_count());
    EXPECT_EQ("", Title());
  
    content::TestNavigationObserver observer(
        browser()->tab_strip_model()->GetActiveWebContents(), 1);
    // The corrections finish loading.
    SetCorrectionServiceDelayRequests(false);
    // Wait for it to commit.
    observer.Wait();
    ExpectDisplayingCorrections("ERR_NAME_NOT_RESOLVED");
  
    // Committing the corections page should trigger sending the probe result
    // again.
    EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN,
              WaitForSentStatus());
    ExpectDisplayingCorrections("ERR_NAME_NOT_RESOLVED");
  }
  
  // Make sure probes update DNS error page properly when they're supposed to.
  IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest,
                         NoInternetProbeResultWithBrokenCorrections) {
    SetCorrectionServiceBroken(true);
    SetMockDnsClientRules(MockDnsClientRule::TIMEOUT,
                          MockDnsClientRule::TIMEOUT);
  
    NavigateToDnsError(2);
  
    EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, WaitForSentStatus());
    EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, WaitForSentStatus());
  
    // Checking the page runs the RunLoop, so make sure nothing hairy happens.
    EXPECT_EQ(0, pending_status_count());
    ExpectDisplayingLocalErrorPage("DNS_PROBE_STARTED");
    EXPECT_EQ(0, pending_status_count());
  
    StartDelayedProbes(1);
  
    EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET,
              WaitForSentStatus());
  
    // Checking the page runs the RunLoop, so make sure nothing hairy happens.
    EXPECT_EQ(0, pending_status_count());
    ExpectDisplayingLocalErrorPage("DNS_PROBE_FINISHED_NO_INTERNET");
  }
  
  // Make sure probes don't break corrections when probes complete before the
  // corrections request returns an error.
  IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest,
                         NoInternetProbeResultWithSlowBrokenCorrections) {
    SetCorrectionServiceBroken(true);
    SetCorrectionServiceDelayRequests(true);
    SetMockDnsClientRules(MockDnsClientRule::TIMEOUT,
                          MockDnsClientRule::TIMEOUT);
  
    NavigateToDnsError(1);
    // A blank page should be displayed while the corrections load.
    EXPECT_EQ("", Title());
  
    // A single probe should be triggered by the error page load, and it should
    // be ignored.
    EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, WaitForSentStatus());
    EXPECT_EQ(0, pending_status_count());
    EXPECT_EQ("", Title());
  
    StartDelayedProbes(1);
    EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET,
              WaitForSentStatus());
    EXPECT_EQ("", Title());
    EXPECT_EQ(0, pending_status_count());
  
    content::TestNavigationObserver observer(
        browser()->tab_strip_model()->GetActiveWebContents(), 1);
    // The corrections request fails.
    SetCorrectionServiceDelayRequests(false);
    // Wait for the DNS error page to load instead.
    observer.Wait();
    // The page committing should result in sending the probe results again.
    EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET,
              WaitForSentStatus());
  
    EXPECT_EQ(0, pending_status_count());
    ExpectDisplayingLocalErrorPage("DNS_PROBE_FINISHED_NO_INTERNET");
  }
  
  // Double-check to make sure sync failures don't explode.
  IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, SyncFailureWithBrokenCorrections) {
    SetCorrectionServiceBroken(true);
    SetMockDnsClientRules(MockDnsClientRule::FAIL, MockDnsClientRule::FAIL);
  
    NavigateToDnsError(2);
  
    EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, WaitForSentStatus());
    EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, WaitForSentStatus());
  
    // Checking the page runs the RunLoop, so make sure nothing hairy happens.
    EXPECT_EQ(0, pending_status_count());
    ExpectDisplayingLocalErrorPage("DNS_PROBE_STARTED");
    EXPECT_EQ(0, pending_status_count());
  
    StartDelayedProbes(1);
  
    EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_INCONCLUSIVE,
              WaitForSentStatus());
  
    // Checking the page runs the RunLoop, so make sure nothing hairy happens.
    EXPECT_EQ(0, pending_status_count());
    ExpectDisplayingLocalErrorPage("ERR_NAME_NOT_RESOLVED");
    EXPECT_EQ(0, pending_status_count());
  }
  
  // Test that pressing the stop button cancels loading corrections.
  // TODO(mmenke):  Add a test for the cross process navigation case.
  // TODO(mmenke):  This test could flakily pass due to the timeout on downloading
  //                the corrections.  Disable that timeout for browser tests.
  IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, CorrectionsLoadStopped) {
    SetCorrectionServiceDelayRequests(true);
    SetCorrectionServiceBroken(true);
    SetMockDnsClientRules(MockDnsClientRule::TIMEOUT, MockDnsClientRule::TIMEOUT);
  
    NavigateToDnsError(1);
  
    EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, WaitForSentStatus());
    StartDelayedProbes(1);
    EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET,
              WaitForSentStatus());
  
    EXPECT_EQ("", Title());
    EXPECT_EQ(0, pending_status_count());
  
    chrome::Stop(browser());
    WaitForDelayedRequestDestruction();
  
    // End up displaying a blank page.
    EXPECT_EQ("", Title());
  }
  
  // Test that pressing the stop button cancels the load of corrections, and
  // receiving a probe result afterwards does not swap in a DNS error page.
  IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, CorrectionsLoadStoppedSlowProbe) {
    SetCorrectionServiceDelayRequests(true);
    SetCorrectionServiceBroken(true);
    SetMockDnsClientRules(MockDnsClientRule::TIMEOUT, MockDnsClientRule::TIMEOUT);
  
    NavigateToDnsError(1);
  
    EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, WaitForSentStatus());
  
    EXPECT_EQ("", Title());
    EXPECT_EQ(0, pending_status_count());
  
    chrome::Stop(browser());
    WaitForDelayedRequestDestruction();
  
    EXPECT_EQ("", Title());
    EXPECT_EQ(0, pending_status_count());
  
    StartDelayedProbes(1);
    EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET,
              WaitForSentStatus());
  
    EXPECT_EQ("", Title());
  }
  
  // Make sure probes don't run for subframe DNS errors.
  IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, NoProbeInSubframe) {
    SetCorrectionServiceBroken(false);
  
    const FilePath::CharType kIframeDnsErrorHtmlName[] =
        FILE_PATH_LITERAL("iframe_dns_error.html");
  
    NavigateToURL(
        browser(),
        URLRequestMockHTTPJob::GetMockUrl(FilePath(kIframeDnsErrorHtmlName)));
  
    // By the time NavigateToURL returns, the browser will have seen the failed
    // provisional load.  If a probe was started (or considered but not run),
    // then the NetErrorTabHelper would have sent a NetErrorInfo message.  Thus,
    // if one hasn't been sent by now, the NetErrorTabHelper has not (and won't)
    // start a probe for this DNS error.
    EXPECT_EQ(0, pending_status_count());
  }
  
  // Make sure browser sends NOT_RUN properly when probes are disabled.
  IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, ProbesDisabled) {
    // Disable probes (And corrections).
    browser()->profile()->GetPrefs()->SetBoolean(
        prefs::kAlternateErrorPagesEnabled, false);
  
    SetCorrectionServiceBroken(true);
    SetMockDnsClientRules(MockDnsClientRule::TIMEOUT, MockDnsClientRule::TIMEOUT);
  
    NavigateToDnsError(1);
  
    EXPECT_EQ(chrome_common_net::DNS_PROBE_NOT_RUN, WaitForSentStatus());
  
    // Checking the page runs the RunLoop, so make sure nothing hairy happens.
    EXPECT_EQ(0, pending_status_count());
    ExpectDisplayingLocalErrorPage("ERR_NAME_NOT_RESOLVED");
  }
  
  // Test the case that corrections are disabled, but DNS probes are enabled.
  // This is the case with Chromium builds.
  IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, CorrectionsDisabled) {
    // Disable corrections.
    browser()->profile()->GetPrefs()->SetBoolean(
        prefs::kAlternateErrorPagesEnabled, false);
    // Requests to the correction service should work if any are made, so the test
    // fails if that happens unexpectedly.
    SetCorrectionServiceBroken(false);
    // Normally disabling corrections disables DNS probes, so force DNS probes
    // to be enabled.
    NetErrorTabHelper::set_state_for_testing(
        NetErrorTabHelper::TESTING_FORCE_ENABLED);
  
    SetMockDnsClientRules(MockDnsClientRule::FAIL, MockDnsClientRule::FAIL);
  
    // Just one commit and one sent status, since corrections are disabled.
    NavigateToDnsError(1);
    EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, WaitForSentStatus());
  
    // Checking the page runs the RunLoop, so make sure nothing hairy happens.
    EXPECT_EQ(0, pending_status_count());
    ExpectDisplayingLocalErrorPage("DNS_PROBE_STARTED");
    EXPECT_EQ(0, pending_status_count());
  
    StartDelayedProbes(1);
  
    EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_INCONCLUSIVE,
              WaitForSentStatus());
    EXPECT_EQ(0, pending_status_count());
    ExpectDisplayingLocalErrorPage("ERR_NAME_NOT_RESOLVED");
  }
  
  // Test incognito mode.  Corrections should be disabled, but DNS probes are
  // still enabled.
  IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, Incognito) {
    // Requests to the correction service should work if any are made, so the test
    // will fail if one is requested unexpectedly.
    SetCorrectionServiceBroken(false);
  
    Browser* incognito = CreateIncognitoBrowser();
    SetActiveBrowser(incognito);
  
    SetMockDnsClientRules(MockDnsClientRule::FAIL, MockDnsClientRule::FAIL);
  
    // Just one commit and one sent status, since the corrections are disabled.
    NavigateToDnsError(1);
    EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, WaitForSentStatus());
  
    // Checking the page runs the RunLoop, so make sure nothing hairy happens.
    EXPECT_EQ(0, pending_status_count());
    ExpectDisplayingLocalErrorPage("DNS_PROBE_STARTED");
    EXPECT_EQ(0, pending_status_count());
  
    StartDelayedProbes(1);
  
    EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_INCONCLUSIVE,
              WaitForSentStatus());
    EXPECT_EQ(0, pending_status_count());
    ExpectDisplayingLocalErrorPage("ERR_NAME_NOT_RESOLVED");
  }
  
  }  // namespace
  
  }  // namespace chrome_browser_net
  