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 "components/error_page/renderer/net_error_helper_core.h"
11 #include "base/bind.h"
12 #include "base/callback.h"
13 #include "base/i18n/rtl.h"
14 #include "base/json/json_reader.h"
15 #include "base/json/json_value_converter.h"
16 #include "base/json/json_writer.h"
17 #include "base/location.h"
18 #include "base/logging.h"
19 #include "base/memory/scoped_vector.h"
20 #include "base/metrics/histogram_macros.h"
21 #include "base/metrics/sparse_histogram.h"
22 #include "base/strings/string16.h"
23 #include "base/strings/string_util.h"
24 #include "base/values.h"
25 #include "components/error_page/common/error_page_params.h"
26 #include "components/url_formatter/url_formatter.h"
27 #include "content/public/common/url_constants.h"
28 #include "grit/components_strings.h"
29 #include "net/base/escape.h"
30 #include "net/base/net_errors.h"
31 #include "third_party/WebKit/public/platform/WebString.h"
32 #include "third_party/WebKit/public/platform/WebURLError.h"
33 #include "ui/base/l10n/l10n_util.h"
35 #include "url/url_constants.h"
37 namespace error_page
{
41 struct CorrectionTypeToResourceTable
{
43 const char* correction_type
;
46 const CorrectionTypeToResourceTable kCorrectionResourceTable
[] = {
47 {IDS_ERRORPAGES_SUGGESTION_VISIT_GOOGLE_CACHE
, "cachedPage"},
48 // "reloadPage" is has special handling.
49 {IDS_ERRORPAGES_SUGGESTION_CORRECTED_URL
, "urlCorrection"},
50 {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL
, "siteDomain"},
51 {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL
, "host"},
52 {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL
, "sitemap"},
53 {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL
, "pathParentFolder"},
54 // "siteSearchQuery" is not yet supported.
55 // TODO(mmenke): Figure out what format "siteSearchQuery" uses for its
57 // "webSearchQuery" has special handling.
58 {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL
, "contentOverlap"},
59 {IDS_ERRORPAGES_SUGGESTION_CORRECTED_URL
, "emphasizedUrlCorrection"},
62 struct NavigationCorrection
{
63 NavigationCorrection() : is_porn(false), is_soft_porn(false) {
66 static void RegisterJSONConverter(
67 base::JSONValueConverter
<NavigationCorrection
>* converter
) {
68 converter
->RegisterStringField("correctionType",
69 &NavigationCorrection::correction_type
);
70 converter
->RegisterStringField("urlCorrection",
71 &NavigationCorrection::url_correction
);
72 converter
->RegisterStringField("clickType",
73 &NavigationCorrection::click_type
);
74 converter
->RegisterStringField("clickData",
75 &NavigationCorrection::click_data
);
76 converter
->RegisterBoolField("isPorn", &NavigationCorrection::is_porn
);
77 converter
->RegisterBoolField("isSoftPorn",
78 &NavigationCorrection::is_soft_porn
);
81 std::string correction_type
;
82 std::string url_correction
;
83 std::string click_type
;
84 std::string click_data
;
89 struct NavigationCorrectionResponse
{
91 std::string fingerprint
;
92 ScopedVector
<NavigationCorrection
> corrections
;
94 static void RegisterJSONConverter(
95 base::JSONValueConverter
<NavigationCorrectionResponse
>* converter
) {
96 converter
->RegisterStringField("result.eventId",
97 &NavigationCorrectionResponse::event_id
);
98 converter
->RegisterStringField("result.fingerprint",
99 &NavigationCorrectionResponse::fingerprint
);
100 converter
->RegisterRepeatedMessage(
101 "result.UrlCorrections",
102 &NavigationCorrectionResponse::corrections
);
106 base::TimeDelta
GetAutoReloadTime(size_t reload_count
) {
107 static const int kDelaysMs
[] = {
108 0, 5000, 30000, 60000, 300000, 600000, 1800000
110 if (reload_count
>= arraysize(kDelaysMs
))
111 reload_count
= arraysize(kDelaysMs
) - 1;
112 return base::TimeDelta::FromMilliseconds(kDelaysMs
[reload_count
]);
115 // Returns whether |net_error| is a DNS-related error (and therefore whether
116 // the tab helper should start a DNS probe after receiving it.)
117 bool IsDnsError(const blink::WebURLError
& error
) {
118 return error
.domain
.utf8() == net::kErrorDomain
&&
119 (error
.reason
== net::ERR_NAME_NOT_RESOLVED
||
120 error
.reason
== net::ERR_NAME_RESOLUTION_FAILED
);
123 GURL
SanitizeURL(const GURL
& url
) {
124 GURL::Replacements remove_params
;
125 remove_params
.ClearUsername();
126 remove_params
.ClearPassword();
127 remove_params
.ClearQuery();
128 remove_params
.ClearRef();
129 return url
.ReplaceComponents(remove_params
);
132 // Sanitizes and formats a URL for upload to the error correction service.
133 std::string
PrepareUrlForUpload(const GURL
& url
) {
134 // TODO(yuusuke): Change to url_formatter::FormatUrl when Link Doctor becomes
136 std::string spec_to_send
= SanitizeURL(url
).spec();
138 // Notify navigation correction service of the url truncation by sending of
141 spec_to_send
.append("?");
145 // Given a WebURLError, returns true if the FixURL service should be used
146 // for that error. Also sets |error_param| to the string that should be sent to
147 // the FixURL service to identify the error type.
148 bool ShouldUseFixUrlServiceForError(const blink::WebURLError
& error
,
149 std::string
* error_param
) {
150 error_param
->clear();
152 // Don't use the correction service for HTTPS (for privacy reasons).
153 GURL
unreachable_url(error
.unreachableURL
);
154 if (GURL(unreachable_url
).SchemeIsCryptographic())
157 std::string domain
= error
.domain
.utf8();
158 if (domain
== url::kHttpScheme
&& error
.reason
== 404) {
159 *error_param
= "http404";
162 if (IsDnsError(error
)) {
163 *error_param
= "dnserror";
166 if (domain
== net::kErrorDomain
&&
167 (error
.reason
== net::ERR_CONNECTION_FAILED
||
168 error
.reason
== net::ERR_CONNECTION_REFUSED
||
169 error
.reason
== net::ERR_ADDRESS_UNREACHABLE
||
170 error
.reason
== net::ERR_CONNECTION_TIMED_OUT
)) {
171 *error_param
= "connectionFailure";
177 // Creates a request body for use with the fixurl service. Sets parameters
178 // shared by all types of requests to the service. |correction_params| must
179 // contain the parameters specific to the actual request type.
180 std::string
CreateRequestBody(
181 const std::string
& method
,
182 const std::string
& error_param
,
183 const NetErrorHelperCore::NavigationCorrectionParams
& correction_params
,
184 scoped_ptr
<base::DictionaryValue
> params_dict
) {
185 // Set params common to all request types.
186 params_dict
->SetString("key", correction_params
.api_key
);
187 params_dict
->SetString("clientName", "chrome");
188 params_dict
->SetString("error", error_param
);
190 if (!correction_params
.language
.empty())
191 params_dict
->SetString("language", correction_params
.language
);
193 if (!correction_params
.country_code
.empty())
194 params_dict
->SetString("originCountry", correction_params
.country_code
);
196 base::DictionaryValue request_dict
;
197 request_dict
.SetString("method", method
);
198 request_dict
.SetString("apiVersion", "v1");
199 request_dict
.Set("params", params_dict
.release());
201 std::string request_body
;
202 bool success
= base::JSONWriter::Write(request_dict
, &request_body
);
207 // If URL correction information should be retrieved remotely for a main frame
208 // load that failed with |error|, returns true and sets
209 // |correction_request_body| to be the body for the correction request.
210 std::string
CreateFixUrlRequestBody(
211 const blink::WebURLError
& error
,
212 const NetErrorHelperCore::NavigationCorrectionParams
& correction_params
) {
213 std::string error_param
;
214 bool result
= ShouldUseFixUrlServiceForError(error
, &error_param
);
217 // TODO(mmenke): Investigate open sourcing the relevant protocol buffers and
218 // using those directly instead.
219 scoped_ptr
<base::DictionaryValue
> params(new base::DictionaryValue());
220 params
->SetString("urlQuery", PrepareUrlForUpload(error
.unreachableURL
));
221 return CreateRequestBody("linkdoctor.fixurl.fixurl", error_param
,
222 correction_params
, params
.Pass());
225 std::string
CreateClickTrackingUrlRequestBody(
226 const blink::WebURLError
& error
,
227 const NetErrorHelperCore::NavigationCorrectionParams
& correction_params
,
228 const NavigationCorrectionResponse
& response
,
229 const NavigationCorrection
& correction
) {
230 std::string error_param
;
231 bool result
= ShouldUseFixUrlServiceForError(error
, &error_param
);
234 scoped_ptr
<base::DictionaryValue
> params(new base::DictionaryValue());
236 params
->SetString("originalUrlQuery",
237 PrepareUrlForUpload(error
.unreachableURL
));
239 params
->SetString("clickedUrlCorrection", correction
.url_correction
);
240 params
->SetString("clickType", correction
.click_type
);
241 params
->SetString("clickData", correction
.click_data
);
243 params
->SetString("eventId", response
.event_id
);
244 params
->SetString("fingerprint", response
.fingerprint
);
246 return CreateRequestBody("linkdoctor.fixurl.clicktracking", error_param
,
247 correction_params
, params
.Pass());
250 base::string16
FormatURLForDisplay(const GURL
& url
, bool is_rtl
,
251 const std::string accept_languages
) {
252 // Translate punycode into UTF8, unescape UTF8 URLs.
253 base::string16
url_for_display(url_formatter::FormatUrl(
254 url
, accept_languages
, url_formatter::kFormatUrlOmitNothing
,
255 net::UnescapeRule::NORMAL
, nullptr, nullptr, nullptr));
256 // URLs are always LTR.
258 base::i18n::WrapStringWithLTRFormatting(&url_for_display
);
259 return url_for_display
;
262 scoped_ptr
<NavigationCorrectionResponse
> ParseNavigationCorrectionResponse(
263 const std::string raw_response
) {
264 // TODO(mmenke): Open source related protocol buffers and use them directly.
265 scoped_ptr
<base::Value
> parsed
= base::JSONReader::Read(raw_response
);
266 scoped_ptr
<NavigationCorrectionResponse
> response(
267 new NavigationCorrectionResponse());
268 base::JSONValueConverter
<NavigationCorrectionResponse
> converter
;
269 if (!parsed
|| !converter
.Convert(*parsed
, response
.get()))
271 return response
.Pass();
274 scoped_ptr
<ErrorPageParams
> CreateErrorPageParams(
275 const NavigationCorrectionResponse
& response
,
276 const blink::WebURLError
& error
,
277 const NetErrorHelperCore::NavigationCorrectionParams
& correction_params
,
278 const std::string
& accept_languages
,
280 // Version of URL for display in suggestions. It has to be sanitized first
281 // because any received suggestions will be relative to the sanitized URL.
282 base::string16 original_url_for_display
=
283 FormatURLForDisplay(SanitizeURL(GURL(error
.unreachableURL
)), is_rtl
,
286 scoped_ptr
<ErrorPageParams
> params(new ErrorPageParams());
287 params
->override_suggestions
.reset(new base::ListValue());
288 scoped_ptr
<base::ListValue
> parsed_corrections(new base::ListValue());
289 for (ScopedVector
<NavigationCorrection
>::const_iterator it
=
290 response
.corrections
.begin();
291 it
!= response
.corrections
.end(); ++it
) {
292 // Doesn't seem like a good idea to show these.
293 if ((*it
)->is_porn
|| (*it
)->is_soft_porn
)
296 int tracking_id
= it
- response
.corrections
.begin();
298 if ((*it
)->correction_type
== "reloadPage") {
299 params
->suggest_reload
= true;
300 params
->reload_tracking_id
= tracking_id
;
304 if ((*it
)->correction_type
== "webSearchQuery") {
305 // If there are mutliple searches suggested, use the first suggestion.
306 if (params
->search_terms
.empty()) {
307 params
->search_url
= correction_params
.search_url
;
308 params
->search_terms
= (*it
)->url_correction
;
309 params
->search_tracking_id
= tracking_id
;
314 // Allow reload page and web search query to be empty strings, but not
316 if ((*it
)->url_correction
.empty())
318 size_t correction_index
;
319 for (correction_index
= 0;
320 correction_index
< arraysize(kCorrectionResourceTable
);
321 ++correction_index
) {
322 if ((*it
)->correction_type
!=
323 kCorrectionResourceTable
[correction_index
].correction_type
) {
326 base::DictionaryValue
* suggest
= new base::DictionaryValue();
327 suggest
->SetString("header",
328 l10n_util::GetStringUTF16(
329 kCorrectionResourceTable
[correction_index
].resource_id
));
330 suggest
->SetString("urlCorrection", (*it
)->url_correction
);
332 "urlCorrectionForDisplay",
333 FormatURLForDisplay(GURL((*it
)->url_correction
), is_rtl
,
335 suggest
->SetString("originalUrlForDisplay", original_url_for_display
);
336 suggest
->SetInteger("trackingId", tracking_id
);
337 suggest
->SetInteger("type", static_cast<int>(correction_index
));
339 params
->override_suggestions
->Append(suggest
);
344 if (params
->override_suggestions
->empty() && !params
->search_url
.is_valid())
346 return params
.Pass();
349 void ReportAutoReloadSuccess(const blink::WebURLError
& error
, size_t count
) {
350 if (error
.domain
.utf8() != net::kErrorDomain
)
352 UMA_HISTOGRAM_SPARSE_SLOWLY("Net.AutoReload.ErrorAtSuccess", -error
.reason
);
353 UMA_HISTOGRAM_COUNTS("Net.AutoReload.CountAtSuccess",
354 static_cast<base::HistogramBase::Sample
>(count
));
356 UMA_HISTOGRAM_SPARSE_SLOWLY("Net.AutoReload.ErrorAtFirstSuccess",
361 void ReportAutoReloadFailure(const blink::WebURLError
& error
, size_t count
) {
362 if (error
.domain
.utf8() != net::kErrorDomain
)
364 UMA_HISTOGRAM_SPARSE_SLOWLY("Net.AutoReload.ErrorAtStop", -error
.reason
);
365 UMA_HISTOGRAM_COUNTS("Net.AutoReload.CountAtStop",
366 static_cast<base::HistogramBase::Sample
>(count
));
371 struct NetErrorHelperCore::ErrorPageInfo
{
372 ErrorPageInfo(blink::WebURLError error
, bool was_failed_post
)
374 was_failed_post(was_failed_post
),
375 needs_dns_updates(false),
376 needs_load_navigation_corrections(false),
377 reload_button_in_page(false),
378 show_saved_copy_button_in_page(false),
379 show_cached_copy_button_in_page(false),
380 is_finished_loading(false),
381 auto_reload_triggered(false) {
384 // Information about the failed page load.
385 blink::WebURLError error
;
386 bool was_failed_post
;
388 // Information about the status of the error page.
390 // True if a page is a DNS error page and has not yet received a final DNS
392 bool needs_dns_updates
;
394 // True if a blank page was loaded, and navigation corrections need to be
395 // loaded to generate the real error page.
396 bool needs_load_navigation_corrections
;
398 // Navigation correction service paramers, which will be used in response to
399 // certain types of network errors. They are all stored here in case they
400 // change over the course of displaying the error page.
401 scoped_ptr
<NetErrorHelperCore::NavigationCorrectionParams
>
402 navigation_correction_params
;
404 scoped_ptr
<NavigationCorrectionResponse
> navigation_correction_response
;
406 // All the navigation corrections that have been clicked, for tracking
408 std::set
<int> clicked_corrections
;
410 // Track if specific buttons are included in an error page, for statistics.
411 bool reload_button_in_page
;
412 bool show_saved_copy_button_in_page
;
413 bool show_cached_copy_button_in_page
;
415 // True if a page has completed loading, at which point it can receive
417 bool is_finished_loading
;
419 // True if the auto-reload timer has fired and a reload is or has been in
421 bool auto_reload_triggered
;
424 NetErrorHelperCore::NavigationCorrectionParams::NavigationCorrectionParams() {
427 NetErrorHelperCore::NavigationCorrectionParams::~NavigationCorrectionParams() {
430 bool NetErrorHelperCore::IsReloadableError(
431 const NetErrorHelperCore::ErrorPageInfo
& info
) {
432 GURL url
= info
.error
.unreachableURL
;
433 return info
.error
.domain
.utf8() == net::kErrorDomain
&&
434 info
.error
.reason
!= net::ERR_ABORTED
&&
435 // For now, net::ERR_UNKNOWN_URL_SCHEME is only being displayed on
436 // Chrome for Android.
437 info
.error
.reason
!= net::ERR_UNKNOWN_URL_SCHEME
&&
438 // Do not trigger if the server rejects a client certificate.
439 // https://crbug.com/431387
440 !net::IsClientCertificateError(info
.error
.reason
) &&
441 // Some servers reject client certificates with a generic
442 // handshake_failure alert.
443 // https://crbug.com/431387
444 info
.error
.reason
!= net::ERR_SSL_PROTOCOL_ERROR
&&
445 !info
.was_failed_post
&&
446 // Don't auto-reload non-http/https schemas.
447 // https://crbug.com/471713
448 url
.SchemeIsHTTPOrHTTPS();
451 NetErrorHelperCore::NetErrorHelperCore(Delegate
* delegate
,
452 bool auto_reload_enabled
,
453 bool auto_reload_visible_only
,
455 : delegate_(delegate
),
456 last_probe_status_(DNS_PROBE_POSSIBLE
),
457 can_show_network_diagnostics_dialog_(false),
458 auto_reload_enabled_(auto_reload_enabled
),
459 auto_reload_visible_only_(auto_reload_visible_only
),
460 auto_reload_timer_(new base::Timer(false, false)),
461 auto_reload_paused_(false),
462 auto_reload_in_flight_(false),
463 uncommitted_load_started_(false),
464 // TODO(ellyjones): Make online_ accurate at object creation.
466 visible_(is_visible
),
467 auto_reload_count_(0),
468 navigation_from_button_(NO_BUTTON
) {
471 NetErrorHelperCore::~NetErrorHelperCore() {
472 if (committed_error_page_info_
&&
473 committed_error_page_info_
->auto_reload_triggered
) {
474 ReportAutoReloadFailure(committed_error_page_info_
->error
,
479 void NetErrorHelperCore::CancelPendingFetches() {
480 // Cancel loading the alternate error page, and prevent any pending error page
481 // load from starting a new error page load. Swapping in the error page when
482 // it's finished loading could abort the navigation, otherwise.
483 if (committed_error_page_info_
)
484 committed_error_page_info_
->needs_load_navigation_corrections
= false;
485 if (pending_error_page_info_
)
486 pending_error_page_info_
->needs_load_navigation_corrections
= false;
487 delegate_
->CancelFetchNavigationCorrections();
488 auto_reload_timer_
->Stop();
489 auto_reload_paused_
= false;
492 void NetErrorHelperCore::OnStop() {
493 if (committed_error_page_info_
&&
494 committed_error_page_info_
->auto_reload_triggered
) {
495 ReportAutoReloadFailure(committed_error_page_info_
->error
,
498 CancelPendingFetches();
499 uncommitted_load_started_
= false;
500 auto_reload_count_
= 0;
501 auto_reload_in_flight_
= false;
504 void NetErrorHelperCore::OnWasShown() {
506 if (!auto_reload_visible_only_
)
508 if (auto_reload_paused_
)
509 MaybeStartAutoReloadTimer();
512 void NetErrorHelperCore::OnWasHidden() {
514 if (!auto_reload_visible_only_
)
516 PauseAutoReloadTimer();
519 void NetErrorHelperCore::OnStartLoad(FrameType frame_type
, PageType page_type
) {
520 if (frame_type
!= MAIN_FRAME
)
523 uncommitted_load_started_
= true;
525 // If there's no pending error page information associated with the page load,
526 // or the new page is not an error page, then reset pending error page state.
527 if (!pending_error_page_info_
|| page_type
!= ERROR_PAGE
)
528 CancelPendingFetches();
531 void NetErrorHelperCore::OnCommitLoad(FrameType frame_type
, const GURL
& url
) {
532 if (frame_type
!= MAIN_FRAME
)
535 // If a page is committing, either it's an error page and autoreload will be
536 // started again below, or it's a success page and we need to clear autoreload
538 auto_reload_in_flight_
= false;
540 // uncommitted_load_started_ could already be false, since RenderFrameImpl
541 // calls OnCommitLoad once for each in-page navigation (like a fragment
542 // change) with no corresponding OnStartLoad.
543 uncommitted_load_started_
= false;
545 // Track if an error occurred due to a page button press.
546 // This isn't perfect; if (for instance), the server is slow responding
547 // to a request generated from the page reload button, and the user hits
548 // the browser reload button, this code will still believe the
549 // result is from the page reload button.
550 if (committed_error_page_info_
&& pending_error_page_info_
&&
551 navigation_from_button_
!= NO_BUTTON
&&
552 committed_error_page_info_
->error
.unreachableURL
==
553 pending_error_page_info_
->error
.unreachableURL
) {
554 DCHECK(navigation_from_button_
== RELOAD_BUTTON
||
555 navigation_from_button_
== SHOW_SAVED_COPY_BUTTON
);
556 RecordEvent(navigation_from_button_
== RELOAD_BUTTON
?
557 NETWORK_ERROR_PAGE_RELOAD_BUTTON_ERROR
:
558 NETWORK_ERROR_PAGE_SHOW_SAVED_COPY_BUTTON_ERROR
);
560 navigation_from_button_
= NO_BUTTON
;
562 if (committed_error_page_info_
&& !pending_error_page_info_
&&
563 committed_error_page_info_
->auto_reload_triggered
) {
564 const blink::WebURLError
& error
= committed_error_page_info_
->error
;
565 const GURL
& error_url
= error
.unreachableURL
;
566 if (url
== error_url
)
567 ReportAutoReloadSuccess(error
, auto_reload_count_
);
568 else if (url
!= GURL(content::kUnreachableWebDataURL
))
569 ReportAutoReloadFailure(error
, auto_reload_count_
);
572 committed_error_page_info_
.reset(pending_error_page_info_
.release());
575 void NetErrorHelperCore::OnFinishLoad(FrameType frame_type
) {
576 if (frame_type
!= MAIN_FRAME
)
579 if (!committed_error_page_info_
) {
580 auto_reload_count_
= 0;
584 committed_error_page_info_
->is_finished_loading
= true;
586 RecordEvent(NETWORK_ERROR_PAGE_SHOWN
);
587 if (committed_error_page_info_
->reload_button_in_page
) {
588 RecordEvent(NETWORK_ERROR_PAGE_RELOAD_BUTTON_SHOWN
);
590 if (committed_error_page_info_
->show_saved_copy_button_in_page
) {
591 RecordEvent(NETWORK_ERROR_PAGE_SHOW_SAVED_COPY_BUTTON_SHOWN
);
593 if (committed_error_page_info_
->reload_button_in_page
&&
594 committed_error_page_info_
->show_saved_copy_button_in_page
) {
595 RecordEvent(NETWORK_ERROR_PAGE_BOTH_BUTTONS_SHOWN
);
597 if (committed_error_page_info_
->show_cached_copy_button_in_page
) {
598 RecordEvent(NETWORK_ERROR_PAGE_CACHED_COPY_BUTTON_SHOWN
);
601 delegate_
->EnablePageHelperFunctions();
603 if (committed_error_page_info_
->needs_load_navigation_corrections
) {
604 // If there is another pending error page load, |fix_url| should have been
606 DCHECK(!pending_error_page_info_
);
607 DCHECK(!committed_error_page_info_
->needs_dns_updates
);
608 delegate_
->FetchNavigationCorrections(
609 committed_error_page_info_
->navigation_correction_params
->url
,
610 CreateFixUrlRequestBody(
611 committed_error_page_info_
->error
,
612 *committed_error_page_info_
->navigation_correction_params
));
613 } else if (auto_reload_enabled_
&&
614 IsReloadableError(*committed_error_page_info_
)) {
615 MaybeStartAutoReloadTimer();
618 if (!committed_error_page_info_
->needs_dns_updates
||
619 last_probe_status_
== DNS_PROBE_POSSIBLE
) {
622 DVLOG(1) << "Error page finished loading; sending saved status.";
626 void NetErrorHelperCore::GetErrorHTML(
627 FrameType frame_type
,
628 const blink::WebURLError
& error
,
630 std::string
* error_html
) {
631 if (frame_type
== MAIN_FRAME
) {
632 // If navigation corrections were needed before, that should have been
633 // cancelled earlier by starting a new page load (Which has now failed).
634 DCHECK(!committed_error_page_info_
||
635 !committed_error_page_info_
->needs_load_navigation_corrections
);
637 pending_error_page_info_
.reset(new ErrorPageInfo(error
, is_failed_post
));
638 pending_error_page_info_
->navigation_correction_params
.reset(
639 new NavigationCorrectionParams(navigation_correction_params_
));
640 GetErrorHtmlForMainFrame(pending_error_page_info_
.get(), error_html
);
642 // These values do not matter, as error pages in iframes hide the buttons.
643 bool reload_button_in_page
;
644 bool show_saved_copy_button_in_page
;
645 bool show_cached_copy_button_in_page
;
647 delegate_
->GenerateLocalizedErrorPage(
648 error
, is_failed_post
,
649 false /* No diagnostics dialogs allowed for subframes. */,
650 scoped_ptr
<ErrorPageParams
>(), &reload_button_in_page
,
651 &show_saved_copy_button_in_page
, &show_cached_copy_button_in_page
,
656 void NetErrorHelperCore::OnNetErrorInfo(DnsProbeStatus status
) {
657 DCHECK_NE(DNS_PROBE_POSSIBLE
, status
);
659 last_probe_status_
= status
;
661 if (!committed_error_page_info_
||
662 !committed_error_page_info_
->needs_dns_updates
||
663 !committed_error_page_info_
->is_finished_loading
) {
670 void NetErrorHelperCore::OnSetCanShowNetworkDiagnosticsDialog(
671 bool can_show_network_diagnostics_dialog
) {
672 can_show_network_diagnostics_dialog_
= can_show_network_diagnostics_dialog
;
675 void NetErrorHelperCore::OnSetNavigationCorrectionInfo(
676 const GURL
& navigation_correction_url
,
677 const std::string
& language
,
678 const std::string
& country_code
,
679 const std::string
& api_key
,
680 const GURL
& search_url
) {
681 navigation_correction_params_
.url
= navigation_correction_url
;
682 navigation_correction_params_
.language
= language
;
683 navigation_correction_params_
.country_code
= country_code
;
684 navigation_correction_params_
.api_key
= api_key
;
685 navigation_correction_params_
.search_url
= search_url
;
688 void NetErrorHelperCore::GetErrorHtmlForMainFrame(
689 ErrorPageInfo
* pending_error_page_info
,
690 std::string
* error_html
) {
691 std::string error_param
;
692 blink::WebURLError error
= pending_error_page_info
->error
;
694 if (pending_error_page_info
->navigation_correction_params
&&
695 pending_error_page_info
->navigation_correction_params
->url
.is_valid() &&
696 ShouldUseFixUrlServiceForError(error
, &error_param
)) {
697 pending_error_page_info
->needs_load_navigation_corrections
= true;
701 if (IsDnsError(pending_error_page_info
->error
)) {
702 // The last probe status needs to be reset if this is a DNS error. This
703 // means that if a DNS error page is committed but has not yet finished
704 // loading, a DNS probe status scheduled to be sent to it may be thrown
705 // out, but since the new error page should trigger a new DNS probe, it
706 // will just get the results for the next page load.
707 last_probe_status_
= DNS_PROBE_POSSIBLE
;
708 pending_error_page_info
->needs_dns_updates
= true;
709 error
= GetUpdatedError(error
);
712 delegate_
->GenerateLocalizedErrorPage(
713 error
, pending_error_page_info
->was_failed_post
,
714 can_show_network_diagnostics_dialog_
,
715 scoped_ptr
<ErrorPageParams
>(),
716 &pending_error_page_info
->reload_button_in_page
,
717 &pending_error_page_info
->show_saved_copy_button_in_page
,
718 &pending_error_page_info
->show_cached_copy_button_in_page
,
722 void NetErrorHelperCore::UpdateErrorPage() {
723 DCHECK(committed_error_page_info_
->needs_dns_updates
);
724 DCHECK(committed_error_page_info_
->is_finished_loading
);
725 DCHECK_NE(DNS_PROBE_POSSIBLE
, last_probe_status_
);
727 UMA_HISTOGRAM_ENUMERATION("DnsProbe.ErrorPageUpdateStatus",
730 // Every status other than DNS_PROBE_POSSIBLE and DNS_PROBE_STARTED is a
731 // final status code. Once one is reached, the page does not need further
733 if (last_probe_status_
!= DNS_PROBE_STARTED
)
734 committed_error_page_info_
->needs_dns_updates
= false;
736 // There is no need to worry about the button display statistics here because
737 // the presentation of the reload and show saved copy buttons can't be changed
738 // by a DNS error update.
739 delegate_
->UpdateErrorPage(
740 GetUpdatedError(committed_error_page_info_
->error
),
741 committed_error_page_info_
->was_failed_post
,
742 can_show_network_diagnostics_dialog_
);
745 void NetErrorHelperCore::OnNavigationCorrectionsFetched(
746 const std::string
& corrections
,
747 const std::string
& accept_languages
,
749 // Loading suggestions only starts when a blank error page finishes loading,
750 // and is cancelled with a new load.
751 DCHECK(!pending_error_page_info_
);
752 DCHECK(committed_error_page_info_
->is_finished_loading
);
753 DCHECK(committed_error_page_info_
->needs_load_navigation_corrections
);
754 DCHECK(committed_error_page_info_
->navigation_correction_params
);
756 pending_error_page_info_
.reset(
757 new ErrorPageInfo(committed_error_page_info_
->error
,
758 committed_error_page_info_
->was_failed_post
));
759 pending_error_page_info_
->navigation_correction_response
=
760 ParseNavigationCorrectionResponse(corrections
);
762 std::string error_html
;
763 scoped_ptr
<ErrorPageParams
> params
;
764 if (pending_error_page_info_
->navigation_correction_response
) {
765 // Copy navigation correction parameters used for the request, so tracking
766 // requests can still be sent if the configuration changes.
767 pending_error_page_info_
->navigation_correction_params
.reset(
768 new NavigationCorrectionParams(
769 *committed_error_page_info_
->navigation_correction_params
));
770 params
= CreateErrorPageParams(
771 *pending_error_page_info_
->navigation_correction_response
,
772 pending_error_page_info_
->error
,
773 *pending_error_page_info_
->navigation_correction_params
,
774 accept_languages
, is_rtl
);
775 delegate_
->GenerateLocalizedErrorPage(
776 pending_error_page_info_
->error
,
777 pending_error_page_info_
->was_failed_post
,
778 can_show_network_diagnostics_dialog_
,
780 &pending_error_page_info_
->reload_button_in_page
,
781 &pending_error_page_info_
->show_saved_copy_button_in_page
,
782 &pending_error_page_info_
->show_cached_copy_button_in_page
,
785 // Since |navigation_correction_params| in |pending_error_page_info_| is
786 // NULL, this won't trigger another attempt to load corrections.
787 GetErrorHtmlForMainFrame(pending_error_page_info_
.get(), &error_html
);
790 // TODO(mmenke): Once the new API is in place, look into replacing this
791 // double page load by just updating the error page, like DNS
793 delegate_
->LoadErrorPageInMainFrame(
795 pending_error_page_info_
->error
.unreachableURL
);
798 blink::WebURLError
NetErrorHelperCore::GetUpdatedError(
799 const blink::WebURLError
& error
) const {
800 // If a probe didn't run or wasn't conclusive, restore the original error.
801 if (last_probe_status_
== DNS_PROBE_NOT_RUN
||
802 last_probe_status_
== DNS_PROBE_FINISHED_INCONCLUSIVE
) {
806 blink::WebURLError updated_error
;
807 updated_error
.domain
= blink::WebString::fromUTF8(kDnsProbeErrorDomain
);
808 updated_error
.reason
= last_probe_status_
;
809 updated_error
.unreachableURL
= error
.unreachableURL
;
810 updated_error
.staleCopyInCache
= error
.staleCopyInCache
;
812 return updated_error
;
815 void NetErrorHelperCore::Reload() {
816 if (!committed_error_page_info_
) {
819 delegate_
->ReloadPage();
822 bool NetErrorHelperCore::MaybeStartAutoReloadTimer() {
823 if (!committed_error_page_info_
||
824 !committed_error_page_info_
->is_finished_loading
||
825 pending_error_page_info_
||
826 uncommitted_load_started_
) {
830 StartAutoReloadTimer();
834 void NetErrorHelperCore::StartAutoReloadTimer() {
835 DCHECK(committed_error_page_info_
);
836 DCHECK(IsReloadableError(*committed_error_page_info_
));
838 committed_error_page_info_
->auto_reload_triggered
= true;
840 if (!online_
|| (!visible_
&& auto_reload_visible_only_
)) {
841 auto_reload_paused_
= true;
845 auto_reload_paused_
= false;
846 base::TimeDelta delay
= GetAutoReloadTime(auto_reload_count_
);
847 auto_reload_timer_
->Stop();
848 auto_reload_timer_
->Start(FROM_HERE
, delay
,
849 base::Bind(&NetErrorHelperCore::AutoReloadTimerFired
,
850 base::Unretained(this)));
853 void NetErrorHelperCore::AutoReloadTimerFired() {
854 // AutoReloadTimerFired only runs if:
855 // 1. StartAutoReloadTimer was previously called, which requires that
856 // committed_error_page_info_ is populated;
857 // 2. No other page load has started since (1), since OnStartLoad stops the
858 // auto-reload timer.
859 DCHECK(committed_error_page_info_
);
861 auto_reload_count_
++;
862 auto_reload_in_flight_
= true;
866 void NetErrorHelperCore::PauseAutoReloadTimer() {
867 if (!auto_reload_timer_
->IsRunning())
869 DCHECK(committed_error_page_info_
);
870 DCHECK(!auto_reload_paused_
);
871 DCHECK(committed_error_page_info_
->auto_reload_triggered
);
872 auto_reload_timer_
->Stop();
873 auto_reload_paused_
= true;
876 void NetErrorHelperCore::NetworkStateChanged(bool online
) {
877 bool was_online
= online_
;
879 if (!was_online
&& online
) {
880 // Transitioning offline -> online
881 if (auto_reload_paused_
)
882 MaybeStartAutoReloadTimer();
883 } else if (was_online
&& !online
) {
884 // Transitioning online -> offline
885 if (auto_reload_timer_
->IsRunning())
886 auto_reload_count_
= 0;
887 PauseAutoReloadTimer();
891 bool NetErrorHelperCore::ShouldSuppressErrorPage(FrameType frame_type
,
893 // Don't suppress child frame errors.
894 if (frame_type
!= MAIN_FRAME
)
897 // If there's no auto reload attempt in flight, this error page didn't come
898 // from auto reload, so don't suppress it.
899 if (!auto_reload_in_flight_
)
902 uncommitted_load_started_
= false;
903 // This serves to terminate the auto-reload in flight attempt. If
904 // ShouldSuppressErrorPage is called, the auto-reload yielded an error, which
905 // means the request was already sent.
906 auto_reload_in_flight_
= false;
907 MaybeStartAutoReloadTimer();
911 void NetErrorHelperCore::ExecuteButtonPress(Button button
) {
912 // If there's no committed error page, should not be invoked.
913 DCHECK(committed_error_page_info_
);
917 RecordEvent(NETWORK_ERROR_PAGE_RELOAD_BUTTON_CLICKED
);
918 if (committed_error_page_info_
->show_saved_copy_button_in_page
) {
919 RecordEvent(NETWORK_ERROR_PAGE_BOTH_BUTTONS_RELOAD_CLICKED
);
921 navigation_from_button_
= RELOAD_BUTTON
;
924 case SHOW_SAVED_COPY_BUTTON
:
925 RecordEvent(NETWORK_ERROR_PAGE_SHOW_SAVED_COPY_BUTTON_CLICKED
);
926 navigation_from_button_
= SHOW_SAVED_COPY_BUTTON
;
927 if (committed_error_page_info_
->reload_button_in_page
) {
928 RecordEvent(NETWORK_ERROR_PAGE_BOTH_BUTTONS_SHOWN_SAVED_COPY_CLICKED
);
930 delegate_
->LoadPageFromCache(
931 committed_error_page_info_
->error
.unreachableURL
);
934 // Visual effects on page are handled in Javascript code.
935 RecordEvent(NETWORK_ERROR_PAGE_MORE_BUTTON_CLICKED
);
938 RecordEvent(NETWORK_ERROR_EASTER_EGG_ACTIVATED
);
940 case SHOW_CACHED_COPY_BUTTON
:
941 RecordEvent(NETWORK_ERROR_PAGE_CACHED_COPY_BUTTON_CLICKED
);
944 RecordEvent(NETWORK_ERROR_DIAGNOSE_BUTTON_CLICKED
);
945 delegate_
->DiagnoseError(
946 committed_error_page_info_
->error
.unreachableURL
);
954 void NetErrorHelperCore::TrackClick(int tracking_id
) {
955 // It's technically possible for |navigation_correction_params| to be NULL but
956 // for |navigation_correction_response| not to be NULL, if the paramters
957 // changed between loading the original error page and loading the error page
958 if (!committed_error_page_info_
||
959 !committed_error_page_info_
->navigation_correction_response
) {
963 NavigationCorrectionResponse
* response
=
964 committed_error_page_info_
->navigation_correction_response
.get();
966 // |tracking_id| is less than 0 when the error page was not generated by the
967 // navigation correction service. |tracking_id| should never be greater than
968 // the array size, but best to be safe, since it contains data from a remote
969 // site, though none of that data should make it into Javascript callbacks.
970 if (tracking_id
< 0 ||
971 static_cast<size_t>(tracking_id
) >= response
->corrections
.size()) {
975 // Only report a clicked link once.
976 if (committed_error_page_info_
->clicked_corrections
.count(tracking_id
))
979 committed_error_page_info_
->clicked_corrections
.insert(tracking_id
);
980 std::string request_body
= CreateClickTrackingUrlRequestBody(
981 committed_error_page_info_
->error
,
982 *committed_error_page_info_
->navigation_correction_params
,
984 *response
->corrections
[tracking_id
]);
985 delegate_
->SendTrackingRequest(
986 committed_error_page_info_
->navigation_correction_params
->url
,
990 } // namespace error_page