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.
4
5#include "chrome/renderer/net/net_error_helper_core.h"
6
7#include <set>
8#include <string>
9#include <vector>
10
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.h"
21#include "base/strings/string16.h"
22#include "base/strings/string_util.h"
23#include "base/values.h"
24#include "chrome/common/localized_error.h"
25#include "content/public/common/url_constants.h"
26#include "grit/generated_resources.h"
27#include "net/base/escape.h"
28#include "net/base/net_errors.h"
29#include "net/base/net_util.h"
30#include "third_party/WebKit/public/platform/WebString.h"
31#include "third_party/WebKit/public/platform/WebURLError.h"
32#include "ui/base/l10n/l10n_util.h"
33#include "url/gurl.h"
34
35namespace {
36
37struct CorrectionTypeToResourceTable {
38  int resource_id;
39  const char* correction_type;
40};
41
42const CorrectionTypeToResourceTable kCorrectionResourceTable[] = {
43  {IDS_ERRORPAGES_SUGGESTION_VISIT_GOOGLE_CACHE, "cachedPage"},
44  // "reloadPage" is has special handling.
45  {IDS_ERRORPAGES_SUGGESTION_CORRECTED_URL, "urlCorrection"},
46  {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "siteDomain"},
47  {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "host"},
48  {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "sitemap"},
49  {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "pathParentFolder"},
50  // "siteSearchQuery" is not yet supported.
51  // TODO(mmenke):  Figure out what format "siteSearchQuery" uses for its
52  // suggestions.
53  // "webSearchQuery" has special handling.
54  {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "contentOverlap"},
55  {IDS_ERRORPAGES_SUGGESTION_CORRECTED_URL, "emphasizedUrlCorrection"},
56};
57
58struct NavigationCorrection {
59  NavigationCorrection() : is_porn(false), is_soft_porn(false) {
60  }
61
62  static void RegisterJSONConverter(
63      base::JSONValueConverter<NavigationCorrection>* converter) {
64    converter->RegisterStringField("correctionType",
65                                   &NavigationCorrection::correction_type);
66    converter->RegisterStringField("urlCorrection",
67                                   &NavigationCorrection::url_correction);
68    converter->RegisterStringField("clickType",
69                                   &NavigationCorrection::click_type);
70    converter->RegisterStringField("clickData",
71                                   &NavigationCorrection::click_data);
72    converter->RegisterBoolField("isPorn", &NavigationCorrection::is_porn);
73    converter->RegisterBoolField("isSoftPorn",
74                                 &NavigationCorrection::is_soft_porn);
75  }
76
77  std::string correction_type;
78  std::string url_correction;
79  std::string click_type;
80  std::string click_data;
81  bool is_porn;
82  bool is_soft_porn;
83};
84
85struct NavigationCorrectionResponse {
86  std::string event_id;
87  std::string fingerprint;
88  ScopedVector<NavigationCorrection> corrections;
89
90  static void RegisterJSONConverter(
91      base::JSONValueConverter<NavigationCorrectionResponse>* converter) {
92    converter->RegisterStringField("result.eventId",
93                                   &NavigationCorrectionResponse::event_id);
94    converter->RegisterStringField("result.fingerprint",
95                                   &NavigationCorrectionResponse::fingerprint);
96    converter->RegisterRepeatedMessage(
97        "result.UrlCorrections",
98        &NavigationCorrectionResponse::corrections);
99  }
100};
101
102base::TimeDelta GetAutoReloadTime(size_t reload_count) {
103  static const int kDelaysMs[] = {
104    0, 5000, 30000, 60000, 300000, 600000, 1800000
105  };
106  if (reload_count >= arraysize(kDelaysMs))
107    reload_count = arraysize(kDelaysMs) - 1;
108  return base::TimeDelta::FromMilliseconds(kDelaysMs[reload_count]);
109}
110
111// Returns whether |net_error| is a DNS-related error (and therefore whether
112// the tab helper should start a DNS probe after receiving it.)
113bool IsDnsError(const blink::WebURLError& error) {
114  return error.domain.utf8() == net::kErrorDomain &&
115         (error.reason == net::ERR_NAME_NOT_RESOLVED ||
116          error.reason == net::ERR_NAME_RESOLUTION_FAILED);
117}
118
119GURL SanitizeURL(const GURL& url) {
120  GURL::Replacements remove_params;
121  remove_params.ClearUsername();
122  remove_params.ClearPassword();
123  remove_params.ClearQuery();
124  remove_params.ClearRef();
125  return url.ReplaceComponents(remove_params);
126}
127
128// Sanitizes and formats a URL for upload to the error correction service.
129std::string PrepareUrlForUpload(const GURL& url) {
130  // TODO(yuusuke): Change to net::FormatUrl when Link Doctor becomes
131  // unicode-capable.
132  std::string spec_to_send = SanitizeURL(url).spec();
133
134  // Notify navigation correction service of the url truncation by sending of
135  // "?" at the end.
136  if (url.has_query())
137    spec_to_send.append("?");
138  return spec_to_send;
139}
140
141// Given a WebURLError, returns true if the FixURL service should be used
142// for that error.  Also sets |error_param| to the string that should be sent to
143// the FixURL service to identify the error type.
144bool ShouldUseFixUrlServiceForError(const blink::WebURLError& error,
145                                    std::string* error_param) {
146  error_param->clear();
147
148  // Don't use the correction service for HTTPS (for privacy reasons).
149  GURL unreachable_url(error.unreachableURL);
150  if (GURL(unreachable_url).SchemeIsSecure())
151    return false;
152
153  std::string domain = error.domain.utf8();
154  if (domain == "http" && error.reason == 404) {
155    *error_param = "http404";
156    return true;
157  }
158  if (IsDnsError(error)) {
159    *error_param = "dnserror";
160    return true;
161  }
162  if (domain == net::kErrorDomain &&
163      (error.reason == net::ERR_CONNECTION_FAILED ||
164       error.reason == net::ERR_CONNECTION_REFUSED ||
165       error.reason == net::ERR_ADDRESS_UNREACHABLE ||
166       error.reason == net::ERR_CONNECTION_TIMED_OUT)) {
167    *error_param = "connectionFailure";
168    return true;
169  }
170  return false;
171}
172
173// Creates a request body for use with the fixurl service.  Sets parameters
174// shared by all types of requests to the service.  |correction_params| must
175// contain the parameters specific to the actual request type.
176std::string CreateRequestBody(
177    const std::string& method,
178    const std::string& error_param,
179    const NetErrorHelperCore::NavigationCorrectionParams& correction_params,
180    scoped_ptr<base::DictionaryValue> params_dict) {
181  // Set params common to all request types.
182  params_dict->SetString("key", correction_params.api_key);
183  params_dict->SetString("clientName", "chrome");
184  params_dict->SetString("error", error_param);
185
186  if (!correction_params.language.empty())
187    params_dict->SetString("language", correction_params.language);
188
189  if (!correction_params.country_code.empty())
190    params_dict->SetString("originCountry", correction_params.country_code);
191
192  base::DictionaryValue request_dict;
193  request_dict.SetString("method", method);
194  request_dict.SetString("apiVersion", "v1");
195  request_dict.Set("params", params_dict.release());
196
197  std::string request_body;
198  bool success = base::JSONWriter::Write(&request_dict, &request_body);
199  DCHECK(success);
200  return request_body;
201}
202
203// If URL correction information should be retrieved remotely for a main frame
204// load that failed with |error|, returns true and sets
205// |correction_request_body| to be the body for the correction request.
206std::string CreateFixUrlRequestBody(
207    const blink::WebURLError& error,
208    const NetErrorHelperCore::NavigationCorrectionParams& correction_params) {
209  std::string error_param;
210  bool result = ShouldUseFixUrlServiceForError(error, &error_param);
211  DCHECK(result);
212
213  // TODO(mmenke):  Investigate open sourcing the relevant protocol buffers and
214  //                using those directly instead.
215  scoped_ptr<base::DictionaryValue> params(new base::DictionaryValue());
216  params->SetString("urlQuery", PrepareUrlForUpload(error.unreachableURL));
217  return CreateRequestBody("linkdoctor.fixurl.fixurl", error_param,
218                           correction_params, params.Pass());
219}
220
221std::string CreateClickTrackingUrlRequestBody(
222    const blink::WebURLError& error,
223    const NetErrorHelperCore::NavigationCorrectionParams& correction_params,
224    const NavigationCorrectionResponse& response,
225    const NavigationCorrection& correction) {
226  std::string error_param;
227  bool result = ShouldUseFixUrlServiceForError(error, &error_param);
228  DCHECK(result);
229
230  scoped_ptr<base::DictionaryValue> params(new base::DictionaryValue());
231
232  params->SetString("originalUrlQuery",
233                    PrepareUrlForUpload(error.unreachableURL));
234
235  params->SetString("clickedUrlCorrection", correction.url_correction);
236  params->SetString("clickType", correction.click_type);
237  params->SetString("clickData", correction.click_data);
238
239  params->SetString("eventId", response.event_id);
240  params->SetString("fingerprint", response.fingerprint);
241
242  return CreateRequestBody("linkdoctor.fixurl.clicktracking", error_param,
243                           correction_params, params.Pass());
244}
245
246base::string16 FormatURLForDisplay(const GURL& url, bool is_rtl,
247                                   const std::string accept_languages) {
248  // Translate punycode into UTF8, unescape UTF8 URLs.
249  base::string16 url_for_display(net::FormatUrl(
250      url, accept_languages, net::kFormatUrlOmitNothing,
251      net::UnescapeRule::NORMAL, NULL, NULL, NULL));
252  // URLs are always LTR.
253  if (is_rtl)
254    base::i18n::WrapStringWithLTRFormatting(&url_for_display);
255  return url_for_display;
256}
257
258scoped_ptr<NavigationCorrectionResponse> ParseNavigationCorrectionResponse(
259    const std::string raw_response) {
260  // TODO(mmenke):  Open source related protocol buffers and use them directly.
261  scoped_ptr<base::Value> parsed(base::JSONReader::Read(raw_response));
262  scoped_ptr<NavigationCorrectionResponse> response(
263      new NavigationCorrectionResponse());
264  base::JSONValueConverter<NavigationCorrectionResponse> converter;
265  if (!parsed || !converter.Convert(*parsed, response.get()))
266    response.reset();
267  return response.Pass();
268}
269
270scoped_ptr<LocalizedError::ErrorPageParams> CreateErrorPageParams(
271    const NavigationCorrectionResponse& response,
272    const blink::WebURLError& error,
273    const NetErrorHelperCore::NavigationCorrectionParams& correction_params,
274    const std::string& accept_languages,
275    bool is_rtl) {
276  // Version of URL for display in suggestions.  It has to be sanitized first
277  // because any received suggestions will be relative to the sanitized URL.
278  base::string16 original_url_for_display =
279      FormatURLForDisplay(SanitizeURL(GURL(error.unreachableURL)), is_rtl,
280                          accept_languages);
281
282  scoped_ptr<LocalizedError::ErrorPageParams> params(
283      new LocalizedError::ErrorPageParams());
284  params->override_suggestions.reset(new base::ListValue());
285  scoped_ptr<base::ListValue> parsed_corrections(new base::ListValue());
286  for (ScopedVector<NavigationCorrection>::const_iterator it =
287           response.corrections.begin();
288       it != response.corrections.end(); ++it) {
289    // Doesn't seem like a good idea to show these.
290    if ((*it)->is_porn || (*it)->is_soft_porn)
291      continue;
292
293    int tracking_id = it - response.corrections.begin();
294
295    if ((*it)->correction_type == "reloadPage") {
296      params->suggest_reload = true;
297      params->reload_tracking_id = tracking_id;
298      continue;
299    }
300
301    if ((*it)->correction_type == "webSearchQuery") {
302      // If there are mutliple searches suggested, use the first suggestion.
303      if (params->search_terms.empty()) {
304        params->search_url = correction_params.search_url;
305        params->search_terms = (*it)->url_correction;
306        params->search_tracking_id = tracking_id;
307      }
308      continue;
309    }
310
311    // Allow reload page and web search query to be empty strings, but not
312    // links.
313    if ((*it)->url_correction.empty())
314      continue;
315    size_t correction_index;
316    for (correction_index = 0;
317         correction_index < arraysize(kCorrectionResourceTable);
318         ++correction_index) {
319      if ((*it)->correction_type !=
320              kCorrectionResourceTable[correction_index].correction_type) {
321        continue;
322      }
323      base::DictionaryValue* suggest = new base::DictionaryValue();
324      suggest->SetString("header",
325          l10n_util::GetStringUTF16(
326              kCorrectionResourceTable[correction_index].resource_id));
327      suggest->SetString("urlCorrection", (*it)->url_correction);
328      suggest->SetString(
329          "urlCorrectionForDisplay",
330          FormatURLForDisplay(GURL((*it)->url_correction), is_rtl,
331                              accept_languages));
332      suggest->SetString("originalUrlForDisplay", original_url_for_display);
333      suggest->SetInteger("trackingId", tracking_id);
334      params->override_suggestions->Append(suggest);
335      break;
336    }
337  }
338
339  if (params->override_suggestions->empty() && !params->search_url.is_valid())
340    params.reset();
341  return params.Pass();
342}
343
344void ReportAutoReloadSuccess(const blink::WebURLError& error, size_t count) {
345  if (error.domain.utf8() != net::kErrorDomain)
346    return;
347  UMA_HISTOGRAM_CUSTOM_ENUMERATION("Net.AutoReload.ErrorAtSuccess",
348                                   -error.reason,
349                                   net::GetAllErrorCodesForUma());
350  UMA_HISTOGRAM_COUNTS("Net.AutoReload.CountAtSuccess", count);
351  if (count == 1) {
352    UMA_HISTOGRAM_CUSTOM_ENUMERATION("Net.AutoReload.ErrorAtFirstSuccess",
353                                     -error.reason,
354                                     net::GetAllErrorCodesForUma());
355  }
356}
357
358void ReportAutoReloadFailure(const blink::WebURLError& error, size_t count) {
359  if (error.domain.utf8() != net::kErrorDomain)
360    return;
361  UMA_HISTOGRAM_CUSTOM_ENUMERATION("Net.AutoReload.ErrorAtStop",
362                                   -error.reason,
363                                   net::GetAllErrorCodesForUma());
364  UMA_HISTOGRAM_COUNTS("Net.AutoReload.CountAtStop", count);
365}
366
367}  // namespace
368
369struct NetErrorHelperCore::ErrorPageInfo {
370  ErrorPageInfo(blink::WebURLError error, bool was_failed_post)
371      : error(error),
372        was_failed_post(was_failed_post),
373        needs_dns_updates(false),
374        needs_load_navigation_corrections(false),
375        reload_button_in_page(false),
376        load_stale_button_in_page(false),
377        is_finished_loading(false),
378        auto_reload_triggered(false) {
379  }
380
381  // Information about the failed page load.
382  blink::WebURLError error;
383  bool was_failed_post;
384
385  // Information about the status of the error page.
386
387  // True if a page is a DNS error page and has not yet received a final DNS
388  // probe status.
389  bool needs_dns_updates;
390
391  // True if a blank page was loaded, and navigation corrections need to be
392  // loaded to generate the real error page.
393  bool needs_load_navigation_corrections;
394
395  // Navigation correction service paramers, which will be used in response to
396  // certain types of network errors.  They are all stored here in case they
397  // change over the course of displaying the error page.
398  scoped_ptr<NetErrorHelperCore::NavigationCorrectionParams>
399      navigation_correction_params;
400
401  scoped_ptr<NavigationCorrectionResponse> navigation_correction_response;
402
403  // All the navigation corrections that have been clicked, for tracking
404  // purposes.
405  std::set<int> clicked_corrections;
406
407  // Track if specific buttons are included in an error page, for statistics.
408  bool reload_button_in_page;
409  bool load_stale_button_in_page;
410
411  // True if a page has completed loading, at which point it can receive
412  // updates.
413  bool is_finished_loading;
414
415  // True if the auto-reload timer has fired and a reload is or has been in
416  // flight.
417  bool auto_reload_triggered;
418};
419
420NetErrorHelperCore::NavigationCorrectionParams::NavigationCorrectionParams() {
421}
422
423NetErrorHelperCore::NavigationCorrectionParams::~NavigationCorrectionParams() {
424}
425
426bool NetErrorHelperCore::IsReloadableError(
427    const NetErrorHelperCore::ErrorPageInfo& info) {
428  return info.error.domain.utf8() == net::kErrorDomain &&
429         info.error.reason != net::ERR_ABORTED &&
430         !info.was_failed_post;
431}
432
433NetErrorHelperCore::NetErrorHelperCore(Delegate* delegate,
434                                       bool auto_reload_enabled,
435                                       bool auto_reload_visible_only,
436                                       bool is_visible)
437    : delegate_(delegate),
438      last_probe_status_(chrome_common_net::DNS_PROBE_POSSIBLE),
439      auto_reload_enabled_(auto_reload_enabled),
440      auto_reload_visible_only_(auto_reload_visible_only),
441      auto_reload_timer_(new base::Timer(false, false)),
442      auto_reload_paused_(false),
443      uncommitted_load_started_(false),
444      // TODO(ellyjones): Make online_ accurate at object creation.
445      online_(true),
446      visible_(is_visible),
447      auto_reload_count_(0),
448      navigation_from_button_(NO_BUTTON) {
449}
450
451NetErrorHelperCore::~NetErrorHelperCore() {
452  if (committed_error_page_info_ &&
453      committed_error_page_info_->auto_reload_triggered) {
454    ReportAutoReloadFailure(committed_error_page_info_->error,
455                            auto_reload_count_);
456  }
457}
458
459void NetErrorHelperCore::CancelPendingFetches() {
460  // Cancel loading the alternate error page, and prevent any pending error page
461  // load from starting a new error page load.  Swapping in the error page when
462  // it's finished loading could abort the navigation, otherwise.
463  if (committed_error_page_info_)
464    committed_error_page_info_->needs_load_navigation_corrections = false;
465  if (pending_error_page_info_)
466    pending_error_page_info_->needs_load_navigation_corrections = false;
467  delegate_->CancelFetchNavigationCorrections();
468  auto_reload_timer_->Stop();
469  auto_reload_paused_ = false;
470}
471
472void NetErrorHelperCore::OnStop() {
473  if (committed_error_page_info_ &&
474      committed_error_page_info_->auto_reload_triggered) {
475    ReportAutoReloadFailure(committed_error_page_info_->error,
476                            auto_reload_count_);
477  }
478  CancelPendingFetches();
479  uncommitted_load_started_ = false;
480  auto_reload_count_ = 0;
481}
482
483void NetErrorHelperCore::OnWasShown() {
484  visible_ = true;
485  if (!auto_reload_visible_only_)
486    return;
487  if (auto_reload_paused_)
488    MaybeStartAutoReloadTimer();
489}
490
491void NetErrorHelperCore::OnWasHidden() {
492  visible_ = false;
493  if (!auto_reload_visible_only_)
494    return;
495  PauseAutoReloadTimer();
496}
497
498void NetErrorHelperCore::OnStartLoad(FrameType frame_type, PageType page_type) {
499  if (frame_type != MAIN_FRAME)
500    return;
501
502  uncommitted_load_started_ = true;
503
504  // If there's no pending error page information associated with the page load,
505  // or the new page is not an error page, then reset pending error page state.
506  if (!pending_error_page_info_ || page_type != ERROR_PAGE)
507    CancelPendingFetches();
508}
509
510void NetErrorHelperCore::OnCommitLoad(FrameType frame_type, const GURL& url) {
511  if (frame_type != MAIN_FRAME)
512    return;
513
514  // uncommitted_load_started_ could already be false, since RenderFrameImpl
515  // calls OnCommitLoad once for each in-page navigation (like a fragment
516  // change) with no corresponding OnStartLoad.
517  uncommitted_load_started_ = false;
518
519  // Track if an error occurred due to a page button press.
520  // This isn't perfect; if (for instance), the server is slow responding
521  // to a request generated from the page reload button, and the user hits
522  // the browser reload button, this code will still believe the
523  // result is from the page reload button.
524  if (committed_error_page_info_ && pending_error_page_info_ &&
525      navigation_from_button_ != NO_BUTTON &&
526      committed_error_page_info_->error.unreachableURL ==
527          pending_error_page_info_->error.unreachableURL) {
528    DCHECK(navigation_from_button_ == RELOAD_BUTTON ||
529           navigation_from_button_ == LOAD_STALE_BUTTON);
530    chrome_common_net::RecordEvent(
531        navigation_from_button_ == RELOAD_BUTTON ?
532            chrome_common_net::NETWORK_ERROR_PAGE_RELOAD_BUTTON_ERROR :
533            chrome_common_net::NETWORK_ERROR_PAGE_LOAD_STALE_BUTTON_ERROR);
534  }
535  navigation_from_button_ = NO_BUTTON;
536
537  if (committed_error_page_info_ && !pending_error_page_info_ &&
538      committed_error_page_info_->auto_reload_triggered) {
539    const blink::WebURLError& error = committed_error_page_info_->error;
540    const GURL& error_url = error.unreachableURL;
541    if (url == error_url)
542      ReportAutoReloadSuccess(error, auto_reload_count_);
543    else if (url != GURL(content::kUnreachableWebDataURL))
544      ReportAutoReloadFailure(error, auto_reload_count_);
545  }
546
547  committed_error_page_info_.reset(pending_error_page_info_.release());
548}
549
550void NetErrorHelperCore::OnFinishLoad(FrameType frame_type) {
551  if (frame_type != MAIN_FRAME)
552    return;
553
554  if (!committed_error_page_info_) {
555    auto_reload_count_ = 0;
556    return;
557  }
558
559  committed_error_page_info_->is_finished_loading = true;
560
561  chrome_common_net::RecordEvent(chrome_common_net::NETWORK_ERROR_PAGE_SHOWN);
562  if (committed_error_page_info_->reload_button_in_page) {
563    chrome_common_net::RecordEvent(
564        chrome_common_net::NETWORK_ERROR_PAGE_RELOAD_BUTTON_SHOWN);
565  }
566  if (committed_error_page_info_->load_stale_button_in_page) {
567    chrome_common_net::RecordEvent(
568        chrome_common_net::NETWORK_ERROR_PAGE_LOAD_STALE_BUTTON_SHOWN);
569  }
570
571  delegate_->EnablePageHelperFunctions();
572
573  if (committed_error_page_info_->needs_load_navigation_corrections) {
574    // If there is another pending error page load, |fix_url| should have been
575    // cleared.
576    DCHECK(!pending_error_page_info_);
577    DCHECK(!committed_error_page_info_->needs_dns_updates);
578    delegate_->FetchNavigationCorrections(
579        committed_error_page_info_->navigation_correction_params->url,
580        CreateFixUrlRequestBody(
581            committed_error_page_info_->error,
582            *committed_error_page_info_->navigation_correction_params));
583  } else if (auto_reload_enabled_ &&
584             IsReloadableError(*committed_error_page_info_)) {
585    MaybeStartAutoReloadTimer();
586  }
587
588  if (!committed_error_page_info_->needs_dns_updates ||
589      last_probe_status_ == chrome_common_net::DNS_PROBE_POSSIBLE) {
590    return;
591  }
592  DVLOG(1) << "Error page finished loading; sending saved status.";
593  UpdateErrorPage();
594}
595
596void NetErrorHelperCore::GetErrorHTML(
597    FrameType frame_type,
598    const blink::WebURLError& error,
599    bool is_failed_post,
600    std::string* error_html) {
601  if (frame_type == MAIN_FRAME) {
602    // If navigation corrections were needed before, that should have been
603    // cancelled earlier by starting a new page load (Which has now failed).
604    DCHECK(!committed_error_page_info_ ||
605           !committed_error_page_info_->needs_load_navigation_corrections);
606
607    pending_error_page_info_.reset(new ErrorPageInfo(error, is_failed_post));
608    pending_error_page_info_->navigation_correction_params.reset(
609        new NavigationCorrectionParams(navigation_correction_params_));
610    GetErrorHtmlForMainFrame(pending_error_page_info_.get(), error_html);
611  } else {
612    // These values do not matter, as error pages in iframes hide the buttons.
613    bool reload_button_in_page;
614    bool load_stale_button_in_page;
615
616    delegate_->GenerateLocalizedErrorPage(
617        error, is_failed_post, scoped_ptr<LocalizedError::ErrorPageParams>(),
618        &reload_button_in_page, &load_stale_button_in_page,
619        error_html);
620  }
621}
622
623void NetErrorHelperCore::OnNetErrorInfo(
624    chrome_common_net::DnsProbeStatus status) {
625  DCHECK_NE(chrome_common_net::DNS_PROBE_POSSIBLE, status);
626
627  last_probe_status_ = status;
628
629  if (!committed_error_page_info_ ||
630      !committed_error_page_info_->needs_dns_updates ||
631      !committed_error_page_info_->is_finished_loading) {
632    return;
633  }
634
635  UpdateErrorPage();
636}
637
638void NetErrorHelperCore::OnSetNavigationCorrectionInfo(
639    const GURL& navigation_correction_url,
640    const std::string& language,
641    const std::string& country_code,
642    const std::string& api_key,
643    const GURL& search_url) {
644  navigation_correction_params_.url = navigation_correction_url;
645  navigation_correction_params_.language = language;
646  navigation_correction_params_.country_code = country_code;
647  navigation_correction_params_.api_key = api_key;
648  navigation_correction_params_.search_url = search_url;
649}
650
651void NetErrorHelperCore::GetErrorHtmlForMainFrame(
652    ErrorPageInfo* pending_error_page_info,
653    std::string* error_html) {
654  std::string error_param;
655  blink::WebURLError error = pending_error_page_info->error;
656
657  if (pending_error_page_info->navigation_correction_params &&
658      pending_error_page_info->navigation_correction_params->url.is_valid() &&
659      ShouldUseFixUrlServiceForError(error, &error_param)) {
660    pending_error_page_info->needs_load_navigation_corrections = true;
661    return;
662  }
663
664  if (IsDnsError(pending_error_page_info->error)) {
665    // The last probe status needs to be reset if this is a DNS error.  This
666    // means that if a DNS error page is committed but has not yet finished
667    // loading, a DNS probe status scheduled to be sent to it may be thrown
668    // out, but since the new error page should trigger a new DNS probe, it
669    // will just get the results for the next page load.
670    last_probe_status_ = chrome_common_net::DNS_PROBE_POSSIBLE;
671    pending_error_page_info->needs_dns_updates = true;
672    error = GetUpdatedError(error);
673  }
674
675  delegate_->GenerateLocalizedErrorPage(
676      error, pending_error_page_info->was_failed_post,
677      scoped_ptr<LocalizedError::ErrorPageParams>(),
678      &pending_error_page_info->reload_button_in_page,
679      &pending_error_page_info->load_stale_button_in_page,
680      error_html);
681}
682
683void NetErrorHelperCore::UpdateErrorPage() {
684  DCHECK(committed_error_page_info_->needs_dns_updates);
685  DCHECK(committed_error_page_info_->is_finished_loading);
686  DCHECK_NE(chrome_common_net::DNS_PROBE_POSSIBLE, last_probe_status_);
687
688  UMA_HISTOGRAM_ENUMERATION("DnsProbe.ErrorPageUpdateStatus",
689                            last_probe_status_,
690                            chrome_common_net::DNS_PROBE_MAX);
691  // Every status other than DNS_PROBE_POSSIBLE and DNS_PROBE_STARTED is a
692  // final status code.  Once one is reached, the page does not need further
693  // updates.
694  if (last_probe_status_ != chrome_common_net::DNS_PROBE_STARTED)
695    committed_error_page_info_->needs_dns_updates = false;
696
697  // There is no need to worry about the button display statistics here because
698  // the presentation of the reload and load stale buttons can't be changed
699  // by a DNS error update.
700  delegate_->UpdateErrorPage(
701      GetUpdatedError(committed_error_page_info_->error),
702      committed_error_page_info_->was_failed_post);
703}
704
705void NetErrorHelperCore::OnNavigationCorrectionsFetched(
706    const std::string& corrections,
707    const std::string& accept_languages,
708    bool is_rtl) {
709  // Loading suggestions only starts when a blank error page finishes loading,
710  // and is cancelled with a new load.
711  DCHECK(!pending_error_page_info_);
712  DCHECK(committed_error_page_info_->is_finished_loading);
713  DCHECK(committed_error_page_info_->needs_load_navigation_corrections);
714  DCHECK(committed_error_page_info_->navigation_correction_params);
715
716  pending_error_page_info_.reset(
717      new ErrorPageInfo(committed_error_page_info_->error,
718                        committed_error_page_info_->was_failed_post));
719  pending_error_page_info_->navigation_correction_response =
720      ParseNavigationCorrectionResponse(corrections);
721
722  std::string error_html;
723  scoped_ptr<LocalizedError::ErrorPageParams> params;
724  if (pending_error_page_info_->navigation_correction_response) {
725    // Copy navigation correction parameters used for the request, so tracking
726    // requests can still be sent if the configuration changes.
727    pending_error_page_info_->navigation_correction_params.reset(
728        new NavigationCorrectionParams(
729            *committed_error_page_info_->navigation_correction_params));
730    params = CreateErrorPageParams(
731          *pending_error_page_info_->navigation_correction_response,
732          pending_error_page_info_->error,
733          *pending_error_page_info_->navigation_correction_params,
734          accept_languages, is_rtl);
735    delegate_->GenerateLocalizedErrorPage(
736        pending_error_page_info_->error,
737        pending_error_page_info_->was_failed_post,
738        params.Pass(),
739        &pending_error_page_info_->reload_button_in_page,
740        &pending_error_page_info_->load_stale_button_in_page,
741        &error_html);
742  } else {
743    // Since |navigation_correction_params| in |pending_error_page_info_| is
744    // NULL, this won't trigger another attempt to load corrections.
745    GetErrorHtmlForMainFrame(pending_error_page_info_.get(), &error_html);
746  }
747
748  // TODO(mmenke):  Once the new API is in place, look into replacing this
749  //                double page load by just updating the error page, like DNS
750  //                probes do.
751  delegate_->LoadErrorPageInMainFrame(
752      error_html,
753      pending_error_page_info_->error.unreachableURL);
754}
755
756blink::WebURLError NetErrorHelperCore::GetUpdatedError(
757    const blink::WebURLError& error) const {
758  // If a probe didn't run or wasn't conclusive, restore the original error.
759  if (last_probe_status_ == chrome_common_net::DNS_PROBE_NOT_RUN ||
760      last_probe_status_ ==
761          chrome_common_net::DNS_PROBE_FINISHED_INCONCLUSIVE) {
762    return error;
763  }
764
765  blink::WebURLError updated_error;
766  updated_error.domain = blink::WebString::fromUTF8(
767      chrome_common_net::kDnsProbeErrorDomain);
768  updated_error.reason = last_probe_status_;
769  updated_error.unreachableURL = error.unreachableURL;
770  updated_error.staleCopyInCache = error.staleCopyInCache;
771
772  return updated_error;
773}
774
775void NetErrorHelperCore::Reload() {
776  if (!committed_error_page_info_) {
777    return;
778  }
779  delegate_->ReloadPage();
780}
781
782bool NetErrorHelperCore::MaybeStartAutoReloadTimer() {
783  if (!committed_error_page_info_ ||
784      !committed_error_page_info_->is_finished_loading ||
785      pending_error_page_info_ ||
786      uncommitted_load_started_) {
787    return false;
788  }
789
790  StartAutoReloadTimer();
791  return true;
792}
793
794void NetErrorHelperCore::StartAutoReloadTimer() {
795  DCHECK(committed_error_page_info_);
796  DCHECK(IsReloadableError(*committed_error_page_info_));
797
798  committed_error_page_info_->auto_reload_triggered = true;
799
800  if (!online_ || (!visible_ && auto_reload_visible_only_)) {
801    auto_reload_paused_ = true;
802    return;
803  }
804
805  auto_reload_paused_ = false;
806  base::TimeDelta delay = GetAutoReloadTime(auto_reload_count_);
807  auto_reload_timer_->Stop();
808  auto_reload_timer_->Start(FROM_HERE, delay,
809      base::Bind(&NetErrorHelperCore::AutoReloadTimerFired,
810                 base::Unretained(this)));
811}
812
813void NetErrorHelperCore::AutoReloadTimerFired() {
814  auto_reload_count_++;
815  Reload();
816}
817
818void NetErrorHelperCore::PauseAutoReloadTimer() {
819  if (!auto_reload_timer_->IsRunning())
820    return;
821  DCHECK(committed_error_page_info_);
822  DCHECK(!auto_reload_paused_);
823  DCHECK(committed_error_page_info_->auto_reload_triggered);
824  auto_reload_timer_->Stop();
825  auto_reload_paused_ = true;
826}
827
828void NetErrorHelperCore::NetworkStateChanged(bool online) {
829  bool was_online = online_;
830  online_ = online;
831  if (!was_online && online) {
832    // Transitioning offline -> online
833    if (auto_reload_paused_)
834      MaybeStartAutoReloadTimer();
835  } else if (was_online && !online) {
836    // Transitioning online -> offline
837    if (auto_reload_timer_->IsRunning())
838      auto_reload_count_ = 0;
839    PauseAutoReloadTimer();
840  }
841}
842
843bool NetErrorHelperCore::ShouldSuppressErrorPage(FrameType frame_type,
844                                                 const GURL& url) {
845  // Don't suppress child frame errors.
846  if (frame_type != MAIN_FRAME)
847    return false;
848
849  if (!auto_reload_enabled_)
850    return false;
851
852  // If there's no committed error page, this error page wasn't from an auto
853  // reload.
854  if (!committed_error_page_info_)
855    return false;
856
857  // If the error page wasn't reloadable, display it.
858  if (!IsReloadableError(*committed_error_page_info_))
859    return false;
860
861  // If |auto_reload_timer_| is still running or is paused, this error page
862  // isn't from an auto reload.
863  if (auto_reload_timer_->IsRunning() || auto_reload_paused_)
864    return false;
865
866  // If the error page was reloadable, and the timer isn't running or paused, an
867  // auto-reload has already been triggered.
868  DCHECK(committed_error_page_info_->auto_reload_triggered);
869
870  GURL error_url = committed_error_page_info_->error.unreachableURL;
871  // TODO(ellyjones): also plumb the error code down to CCRC and check that
872  if (error_url != url)
873    return false;
874
875  // Suppressed an error-page load; the previous uncommitted load was the error
876  // page load starting, so forget about it.
877  uncommitted_load_started_ = false;
878
879  // The first iteration of the timer is started by OnFinishLoad calling
880  // MaybeStartAutoReloadTimer, but since error pages for subsequent loads are
881  // suppressed in this function, subsequent iterations of the timer have to be
882  // started here.
883  MaybeStartAutoReloadTimer();
884  return true;
885}
886
887void NetErrorHelperCore::ExecuteButtonPress(Button button) {
888  switch (button) {
889    case RELOAD_BUTTON:
890      chrome_common_net::RecordEvent(
891          chrome_common_net::NETWORK_ERROR_PAGE_RELOAD_BUTTON_CLICKED);
892      navigation_from_button_ = RELOAD_BUTTON;
893      Reload();
894      return;
895    case LOAD_STALE_BUTTON:
896      chrome_common_net::RecordEvent(
897          chrome_common_net::NETWORK_ERROR_PAGE_LOAD_STALE_BUTTON_CLICKED);
898      navigation_from_button_ = LOAD_STALE_BUTTON;
899      delegate_->LoadPageFromCache(
900          committed_error_page_info_->error.unreachableURL);
901      return;
902    case MORE_BUTTON:
903      // Visual effects on page are handled in Javascript code.
904      chrome_common_net::RecordEvent(
905          chrome_common_net::NETWORK_ERROR_PAGE_MORE_BUTTON_CLICKED);
906      return;
907    case NO_BUTTON:
908      NOTREACHED();
909      return;
910  }
911}
912
913void NetErrorHelperCore::TrackClick(int tracking_id) {
914  // It's technically possible for |navigation_correction_params| to be NULL but
915  // for |navigation_correction_response| not to be NULL, if the paramters
916  // changed between loading the original error page and loading the error page
917  if (!committed_error_page_info_ ||
918      !committed_error_page_info_->navigation_correction_response) {
919    return;
920  }
921
922  NavigationCorrectionResponse* response =
923      committed_error_page_info_->navigation_correction_response.get();
924
925  // |tracking_id| is less than 0 when the error page was not generated by the
926  // navigation correction service.  |tracking_id| should never be greater than
927  // the array size, but best to be safe, since it contains data from a remote
928  // site, though none of that data should make it into Javascript callbacks.
929  if (tracking_id < 0 ||
930      static_cast<size_t>(tracking_id) >= response->corrections.size()) {
931    return;
932  }
933
934  // Only report a clicked link once.
935  if (committed_error_page_info_->clicked_corrections.count(tracking_id))
936    return;
937
938  committed_error_page_info_->clicked_corrections.insert(tracking_id);
939  std::string request_body = CreateClickTrackingUrlRequestBody(
940      committed_error_page_info_->error,
941      *committed_error_page_info_->navigation_correction_params,
942      *response,
943      *response->corrections[tracking_id]);
944  delegate_->SendTrackingRequest(
945      committed_error_page_info_->navigation_correction_params->url,
946      request_body);
947}
948
949