• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.)
IsDnsError(int net_error)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 
OnDnsProbeFinishedOnIOThread(const base::Callback<void (DnsProbeStatus)> & callback,DnsProbeStatus result)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.
StartDnsProbeOnIOThread(const base::Callback<void (DnsProbeStatus)> & callback,IOThread * io_thread)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 
~NetErrorTabHelper()72 NetErrorTabHelper::~NetErrorTabHelper() {
73 }
74 
75 // static
set_state_for_testing(TestingState state)76 void NetErrorTabHelper::set_state_for_testing(TestingState state) {
77   testing_state_ = state;
78 }
79 
DidStartNavigationToPendingEntry(const GURL & url,content::NavigationController::ReloadType reload_type)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 
DidStartProvisionalLoadForFrame(int64 frame_id,int64 parent_frame_id,bool is_main_frame,const GURL & validated_url,bool is_error_page,bool is_iframe_srcdoc,RenderViewHost * render_view_host)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 
DidCommitProvisionalLoadForFrame(int64 frame_id,const base::string16 & frame_unique_name,bool is_main_frame,const GURL & url,PageTransition transition_type,RenderViewHost * render_view_host)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 
DidFailProvisionalLoad(int64 frame_id,const base::string16 & frame_unique_name,bool is_main_frame,const GURL & validated_url,int error_code,const base::string16 & error_description,RenderViewHost * render_view_host)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 
NetErrorTabHelper(WebContents * contents)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 
OnMainFrameDnsError()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 
StartDnsProbe()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 
OnDnsProbeFinished(DnsProbeStatus result)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 
InitializePref(WebContents * contents)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 
ProbesAllowed() const222 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 
SendInfo()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