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 content::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 int64 frame_id, 97 int64 parent_frame_id, 98 bool is_main_frame, 99 const GURL& validated_url, 100 bool is_error_page, 101 bool is_iframe_srcdoc, 102 RenderViewHost* render_view_host) { 103 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 104 105 if (!is_main_frame) 106 return; 107 108 is_error_page_ = is_error_page; 109 } 110 111 void NetErrorTabHelper::DidCommitProvisionalLoadForFrame( 112 int64 frame_id, 113 const base::string16& frame_unique_name, 114 bool is_main_frame, 115 const GURL& url, 116 PageTransition transition_type, 117 RenderViewHost* render_view_host) { 118 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 119 120 if (!is_main_frame) 121 return; 122 123 // Resend status every time an error page commits; this is somewhat spammy, 124 // but ensures that the status will make it to the real error page, even if 125 // the link doctor loads a blank intermediate page or the tab switches 126 // renderer processes. 127 if (is_error_page_ && dns_error_active_) { 128 dns_error_page_committed_ = true; 129 DVLOG(1) << "Committed error page; resending status."; 130 SendInfo(); 131 } else { 132 dns_error_active_ = false; 133 dns_error_page_committed_ = false; 134 } 135 } 136 137 void NetErrorTabHelper::DidFailProvisionalLoad( 138 int64 frame_id, 139 const base::string16& frame_unique_name, 140 bool is_main_frame, 141 const GURL& validated_url, 142 int error_code, 143 const base::string16& error_description, 144 RenderViewHost* render_view_host) { 145 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 146 147 if (!is_main_frame) 148 return; 149 150 if (IsDnsError(error_code)) { 151 dns_error_active_ = true; 152 OnMainFrameDnsError(); 153 } 154 } 155 156 NetErrorTabHelper::NetErrorTabHelper(WebContents* contents) 157 : WebContentsObserver(contents), 158 weak_factory_(this), 159 is_error_page_(false), 160 dns_error_active_(false), 161 dns_error_page_committed_(false), 162 dns_probe_status_(chrome_common_net::DNS_PROBE_POSSIBLE) { 163 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 164 165 // If this helper is under test, it won't have a WebContents. 166 if (contents) 167 InitializePref(contents); 168 } 169 170 void NetErrorTabHelper::OnMainFrameDnsError() { 171 if (ProbesAllowed()) { 172 // Don't start more than one probe at a time. 173 if (dns_probe_status_ != chrome_common_net::DNS_PROBE_STARTED) { 174 StartDnsProbe(); 175 dns_probe_status_ = chrome_common_net::DNS_PROBE_STARTED; 176 } 177 } else { 178 dns_probe_status_ = chrome_common_net::DNS_PROBE_NOT_RUN; 179 } 180 } 181 182 void NetErrorTabHelper::StartDnsProbe() { 183 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 184 DCHECK(dns_error_active_); 185 DCHECK_NE(chrome_common_net::DNS_PROBE_STARTED, dns_probe_status_); 186 187 DVLOG(1) << "Starting DNS probe."; 188 189 BrowserThread::PostTask( 190 BrowserThread::IO, 191 FROM_HERE, 192 base::Bind(&StartDnsProbeOnIOThread, 193 base::Bind(&NetErrorTabHelper::OnDnsProbeFinished, 194 weak_factory_.GetWeakPtr()), 195 g_browser_process->io_thread())); 196 } 197 198 void NetErrorTabHelper::OnDnsProbeFinished(DnsProbeStatus result) { 199 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 200 DCHECK_EQ(chrome_common_net::DNS_PROBE_STARTED, dns_probe_status_); 201 DCHECK(chrome_common_net::DnsProbeStatusIsFinished(result)); 202 203 DVLOG(1) << "Finished DNS probe with result " 204 << DnsProbeStatusToString(result) << "."; 205 206 dns_probe_status_ = result; 207 208 if (dns_error_page_committed_) 209 SendInfo(); 210 } 211 212 void NetErrorTabHelper::InitializePref(WebContents* contents) { 213 DCHECK(contents); 214 215 BrowserContext* browser_context = contents->GetBrowserContext(); 216 Profile* profile = Profile::FromBrowserContext(browser_context); 217 resolve_errors_with_web_service_.Init( 218 prefs::kAlternateErrorPagesEnabled, 219 profile->GetPrefs()); 220 } 221 222 bool NetErrorTabHelper::ProbesAllowed() const { 223 if (testing_state_ != TESTING_DEFAULT) 224 return testing_state_ == TESTING_FORCE_ENABLED; 225 226 // TODO(ttuttle): Disable on mobile? 227 return *resolve_errors_with_web_service_; 228 } 229 230 void NetErrorTabHelper::SendInfo() { 231 DCHECK_NE(chrome_common_net::DNS_PROBE_POSSIBLE, dns_probe_status_); 232 DCHECK(dns_error_page_committed_); 233 234 DVLOG(1) << "Sending status " << DnsProbeStatusToString(dns_probe_status_); 235 content::RenderFrameHost* rfh = web_contents()->GetMainFrame(); 236 rfh->Send(new ChromeViewMsg_NetErrorInfo(rfh->GetRoutingID(), 237 dns_probe_status_)); 238 239 if (!dns_probe_status_snoop_callback_.is_null()) 240 dns_probe_status_snoop_callback_.Run(dns_probe_status_); 241 } 242 243 } // namespace chrome_browser_net 244