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