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