Adds shutdown when FrameTreeServer goes away
[chromium-blink-merge.git] / components / error_page / renderer / net_error_helper_core.cc
blob60e2eeaf4e1918ada1e5cab18b3d153bde89c029
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"
7 #include <set>
8 #include <string>
9 #include <vector>
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"
34 #include "url/gurl.h"
35 #include "url/url_constants.h"
37 namespace error_page {
39 namespace {
41 struct CorrectionTypeToResourceTable {
42 int resource_id;
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
56 // suggestions.
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;
85 bool is_porn;
86 bool is_soft_porn;
89 struct NavigationCorrectionResponse {
90 std::string event_id;
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
135 // unicode-capable.
136 std::string spec_to_send = SanitizeURL(url).spec();
138 // Notify navigation correction service of the url truncation by sending of
139 // "?" at the end.
140 if (url.has_query())
141 spec_to_send.append("?");
142 return spec_to_send;
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())
155 return false;
157 std::string domain = error.domain.utf8();
158 if (domain == url::kHttpScheme && error.reason == 404) {
159 *error_param = "http404";
160 return true;
162 if (IsDnsError(error)) {
163 *error_param = "dnserror";
164 return true;
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";
172 return true;
174 return false;
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);
203 DCHECK(success);
204 return 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);
215 DCHECK(result);
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);
232 DCHECK(result);
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.
257 if (is_rtl)
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()))
270 response.reset();
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,
279 bool is_rtl) {
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,
284 accept_languages);
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)
294 continue;
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;
301 continue;
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;
311 continue;
314 // Allow reload page and web search query to be empty strings, but not
315 // links.
316 if ((*it)->url_correction.empty())
317 continue;
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) {
324 continue;
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);
331 suggest->SetString(
332 "urlCorrectionForDisplay",
333 FormatURLForDisplay(GURL((*it)->url_correction), is_rtl,
334 accept_languages));
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);
340 break;
344 if (params->override_suggestions->empty() && !params->search_url.is_valid())
345 params.reset();
346 return params.Pass();
349 void ReportAutoReloadSuccess(const blink::WebURLError& error, size_t count) {
350 if (error.domain.utf8() != net::kErrorDomain)
351 return;
352 UMA_HISTOGRAM_SPARSE_SLOWLY("Net.AutoReload.ErrorAtSuccess", -error.reason);
353 UMA_HISTOGRAM_COUNTS("Net.AutoReload.CountAtSuccess",
354 static_cast<base::HistogramBase::Sample>(count));
355 if (count == 1) {
356 UMA_HISTOGRAM_SPARSE_SLOWLY("Net.AutoReload.ErrorAtFirstSuccess",
357 -error.reason);
361 void ReportAutoReloadFailure(const blink::WebURLError& error, size_t count) {
362 if (error.domain.utf8() != net::kErrorDomain)
363 return;
364 UMA_HISTOGRAM_SPARSE_SLOWLY("Net.AutoReload.ErrorAtStop", -error.reason);
365 UMA_HISTOGRAM_COUNTS("Net.AutoReload.CountAtStop",
366 static_cast<base::HistogramBase::Sample>(count));
369 } // namespace
371 struct NetErrorHelperCore::ErrorPageInfo {
372 ErrorPageInfo(blink::WebURLError error, bool was_failed_post)
373 : error(error),
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
391 // probe status.
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
407 // purposes.
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
416 // updates.
417 bool is_finished_loading;
419 // True if the auto-reload timer has fired and a reload is or has been in
420 // flight.
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,
454 bool is_visible)
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.
465 online_(true),
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,
475 auto_reload_count_);
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,
496 auto_reload_count_);
498 CancelPendingFetches();
499 uncommitted_load_started_ = false;
500 auto_reload_count_ = 0;
501 auto_reload_in_flight_ = false;
504 void NetErrorHelperCore::OnWasShown() {
505 visible_ = true;
506 if (!auto_reload_visible_only_)
507 return;
508 if (auto_reload_paused_)
509 MaybeStartAutoReloadTimer();
512 void NetErrorHelperCore::OnWasHidden() {
513 visible_ = false;
514 if (!auto_reload_visible_only_)
515 return;
516 PauseAutoReloadTimer();
519 void NetErrorHelperCore::OnStartLoad(FrameType frame_type, PageType page_type) {
520 if (frame_type != MAIN_FRAME)
521 return;
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)
533 return;
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
537 // state.
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)
577 return;
579 if (!committed_error_page_info_) {
580 auto_reload_count_ = 0;
581 return;
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
605 // cleared.
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) {
620 return;
622 DVLOG(1) << "Error page finished loading; sending saved status.";
623 UpdateErrorPage();
626 void NetErrorHelperCore::GetErrorHTML(
627 FrameType frame_type,
628 const blink::WebURLError& error,
629 bool is_failed_post,
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);
641 } else {
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,
652 error_html);
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) {
664 return;
667 UpdateErrorPage();
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;
698 return;
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,
719 error_html);
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",
728 last_probe_status_,
729 DNS_PROBE_MAX);
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
732 // updates.
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,
748 bool is_rtl) {
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_,
779 params.Pass(),
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,
783 &error_html);
784 } else {
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
792 // probes do.
793 delegate_->LoadErrorPageInMainFrame(
794 error_html,
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) {
803 return error;
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_) {
817 return;
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_) {
827 return false;
830 StartAutoReloadTimer();
831 return true;
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;
842 return;
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;
863 Reload();
866 void NetErrorHelperCore::PauseAutoReloadTimer() {
867 if (!auto_reload_timer_->IsRunning())
868 return;
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_;
878 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,
892 const GURL& url) {
893 // Don't suppress child frame errors.
894 if (frame_type != MAIN_FRAME)
895 return false;
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_)
900 return false;
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();
908 return true;
911 void NetErrorHelperCore::ExecuteButtonPress(Button button) {
912 // If there's no committed error page, should not be invoked.
913 DCHECK(committed_error_page_info_);
915 switch (button) {
916 case RELOAD_BUTTON:
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;
922 Reload();
923 return;
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);
932 return;
933 case MORE_BUTTON:
934 // Visual effects on page are handled in Javascript code.
935 RecordEvent(NETWORK_ERROR_PAGE_MORE_BUTTON_CLICKED);
936 return;
937 case EASTER_EGG:
938 RecordEvent(NETWORK_ERROR_EASTER_EGG_ACTIVATED);
939 return;
940 case SHOW_CACHED_COPY_BUTTON:
941 RecordEvent(NETWORK_ERROR_PAGE_CACHED_COPY_BUTTON_CLICKED);
942 return;
943 case DIAGNOSE_ERROR:
944 RecordEvent(NETWORK_ERROR_DIAGNOSE_BUTTON_CLICKED);
945 delegate_->DiagnoseError(
946 committed_error_page_info_->error.unreachableURL);
947 return;
948 case NO_BUTTON:
949 NOTREACHED();
950 return;
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) {
960 return;
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()) {
972 return;
975 // Only report a clicked link once.
976 if (committed_error_page_info_->clicked_corrections.count(tracking_id))
977 return;
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,
983 *response,
984 *response->corrections[tracking_id]);
985 delegate_->SendTrackingRequest(
986 committed_error_page_info_->navigation_correction_params->url,
987 request_body);
990 } // namespace error_page