1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/net/net_error_tab_helper.h" 6 7 #include "base/bind.h" 8 #include "base/logging.h" 9 #include "base/prefs/pref_service.h" 10 #include "chrome/browser/browser_process.h" 11 #include "chrome/browser/io_thread.h" 12 #include "chrome/browser/net/dns_probe_service.h" 13 #include "chrome/browser/profiles/profile.h" 14 #include "chrome/common/net/net_error_info.h" 15 #include "chrome/common/pref_names.h" 16 #include "chrome/common/render_messages.h" 17 #include "content/public/browser/browser_thread.h" 18 #include "content/public/browser/render_frame_host.h" 19 #include "net/base/net_errors.h" 20 21 using chrome_common_net::DnsProbeStatus; 22 using chrome_common_net::DnsProbeStatusToString; 23 using content::BrowserContext; 24 using content::BrowserThread; 25 using ui::PageTransition; 26 using content::RenderViewHost; 27 using content::WebContents; 28 using content::WebContentsObserver; 29 30 DEFINE_WEB_CONTENTS_USER_DATA_KEY(chrome_browser_net::NetErrorTabHelper); 31 32 namespace chrome_browser_net { 33 34 namespace { 35 36 static NetErrorTabHelper::TestingState testing_state_ = 37 NetErrorTabHelper::TESTING_DEFAULT; 38 39 // Returns whether |net_error| is a DNS-related error (and therefore whether 40 // the tab helper should start a DNS probe after receiving it.) 41 bool IsDnsError(int net_error) { 42 return net_error == net::ERR_NAME_NOT_RESOLVED || 43 net_error == net::ERR_NAME_RESOLUTION_FAILED; 44 } 45 46 void OnDnsProbeFinishedOnIOThread( 47 const base::Callback<void(DnsProbeStatus)>& callback, 48 DnsProbeStatus result) { 49 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 50 51 BrowserThread::PostTask( 52 BrowserThread::UI, 53 FROM_HERE, 54 base::Bind(callback, result)); 55 } 56 57 // Can only access g_browser_process->io_thread() from the browser thread, 58 // so have to pass it in to the callback instead of dereferencing it here. 59 void StartDnsProbeOnIOThread( 60 const base::Callback<void(DnsProbeStatus)>& callback, 61 IOThread* io_thread) { 62 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 63 64 DnsProbeService* probe_service = 65 io_thread->globals()->dns_probe_service.get(); 66 67 probe_service->ProbeDns(base::Bind(&OnDnsProbeFinishedOnIOThread, callback)); 68 } 69 70 } // namespace 71 72 NetErrorTabHelper::~NetErrorTabHelper() { 73 } 74 75 // static 76 void NetErrorTabHelper::set_state_for_testing(TestingState state) { 77 testing_state_ = state; 78 } 79 80 void NetErrorTabHelper::DidStartNavigationToPendingEntry( 81 const GURL& url, 82 content::NavigationController::ReloadType reload_type) { 83 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 84 85 if (!is_error_page_) 86 return; 87 88 // Only record reloads. 89 if (reload_type != content::NavigationController::NO_RELOAD) { 90 chrome_common_net::RecordEvent( 91 chrome_common_net::NETWORK_ERROR_PAGE_BROWSER_INITIATED_RELOAD); 92 } 93 } 94 95 void NetErrorTabHelper::DidStartProvisionalLoadForFrame( 96 content::RenderFrameHost* render_frame_host, 97 const GURL& validated_url, 98 bool is_error_page, 99 bool is_iframe_srcdoc) { 100 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 101 102 if (render_frame_host->GetParent()) 103 return; 104 105 is_error_page_ = is_error_page; 106 } 107 108 void NetErrorTabHelper::DidCommitProvisionalLoadForFrame( 109 content::RenderFrameHost* render_frame_host, 110 const GURL& url, 111 PageTransition transition_type) { 112 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 113 114 if (render_frame_host->GetParent()) 115 return; 116 117 // Resend status every time an error page commits; this is somewhat spammy, 118 // but ensures that the status will make it to the real error page, even if 119 // the link doctor loads a blank intermediate page or the tab switches 120 // renderer processes. 121 if (is_error_page_ && dns_error_active_) { 122 dns_error_page_committed_ = true; 123 DVLOG(1) << "Committed error page; resending status."; 124 SendInfo(); 125 } else { 126 dns_error_active_ = false; 127 dns_error_page_committed_ = false; 128 } 129 } 130 131 void NetErrorTabHelper::DidFailProvisionalLoad( 132 content::RenderFrameHost* render_frame_host, 133 const GURL& validated_url, 134 int error_code, 135 const base::string16& error_description) { 136 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 137 138 if (render_frame_host->GetParent()) 139 return; 140 141 if (IsDnsError(error_code)) { 142 dns_error_active_ = true; 143 OnMainFrameDnsError(); 144 } 145 } 146 147 NetErrorTabHelper::NetErrorTabHelper(WebContents* contents) 148 : WebContentsObserver(contents), 149 is_error_page_(false), 150 dns_error_active_(false), 151 dns_error_page_committed_(false), 152 dns_probe_status_(chrome_common_net::DNS_PROBE_POSSIBLE), 153 weak_factory_(this) { 154 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 155 156 // If this helper is under test, it won't have a WebContents. 157 if (contents) 158 InitializePref(contents); 159 } 160 161 void NetErrorTabHelper::OnMainFrameDnsError() { 162 if (ProbesAllowed()) { 163 // Don't start more than one probe at a time. 164 if (dns_probe_status_ != chrome_common_net::DNS_PROBE_STARTED) { 165 StartDnsProbe(); 166 dns_probe_status_ = chrome_common_net::DNS_PROBE_STARTED; 167 } 168 } else { 169 dns_probe_status_ = chrome_common_net::DNS_PROBE_NOT_RUN; 170 } 171 } 172 173 void NetErrorTabHelper::StartDnsProbe() { 174 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 175 DCHECK(dns_error_active_); 176 DCHECK_NE(chrome_common_net::DNS_PROBE_STARTED, dns_probe_status_); 177 178 DVLOG(1) << "Starting DNS probe."; 179 180 BrowserThread::PostTask( 181 BrowserThread::IO, 182 FROM_HERE, 183 base::Bind(&StartDnsProbeOnIOThread, 184 base::Bind(&NetErrorTabHelper::OnDnsProbeFinished, 185 weak_factory_.GetWeakPtr()), 186 g_browser_process->io_thread())); 187 } 188 189 void NetErrorTabHelper::OnDnsProbeFinished(DnsProbeStatus result) { 190 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 191 DCHECK_EQ(chrome_common_net::DNS_PROBE_STARTED, dns_probe_status_); 192 DCHECK(chrome_common_net::DnsProbeStatusIsFinished(result)); 193 194 DVLOG(1) << "Finished DNS probe with result " 195 << DnsProbeStatusToString(result) << "."; 196 197 dns_probe_status_ = result; 198 199 if (dns_error_page_committed_) 200 SendInfo(); 201 } 202 203 void NetErrorTabHelper::InitializePref(WebContents* contents) { 204 DCHECK(contents); 205 206 BrowserContext* browser_context = contents->GetBrowserContext(); 207 Profile* profile = Profile::FromBrowserContext(browser_context); 208 resolve_errors_with_web_service_.Init( 209 prefs::kAlternateErrorPagesEnabled, 210 profile->GetPrefs()); 211 } 212 213 bool NetErrorTabHelper::ProbesAllowed() const { 214 if (testing_state_ != TESTING_DEFAULT) 215 return testing_state_ == TESTING_FORCE_ENABLED; 216 217 // TODO(ttuttle): Disable on mobile? 218 return *resolve_errors_with_web_service_; 219 } 220 221 void NetErrorTabHelper::SendInfo() { 222 DCHECK_NE(chrome_common_net::DNS_PROBE_POSSIBLE, dns_probe_status_); 223 DCHECK(dns_error_page_committed_); 224 225 DVLOG(1) << "Sending status " << DnsProbeStatusToString(dns_probe_status_); 226 content::RenderFrameHost* rfh = web_contents()->GetMainFrame(); 227 rfh->Send(new ChromeViewMsg_NetErrorInfo(rfh->GetRoutingID(), 228 dns_probe_status_)); 229 230 if (!dns_probe_status_snoop_callback_.is_null()) 231 dns_probe_status_snoop_callback_.Run(dns_probe_status_); 232 } 233 234 } // namespace chrome_browser_net 235