Home | History | Annotate | Download | only in net
      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 "chrome/grit/generated_resources.h"
     26 #include "content/public/common/url_constants.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 
     35 namespace {
     36 
     37 struct CorrectionTypeToResourceTable {
     38   int resource_id;
     39   const char* correction_type;
     40 };
     41 
     42 const 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 
     58 struct 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 
     85 struct 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 
    102 base::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.)
    113 bool 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 
    119 GURL 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.
    129 std::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.
    144 bool 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.
    176 std::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.
    206 std::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 
    221 std::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 
    246 base::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 
    258 scoped_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 
    270 scoped_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 
    344 void 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 
    358 void 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 
    369 struct 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 
    420 NetErrorHelperCore::NavigationCorrectionParams::NavigationCorrectionParams() {
    421 }
    422 
    423 NetErrorHelperCore::NavigationCorrectionParams::~NavigationCorrectionParams() {
    424 }
    425 
    426 bool 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 
    433 NetErrorHelperCore::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 
    451 NetErrorHelperCore::~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 
    459 void 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 
    472 void 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 
    483 void NetErrorHelperCore::OnWasShown() {
    484   visible_ = true;
    485   if (!auto_reload_visible_only_)
    486     return;
    487   if (auto_reload_paused_)
    488     MaybeStartAutoReloadTimer();
    489 }
    490 
    491 void NetErrorHelperCore::OnWasHidden() {
    492   visible_ = false;
    493   if (!auto_reload_visible_only_)
    494     return;
    495   PauseAutoReloadTimer();
    496 }
    497 
    498 void 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 
    510 void 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 
    550 void 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 
    596 void 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 
    623 void 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 
    638 void 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 
    651 void 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 
    683 void 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 
    705 void 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 
    756 blink::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 
    775 void NetErrorHelperCore::Reload() {
    776   if (!committed_error_page_info_) {
    777     return;
    778   }
    779   delegate_->ReloadPage();
    780 }
    781 
    782 bool 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 
    794 void 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 
    813 void NetErrorHelperCore::AutoReloadTimerFired() {
    814   auto_reload_count_++;
    815   Reload();
    816 }
    817 
    818 void 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 
    828 void 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 
    843 bool 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 
    887 void 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 
    913 void 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