1 // Copyright 2013 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.
5 #include "chrome/renderer/net/net_error_helper_core.h"
10 #include "base/callback.h"
11 #include "base/location.h"
12 #include "base/metrics/histogram.h"
13 #include "chrome/common/localized_error.h"
14 #include "net/base/escape.h"
15 #include "net/base/net_errors.h"
16 #include "third_party/WebKit/public/platform/WebString.h"
17 #include "third_party/WebKit/public/platform/WebURLError.h"
22 base::TimeDelta GetAutoReloadTime(size_t reload_count) {
23 static const int kDelaysMs[] = {
24 0, 5000, 30000, 60000, 300000, 600000, 1800000
26 if (reload_count >= arraysize(kDelaysMs))
27 reload_count = arraysize(kDelaysMs) - 1;
28 return base::TimeDelta::FromMilliseconds(kDelaysMs[reload_count]);
31 // Returns whether |net_error| is a DNS-related error (and therefore whether
32 // the tab helper should start a DNS probe after receiving it.)
33 bool IsDnsError(const blink::WebURLError& error) {
34 return error.domain.utf8() == net::kErrorDomain &&
35 (error.reason == net::ERR_NAME_NOT_RESOLVED ||
36 error.reason == net::ERR_NAME_RESOLUTION_FAILED);
39 // If an alternate error page should be retrieved remotely for a main frame load
40 // that failed with |error|, returns true and sets |error_page_url| to the URL
41 // of the remote error page.
42 bool GetErrorPageURL(const blink::WebURLError& error,
43 const GURL& alt_error_page_url,
44 GURL* error_page_url) {
45 if (!alt_error_page_url.is_valid())
48 // Parameter to send to the error page indicating the error type.
49 std::string error_param;
51 std::string domain = error.domain.utf8();
52 if (domain == "http" && error.reason == 404) {
53 error_param = "http404";
54 } else if (IsDnsError(error)) {
55 error_param = "dnserror";
56 } else if (domain == net::kErrorDomain &&
57 (error.reason == net::ERR_CONNECTION_FAILED ||
58 error.reason == net::ERR_CONNECTION_REFUSED ||
59 error.reason == net::ERR_ADDRESS_UNREACHABLE ||
60 error.reason == net::ERR_CONNECTION_TIMED_OUT)) {
61 error_param = "connectionfailure";
66 // Don't use the Link Doctor for HTTPS (for privacy reasons).
67 GURL unreachable_url(error.unreachableURL);
68 if (unreachable_url.SchemeIsSecure())
71 // Sanitize the unreachable URL.
72 GURL::Replacements remove_params;
73 remove_params.ClearUsername();
74 remove_params.ClearPassword();
75 remove_params.ClearQuery();
76 remove_params.ClearRef();
77 // TODO(yuusuke): change to net::FormatUrl when Link Doctor becomes
79 std::string spec_to_send =
80 unreachable_url.ReplaceComponents(remove_params).spec();
82 // Notify Link Doctor of the url truncation by sending of "?" at the end.
83 if (unreachable_url.has_query())
84 spec_to_send.append("?");
86 std::string params(alt_error_page_url.query());
87 params.append("&url=");
88 params.append(net::EscapeQueryParamValue(spec_to_send, true));
89 params.append("&sourceid=chrome");
90 params.append("&error=");
91 params.append(error_param);
93 // Build the final url to request.
94 GURL::Replacements link_doctor_params;
95 link_doctor_params.SetQueryStr(params);
96 *error_page_url = alt_error_page_url.ReplaceComponents(link_doctor_params);
102 struct NetErrorHelperCore::ErrorPageInfo {
103 ErrorPageInfo(blink::WebURLError error, bool was_failed_post)
105 was_failed_post(was_failed_post),
106 needs_dns_updates(false),
107 is_finished_loading(false) {
110 // Information about the failed page load.
111 blink::WebURLError error;
112 bool was_failed_post;
114 // Information about the status of the error page.
116 // True if a page is a DNS error page and has not yet received a final DNS
118 bool needs_dns_updates;
120 // URL of an alternate error page to repace this error page with, if it's a
121 // valid URL. Request will be issued when the error page finishes loading.
122 // This is done on load complete to ensure that there are two complete loads
123 // for tests to wait for.
124 GURL alternate_error_page_url;
126 // True if a page has completed loading, at which point it can receive
128 bool is_finished_loading;
131 bool NetErrorHelperCore::IsReloadableError(
132 const NetErrorHelperCore::ErrorPageInfo& info) {
133 return info.error.domain.utf8() == net::kErrorDomain &&
134 info.error.reason != net::ERR_ABORTED &&
135 !info.was_failed_post;
138 NetErrorHelperCore::NetErrorHelperCore(Delegate* delegate)
139 : delegate_(delegate),
140 last_probe_status_(chrome_common_net::DNS_PROBE_POSSIBLE),
141 auto_reload_enabled_(false),
142 auto_reload_timer_(new base::Timer(false, false)),
143 // TODO(ellyjones): Make online_ accurate at object creation.
145 auto_reload_count_(0),
146 can_auto_reload_page_(false) {
149 NetErrorHelperCore::~NetErrorHelperCore() {
150 if (committed_error_page_info_ && can_auto_reload_page_) {
151 UMA_HISTOGRAM_CUSTOM_ENUMERATION("Net.AutoReload.ErrorAtStop",
152 -committed_error_page_info_->error.reason,
153 net::GetAllErrorCodesForUma());
154 UMA_HISTOGRAM_COUNTS("Net.AutoReload.CountAtStop", auto_reload_count_);
158 void NetErrorHelperCore::CancelPendingFetches() {
159 // Cancel loading the alternate error page, and prevent any pending error page
160 // load from starting a new error page load. Swapping in the error page when
161 // it's finished loading could abort the navigation, otherwise.
162 if (committed_error_page_info_ && can_auto_reload_page_) {
163 UMA_HISTOGRAM_CUSTOM_ENUMERATION("Net.AutoReload.ErrorAtStop",
164 -committed_error_page_info_->error.reason,
165 net::GetAllErrorCodesForUma());
166 UMA_HISTOGRAM_COUNTS("Net.AutoReload.CountAtStop", auto_reload_count_);
168 if (committed_error_page_info_)
169 committed_error_page_info_->alternate_error_page_url = GURL();
170 if (pending_error_page_info_)
171 pending_error_page_info_->alternate_error_page_url = GURL();
172 delegate_->CancelFetchErrorPage();
173 auto_reload_timer_->Stop();
174 can_auto_reload_page_ = false;
177 void NetErrorHelperCore::OnStop() {
178 CancelPendingFetches();
179 auto_reload_count_ = 0;
182 void NetErrorHelperCore::OnStartLoad(FrameType frame_type, PageType page_type) {
183 if (frame_type != MAIN_FRAME)
186 // If there's no pending error page information associated with the page load,
187 // or the new page is not an error page, then reset pending error page state.
188 if (!pending_error_page_info_ || page_type != ERROR_PAGE) {
189 CancelPendingFetches();
190 } else if (auto_reload_enabled_) {
191 // If an error load is starting, the resulting error page is autoreloadable.
192 can_auto_reload_page_ = IsReloadableError(*pending_error_page_info_);
196 void NetErrorHelperCore::OnCommitLoad(FrameType frame_type) {
197 if (frame_type != MAIN_FRAME)
200 if (committed_error_page_info_ && !pending_error_page_info_ &&
201 can_auto_reload_page_) {
202 int reason = committed_error_page_info_->error.reason;
203 UMA_HISTOGRAM_CUSTOM_ENUMERATION("Net.AutoReload.ErrorAtSuccess",
205 net::GetAllErrorCodesForUma());
206 UMA_HISTOGRAM_COUNTS("Net.AutoReload.CountAtSuccess", auto_reload_count_);
207 if (auto_reload_count_ == 1) {
208 UMA_HISTOGRAM_CUSTOM_ENUMERATION("Net.AutoReload.ErrorAtFirstSuccess",
210 net::GetAllErrorCodesForUma());
214 committed_error_page_info_.reset(pending_error_page_info_.release());
217 void NetErrorHelperCore::OnFinishLoad(FrameType frame_type) {
218 if (frame_type != MAIN_FRAME)
221 if (!committed_error_page_info_) {
222 auto_reload_count_ = 0;
226 committed_error_page_info_->is_finished_loading = true;
228 // Only enable stale cache JS bindings if this wasn't a post.
229 if (!committed_error_page_info_->was_failed_post) {
230 delegate_->EnableStaleLoadBindings(
231 committed_error_page_info_->error.unreachableURL);
234 if (committed_error_page_info_->alternate_error_page_url.is_valid()) {
235 // If there is another pending error page load,
236 // |replace_with_alternate_error_page| should have been set to false.
237 DCHECK(!pending_error_page_info_);
238 DCHECK(!committed_error_page_info_->needs_dns_updates);
240 delegate_->FetchErrorPage(
241 committed_error_page_info_->alternate_error_page_url);
242 } else if (auto_reload_enabled_ &&
243 IsReloadableError(*committed_error_page_info_)) {
244 MaybeStartAutoReloadTimer();
247 if (!committed_error_page_info_->needs_dns_updates ||
248 last_probe_status_ == chrome_common_net::DNS_PROBE_POSSIBLE) {
251 DVLOG(1) << "Error page finished loading; sending saved status.";
255 void NetErrorHelperCore::GetErrorHTML(
256 FrameType frame_type,
257 const blink::WebURLError& error,
259 std::string* error_html) {
260 if (frame_type == MAIN_FRAME) {
261 // If an alternate error page was going to be fetched, that should have been
262 // cancelled by loading a new page load (Which has now failed to load).
263 DCHECK(!committed_error_page_info_ ||
264 !committed_error_page_info_->alternate_error_page_url.is_valid());
266 // The last probe status needs to be reset if this is a DNS error. This
267 // means that if a DNS error page is committed but has not yet finished
268 // loading, a DNS probe status scheduled to be sent to it may be thrown
269 // out, but since the new error page should trigger a new DNS probe, it
270 // will just get the results for the next page load.
271 if (IsDnsError(error))
272 last_probe_status_ = chrome_common_net::DNS_PROBE_POSSIBLE;
275 if (GetErrorPageURL(error, alt_error_page_url_, &error_page_url)) {
276 pending_error_page_info_.reset(new ErrorPageInfo(error, is_failed_post));
277 pending_error_page_info_->alternate_error_page_url = error_page_url;
282 GenerateLocalErrorPage(frame_type, error, is_failed_post, error_html);
285 void NetErrorHelperCore::GenerateLocalErrorPage(
286 FrameType frame_type,
287 const blink::WebURLError& error,
289 std::string* error_html) {
290 if (frame_type == MAIN_FRAME) {
291 pending_error_page_info_.reset(new ErrorPageInfo(error, is_failed_post));
292 if (IsDnsError(error)) {
293 // This is not strictly necessary, but waiting for a new status to be
294 // sent as a result of the DidFinishLoading call keeps the histograms
295 // consistent with older versions of the code, at no real cost.
296 last_probe_status_ = chrome_common_net::DNS_PROBE_POSSIBLE;
298 delegate_->GenerateLocalizedErrorPage(
299 GetUpdatedError(error), is_failed_post, error_html);
300 pending_error_page_info_->needs_dns_updates = true;
304 delegate_->GenerateLocalizedErrorPage(error, is_failed_post, error_html);
307 void NetErrorHelperCore::OnNetErrorInfo(
308 chrome_common_net::DnsProbeStatus status) {
309 DCHECK_NE(chrome_common_net::DNS_PROBE_POSSIBLE, status);
311 last_probe_status_ = status;
313 if (!committed_error_page_info_ ||
314 !committed_error_page_info_->needs_dns_updates ||
315 !committed_error_page_info_->is_finished_loading) {
322 void NetErrorHelperCore::UpdateErrorPage() {
323 DCHECK(committed_error_page_info_->needs_dns_updates);
324 DCHECK(committed_error_page_info_->is_finished_loading);
325 DCHECK_NE(chrome_common_net::DNS_PROBE_POSSIBLE, last_probe_status_);
327 UMA_HISTOGRAM_ENUMERATION("DnsProbe.ErrorPageUpdateStatus",
329 chrome_common_net::DNS_PROBE_MAX);
330 // Every status other than DNS_PROBE_POSSIBLE and DNS_PROBE_STARTED is a
331 // final status code. Once one is reached, the page does not need further
333 if (last_probe_status_ != chrome_common_net::DNS_PROBE_STARTED)
334 committed_error_page_info_->needs_dns_updates = false;
336 delegate_->UpdateErrorPage(
337 GetUpdatedError(committed_error_page_info_->error),
338 committed_error_page_info_->was_failed_post);
341 void NetErrorHelperCore::OnAlternateErrorPageFetched(const std::string& data) {
342 // Alternate error page load only starts when an error page finishes loading,
343 // and is cancelled with a new load
344 DCHECK(!pending_error_page_info_);
345 DCHECK(committed_error_page_info_->is_finished_loading);
347 const std::string* error_html = NULL;
348 std::string generated_html;
350 // If the request succeeded, use the response in place of a generated error
352 pending_error_page_info_.reset(
353 new ErrorPageInfo(committed_error_page_info_->error,
354 committed_error_page_info_->was_failed_post));
357 // Otherwise, generate a local error page. |pending_error_page_info_| will
358 // be set by GenerateLocalErrorPage.
359 GenerateLocalErrorPage(MAIN_FRAME,
360 committed_error_page_info_->error,
361 committed_error_page_info_->was_failed_post,
363 error_html = &generated_html;
366 // |error_page_info| may have been destroyed by this point, since
367 // |pending_error_page_info_| was set to a new ErrorPageInfo.
369 // TODO(mmenke): Once the new API is in place, look into replacing this
370 // double page load by just updating the error page, like DNS
372 delegate_->LoadErrorPageInMainFrame(
374 pending_error_page_info_->error.unreachableURL);
377 blink::WebURLError NetErrorHelperCore::GetUpdatedError(
378 const blink::WebURLError& error) const {
379 // If a probe didn't run or wasn't conclusive, restore the original error.
380 if (last_probe_status_ == chrome_common_net::DNS_PROBE_NOT_RUN ||
381 last_probe_status_ ==
382 chrome_common_net::DNS_PROBE_FINISHED_INCONCLUSIVE) {
386 blink::WebURLError updated_error;
387 updated_error.domain = blink::WebString::fromUTF8(
388 chrome_common_net::kDnsProbeErrorDomain);
389 updated_error.reason = last_probe_status_;
390 updated_error.unreachableURL = error.unreachableURL;
391 updated_error.staleCopyInCache = error.staleCopyInCache;
393 return updated_error;
396 void NetErrorHelperCore::Reload() {
397 if (!committed_error_page_info_) {
400 delegate_->ReloadPage();
403 bool NetErrorHelperCore::MaybeStartAutoReloadTimer() {
404 if (!committed_error_page_info_ ||
405 !committed_error_page_info_->is_finished_loading ||
406 !can_auto_reload_page_ ||
407 pending_error_page_info_) {
411 DCHECK(IsReloadableError(*committed_error_page_info_));
416 StartAutoReloadTimer();
420 void NetErrorHelperCore::StartAutoReloadTimer() {
421 DCHECK(committed_error_page_info_);
422 DCHECK(can_auto_reload_page_);
423 base::TimeDelta delay = GetAutoReloadTime(auto_reload_count_);
424 auto_reload_count_++;
425 auto_reload_timer_->Stop();
426 auto_reload_timer_->Start(FROM_HERE, delay,
427 base::Bind(&NetErrorHelperCore::Reload,
428 base::Unretained(this)));
431 void NetErrorHelperCore::NetworkStateChanged(bool online) {
433 if (auto_reload_timer_->IsRunning()) {
434 DCHECK(committed_error_page_info_);
435 // If there's an existing timer running, stop it and reset the retry count.
436 auto_reload_timer_->Stop();
437 auto_reload_count_ = 0;
440 // If the network state changed to online, maybe start auto-reloading again.
442 MaybeStartAutoReloadTimer();
445 bool NetErrorHelperCore::ShouldSuppressErrorPage(FrameType frame_type,
447 // Don't suppress child frame errors.
448 if (frame_type != MAIN_FRAME)
451 // If |auto_reload_timer_| is still running, this error page isn't from an
453 if (auto_reload_timer_->IsRunning())
456 // If there's no committed error page, this error page wasn't from an auto
458 if (!committed_error_page_info_ || !can_auto_reload_page_)
461 GURL error_url = committed_error_page_info_->error.unreachableURL;
462 // TODO(ellyjones): also plumb the error code down to CCRC and check that
463 if (error_url != url)
466 // The first iteration of the timer is started by OnFinishLoad calling
467 // MaybeStartAutoReloadTimer, but since error pages for subsequent loads are
468 // suppressed in this function, subsequent iterations of the timer have to be
470 MaybeStartAutoReloadTimer();