summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorttuttle@chromium.org <ttuttle@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-07-17 05:23:13 +0000
committerttuttle@chromium.org <ttuttle@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-07-17 05:23:13 +0000
commit2ea1efe9660ee70c8eefb98dde76caac1c918005 (patch)
treeb5b7e0ea619ae3df960f25d3c4ce23cbba335e2c
parent3793b5f264db5e0ce9950d0e28e8748e5b8c56ff (diff)
downloadchromium_src-2ea1efe9660ee70c8eefb98dde76caac1c918005.zip
chromium_src-2ea1efe9660ee70c8eefb98dde76caac1c918005.tar.gz
chromium_src-2ea1efe9660ee70c8eefb98dde76caac1c918005.tar.bz2
Display DNS probe results.
1. Modify the browser-side NetErrorTabHelper to send extra messages when it starts or declines to start a DNS probe. 2. Create a new error domain, "dnsprobe", with errors for "might run a DNS probe", "currently running a DNS probe", and all of the possible probe results. 3. Modify ChromeContentRendererClient to give the renderer-side NetErrorHelper a chance to choose the error strings before we call LocalizedError directly. 4. Catch DNS errors and provide the strings for the "might run a DNS probe" pseudo-error instead. 5. Add a function to neterror.html that lets us re-render the template with a new set of strings. 6. When we get a "probe started" message, replace the strings with those for the "currently running a DNS probe" pseudo-error. 7. When we get a "probe finished" message, replace the strings with those for the probe result. 8. When we get a "probe not run" message, replace the strings with those for the original error we received. BUG=156415 TEST=DnsProbeBrowserTest Review URL: https://chromiumcodereview.appspot.com/13270005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@211950 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/app/generated_resources.grd8
-rw-r--r--chrome/browser/net/dns_probe_browsertest.cc535
-rw-r--r--chrome/browser/net/dns_probe_job.cc236
-rw-r--r--chrome/browser/net/dns_probe_job.h55
-rw-r--r--chrome/browser/net/dns_probe_job_unittest.cc127
-rw-r--r--chrome/browser/net/dns_probe_runner.cc143
-rw-r--r--chrome/browser/net/dns_probe_runner.h85
-rw-r--r--chrome/browser/net/dns_probe_runner_unittest.cc95
-rw-r--r--chrome/browser/net/dns_probe_service.cc392
-rw-r--r--chrome/browser/net/dns_probe_service.h78
-rw-r--r--chrome/browser/net/dns_probe_service_unittest.cc239
-rw-r--r--chrome/browser/net/dns_probe_test_util.cc37
-rw-r--r--chrome/browser/net/dns_probe_test_util.h21
-rw-r--r--chrome/browser/net/net_error_tab_helper.cc166
-rw-r--r--chrome/browser/net/net_error_tab_helper.h66
-rw-r--r--chrome/browser/net/net_error_tab_helper_unittest.cc392
-rw-r--r--chrome/chrome_browser.gypi4
-rw-r--r--chrome/chrome_common.gypi4
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/chrome_tests_unit.gypi7
-rw-r--r--chrome/common/localized_error.cc59
-rw-r--r--chrome/common/net/net_error_info.cc48
-rw-r--r--chrome/common/net/net_error_info.h65
-rw-r--r--chrome/common/net/net_error_tracker.cc66
-rw-r--r--chrome/common/net/net_error_tracker.h66
-rw-r--r--chrome/common/net/net_error_tracker_unittest.cc98
-rw-r--r--chrome/common/render_messages.h2
-rw-r--r--chrome/renderer/chrome_content_renderer_client.cc14
-rw-r--r--chrome/renderer/net/net_error_helper.cc264
-rw-r--r--chrome/renderer/net/net_error_helper.h70
-rw-r--r--chrome/renderer/net/net_error_helper_unittest.cc412
-rw-r--r--chrome/renderer/resources/neterror.html6
32 files changed, 2535 insertions, 1326 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index fa61ffd..1ab910b 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -9711,6 +9711,11 @@ The following plug-in is unresponsive: <ph name="PLUGIN_NAME">$1
<ph name="PRODUCT_NAME">&lt;span jscontent="productName"&gt;&lt;/span&gt;<ex>Google Chrome</ex></ph>
won't use insecure connections in order to protect your privacy.
</message>
+ <message name="IDS_ERRORPAGES_SUMMARY_DNS_PROBE_RUNNING" desc="Summary in the error page when DNS resolution failed and we are running a probe to figure out why.">
+ Unable to look up
+ <ph name="HOST_NAME">&lt;strong jscontent="hostName"&gt;&lt;/strong&gt;<ex>www.whatever.com</ex></ph>.
+ Trying to diagnose the problem...
+ </message>
<message name="IDS_ERRORPAGES_ERROR_CODE" desc="At the bottom of error pages, a non-internationalized string or numeric code is displayed for debugging purposes">
Error code: <ph name="ERROR_NAME">$1<ex>ERR_FILE_NOT_FOUND</ex></ph>
@@ -9782,6 +9787,9 @@ The following plug-in is unresponsive: <ph name="PLUGIN_NAME">$1
Multiple distinct Location headers received. This is disallowed to protect
against HTTP response splitting attacks.
</message>
+ <message name="IDS_ERRORPAGES_DETAILS_DNS_PROBE_RUNNING" desc="The error message displayed when we are waiting to see whether we will run a DNS probe.">
+ Waiting for DNS probe.
+ </message>
<message name="IDS_ERRORPAGES_DETAILS_UNKNOWN" desc="The default error message displayed if we don't have a more specific error message.">
Unknown error.
</message>
diff --git a/chrome/browser/net/dns_probe_browsertest.cc b/chrome/browser/net/dns_probe_browsertest.cc
new file mode 100644
index 0000000..76d494a
--- /dev/null
+++ b/chrome/browser/net/dns_probe_browsertest.cc
@@ -0,0 +1,535 @@
+// 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 "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "base/path_service.h"
+#include "base/threading/thread_restrictions.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/google/google_util.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/ui/browser.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/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.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/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_job.h"
+#include "net/url_request/url_request_job_factory.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::URLRequestJob;
+using net::URLRequestJobFactory;
+using ui_test_utils::NavigateToURL;
+using ui_test_utils::NavigateToURLBlockUntilNavigationsComplete;
+
+namespace chrome_browser_net {
+
+namespace {
+
+// 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.html");
+}
+
+class BreakableLinkDoctorProtocolHandler
+ : public URLRequestJobFactory::ProtocolHandler {
+ public:
+ explicit BreakableLinkDoctorProtocolHandler(
+ const FilePath& mock_link_doctor_file_path)
+ : mock_link_doctor_file_path_(mock_link_doctor_file_path),
+ net_error_(net::OK) {}
+
+ virtual ~BreakableLinkDoctorProtocolHandler() {}
+
+ virtual URLRequestJob* MaybeCreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const OVERRIDE {
+ if (net_error_ != net::OK) {
+ return new URLRequestFailedJob(request, network_delegate, net_error_);
+ } else {
+ return new URLRequestMockHTTPJob(
+ request, network_delegate, mock_link_doctor_file_path_);
+ }
+ }
+
+ void set_net_error(int net_error) { net_error_ = net_error; }
+
+ private:
+ const FilePath mock_link_doctor_file_path_;
+ int net_error_;
+};
+
+class DnsProbeBrowserTestIOThreadHelper {
+ public:
+ DnsProbeBrowserTestIOThreadHelper();
+
+ void SetUpOnIOThread(IOThread* io_thread);
+ void CleanUpOnIOThreadAndDeleteHelper();
+
+ void SetMockDnsClientRules(MockDnsClientRule::Result system_good_result,
+ MockDnsClientRule::Result public_good_result);
+ void SetLinkDoctorNetError(int link_doctor_net_error);
+ void StartDelayedProbes(int expected_delayed_probe_count);
+
+ private:
+ IOThread* io_thread_;
+ DnsProbeService* original_dns_probe_service_;
+ DelayingDnsProbeService* delaying_dns_probe_service_;
+ BreakableLinkDoctorProtocolHandler* protocol_handler_;
+ FilePath mock_link_doctor_file_path_;
+};
+
+DnsProbeBrowserTestIOThreadHelper::DnsProbeBrowserTestIOThreadHelper()
+ : io_thread_(NULL),
+ original_dns_probe_service_(NULL),
+ delaying_dns_probe_service_(NULL),
+ protocol_handler_(NULL),
+ mock_link_doctor_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(!protocol_handler_);
+
+ 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();
+
+ scoped_ptr<URLRequestJobFactory::ProtocolHandler> protocol_handler(
+ new BreakableLinkDoctorProtocolHandler(mock_link_doctor_file_path_));
+ protocol_handler_ =
+ static_cast<BreakableLinkDoctorProtocolHandler*>(protocol_handler.get());
+ const GURL link_doctor_base_url = LinkDoctorBaseURL();
+ const std::string link_doctor_host = link_doctor_base_url.host();
+ URLRequestFilter::GetInstance()->AddHostnameProtocolHandler(
+ "http", link_doctor_host, protocol_handler.Pass());
+}
+
+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::SetLinkDoctorNetError(
+ int link_doctor_net_error) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ protocol_handler_->set_net_error(link_doctor_net_error);
+}
+
+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 void SetUpOnMainThread() OVERRIDE;
+ virtual void CleanUpOnMainThread() OVERRIDE;
+
+ protected:
+ void SetLinkDoctorBroken(bool broken);
+ void SetMockDnsClientRules(MockDnsClientRule::Result system_result,
+ MockDnsClientRule::Result public_result);
+ void NavigateToDnsError();
+ void NavigateToOtherError();
+
+ 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);
+
+ private:
+ void OnDnsProbeStatusSent(DnsProbeStatus dns_probe_status);
+
+ DnsProbeBrowserTestIOThreadHelper* 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()),
+ awaiting_dns_probe_status_(false) {
+}
+
+void DnsProbeBrowserTest::SetUpOnMainThread() {
+ NetErrorTabHelper::set_state_for_testing(
+ NetErrorTabHelper::TESTING_FORCE_ENABLED);
+
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ Bind(&DnsProbeBrowserTestIOThreadHelper::SetUpOnIOThread,
+ Unretained(helper_),
+ g_browser_process->io_thread()));
+
+ NetErrorTabHelper* tab_helper = NetErrorTabHelper::FromWebContents(
+ browser()->tab_strip_model()->GetActiveWebContents());
+ tab_helper->set_dns_probe_status_snoop_callback_for_testing(Bind(
+ &DnsProbeBrowserTest::OnDnsProbeStatusSent,
+ Unretained(this)));
+}
+
+void DnsProbeBrowserTest::CleanUpOnMainThread() {
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ Bind(&DnsProbeBrowserTestIOThreadHelper::CleanUpOnIOThreadAndDeleteHelper,
+ Unretained(helper_)));
+
+ NetErrorTabHelper::set_state_for_testing(
+ NetErrorTabHelper::TESTING_DEFAULT);
+}
+
+void DnsProbeBrowserTest::SetLinkDoctorBroken(bool broken) {
+ int net_error = broken ? net::ERR_NAME_NOT_RESOLVED : net::OK;
+
+ BrowserThread::PostTask(
+ BrowserThread::IO, FROM_HERE,
+ Bind(&DnsProbeBrowserTestIOThreadHelper::SetLinkDoctorNetError,
+ Unretained(helper_),
+ net_error));
+}
+
+// These two functions wait for two navigations because Link Doctor loads two
+// pages: a blank page, so the user stops seeing the previous page, and then
+// either the Link Doctor page or a regular error page. We want to wait for
+// the error page, so we wait for both loads to finish.
+
+void DnsProbeBrowserTest::NavigateToDnsError() {
+ NavigateToURLBlockUntilNavigationsComplete(
+ browser(),
+ URLRequestFailedJob::GetMockHttpUrl(net::ERR_NAME_NOT_RESOLVED),
+ 2);
+}
+
+void DnsProbeBrowserTest::NavigateToOtherError() {
+ NavigateToURLBlockUntilNavigationsComplete(
+ browser(),
+ URLRequestFailedJob::GetMockHttpUrl(net::ERR_CONNECTION_REFUSED),
+ 2);
+}
+
+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 =
+ 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(
+ 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::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 Link Doctor loads.
+IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, OtherErrorWithWorkingLinkDoctor) {
+ SetLinkDoctorBroken(false);
+
+ NavigateToOtherError();
+ EXPECT_EQ("Mock Link Doctor", Title());
+
+ EXPECT_EQ(0, pending_status_count());
+}
+
+// Make sure probes don't break non-DNS error pages when Link Doctor doesn't
+// load.
+IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, OtherErrorWithBrokenLinkDoctor) {
+ SetLinkDoctorBroken(true);
+
+ NavigateToOtherError();
+ EXPECT_TRUE(PageContains("CONNECTION_REFUSED"));
+
+ EXPECT_EQ(0, pending_status_count());
+}
+
+// Make sure probes don't break DNS error pages when Link doctor loads.
+IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest,
+ NxdomainProbeResultWithWorkingLinkDoctor) {
+ SetLinkDoctorBroken(false);
+ SetMockDnsClientRules(MockDnsClientRule::OK, MockDnsClientRule::OK);
+
+ NavigateToDnsError();
+ EXPECT_EQ("Mock Link Doctor", Title());
+
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, WaitForSentStatus());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, WaitForSentStatus());
+ EXPECT_EQ(0, pending_status_count());
+ EXPECT_EQ("Mock Link Doctor", Title());
+
+ StartDelayedProbes(1);
+
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN,
+ WaitForSentStatus());
+ EXPECT_EQ(0, pending_status_count());
+ EXPECT_EQ("Mock Link Doctor", Title());
+}
+
+// Make sure probes update DNS error page properly when they're supposed to.
+IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest,
+ NoInternetProbeResultWithBrokenLinkDoctor) {
+ SetLinkDoctorBroken(true);
+ SetMockDnsClientRules(MockDnsClientRule::TIMEOUT,
+ MockDnsClientRule::TIMEOUT);
+
+ NavigateToDnsError();
+
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, WaitForSentStatus());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, WaitForSentStatus());
+
+ // PageContains runs the RunLoop, so make sure nothing hairy happens.
+ EXPECT_EQ(0, pending_status_count());
+ EXPECT_TRUE(PageContains("DNS_PROBE_STARTED"));
+ EXPECT_EQ(0, pending_status_count());
+
+ StartDelayedProbes(1);
+
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET,
+ WaitForSentStatus());
+
+ // PageContains runs the RunLoop, so make sure nothing hairy happens.
+ EXPECT_EQ(0, pending_status_count());
+ EXPECT_TRUE(PageContains("DNS_PROBE_FINISHED_NO_INTERNET"));
+ EXPECT_EQ(0, pending_status_count());
+}
+
+// Double-check to make sure sync failures don't explode.
+IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, SyncFailureWithBrokenLinkDoctor) {
+ SetLinkDoctorBroken(true);
+ SetMockDnsClientRules(MockDnsClientRule::FAIL_SYNC,
+ MockDnsClientRule::FAIL_SYNC);
+
+ NavigateToDnsError();
+
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, WaitForSentStatus());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, WaitForSentStatus());
+
+ // PageContains runs the RunLoop, so make sure nothing hairy happens.
+ EXPECT_EQ(0, pending_status_count());
+ EXPECT_TRUE(PageContains("DNS_PROBE_STARTED"));
+ EXPECT_EQ(0, pending_status_count());
+
+ StartDelayedProbes(1);
+
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_INCONCLUSIVE,
+ WaitForSentStatus());
+
+ // PageContains runs the RunLoop, so make sure nothing hairy happens.
+ EXPECT_EQ(0, pending_status_count());
+ EXPECT_TRUE(PageContains("NAME_NOT_RESOLVED"));
+ EXPECT_EQ(0, pending_status_count());
+}
+
+// Make sure probes don't run for subframe DNS errors.
+IN_PROC_BROWSER_TEST_F(DnsProbeBrowserTest, NoProbeInSubframe) {
+ SetLinkDoctorBroken(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) {
+ NetErrorTabHelper::set_state_for_testing(
+ NetErrorTabHelper::TESTING_FORCE_DISABLED);
+
+ SetLinkDoctorBroken(true);
+ SetMockDnsClientRules(MockDnsClientRule::TIMEOUT,
+ MockDnsClientRule::TIMEOUT);
+
+ NavigateToDnsError();
+
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_NOT_RUN, WaitForSentStatus());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_NOT_RUN, WaitForSentStatus());
+
+ // PageContains runs the RunLoop, so make sure nothing hairy happens.
+ EXPECT_EQ(0, pending_status_count());
+ EXPECT_TRUE(PageContains("NAME_NOT_RESOLVED"));
+ EXPECT_EQ(0, pending_status_count());
+}
+
+} // namespace
+
+} // namespace chrome_browser_net
diff --git a/chrome/browser/net/dns_probe_job.cc b/chrome/browser/net/dns_probe_job.cc
deleted file mode 100644
index f51f450..0000000
--- a/chrome/browser/net/dns_probe_job.cc
+++ /dev/null
@@ -1,236 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/net/dns_probe_job.h"
-
-#include "base/compiler_specific.h"
-#include "base/memory/scoped_ptr.h"
-#include "base/memory/weak_ptr.h"
-#include "base/message_loop.h"
-#include "base/time/time.h"
-#include "net/base/address_list.h"
-#include "net/base/net_errors.h"
-#include "net/base/net_log.h"
-#include "net/dns/dns_client.h"
-#include "net/dns/dns_protocol.h"
-#include "net/dns/dns_response.h"
-#include "net/dns/dns_transaction.h"
-
-using base::TimeDelta;
-using net::AddressList;
-using net::BoundNetLog;
-using net::DnsClient;
-using net::DnsResponse;
-using net::DnsTransaction;
-using net::NetLog;
-
-namespace chrome_browser_net {
-
-namespace {
-
-// Returns true if the given net_error indicates that we received a response
-// from the DNS server containing an error, or false if the given net_error
-// indicates that we never received a response.
-bool DidReceiveDnsResponse(int net_error) {
- switch (net_error) {
- case net::ERR_NAME_NOT_RESOLVED: // NXDOMAIN maps to this.
- case net::ERR_DNS_MALFORMED_RESPONSE:
- case net::ERR_DNS_SERVER_REQUIRES_TCP:
- case net::ERR_DNS_SERVER_FAILED:
- case net::ERR_DNS_SORT_ERROR: // Can only happen if the server responds.
- return true;
- default:
- return false;
- }
-}
-
-class DnsProbeJobImpl : public DnsProbeJob {
- public:
- DnsProbeJobImpl(scoped_ptr<DnsClient> dns_client,
- const DnsProbeJob::CallbackType& callback,
- NetLog* net_log);
- virtual ~DnsProbeJobImpl();
-
- private:
- enum QueryResult {
- QUERY_UNKNOWN,
- QUERY_CORRECT,
- QUERY_INCORRECT,
- QUERY_DNS_ERROR,
- QUERY_NET_ERROR,
- };
-
- void Start();
-
- scoped_ptr<DnsTransaction> CreateTransaction(
- const std::string& hostname);
- void StartTransaction(DnsTransaction* transaction);
- // Checks that |net_error| is OK, |response| parses, and has at least one
- // address.
- QueryResult EvaluateGoodResponse(int net_error, const DnsResponse* response);
- // Checks that |net_error| is OK, |response| parses but has no addresses.
- QueryResult EvaluateBadResponse(int net_error, const DnsResponse* response);
- DnsProbeJob::Result EvaluateQueryResults();
- void OnTransactionComplete(DnsTransaction* transaction,
- int net_error,
- const DnsResponse* response);
-
- BoundNetLog bound_net_log_;
- scoped_ptr<DnsClient> dns_client_;
- const DnsProbeJob::CallbackType callback_;
- scoped_ptr<DnsTransaction> good_transaction_;
- scoped_ptr<DnsTransaction> bad_transaction_;
- bool good_running_;
- bool bad_running_;
- QueryResult good_result_;
- QueryResult bad_result_;
- base::WeakPtrFactory<DnsProbeJobImpl> weak_factory_;
-
- DISALLOW_COPY_AND_ASSIGN(DnsProbeJobImpl);
-};
-
-DnsProbeJobImpl::DnsProbeJobImpl(scoped_ptr<DnsClient> dns_client,
- const DnsProbeJob::CallbackType& callback,
- NetLog* net_log)
- : bound_net_log_(
- BoundNetLog::Make(net_log, NetLog::SOURCE_DNS_PROBER)),
- dns_client_(dns_client.Pass()),
- callback_(callback),
- good_running_(false),
- bad_running_(false),
- good_result_(QUERY_UNKNOWN),
- bad_result_(QUERY_UNKNOWN),
- weak_factory_(this) {
- DCHECK(dns_client_.get());
- DCHECK(dns_client_->GetConfig());
-
- base::MessageLoop::current()->PostTask(
- FROM_HERE,
- base::Bind(&DnsProbeJobImpl::Start,
- weak_factory_.GetWeakPtr()));
-}
-
-DnsProbeJobImpl::~DnsProbeJobImpl() {
-}
-
-void DnsProbeJobImpl::Start() {
- // TODO(ttuttle): Pick a good random hostname for the bad case.
- // Consider running transactions in series?
- good_transaction_ = CreateTransaction("google.com");
- bad_transaction_ = CreateTransaction("thishostname.doesnotresolve");
-
- // StartTransaction may call the callback synchrononously, so set these
- // before we call it.
- good_running_ = true;
- bad_running_ = true;
-
- // TODO(ttuttle): Log probe started.
-
- StartTransaction(good_transaction_.get());
- StartTransaction(bad_transaction_.get());
-}
-
-scoped_ptr<DnsTransaction> DnsProbeJobImpl::CreateTransaction(
- const std::string& hostname) {
- return dns_client_->GetTransactionFactory()->CreateTransaction(
- hostname,
- net::dns_protocol::kTypeA,
- base::Bind(&DnsProbeJobImpl::OnTransactionComplete,
- base::Unretained(this)),
- bound_net_log_);
-}
-
-void DnsProbeJobImpl::StartTransaction(DnsTransaction* transaction) {
- int rv = transaction->Start();
- if (rv != net::ERR_IO_PENDING) {
- // TODO(ttuttle): Make sure this counts as unreachable, not error.
- OnTransactionComplete(transaction, rv, NULL);
- }
-
- // TODO(ttuttle): Log transaction started.
-}
-
-DnsProbeJobImpl::QueryResult DnsProbeJobImpl::EvaluateGoodResponse(
- int net_error,
- const DnsResponse* response) {
- if (net_error != net::OK)
- return DidReceiveDnsResponse(net_error) ? QUERY_DNS_ERROR : QUERY_NET_ERROR;
-
- AddressList addr_list;
- TimeDelta ttl;
- DnsResponse::Result result = response->ParseToAddressList(&addr_list, &ttl);
-
- if (result != DnsResponse::DNS_PARSE_OK)
- return QUERY_DNS_ERROR;
-
- if (addr_list.empty())
- return QUERY_INCORRECT;
-
- return QUERY_CORRECT;
-}
-
-DnsProbeJobImpl::QueryResult DnsProbeJobImpl::EvaluateBadResponse(
- int net_error,
- const DnsResponse* response) {
- if (net_error == net::ERR_NAME_NOT_RESOLVED) // NXDOMAIN maps to this
- return QUERY_CORRECT;
-
- if (net_error != net::OK)
- return DidReceiveDnsResponse(net_error) ? QUERY_DNS_ERROR : QUERY_NET_ERROR;
-
- return QUERY_INCORRECT;
-}
-
-DnsProbeJob::Result DnsProbeJobImpl::EvaluateQueryResults() {
- if (good_result_ == QUERY_NET_ERROR || bad_result_ == QUERY_NET_ERROR)
- return SERVERS_UNREACHABLE;
-
- if (good_result_ == QUERY_DNS_ERROR || bad_result_ == QUERY_DNS_ERROR)
- return SERVERS_FAILING;
-
- // Ignore incorrect responses to known-bad query to avoid flagging domain
- // helpers.
- if (good_result_ == QUERY_INCORRECT)
- return SERVERS_INCORRECT;
-
- return SERVERS_CORRECT;
-}
-
-void DnsProbeJobImpl::OnTransactionComplete(DnsTransaction* transaction,
- int net_error,
- const DnsResponse* response) {
- if (transaction == good_transaction_.get()) {
- DCHECK(good_running_);
- DCHECK_EQ(QUERY_UNKNOWN, good_result_);
- good_result_ = EvaluateGoodResponse(net_error, response);
- good_running_ = false;
- } else if (transaction == bad_transaction_.get()) {
- DCHECK(bad_running_);
- DCHECK_EQ(QUERY_UNKNOWN, bad_result_);
- bad_result_ = EvaluateBadResponse(net_error, response);
- bad_running_ = false;
- } else {
- NOTREACHED();
- return;
- }
-
- if (good_running_ || bad_running_)
- return;
-
- callback_.Run(this, EvaluateQueryResults());
-
- // TODO(ttuttle): Log probe finished.
-}
-
-} // namespace
-
-scoped_ptr<DnsProbeJob> DnsProbeJob::CreateJob(
- scoped_ptr<DnsClient> dns_client,
- const CallbackType& callback,
- NetLog* net_log) {
- return scoped_ptr<DnsProbeJob>(
- new DnsProbeJobImpl(dns_client.Pass(), callback, net_log));
-}
-
-} // namespace chrome_browser_net
diff --git a/chrome/browser/net/dns_probe_job.h b/chrome/browser/net/dns_probe_job.h
deleted file mode 100644
index 6572de1..0000000
--- a/chrome/browser/net/dns_probe_job.h
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_NET_DNS_PROBE_JOB_H_
-#define CHROME_BROWSER_NET_DNS_PROBE_JOB_H_
-
-#include "base/basictypes.h"
-#include "base/bind.h"
-#include "base/memory/scoped_ptr.h"
-
-namespace net {
-class DnsClient;
-class NetLog;
-}
-
-namespace chrome_browser_net {
-
-// A job to probe the health of a DNS configuration.
-class DnsProbeJob {
- public:
- enum Result {
- SERVERS_UNKNOWN,
- SERVERS_CORRECT, // Server responds with correct answers.
- SERVERS_INCORRECT, // Server responds with success but incorrect answer.
- // TODO(ttuttle): Do we want an "unreliable" result, for e.g. servers that
- // lie about NXDOMAIN?
- SERVERS_FAILING, // Server responds with errors.
- SERVERS_UNREACHABLE, // Server doesn't respond (or never got our packets)
- MAX_RESULT
- };
- typedef base::Callback<void(DnsProbeJob* job, Result result)> CallbackType;
-
- virtual ~DnsProbeJob() { }
-
- // Creates and starts a probe job.
- //
- // |dns_client| should be a DnsClient with the DnsConfig already set.
- // |callback| will be called when the probe finishes, which may happen
- // before the constructor returns (for example, if we can't create the DNS
- // transactions).
- static scoped_ptr<DnsProbeJob> CreateJob(
- scoped_ptr<net::DnsClient> dns_client,
- const CallbackType& callback,
- net::NetLog* net_log);
-
- protected:
- DnsProbeJob() { }
-
- DISALLOW_COPY_AND_ASSIGN(DnsProbeJob);
-};
-
-} // namespace chrome_browser_net
-
-#endif // CHROME_BROWSER_NET_DNS_PROBE_JOB_H_
diff --git a/chrome/browser/net/dns_probe_job_unittest.cc b/chrome/browser/net/dns_probe_job_unittest.cc
deleted file mode 100644
index d1779b1..0000000
--- a/chrome/browser/net/dns_probe_job_unittest.cc
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/net/dns_probe_job.h"
-
-#include "base/basictypes.h"
-#include "base/message_loop.h"
-#include "base/run_loop.h"
-#include "net/base/net_log.h"
-#include "net/dns/dns_client.h"
-#include "net/dns/dns_config_service.h"
-#include "net/dns/dns_protocol.h"
-#include "net/dns/dns_test_util.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-using net::DnsClient;
-using net::DnsConfig;
-using net::IPAddressNumber;
-using net::IPEndPoint;
-using net::ParseIPLiteralToNumber;
-using net::MockDnsClientRule;
-using net::MockDnsClientRuleList;
-using net::NetLog;
-
-namespace chrome_browser_net {
-
-namespace {
-
-class DnsProbeJobTest : public testing::Test {
- public:
- void RunProbe(MockDnsClientRule::Result expected_good_result,
- MockDnsClientRule::Result expected_bad_result);
-
- protected:
- void OnProbeFinished(DnsProbeJob* job, DnsProbeJob::Result result);
-
- bool callback_called_;
- DnsProbeJob::Result callback_result_;
-};
-
-// Runs a probe and waits for the callback. |good_result| and |bad_result|
-// are the result of the good and bad transactions that the DnsProbeJob will
-// receive.
-void DnsProbeJobTest::RunProbe(MockDnsClientRule::Result good_result,
- MockDnsClientRule::Result bad_result) {
- DnsConfig config;
- config.nameservers.clear();
- IPAddressNumber dns_ip;
- ParseIPLiteralToNumber("192.168.1.1", &dns_ip);
- const uint16 kDnsPort = net::dns_protocol::kDefaultPort;
- config.nameservers.push_back(IPEndPoint(dns_ip, kDnsPort));
-
- const uint16 kTypeA = net::dns_protocol::kTypeA;
- MockDnsClientRuleList rules;
- rules.push_back(MockDnsClientRule("google.com", kTypeA, good_result));
- rules.push_back(MockDnsClientRule(std::string(), kTypeA, bad_result));
-
- scoped_ptr<DnsClient> dns_client = CreateMockDnsClient(config, rules);
- dns_client->SetConfig(config);
-
- NetLog* net_log = NULL;
- DnsProbeJob::CallbackType callback = base::Bind(
- &DnsProbeJobTest::OnProbeFinished,
- base::Unretained(this));
-
- // Need to set these before creating job, because it can call the callback
- // synchronously in the constructor if both transactions fail to start.
- callback_called_ = false;
- callback_result_ = DnsProbeJob::SERVERS_UNKNOWN;
-
- // DnsProbeJob needs somewhere to post the callback.
- scoped_ptr<base::MessageLoop> message_loop_(new base::MessageLoopForIO());
-
- scoped_ptr<DnsProbeJob> job(
- DnsProbeJob::CreateJob(dns_client.Pass(), callback, net_log));
-
- // Force callback to run.
- base::RunLoop run_loop;
- run_loop.RunUntilIdle();
-}
-
-void DnsProbeJobTest::OnProbeFinished(DnsProbeJob* job,
- DnsProbeJob::Result result) {
- EXPECT_FALSE(callback_called_);
-
- callback_called_ = true;
- callback_result_ = result;
-}
-
-struct TestCase {
- MockDnsClientRule::Result good_result;
- MockDnsClientRule::Result bad_result;
- DnsProbeJob::Result expected_probe_result;
-};
-
-TEST_F(DnsProbeJobTest, Test) {
- static const TestCase kTestCases[] = {
- { MockDnsClientRule::OK,
- MockDnsClientRule::EMPTY,
- DnsProbeJob::SERVERS_CORRECT },
- { MockDnsClientRule::EMPTY,
- MockDnsClientRule::EMPTY,
- DnsProbeJob::SERVERS_INCORRECT },
- // TODO(ttuttle): Test that triggers QUERY_DNS_ERROR.
- // (Need to add another mock behavior to MockDnsClient.)
- { MockDnsClientRule::FAIL_ASYNC,
- MockDnsClientRule::FAIL_ASYNC,
- DnsProbeJob::SERVERS_FAILING },
- { MockDnsClientRule::FAIL_SYNC,
- MockDnsClientRule::FAIL_SYNC,
- DnsProbeJob::SERVERS_FAILING },
- { MockDnsClientRule::TIMEOUT,
- MockDnsClientRule::TIMEOUT,
- DnsProbeJob::SERVERS_UNREACHABLE },
- };
- for (size_t i = 0; i < arraysize(kTestCases); i++) {
- const TestCase* test_case = &kTestCases[i];
- RunProbe(test_case->good_result, test_case->bad_result);
- EXPECT_TRUE(callback_called_);
- EXPECT_EQ(test_case->expected_probe_result, callback_result_);
- }
-}
-
-} // namespace
-
-} // namespace chrome_browser_net
diff --git a/chrome/browser/net/dns_probe_runner.cc b/chrome/browser/net/dns_probe_runner.cc
new file mode 100644
index 0000000..d2b3a5d7
--- /dev/null
+++ b/chrome/browser/net/dns_probe_runner.cc
@@ -0,0 +1,143 @@
+// 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 "chrome/browser/net/dns_probe_runner.h"
+
+#include "base/bind.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/base/address_list.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+#include "net/base/network_change_notifier.h"
+#include "net/dns/dns_client.h"
+#include "net/dns/dns_protocol.h"
+#include "net/dns/dns_response.h"
+#include "net/dns/dns_transaction.h"
+
+using base::TimeDelta;
+using content::BrowserThread;
+using net::AddressList;
+using net::BoundNetLog;
+using net::DnsClient;
+using net::DnsResponse;
+using net::DnsTransaction;
+using net::IPAddressNumber;
+using net::IPEndPoint;
+using net::NetLog;
+using net::NetworkChangeNotifier;
+using net::ParseIPLiteralToNumber;
+
+namespace chrome_browser_net {
+
+const char* DnsProbeRunner::kKnownGoodHostname = "google.com";
+
+namespace {
+
+DnsProbeRunner::Result EvaluateResponse(
+ int net_error,
+ const DnsResponse* response) {
+ switch (net_error) {
+ case net::OK:
+ break;
+
+ // ERR_NAME_NOT_RESOLVED maps to NXDOMAIN, which means the server is working
+ // but gave us a wrong answer.
+ case net::ERR_NAME_NOT_RESOLVED:
+ return DnsProbeRunner::INCORRECT;
+
+ // These results mean we heard *something* from the DNS server, but it was
+ // unsuccessful (SERVFAIL) or malformed.
+ case net::ERR_DNS_MALFORMED_RESPONSE:
+ case net::ERR_DNS_SERVER_REQUIRES_TCP: // Shouldn't happen; DnsTransaction
+ // will retry with TCP.
+ case net::ERR_DNS_SERVER_FAILED:
+ case net::ERR_DNS_SORT_ERROR: // Can only happen if the server responds.
+ return DnsProbeRunner::FAILING;
+
+ // Any other error means we never reached the DNS server in the first place.
+ case net::ERR_DNS_TIMED_OUT:
+ default:
+ // Something else happened, probably at a network level.
+ return DnsProbeRunner::UNREACHABLE;
+ }
+
+ AddressList addr_list;
+ TimeDelta ttl;
+ DnsResponse::Result result = response->ParseToAddressList(&addr_list, &ttl);
+
+ if (result != DnsResponse::DNS_PARSE_OK) {
+ return DnsProbeRunner::FAILING;
+ }
+
+ if (addr_list.empty()) {
+ return DnsProbeRunner::INCORRECT;
+ }
+
+ return DnsProbeRunner::CORRECT;
+}
+
+} // namespace
+
+DnsProbeRunner::DnsProbeRunner() : weak_factory_(this), result_(UNKNOWN) {}
+
+DnsProbeRunner::~DnsProbeRunner() {}
+
+void DnsProbeRunner::SetClient(scoped_ptr<net::DnsClient> client) {
+ client_ = client.Pass();
+}
+
+void DnsProbeRunner::RunProbe(const base::Closure& callback) {
+ DCHECK(!callback.is_null());
+ DCHECK(client_.get());
+ DCHECK(callback_.is_null());
+ DCHECK(!transaction_.get());
+
+ callback_ = callback;
+ transaction_ = client_->GetTransactionFactory()->CreateTransaction(
+ kKnownGoodHostname,
+ net::dns_protocol::kTypeA,
+ base::Bind(&DnsProbeRunner::OnTransactionComplete,
+ weak_factory_.GetWeakPtr()),
+ BoundNetLog());
+
+ int rv = transaction_->Start();
+ if (rv != net::ERR_IO_PENDING)
+ OnTransactionComplete(transaction_.get(), rv, NULL);
+}
+
+bool DnsProbeRunner::IsRunning() const {
+ return !callback_.is_null();
+}
+
+void DnsProbeRunner::OnTransactionComplete(
+ DnsTransaction* transaction,
+ int net_error,
+ const DnsResponse* response) {
+ DCHECK(!callback_.is_null());
+ DCHECK(transaction_.get());
+ DCHECK_EQ(transaction_.get(), transaction);
+
+ result_ = EvaluateResponse(net_error, response);
+ transaction_.reset();
+
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&DnsProbeRunner::CallCallback,
+ weak_factory_.GetWeakPtr()));
+}
+
+void DnsProbeRunner::CallCallback() {
+ DCHECK(!callback_.is_null());
+ DCHECK(!transaction_.get());
+
+ // Clear callback in case it starts a new probe immediately.
+ const base::Closure callback = callback_;
+ callback_.Reset();
+ callback.Run();
+}
+
+} // namespace chrome_browser_net
diff --git a/chrome/browser/net/dns_probe_runner.h b/chrome/browser/net/dns_probe_runner.h
new file mode 100644
index 0000000..3f294c2
--- /dev/null
+++ b/chrome/browser/net/dns_probe_runner.h
@@ -0,0 +1,85 @@
+// 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.
+
+#ifndef CHROME_BROWSER_NET_DNS_PROBE_RUNNER_H_
+#define CHROME_BROWSER_NET_DNS_PROBE_RUNNER_H_
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+
+namespace net {
+class DnsClient;
+class DnsResponse;
+class DnsTransaction;
+}
+
+namespace chrome_browser_net {
+
+// Runs DNS probes using a single DnsClient and evaluates the responses.
+// (Currently requests A records for google.com and expects at least one IP
+// address in the response.)
+// Used by DnsProbeService to probe the system and public DNS configurations.
+class DnsProbeRunner {
+ public:
+ static const char* kKnownGoodHostname;
+
+ // Used in histograms; add new entries at the bottom, and don't remove any.
+ enum Result {
+ UNKNOWN,
+ CORRECT, // Response contains at least one A record.
+ INCORRECT, // Response claimed success but included no A records.
+ FAILING, // Response included an error or was malformed.
+ UNREACHABLE // No response received (timeout, network unreachable, etc.).
+ };
+
+ DnsProbeRunner();
+ ~DnsProbeRunner();
+
+ // Sets the DnsClient that will be used for DNS probes sent by this runner.
+ // Must be called before RunProbe; can be called repeatedly, including during
+ // a probe. It will not affect an in-flight probe, if one is running.
+ void SetClient(scoped_ptr<net::DnsClient> client);
+
+ // Starts a probe using the client specified with SetClient, which must have
+ // been called before RunProbe. |callback| will be called asynchronously
+ // when the result is ready, even if it is ready synchronously. Must not
+ // be called again until the callback is called, but may be called during the
+ // callback.
+ void RunProbe(const base::Closure& callback);
+
+ // Returns true if a probe is running. Guaranteed to return true after
+ // RunProbe returns, and false during and after the callback.
+ bool IsRunning() const;
+
+ // Returns the result of the last probe.
+ Result result() const { return result_; }
+
+ private:
+ void OnTransactionComplete(net::DnsTransaction* transaction,
+ int net_error,
+ const net::DnsResponse* response);
+ void CallCallback();
+
+ base::WeakPtrFactory<DnsProbeRunner> weak_factory_;
+
+ scoped_ptr<net::DnsClient> client_;
+
+ // The callback passed to |RunProbe|. Cleared right before calling the
+ // callback.
+ base::Closure callback_;
+
+ // The transaction started in |RunProbe| for the DNS probe. Reset once the
+ // results have been examined.
+ scoped_ptr<net::DnsTransaction> transaction_;
+
+ Result result_;
+
+ DISALLOW_COPY_AND_ASSIGN(DnsProbeRunner);
+};
+
+} // namespace chrome_browser_net
+
+#endif // CHROME_BROWSER_NET_DNS_PROBE_RUNNER_H_
diff --git a/chrome/browser/net/dns_probe_runner_unittest.cc b/chrome/browser/net/dns_probe_runner_unittest.cc
new file mode 100644
index 0000000..5519da2
--- /dev/null
+++ b/chrome/browser/net/dns_probe_runner_unittest.cc
@@ -0,0 +1,95 @@
+// 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 "base/bind.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop.h"
+#include "base/run_loop.h"
+#include "chrome/browser/net/dns_probe_runner.h"
+#include "chrome/browser/net/dns_probe_test_util.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "net/dns/dns_client.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::MessageLoopForIO;
+using base::RunLoop;
+using content::TestBrowserThreadBundle;
+using net::MockDnsClientRule;
+
+namespace chrome_browser_net {
+
+namespace {
+
+class TestDnsProbeRunnerCallback {
+ public:
+ TestDnsProbeRunnerCallback()
+ : callback_(base::Bind(&TestDnsProbeRunnerCallback::OnCalled,
+ base::Unretained(this))),
+ called_(false) {}
+
+ const base::Closure& callback() const { return callback_; }
+ bool called() const { return called_; }
+
+ private:
+ void OnCalled() {
+ EXPECT_FALSE(called_);
+ called_ = true;
+ }
+
+ base::Closure callback_;
+ bool called_;
+};
+
+class DnsProbeRunnerTest : public testing::Test {
+ protected:
+ void RunTest(MockDnsClientRule::Result query_result,
+ DnsProbeRunner::Result expected_probe_result);
+
+ TestBrowserThreadBundle bundle_;
+ DnsProbeRunner runner_;
+};
+
+void DnsProbeRunnerTest::RunTest(
+ MockDnsClientRule::Result query_result,
+ DnsProbeRunner::Result expected_probe_result) {
+ TestDnsProbeRunnerCallback callback;
+
+ runner_.SetClient(CreateMockDnsClientForProbes(query_result));
+ runner_.RunProbe(callback.callback());
+ EXPECT_TRUE(runner_.IsRunning());
+
+ RunLoop().RunUntilIdle();
+ EXPECT_FALSE(runner_.IsRunning());
+ EXPECT_TRUE(callback.called());
+ EXPECT_EQ(expected_probe_result, runner_.result());
+}
+
+TEST_F(DnsProbeRunnerTest, Probe_OK) {
+ RunTest(MockDnsClientRule::OK, DnsProbeRunner::CORRECT);
+}
+
+TEST_F(DnsProbeRunnerTest, Probe_EMPTY) {
+ RunTest(MockDnsClientRule::EMPTY, DnsProbeRunner::INCORRECT);
+}
+
+TEST_F(DnsProbeRunnerTest, Probe_TIMEOUT) {
+ RunTest(MockDnsClientRule::TIMEOUT, DnsProbeRunner::UNREACHABLE);
+}
+
+TEST_F(DnsProbeRunnerTest, Probe_FAIL_ASYNC) {
+ RunTest(MockDnsClientRule::FAIL_ASYNC, DnsProbeRunner::INCORRECT);
+}
+
+TEST_F(DnsProbeRunnerTest, Probe_FAIL_SYNC) {
+ RunTest(MockDnsClientRule::FAIL_SYNC, DnsProbeRunner::INCORRECT);
+}
+
+TEST_F(DnsProbeRunnerTest, TwoProbes) {
+ RunTest(MockDnsClientRule::OK, DnsProbeRunner::CORRECT);
+ RunTest(MockDnsClientRule::EMPTY, DnsProbeRunner::INCORRECT);
+}
+
+} // namespace
+
+} // namespace chrome_browser_net
diff --git a/chrome/browser/net/dns_probe_service.cc b/chrome/browser/net/dns_probe_service.cc
index 156313f..36e28af 100644
--- a/chrome/browser/net/dns_probe_service.cc
+++ b/chrome/browser/net/dns_probe_service.cc
@@ -7,8 +7,6 @@
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_number_conversions.h"
-#include "chrome/browser/net/dns_probe_job.h"
-#include "chrome/common/net/net_error_info.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_util.h"
#include "net/dns/dns_client.h"
@@ -17,7 +15,7 @@
using base::FieldTrialList;
using base::StringToInt;
-using chrome_common_net::DnsProbeResult;
+using chrome_common_net::DnsProbeStatus;
using net::DnsClient;
using net::DnsConfig;
using net::IPAddressNumber;
@@ -36,8 +34,8 @@ const int kMaxResultAgeMs = 5000;
// The public DNS servers used by the DnsProbeService to verify internet
// connectivity.
-const char kPublicDnsPrimary[] = "8.8.8.8";
-const char kPublicDnsSecondary[] = "8.8.4.4";
+const char kGooglePublicDns1[] = "8.8.8.8";
+const char kGooglePublicDns2[] = "8.8.4.4";
IPEndPoint MakeDnsEndPoint(const std::string& dns_ip_literal) {
IPAddressNumber dns_ip_number;
@@ -46,299 +44,217 @@ IPEndPoint MakeDnsEndPoint(const std::string& dns_ip_literal) {
return IPEndPoint(dns_ip_number, net::dns_protocol::kDefaultPort);
}
-const int kAttemptsUseDefault = -1;
-
-const char kAttemptsFieldTrialName[] = "DnsProbe-Attempts";
+DnsProbeStatus EvaluateResults(DnsProbeRunner::Result system_result,
+ DnsProbeRunner::Result public_result) {
+ // If the system DNS is working, assume the domain doesn't exist.
+ if (system_result == DnsProbeRunner::CORRECT)
+ return chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN;
-int GetAttemptsFromFieldTrial() {
- std::string group = FieldTrialList::FindFullName(kAttemptsFieldTrialName);
- if (group == "" || group == "default")
- return kAttemptsUseDefault;
+ // If the system DNS is not working but another public server is, assume the
+ // DNS config is bad (or perhaps the DNS servers are down or broken).
+ if (public_result == DnsProbeRunner::CORRECT)
+ return chrome_common_net::DNS_PROBE_FINISHED_BAD_CONFIG;
- int attempts;
- if (!StringToInt(group, &attempts))
- return kAttemptsUseDefault;
+ // If the system DNS is not working and another public server is unreachable,
+ // assume the internet connection is down (note that system DNS may be a
+ // router on the LAN, so it may be reachable but returning errors.)
+ if (public_result == DnsProbeRunner::UNREACHABLE)
+ return chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET;
- return attempts;
+ // Otherwise: the system DNS is not working and another public server is
+ // responding but with errors or incorrect results. This is an awkward case;
+ // an invasive captive portal or a restrictive firewall may be intercepting
+ // or rewriting DNS traffic, or the public server may itself be failing or
+ // down.
+ return chrome_common_net::DNS_PROBE_FINISHED_INCONCLUSIVE;
}
-bool IsLocalhost(const IPAddressNumber& ip) {
- return (ip.size() == net::kIPv4AddressSize)
- && (ip[0] == 127) && (ip[1] == 0) && (ip[2] == 0) && (ip[3] == 1);
-}
+void HistogramProbe(DnsProbeStatus status, base::TimeDelta elapsed) {
+ DCHECK(chrome_common_net::DnsProbeStatusIsFinished(status));
+
+ int result = status - chrome_common_net::DNS_PROBE_FINISHED_INCONCLUSIVE;
+
+ const int kMaxResult = chrome_common_net::DNS_PROBE_MAX -
+ chrome_common_net::DNS_PROBE_FINISHED_INCONCLUSIVE;
-// The maximum number of nameservers counted in histograms.
-const int kNameserverCountMax = 10;
+ UMA_HISTOGRAM_ENUMERATION("DnsProbe.Status", result, kMaxResult);
+ UMA_HISTOGRAM_MEDIUM_TIMES("DnsProbe.Elapsed", elapsed);
+
+ if (NetworkChangeNotifier::IsOffline()) {
+ UMA_HISTOGRAM_ENUMERATION("DnsProbe.Status_NcnOffline",
+ result, kMaxResult);
+ UMA_HISTOGRAM_MEDIUM_TIMES("DnsProbe.Elapsed_NcnOffline", elapsed);
+ } else {
+ UMA_HISTOGRAM_ENUMERATION("DnsProbe.Status_NcnOnline",
+ result, kMaxResult);
+ UMA_HISTOGRAM_MEDIUM_TIMES("DnsProbe.Elapsed_NcnOnline", elapsed);
+ }
+
+ switch (status) {
+ case chrome_common_net::DNS_PROBE_FINISHED_INCONCLUSIVE:
+ UMA_HISTOGRAM_MEDIUM_TIMES("DnsProbe.Elapsed_Unknown",
+ elapsed);
+ break;
+ case chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET:
+ UMA_HISTOGRAM_MEDIUM_TIMES("DnsProbe.Elapsed_NoInternet",
+ elapsed);
+ break;
+ case chrome_common_net::DNS_PROBE_FINISHED_BAD_CONFIG:
+ UMA_HISTOGRAM_MEDIUM_TIMES("DnsProbe.Elapsed_BadConfig",
+ elapsed);
+ break;
+ case chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN:
+ UMA_HISTOGRAM_MEDIUM_TIMES("DnsProbe.Elapsed_Nxdomain",
+ elapsed);
+ break;
+
+ // These aren't actually results.
+ case chrome_common_net::DNS_PROBE_POSSIBLE:
+ case chrome_common_net::DNS_PROBE_NOT_RUN:
+ case chrome_common_net::DNS_PROBE_STARTED:
+ case chrome_common_net::DNS_PROBE_MAX:
+ NOTREACHED();
+ break;
+ }
+}
} // namespace
DnsProbeService::DnsProbeService()
- : system_result_(DnsProbeJob::SERVERS_UNKNOWN),
- public_result_(DnsProbeJob::SERVERS_UNKNOWN),
- state_(STATE_NO_RESULTS),
- result_(chrome_common_net::DNS_PROBE_UNKNOWN),
- dns_attempts_(GetAttemptsFromFieldTrial()) {
- NetworkChangeNotifier::AddIPAddressObserver(this);
+ : state_(STATE_NO_RESULT) {
+ NetworkChangeNotifier::AddDNSObserver(this);
+ SetSystemClientToCurrentConfig();
+ SetPublicClientToGooglePublicDns();
}
DnsProbeService::~DnsProbeService() {
- NetworkChangeNotifier::RemoveIPAddressObserver(this);
+ NetworkChangeNotifier::RemoveDNSObserver(this);
}
-void DnsProbeService::ProbeDns(const DnsProbeService::CallbackType& callback) {
- callbacks_.push_back(callback);
+void DnsProbeService::ProbeDns(const DnsProbeService::ProbeCallback& callback) {
+ pending_callbacks_.push_back(callback);
- if (state_ == STATE_RESULTS_CACHED && ResultsExpired())
- ExpireResults();
+ if (CachedResultIsExpired())
+ ClearCachedResult();
switch (state_) {
- case STATE_NO_RESULTS:
- StartProbes();
- break;
- case STATE_RESULTS_CACHED:
- CallCallbacks();
- break;
- case STATE_PROBE_RUNNING:
- // do nothing; probe is already running, and will call the callback
- break;
+ case STATE_NO_RESULT:
+ StartProbes();
+ break;
+ case STATE_RESULT_CACHED:
+ CallCallbacks();
+ break;
+ case STATE_PROBE_RUNNING:
+ // Do nothing; probe is already running, and will call the callback.
+ break;
}
}
-scoped_ptr<DnsProbeJob> DnsProbeService::CreateSystemProbeJob(
- const DnsProbeJob::CallbackType& job_callback) {
- DnsConfig system_config;
- GetSystemDnsConfig(&system_config);
- return CreateProbeJob(system_config, job_callback);
+void DnsProbeService::OnDNSChanged() {
+ ClearCachedResult();
+ SetSystemClientToCurrentConfig();
}
-scoped_ptr<DnsProbeJob> DnsProbeService::CreatePublicProbeJob(
- const DnsProbeJob::CallbackType& job_callback) {
- DnsConfig public_config;
- GetPublicDnsConfig(&public_config);
- return CreateProbeJob(public_config, job_callback);
+void DnsProbeService::SetSystemClientForTesting(
+ scoped_ptr<DnsClient> system_client) {
+ system_runner_.SetClient(system_client.Pass());
}
-void DnsProbeService::OnIPAddressChanged() {
- if (state_ == STATE_RESULTS_CACHED)
- ExpireResults();
+void DnsProbeService::SetPublicClientForTesting(
+ scoped_ptr<DnsClient> public_client) {
+ public_runner_.SetClient(public_client.Pass());
}
-void DnsProbeService::ExpireResults() {
- DCHECK_EQ(STATE_RESULTS_CACHED, state_);
-
- state_ = STATE_NO_RESULTS;
- result_ = chrome_common_net::DNS_PROBE_UNKNOWN;
+void DnsProbeService::ClearCachedResultForTesting() {
+ ClearCachedResult();
}
-void DnsProbeService::StartProbes() {
- DCHECK_NE(STATE_PROBE_RUNNING, state_);
- DCHECK(!system_job_.get());
- DCHECK(!public_job_.get());
-
- DnsProbeJob::CallbackType job_callback =
- base::Bind(&DnsProbeService::OnProbeJobComplete,
- base::Unretained(this));
-
- // TODO(ttuttle): Do we want to keep explicit flags for "job done"?
- // Or maybe DnsProbeJob should have a "finished" flag?
- system_result_ = DnsProbeJob::SERVERS_UNKNOWN;
- public_result_ = DnsProbeJob::SERVERS_UNKNOWN;
-
- system_job_ = CreateSystemProbeJob(job_callback);
- public_job_ = CreatePublicProbeJob(job_callback);
-
- // If we can't create one or both jobs, fail the probe immediately.
- if (!system_job_.get() || !public_job_.get()) {
- system_job_.reset();
- public_job_.reset();
- state_ = STATE_RESULTS_CACHED;
- // TODO(ttuttle): Should this be BAD_CONFIG? Currently I think it only
- // happens when the system DnsConfig has no servers.
- result_ = chrome_common_net::DNS_PROBE_UNKNOWN;
- CallCallbacks();
- return;
- }
+void DnsProbeService::SetSystemClientToCurrentConfig() {
+ DnsConfig system_config;
+ NetworkChangeNotifier::GetDnsConfig(&system_config);
+ system_config.search.clear();
+ system_config.attempts = 1;
+ system_config.randomize_ports = false;
- state_ = STATE_PROBE_RUNNING;
- probe_start_time_ = base::Time::Now();
-}
+ scoped_ptr<DnsClient> system_client(DnsClient::CreateClient(NULL));
+ system_client->SetConfig(system_config);
-void DnsProbeService::OnProbesComplete() {
- DCHECK_EQ(STATE_PROBE_RUNNING, state_);
+ system_runner_.SetClient(system_client.Pass());
+}
- state_ = STATE_RESULTS_CACHED;
- result_ = EvaluateResults();
+void DnsProbeService::SetPublicClientToGooglePublicDns() {
+ DnsConfig public_config;
+ public_config.nameservers.push_back(MakeDnsEndPoint(kGooglePublicDns1));
+ public_config.nameservers.push_back(MakeDnsEndPoint(kGooglePublicDns2));
+ public_config.attempts = 1;
+ public_config.randomize_ports = false;
- HistogramProbes();
+ scoped_ptr<DnsClient> public_client(DnsClient::CreateClient(NULL));
+ public_client->SetConfig(public_config);
- CallCallbacks();
+ public_runner_.SetClient(public_client.Pass());
}
-void DnsProbeService::HistogramProbes() const {
- const DnsProbeResult kMaxResult = chrome_common_net::DNS_PROBE_MAX;
-
- DCHECK_EQ(STATE_RESULTS_CACHED, state_);
- DCHECK_NE(kMaxResult, result_);
-
- base::TimeDelta elapsed = base::Time::Now() - probe_start_time_;
+void DnsProbeService::StartProbes() {
+ DCHECK_EQ(STATE_NO_RESULT, state_);
- UMA_HISTOGRAM_ENUMERATION("DnsProbe.Probe.Result", result_, kMaxResult);
- UMA_HISTOGRAM_MEDIUM_TIMES("DnsProbe.Probe.Elapsed", elapsed);
+ DCHECK(!system_runner_.IsRunning());
+ DCHECK(!public_runner_.IsRunning());
- if (NetworkChangeNotifier::IsOffline()) {
- UMA_HISTOGRAM_ENUMERATION("DnsProbe.Probe.NcnOffline.Result",
- result_, kMaxResult);
- UMA_HISTOGRAM_MEDIUM_TIMES("DnsProbe.Probe.NcnOffline.Elapsed", elapsed);
- } else {
- UMA_HISTOGRAM_ENUMERATION("DnsProbe.Probe.NcnOnline.Result",
- result_, kMaxResult);
- UMA_HISTOGRAM_MEDIUM_TIMES("DnsProbe.Probe.NcnOnline.Elapsed", elapsed);
- }
+ const base::Closure callback = base::Bind(&DnsProbeService::OnProbeComplete,
+ base::Unretained(this));
+ system_runner_.RunProbe(callback);
+ public_runner_.RunProbe(callback);
+ probe_start_time_ = base::Time::Now();
+ state_ = STATE_PROBE_RUNNING;
- switch (result_) {
- case chrome_common_net::DNS_PROBE_UNKNOWN:
- UMA_HISTOGRAM_MEDIUM_TIMES("DnsProbe.Probe.ResultUnknown.Elapsed",
- elapsed);
- break;
- case chrome_common_net::DNS_PROBE_NO_INTERNET:
- UMA_HISTOGRAM_MEDIUM_TIMES("DnsProbe.Probe.ResultNoInternet.Elapsed",
- elapsed);
- break;
- case chrome_common_net::DNS_PROBE_BAD_CONFIG:
- UMA_HISTOGRAM_MEDIUM_TIMES("DnsProbe.Probe.ResultBadConfig.Elapsed",
- elapsed);
-
- // Histogram some extra data to see why BAD_CONFIG is happening.
- UMA_HISTOGRAM_ENUMERATION(
- "DnsProbe.Probe.ResultBadConfig.SystemJobResult",
- system_result_,
- DnsProbeJob::MAX_RESULT);
- UMA_HISTOGRAM_CUSTOM_COUNTS(
- "DnsProbe.Probe.ResultBadConfig.SystemNameserverCount",
- system_nameserver_count_,
- 0, kNameserverCountMax, kNameserverCountMax + 1);
- UMA_HISTOGRAM_BOOLEAN(
- "DnsProbe.Probe.ResultBadConfig.SystemIsLocalhost",
- system_is_localhost_);
- break;
- case chrome_common_net::DNS_PROBE_NXDOMAIN:
- UMA_HISTOGRAM_MEDIUM_TIMES("DnsProbe.Probe.ResultNxdomain.Elapsed",
- elapsed);
- break;
- case chrome_common_net::DNS_PROBE_MAX:
- NOTREACHED();
- break;
- }
+ DCHECK(system_runner_.IsRunning());
+ DCHECK(public_runner_.IsRunning());
}
-DnsProbeResult DnsProbeService::EvaluateResults() const {
- DCHECK_NE(DnsProbeJob::SERVERS_UNKNOWN, system_result_);
- DCHECK_NE(DnsProbeJob::SERVERS_UNKNOWN, public_result_);
+void DnsProbeService::OnProbeComplete() {
+ DCHECK_EQ(STATE_PROBE_RUNNING, state_);
- // If the system DNS is working, assume the domain doesn't exist.
- if (system_result_ == DnsProbeJob::SERVERS_CORRECT)
- return chrome_common_net::DNS_PROBE_NXDOMAIN;
+ if (system_runner_.IsRunning() || public_runner_.IsRunning())
+ return;
- // If the system DNS is not working but another public server is, assume the
- // DNS config is bad (or perhaps the DNS servers are down or broken).
- if (public_result_ == DnsProbeJob::SERVERS_CORRECT)
- return chrome_common_net::DNS_PROBE_BAD_CONFIG;
+ cached_result_ = EvaluateResults(system_runner_.result(),
+ public_runner_.result());
+ state_ = STATE_RESULT_CACHED;
- // If the system DNS is not working and another public server is unreachable,
- // assume the internet connection is down (note that system DNS may be a
- // router on the LAN, so it may be reachable but returning errors.)
- if (public_result_ == DnsProbeJob::SERVERS_UNREACHABLE)
- return chrome_common_net::DNS_PROBE_NO_INTERNET;
+ HistogramProbe(cached_result_, base::Time::Now() - probe_start_time_);
- // Otherwise: the system DNS is not working and another public server is
- // responding but with errors or incorrect results. This is an awkward case;
- // an invasive captive portal or a restrictive firewall may be intercepting
- // or rewriting DNS traffic, or the public server may itself be failing or
- // down.
- return chrome_common_net::DNS_PROBE_UNKNOWN;
+ CallCallbacks();
}
void DnsProbeService::CallCallbacks() {
- DCHECK_EQ(STATE_RESULTS_CACHED, state_);
- DCHECK(!callbacks_.empty());
+ DCHECK_EQ(STATE_RESULT_CACHED, state_);
+ DCHECK(chrome_common_net::DnsProbeStatusIsFinished(cached_result_));
+ DCHECK(!pending_callbacks_.empty());
- std::vector<CallbackType> callbacks = callbacks_;
- callbacks_.clear();
+ std::vector<ProbeCallback> callbacks;
+ callbacks.swap(pending_callbacks_);
- for (std::vector<CallbackType>::const_iterator i = callbacks.begin();
+ for (std::vector<ProbeCallback>::const_iterator i = callbacks.begin();
i != callbacks.end(); ++i) {
- i->Run(result_);
+ i->Run(cached_result_);
}
}
-scoped_ptr<DnsProbeJob> DnsProbeService::CreateProbeJob(
- const DnsConfig& dns_config,
- const DnsProbeJob::CallbackType& job_callback) {
- if (!dns_config.IsValid())
- return scoped_ptr<DnsProbeJob>();
-
- scoped_ptr<DnsClient> dns_client(DnsClient::CreateClient(NULL));
- dns_client->SetConfig(dns_config);
- return DnsProbeJob::CreateJob(dns_client.Pass(), job_callback, NULL);
-}
-
-void DnsProbeService::OnProbeJobComplete(DnsProbeJob* job,
- DnsProbeJob::Result result) {
- DCHECK_EQ(STATE_PROBE_RUNNING, state_);
-
- if (job == system_job_.get()) {
- system_result_ = result;
- system_job_.reset();
- } else if (job == public_job_.get()) {
- public_result_ = result;
- public_job_.reset();
- } else {
- NOTREACHED();
- return;
- }
-
- if (system_result_ != DnsProbeJob::SERVERS_UNKNOWN &&
- public_result_ != DnsProbeJob::SERVERS_UNKNOWN) {
- OnProbesComplete();
+void DnsProbeService::ClearCachedResult() {
+ if (state_ == STATE_RESULT_CACHED) {
+ state_ = STATE_NO_RESULT;
+ cached_result_ = chrome_common_net::DNS_PROBE_MAX;
}
}
-void DnsProbeService::GetSystemDnsConfig(DnsConfig* config) {
- NetworkChangeNotifier::GetDnsConfig(config);
-
- // DNS probes don't need or want the suffix search list populated
- config->search.clear();
-
- if (dns_attempts_ != kAttemptsUseDefault)
- config->attempts = dns_attempts_;
-
- // Take notes in case the config turns out to be bad, so we can histogram
- // some useful data.
- system_nameserver_count_ = config->nameservers.size();
- system_is_localhost_ = (system_nameserver_count_ == 1)
- && IsLocalhost(config->nameservers[0].address());
-
- // Disable port randomization.
- config->randomize_ports = false;
-}
-
-void DnsProbeService::GetPublicDnsConfig(DnsConfig* config) {
- *config = DnsConfig();
-
- config->nameservers.push_back(MakeDnsEndPoint(kPublicDnsPrimary));
- config->nameservers.push_back(MakeDnsEndPoint(kPublicDnsSecondary));
-
- if (dns_attempts_ != kAttemptsUseDefault)
- config->attempts = dns_attempts_;
-
- // Disable port randomization.
- config->randomize_ports = false;
-}
+bool DnsProbeService::CachedResultIsExpired() const {
+ if (state_ != STATE_RESULT_CACHED)
+ return false;
-bool DnsProbeService::ResultsExpired() {
const base::TimeDelta kMaxResultAge =
base::TimeDelta::FromMilliseconds(kMaxResultAgeMs);
return base::Time::Now() - probe_start_time_ > kMaxResultAge;
}
-} // namespace chrome_browser_net
+} // namespace chrome_browser_net
diff --git a/chrome/browser/net/dns_probe_service.h b/chrome/browser/net/dns_probe_service.h
index a906d75..d1df98e 100644
--- a/chrome/browser/net/dns_probe_service.h
+++ b/chrome/browser/net/dns_probe_service.h
@@ -8,78 +8,70 @@
#include <vector>
#include "base/basictypes.h"
+#include "base/bind.h"
#include "base/memory/scoped_ptr.h"
#include "base/time/time.h"
-#include "chrome/browser/net/dns_probe_job.h"
+#include "chrome/browser/net/dns_probe_runner.h"
#include "chrome/common/net/net_error_info.h"
#include "net/base/network_change_notifier.h"
namespace net {
+class DnsClient;
struct DnsConfig;
}
namespace chrome_browser_net {
-class DnsProbeService : public net::NetworkChangeNotifier::IPAddressObserver {
+// Probes the system and public DNS servers to determine the (probable) cause
+// of a recent DNS-related page load error. Coalesces multiple probe requests
+// (perhaps from multiple tabs) and caches the results.
+//
+// Uses a single DNS attempt per config, and doesn't randomize source ports.
+class DnsProbeService : public net::NetworkChangeNotifier::DNSObserver {
public:
- typedef base::Callback<void(chrome_common_net::DnsProbeResult result)>
- CallbackType;
+ typedef base::Callback<void(chrome_common_net::DnsProbeStatus result)>
+ ProbeCallback;
DnsProbeService();
virtual ~DnsProbeService();
- void ProbeDns(const CallbackType& callback);
+ virtual void ProbeDns(const ProbeCallback& callback);
- // NetworkChangeNotifier::IPAddressObserver implementation:
- virtual void OnIPAddressChanged() OVERRIDE;
+ // NetworkChangeNotifier::DNSObserver implementation:
+ virtual void OnDNSChanged() OVERRIDE;
- protected:
- // This can be called by tests to pretend the cached reuslt has expired.
- void ExpireResults();
+ void SetSystemClientForTesting(scoped_ptr<net::DnsClient> system_client);
+ void SetPublicClientForTesting(scoped_ptr<net::DnsClient> public_client);
+ void ClearCachedResultForTesting();
private:
enum State {
- STATE_NO_RESULTS,
+ STATE_NO_RESULT,
STATE_PROBE_RUNNING,
- STATE_RESULTS_CACHED,
+ STATE_RESULT_CACHED,
};
+ void SetSystemClientToCurrentConfig();
+ void SetPublicClientToGooglePublicDns();
+
+ // Starts a probe (runs system and public probes).
void StartProbes();
- void OnProbesComplete();
+ void OnProbeComplete();
+ // Calls all |pending_callbacks_| with the |cached_result_|.
void CallCallbacks();
+ // Clears a cached probe result.
+ void ClearCachedResult();
+
+ bool CachedResultIsExpired() const;
- void OnProbeJobComplete(DnsProbeJob* job, DnsProbeJob::Result result);
- chrome_common_net::DnsProbeResult EvaluateResults() const;
- void HistogramProbes() const;
-
- // These are expected to be overridden by tests to return mock jobs.
- virtual scoped_ptr<DnsProbeJob> CreateSystemProbeJob(
- const DnsProbeJob::CallbackType& job_callback);
- virtual scoped_ptr<DnsProbeJob> CreatePublicProbeJob(
- const DnsProbeJob::CallbackType& job_callback);
-
- scoped_ptr<DnsProbeJob> CreateProbeJob(
- const net::DnsConfig& dns_config,
- const DnsProbeJob::CallbackType& job_callback);
- void GetSystemDnsConfig(net::DnsConfig* config);
- void GetPublicDnsConfig(net::DnsConfig* config);
- bool ResultsExpired();
-
- scoped_ptr<DnsProbeJob> system_job_;
- scoped_ptr<DnsProbeJob> public_job_;
- DnsProbeJob::Result system_result_;
- DnsProbeJob::Result public_result_;
- std::vector<CallbackType> callbacks_;
State state_;
- chrome_common_net::DnsProbeResult result_;
+ std::vector<ProbeCallback> pending_callbacks_;
base::Time probe_start_time_;
- // How many DNS request attempts the probe jobs will make before giving up
- // (Overrides the attempts field in the system DnsConfig.)
- const int dns_attempts_;
- // How many nameservers the system config has.
- int system_nameserver_count_;
- // Whether the only system nameserver is 127.0.0.1.
- bool system_is_localhost_;
+ chrome_common_net::DnsProbeStatus cached_result_;
+
+ // DnsProbeRunners for the system DNS configuration and a public DNS server.
+ DnsProbeRunner system_runner_;
+ DnsProbeRunner public_runner_;
DISALLOW_COPY_AND_ASSIGN(DnsProbeService);
};
diff --git a/chrome/browser/net/dns_probe_service_unittest.cc b/chrome/browser/net/dns_probe_service_unittest.cc
index 15cbb44..d8b55eb 100644
--- a/chrome/browser/net/dns_probe_service_unittest.cc
+++ b/chrome/browser/net/dns_probe_service_unittest.cc
@@ -9,111 +9,28 @@
#include "base/memory/weak_ptr.h"
#include "base/message_loop.h"
#include "base/run_loop.h"
-#include "chrome/browser/net/dns_probe_job.h"
+#include "chrome/browser/net/dns_probe_runner.h"
+#include "chrome/browser/net/dns_probe_test_util.h"
#include "chrome/common/net/net_error_info.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "net/dns/dns_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
-using chrome_common_net::DnsProbeResult;
+using base::MessageLoopForIO;
+using base::RunLoop;
+using chrome_common_net::DnsProbeStatus;
+using content::TestBrowserThreadBundle;
+using net::MockDnsClientRule;
namespace chrome_browser_net {
namespace {
-class MockDnsProbeJob : public DnsProbeJob {
- public:
- MockDnsProbeJob(const CallbackType& callback,
- DnsProbeJob::Result result)
- : weak_factory_(this) {
- base::MessageLoop::current()->PostTask(
- FROM_HERE,
- base::Bind(&MockDnsProbeJob::CallCallback,
- weak_factory_.GetWeakPtr(),
- callback,
- result));
- }
-
- virtual ~MockDnsProbeJob() { }
-
- private:
- void CallCallback(const CallbackType& callback, Result result) {
- callback.Run(this, result);
- }
-
- base::WeakPtrFactory<MockDnsProbeJob> weak_factory_;
-};
-
-class TestDnsProbeService : public DnsProbeService {
- public:
- TestDnsProbeService()
- : DnsProbeService(),
- system_job_created_(false),
- public_job_created_(false),
- mock_system_result_(DnsProbeJob::SERVERS_UNKNOWN),
- mock_public_result_(DnsProbeJob::SERVERS_UNKNOWN),
- mock_system_fail_(false) {
- }
-
- virtual ~TestDnsProbeService() { }
-
- void set_mock_results(
- DnsProbeJob::Result mock_system_result,
- DnsProbeJob::Result mock_public_result) {
- mock_system_result_ = mock_system_result;
- mock_public_result_ = mock_public_result;
- }
-
- void set_mock_system_fail(bool mock_system_fail) {
- mock_system_fail_ = mock_system_fail;
- }
-
- bool jobs_created(void) {
- return system_job_created_ && public_job_created_;
- }
-
- void ResetJobsCreated() {
- system_job_created_ = false;
- public_job_created_ = false;
- }
-
- void MockExpireResults() {
- ExpireResults();
- }
-
- bool system_job_created_;
- bool public_job_created_;
-
- private:
- // Override methods in DnsProbeService to return mock jobs:
-
- virtual scoped_ptr<DnsProbeJob> CreateSystemProbeJob(
- const DnsProbeJob::CallbackType& job_callback) OVERRIDE {
- if (mock_system_fail_)
- return scoped_ptr<DnsProbeJob>();
-
- system_job_created_ = true;
- return scoped_ptr<DnsProbeJob>(
- new MockDnsProbeJob(job_callback,
- mock_system_result_));
- }
-
- virtual scoped_ptr<DnsProbeJob> CreatePublicProbeJob(
- const DnsProbeJob::CallbackType& job_callback) OVERRIDE {
- public_job_created_ = true;
- return scoped_ptr<DnsProbeJob>(
- new MockDnsProbeJob(job_callback,
- mock_public_result_));
- }
-
- DnsProbeJob::Result mock_system_result_;
- DnsProbeJob::Result mock_public_result_;
- bool mock_system_fail_;
-};
-
class DnsProbeServiceTest : public testing::Test {
public:
DnsProbeServiceTest()
: callback_called_(false),
- callback_result_(chrome_common_net::DNS_PROBE_UNKNOWN) {
+ callback_result_(chrome_common_net::DNS_PROBE_MAX) {
}
void Probe() {
@@ -121,98 +38,94 @@ class DnsProbeServiceTest : public testing::Test {
base::Unretained(this)));
}
- void RunUntilIdle() {
- base::RunLoop run_loop;
- run_loop.RunUntilIdle();
- }
-
void Reset() {
- service_.ResetJobsCreated();
callback_called_ = false;
}
- base::MessageLoopForIO message_loop_;
- TestDnsProbeService service_;
- bool callback_called_;
- DnsProbeResult callback_result_;
+ protected:
+ void SetRules(MockDnsClientRule::Result system_query_result,
+ MockDnsClientRule::Result public_query_result) {
+ service_.SetSystemClientForTesting(
+ CreateMockDnsClientForProbes(system_query_result));
+ service_.SetPublicClientForTesting(
+ CreateMockDnsClientForProbes(public_query_result));
+ }
+
+ void RunTest(MockDnsClientRule::Result system_query_result,
+ MockDnsClientRule::Result public_query_result,
+ DnsProbeStatus expected_result) {
+ Reset();
+ SetRules(system_query_result, public_query_result);
+
+ Probe();
+ RunLoop().RunUntilIdle();
+ EXPECT_TRUE(callback_called_);
+ EXPECT_EQ(expected_result, callback_result_);
+ }
+
+ void ClearCachedResult() {
+ service_.ClearCachedResultForTesting();
+ }
private:
- void ProbeCallback(DnsProbeResult result) {
+ void ProbeCallback(DnsProbeStatus result) {
+ EXPECT_FALSE(callback_called_);
callback_called_ = true;
callback_result_ = result;
}
+
+ DnsProbeService service_;
+ bool callback_called_;
+ DnsProbeStatus callback_result_;
+ TestBrowserThreadBundle bundle_;
};
-TEST_F(DnsProbeServiceTest, Null) {
+TEST_F(DnsProbeServiceTest, Probe_OK_OK) {
+ RunTest(MockDnsClientRule::OK, MockDnsClientRule::OK,
+ chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN);
}
-TEST_F(DnsProbeServiceTest, Probe) {
- service_.set_mock_results(DnsProbeJob::SERVERS_CORRECT,
- DnsProbeJob::SERVERS_CORRECT);
-
- Probe();
- EXPECT_TRUE(service_.jobs_created());
- EXPECT_FALSE(callback_called_);
-
- RunUntilIdle();
- EXPECT_TRUE(callback_called_);
- EXPECT_EQ(chrome_common_net::DNS_PROBE_NXDOMAIN, callback_result_);
+TEST_F(DnsProbeServiceTest, Probe_TIMEOUT_OK) {
+ RunTest(MockDnsClientRule::TIMEOUT, MockDnsClientRule::OK,
+ chrome_common_net::DNS_PROBE_FINISHED_BAD_CONFIG);
}
-TEST_F(DnsProbeServiceTest, Cache) {
- service_.set_mock_results(DnsProbeJob::SERVERS_CORRECT,
- DnsProbeJob::SERVERS_CORRECT);
-
- Probe();
- RunUntilIdle();
- Reset();
-
- // Cached NXDOMAIN result should persist.
-
- Probe();
- EXPECT_FALSE(service_.jobs_created());
-
- RunUntilIdle();
- EXPECT_TRUE(callback_called_);
- EXPECT_EQ(chrome_common_net::DNS_PROBE_NXDOMAIN, callback_result_);
+TEST_F(DnsProbeServiceTest, Probe_TIMEOUT_TIMEOUT) {
+ RunTest(MockDnsClientRule::TIMEOUT, MockDnsClientRule::TIMEOUT,
+ chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET);
}
-TEST_F(DnsProbeServiceTest, Expired) {
- service_.set_mock_results(DnsProbeJob::SERVERS_CORRECT,
- DnsProbeJob::SERVERS_CORRECT);
-
- Probe();
- EXPECT_TRUE(service_.jobs_created());
-
- RunUntilIdle();
- EXPECT_TRUE(callback_called_);
- EXPECT_EQ(chrome_common_net::DNS_PROBE_NXDOMAIN, callback_result_);
-
- Reset();
-
- service_.MockExpireResults();
-
- Probe();
- EXPECT_TRUE(service_.jobs_created());
-
- RunUntilIdle();
- EXPECT_TRUE(callback_called_);
- EXPECT_EQ(chrome_common_net::DNS_PROBE_NXDOMAIN, callback_result_);
+TEST_F(DnsProbeServiceTest, Probe_OK_FAIL_SYNC) {
+ RunTest(MockDnsClientRule::OK, MockDnsClientRule::FAIL_SYNC,
+ chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN);
}
-TEST_F(DnsProbeServiceTest, SystemFail) {
- service_.set_mock_results(DnsProbeJob::SERVERS_CORRECT,
- DnsProbeJob::SERVERS_CORRECT);
- service_.set_mock_system_fail(true);
+TEST_F(DnsProbeServiceTest, Probe_FAIL_SYNC_OK) {
+ RunTest(MockDnsClientRule::FAIL_SYNC, MockDnsClientRule::OK,
+ chrome_common_net::DNS_PROBE_FINISHED_BAD_CONFIG);
+}
- Probe();
- EXPECT_TRUE(callback_called_);
- EXPECT_EQ(chrome_common_net::DNS_PROBE_UNKNOWN, callback_result_);
+TEST_F(DnsProbeServiceTest, Probe_FAIL_SYNC_FAIL_SYNC) {
+ RunTest(MockDnsClientRule::FAIL_SYNC, MockDnsClientRule::FAIL_SYNC,
+ chrome_common_net::DNS_PROBE_FINISHED_INCONCLUSIVE);
+}
- Reset();
+TEST_F(DnsProbeServiceTest, Cache) {
+ RunTest(MockDnsClientRule::OK, MockDnsClientRule::OK,
+ chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN);
+ // Cached NXDOMAIN result should persist, not the result from the new rules.
+ RunTest(MockDnsClientRule::TIMEOUT, MockDnsClientRule::TIMEOUT,
+ chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN);
+}
- RunUntilIdle();
- EXPECT_FALSE(callback_called_);
+TEST_F(DnsProbeServiceTest, Expire) {
+ RunTest(MockDnsClientRule::OK, MockDnsClientRule::OK,
+ chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN);
+ // Pretend cache expires.
+ ClearCachedResult();
+ // New rules should apply, since a new probe should be run.
+ RunTest(MockDnsClientRule::TIMEOUT, MockDnsClientRule::TIMEOUT,
+ chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET);
}
} // namespace
diff --git a/chrome/browser/net/dns_probe_test_util.cc b/chrome/browser/net/dns_probe_test_util.cc
new file mode 100644
index 0000000..88d481d
--- /dev/null
+++ b/chrome/browser/net/dns_probe_test_util.cc
@@ -0,0 +1,37 @@
+// 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 "chrome/browser/net/dns_probe_test_util.h"
+
+#include "chrome/browser/net/dns_probe_runner.h"
+#include "net/dns/dns_config_service.h"
+#include "net/dns/dns_protocol.h"
+
+using net::DnsClient;
+using net::DnsConfig;
+using net::IPAddressNumber;
+using net::IPEndPoint;
+using net::MockDnsClientRule;
+using net::MockDnsClientRuleList;
+using net::ParseIPLiteralToNumber;
+
+namespace chrome_browser_net {
+
+scoped_ptr<DnsClient> CreateMockDnsClientForProbes(
+ MockDnsClientRule::Result result) {
+ DnsConfig config;
+ IPAddressNumber dns_ip;
+ ParseIPLiteralToNumber("192.168.1.1", &dns_ip);
+ const uint16 kDnsPort = net::dns_protocol::kDefaultPort;
+ config.nameservers.push_back(IPEndPoint(dns_ip, kDnsPort));
+
+ const uint16 kTypeA = net::dns_protocol::kTypeA;
+ MockDnsClientRuleList rules;
+ rules.push_back(
+ MockDnsClientRule(DnsProbeRunner::kKnownGoodHostname, kTypeA, result));
+
+ return CreateMockDnsClient(config, rules).Pass();
+}
+
+} // namespace chrome_browser_net
diff --git a/chrome/browser/net/dns_probe_test_util.h b/chrome/browser/net/dns_probe_test_util.h
new file mode 100644
index 0000000..0f36174
--- /dev/null
+++ b/chrome/browser/net/dns_probe_test_util.h
@@ -0,0 +1,21 @@
+// 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.
+
+#ifndef CHROME_BROWSER_NET_DNS_PROBE_TEST_UTIL_H_
+#define CHROME_BROWSER_NET_DNS_PROBE_TEST_UTIL_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "net/dns/dns_client.h"
+#include "net/dns/dns_test_util.h"
+
+namespace chrome_browser_net {
+
+// Creates a mock DNS client with a single rule for the known-good query
+// (currently google.com) that returns |result|.
+scoped_ptr<net::DnsClient> CreateMockDnsClientForProbes(
+ net::MockDnsClientRule::Result result);
+
+} // namespace chrome_browser_net
+
+#endif // CHROME_BROWSER_NET_DNS_PROBE_TEST_UTIL_H_
diff --git a/chrome/browser/net/net_error_tab_helper.cc b/chrome/browser/net/net_error_tab_helper.cc
index c7189a9..6f20a8a 100644
--- a/chrome/browser/net/net_error_tab_helper.cc
+++ b/chrome/browser/net/net_error_tab_helper.cc
@@ -18,7 +18,8 @@
#include "net/base/net_errors.h"
using base::FieldTrialList;
-using chrome_common_net::DnsProbeResult;
+using chrome_common_net::DnsProbeStatus;
+using chrome_common_net::DnsProbeStatusToString;
using content::BrowserContext;
using content::BrowserThread;
using content::PageTransition;
@@ -32,9 +33,6 @@ namespace chrome_browser_net {
namespace {
-const char kDnsProbeFieldTrialName[] = "DnsProbe-Enable";
-const char kDnsProbeFieldTrialEnableGroupName[] = "enable";
-
static NetErrorTabHelper::TestingState testing_state_ =
NetErrorTabHelper::TESTING_DEFAULT;
@@ -45,48 +43,24 @@ bool IsDnsError(int net_error) {
net_error == net::ERR_NAME_RESOLUTION_FAILED;
}
-bool GetEnabledByTrial() {
- return (FieldTrialList::FindFullName(kDnsProbeFieldTrialName)
- == kDnsProbeFieldTrialEnableGroupName);
-}
-
-NetErrorTracker::FrameType GetFrameType(bool is_main_frame) {
- return is_main_frame ? NetErrorTracker::FRAME_MAIN
- : NetErrorTracker::FRAME_SUB;
-}
-
-NetErrorTracker::PageType GetPageType(bool is_error_page) {
- return is_error_page ? NetErrorTracker::PAGE_ERROR
- : NetErrorTracker::PAGE_NORMAL;
-}
-
-NetErrorTracker::ErrorType GetErrorType(int net_error) {
- return IsDnsError(net_error) ? NetErrorTracker::ERROR_DNS
- : NetErrorTracker::ERROR_OTHER;
-}
-
void OnDnsProbeFinishedOnIOThread(
- const base::Callback<void(DnsProbeResult)>& callback,
- DnsProbeResult result) {
+ const base::Callback<void(DnsProbeStatus)>& callback,
+ DnsProbeStatus result) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- DVLOG(1) << "DNS probe finished with result " << result;
-
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(callback, result));
}
-// We can only access g_browser_process->io_thread() from the browser thread,
-// so we have to pass it in to the callback instead of dereferencing it here.
+// Can only access g_browser_process->io_thread() from the browser thread,
+// so have to pass it in to the callback instead of dereferencing it here.
void StartDnsProbeOnIOThread(
- const base::Callback<void(DnsProbeResult)>& callback,
+ const base::Callback<void(DnsProbeStatus)>& callback,
IOThread* io_thread) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- DVLOG(1) << "Starting DNS probe";
-
DnsProbeService* probe_service =
io_thread->globals()->dns_probe_service.get();
@@ -113,8 +87,10 @@ void NetErrorTabHelper::DidStartProvisionalLoadForFrame(
RenderViewHost* render_view_host) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- tracker_.OnStartProvisionalLoad(GetFrameType(is_main_frame),
- GetPageType(is_error_page));
+ if (!is_main_frame)
+ return;
+
+ is_error_page_ = is_error_page;
}
void NetErrorTabHelper::DidCommitProvisionalLoadForFrame(
@@ -125,7 +101,21 @@ void NetErrorTabHelper::DidCommitProvisionalLoadForFrame(
RenderViewHost* render_view_host) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- tracker_.OnCommitProvisionalLoad(GetFrameType(is_main_frame));
+ if (!is_main_frame)
+ return;
+
+ // Resend status every time an error page commits; this is somewhat spammy,
+ // but ensures that the status will make it to the real error page, even if
+ // the link doctor loads a blank intermediate page or the tab switches
+ // renderer processes.
+ if (is_error_page_ && dns_error_active_) {
+ dns_error_page_committed_ = true;
+ DVLOG(1) << "Committed error page; resending status.";
+ SendInfo();
+ } else {
+ dns_error_active_ = false;
+ dns_error_page_committed_ = false;
+ }
}
void NetErrorTabHelper::DidFailProvisionalLoad(
@@ -137,75 +127,70 @@ void NetErrorTabHelper::DidFailProvisionalLoad(
RenderViewHost* render_view_host) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- tracker_.OnFailProvisionalLoad(GetFrameType(is_main_frame),
- GetErrorType(error_code));
-}
+ if (!is_main_frame)
+ return;
-void NetErrorTabHelper::DidFinishLoad(
- int64 frame_id,
- const GURL& validated_url,
- bool is_main_frame,
- RenderViewHost* render_view_host) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
-
- tracker_.OnFinishLoad(GetFrameType(is_main_frame));
+ if (IsDnsError(error_code)) {
+ dns_error_active_ = true;
+ OnMainFrameDnsError();
+ }
}
NetErrorTabHelper::NetErrorTabHelper(WebContents* contents)
: WebContentsObserver(contents),
weak_factory_(this),
- tracker_(base::Bind(&NetErrorTabHelper::TrackerCallback,
- weak_factory_.GetWeakPtr())),
- dns_error_page_state_(NetErrorTracker::DNS_ERROR_PAGE_NONE),
- dns_probe_state_(DNS_PROBE_NONE),
- enabled_by_trial_(GetEnabledByTrial()) {
+ is_error_page_(false),
+ dns_error_active_(false),
+ dns_error_page_committed_(false),
+ dns_probe_status_(chrome_common_net::DNS_PROBE_POSSIBLE),
+ enabled_by_trial_(chrome_common_net::DnsProbesEnabledByFieldTrial()) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- InitializePref(contents);
+ // If this helper is under test, it won't have a WebContents.
+ if (contents)
+ InitializePref(contents);
}
-void NetErrorTabHelper::TrackerCallback(
- NetErrorTracker::DnsErrorPageState state) {
- dns_error_page_state_ = state;
-
- MaybePostStartDnsProbeTask();
- MaybeSendInfo();
+void NetErrorTabHelper::OnMainFrameDnsError() {
+ if (ProbesAllowed()) {
+ // Don't start more than one probe at a time.
+ if (dns_probe_status_ != chrome_common_net::DNS_PROBE_STARTED) {
+ StartDnsProbe();
+ dns_probe_status_ = chrome_common_net::DNS_PROBE_STARTED;
+ }
+ } else {
+ dns_probe_status_ = chrome_common_net::DNS_PROBE_NOT_RUN;
+ }
}
-void NetErrorTabHelper::MaybePostStartDnsProbeTask() {
+void NetErrorTabHelper::StartDnsProbe() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(dns_error_active_);
+ DCHECK_NE(chrome_common_net::DNS_PROBE_STARTED, dns_probe_status_);
- if (dns_error_page_state_ != NetErrorTracker::DNS_ERROR_PAGE_NONE &&
- dns_probe_state_ != DNS_PROBE_STARTED &&
- ProbesAllowed()) {
- BrowserThread::PostTask(
- BrowserThread::IO,
- FROM_HERE,
- base::Bind(&StartDnsProbeOnIOThread,
- base::Bind(&NetErrorTabHelper::OnDnsProbeFinished,
- weak_factory_.GetWeakPtr()),
- g_browser_process->io_thread()));
- dns_probe_state_ = DNS_PROBE_STARTED;
- }
+ DVLOG(1) << "Starting DNS probe.";
+
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&StartDnsProbeOnIOThread,
+ base::Bind(&NetErrorTabHelper::OnDnsProbeFinished,
+ weak_factory_.GetWeakPtr()),
+ g_browser_process->io_thread()));
}
-void NetErrorTabHelper::OnDnsProbeFinished(DnsProbeResult result) {
+void NetErrorTabHelper::OnDnsProbeFinished(DnsProbeStatus result) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- DCHECK_EQ(DNS_PROBE_STARTED, dns_probe_state_);
+ DCHECK_EQ(chrome_common_net::DNS_PROBE_STARTED, dns_probe_status_);
+ DCHECK(chrome_common_net::DnsProbeStatusIsFinished(result));
- dns_probe_result_ = result;
- dns_probe_state_ = DNS_PROBE_FINISHED;
+ DVLOG(1) << "Finished DNS probe with result "
+ << DnsProbeStatusToString(result) << ".";
- MaybeSendInfo();
-}
+ dns_probe_status_ = result;
-void NetErrorTabHelper::MaybeSendInfo() {
- if (dns_error_page_state_ == NetErrorTracker::DNS_ERROR_PAGE_LOADED &&
- dns_probe_state_ == DNS_PROBE_FINISHED) {
- DVLOG(1) << "Sending result " << dns_probe_result_ << " to renderer";
- Send(new ChromeViewMsg_NetErrorInfo(routing_id(), dns_probe_result_));
- dns_probe_state_ = DNS_PROBE_NONE;
- }
+ if (dns_error_page_committed_)
+ SendInfo();
}
void NetErrorTabHelper::InitializePref(WebContents* contents) {
@@ -226,4 +211,15 @@ bool NetErrorTabHelper::ProbesAllowed() const {
return enabled_by_trial_ && *resolve_errors_with_web_service_;
}
+void NetErrorTabHelper::SendInfo() {
+ DCHECK_NE(chrome_common_net::DNS_PROBE_POSSIBLE, dns_probe_status_);
+ DCHECK(dns_error_page_committed_);
+
+ DVLOG(1) << "Sending status " << DnsProbeStatusToString(dns_probe_status_);
+ Send(new ChromeViewMsg_NetErrorInfo(routing_id(), dns_probe_status_));
+
+ if (!dns_probe_status_snoop_callback_.is_null())
+ dns_probe_status_snoop_callback_.Run(dns_probe_status_);
+}
+
} // namespace chrome_browser_net
diff --git a/chrome/browser/net/net_error_tab_helper.h b/chrome/browser/net/net_error_tab_helper.h
index e48fa9f..fd0553e 100644
--- a/chrome/browser/net/net_error_tab_helper.h
+++ b/chrome/browser/net/net_error_tab_helper.h
@@ -6,12 +6,12 @@
#define CHROME_BROWSER_NET_NET_ERROR_TAB_HELPER_H_
#include "base/basictypes.h"
+#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/memory/weak_ptr.h"
#include "base/prefs/pref_member.h"
#include "chrome/browser/net/dns_probe_service.h"
#include "chrome/common/net/net_error_info.h"
-#include "chrome/common/net/net_error_tracker.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"
@@ -30,10 +30,21 @@ class NetErrorTabHelper
TESTING_FORCE_ENABLED
};
+ typedef base::Callback<void(chrome_common_net::DnsProbeStatus)>
+ DnsProbeStatusSnoopCallback;
+
virtual ~NetErrorTabHelper();
static void set_state_for_testing(TestingState testing_state);
+ // Sets a callback that will be called immediately after the helper sends
+ // a NetErrorHelper IPC. (Used by the DNS probe browser test to know when to
+ // check the error page for updates, instead of polling.)
+ void set_dns_probe_status_snoop_callback_for_testing(
+ const DnsProbeStatusSnoopCallback& dns_probe_status_snoop_callback) {
+ dns_probe_status_snoop_callback_ = dns_probe_status_snoop_callback;
+ }
+
// content::WebContentsObserver implementation.
virtual void DidStartProvisionalLoadForFrame(
int64 frame_id,
@@ -59,43 +70,50 @@ class NetErrorTabHelper
const string16& error_description,
content::RenderViewHost* render_view_host) OVERRIDE;
- virtual void DidFinishLoad(
- int64 frame_id,
- const GURL& validated_url,
- bool is_main_frame,
- content::RenderViewHost* render_view_host) OVERRIDE;
-
- private:
- friend class content::WebContentsUserData<NetErrorTabHelper>;
-
- enum DnsProbeState {
- DNS_PROBE_NONE,
- DNS_PROBE_STARTED,
- DNS_PROBE_FINISHED
- };
-
+ protected:
// |contents| is the WebContents of the tab this NetErrorTabHelper is
// attached to.
explicit NetErrorTabHelper(content::WebContents* contents);
+ virtual void StartDnsProbe();
+ virtual void SendInfo();
+ void OnDnsProbeFinished(chrome_common_net::DnsProbeStatus result);
+
+ chrome_common_net::DnsProbeStatus dns_probe_status() const {
+ return dns_probe_status_;
+ }
+
+ private:
+ friend class content::WebContentsUserData<NetErrorTabHelper>;
- void TrackerCallback(NetErrorTracker::DnsErrorPageState state);
- void MaybePostStartDnsProbeTask();
- void OnDnsProbeFinished(chrome_common_net::DnsProbeResult result);
- void MaybeSendInfo();
+ void OnMainFrameDnsError();
void InitializePref(content::WebContents* contents);
bool ProbesAllowed() const;
base::WeakPtrFactory<NetErrorTabHelper> weak_factory_;
- NetErrorTracker tracker_;
- NetErrorTracker::DnsErrorPageState dns_error_page_state_;
+ // True if the last provisional load that started was for an error page.
+ bool is_error_page_;
+
+ // True if the helper has seen a main frame page load fail with a DNS error,
+ // but has not yet seen a new page commit successfully afterwards.
+ bool dns_error_active_;
+
+ // True if the helper has seen an error page commit while |dns_error_active_|
+ // is true. (This should never be true if |dns_error_active_| is false.)
+ bool dns_error_page_committed_;
- DnsProbeState dns_probe_state_;
- chrome_common_net::DnsProbeResult dns_probe_result_;
+ // The status of a DNS probe that may or may not have started or finished.
+ // Since the renderer can change out from under the helper (in cross-process
+ // navigations), it re-sends the status whenever an error page commits.
+ chrome_common_net::DnsProbeStatus dns_probe_status_;
// Whether we are enabled to run by the DnsProbe-Enable field trial.
const bool enabled_by_trial_;
+
+ // Optional callback for browser test to snoop on outgoing NetErrorInfo IPCs.
+ DnsProbeStatusSnoopCallback dns_probe_status_snoop_callback_;
+
// "Use a web service to resolve navigation errors" preference is required
// to allow probes.
BooleanPrefMember resolve_errors_with_web_service_;
diff --git a/chrome/browser/net/net_error_tab_helper_unittest.cc b/chrome/browser/net/net_error_tab_helper_unittest.cc
new file mode 100644
index 0000000..3a029bb
--- /dev/null
+++ b/chrome/browser/net/net_error_tab_helper_unittest.cc
@@ -0,0 +1,392 @@
+// 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 "chrome/browser/net/net_error_tab_helper.h"
+
+#include "base/message_loop.h"
+#include "chrome/common/net/net_error_info.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/common/page_transition_types.h"
+#include "content/public/test/test_browser_thread.h"
+#include "net/base/net_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::MessageLoop;
+using chrome_browser_net::NetErrorTabHelper;
+using chrome_common_net::DnsProbeStatus;
+using content::BrowserThread;
+using content::TestBrowserThread;
+
+class TestNetErrorTabHelper : public NetErrorTabHelper {
+ public:
+ TestNetErrorTabHelper()
+ : NetErrorTabHelper(NULL),
+ mock_probe_running_(false),
+ last_status_sent_(chrome_common_net::DNS_PROBE_MAX),
+ mock_sent_count_(0) {}
+
+ void FinishProbe(DnsProbeStatus status) {
+ EXPECT_TRUE(mock_probe_running_);
+ OnDnsProbeFinished(status);
+ mock_probe_running_ = false;
+ }
+
+ bool mock_probe_running() const { return mock_probe_running_; }
+ DnsProbeStatus last_status_sent() const { return last_status_sent_; }
+ int mock_sent_count() const { return mock_sent_count_; }
+
+ private:
+ virtual void StartDnsProbe() OVERRIDE {
+ EXPECT_FALSE(mock_probe_running_);
+ mock_probe_running_ = true;
+ }
+
+ virtual void SendInfo() OVERRIDE {
+ last_status_sent_ = dns_probe_status();
+ mock_sent_count_++;
+ }
+
+ bool mock_probe_running_;
+ DnsProbeStatus last_status_sent_;
+ int mock_sent_count_;
+};
+
+class NetErrorTabHelperTest : public testing::Test {
+ protected:
+ enum MainFrame { SUB_FRAME, MAIN_FRAME };
+ enum ErrorPage { NORMAL_PAGE, ERROR_PAGE };
+ enum ErrorType { DNS_ERROR, OTHER_ERROR };
+
+ NetErrorTabHelperTest()
+ : fake_ui_thread_(BrowserThread::UI, &message_loop_) {
+ NetErrorTabHelper::set_state_for_testing(
+ NetErrorTabHelper::TESTING_FORCE_ENABLED);
+ }
+
+ void StartProvisionalLoad(MainFrame main_frame, ErrorPage error_page) {
+ tab_helper_.DidStartProvisionalLoadForFrame(
+ 1, // frame_id
+ 0, // parent_frame_id
+ (main_frame == MAIN_FRAME),
+ bogus_url_, // validated_url
+ (error_page == ERROR_PAGE),
+ false, // is_iframe_srcdoc
+ NULL); // render_view_host
+ }
+
+ void CommitProvisionalLoad(MainFrame main_frame) {
+ tab_helper_.DidCommitProvisionalLoadForFrame(
+ 1, // frame id
+ (main_frame == MAIN_FRAME),
+ bogus_url_, // url
+ content::PAGE_TRANSITION_TYPED,
+ NULL); // render_view_host
+ }
+
+ void FailProvisionalLoad(MainFrame main_frame, ErrorType error_type) {
+ int net_error;
+
+ if (error_type == DNS_ERROR)
+ net_error = net::ERR_NAME_NOT_RESOLVED;
+ else
+ net_error = net::ERR_TIMED_OUT;
+
+ tab_helper_.DidFailProvisionalLoad(
+ 1, // frame id
+ (main_frame == MAIN_FRAME),
+ bogus_url_, // validated_url
+ net_error,
+ string16(),
+ NULL); // render_view_host
+ }
+
+ void FinishProbe(DnsProbeStatus status) {
+ tab_helper_.FinishProbe(status);
+ }
+
+ bool probe_running() { return tab_helper_.mock_probe_running(); }
+ DnsProbeStatus last_status_sent() { return tab_helper_.last_status_sent(); }
+ int sent_count() { return tab_helper_.mock_sent_count(); }
+
+ private:
+ MessageLoop message_loop_;
+ TestBrowserThread fake_ui_thread_;
+ TestNetErrorTabHelper tab_helper_;
+ GURL bogus_url_;
+};
+
+TEST_F(NetErrorTabHelperTest, Null) {
+ EXPECT_FALSE(probe_running());
+}
+
+TEST_F(NetErrorTabHelperTest, MainFrameNonDnsError) {
+ StartProvisionalLoad(MAIN_FRAME, NORMAL_PAGE);
+ FailProvisionalLoad(MAIN_FRAME, OTHER_ERROR);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(0, sent_count());
+}
+
+TEST_F(NetErrorTabHelperTest, NonMainFrameDnsError) {
+ StartProvisionalLoad(SUB_FRAME, NORMAL_PAGE);
+ FailProvisionalLoad(SUB_FRAME, DNS_ERROR);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(0, sent_count());
+}
+
+// Test complete DNS error page loads. Note that the helper can see two error
+// page loads: Link Doctor loads an empty HTML page so the user knows something
+// is going on, then fails over to the normal error page if and when Link
+// Doctor fails to load or declines to provide a page.
+
+TEST_F(NetErrorTabHelperTest, ProbeResponseBeforeFirstCommit) {
+ StartProvisionalLoad(MAIN_FRAME, NORMAL_PAGE);
+ FailProvisionalLoad(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(0, sent_count());
+
+ StartProvisionalLoad(MAIN_FRAME, ERROR_PAGE);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(0, sent_count());
+
+ FinishProbe(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(0, sent_count());
+
+ CommitProvisionalLoad(MAIN_FRAME);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(1, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN, last_status_sent());
+
+ StartProvisionalLoad(MAIN_FRAME, ERROR_PAGE);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(1, sent_count());
+
+ CommitProvisionalLoad(MAIN_FRAME);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(2, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN, last_status_sent());
+}
+
+TEST_F(NetErrorTabHelperTest, ProbeResponseBetweenFirstAndSecondCommit) {
+ StartProvisionalLoad(MAIN_FRAME, NORMAL_PAGE);
+ FailProvisionalLoad(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(0, sent_count());
+
+ StartProvisionalLoad(MAIN_FRAME, ERROR_PAGE);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(0, sent_count());
+
+ CommitProvisionalLoad(MAIN_FRAME);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(1, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, last_status_sent());
+
+ FinishProbe(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(2, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN, last_status_sent());
+
+ StartProvisionalLoad(MAIN_FRAME, ERROR_PAGE);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(2, sent_count());
+
+ CommitProvisionalLoad(MAIN_FRAME);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(3, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN, last_status_sent());
+}
+
+TEST_F(NetErrorTabHelperTest, ProbeResponseAfterSecondCommit) {
+ StartProvisionalLoad(MAIN_FRAME, NORMAL_PAGE);
+ FailProvisionalLoad(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(0, sent_count());
+
+ StartProvisionalLoad(MAIN_FRAME, ERROR_PAGE);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(0, sent_count());
+
+ CommitProvisionalLoad(MAIN_FRAME);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(1, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, last_status_sent());
+
+ StartProvisionalLoad(MAIN_FRAME, ERROR_PAGE);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(1, sent_count());
+
+ CommitProvisionalLoad(MAIN_FRAME);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(2, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, last_status_sent());
+
+ FinishProbe(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(3, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN, last_status_sent());
+}
+
+// Send result even if a new page load has started; the error page is still
+// visible, and the user might cancel the load.
+TEST_F(NetErrorTabHelperTest, ProbeResponseAfterNewStart) {
+ StartProvisionalLoad(MAIN_FRAME, NORMAL_PAGE);
+ FailProvisionalLoad(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(0, sent_count());
+
+ StartProvisionalLoad(MAIN_FRAME, ERROR_PAGE);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(0, sent_count());
+
+ CommitProvisionalLoad(MAIN_FRAME);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(1, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, last_status_sent());
+
+ StartProvisionalLoad(MAIN_FRAME, ERROR_PAGE);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(1, sent_count());
+
+ CommitProvisionalLoad(MAIN_FRAME);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(2, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, last_status_sent());
+
+ StartProvisionalLoad(MAIN_FRAME, NORMAL_PAGE);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(2, sent_count());
+
+ FinishProbe(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(3, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN, last_status_sent());
+}
+
+// Don't send result if a new page has committed; the result would go to the
+// wrong page, and the error page is gone anyway.
+TEST_F(NetErrorTabHelperTest, ProbeResponseAfterNewCommit) {
+ StartProvisionalLoad(MAIN_FRAME, NORMAL_PAGE);
+ FailProvisionalLoad(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(0, sent_count());
+
+ StartProvisionalLoad(MAIN_FRAME, ERROR_PAGE);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(0, sent_count());
+
+ CommitProvisionalLoad(MAIN_FRAME);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(1, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, last_status_sent());
+
+ StartProvisionalLoad(MAIN_FRAME, ERROR_PAGE);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(1, sent_count());
+
+ CommitProvisionalLoad(MAIN_FRAME);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(2, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, last_status_sent());
+
+ StartProvisionalLoad(MAIN_FRAME, NORMAL_PAGE);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(2, sent_count());
+
+ CommitProvisionalLoad(MAIN_FRAME);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(2, sent_count());
+
+ FinishProbe(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(2, sent_count());
+}
+
+TEST_F(NetErrorTabHelperTest, MultipleDnsErrorsWithProbesWithoutErrorPages) {
+ StartProvisionalLoad(MAIN_FRAME, NORMAL_PAGE);
+ FailProvisionalLoad(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(0, sent_count());
+
+ FinishProbe(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(0, sent_count());
+
+ StartProvisionalLoad(MAIN_FRAME, NORMAL_PAGE);
+ FailProvisionalLoad(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(0, sent_count());
+
+ FinishProbe(chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(0, sent_count());
+}
+
+TEST_F(NetErrorTabHelperTest, MultipleDnsErrorsWithProbesAndErrorPages) {
+ StartProvisionalLoad(MAIN_FRAME, NORMAL_PAGE);
+ FailProvisionalLoad(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(0, sent_count());
+
+ StartProvisionalLoad(MAIN_FRAME, ERROR_PAGE);
+ CommitProvisionalLoad(MAIN_FRAME);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(1, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, last_status_sent());
+
+ FinishProbe(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(2, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN, last_status_sent());
+
+ StartProvisionalLoad(MAIN_FRAME, NORMAL_PAGE);
+ FailProvisionalLoad(MAIN_FRAME, DNS_ERROR);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(2, sent_count());
+
+ StartProvisionalLoad(MAIN_FRAME, ERROR_PAGE);
+ CommitProvisionalLoad(MAIN_FRAME);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(3, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, last_status_sent());
+
+ FinishProbe(chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(4, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET,
+ last_status_sent());
+}
+
+// If multiple DNS errors occur in a row before a probe result, don't start
+// multiple probes.
+TEST_F(NetErrorTabHelperTest, CoalesceFailures) {
+ StartProvisionalLoad(MAIN_FRAME, NORMAL_PAGE);
+ FailProvisionalLoad(MAIN_FRAME, DNS_ERROR);
+ StartProvisionalLoad(MAIN_FRAME, ERROR_PAGE);
+ CommitProvisionalLoad(MAIN_FRAME);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(1, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, last_status_sent());
+
+ StartProvisionalLoad(MAIN_FRAME, NORMAL_PAGE);
+ FailProvisionalLoad(MAIN_FRAME, DNS_ERROR);
+ StartProvisionalLoad(MAIN_FRAME, ERROR_PAGE);
+ CommitProvisionalLoad(MAIN_FRAME);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(2, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, last_status_sent());
+
+ StartProvisionalLoad(MAIN_FRAME, NORMAL_PAGE);
+ FailProvisionalLoad(MAIN_FRAME, DNS_ERROR);
+ StartProvisionalLoad(MAIN_FRAME, ERROR_PAGE);
+ CommitProvisionalLoad(MAIN_FRAME);
+ EXPECT_TRUE(probe_running());
+ EXPECT_EQ(3, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_STARTED, last_status_sent());
+
+ FinishProbe(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_FALSE(probe_running());
+ EXPECT_EQ(4, sent_count());
+ EXPECT_EQ(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN, last_status_sent());
+}
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index fd97650..618e0a8 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -1095,8 +1095,8 @@
'browser/net/connection_tester.h',
'browser/net/crl_set_fetcher.cc',
'browser/net/crl_set_fetcher.h',
- 'browser/net/dns_probe_job.cc',
- 'browser/net/dns_probe_job.h',
+ 'browser/net/dns_probe_runner.cc',
+ 'browser/net/dns_probe_runner.h',
'browser/net/dns_probe_service.cc',
'browser/net/dns_probe_service.h',
'browser/net/evicted_domain_cookie_counter.cc',
diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi
index a9b5d56..1f72cee 100644
--- a/chrome/chrome_common.gypi
+++ b/chrome/chrome_common.gypi
@@ -729,8 +729,8 @@
'target_name': 'common_net',
'type': 'static_library',
'sources': [
- 'common/net/net_error_tracker.cc',
- 'common/net/net_error_tracker.h',
+ 'common/net/net_error_info.cc',
+ 'common/net/net_error_info.h',
'common/net/net_resource_provider.cc',
'common/net/net_resource_provider.h',
'common/net/predictor_common.h',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 34470b6..fa669016 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1454,6 +1454,7 @@
'browser/media_galleries/media_galleries_dialog_controller_mock.h',
'browser/metrics/metrics_service_browsertest.cc',
'browser/net/cookie_policy_browsertest.cc',
+ 'browser/net/dns_probe_browsertest.cc',
'browser/net/ftp_browsertest.cc',
'browser/net/load_timing_browsertest.cc',
'browser/net/predictor_browsertest.cc',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index 95e3f54..a78d569 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -134,6 +134,8 @@
'browser/google_apis/test_util.h',
'browser/media_galleries/media_galleries_test_util.cc',
'browser/media_galleries/media_galleries_test_util.h',
+ 'browser/net/dns_probe_test_util.cc',
+ 'browser/net/dns_probe_test_util.h',
'browser/net/url_request_mock_util.cc',
'browser/net/url_request_mock_util.h',
'browser/notifications/notification_test_util.cc',
@@ -969,12 +971,13 @@
'browser/net/chrome_fraudulent_certificate_reporter_unittest.cc',
'browser/net/chrome_network_delegate_unittest.cc',
'browser/net/connection_tester_unittest.cc',
- 'browser/net/dns_probe_job_unittest.cc',
+ 'browser/net/dns_probe_runner_unittest.cc',
'browser/net/dns_probe_service_unittest.cc',
'browser/net/evicted_domain_cookie_counter_unittest.cc',
'browser/net/gaia/gaia_oauth_fetcher_unittest.cc',
'browser/net/http_pipelining_compatibility_client_unittest.cc',
'browser/net/http_server_properties_manager_unittest.cc',
+ 'browser/net/net_error_tab_helper_unittest.cc',
'browser/net/net_log_temp_file_unittest.cc',
'browser/net/network_stats_unittest.cc',
'browser/net/network_time_tracker_unittest.cc',
@@ -1722,7 +1725,6 @@
'common/metrics/metrics_util_unittest.cc',
'common/metrics/variations/variations_util_unittest.cc',
'common/multi_process_lock_unittest.cc',
- 'common/net/net_error_tracker_unittest.cc',
'common/net/x509_certificate_model_unittest.cc',
'common/partial_circular_buffer_unittest.cc',
'common/policy/policy_schema_unittest.cc',
@@ -1747,6 +1749,7 @@
'renderer/extensions/renderer_permissions_policy_delegate_unittest.cc',
'renderer/extensions/safe_builtins_unittest.cc',
'renderer/media/chrome_webrtc_log_message_delegate_unittest.cc',
+ 'renderer/net/net_error_helper_unittest.cc',
'renderer/net/predictor_queue_unittest.cc',
'renderer/net/renderer_predictor_unittest.cc',
'renderer/plugins/plugin_uma_unittest.cc',
diff --git a/chrome/common/localized_error.cc b/chrome/common/localized_error.cc
index d23b1b4..136607a 100644
--- a/chrome/common/localized_error.cc
+++ b/chrome/common/localized_error.cc
@@ -15,6 +15,7 @@
#include "chrome/common/extensions/extension_icon_set.h"
#include "chrome/common/extensions/extension_set.h"
#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
+#include "chrome/common/net/net_error_info.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "net/base/escape.h"
@@ -369,6 +370,53 @@ const LocalizedErrorMap http_error_options[] = {
},
};
+const LocalizedErrorMap dns_probe_error_options[] = {
+ {chrome_common_net::DNS_PROBE_POSSIBLE,
+ IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+ IDS_ERRORPAGES_HEADING_NOT_AVAILABLE,
+ IDS_ERRORPAGES_SUMMARY_DNS_PROBE_RUNNING,
+ IDS_ERRORPAGES_DETAILS_DNS_PROBE_RUNNING,
+ SUGGEST_RELOAD,
+ },
+
+ // DNS_PROBE_NOT_RUN is not here; NetErrorHelper will restore the original
+ // error, which might be one of several DNS-related errors.
+
+ {chrome_common_net::DNS_PROBE_STARTED,
+ IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+ IDS_ERRORPAGES_HEADING_NOT_AVAILABLE,
+ IDS_ERRORPAGES_SUMMARY_DNS_PROBE_RUNNING,
+ IDS_ERRORPAGES_DETAILS_DNS_PROBE_RUNNING,
+ // Include SUGGEST_RELOAD so the More button doesn't jump when we update.
+ SUGGEST_RELOAD,
+ },
+
+ // DNS_PROBE_FINISHED_UNKNOWN is not here; NetErrorHelper will restore the
+ // original error, which might be one of several DNS-related errors.
+
+ {chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET,
+ IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+ IDS_ERRORPAGES_HEADING_NOT_AVAILABLE,
+ IDS_ERRORPAGES_SUMMARY_INTERNET_DISCONNECTED,
+ IDS_ERRORPAGES_DETAILS_INTERNET_DISCONNECTED,
+ SUGGEST_RELOAD | SUGGEST_CHECK_CONNECTION | SUGGEST_FIREWALL_CONFIG,
+ },
+ {chrome_common_net::DNS_PROBE_FINISHED_BAD_CONFIG,
+ IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+ IDS_ERRORPAGES_HEADING_NOT_AVAILABLE,
+ IDS_ERRORPAGES_SUMMARY_NAME_NOT_RESOLVED,
+ IDS_ERRORPAGES_DETAILS_NAME_NOT_RESOLVED,
+ SUGGEST_RELOAD | SUGGEST_DNS_CONFIG | SUGGEST_FIREWALL_CONFIG,
+ },
+ {chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN,
+ IDS_ERRORPAGES_TITLE_NOT_AVAILABLE,
+ IDS_ERRORPAGES_HEADING_NOT_AVAILABLE,
+ IDS_ERRORPAGES_SUMMARY_NAME_NOT_RESOLVED,
+ IDS_ERRORPAGES_DETAILS_NAME_NOT_RESOLVED,
+ SUGGEST_RELOAD,
+ },
+};
+
const LocalizedErrorMap* FindErrorMapInArray(const LocalizedErrorMap* maps,
size_t num_maps,
int error_code) {
@@ -393,6 +441,13 @@ const LocalizedErrorMap* LookupErrorMap(const std::string& error_domain,
return FindErrorMapInArray(http_error_options,
arraysize(http_error_options),
error_code);
+ } else if (error_domain == chrome_common_net::kDnsProbeErrorDomain) {
+ const LocalizedErrorMap* map =
+ FindErrorMapInArray(dns_probe_error_options,
+ arraysize(dns_probe_error_options),
+ error_code);
+ DCHECK(map);
+ return map;
} else {
NOTREACHED();
return NULL;
@@ -502,6 +557,10 @@ void LocalizedError::GetStrings(const WebKit::WebURLError& error,
// Remove the leading "net::" from the returned string.
RemoveChars(ascii_error_string, "net:", &ascii_error_string);
error_string = ASCIIToUTF16(ascii_error_string);
+ } else if (error_domain == chrome_common_net::kDnsProbeErrorDomain) {
+ std::string ascii_error_string =
+ chrome_common_net::DnsProbeStatusToString(error_code);
+ error_string = ASCIIToUTF16(ascii_error_string);
} else {
DCHECK_EQ(LocalizedError::kHttpErrorDomain, error_domain);
error_string = base::IntToString16(error_code);
diff --git a/chrome/common/net/net_error_info.cc b/chrome/common/net/net_error_info.cc
new file mode 100644
index 0000000..e350b89
--- /dev/null
+++ b/chrome/common/net/net_error_info.cc
@@ -0,0 +1,48 @@
+// 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 "base/logging.h"
+#include "base/metrics/field_trial.h"
+#include "chrome/common/net/net_error_info.h"
+
+namespace chrome_common_net {
+
+const char kDnsProbeErrorDomain[] = "dnsprobe";
+
+const char* DnsProbeStatusToString(int status) {
+ switch (status) {
+ case DNS_PROBE_POSSIBLE:
+ return "DNS_PROBE_POSSIBLE";
+ case DNS_PROBE_NOT_RUN:
+ return "DNS_PROBE_NOT_RUN";
+ case DNS_PROBE_STARTED:
+ return "DNS_PROBE_STARTED";
+ case DNS_PROBE_FINISHED_INCONCLUSIVE:
+ return "DNS_PROBE_FINISHED_INCONCLUSIVE";
+ case DNS_PROBE_FINISHED_NO_INTERNET:
+ return "DNS_PROBE_FINISHED_NO_INTERNET";
+ case DNS_PROBE_FINISHED_BAD_CONFIG:
+ return "DNS_PROBE_FINISHED_BAD_CONFIG";
+ case DNS_PROBE_FINISHED_NXDOMAIN:
+ return "DNS_PROBE_FINISHED_NXDOMAIN";
+ default:
+ NOTREACHED();
+ return "";
+ }
+}
+
+bool DnsProbeStatusIsFinished(DnsProbeStatus status) {
+ return status >= DNS_PROBE_FINISHED_INCONCLUSIVE &&
+ status < DNS_PROBE_MAX;
+}
+
+bool DnsProbesEnabledByFieldTrial() {
+ const char kDnsProbeFieldTrialName[] = "DnsProbe-Enable";
+ const char kDnsProbeFieldTrialEnableGroupName[] = "enable";
+
+ return base::FieldTrialList::FindFullName(kDnsProbeFieldTrialName) ==
+ kDnsProbeFieldTrialEnableGroupName;
+}
+
+} // namespace chrome_common_net
diff --git a/chrome/common/net/net_error_info.h b/chrome/common/net/net_error_info.h
index 9f8d08a..ec9d816 100644
--- a/chrome/common/net/net_error_info.h
+++ b/chrome/common/net/net_error_info.h
@@ -7,14 +7,69 @@
namespace chrome_common_net {
-enum DnsProbeResult {
- DNS_PROBE_UNKNOWN,
- DNS_PROBE_NO_INTERNET,
- DNS_PROBE_BAD_CONFIG,
- DNS_PROBE_NXDOMAIN,
+// The status of a DNS probe that the NetErrorTabHelper may or may not have
+// started.
+//
+// The DNS_PROBE_FINISHED_* values are used in histograms, so:
+// 1. FINISHED_UNKNOWN must remain the first FINISHED_* value.
+// 2. FINISHED_* values must not be rearranged relative to FINISHED_UNKNOWN.
+// 3. New FINISHED_* values must be inserted at the end.
+// 4. New non-FINISHED_* values must be inserted before FINISHED_UNKNOWN.
+enum DnsProbeStatus {
+ // A DNS probe may be run for this error page. (This status is only used on
+ // the renderer side before it's received a status update from the browser.)
+ DNS_PROBE_POSSIBLE,
+
+ // A DNS probe will not be run for this error page. (This happens if the
+ // user has the "Use web service to resolve navigation errors" preference
+ // turned off, or if probes are disabled by the field trial.)
+ DNS_PROBE_NOT_RUN,
+
+ // A DNS probe has been started for this error page. The renderer should
+ // expect to receive another IPC with one of the FINISHED statuses once the
+ // probe has finished (as long as the error page is still loaded).
+ DNS_PROBE_STARTED,
+
+ // A DNS probe has finished with one of the following results:
+
+ // The probe was inconclusive.
+ DNS_PROBE_FINISHED_INCONCLUSIVE,
+
+ // There's no internet connection.
+ DNS_PROBE_FINISHED_NO_INTERNET,
+
+ // The DNS configuration is wrong, or the servers are down or broken.
+ DNS_PROBE_FINISHED_BAD_CONFIG,
+
+ // The DNS servers are working fine, so the domain must not exist.
+ DNS_PROBE_FINISHED_NXDOMAIN,
+
DNS_PROBE_MAX
};
+// Returns a string representing |status|. It should be simply the name of
+// the value as a string, but don't rely on that. This is presented to the
+// user as part of the DNS error page (as the error code, at the bottom),
+// and is also used in some verbose log messages.
+//
+// |status| is an int because error codes are ints by the time they get to the
+// localized error system, and we don't want to require the caller to cast back
+// to a probe status. The function will NOTREACHED() and return an empty
+// string if given an int that does not match a value in DnsProbeStatus (or if
+// it is DNS_PROBE_MAX, which is not a real status).
+const char* DnsProbeStatusToString(int status);
+
+// Returns true if |status| is one of the DNS_PROBE_FINISHED_* statuses.
+bool DnsProbeStatusIsFinished(DnsProbeStatus status);
+
+// Returns true if DNS probes are enabled (by the DnsProbe-Enable field trial),
+// or false if they are disabled or the field trial wasn't found.
+bool DnsProbesEnabledByFieldTrial();
+
+// The error domain used to pass DNS probe statuses to the localized error
+// code.
+extern const char kDnsProbeErrorDomain[];
+
} // namespace chrome_common_net
#endif // CHROME_COMMON_NET_NET_ERROR_INFO_H_
diff --git a/chrome/common/net/net_error_tracker.cc b/chrome/common/net/net_error_tracker.cc
deleted file mode 100644
index 6cf69ca..0000000
--- a/chrome/common/net/net_error_tracker.cc
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 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 "chrome/common/net/net_error_tracker.h"
-
-NetErrorTracker::NetErrorTracker(const Callback& callback)
- : callback_(callback),
- load_state_(LOAD_NONE),
- load_type_(PAGE_NORMAL),
- error_type_(ERROR_OTHER),
- dns_error_page_state_(DNS_ERROR_PAGE_NONE) {
-}
-
-NetErrorTracker::~NetErrorTracker() {
-}
-
-void NetErrorTracker::OnStartProvisionalLoad(FrameType frame, PageType page) {
- if (frame == FRAME_SUB)
- return;
-
- load_state_ = LOAD_STARTED;
- load_type_ = page;
-
- // TODO(ttuttle): Add support for aborts, then move this to OnCommit.
- if (load_type_ == PAGE_NORMAL)
- SetDnsErrorPageState(DNS_ERROR_PAGE_NONE);
-}
-
-void NetErrorTracker::OnCommitProvisionalLoad(FrameType frame) {
- if (frame == FRAME_SUB)
- return;
-
- load_state_ = LOAD_COMMITTED;
-}
-
-void NetErrorTracker::OnFailProvisionalLoad(FrameType frame, ErrorType error) {
- if (frame == FRAME_SUB)
- return;
-
- load_state_ = LOAD_FAILED;
-
- if (load_type_ == PAGE_NORMAL) {
- error_type_ = error;
- if (error_type_ == ERROR_DNS)
- SetDnsErrorPageState(DNS_ERROR_PAGE_PENDING);
- }
-}
-
-void NetErrorTracker::OnFinishLoad(FrameType frame) {
- if (frame == FRAME_SUB)
- return;
-
- load_state_ = LOAD_FINISHED;
-
- if (load_type_ == PAGE_ERROR && error_type_ == ERROR_DNS)
- SetDnsErrorPageState(DNS_ERROR_PAGE_LOADED);
-}
-
-void NetErrorTracker::SetDnsErrorPageState(DnsErrorPageState state) {
- if (state == dns_error_page_state_)
- return;
-
- dns_error_page_state_ = state;
- callback_.Run(state);
-}
diff --git a/chrome/common/net/net_error_tracker.h b/chrome/common/net/net_error_tracker.h
deleted file mode 100644
index 530abcb..0000000
--- a/chrome/common/net/net_error_tracker.h
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 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.
-
-#ifndef CHROME_COMMON_NET_NET_ERROR_TRACKER_H_
-#define CHROME_COMMON_NET_NET_ERROR_TRACKER_H_
-
-#include "base/bind.h"
-
-class NetErrorTracker {
- public:
- enum FrameType {
- FRAME_SUB,
- FRAME_MAIN
- };
-
- enum PageType {
- PAGE_NORMAL,
- PAGE_ERROR
- };
-
- enum ErrorType {
- ERROR_OTHER,
- ERROR_DNS
- };
-
- enum DnsErrorPageState {
- DNS_ERROR_PAGE_NONE,
- DNS_ERROR_PAGE_PENDING,
- DNS_ERROR_PAGE_LOADED
- };
-
- typedef base::Callback<void(DnsErrorPageState state)> Callback;
-
- explicit NetErrorTracker(const Callback& callback);
- ~NetErrorTracker();
-
- void OnStartProvisionalLoad(FrameType frame, PageType page);
- void OnCommitProvisionalLoad(FrameType frame);
- void OnFailProvisionalLoad(FrameType frame, ErrorType error);
- void OnFinishLoad(FrameType frame);
-
- private:
- enum LoadState {
- LOAD_NONE,
- LOAD_STARTED,
- LOAD_COMMITTED,
- LOAD_FAILED,
- LOAD_FINISHED
- };
-
- void SetDnsErrorPageState(DnsErrorPageState state);
-
- Callback callback_;
-
- LoadState load_state_;
- PageType load_type_;
-
- ErrorType error_type_;
-
- DnsErrorPageState dns_error_page_state_;
-
- DISALLOW_COPY_AND_ASSIGN(NetErrorTracker);
-};
-
-#endif // CHROME_COMMON_NET_NET_ERROR_TRACKER_H_
diff --git a/chrome/common/net/net_error_tracker_unittest.cc b/chrome/common/net/net_error_tracker_unittest.cc
deleted file mode 100644
index 7253deb..0000000
--- a/chrome/common/net/net_error_tracker_unittest.cc
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright (c) 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 "chrome/common/net/net_error_tracker.h"
-
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace {
-
-typedef NetErrorTracker::FrameType FrameType;
-typedef NetErrorTracker::PageType PageType;
-typedef NetErrorTracker::ErrorType ErrorType;
-
-const FrameType FRAME_SUB = NetErrorTracker::FRAME_SUB;
-const FrameType FRAME_MAIN = NetErrorTracker::FRAME_MAIN;
-
-const PageType PAGE_NORMAL = NetErrorTracker::PAGE_NORMAL;
-const PageType PAGE_ERROR = NetErrorTracker::PAGE_ERROR;
-
-const ErrorType ERROR_OTHER = NetErrorTracker::ERROR_OTHER;
-const ErrorType ERROR_DNS = NetErrorTracker::ERROR_DNS;
-
-class NetErrorTrackerTest : public testing::Test {
- public:
- NetErrorTrackerTest()
- : tracker_(base::Bind(&NetErrorTrackerTest::TrackerCallback,
- base::Unretained(this))),
- callback_state_(NetErrorTracker::DNS_ERROR_PAGE_NONE),
- callback_count_(0) {
- }
-
- protected:
- NetErrorTracker tracker_;
- NetErrorTracker::DnsErrorPageState callback_state_;
- int callback_count_;
-
- private:
- void TrackerCallback(NetErrorTracker::DnsErrorPageState state) {
- callback_state_ = state;
- ++callback_count_;
- }
-};
-
-TEST_F(NetErrorTrackerTest, InitialState) {
- EXPECT_EQ(0, callback_count_);
- EXPECT_EQ(NetErrorTracker::DNS_ERROR_PAGE_NONE, callback_state_);
-}
-
-TEST_F(NetErrorTrackerTest, SuccessfulMainFrameLoad) {
- tracker_.OnStartProvisionalLoad(FRAME_MAIN, PAGE_NORMAL);
- tracker_.OnCommitProvisionalLoad(FRAME_MAIN);
- tracker_.OnFinishLoad(FRAME_MAIN);
-
- EXPECT_EQ(0, callback_count_);
- EXPECT_EQ(NetErrorTracker::DNS_ERROR_PAGE_NONE, callback_state_);
-}
-
-TEST_F(NetErrorTrackerTest, SuccessfulSubFrameLoad) {
- tracker_.OnStartProvisionalLoad(FRAME_SUB, PAGE_NORMAL);
- tracker_.OnCommitProvisionalLoad(FRAME_SUB);
- tracker_.OnFinishLoad(FRAME_SUB);
-
- EXPECT_EQ(0, callback_count_);
- EXPECT_EQ(NetErrorTracker::DNS_ERROR_PAGE_NONE, callback_state_);
-}
-
-TEST_F(NetErrorTrackerTest, FailedMainFrameLoad) {
- tracker_.OnStartProvisionalLoad(FRAME_MAIN, PAGE_NORMAL);
- EXPECT_EQ(0, callback_count_);
- EXPECT_EQ(NetErrorTracker::DNS_ERROR_PAGE_NONE, callback_state_);
-
- tracker_.OnFailProvisionalLoad(FRAME_MAIN, ERROR_DNS);
- EXPECT_EQ(1, callback_count_);
- EXPECT_EQ(NetErrorTracker::DNS_ERROR_PAGE_PENDING, callback_state_);
-
- tracker_.OnStartProvisionalLoad(FRAME_MAIN, PAGE_ERROR);
- tracker_.OnCommitProvisionalLoad(FRAME_MAIN);
- tracker_.OnFinishLoad(FRAME_MAIN);
- EXPECT_EQ(2, callback_count_);
- EXPECT_EQ(NetErrorTracker::DNS_ERROR_PAGE_LOADED, callback_state_);
-
- tracker_.OnStartProvisionalLoad(FRAME_MAIN, PAGE_NORMAL);
- EXPECT_EQ(3, callback_count_);
- EXPECT_EQ(NetErrorTracker::DNS_ERROR_PAGE_NONE, callback_state_);
-}
-
-TEST_F(NetErrorTrackerTest, FailedSubFrameLoad) {
- tracker_.OnStartProvisionalLoad(FRAME_SUB, PAGE_NORMAL);
- tracker_.OnFailProvisionalLoad(FRAME_SUB, ERROR_DNS);
- tracker_.OnStartProvisionalLoad(FRAME_SUB, PAGE_ERROR);
- tracker_.OnCommitProvisionalLoad(FRAME_SUB);
- tracker_.OnFinishLoad(FRAME_SUB);
- EXPECT_EQ(0, callback_count_);
- EXPECT_EQ(NetErrorTracker::DNS_ERROR_PAGE_NONE, callback_state_);
-}
-
-} // namespace
diff --git a/chrome/common/render_messages.h b/chrome/common/render_messages.h
index fc065fb..fee44d6 100644
--- a/chrome/common/render_messages.h
+++ b/chrome/common/render_messages.h
@@ -381,7 +381,7 @@ IPC_MESSAGE_ROUTED0(ChromeViewMsg_SetAsInterstitial)
// NetErrorHelper will receive this mesage and replace or update the error
// page with more specific troubleshooting suggestions.
IPC_MESSAGE_ROUTED1(ChromeViewMsg_NetErrorInfo,
- int /* DNS probe result */)
+ int /* DNS probe status */)
//-----------------------------------------------------------------------------
// Misc messages
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index 45f9029..79d57a7 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -849,7 +849,7 @@ bool ChromeContentRendererClient::HasErrorPage(int http_status_code,
}
void ChromeContentRendererClient::GetNavigationErrorStrings(
- WebKit::WebFrame* /* frame */,
+ WebKit::WebFrame* frame,
const WebKit::WebURLRequest& failed_request,
const WebKit::WebURLError& error,
std::string* error_html,
@@ -877,11 +877,13 @@ void ChromeContentRendererClient::GetNavigationErrorStrings(
// error messages?
resource_id = IDR_ERROR_APP_HTML;
} else {
- LocalizedError::GetStrings(
- error,
- is_post,
- RenderThread::Get()->GetLocale(),
- &error_strings);
+ const std::string locale = RenderThread::Get()->GetLocale();
+ if (!NetErrorHelper::GetErrorStringsForDnsProbe(
+ frame, error, is_post, locale, &error_strings)) {
+ // In most cases, the NetErrorHelper won't provide DNS-probe-specific
+ // error pages, so fall back to LocalizedError.
+ LocalizedError::GetStrings(error, is_post, locale, &error_strings);
+ }
resource_id = IDR_NET_ERROR_HTML;
}
diff --git a/chrome/renderer/net/net_error_helper.cc b/chrome/renderer/net/net_error_helper.cc
index d73386d..46a8e0e 100644
--- a/chrome/renderer/net/net_error_helper.cc
+++ b/chrome/renderer/net/net_error_helper.cc
@@ -4,6 +4,10 @@
#include "chrome/renderer/net/net_error_helper.h"
+#include <string>
+
+#include "base/json/json_writer.h"
+#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/common/localized_error.h"
#include "chrome/common/net/net_error_info.h"
@@ -17,13 +21,16 @@
#include "ipc/ipc_message_macros.h"
#include "net/base/net_errors.h"
#include "third_party/WebKit/public/platform/WebURL.h"
-#include "third_party/WebKit/public/platform/WebURLError.h"
#include "third_party/WebKit/public/platform/WebURLRequest.h"
#include "third_party/WebKit/public/web/WebDataSource.h"
#include "third_party/WebKit/public/web/WebFrame.h"
#include "url/gurl.h"
-using chrome_common_net::DnsProbeResult;
+using base::JSONWriter;
+using chrome_common_net::DnsProbeStatus;
+using chrome_common_net::DnsProbeStatusIsFinished;
+using chrome_common_net::DnsProbeStatusToString;
+using chrome_common_net::DnsProbesEnabledByFieldTrial;
using content::RenderThread;
using content::RenderView;
using content::RenderViewObserver;
@@ -31,75 +38,31 @@ using content::kUnreachableWebDataURL;
namespace {
-GURL GetProvisionallyLoadingURLFromWebFrame(WebKit::WebFrame* frame) {
- return frame->provisionalDataSource()->request().url();
+bool IsLoadingErrorPage(WebKit::WebFrame* frame) {
+ GURL url = frame->provisionalDataSource()->request().url();
+ return url.spec() == kUnreachableWebDataURL;
}
-bool IsErrorPage(const GURL& url) {
- return (url.spec() == kUnreachableWebDataURL);
+bool IsMainFrame(const WebKit::WebFrame* frame) {
+ return !frame->parent();
}
// Returns whether |net_error| is a DNS-related error (and therefore whether
// the tab helper should start a DNS probe after receiving it.)
-bool IsDnsError(int net_error) {
- return net_error == net::ERR_NAME_NOT_RESOLVED ||
- net_error == net::ERR_NAME_RESOLUTION_FAILED;
-}
-
-NetErrorTracker::FrameType GetFrameType(WebKit::WebFrame* frame) {
- return frame->parent() ? NetErrorTracker::FRAME_SUB
- : NetErrorTracker::FRAME_MAIN;
-}
-
-NetErrorTracker::PageType GetPageType(WebKit::WebFrame* frame) {
- bool error_page = IsErrorPage(GetProvisionallyLoadingURLFromWebFrame(frame));
- return error_page ? NetErrorTracker::PAGE_ERROR
- : NetErrorTracker::PAGE_NORMAL;
-}
-
-NetErrorTracker::ErrorType GetErrorType(const WebKit::WebURLError& error) {
- return IsDnsError(error.reason) ? NetErrorTracker::ERROR_DNS
- : NetErrorTracker::ERROR_OTHER;
-}
-
-// Converts a DNS probe result into a net error. Returns OK if the error page
-// should not be changed from the original DNS error.
-int DnsProbeResultToNetError(DnsProbeResult result) {
- switch (result) {
- case chrome_common_net::DNS_PROBE_UNKNOWN:
- return net::OK;
- case chrome_common_net::DNS_PROBE_NO_INTERNET:
- // TODO(ttuttle): This is not the same error as when NCN returns this;
- // ideally we should have two separate error codes for "no network" and
- // "network with no internet".
- return net::ERR_INTERNET_DISCONNECTED;
- case chrome_common_net::DNS_PROBE_BAD_CONFIG:
- // This is unspecific enough that we should still show the full DNS error
- // page.
- return net::OK;
- case chrome_common_net::DNS_PROBE_NXDOMAIN:
- return net::ERR_NAME_NOT_RESOLVED;
- default:
- NOTREACHED();
- return net::OK;
- }
-}
-
-WebKit::WebURLError NetErrorToWebURLError(int net_error) {
- WebKit::WebURLError error;
- error.domain = WebKit::WebString::fromUTF8(net::kErrorDomain);
- error.reason = net_error;
- return error;
+bool IsDnsError(const WebKit::WebURLError& error) {
+ return std::string(error.domain.utf8()) == net::kErrorDomain &&
+ (error.reason == net::ERR_NAME_NOT_RESOLVED ||
+ error.reason == net::ERR_NAME_RESOLUTION_FAILED);
}
} // namespace
NetErrorHelper::NetErrorHelper(RenderView* render_view)
: RenderViewObserver(render_view),
- tracker_(base::Bind(&NetErrorHelper::TrackerCallback,
- base::Unretained(this))),
- dns_error_page_state_(NetErrorTracker::DNS_ERROR_PAGE_NONE),
- updated_error_page_(false),
+ last_probe_status_(chrome_common_net::DNS_PROBE_POSSIBLE),
+ last_start_was_error_page_(false),
+ last_fail_was_dns_error_(false),
+ forwarding_probe_results_(false),
is_failed_post_(false) {
}
@@ -107,84 +70,185 @@ NetErrorHelper::~NetErrorHelper() {
}
void NetErrorHelper::DidStartProvisionalLoad(WebKit::WebFrame* frame) {
- tracker_.OnStartProvisionalLoad(GetFrameType(frame), GetPageType(frame));
+ OnStartLoad(IsMainFrame(frame), IsLoadingErrorPage(frame));
}
void NetErrorHelper::DidFailProvisionalLoad(WebKit::WebFrame* frame,
const WebKit::WebURLError& error) {
- WebKit::WebDataSource* data_source = frame->provisionalDataSource();
- const WebKit::WebURLRequest& failed_request = data_source->request();
- is_failed_post_ = EqualsASCII(failed_request.httpMethod(), "POST");
- tracker_.OnFailProvisionalLoad(GetFrameType(frame), GetErrorType(error));
+ const bool main_frame = IsMainFrame(frame);
+ const bool dns_error = IsDnsError(error);
+
+ OnFailLoad(main_frame, dns_error);
+
+ if (main_frame && dns_error) {
+ last_error_ = error;
+
+ WebKit::WebDataSource* data_source = frame->provisionalDataSource();
+ const WebKit::WebURLRequest& failed_request = data_source->request();
+ is_failed_post_ = EqualsASCII(failed_request.httpMethod(), "POST");
+ }
}
void NetErrorHelper::DidCommitProvisionalLoad(WebKit::WebFrame* frame,
bool is_new_navigation) {
- tracker_.OnCommitProvisionalLoad(GetFrameType(frame));
+ OnCommitLoad(IsMainFrame(frame));
}
void NetErrorHelper::DidFinishLoad(WebKit::WebFrame* frame) {
- tracker_.OnFinishLoad(GetFrameType(frame));
+ OnFinishLoad(IsMainFrame(frame));
}
-bool NetErrorHelper::OnMessageReceived(const IPC::Message& message) {
- bool handled = true;
-
- IPC_BEGIN_MESSAGE_MAP(NetErrorHelper, message)
- IPC_MESSAGE_HANDLER(ChromeViewMsg_NetErrorInfo, OnNetErrorInfo)
- IPC_MESSAGE_UNHANDLED(handled = false)
- IPC_END_MESSAGE_MAP()
+void NetErrorHelper::OnStartLoad(bool is_main_frame, bool is_error_page) {
+ DVLOG(1) << "OnStartLoad(is_main_frame=" << is_main_frame
+ << ", is_error_page=" << is_error_page << ")";
+ if (!is_main_frame)
+ return;
- return handled;
+ last_start_was_error_page_ = is_error_page;
}
-void NetErrorHelper::OnNetErrorInfo(int dns_probe_result) {
- DVLOG(1) << "Received DNS probe result " << dns_probe_result;
+void NetErrorHelper::OnFailLoad(bool is_main_frame, bool is_dns_error) {
+ DVLOG(1) << "OnFailLoad(is_main_frame=" << is_main_frame
+ << ", is_dns_error=" << is_dns_error << ")";
- if (dns_probe_result < 0 ||
- dns_probe_result >= chrome_common_net::DNS_PROBE_MAX) {
- DLOG(WARNING) << "Ignoring DNS probe result: invalid result "
- << dns_probe_result;
- NOTREACHED();
+ if (!is_main_frame)
return;
+
+ last_fail_was_dns_error_ = is_dns_error;
+
+ if (is_dns_error) {
+ last_probe_status_ = chrome_common_net::DNS_PROBE_POSSIBLE;
+ // If the helper was forwarding probe results and another DNS error has
+ // occurred, stop forwarding probe results until the corresponding (new)
+ // error page loads.
+ forwarding_probe_results_ = false;
}
+}
- if (dns_error_page_state_ != NetErrorTracker::DNS_ERROR_PAGE_LOADED) {
- DVLOG(1) << "Ignoring DNS probe result: not on DNS error page.";
+void NetErrorHelper::OnCommitLoad(bool is_main_frame) {
+ DVLOG(1) << "OnCommitLoad(is_main_frame=" << is_main_frame << ")";
+
+ if (!is_main_frame)
return;
- }
- if (updated_error_page_) {
- DVLOG(1) << "Ignoring DNS probe result: already updated error page.";
+ // Stop forwarding results. If the page is a DNS error page, forwarding
+ // will resume once the page is loaded; if not, it should stay stopped until
+ // the next DNS error page.
+ forwarding_probe_results_ = false;
+}
+
+void NetErrorHelper::OnFinishLoad(bool is_main_frame) {
+ DVLOG(1) << "OnFinishLoad(is_main_frame=" << is_main_frame << ")";
+
+ if (!is_main_frame)
return;
+
+ // If a DNS error page just finished loading, start forwarding probe results
+ // to it.
+ forwarding_probe_results_ =
+ last_fail_was_dns_error_ && last_start_was_error_page_;
+
+ if (forwarding_probe_results_ &&
+ last_probe_status_ != chrome_common_net::DNS_PROBE_POSSIBLE) {
+ DVLOG(1) << "Error page finished loading; sending saved status.";
+ UpdateErrorPage();
}
+}
+
+bool NetErrorHelper::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
- UpdateErrorPage(static_cast<DnsProbeResult>(dns_probe_result));
- updated_error_page_ = true;
+ IPC_BEGIN_MESSAGE_MAP(NetErrorHelper, message)
+ IPC_MESSAGE_HANDLER(ChromeViewMsg_NetErrorInfo, OnNetErrorInfo)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+
+ return handled;
}
-void NetErrorHelper::TrackerCallback(
- NetErrorTracker::DnsErrorPageState state) {
- dns_error_page_state_ = state;
+bool NetErrorHelper::GetErrorStringsForDnsProbe(
+ WebKit::WebFrame* frame,
+ const WebKit::WebURLError& error,
+ bool is_failed_post,
+ const std::string& locale,
+ base::DictionaryValue* error_strings) {
+ if (!IsMainFrame(frame))
+ return false;
- if (state == NetErrorTracker::DNS_ERROR_PAGE_LOADED)
- updated_error_page_ = false;
+ if (!IsDnsError(error))
+ return false;
+
+ // Get the strings for a fake "DNS probe possible" error.
+ WebKit::WebURLError fake_error;
+ fake_error.domain = WebKit::WebString::fromUTF8(
+ chrome_common_net::kDnsProbeErrorDomain);
+ fake_error.reason = chrome_common_net::DNS_PROBE_POSSIBLE;
+ fake_error.unreachableURL = error.unreachableURL;
+ LocalizedError::GetStrings(
+ fake_error, is_failed_post, locale, error_strings);
+ return true;
}
-void NetErrorHelper::UpdateErrorPage(DnsProbeResult dns_probe_result) {
- DVLOG(1) << "Updating error page with result " << dns_probe_result;
+void NetErrorHelper::OnNetErrorInfo(int status_num) {
+ DCHECK(status_num >= 0 && status_num < chrome_common_net::DNS_PROBE_MAX);
+
+ DVLOG(1) << "Received status " << DnsProbeStatusToString(status_num);
- int net_error = DnsProbeResultToNetError(dns_probe_result);
- if (net_error == net::OK)
+ DnsProbeStatus status = static_cast<DnsProbeStatus>(status_num);
+ DCHECK_NE(chrome_common_net::DNS_PROBE_POSSIBLE, status);
+
+ if (!(last_fail_was_dns_error_ || forwarding_probe_results_)) {
+ DVLOG(1) << "Ignoring NetErrorInfo: no DNS error";
return;
+ }
+
+ last_probe_status_ = status;
- DVLOG(1) << "net error code is " << net_error;
+ if (forwarding_probe_results_)
+ UpdateErrorPage();
+}
+
+void NetErrorHelper::UpdateErrorPage() {
+ DCHECK(forwarding_probe_results_);
base::DictionaryValue error_strings;
- LocalizedError::GetStrings(NetErrorToWebURLError(net_error),
+ LocalizedError::GetStrings(GetUpdatedError(),
is_failed_post_,
RenderThread::Get()->GetLocale(),
&error_strings);
- // TODO(ttuttle): Update error page with error_strings.
+ std::string json;
+ JSONWriter::Write(&error_strings, &json);
+
+ std::string js = "if (window.updateForDnsProbe) "
+ "updateForDnsProbe(" + json + ");";
+ string16 js16;
+ if (!UTF8ToUTF16(js.c_str(), js.length(), &js16)) {
+ NOTREACHED();
+ return;
+ }
+
+ DVLOG(1) << "Updating error page with status "
+ << chrome_common_net::DnsProbeStatusToString(last_probe_status_);
+ DVLOG(2) << "New strings: " << js;
+
+ string16 frame_xpath;
+ render_view()->EvaluateScript(frame_xpath, js16, 0, false);
+}
+
+WebKit::WebURLError NetErrorHelper::GetUpdatedError() const {
+ // If a probe didn't run or wasn't conclusive, restore the original error.
+ if (last_probe_status_ == chrome_common_net::DNS_PROBE_NOT_RUN ||
+ last_probe_status_ ==
+ chrome_common_net::DNS_PROBE_FINISHED_INCONCLUSIVE) {
+ return last_error_;
+ }
+
+ WebKit::WebURLError error;
+ error.domain = WebKit::WebString::fromUTF8(
+ chrome_common_net::kDnsProbeErrorDomain);
+ error.reason = last_probe_status_;
+ error.unreachableURL = last_error_.unreachableURL;
+
+ return error;
}
diff --git a/chrome/renderer/net/net_error_helper.h b/chrome/renderer/net/net_error_helper.h
index cb33387e..84cdcb4 100644
--- a/chrome/renderer/net/net_error_helper.h
+++ b/chrome/renderer/net/net_error_helper.h
@@ -5,14 +5,23 @@
#ifndef CHROME_RENDERER_NET_NET_ERROR_HELPER_H_
#define CHROME_RENDERER_NET_NET_ERROR_HELPER_H_
+#include <string>
+
#include "chrome/common/net/net_error_info.h"
-#include "chrome/common/net/net_error_tracker.h"
#include "content/public/renderer/render_view_observer.h"
+#include "third_party/WebKit/public/platform/WebURLError.h"
+
+namespace base {
+class DictionaryValue;
+}
namespace WebKit {
class WebFrame;
}
+// Listens for NetErrorInfo messages from the NetErrorTabHelper on the
+// browser side and updates the error page with more details (currently, just
+// DNS probe results) if/when available.
class NetErrorHelper : public content::RenderViewObserver {
public:
explicit NetErrorHelper(content::RenderView* render_view);
@@ -31,14 +40,61 @@ class NetErrorHelper : public content::RenderViewObserver {
// IPC::Listener implementation.
virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+ // Examines |frame| and |error| to see if this is an error worthy of a DNS
+ // probe. If it is, initializes |error_strings| based on |error|,
+ // |is_failed_post|, and |locale| with suitable strings and returns true.
+ // If not, returns false, in which case the caller should look up error
+ // strings directly using LocalizedError::GetNavigationErrorStrings.
+ static bool GetErrorStringsForDnsProbe(
+ WebKit::WebFrame* frame,
+ const WebKit::WebURLError& error,
+ bool is_failed_post,
+ const std::string& locale,
+ base::DictionaryValue* error_strings);
+
+ protected:
+ // These methods handle tracking the actual state of the page; this allows
+ // unit-testing of the state tracking without having to mock out WebFrames
+ // and such.
+ void OnStartLoad(bool is_main_frame, bool is_error_page);
+ void OnFailLoad(bool is_main_frame, bool is_dns_error);
+ void OnCommitLoad(bool is_main_frame);
+ void OnFinishLoad(bool is_main_frame);
+
+ void OnNetErrorInfo(int status);
+
+ // |UpdateErrorPage| is virtual so it can be mocked out in the unittest.
+ virtual void UpdateErrorPage();
+
+ // The last DnsProbeStatus received from the browser.
+ chrome_common_net::DnsProbeStatus last_probe_status_;
+
private:
- void OnNetErrorInfo(int dns_probe_result);
- void TrackerCallback(NetErrorTracker::DnsErrorPageState state);
- void UpdateErrorPage(chrome_common_net::DnsProbeResult dns_probe_result);
+ WebKit::WebURLError GetUpdatedError() const;
+
+ // Whether the last provisional load started was for an error page.
+ bool last_start_was_error_page_;
+
+ // Whether the last provisional load failure failed with a DNS error.
+ bool last_fail_was_dns_error_;
+
+ // Ideally, this would be simply "last_commit_was_dns_error_page_".
+ //
+ // Unfortunately, that breaks if two DNS errors occur in a row; after the
+ // second failure, but before the second page commits, the helper can receive
+ // probe results. If all it knows is that the last commit was a DNS error
+ // page, it will cheerfully forward the results for the second probe to the
+ // first page.
+ //
+ // Thus, the semantics of this flag are a little weird. It is set whenever
+ // a DNS error page commits, and cleared whenever any other page commits,
+ // but it is also cleared whenever a DNS error occurs, to prevent the race
+ // described above.
+ bool forwarding_probe_results_;
+
+ // The last main frame error seen by the helper.
+ WebKit::WebURLError last_error_;
- NetErrorTracker tracker_;
- NetErrorTracker::DnsErrorPageState dns_error_page_state_;
- bool updated_error_page_;
bool is_failed_post_;
};
diff --git a/chrome/renderer/net/net_error_helper_unittest.cc b/chrome/renderer/net/net_error_helper_unittest.cc
new file mode 100644
index 0000000..828c34e
--- /dev/null
+++ b/chrome/renderer/net/net_error_helper_unittest.cc
@@ -0,0 +1,412 @@
+// 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 "chrome/renderer/net/net_error_helper.h"
+
+#include "base/logging.h"
+#include "chrome/common/net/net_error_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using chrome_common_net::DnsProbeStatus;
+using chrome_common_net::DnsProbeStatusToString;
+
+// NetErrorHelperTest cases consist of a string of these steps.
+enum TestStep {
+ // Simulate a provisional load start, fail, commit or a finish-load event.
+ // (Start and fail differentiate between normal and error pages.)
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, LOAD_ERROR_START,
+ LOAD_COMMIT, LOAD_FINISH,
+
+ // Simulate an IPC from the browser with DNS_PROBE_STARTED, _NOT_RUN, or
+ // _FINISHED_NXDOMAIN.
+ STATUS_STARTED, STATUS_NOT_RUN, STATUS_FINISHED,
+
+ // Expect that the *next* step will cause an update. (Any step that is not
+ // prefixed by this pseudo-step is expected *not* to cause an update.)
+ EXPECT_UPDATE
+};
+
+class TestNetErrorHelper : public NetErrorHelper {
+ public:
+ TestNetErrorHelper()
+ : NetErrorHelper(NULL),
+ mock_page_update_count_(0),
+ mock_displayed_probe_status_(chrome_common_net::DNS_PROBE_MAX) {}
+
+ virtual ~TestNetErrorHelper() {}
+
+ void StartLoad(bool is_main_frame, bool is_error_page) {
+ OnStartLoad(is_main_frame, is_error_page);
+ }
+
+ void FailLoad(bool is_main_frame, bool is_dns_error) {
+ OnFailLoad(is_main_frame, is_dns_error);
+ }
+
+ void CommitLoad(bool is_main_frame) {
+ OnCommitLoad(is_main_frame);
+ }
+
+ void FinishLoad(bool is_main_frame) {
+ OnFinishLoad(is_main_frame);
+ }
+
+ void ReceiveProbeStatus(DnsProbeStatus status) {
+ OnNetErrorInfo(static_cast<int>(status));
+ }
+
+ int mock_page_update_count() const { return mock_page_update_count_; }
+ DnsProbeStatus mock_displayed_probe_status() const {
+ return mock_displayed_probe_status_;
+ }
+
+ protected:
+ virtual void UpdateErrorPage() OVERRIDE {
+ DVLOG(1) << "Updating error page with status "
+ << DnsProbeStatusToString(last_probe_status_);
+ mock_page_update_count_++;
+ mock_displayed_probe_status_ = last_probe_status_;
+ }
+
+ private:
+ int mock_page_update_count_;
+ DnsProbeStatus mock_displayed_probe_status_;
+};
+
+class NetErrorHelperTest : public testing::Test {
+ protected:
+ enum MainFrame { SUB_FRAME, MAIN_FRAME };
+ enum ErrorPage { NORMAL_PAGE, ERROR_PAGE };
+ enum ErrorType { OTHER_ERROR, DNS_ERROR };
+
+ void StartLoad(MainFrame main_frame, ErrorPage error_page) {
+ helper_.StartLoad(main_frame == MAIN_FRAME, error_page == ERROR_PAGE);
+ }
+
+ void FailLoad(MainFrame main_frame, ErrorType error_type) {
+ helper_.FailLoad(main_frame == MAIN_FRAME, error_type == DNS_ERROR);
+ }
+
+ void CommitLoad(MainFrame main_frame) {
+ helper_.CommitLoad(main_frame == MAIN_FRAME);
+ }
+
+ void FinishLoad(MainFrame main_frame) {
+ helper_.FinishLoad(main_frame == MAIN_FRAME);
+ }
+
+ void ReceiveProbeStatus(DnsProbeStatus status) {
+ helper_.ReceiveProbeStatus(status);
+ }
+
+ void RunTest(const TestStep steps[], int step_count);
+
+ int page_update_count() const { return helper_.mock_page_update_count(); }
+ DnsProbeStatus displayed_probe_status() const {
+ return helper_.mock_displayed_probe_status();
+ }
+
+ private:
+ TestNetErrorHelper helper_;
+};
+
+void NetErrorHelperTest::RunTest(const TestStep steps[], int step_count) {
+ // Whether the next instruction is expected to cause an update (since the
+ // step right before it was EXPECT_UPDATE) or not.
+ bool update_expected = false;
+ int expected_update_count = page_update_count();
+ // The last status that the test simulated receiving from the browser.
+ // When an update is expected, the status is expected to match this.
+ chrome_common_net::DnsProbeStatus last_status_received =
+ chrome_common_net::DNS_PROBE_POSSIBLE;
+
+ for (int i = 0; i < step_count; i++) {
+ switch (steps[i]) {
+ case LOAD_NORMAL_START:
+ StartLoad(MAIN_FRAME, NORMAL_PAGE);
+ break;
+ case LOAD_NORMAL_FAIL:
+ FailLoad(MAIN_FRAME, DNS_ERROR);
+ break;
+ case LOAD_ERROR_START:
+ StartLoad(MAIN_FRAME, ERROR_PAGE);
+ break;
+ case LOAD_COMMIT:
+ CommitLoad(MAIN_FRAME);
+ break;
+ case LOAD_FINISH:
+ FinishLoad(MAIN_FRAME);
+ break;
+ case STATUS_STARTED:
+ ReceiveProbeStatus(chrome_common_net::DNS_PROBE_STARTED);
+ last_status_received = chrome_common_net::DNS_PROBE_STARTED;
+ break;
+ case STATUS_NOT_RUN:
+ ReceiveProbeStatus(chrome_common_net::DNS_PROBE_NOT_RUN);
+ last_status_received = chrome_common_net::DNS_PROBE_NOT_RUN;
+ break;
+ case STATUS_FINISHED:
+ ReceiveProbeStatus(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN);
+ last_status_received = chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN;
+ break;
+ case EXPECT_UPDATE:
+ ASSERT_FALSE(update_expected);
+ update_expected = true;
+ // Skip to next step to see if it updates the status, instead of
+ // checking whether EXPECT_UPDATE itself caused an update.
+ continue;
+ }
+
+ if (update_expected) {
+ DCHECK_NE(chrome_common_net::DNS_PROBE_POSSIBLE, last_status_received);
+ ++expected_update_count;
+
+ EXPECT_EQ(last_status_received, displayed_probe_status());
+ if (displayed_probe_status() != last_status_received) {
+ LOG(ERROR) << "Unexpected status at step " << i << ".";
+ return;
+ }
+ }
+
+ EXPECT_EQ(expected_update_count, page_update_count());
+ if (page_update_count() != expected_update_count) {
+ LOG(ERROR) << (update_expected ? "Missing" : "Spurious")
+ << " update at step " << i << ".";
+ return;
+ }
+
+ update_expected = false;
+ }
+
+ DCHECK(!update_expected);
+}
+
+TEST_F(NetErrorHelperTest, Null) {
+ // Test that we can simply create and destroy a NetErrorHelper.
+}
+
+TEST_F(NetErrorHelperTest, SuccessfulPageLoad) {
+ StartLoad(MAIN_FRAME, NORMAL_PAGE);
+ CommitLoad(MAIN_FRAME);
+ FinishLoad(MAIN_FRAME);
+
+ // Ignore spurious status.
+ ReceiveProbeStatus(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(0, page_update_count());
+}
+
+TEST_F(NetErrorHelperTest, MainFrameNonDnsError) {
+ StartLoad(MAIN_FRAME, NORMAL_PAGE);
+ FailLoad(MAIN_FRAME, OTHER_ERROR);
+ StartLoad(MAIN_FRAME, ERROR_PAGE);
+ CommitLoad(MAIN_FRAME);
+ FinishLoad(MAIN_FRAME);
+
+ // Ignore spurious status.
+ ReceiveProbeStatus(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(0, page_update_count());
+}
+
+TEST_F(NetErrorHelperTest, SubFrameDnsError) {
+ StartLoad(SUB_FRAME, NORMAL_PAGE);
+ FailLoad(SUB_FRAME, DNS_ERROR);
+ StartLoad(SUB_FRAME, ERROR_PAGE);
+ CommitLoad(SUB_FRAME);
+ FinishLoad(SUB_FRAME);
+
+ // Ignore spurious status.
+ ReceiveProbeStatus(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN);
+ EXPECT_EQ(0, page_update_count());
+}
+
+TEST_F(NetErrorHelperTest, FinishedAfterFail) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, STATUS_FINISHED, LOAD_ERROR_START,
+ LOAD_COMMIT, EXPECT_UPDATE, LOAD_FINISH
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+TEST_F(NetErrorHelperTest, FinishedAfterFail_StartedAfterFail) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, STATUS_STARTED, STATUS_FINISHED,
+ LOAD_ERROR_START, LOAD_COMMIT, EXPECT_UPDATE, LOAD_FINISH
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+TEST_F(NetErrorHelperTest, FinishedAfterStart) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, LOAD_ERROR_START, STATUS_FINISHED,
+ LOAD_COMMIT, EXPECT_UPDATE, LOAD_FINISH
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+TEST_F(NetErrorHelperTest, FinishedAfterStart_StartedAfterFail) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, STATUS_STARTED, LOAD_ERROR_START,
+ STATUS_FINISHED, LOAD_COMMIT, EXPECT_UPDATE, LOAD_FINISH
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+TEST_F(NetErrorHelperTest, FinishedAfterStart_StartedAfterStart) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, STATUS_STARTED, LOAD_ERROR_START,
+ STATUS_FINISHED, LOAD_COMMIT, EXPECT_UPDATE, LOAD_FINISH
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+TEST_F(NetErrorHelperTest, FinishedAfterCommit) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, LOAD_ERROR_START, LOAD_COMMIT,
+ STATUS_FINISHED, EXPECT_UPDATE, LOAD_FINISH
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+TEST_F(NetErrorHelperTest, FinishedAfterCommit_StartedAfterFail) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, STATUS_STARTED, LOAD_ERROR_START,
+ LOAD_COMMIT, STATUS_FINISHED, EXPECT_UPDATE, LOAD_FINISH
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+TEST_F(NetErrorHelperTest, FinishedAfterCommit_StartedAfterStart) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, LOAD_ERROR_START, STATUS_STARTED,
+ LOAD_ERROR_START, LOAD_COMMIT, STATUS_FINISHED, EXPECT_UPDATE, LOAD_FINISH
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+TEST_F(NetErrorHelperTest, FinishedAfterCommit_StartedAfterCommit) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, LOAD_ERROR_START, LOAD_COMMIT,
+ STATUS_STARTED, STATUS_FINISHED, EXPECT_UPDATE, LOAD_FINISH
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+TEST_F(NetErrorHelperTest, FinishedAfterFinish) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, LOAD_ERROR_START, LOAD_COMMIT,
+ LOAD_FINISH, EXPECT_UPDATE, STATUS_FINISHED
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+TEST_F(NetErrorHelperTest, FinishedAfterFinish_StartAfterFail) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, STATUS_STARTED, LOAD_ERROR_START,
+ LOAD_COMMIT, EXPECT_UPDATE, LOAD_FINISH, EXPECT_UPDATE, STATUS_FINISHED
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+TEST_F(NetErrorHelperTest, FinishedAfterFinish_StartAfterStart) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, LOAD_ERROR_START, STATUS_STARTED,
+ LOAD_COMMIT, EXPECT_UPDATE, LOAD_FINISH, EXPECT_UPDATE, STATUS_FINISHED
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+TEST_F(NetErrorHelperTest, FinishedAfterFinish_StartAfterCommit) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, LOAD_ERROR_START, LOAD_COMMIT,
+ STATUS_STARTED, EXPECT_UPDATE, LOAD_FINISH, EXPECT_UPDATE, STATUS_FINISHED
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+TEST_F(NetErrorHelperTest, FinishedAfterFinish_StartAfterFinish) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, LOAD_ERROR_START, LOAD_COMMIT,
+ LOAD_FINISH, EXPECT_UPDATE, STATUS_STARTED, EXPECT_UPDATE, STATUS_FINISHED
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+TEST_F(NetErrorHelperTest, FinishedAfterNewStart) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, LOAD_ERROR_START, LOAD_COMMIT,
+ LOAD_FINISH, LOAD_NORMAL_START, EXPECT_UPDATE, STATUS_FINISHED,
+ LOAD_COMMIT, LOAD_FINISH
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+TEST_F(NetErrorHelperTest, NotRunAfterFail) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, STATUS_NOT_RUN, LOAD_ERROR_START,
+ LOAD_COMMIT, EXPECT_UPDATE, LOAD_FINISH
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+TEST_F(NetErrorHelperTest, NotRunAfterStart) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, LOAD_ERROR_START, STATUS_NOT_RUN,
+ LOAD_COMMIT, EXPECT_UPDATE, LOAD_FINISH
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+TEST_F(NetErrorHelperTest, NotRunAfterCommit) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, LOAD_ERROR_START, LOAD_COMMIT,
+ STATUS_NOT_RUN, EXPECT_UPDATE, LOAD_FINISH
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+TEST_F(NetErrorHelperTest, NotRunAfterFinish) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, LOAD_ERROR_START, LOAD_COMMIT,
+ LOAD_FINISH, EXPECT_UPDATE, STATUS_NOT_RUN
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+TEST_F(NetErrorHelperTest, FinishedAfterNewCommit) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, LOAD_ERROR_START, LOAD_COMMIT,
+ LOAD_FINISH, LOAD_NORMAL_START, LOAD_COMMIT, STATUS_FINISHED, LOAD_FINISH
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+TEST_F(NetErrorHelperTest, FinishedAfterNewFinish) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, LOAD_ERROR_START, LOAD_COMMIT,
+ LOAD_FINISH, LOAD_NORMAL_START, LOAD_COMMIT, LOAD_FINISH, STATUS_FINISHED
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+// Two iterations of FinishedAfterStart_StartAfterFail
+TEST_F(NetErrorHelperTest, TwoProbes_FinishedAfterStart_StartAfterFail) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, STATUS_STARTED, LOAD_ERROR_START,
+ STATUS_FINISHED, LOAD_COMMIT, EXPECT_UPDATE, LOAD_FINISH,
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, STATUS_STARTED, LOAD_ERROR_START,
+ STATUS_FINISHED, LOAD_COMMIT, EXPECT_UPDATE, LOAD_FINISH
+ };
+ RunTest(steps, arraysize(steps));
+}
+
+// Two iterations of FinishedAfterFinish
+TEST_F(NetErrorHelperTest, TwoProbes_FinishedAfterFinish) {
+ const TestStep steps[] = {
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, LOAD_ERROR_START, LOAD_COMMIT,
+ LOAD_FINISH, EXPECT_UPDATE, STATUS_FINISHED,
+ LOAD_NORMAL_START, LOAD_NORMAL_FAIL, LOAD_ERROR_START, LOAD_COMMIT,
+ LOAD_FINISH, EXPECT_UPDATE, STATUS_FINISHED
+ };
+ RunTest(steps, arraysize(steps));
+}
diff --git a/chrome/renderer/resources/neterror.html b/chrome/renderer/resources/neterror.html
index bb301f2..41c80c8 100644
--- a/chrome/renderer/resources/neterror.html
+++ b/chrome/renderer/resources/neterror.html
@@ -307,6 +307,12 @@ function toggleHelpBox() {
if (window.top.location != window.location)
document.documentElement.setAttribute('subframe', '');
+function updateForDnsProbe(strings) {
+ var context = new JsEvalContext(strings);
+ jstProcess(context, document.getElementById('help-box-outer'));
+ jstProcess(context, document.getElementById('details'));
+}
+
</script>
<body id="t">