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