// 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/net_error_tab_helper.h" #include "base/bind.h" #include "base/logging.h" #include "base/prefs/pref_service.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/io_thread.h" #include "chrome/browser/net/dns_probe_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/pref_names.h" #include "chrome/common/render_messages.h" #include "components/error_page/common/net_error_info.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/render_frame_host.h" #include "net/base/net_errors.h" using chrome_common_net::DnsProbeStatus; using chrome_common_net::DnsProbeStatusToString; using content::BrowserContext; using content::BrowserThread; using ui::PageTransition; using content::RenderViewHost; using content::WebContents; using content::WebContentsObserver; DEFINE_WEB_CONTENTS_USER_DATA_KEY(chrome_browser_net::NetErrorTabHelper); namespace chrome_browser_net { namespace { static NetErrorTabHelper::TestingState testing_state_ = NetErrorTabHelper::TESTING_DEFAULT; // 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; } void OnDnsProbeFinishedOnIOThread( const base::Callback& callback, DnsProbeStatus result) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(callback, result)); } // 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& callback, IOThread* io_thread) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); DnsProbeService* probe_service = io_thread->globals()->dns_probe_service.get(); probe_service->ProbeDns(base::Bind(&OnDnsProbeFinishedOnIOThread, callback)); } } // namespace NetErrorTabHelper::~NetErrorTabHelper() { } // static void NetErrorTabHelper::set_state_for_testing(TestingState state) { testing_state_ = state; } void NetErrorTabHelper::DidStartNavigationToPendingEntry( const GURL& url, content::NavigationController::ReloadType reload_type) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (!is_error_page_) return; // Only record reloads. if (reload_type != content::NavigationController::NO_RELOAD) { chrome_common_net::RecordEvent( chrome_common_net::NETWORK_ERROR_PAGE_BROWSER_INITIATED_RELOAD); } } void NetErrorTabHelper::DidStartProvisionalLoadForFrame( content::RenderFrameHost* render_frame_host, const GURL& validated_url, bool is_error_page, bool is_iframe_srcdoc) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (render_frame_host->GetParent()) return; is_error_page_ = is_error_page; } void NetErrorTabHelper::DidCommitProvisionalLoadForFrame( content::RenderFrameHost* render_frame_host, const GURL& url, PageTransition transition_type) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (render_frame_host->GetParent()) 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( content::RenderFrameHost* render_frame_host, const GURL& validated_url, int error_code, const base::string16& error_description) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (render_frame_host->GetParent()) return; if (IsDnsError(error_code)) { dns_error_active_ = true; OnMainFrameDnsError(); } } NetErrorTabHelper::NetErrorTabHelper(WebContents* contents) : WebContentsObserver(contents), is_error_page_(false), dns_error_active_(false), dns_error_page_committed_(false), dns_probe_status_(chrome_common_net::DNS_PROBE_POSSIBLE), weak_factory_(this) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); // If this helper is under test, it won't have a WebContents. if (contents) InitializePref(contents); } 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::StartDnsProbe() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(dns_error_active_); DCHECK_NE(chrome_common_net::DNS_PROBE_STARTED, dns_probe_status_); 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(DnsProbeStatus result) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK_EQ(chrome_common_net::DNS_PROBE_STARTED, dns_probe_status_); DCHECK(chrome_common_net::DnsProbeStatusIsFinished(result)); DVLOG(1) << "Finished DNS probe with result " << DnsProbeStatusToString(result) << "."; dns_probe_status_ = result; if (dns_error_page_committed_) SendInfo(); } void NetErrorTabHelper::InitializePref(WebContents* contents) { DCHECK(contents); BrowserContext* browser_context = contents->GetBrowserContext(); Profile* profile = Profile::FromBrowserContext(browser_context); resolve_errors_with_web_service_.Init( prefs::kAlternateErrorPagesEnabled, profile->GetPrefs()); } bool NetErrorTabHelper::ProbesAllowed() const { if (testing_state_ != TESTING_DEFAULT) return testing_state_ == TESTING_FORCE_ENABLED; // TODO(ttuttle): Disable on mobile? return *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_); content::RenderFrameHost* rfh = web_contents()->GetMainFrame(); rfh->Send(new ChromeViewMsg_NetErrorInfo(rfh->GetRoutingID(), dns_probe_status_)); if (!dns_probe_status_snoop_callback_.is_null()) dns_probe_status_snoop_callback_.Run(dns_probe_status_); } } // namespace chrome_browser_net