diff options
author | ttuttle@chromium.org <ttuttle@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-17 05:23:13 +0000 |
---|---|---|
committer | ttuttle@chromium.org <ttuttle@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-17 05:23:13 +0000 |
commit | 2ea1efe9660ee70c8eefb98dde76caac1c918005 (patch) | |
tree | b5b7e0ea619ae3df960f25d3c4ce23cbba335e2c | |
parent | 3793b5f264db5e0ce9950d0e28e8748e5b8c56ff (diff) | |
download | chromium_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
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"><span jscontent="productName"></span><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"><strong jscontent="hostName"></strong><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"> |