• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
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 {
NavigationCorrection__anona069a5230111::NavigationCorrection59   NavigationCorrection() : is_porn(false), is_soft_porn(false) {
60   }
61 
RegisterJSONConverter__anona069a5230111::NavigationCorrection62   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 
RegisterJSONConverter__anona069a5230111::NavigationCorrectionResponse90   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 
GetAutoReloadTime(size_t reload_count)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.)
IsDnsError(const blink::WebURLError & error)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 
SanitizeURL(const GURL & url)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.
PrepareUrlForUpload(const GURL & url)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.
ShouldUseFixUrlServiceForError(const blink::WebURLError & error,std::string * error_param)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.
CreateRequestBody(const std::string & method,const std::string & error_param,const NetErrorHelperCore::NavigationCorrectionParams & correction_params,scoped_ptr<base::DictionaryValue> params_dict)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.
CreateFixUrlRequestBody(const blink::WebURLError & error,const NetErrorHelperCore::NavigationCorrectionParams & correction_params)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 
CreateClickTrackingUrlRequestBody(const blink::WebURLError & error,const NetErrorHelperCore::NavigationCorrectionParams & correction_params,const NavigationCorrectionResponse & response,const NavigationCorrection & correction)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 
FormatURLForDisplay(const GURL & url,bool is_rtl,const std::string accept_languages)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 
ParseNavigationCorrectionResponse(const std::string raw_response)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 
CreateErrorPageParams(const NavigationCorrectionResponse & response,const blink::WebURLError & error,const NetErrorHelperCore::NavigationCorrectionParams & correction_params,const std::string & accept_languages,bool is_rtl)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 
ReportAutoReloadSuccess(const blink::WebURLError & error,size_t count)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 
ReportAutoReloadFailure(const blink::WebURLError & error,size_t count)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 {
ErrorPageInfoNetErrorHelperCore::ErrorPageInfo370   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 
NavigationCorrectionParams()420 NetErrorHelperCore::NavigationCorrectionParams::NavigationCorrectionParams() {
421 }
422 
~NavigationCorrectionParams()423 NetErrorHelperCore::NavigationCorrectionParams::~NavigationCorrectionParams() {
424 }
425 
IsReloadableError(const NetErrorHelperCore::ErrorPageInfo & info)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 
NetErrorHelperCore(Delegate * delegate,bool auto_reload_enabled,bool auto_reload_visible_only,bool is_visible)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 
~NetErrorHelperCore()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 
CancelPendingFetches()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 
OnStop()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 
OnWasShown()483 void NetErrorHelperCore::OnWasShown() {
484   visible_ = true;
485   if (!auto_reload_visible_only_)
486     return;
487   if (auto_reload_paused_)
488     MaybeStartAutoReloadTimer();
489 }
490 
OnWasHidden()491 void NetErrorHelperCore::OnWasHidden() {
492   visible_ = false;
493   if (!auto_reload_visible_only_)
494     return;
495   PauseAutoReloadTimer();
496 }
497 
OnStartLoad(FrameType frame_type,PageType page_type)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 
OnCommitLoad(FrameType frame_type,const GURL & url)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 
OnFinishLoad(FrameType frame_type)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 
GetErrorHTML(FrameType frame_type,const blink::WebURLError & error,bool is_failed_post,std::string * error_html)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 
OnNetErrorInfo(chrome_common_net::DnsProbeStatus status)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 
OnSetNavigationCorrectionInfo(const GURL & navigation_correction_url,const std::string & language,const std::string & country_code,const std::string & api_key,const GURL & search_url)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 
GetErrorHtmlForMainFrame(ErrorPageInfo * pending_error_page_info,std::string * error_html)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 
UpdateErrorPage()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 
OnNavigationCorrectionsFetched(const std::string & corrections,const std::string & accept_languages,bool is_rtl)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 
GetUpdatedError(const blink::WebURLError & error) const756 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 
Reload()775 void NetErrorHelperCore::Reload() {
776   if (!committed_error_page_info_) {
777     return;
778   }
779   delegate_->ReloadPage();
780 }
781 
MaybeStartAutoReloadTimer()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 
StartAutoReloadTimer()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 
AutoReloadTimerFired()813 void NetErrorHelperCore::AutoReloadTimerFired() {
814   auto_reload_count_++;
815   Reload();
816 }
817 
PauseAutoReloadTimer()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 
NetworkStateChanged(bool online)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 
ShouldSuppressErrorPage(FrameType frame_type,const GURL & url)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 
ExecuteButtonPress(Button button)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 
TrackClick(int tracking_id)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