| OLD | NEW |
| (Empty) |
| 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 | |
| OLD | NEW |