diff options
author | hashimoto <hashimoto@chromium.org> | 2014-10-14 20:56:53 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-10-15 03:57:40 +0000 |
commit | 9b160e24e7f967dda04cac13d393124ba173a0e8 (patch) | |
tree | 8ab2a11f55aa1886b158c636349b774e65580eb2 /components/error_page | |
parent | 3517bb6c601efad861dddcf01a16e2c68bf927c7 (diff) | |
download | chromium_src-9b160e24e7f967dda04cac13d393124ba173a0e8.zip chromium_src-9b160e24e7f967dda04cac13d393124ba173a0e8.tar.gz chromium_src-9b160e24e7f967dda04cac13d393124ba173a0e8.tar.bz2 |
Componentize NetErrorHelperCore
Create a new component error_page which will host NetErrorHelper to make it usable from binaries other than chrome.
Move NetErrorHelperCore and net_error_info.{cc,h} to components/error_page.
Split LocalizedError::ErrorPageParams to components/error_page/common/error_page_params.
Move string resources to error_page_strings.grdp
Fix GYP, GN, DEPS.
Copy OWNERS from chrome/renderer/net to components/error_page.
BUG=398173
TEST=build
TBR=sky@chromium.org for +ui/base in DEPS
Review URL: https://codereview.chromium.org/570253002
Cr-Commit-Position: refs/heads/master@{#299647}
Diffstat (limited to 'components/error_page')
-rw-r--r-- | components/error_page/OWNERS | 2 | ||||
-rw-r--r-- | components/error_page/common/BUILD.gn | 17 | ||||
-rw-r--r-- | components/error_page/common/error_page_params.cc | 20 | ||||
-rw-r--r-- | components/error_page/common/error_page_params.h | 45 | ||||
-rw-r--r-- | components/error_page/common/net_error_info.cc | 46 | ||||
-rw-r--r-- | components/error_page/common/net_error_info.h | 92 | ||||
-rw-r--r-- | components/error_page/renderer/BUILD.gn | 21 | ||||
-rw-r--r-- | components/error_page/renderer/DEPS | 7 | ||||
-rw-r--r-- | components/error_page/renderer/net_error_helper_core.cc | 952 | ||||
-rw-r--r-- | components/error_page/renderer/net_error_helper_core.h | 271 | ||||
-rw-r--r-- | components/error_page/renderer/net_error_helper_core_unittest.cc | 2436 |
11 files changed, 3909 insertions, 0 deletions
diff --git a/components/error_page/OWNERS b/components/error_page/OWNERS new file mode 100644 index 0000000..a479fb9 --- /dev/null +++ b/components/error_page/OWNERS @@ -0,0 +1,2 @@ +mmenke@chromium.org +ttuttle@chromium.org diff --git a/components/error_page/common/BUILD.gn b/components/error_page/common/BUILD.gn new file mode 100644 index 0000000..b3c5b88 --- /dev/null +++ b/components/error_page/common/BUILD.gn @@ -0,0 +1,17 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +static_library("common") { + sources = [ + "error_page_params.cc", + "error_page_params.h", + "net_error_info.cc", + "net_error_info.h", + ] + + deps = [ + "//base", + "//url", + ] +}
\ No newline at end of file diff --git a/components/error_page/common/error_page_params.cc b/components/error_page/common/error_page_params.cc new file mode 100644 index 0000000..a6d3c42 --- /dev/null +++ b/components/error_page/common/error_page_params.cc @@ -0,0 +1,20 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/error_page/common/error_page_params.h" + +#include "base/values.h" + +namespace error_page { + +ErrorPageParams::ErrorPageParams() + : suggest_reload(false), + reload_tracking_id(-1), + search_tracking_id(-1) { +} + +ErrorPageParams::~ErrorPageParams() { +} + +} // namespace error_page diff --git a/components/error_page/common/error_page_params.h b/components/error_page/common/error_page_params.h new file mode 100644 index 0000000..c0b2203 --- /dev/null +++ b/components/error_page/common/error_page_params.h @@ -0,0 +1,45 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_NET_ERROR_COMMON_ERROR_PAGE_PARAMS_H_ +#define COMPONENTS_NET_ERROR_COMMON_ERROR_PAGE_PARAMS_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "url/gurl.h" + +namespace base { +class ListValue; +} + +namespace error_page { + +// Optional parameters that affect the display of an error page. +struct ErrorPageParams { + ErrorPageParams(); + ~ErrorPageParams(); + + // Overrides whether reloading is suggested. + bool suggest_reload; + int reload_tracking_id; + + // Overrides default suggestions. Each entry must be a DictionaryValuethat + // contains a "header" entry. A "body" entry may optionally be specified. + // JSTemplate evaluation will be applied when added to the DOM. If NULL, the + // default suggestions will be used. + scoped_ptr<base::ListValue> override_suggestions; + + // Prefix to prepend to search terms. Search box is only shown if this is + // a valid url. The search terms will be appended to the end of this URL to + // conduct a search. + GURL search_url; + // Default search terms. Ignored if |search_url| is invalid. + std::string search_terms; + int search_tracking_id; +}; + +} // namespace error_page + +#endif // COMPONENTS_NET_ERROR_COMMON_ERROR_PAGE_PARAMS_H_ diff --git a/components/error_page/common/net_error_info.cc b/components/error_page/common/net_error_info.cc new file mode 100644 index 0000000..d47e7e5 --- /dev/null +++ b/components/error_page/common/net_error_info.cc @@ -0,0 +1,46 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/error_page/common/net_error_info.h" + +#include "base/logging.h" +#include "base/metrics/histogram.h" + +namespace chrome_common_net { + +const char kDnsProbeErrorDomain[] = "dnsprobe"; + +const char* DnsProbeStatusToString(int status) { + switch (status) { + case DNS_PROBE_POSSIBLE: + return "DNS_PROBE_POSSIBLE"; + case DNS_PROBE_NOT_RUN: + return "DNS_PROBE_NOT_RUN"; + case DNS_PROBE_STARTED: + return "DNS_PROBE_STARTED"; + case DNS_PROBE_FINISHED_INCONCLUSIVE: + return "DNS_PROBE_FINISHED_INCONCLUSIVE"; + case DNS_PROBE_FINISHED_NO_INTERNET: + return "DNS_PROBE_FINISHED_NO_INTERNET"; + case DNS_PROBE_FINISHED_BAD_CONFIG: + return "DNS_PROBE_FINISHED_BAD_CONFIG"; + case DNS_PROBE_FINISHED_NXDOMAIN: + return "DNS_PROBE_FINISHED_NXDOMAIN"; + default: + NOTREACHED(); + return ""; + } +} + +bool DnsProbeStatusIsFinished(DnsProbeStatus status) { + return status >= DNS_PROBE_FINISHED_INCONCLUSIVE && + status < DNS_PROBE_MAX; +} + +void RecordEvent(NetworkErrorPageEvent event) { + UMA_HISTOGRAM_ENUMERATION("Net.ErrorPageCounts", event, + NETWORK_ERROR_PAGE_EVENT_MAX); +} + +} // namespace chrome_common_net diff --git a/components/error_page/common/net_error_info.h b/components/error_page/common/net_error_info.h new file mode 100644 index 0000000..35433ff --- /dev/null +++ b/components/error_page/common/net_error_info.h @@ -0,0 +1,92 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_ERROR_PAGE_COMMON_NET_ERROR_INFO_H_ +#define COMPONENTS_ERROR_PAGE_COMMON_NET_ERROR_INFO_H_ + +// TODO(hashimoto): Change this to namespace error_page. +namespace chrome_common_net { + +// Network error page events. Used for UMA statistics. +enum NetworkErrorPageEvent { + NETWORK_ERROR_PAGE_SHOWN, // Error pages shown. + + NETWORK_ERROR_PAGE_RELOAD_BUTTON_SHOWN, // Reload buttons shown. + NETWORK_ERROR_PAGE_RELOAD_BUTTON_CLICKED, // Reload button clicked. + NETWORK_ERROR_PAGE_RELOAD_BUTTON_ERROR, // Reload button clicked + // -> error. + + NETWORK_ERROR_PAGE_LOAD_STALE_BUTTON_SHOWN, // Load stale buttons shown. + NETWORK_ERROR_PAGE_LOAD_STALE_BUTTON_CLICKED, // Load stale button clicked. + NETWORK_ERROR_PAGE_LOAD_STALE_BUTTON_ERROR, // Load stale buttons -> error. + + NETWORK_ERROR_PAGE_MORE_BUTTON_CLICKED, // More button clicked. + + NETWORK_ERROR_PAGE_BROWSER_INITIATED_RELOAD, // Reload from browser. + + NETWORK_ERROR_PAGE_EVENT_MAX, +}; + +// The status of a DNS probe. +// +// The DNS_PROBE_FINISHED_* values are used in histograms, so: +// 1. FINISHED_UNKNOWN must remain the first FINISHED_* value. +// 2. FINISHED_* values must not be rearranged relative to FINISHED_UNKNOWN. +// 3. New FINISHED_* values must be inserted at the end. +// 4. New non-FINISHED_* values cannot be inserted. +enum DnsProbeStatus { + // A DNS probe may be run for this error page. (This status is only used on + // the renderer side before it's received a status update from the browser.) + DNS_PROBE_POSSIBLE, + + // A DNS probe will not be run for this error page. (This happens if the + // user has the "Use web service to resolve navigation errors" preference + // turned off, or if probes are disabled by the field trial.) + DNS_PROBE_NOT_RUN, + + // A DNS probe has been started for this error page. The renderer should + // expect to receive another IPC with one of the FINISHED statuses once the + // probe has finished (as long as the error page is still loaded). + DNS_PROBE_STARTED, + + // A DNS probe has finished with one of the following results: + + // The probe was inconclusive. + DNS_PROBE_FINISHED_INCONCLUSIVE, + + // There's no internet connection. + DNS_PROBE_FINISHED_NO_INTERNET, + + // The DNS configuration is wrong, or the servers are down or broken. + DNS_PROBE_FINISHED_BAD_CONFIG, + + // The DNS servers are working fine, so the domain must not exist. + DNS_PROBE_FINISHED_NXDOMAIN, + + DNS_PROBE_MAX +}; + +// Returns a string representing |status|. It should be simply the name of +// the value as a string, but don't rely on that. This is presented to the +// user as part of the DNS error page (as the error code, at the bottom), +// and is also used in some verbose log messages. +// +// The function will NOTREACHED() and return an empty string if given an int +// that does not match a value in DnsProbeStatus (or if it is DNS_PROBE_MAX, +// which is not a real status). +const char* DnsProbeStatusToString(int status); + +// Returns true if |status| is one of the DNS_PROBE_FINISHED_* statuses. +bool DnsProbeStatusIsFinished(DnsProbeStatus status); + +// Record specific error page events. +void RecordEvent(NetworkErrorPageEvent event); + +// The error domain used to pass DNS probe statuses to the localized error +// code. +extern const char kDnsProbeErrorDomain[]; + +} // namespace chrome_common_net + +#endif // COMPONENTS_ERROR_PAGE_COMMON_NET_ERROR_INFO_H_ diff --git a/components/error_page/renderer/BUILD.gn b/components/error_page/renderer/BUILD.gn new file mode 100644 index 0000000..96fe7ff --- /dev/null +++ b/components/error_page/renderer/BUILD.gn @@ -0,0 +1,21 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +static_library("renderer") { + sources = [ + "net_error_helper_core.cc", + "net_error_helper_core.h", + ] + + deps = [ + "//base", + "//components/error_page/common", + "//components/strings", + "//content/public/common", + "//net", + "//third_party/WebKit/public:blink", + "//ui/base", + "//url", + ] +}
\ No newline at end of file diff --git a/components/error_page/renderer/DEPS b/components/error_page/renderer/DEPS new file mode 100644 index 0000000..5d271a9 --- /dev/null +++ b/components/error_page/renderer/DEPS @@ -0,0 +1,7 @@ +include_rules = [ + "+content/public/common", + "+grit/components_strings.h", + "+net", + "+third_party/WebKit/public/platform", + "+ui/base", +] diff --git a/components/error_page/renderer/net_error_helper_core.cc b/components/error_page/renderer/net_error_helper_core.cc new file mode 100644 index 0000000..93f943e --- /dev/null +++ b/components/error_page/renderer/net_error_helper_core.cc @@ -0,0 +1,952 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/error_page/renderer/net_error_helper_core.h" + +#include <set> +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/i18n/rtl.h" +#include "base/json/json_reader.h" +#include "base/json/json_value_converter.h" +#include "base/json/json_writer.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/scoped_vector.h" +#include "base/metrics/histogram.h" +#include "base/strings/string16.h" +#include "base/strings/string_util.h" +#include "base/values.h" +#include "components/error_page/common/error_page_params.h" +#include "content/public/common/url_constants.h" +#include "grit/components_strings.h" +#include "net/base/escape.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "third_party/WebKit/public/platform/WebString.h" +#include "third_party/WebKit/public/platform/WebURLError.h" +#include "ui/base/l10n/l10n_util.h" +#include "url/gurl.h" + +namespace error_page { + +namespace { + +struct CorrectionTypeToResourceTable { + int resource_id; + const char* correction_type; +}; + +const CorrectionTypeToResourceTable kCorrectionResourceTable[] = { + {IDS_ERRORPAGES_SUGGESTION_VISIT_GOOGLE_CACHE, "cachedPage"}, + // "reloadPage" is has special handling. + {IDS_ERRORPAGES_SUGGESTION_CORRECTED_URL, "urlCorrection"}, + {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "siteDomain"}, + {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "host"}, + {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "sitemap"}, + {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "pathParentFolder"}, + // "siteSearchQuery" is not yet supported. + // TODO(mmenke): Figure out what format "siteSearchQuery" uses for its + // suggestions. + // "webSearchQuery" has special handling. + {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "contentOverlap"}, + {IDS_ERRORPAGES_SUGGESTION_CORRECTED_URL, "emphasizedUrlCorrection"}, +}; + +struct NavigationCorrection { + NavigationCorrection() : is_porn(false), is_soft_porn(false) { + } + + static void RegisterJSONConverter( + base::JSONValueConverter<NavigationCorrection>* converter) { + converter->RegisterStringField("correctionType", + &NavigationCorrection::correction_type); + converter->RegisterStringField("urlCorrection", + &NavigationCorrection::url_correction); + converter->RegisterStringField("clickType", + &NavigationCorrection::click_type); + converter->RegisterStringField("clickData", + &NavigationCorrection::click_data); + converter->RegisterBoolField("isPorn", &NavigationCorrection::is_porn); + converter->RegisterBoolField("isSoftPorn", + &NavigationCorrection::is_soft_porn); + } + + std::string correction_type; + std::string url_correction; + std::string click_type; + std::string click_data; + bool is_porn; + bool is_soft_porn; +}; + +struct NavigationCorrectionResponse { + std::string event_id; + std::string fingerprint; + ScopedVector<NavigationCorrection> corrections; + + static void RegisterJSONConverter( + base::JSONValueConverter<NavigationCorrectionResponse>* converter) { + converter->RegisterStringField("result.eventId", + &NavigationCorrectionResponse::event_id); + converter->RegisterStringField("result.fingerprint", + &NavigationCorrectionResponse::fingerprint); + converter->RegisterRepeatedMessage( + "result.UrlCorrections", + &NavigationCorrectionResponse::corrections); + } +}; + +base::TimeDelta GetAutoReloadTime(size_t reload_count) { + static const int kDelaysMs[] = { + 0, 5000, 30000, 60000, 300000, 600000, 1800000 + }; + if (reload_count >= arraysize(kDelaysMs)) + reload_count = arraysize(kDelaysMs) - 1; + return base::TimeDelta::FromMilliseconds(kDelaysMs[reload_count]); +} + +// Returns whether |net_error| is a DNS-related error (and therefore whether +// the tab helper should start a DNS probe after receiving it.) +bool IsDnsError(const blink::WebURLError& error) { + return error.domain.utf8() == net::kErrorDomain && + (error.reason == net::ERR_NAME_NOT_RESOLVED || + error.reason == net::ERR_NAME_RESOLUTION_FAILED); +} + +GURL SanitizeURL(const GURL& url) { + GURL::Replacements remove_params; + remove_params.ClearUsername(); + remove_params.ClearPassword(); + remove_params.ClearQuery(); + remove_params.ClearRef(); + return url.ReplaceComponents(remove_params); +} + +// Sanitizes and formats a URL for upload to the error correction service. +std::string PrepareUrlForUpload(const GURL& url) { + // TODO(yuusuke): Change to net::FormatUrl when Link Doctor becomes + // unicode-capable. + std::string spec_to_send = SanitizeURL(url).spec(); + + // Notify navigation correction service of the url truncation by sending of + // "?" at the end. + if (url.has_query()) + spec_to_send.append("?"); + return spec_to_send; +} + +// Given a WebURLError, returns true if the FixURL service should be used +// for that error. Also sets |error_param| to the string that should be sent to +// the FixURL service to identify the error type. +bool ShouldUseFixUrlServiceForError(const blink::WebURLError& error, + std::string* error_param) { + error_param->clear(); + + // Don't use the correction service for HTTPS (for privacy reasons). + GURL unreachable_url(error.unreachableURL); + if (GURL(unreachable_url).SchemeIsSecure()) + return false; + + std::string domain = error.domain.utf8(); + if (domain == "http" && error.reason == 404) { + *error_param = "http404"; + return true; + } + if (IsDnsError(error)) { + *error_param = "dnserror"; + return true; + } + if (domain == net::kErrorDomain && + (error.reason == net::ERR_CONNECTION_FAILED || + error.reason == net::ERR_CONNECTION_REFUSED || + error.reason == net::ERR_ADDRESS_UNREACHABLE || + error.reason == net::ERR_CONNECTION_TIMED_OUT)) { + *error_param = "connectionFailure"; + return true; + } + return false; +} + +// Creates a request body for use with the fixurl service. Sets parameters +// shared by all types of requests to the service. |correction_params| must +// contain the parameters specific to the actual request type. +std::string CreateRequestBody( + const std::string& method, + const std::string& error_param, + const NetErrorHelperCore::NavigationCorrectionParams& correction_params, + scoped_ptr<base::DictionaryValue> params_dict) { + // Set params common to all request types. + params_dict->SetString("key", correction_params.api_key); + params_dict->SetString("clientName", "chrome"); + params_dict->SetString("error", error_param); + + if (!correction_params.language.empty()) + params_dict->SetString("language", correction_params.language); + + if (!correction_params.country_code.empty()) + params_dict->SetString("originCountry", correction_params.country_code); + + base::DictionaryValue request_dict; + request_dict.SetString("method", method); + request_dict.SetString("apiVersion", "v1"); + request_dict.Set("params", params_dict.release()); + + std::string request_body; + bool success = base::JSONWriter::Write(&request_dict, &request_body); + DCHECK(success); + return request_body; +} + +// If URL correction information should be retrieved remotely for a main frame +// load that failed with |error|, returns true and sets +// |correction_request_body| to be the body for the correction request. +std::string CreateFixUrlRequestBody( + const blink::WebURLError& error, + const NetErrorHelperCore::NavigationCorrectionParams& correction_params) { + std::string error_param; + bool result = ShouldUseFixUrlServiceForError(error, &error_param); + DCHECK(result); + + // TODO(mmenke): Investigate open sourcing the relevant protocol buffers and + // using those directly instead. + scoped_ptr<base::DictionaryValue> params(new base::DictionaryValue()); + params->SetString("urlQuery", PrepareUrlForUpload(error.unreachableURL)); + return CreateRequestBody("linkdoctor.fixurl.fixurl", error_param, + correction_params, params.Pass()); +} + +std::string CreateClickTrackingUrlRequestBody( + const blink::WebURLError& error, + const NetErrorHelperCore::NavigationCorrectionParams& correction_params, + const NavigationCorrectionResponse& response, + const NavigationCorrection& correction) { + std::string error_param; + bool result = ShouldUseFixUrlServiceForError(error, &error_param); + DCHECK(result); + + scoped_ptr<base::DictionaryValue> params(new base::DictionaryValue()); + + params->SetString("originalUrlQuery", + PrepareUrlForUpload(error.unreachableURL)); + + params->SetString("clickedUrlCorrection", correction.url_correction); + params->SetString("clickType", correction.click_type); + params->SetString("clickData", correction.click_data); + + params->SetString("eventId", response.event_id); + params->SetString("fingerprint", response.fingerprint); + + return CreateRequestBody("linkdoctor.fixurl.clicktracking", error_param, + correction_params, params.Pass()); +} + +base::string16 FormatURLForDisplay(const GURL& url, bool is_rtl, + const std::string accept_languages) { + // Translate punycode into UTF8, unescape UTF8 URLs. + base::string16 url_for_display(net::FormatUrl( + url, accept_languages, net::kFormatUrlOmitNothing, + net::UnescapeRule::NORMAL, NULL, NULL, NULL)); + // URLs are always LTR. + if (is_rtl) + base::i18n::WrapStringWithLTRFormatting(&url_for_display); + return url_for_display; +} + +scoped_ptr<NavigationCorrectionResponse> ParseNavigationCorrectionResponse( + const std::string raw_response) { + // TODO(mmenke): Open source related protocol buffers and use them directly. + scoped_ptr<base::Value> parsed(base::JSONReader::Read(raw_response)); + scoped_ptr<NavigationCorrectionResponse> response( + new NavigationCorrectionResponse()); + base::JSONValueConverter<NavigationCorrectionResponse> converter; + if (!parsed || !converter.Convert(*parsed, response.get())) + response.reset(); + return response.Pass(); +} + +scoped_ptr<ErrorPageParams> CreateErrorPageParams( + const NavigationCorrectionResponse& response, + const blink::WebURLError& error, + const NetErrorHelperCore::NavigationCorrectionParams& correction_params, + const std::string& accept_languages, + bool is_rtl) { + // Version of URL for display in suggestions. It has to be sanitized first + // because any received suggestions will be relative to the sanitized URL. + base::string16 original_url_for_display = + FormatURLForDisplay(SanitizeURL(GURL(error.unreachableURL)), is_rtl, + accept_languages); + + scoped_ptr<ErrorPageParams> params(new ErrorPageParams()); + params->override_suggestions.reset(new base::ListValue()); + scoped_ptr<base::ListValue> parsed_corrections(new base::ListValue()); + for (ScopedVector<NavigationCorrection>::const_iterator it = + response.corrections.begin(); + it != response.corrections.end(); ++it) { + // Doesn't seem like a good idea to show these. + if ((*it)->is_porn || (*it)->is_soft_porn) + continue; + + int tracking_id = it - response.corrections.begin(); + + if ((*it)->correction_type == "reloadPage") { + params->suggest_reload = true; + params->reload_tracking_id = tracking_id; + continue; + } + + if ((*it)->correction_type == "webSearchQuery") { + // If there are mutliple searches suggested, use the first suggestion. + if (params->search_terms.empty()) { + params->search_url = correction_params.search_url; + params->search_terms = (*it)->url_correction; + params->search_tracking_id = tracking_id; + } + continue; + } + + // Allow reload page and web search query to be empty strings, but not + // links. + if ((*it)->url_correction.empty()) + continue; + size_t correction_index; + for (correction_index = 0; + correction_index < arraysize(kCorrectionResourceTable); + ++correction_index) { + if ((*it)->correction_type != + kCorrectionResourceTable[correction_index].correction_type) { + continue; + } + base::DictionaryValue* suggest = new base::DictionaryValue(); + suggest->SetString("header", + l10n_util::GetStringUTF16( + kCorrectionResourceTable[correction_index].resource_id)); + suggest->SetString("urlCorrection", (*it)->url_correction); + suggest->SetString( + "urlCorrectionForDisplay", + FormatURLForDisplay(GURL((*it)->url_correction), is_rtl, + accept_languages)); + suggest->SetString("originalUrlForDisplay", original_url_for_display); + suggest->SetInteger("trackingId", tracking_id); + params->override_suggestions->Append(suggest); + break; + } + } + + if (params->override_suggestions->empty() && !params->search_url.is_valid()) + params.reset(); + return params.Pass(); +} + +void ReportAutoReloadSuccess(const blink::WebURLError& error, size_t count) { + if (error.domain.utf8() != net::kErrorDomain) + return; + UMA_HISTOGRAM_CUSTOM_ENUMERATION("Net.AutoReload.ErrorAtSuccess", + -error.reason, + net::GetAllErrorCodesForUma()); + UMA_HISTOGRAM_COUNTS("Net.AutoReload.CountAtSuccess", + static_cast<base::HistogramBase::Sample>(count)); + if (count == 1) { + UMA_HISTOGRAM_CUSTOM_ENUMERATION("Net.AutoReload.ErrorAtFirstSuccess", + -error.reason, + net::GetAllErrorCodesForUma()); + } +} + +void ReportAutoReloadFailure(const blink::WebURLError& error, size_t count) { + if (error.domain.utf8() != net::kErrorDomain) + return; + UMA_HISTOGRAM_CUSTOM_ENUMERATION("Net.AutoReload.ErrorAtStop", + -error.reason, + net::GetAllErrorCodesForUma()); + UMA_HISTOGRAM_COUNTS("Net.AutoReload.CountAtStop", + static_cast<base::HistogramBase::Sample>(count)); +} + +} // namespace + +struct NetErrorHelperCore::ErrorPageInfo { + ErrorPageInfo(blink::WebURLError error, bool was_failed_post) + : error(error), + was_failed_post(was_failed_post), + needs_dns_updates(false), + needs_load_navigation_corrections(false), + reload_button_in_page(false), + load_stale_button_in_page(false), + is_finished_loading(false), + auto_reload_triggered(false) { + } + + // Information about the failed page load. + blink::WebURLError error; + bool was_failed_post; + + // Information about the status of the error page. + + // True if a page is a DNS error page and has not yet received a final DNS + // probe status. + bool needs_dns_updates; + + // True if a blank page was loaded, and navigation corrections need to be + // loaded to generate the real error page. + bool needs_load_navigation_corrections; + + // Navigation correction service paramers, which will be used in response to + // certain types of network errors. They are all stored here in case they + // change over the course of displaying the error page. + scoped_ptr<NetErrorHelperCore::NavigationCorrectionParams> + navigation_correction_params; + + scoped_ptr<NavigationCorrectionResponse> navigation_correction_response; + + // All the navigation corrections that have been clicked, for tracking + // purposes. + std::set<int> clicked_corrections; + + // Track if specific buttons are included in an error page, for statistics. + bool reload_button_in_page; + bool load_stale_button_in_page; + + // True if a page has completed loading, at which point it can receive + // updates. + bool is_finished_loading; + + // True if the auto-reload timer has fired and a reload is or has been in + // flight. + bool auto_reload_triggered; +}; + +NetErrorHelperCore::NavigationCorrectionParams::NavigationCorrectionParams() { +} + +NetErrorHelperCore::NavigationCorrectionParams::~NavigationCorrectionParams() { +} + +bool NetErrorHelperCore::IsReloadableError( + const NetErrorHelperCore::ErrorPageInfo& info) { + return info.error.domain.utf8() == net::kErrorDomain && + info.error.reason != net::ERR_ABORTED && + !info.was_failed_post; +} + +NetErrorHelperCore::NetErrorHelperCore(Delegate* delegate, + bool auto_reload_enabled, + bool auto_reload_visible_only, + bool is_visible) + : delegate_(delegate), + last_probe_status_(chrome_common_net::DNS_PROBE_POSSIBLE), + auto_reload_enabled_(auto_reload_enabled), + auto_reload_visible_only_(auto_reload_visible_only), + auto_reload_timer_(new base::Timer(false, false)), + auto_reload_paused_(false), + uncommitted_load_started_(false), + // TODO(ellyjones): Make online_ accurate at object creation. + online_(true), + visible_(is_visible), + auto_reload_count_(0), + navigation_from_button_(NO_BUTTON) { +} + +NetErrorHelperCore::~NetErrorHelperCore() { + if (committed_error_page_info_ && + committed_error_page_info_->auto_reload_triggered) { + ReportAutoReloadFailure(committed_error_page_info_->error, + auto_reload_count_); + } +} + +void NetErrorHelperCore::CancelPendingFetches() { + // Cancel loading the alternate error page, and prevent any pending error page + // load from starting a new error page load. Swapping in the error page when + // it's finished loading could abort the navigation, otherwise. + if (committed_error_page_info_) + committed_error_page_info_->needs_load_navigation_corrections = false; + if (pending_error_page_info_) + pending_error_page_info_->needs_load_navigation_corrections = false; + delegate_->CancelFetchNavigationCorrections(); + auto_reload_timer_->Stop(); + auto_reload_paused_ = false; +} + +void NetErrorHelperCore::OnStop() { + if (committed_error_page_info_ && + committed_error_page_info_->auto_reload_triggered) { + ReportAutoReloadFailure(committed_error_page_info_->error, + auto_reload_count_); + } + CancelPendingFetches(); + uncommitted_load_started_ = false; + auto_reload_count_ = 0; +} + +void NetErrorHelperCore::OnWasShown() { + visible_ = true; + if (!auto_reload_visible_only_) + return; + if (auto_reload_paused_) + MaybeStartAutoReloadTimer(); +} + +void NetErrorHelperCore::OnWasHidden() { + visible_ = false; + if (!auto_reload_visible_only_) + return; + PauseAutoReloadTimer(); +} + +void NetErrorHelperCore::OnStartLoad(FrameType frame_type, PageType page_type) { + if (frame_type != MAIN_FRAME) + return; + + uncommitted_load_started_ = true; + + // If there's no pending error page information associated with the page load, + // or the new page is not an error page, then reset pending error page state. + if (!pending_error_page_info_ || page_type != ERROR_PAGE) + CancelPendingFetches(); +} + +void NetErrorHelperCore::OnCommitLoad(FrameType frame_type, const GURL& url) { + if (frame_type != MAIN_FRAME) + return; + + // uncommitted_load_started_ could already be false, since RenderFrameImpl + // calls OnCommitLoad once for each in-page navigation (like a fragment + // change) with no corresponding OnStartLoad. + uncommitted_load_started_ = false; + + // Track if an error occurred due to a page button press. + // This isn't perfect; if (for instance), the server is slow responding + // to a request generated from the page reload button, and the user hits + // the browser reload button, this code will still believe the + // result is from the page reload button. + if (committed_error_page_info_ && pending_error_page_info_ && + navigation_from_button_ != NO_BUTTON && + committed_error_page_info_->error.unreachableURL == + pending_error_page_info_->error.unreachableURL) { + DCHECK(navigation_from_button_ == RELOAD_BUTTON || + navigation_from_button_ == LOAD_STALE_BUTTON); + chrome_common_net::RecordEvent( + navigation_from_button_ == RELOAD_BUTTON ? + chrome_common_net::NETWORK_ERROR_PAGE_RELOAD_BUTTON_ERROR : + chrome_common_net::NETWORK_ERROR_PAGE_LOAD_STALE_BUTTON_ERROR); + } + navigation_from_button_ = NO_BUTTON; + + if (committed_error_page_info_ && !pending_error_page_info_ && + committed_error_page_info_->auto_reload_triggered) { + const blink::WebURLError& error = committed_error_page_info_->error; + const GURL& error_url = error.unreachableURL; + if (url == error_url) + ReportAutoReloadSuccess(error, auto_reload_count_); + else if (url != GURL(content::kUnreachableWebDataURL)) + ReportAutoReloadFailure(error, auto_reload_count_); + } + + committed_error_page_info_.reset(pending_error_page_info_.release()); +} + +void NetErrorHelperCore::OnFinishLoad(FrameType frame_type) { + if (frame_type != MAIN_FRAME) + return; + + if (!committed_error_page_info_) { + auto_reload_count_ = 0; + return; + } + + committed_error_page_info_->is_finished_loading = true; + + chrome_common_net::RecordEvent(chrome_common_net::NETWORK_ERROR_PAGE_SHOWN); + if (committed_error_page_info_->reload_button_in_page) { + chrome_common_net::RecordEvent( + chrome_common_net::NETWORK_ERROR_PAGE_RELOAD_BUTTON_SHOWN); + } + if (committed_error_page_info_->load_stale_button_in_page) { + chrome_common_net::RecordEvent( + chrome_common_net::NETWORK_ERROR_PAGE_LOAD_STALE_BUTTON_SHOWN); + } + + delegate_->EnablePageHelperFunctions(); + + if (committed_error_page_info_->needs_load_navigation_corrections) { + // If there is another pending error page load, |fix_url| should have been + // cleared. + DCHECK(!pending_error_page_info_); + DCHECK(!committed_error_page_info_->needs_dns_updates); + delegate_->FetchNavigationCorrections( + committed_error_page_info_->navigation_correction_params->url, + CreateFixUrlRequestBody( + committed_error_page_info_->error, + *committed_error_page_info_->navigation_correction_params)); + } else if (auto_reload_enabled_ && + IsReloadableError(*committed_error_page_info_)) { + MaybeStartAutoReloadTimer(); + } + + if (!committed_error_page_info_->needs_dns_updates || + last_probe_status_ == chrome_common_net::DNS_PROBE_POSSIBLE) { + return; + } + DVLOG(1) << "Error page finished loading; sending saved status."; + UpdateErrorPage(); +} + +void NetErrorHelperCore::GetErrorHTML( + FrameType frame_type, + const blink::WebURLError& error, + bool is_failed_post, + std::string* error_html) { + if (frame_type == MAIN_FRAME) { + // If navigation corrections were needed before, that should have been + // cancelled earlier by starting a new page load (Which has now failed). + DCHECK(!committed_error_page_info_ || + !committed_error_page_info_->needs_load_navigation_corrections); + + pending_error_page_info_.reset(new ErrorPageInfo(error, is_failed_post)); + pending_error_page_info_->navigation_correction_params.reset( + new NavigationCorrectionParams(navigation_correction_params_)); + GetErrorHtmlForMainFrame(pending_error_page_info_.get(), error_html); + } else { + // These values do not matter, as error pages in iframes hide the buttons. + bool reload_button_in_page; + bool load_stale_button_in_page; + + delegate_->GenerateLocalizedErrorPage( + error, is_failed_post, scoped_ptr<ErrorPageParams>(), + &reload_button_in_page, &load_stale_button_in_page, + error_html); + } +} + +void NetErrorHelperCore::OnNetErrorInfo( + chrome_common_net::DnsProbeStatus status) { + DCHECK_NE(chrome_common_net::DNS_PROBE_POSSIBLE, status); + + last_probe_status_ = status; + + if (!committed_error_page_info_ || + !committed_error_page_info_->needs_dns_updates || + !committed_error_page_info_->is_finished_loading) { + return; + } + + UpdateErrorPage(); +} + +void NetErrorHelperCore::OnSetNavigationCorrectionInfo( + const GURL& navigation_correction_url, + const std::string& language, + const std::string& country_code, + const std::string& api_key, + const GURL& search_url) { + navigation_correction_params_.url = navigation_correction_url; + navigation_correction_params_.language = language; + navigation_correction_params_.country_code = country_code; + navigation_correction_params_.api_key = api_key; + navigation_correction_params_.search_url = search_url; +} + +void NetErrorHelperCore::GetErrorHtmlForMainFrame( + ErrorPageInfo* pending_error_page_info, + std::string* error_html) { + std::string error_param; + blink::WebURLError error = pending_error_page_info->error; + + if (pending_error_page_info->navigation_correction_params && + pending_error_page_info->navigation_correction_params->url.is_valid() && + ShouldUseFixUrlServiceForError(error, &error_param)) { + pending_error_page_info->needs_load_navigation_corrections = true; + return; + } + + if (IsDnsError(pending_error_page_info->error)) { + // The last probe status needs to be reset if this is a DNS error. This + // means that if a DNS error page is committed but has not yet finished + // loading, a DNS probe status scheduled to be sent to it may be thrown + // out, but since the new error page should trigger a new DNS probe, it + // will just get the results for the next page load. + last_probe_status_ = chrome_common_net::DNS_PROBE_POSSIBLE; + pending_error_page_info->needs_dns_updates = true; + error = GetUpdatedError(error); + } + + delegate_->GenerateLocalizedErrorPage( + error, pending_error_page_info->was_failed_post, + scoped_ptr<ErrorPageParams>(), + &pending_error_page_info->reload_button_in_page, + &pending_error_page_info->load_stale_button_in_page, + error_html); +} + +void NetErrorHelperCore::UpdateErrorPage() { + DCHECK(committed_error_page_info_->needs_dns_updates); + DCHECK(committed_error_page_info_->is_finished_loading); + DCHECK_NE(chrome_common_net::DNS_PROBE_POSSIBLE, last_probe_status_); + + UMA_HISTOGRAM_ENUMERATION("DnsProbe.ErrorPageUpdateStatus", + last_probe_status_, + chrome_common_net::DNS_PROBE_MAX); + // Every status other than DNS_PROBE_POSSIBLE and DNS_PROBE_STARTED is a + // final status code. Once one is reached, the page does not need further + // updates. + if (last_probe_status_ != chrome_common_net::DNS_PROBE_STARTED) + committed_error_page_info_->needs_dns_updates = false; + + // There is no need to worry about the button display statistics here because + // the presentation of the reload and load stale buttons can't be changed + // by a DNS error update. + delegate_->UpdateErrorPage( + GetUpdatedError(committed_error_page_info_->error), + committed_error_page_info_->was_failed_post); +} + +void NetErrorHelperCore::OnNavigationCorrectionsFetched( + const std::string& corrections, + const std::string& accept_languages, + bool is_rtl) { + // Loading suggestions only starts when a blank error page finishes loading, + // and is cancelled with a new load. + DCHECK(!pending_error_page_info_); + DCHECK(committed_error_page_info_->is_finished_loading); + DCHECK(committed_error_page_info_->needs_load_navigation_corrections); + DCHECK(committed_error_page_info_->navigation_correction_params); + + pending_error_page_info_.reset( + new ErrorPageInfo(committed_error_page_info_->error, + committed_error_page_info_->was_failed_post)); + pending_error_page_info_->navigation_correction_response = + ParseNavigationCorrectionResponse(corrections); + + std::string error_html; + scoped_ptr<ErrorPageParams> params; + if (pending_error_page_info_->navigation_correction_response) { + // Copy navigation correction parameters used for the request, so tracking + // requests can still be sent if the configuration changes. + pending_error_page_info_->navigation_correction_params.reset( + new NavigationCorrectionParams( + *committed_error_page_info_->navigation_correction_params)); + params = CreateErrorPageParams( + *pending_error_page_info_->navigation_correction_response, + pending_error_page_info_->error, + *pending_error_page_info_->navigation_correction_params, + accept_languages, is_rtl); + delegate_->GenerateLocalizedErrorPage( + pending_error_page_info_->error, + pending_error_page_info_->was_failed_post, + params.Pass(), + &pending_error_page_info_->reload_button_in_page, + &pending_error_page_info_->load_stale_button_in_page, + &error_html); + } else { + // Since |navigation_correction_params| in |pending_error_page_info_| is + // NULL, this won't trigger another attempt to load corrections. + GetErrorHtmlForMainFrame(pending_error_page_info_.get(), &error_html); + } + + // TODO(mmenke): Once the new API is in place, look into replacing this + // double page load by just updating the error page, like DNS + // probes do. + delegate_->LoadErrorPageInMainFrame( + error_html, + pending_error_page_info_->error.unreachableURL); +} + +blink::WebURLError NetErrorHelperCore::GetUpdatedError( + const blink::WebURLError& error) const { + // If a probe didn't run or wasn't conclusive, restore the original error. + if (last_probe_status_ == chrome_common_net::DNS_PROBE_NOT_RUN || + last_probe_status_ == + chrome_common_net::DNS_PROBE_FINISHED_INCONCLUSIVE) { + return error; + } + + blink::WebURLError updated_error; + updated_error.domain = blink::WebString::fromUTF8( + chrome_common_net::kDnsProbeErrorDomain); + updated_error.reason = last_probe_status_; + updated_error.unreachableURL = error.unreachableURL; + updated_error.staleCopyInCache = error.staleCopyInCache; + + return updated_error; +} + +void NetErrorHelperCore::Reload() { + if (!committed_error_page_info_) { + return; + } + delegate_->ReloadPage(); +} + +bool NetErrorHelperCore::MaybeStartAutoReloadTimer() { + if (!committed_error_page_info_ || + !committed_error_page_info_->is_finished_loading || + pending_error_page_info_ || + uncommitted_load_started_) { + return false; + } + + StartAutoReloadTimer(); + return true; +} + +void NetErrorHelperCore::StartAutoReloadTimer() { + DCHECK(committed_error_page_info_); + DCHECK(IsReloadableError(*committed_error_page_info_)); + + committed_error_page_info_->auto_reload_triggered = true; + + if (!online_ || (!visible_ && auto_reload_visible_only_)) { + auto_reload_paused_ = true; + return; + } + + auto_reload_paused_ = false; + base::TimeDelta delay = GetAutoReloadTime(auto_reload_count_); + auto_reload_timer_->Stop(); + auto_reload_timer_->Start(FROM_HERE, delay, + base::Bind(&NetErrorHelperCore::AutoReloadTimerFired, + base::Unretained(this))); +} + +void NetErrorHelperCore::AutoReloadTimerFired() { + auto_reload_count_++; + Reload(); +} + +void NetErrorHelperCore::PauseAutoReloadTimer() { + if (!auto_reload_timer_->IsRunning()) + return; + DCHECK(committed_error_page_info_); + DCHECK(!auto_reload_paused_); + DCHECK(committed_error_page_info_->auto_reload_triggered); + auto_reload_timer_->Stop(); + auto_reload_paused_ = true; +} + +void NetErrorHelperCore::NetworkStateChanged(bool online) { + bool was_online = online_; + online_ = online; + if (!was_online && online) { + // Transitioning offline -> online + if (auto_reload_paused_) + MaybeStartAutoReloadTimer(); + } else if (was_online && !online) { + // Transitioning online -> offline + if (auto_reload_timer_->IsRunning()) + auto_reload_count_ = 0; + PauseAutoReloadTimer(); + } +} + +bool NetErrorHelperCore::ShouldSuppressErrorPage(FrameType frame_type, + const GURL& url) { + // Don't suppress child frame errors. + if (frame_type != MAIN_FRAME) + return false; + + if (!auto_reload_enabled_) + return false; + + // If there's no committed error page, this error page wasn't from an auto + // reload. + if (!committed_error_page_info_) + return false; + + // If the error page wasn't reloadable, display it. + if (!IsReloadableError(*committed_error_page_info_)) + return false; + + // If |auto_reload_timer_| is still running or is paused, this error page + // isn't from an auto reload. + if (auto_reload_timer_->IsRunning() || auto_reload_paused_) + return false; + + // If the error page was reloadable, and the timer isn't running or paused, an + // auto-reload has already been triggered. + DCHECK(committed_error_page_info_->auto_reload_triggered); + + GURL error_url = committed_error_page_info_->error.unreachableURL; + // TODO(ellyjones): also plumb the error code down to CCRC and check that + if (error_url != url) + return false; + + // Suppressed an error-page load; the previous uncommitted load was the error + // page load starting, so forget about it. + uncommitted_load_started_ = false; + + // The first iteration of the timer is started by OnFinishLoad calling + // MaybeStartAutoReloadTimer, but since error pages for subsequent loads are + // suppressed in this function, subsequent iterations of the timer have to be + // started here. + MaybeStartAutoReloadTimer(); + return true; +} + +void NetErrorHelperCore::ExecuteButtonPress(Button button) { + switch (button) { + case RELOAD_BUTTON: + chrome_common_net::RecordEvent( + chrome_common_net::NETWORK_ERROR_PAGE_RELOAD_BUTTON_CLICKED); + navigation_from_button_ = RELOAD_BUTTON; + Reload(); + return; + case LOAD_STALE_BUTTON: + chrome_common_net::RecordEvent( + chrome_common_net::NETWORK_ERROR_PAGE_LOAD_STALE_BUTTON_CLICKED); + navigation_from_button_ = LOAD_STALE_BUTTON; + delegate_->LoadPageFromCache( + committed_error_page_info_->error.unreachableURL); + return; + case MORE_BUTTON: + // Visual effects on page are handled in Javascript code. + chrome_common_net::RecordEvent( + chrome_common_net::NETWORK_ERROR_PAGE_MORE_BUTTON_CLICKED); + return; + case NO_BUTTON: + NOTREACHED(); + return; + } +} + +void NetErrorHelperCore::TrackClick(int tracking_id) { + // It's technically possible for |navigation_correction_params| to be NULL but + // for |navigation_correction_response| not to be NULL, if the paramters + // changed between loading the original error page and loading the error page + if (!committed_error_page_info_ || + !committed_error_page_info_->navigation_correction_response) { + return; + } + + NavigationCorrectionResponse* response = + committed_error_page_info_->navigation_correction_response.get(); + + // |tracking_id| is less than 0 when the error page was not generated by the + // navigation correction service. |tracking_id| should never be greater than + // the array size, but best to be safe, since it contains data from a remote + // site, though none of that data should make it into Javascript callbacks. + if (tracking_id < 0 || + static_cast<size_t>(tracking_id) >= response->corrections.size()) { + return; + } + + // Only report a clicked link once. + if (committed_error_page_info_->clicked_corrections.count(tracking_id)) + return; + + committed_error_page_info_->clicked_corrections.insert(tracking_id); + std::string request_body = CreateClickTrackingUrlRequestBody( + committed_error_page_info_->error, + *committed_error_page_info_->navigation_correction_params, + *response, + *response->corrections[tracking_id]); + delegate_->SendTrackingRequest( + committed_error_page_info_->navigation_correction_params->url, + request_body); +} + +} // namespace error_page diff --git a/components/error_page/renderer/net_error_helper_core.h b/components/error_page/renderer/net_error_helper_core.h new file mode 100644 index 0000000..ec00d33 --- /dev/null +++ b/components/error_page/renderer/net_error_helper_core.h @@ -0,0 +1,271 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_ERROR_PAGE_RENDERER_NET_ERROR_HELPER_CORE_H_ +#define COMPONENTS_ERROR_PAGE_RENDERER_NET_ERROR_HELPER_CORE_H_ + +#include <string> + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/timer/timer.h" +#include "components/error_page/common/net_error_info.h" +#include "url/gurl.h" + +namespace base { +class ListValue; +} + +namespace blink { +struct WebURLError; +} + +namespace error_page { + +struct ErrorPageParams; + +// Class that contains the logic for how the NetErrorHelper. This allows for +// testing the logic without a RenderView or WebFrame, which are difficult to +// mock, and for testing races which are impossible to reliably reproduce +// with real RenderViews or WebFrames. +class NetErrorHelperCore { + public: + enum FrameType { + MAIN_FRAME, + SUB_FRAME, + }; + + enum PageType { + NON_ERROR_PAGE, + ERROR_PAGE, + }; + + enum Button { + NO_BUTTON, + RELOAD_BUTTON, + LOAD_STALE_BUTTON, + MORE_BUTTON, + }; + + // The Delegate handles all interaction with the RenderView, WebFrame, and + // the network, as well as the generation of error pages. + class Delegate { + public: + // Generates an error page's HTML for the given error. + virtual void GenerateLocalizedErrorPage( + const blink::WebURLError& error, + bool is_failed_post, + scoped_ptr<error_page::ErrorPageParams> params, + bool* reload_button_shown, + bool* load_stale_button_shown, + std::string* html) const = 0; + + // Loads the given HTML in the main frame for use as an error page. + virtual void LoadErrorPageInMainFrame(const std::string& html, + const GURL& failed_url) = 0; + + // Create extra Javascript bindings in the error page. + virtual void EnablePageHelperFunctions() = 0; + + // Updates the currently displayed error page with a new error code. The + // currently displayed error page must have finished loading, and must have + // been generated by a call to GenerateLocalizedErrorPage. + virtual void UpdateErrorPage(const blink::WebURLError& error, + bool is_failed_post) = 0; + + // Fetches an error page and calls into OnErrorPageFetched when done. Any + // previous fetch must either be canceled or finished before calling. Can't + // be called synchronously after a previous fetch completes. + virtual void FetchNavigationCorrections( + const GURL& navigation_correction_url, + const std::string& navigation_correction_request_body) = 0; + + // Cancels fetching navigation corrections. Does nothing if no fetch is + // ongoing. + virtual void CancelFetchNavigationCorrections() = 0; + + // Sends an HTTP request used to track which link on the page was clicked to + // the navigation correction service. + virtual void SendTrackingRequest( + const GURL& tracking_url, + const std::string& tracking_request_body) = 0; + + // Starts a reload of the page in the observed frame. + virtual void ReloadPage() = 0; + + // Load the original page from cache. + virtual void LoadPageFromCache(const GURL& page_url) = 0; + + protected: + virtual ~Delegate() {} + }; + + struct NavigationCorrectionParams { + NavigationCorrectionParams(); + ~NavigationCorrectionParams(); + + // URL used both for getting the suggestions and tracking clicks. + GURL url; + + std::string language; + std::string country_code; + std::string api_key; + GURL search_url; + }; + + NetErrorHelperCore(Delegate* delegate, + bool auto_reload_enabled, + bool auto_reload_visible_only, + bool is_visible); + ~NetErrorHelperCore(); + + // Examines |frame| and |error| to see if this is an error worthy of a DNS + // probe. If it is, initializes |error_strings| based on |error|, + // |is_failed_post|, and |locale| with suitable strings and returns true. + // If not, returns false, in which case the caller should look up error + // strings directly using LocalizedError::GetNavigationErrorStrings. + // + // Updates the NetErrorHelper with the assumption the page will be loaded + // immediately. + void GetErrorHTML(FrameType frame_type, + const blink::WebURLError& error, + bool is_failed_post, + std::string* error_html); + + // These methods handle tracking the actual state of the page. + void OnStartLoad(FrameType frame_type, PageType page_type); + void OnCommitLoad(FrameType frame_type, const GURL& url); + void OnFinishLoad(FrameType frame_type); + void OnStop(); + void OnWasShown(); + void OnWasHidden(); + + void CancelPendingFetches(); + + // Called when an error page have has been retrieved over the network. |html| + // must be an empty string on error. + void OnNavigationCorrectionsFetched(const std::string& corrections, + const std::string& accept_languages, + bool is_rtl); + + // Notifies |this| that network error information from the browser process + // has been received. + void OnNetErrorInfo(chrome_common_net::DnsProbeStatus status); + + void OnSetNavigationCorrectionInfo(const GURL& navigation_correction_url, + const std::string& language, + const std::string& country_code, + const std::string& api_key, + const GURL& search_url); + // Notifies |this| that the network's online status changed. + // Handler for NetworkStateChanged notification from the browser process. If + // the network state changes to online, this method is responsible for + // starting the auto-reload process. + // + // Warning: if there are many tabs sitting at an error page, this handler will + // be run at the same time for each of their top-level renderframes, which can + // cause many requests to be started at the same time. There's no current + // protection against this kind of "reload storm". + // + // TODO(rdsmith): prevent the reload storm. + void NetworkStateChanged(bool online); + + int auto_reload_count() const { return auto_reload_count_; } + + bool ShouldSuppressErrorPage(FrameType frame_type, const GURL& url); + + void set_timer_for_testing(scoped_ptr<base::Timer> timer) { + auto_reload_timer_.reset(timer.release()); + } + + // Execute the effect of pressing the specified button. + // Note that the visual effects of the 'MORE' button are taken + // care of in JavaScript. + void ExecuteButtonPress(Button button); + + // Reports to the correction service that the link with the given tracking + // ID was clicked. Only pages generated with information from the service + // have links with tracking IDs. Duplicate requests from the same page with + // the same tracking ID are ignored. + void TrackClick(int tracking_id); + + private: + struct ErrorPageInfo; + + // Gets HTML for a main frame error page. Depending on + // |pending_error_page_info|, may use the navigation correction service, or + // show a DNS probe error page. May modify |pending_error_page_info|. + void GetErrorHtmlForMainFrame(ErrorPageInfo* pending_error_page_info, + std::string* error_html); + + // Updates the currently displayed error page with a new error based on the + // most recently received DNS probe result. The page must have finished + // loading before this is called. + void UpdateErrorPage(); + + blink::WebURLError GetUpdatedError(const blink::WebURLError& error) const; + + void Reload(); + bool MaybeStartAutoReloadTimer(); + void StartAutoReloadTimer(); + void AutoReloadTimerFired(); + void PauseAutoReloadTimer(); + + static bool IsReloadableError(const ErrorPageInfo& info); + + Delegate* delegate_; + + // The last DnsProbeStatus received from the browser. + chrome_common_net::DnsProbeStatus last_probe_status_; + + // Information for the provisional / "pre-provisional" error page. NULL when + // there's no page pending, or the pending page is not an error page. + scoped_ptr<ErrorPageInfo> pending_error_page_info_; + + // Information for the committed error page. NULL when the committed page is + // not an error page. + scoped_ptr<ErrorPageInfo> committed_error_page_info_; + + NavigationCorrectionParams navigation_correction_params_; + + // True if auto-reload is enabled at all. + const bool auto_reload_enabled_; + + // True if auto-reload should only run when the observed frame is visible. + const bool auto_reload_visible_only_; + + // Timer used to wait for auto-reload attempts. + scoped_ptr<base::Timer> auto_reload_timer_; + + // True if the auto-reload timer would be running but is waiting for an + // offline->online network transition. + bool auto_reload_paused_; + + // True if there is an uncommitted-but-started load, error page or not. This + // is used to inhibit starting auto-reload when an error page finishes, in + // case this happens: + // Error page starts + // Error page commits + // Non-error page starts + // Error page finishes + bool uncommitted_load_started_; + + // Is the browser online? + bool online_; + + // Is the RenderFrame this object is observing visible? + bool visible_; + + int auto_reload_count_; + + // This value is set only when a navigation has been initiated from + // the error page. It is used to detect when such navigations result + // in errors. + Button navigation_from_button_; +}; + +} // namespace error_page + +#endif // COMPONENTS_ERROR_PAGE_RENDERER_NET_ERROR_HELPER_CORE_H_ diff --git a/components/error_page/renderer/net_error_helper_core_unittest.cc b/components/error_page/renderer/net_error_helper_core_unittest.cc new file mode 100644 index 0000000..88cdcc3 --- /dev/null +++ b/components/error_page/renderer/net_error_helper_core_unittest.cc @@ -0,0 +1,2436 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/error_page/renderer/net_error_helper_core.h" + +#include <map> +#include <string> +#include <vector> + +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/logging.h" +#include "base/metrics/statistics_recorder.h" +#include "base/strings/stringprintf.h" +#include "base/test/histogram_tester.h" +#include "base/timer/mock_timer.h" +#include "base/timer/timer.h" +#include "base/values.h" +#include "components/error_page/common/error_page_params.h" +#include "components/error_page/common/net_error_info.h" +#include "content/public/common/url_constants.h" +#include "net/base/net_errors.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/public/platform/WebURLError.h" +#include "url/gurl.h" + +namespace error_page { +namespace { + +using blink::WebURLError; +using chrome_common_net::DnsProbeStatus; +using chrome_common_net::DnsProbeStatusToString; +using error_page::ErrorPageParams; + +const char kFailedUrl[] = "http://failed/"; +const char kFailedHttpsUrl[] = "https://failed/"; + +const char kNavigationCorrectionUrl[] = "http://navigation.corrections/"; +const char kLanguage[] = "en"; +const char kCountry[] = "us"; +const char kApiKey[] = "api_key"; +const char kSearchUrl[] = "http://www.google.com/search"; + +const char kSuggestedSearchTerms[] = "Happy Goats"; +const char kNavigationCorrectionEventId[] = "#007"; +const char kNavigationCorrectionFingerprint[] = "RandumStuff"; + +struct NavigationCorrection { + const char* correction_type; + const char* url_correction; + const char* click_type; + const char* click_data; + bool is_porn; + bool is_soft_porn; + + base::Value* ToValue() const { + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetString("correctionType", correction_type); + dict->SetString("urlCorrection", url_correction); + dict->SetString("clickType", click_type); + dict->SetString("clickData", click_data); + dict->SetBoolean("isPorn", is_porn); + dict->SetBoolean("isSoftPorn", is_soft_porn); + return dict; + } +}; + +const NavigationCorrection kDefaultCorrections[] = { + {"reloadPage", kFailedUrl, "rld", "data1", false, false}, + {"urlCorrection", "http://somewhere_else/", "btn", "data2", false, false}, + {"contentOverlap", "http://pony_island/", "btn", "data3", false, false}, + + // Porn should be ignored. + {"emphasizedUrlCorrection", "http://porn/", "btn", "data4", true, false}, + {"sitemap", "http://more_porn/", "btn", "data5", false, true}, + + {"webSearchQuery", kSuggestedSearchTerms, "frm", "data6", false, false}, +}; + +std::string SuggestionsToResponse(const NavigationCorrection* corrections, + int num_corrections) { + base::ListValue* url_corrections = new base::ListValue(); + for (int i = 0; i < num_corrections; ++i) + url_corrections->Append(corrections[i].ToValue()); + + scoped_ptr<base::DictionaryValue> response(new base::DictionaryValue()); + response->Set("result.UrlCorrections", url_corrections); + response->SetString("result.eventId", kNavigationCorrectionEventId); + response->SetString("result.fingerprint", kNavigationCorrectionFingerprint); + + std::string json; + base::JSONWriter::Write(response.get(), &json); + return json; +} + +testing::AssertionResult StringValueEquals( + const base::DictionaryValue& dict, + const std::string& key, + const std::string& expected_value) { + std::string actual_value; + if (!dict.GetString(key, &actual_value)) + return testing::AssertionFailure() << key << " not found."; + if (actual_value != expected_value) { + return testing::AssertionFailure() + << "actual: " << actual_value << "\n expected: " << expected_value; + } + return testing::AssertionSuccess(); +} + +// Creates a string from an error that is used as a mock locally generated +// error page for that error. +std::string ErrorToString(const WebURLError& error, bool is_failed_post) { + return base::StringPrintf("(%s, %s, %i, %s)", + error.unreachableURL.string().utf8().c_str(), + error.domain.utf8().c_str(), error.reason, + is_failed_post ? "POST" : "NOT POST"); +} + +WebURLError ProbeError(DnsProbeStatus status) { + WebURLError error; + error.unreachableURL = GURL(kFailedUrl); + error.domain = blink::WebString::fromUTF8( + chrome_common_net::kDnsProbeErrorDomain); + error.reason = status; + return error; +} + +WebURLError NetError(net::Error net_error) { + WebURLError error; + error.unreachableURL = GURL(kFailedUrl); + error.domain = blink::WebString::fromUTF8(net::kErrorDomain); + error.reason = net_error; + return error; +} + +// Convenience functions that create an error string for a non-POST request. + +std::string ProbeErrorString(DnsProbeStatus status) { + return ErrorToString(ProbeError(status), false); +} + +std::string NetErrorString(net::Error net_error) { + return ErrorToString(NetError(net_error), false); +} + +class NetErrorHelperCoreTest : public testing::Test, + public NetErrorHelperCore::Delegate { + public: + NetErrorHelperCoreTest() : timer_(NULL), + update_count_(0), + error_html_update_count_(0), + reload_count_(0), + load_stale_count_(0), + enable_page_helper_functions_count_(0), + default_url_(GURL(kFailedUrl)), + error_url_(GURL(content::kUnreachableWebDataURL)), + tracking_request_count_(0) { + SetUpCore(false, false, true); + } + + virtual ~NetErrorHelperCoreTest() { + // No test finishes while an error page is being fetched. + EXPECT_FALSE(is_url_being_fetched()); + } + + virtual void SetUp() override { + base::StatisticsRecorder::Initialize(); + } + + void SetUpCore(bool auto_reload_enabled, + bool auto_reload_visible_only, + bool visible) { + // The old value of timer_, if any, will be freed by the old core_ being + // destructed, since core_ takes ownership of the timer. + timer_ = new base::MockTimer(false, false); + core_.reset(new NetErrorHelperCore(this, + auto_reload_enabled, + auto_reload_visible_only, + visible)); + core_->set_timer_for_testing(scoped_ptr<base::Timer>(timer_)); + } + + NetErrorHelperCore* core() { return core_.get(); } + + const GURL& url_being_fetched() const { return url_being_fetched_; } + bool is_url_being_fetched() const { return !url_being_fetched_.is_empty(); } + + int reload_count() const { + return reload_count_; + } + + int load_stale_count() const { + return load_stale_count_; + } + + const GURL& load_stale_url() const { + return load_stale_url_; + } + + const GURL& default_url() const { + return default_url_; + } + + const GURL& error_url() const { + return error_url_; + } + + int enable_page_helper_functions_count() const { + return enable_page_helper_functions_count_; + } + + const std::string& last_update_string() const { return last_update_string_; } + int update_count() const { return update_count_; } + + const std::string& last_error_html() const { return last_error_html_; } + int error_html_update_count() const { return error_html_update_count_; } + + const ErrorPageParams* last_error_page_params() const { + return last_error_page_params_.get(); + } + + const GURL& last_tracking_url() const { return last_tracking_url_; } + const std::string& last_tracking_request_body() const { + return last_tracking_request_body_; + } + int tracking_request_count() const { return tracking_request_count_; } + + base::MockTimer* timer() { return timer_; } + + void NavigationCorrectionsLoadSuccess( + const NavigationCorrection* corrections, int num_corrections) { + NavigationCorrectionsLoadFinished( + SuggestionsToResponse(corrections, num_corrections)); + } + + void NavigationCorrectionsLoadFailure() { + NavigationCorrectionsLoadFinished(""); + } + + void NavigationCorrectionsLoadFinished(const std::string& result) { + url_being_fetched_ = GURL(); + core()->OnNavigationCorrectionsFetched(result, "en", false); + } + + void DoErrorLoad(net::Error error) { + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(error), false, &html); + EXPECT_FALSE(html.empty()); + EXPECT_EQ(NetErrorString(error), html); + + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, + error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + } + + void DoSuccessLoad() { + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, default_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + } + + void DoDnsProbe(chrome_common_net::DnsProbeStatus final_status) { + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + core()->OnNetErrorInfo(final_status); + } + + void EnableNavigationCorrections() { + SetNavigationCorrectionURL(GURL(kNavigationCorrectionUrl)); + } + + void DisableNavigationCorrections() { + SetNavigationCorrectionURL(GURL()); + } + + void ExpectDefaultNavigationCorrections() const { + // Checks that the last error page params correspond to kDefaultSuggestions. + ASSERT_TRUE(last_error_page_params()); + EXPECT_TRUE(last_error_page_params()->suggest_reload); + EXPECT_EQ(2u, last_error_page_params()->override_suggestions->GetSize()); + EXPECT_EQ(GURL(kSearchUrl), last_error_page_params()->search_url); + EXPECT_EQ(kSuggestedSearchTerms, last_error_page_params()->search_terms); + } + + private: + void SetNavigationCorrectionURL(const GURL& navigation_correction_url) { + core()->OnSetNavigationCorrectionInfo(navigation_correction_url, + kLanguage, kCountry, kApiKey, + GURL(kSearchUrl)); + } + + // NetErrorHelperCore::Delegate implementation: + virtual void GenerateLocalizedErrorPage(const WebURLError& error, + bool is_failed_post, + scoped_ptr<ErrorPageParams> params, + bool* reload_button_shown, + bool* load_stale_button_shown, + std::string* html) const override { + last_error_page_params_.reset(params.release()); + *reload_button_shown = false; + *load_stale_button_shown = false; + *html = ErrorToString(error, is_failed_post); + } + + virtual void LoadErrorPageInMainFrame(const std::string& html, + const GURL& failed_url) override { + error_html_update_count_++; + last_error_html_ = html; + } + + virtual void EnablePageHelperFunctions() override { + enable_page_helper_functions_count_++; + } + + virtual void UpdateErrorPage(const WebURLError& error, + bool is_failed_post) override { + update_count_++; + last_error_page_params_.reset(NULL); + last_error_html_ = ErrorToString(error, is_failed_post); + } + + virtual void FetchNavigationCorrections( + const GURL& navigation_correction_url, + const std::string& navigation_correction_request_body) override { + EXPECT_TRUE(url_being_fetched_.is_empty()); + EXPECT_TRUE(request_body_.empty()); + EXPECT_EQ(GURL(kNavigationCorrectionUrl), navigation_correction_url); + + url_being_fetched_ = navigation_correction_url; + request_body_ = navigation_correction_request_body; + + // Check the body of the request. + + base::JSONReader reader; + scoped_ptr<base::Value> parsed_body(reader.Read( + navigation_correction_request_body)); + ASSERT_TRUE(parsed_body); + base::DictionaryValue* dict = NULL; + ASSERT_TRUE(parsed_body->GetAsDictionary(&dict)); + + EXPECT_TRUE(StringValueEquals(*dict, "params.urlQuery", kFailedUrl)); + EXPECT_TRUE(StringValueEquals(*dict, "params.language", kLanguage)); + EXPECT_TRUE(StringValueEquals(*dict, "params.originCountry", kCountry)); + EXPECT_TRUE(StringValueEquals(*dict, "params.key", kApiKey)); + } + + virtual void CancelFetchNavigationCorrections() override { + url_being_fetched_ = GURL(); + request_body_.clear(); + } + + virtual void ReloadPage() override { + reload_count_++; + } + + virtual void LoadPageFromCache(const GURL& error_url) override { + load_stale_count_++; + load_stale_url_ = error_url; + } + + virtual void SendTrackingRequest( + const GURL& tracking_url, + const std::string& tracking_request_body) override { + last_tracking_url_ = tracking_url; + last_tracking_request_body_ = tracking_request_body; + tracking_request_count_++; + + // Check the body of the request. + + base::JSONReader reader; + scoped_ptr<base::Value> parsed_body(reader.Read(tracking_request_body)); + ASSERT_TRUE(parsed_body); + base::DictionaryValue* dict = NULL; + ASSERT_TRUE(parsed_body->GetAsDictionary(&dict)); + + EXPECT_TRUE(StringValueEquals(*dict, "params.originalUrlQuery", + kFailedUrl)); + EXPECT_TRUE(StringValueEquals(*dict, "params.language", kLanguage)); + EXPECT_TRUE(StringValueEquals(*dict, "params.originCountry", kCountry)); + EXPECT_TRUE(StringValueEquals(*dict, "params.key", kApiKey)); + } + + base::MockTimer* timer_; + + scoped_ptr<NetErrorHelperCore> core_; + + GURL url_being_fetched_; + std::string request_body_; + + // Contains the information passed to the last call to UpdateErrorPage, as a + // string. + std::string last_update_string_; + // Number of times |last_update_string_| has been changed. + int update_count_; + + // Contains the HTML set by the last call to LoadErrorPageInMainFrame. + std::string last_error_html_; + // Number of times |last_error_html_| has been changed. + int error_html_update_count_; + + // Mutable because GenerateLocalizedErrorPage is const. + mutable scoped_ptr<ErrorPageParams> last_error_page_params_; + + int reload_count_; + int load_stale_count_; + GURL load_stale_url_; + + int enable_page_helper_functions_count_; + + const GURL default_url_; + const GURL error_url_; + + GURL last_tracking_url_; + std::string last_tracking_request_body_; + int tracking_request_count_; +}; + +//------------------------------------------------------------------------------ +// Basic tests that don't update the error page for probes or load navigation +// corrections. +//------------------------------------------------------------------------------ + +TEST_F(NetErrorHelperCoreTest, Null) { +} + +TEST_F(NetErrorHelperCoreTest, SuccessfulPageLoad) { + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, default_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_EQ(0, update_count()); + EXPECT_EQ(0, error_html_update_count()); +} + +TEST_F(NetErrorHelperCoreTest, SuccessfulPageLoadWithNavigationCorrections) { + EnableNavigationCorrections(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, default_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_EQ(0, update_count()); + EXPECT_EQ(0, error_html_update_count()); +} + +TEST_F(NetErrorHelperCoreTest, MainFrameNonDnsError) { + // Original page starts loading. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and an error page is requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_CONNECTION_RESET), false, &html); + // Should have returned a local error page. + EXPECT_FALSE(html.empty()); + EXPECT_EQ(NetErrorString(net::ERR_CONNECTION_RESET), html); + + // Error page loads. + EXPECT_EQ(0, enable_page_helper_functions_count()); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_EQ(0, update_count()); + EXPECT_EQ(0, error_html_update_count()); + EXPECT_EQ(1, enable_page_helper_functions_count()); +} + +TEST_F(NetErrorHelperCoreTest, MainFrameNonDnsErrorWithCorrections) { + EnableNavigationCorrections(); + + // Original page starts loading. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and an error page is requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_CONNECTION_RESET), false, &html); + // Should have returned a local error page. + EXPECT_FALSE(html.empty()); + EXPECT_EQ(NetErrorString(net::ERR_CONNECTION_RESET), html); + + // Error page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_EQ(0, update_count()); + EXPECT_EQ(0, error_html_update_count()); +} + +// Much like above tests, but with a bunch of spurious DNS status messages that +// should have no effect. +TEST_F(NetErrorHelperCoreTest, MainFrameNonDnsErrorSpuriousStatus) { + // Original page starts loading. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + + // It fails, and an error page is requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_CONNECTION_RESET), + false, &html); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + + // Should have returned a local error page. + EXPECT_FALSE(html.empty()); + EXPECT_EQ(NetErrorString(net::ERR_CONNECTION_RESET), html); + + // Error page loads. + + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + + EXPECT_EQ(0, update_count()); + EXPECT_EQ(0, error_html_update_count()); +} + +TEST_F(NetErrorHelperCoreTest, SubFrameDnsError) { + // Original page starts loading. + core()->OnStartLoad(NetErrorHelperCore::SUB_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and an error page is requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::SUB_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + // Should have returned a local error page. + EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), html); + + // Error page loads. + core()->OnStartLoad(NetErrorHelperCore::SUB_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::SUB_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::SUB_FRAME); + EXPECT_EQ(0, update_count()); + EXPECT_EQ(0, error_html_update_count()); +} + +TEST_F(NetErrorHelperCoreTest, SubFrameDnsErrorWithCorrections) { + EnableNavigationCorrections(); + + // Original page starts loading. + core()->OnStartLoad(NetErrorHelperCore::SUB_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and an error page is requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::SUB_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + // Should have returned a local error page. + EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), html); + + // Error page loads. + core()->OnStartLoad(NetErrorHelperCore::SUB_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::SUB_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::SUB_FRAME); + EXPECT_EQ(0, update_count()); + EXPECT_EQ(0, error_html_update_count()); +} + +// Much like above tests, but with a bunch of spurious DNS status messages that +// should have no effect. +TEST_F(NetErrorHelperCoreTest, SubFrameDnsErrorSpuriousStatus) { + // Original page starts loading. + core()->OnStartLoad(NetErrorHelperCore::SUB_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + + // It fails, and an error page is requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::SUB_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + + // Should have returned a local error page. + EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), html); + + // Error page loads. + + core()->OnStartLoad(NetErrorHelperCore::SUB_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + + core()->OnCommitLoad(NetErrorHelperCore::SUB_FRAME, error_url()); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + + core()->OnFinishLoad(NetErrorHelperCore::SUB_FRAME); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + + EXPECT_EQ(0, update_count()); + EXPECT_EQ(0, error_html_update_count()); +} + +//------------------------------------------------------------------------------ +// Tests for updating the error page in response to DNS probe results. None +// of these have navigation corrections enabled. +//------------------------------------------------------------------------------ + +// Test case where the error page finishes loading before receiving any DNS +// probe messages. +TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbe) { + // Original page starts loading. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and an error page is requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + // Should have returned a local error page indicating a probe may run. + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_POSSIBLE), html); + + // Error page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_EQ(0, update_count()); + + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + EXPECT_EQ(1, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_STARTED), + last_error_html()); + + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(2, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN), + last_error_html()); + + // Any other probe updates should be ignored. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(2, update_count()); + EXPECT_EQ(0, error_html_update_count()); +} + +// Same as above, but the probe is not run. +TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbeNotRun) { + // Original page starts loading. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and an error page is requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + // Should have returned a local error page indicating a probe may run. + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_POSSIBLE), html); + + // Error page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_EQ(0, update_count()); + + // When the not run status arrives, the page should revert to the normal dns + // error page. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_NOT_RUN); + EXPECT_EQ(1, update_count()); + EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), last_error_html()); + + // Any other probe updates should be ignored. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(1, update_count()); + EXPECT_EQ(0, error_html_update_count()); +} + +// Same as above, but the probe result is inconclusive. +TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbeInconclusive) { + // Original page starts loading. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and an error page is requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + // Should have returned a local error page indicating a probe may run. + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_POSSIBLE), html); + + // Error page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_EQ(0, update_count()); + + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + EXPECT_EQ(1, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_STARTED), + last_error_html()); + + // When the inconclusive status arrives, the page should revert to the normal + // dns error page. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_INCONCLUSIVE); + EXPECT_EQ(2, update_count()); + EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), last_error_html()); + + // Any other probe updates should be ignored. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_INCONCLUSIVE); + EXPECT_EQ(2, update_count()); + EXPECT_EQ(0, error_html_update_count()); +} + +// Same as above, but the probe result is no internet. +TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbeNoInternet) { + // Original page starts loading. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and an error page is requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + // Should have returned a local error page indicating a probe may run. + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_POSSIBLE), html); + + // Error page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_EQ(0, update_count()); + + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + EXPECT_EQ(1, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_STARTED), + last_error_html()); + + // When the inconclusive status arrives, the page should revert to the normal + // dns error page. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET); + EXPECT_EQ(2, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET), + last_error_html()); + + // Any other probe updates should be ignored. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET); + EXPECT_EQ(2, update_count()); + EXPECT_EQ(0, error_html_update_count()); +} + +// Same as above, but the probe result is bad config. +TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbeBadConfig) { + // Original page starts loading. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and an error page is requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + // Should have returned a local error page indicating a probe may run. + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_POSSIBLE), html); + + // Error page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_EQ(0, update_count()); + + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + EXPECT_EQ(1, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_STARTED), + last_error_html()); + + // When the inconclusive status arrives, the page should revert to the normal + // dns error page. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_BAD_CONFIG); + EXPECT_EQ(2, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_FINISHED_BAD_CONFIG), + last_error_html()); + + // Any other probe updates should be ignored. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_BAD_CONFIG); + EXPECT_EQ(2, update_count()); + EXPECT_EQ(0, error_html_update_count()); +} + +// Test case where the error page finishes loading after receiving the start +// DNS probe message. +TEST_F(NetErrorHelperCoreTest, FinishedAfterStartProbe) { + // Original page starts loading. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and an error page is requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + // Should have returned a local error page indicating a probe may run. + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_POSSIBLE), html); + + // Error page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + + // Nothing should be done when a probe status comes in before loading + // finishes. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + EXPECT_EQ(0, update_count()); + + // When loading finishes, however, the buffered probe status should be sent + // to the page. + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_EQ(1, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_STARTED), + last_error_html()); + + // Should update the page again when the probe result comes in. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(2, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN), + last_error_html()); + + // Any other probe updates should be ignored. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_NOT_RUN); + EXPECT_EQ(2, update_count()); +} + +// Test case where the error page finishes loading before receiving any DNS +// probe messages and the request is a POST. +TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbePost) { + // Original page starts loading. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and an error page is requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + true, &html); + // Should have returned a local error page indicating a probe may run. + EXPECT_EQ(ErrorToString( + ProbeError(chrome_common_net::DNS_PROBE_POSSIBLE), + true), + html); + + // Error page loads. + EXPECT_EQ(0, enable_page_helper_functions_count()); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_EQ(0, update_count()); + EXPECT_EQ(1, enable_page_helper_functions_count()); + + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + EXPECT_EQ(1, update_count()); + EXPECT_EQ(ErrorToString( + ProbeError(chrome_common_net::DNS_PROBE_STARTED), true), + last_error_html()); + + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(2, update_count()); + EXPECT_EQ(ErrorToString( + ProbeError(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN), + true), + last_error_html()); + EXPECT_EQ(0, error_html_update_count()); +} + +// Test case where the probe finishes before the page is committed. +TEST_F(NetErrorHelperCoreTest, ProbeFinishesEarly) { + // Original page starts loading. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and an error page is requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + // Should have returned a local error page indicating a probe may run. + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_POSSIBLE), html); + + // Error page starts loading. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + + // Nothing should be done when the probe statuses come in before loading + // finishes. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(0, update_count()); + + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + EXPECT_EQ(0, update_count()); + + // When loading finishes, however, the buffered probe status should be sent + // to the page. + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_EQ(1, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN), + last_error_html()); + + // Any other probe updates should be ignored. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(1, update_count()); +} + +// Test case where one error page loads completely before a new navigation +// results in another error page. Probes are run for both pages. +TEST_F(NetErrorHelperCoreTest, TwoErrorsWithProbes) { + // Original page starts loading. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and an error page is requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + // Should have returned a local error page indicating a probe may run. + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_POSSIBLE), html); + + // Error page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + + // Probe results come in. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(2, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN), + last_error_html()); + + // The process starts again. + + // Normal page starts loading. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and an error page is requested. + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + // Should have returned a local error page indicating a probe may run. + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_POSSIBLE), html); + + // Error page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_EQ(2, update_count()); + + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + EXPECT_EQ(3, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_STARTED), + last_error_html()); + + // The probe returns a different result this time. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET); + EXPECT_EQ(4, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET), + last_error_html()); + EXPECT_EQ(0, error_html_update_count()); +} + +// Test case where one error page loads completely before a new navigation +// results in another error page. Probe results for the first probe are only +// received after the second load starts, but before it commits. +TEST_F(NetErrorHelperCoreTest, TwoErrorsWithProbesAfterSecondStarts) { + // Original page starts loading. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and an error page is requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + // Should have returned a local error page indicating a probe may run. + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_POSSIBLE), html); + + // Error page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + + // The process starts again. + + // Normal page starts loading. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and an error page is requested. + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + // Should have returned a local error page indicating a probe may run. + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_POSSIBLE), html); + + // Error page starts to load. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + + // Probe results come in, and the first page is updated. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(2, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN), + last_error_html()); + + // Second page finishes loading, and is updated using the same probe result. + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_EQ(3, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN), + last_error_html()); + + // Other probe results should be ignored. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET); + EXPECT_EQ(3, update_count()); + EXPECT_EQ(0, error_html_update_count()); +} + +// Same as above, but a new page is loaded before the error page commits. +TEST_F(NetErrorHelperCoreTest, ErrorPageLoadInterrupted) { + // Original page starts loading. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and an error page is requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + // Should have returned a local error page indicating a probe may run. + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_POSSIBLE), html); + + // Error page starts loading. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + // Probe statuses come in, but should be ignored. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(0, update_count()); + + // A new navigation begins while the error page is loading. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // And fails. + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + // Should have returned a local error page indicating a probe may run. + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_POSSIBLE), html); + + // Error page finishes loading. + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + + // Probe results come in. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + EXPECT_EQ(1, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_STARTED), + last_error_html()); + + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET); + EXPECT_EQ(2, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_FINISHED_NO_INTERNET), + last_error_html()); + EXPECT_EQ(0, error_html_update_count()); +} + +//------------------------------------------------------------------------------ +// Navigation correction tests. +//------------------------------------------------------------------------------ + +// Check that corrections are not used for HTTPS URLs. +TEST_F(NetErrorHelperCoreTest, NoCorrectionsForHttps) { + // Original page starts loading. + EnableNavigationCorrections(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // The HTTPS page fails to load. + std::string html; + blink::WebURLError error = NetError(net::ERR_NAME_NOT_RESOLVED); + error.unreachableURL = GURL(kFailedHttpsUrl); + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, error, false, &html); + + blink::WebURLError probe_error = + ProbeError(chrome_common_net::DNS_PROBE_POSSIBLE); + probe_error.unreachableURL = GURL(kFailedHttpsUrl); + EXPECT_EQ(ErrorToString(probe_error, false), html); + EXPECT_FALSE(is_url_being_fetched()); + EXPECT_FALSE(last_error_page_params()); + + // The blank page loads, no error page is loaded. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_FALSE(is_url_being_fetched()); + EXPECT_FALSE(last_error_page_params()); + + // Page is updated in response to DNS probes as normal. + EXPECT_EQ(0, update_count()); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(2, update_count()); + EXPECT_FALSE(last_error_page_params()); + blink::WebURLError final_probe_error = + ProbeError(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + final_probe_error.unreachableURL = GURL(kFailedHttpsUrl); + EXPECT_EQ(ErrorToString(final_probe_error, false), last_error_html()); +} + +// The blank page loads, then the navigation corrections request succeeds and is +// loaded. Then the probe results come in. +TEST_F(NetErrorHelperCoreTest, CorrectionsReceivedBeforeProbe) { + // Original page starts loading. + EnableNavigationCorrections(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + EXPECT_TRUE(html.empty()); + EXPECT_FALSE(is_url_being_fetched()); + EXPECT_FALSE(last_error_page_params()); + + // The blank page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + + // Corrections retrieval starts when the error page finishes loading. + EXPECT_FALSE(is_url_being_fetched()); + EXPECT_FALSE(last_error_page_params()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_TRUE(is_url_being_fetched()); + EXPECT_FALSE(last_error_page_params()); + + // Corrections are retrieved. + NavigationCorrectionsLoadSuccess(kDefaultCorrections, + arraysize(kDefaultCorrections)); + EXPECT_EQ(1, error_html_update_count()); + EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), last_error_html()); + ExpectDefaultNavigationCorrections(); + EXPECT_FALSE(is_url_being_fetched()); + + // Corrections load. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + + // Any probe statuses should be ignored. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + + EXPECT_EQ(0, update_count()); + EXPECT_EQ(1, error_html_update_count()); +} + +// The blank page finishes loading, then probe results come in, and then +// the navigation corrections request succeeds. +TEST_F(NetErrorHelperCoreTest, CorrectionsRetrievedAfterProbes) { + // Original page starts loading. + EnableNavigationCorrections(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and corrections are requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + EXPECT_TRUE(html.empty()); + + // The blank page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_TRUE(is_url_being_fetched()); + EXPECT_FALSE(last_error_page_params()); + + // Probe statuses should be ignored. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(0, update_count()); + EXPECT_EQ(0, error_html_update_count()); + EXPECT_FALSE(last_error_page_params()); + + // Corrections are retrieved. + EXPECT_TRUE(is_url_being_fetched()); + NavigationCorrectionsLoadSuccess(kDefaultCorrections, + arraysize(kDefaultCorrections)); + EXPECT_EQ(1, error_html_update_count()); + EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), last_error_html()); + ExpectDefaultNavigationCorrections(); + EXPECT_FALSE(is_url_being_fetched()); + + // Corrections load. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_EQ(1, error_html_update_count()); + EXPECT_EQ(0, update_count()); +} + +// The corrections request fails and then the error page loads for an error that +// does not trigger DNS probes. +TEST_F(NetErrorHelperCoreTest, CorrectionsFailLoadNoProbes) { + // Original page starts loading. + EnableNavigationCorrections(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and corrections are requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_CONNECTION_FAILED), + false, &html); + EXPECT_TRUE(html.empty()); + + // The blank page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + + // Corrections request fails, final error page is shown. + EXPECT_TRUE(is_url_being_fetched()); + NavigationCorrectionsLoadFailure(); + EXPECT_EQ(1, error_html_update_count()); + EXPECT_EQ(last_error_html(), NetErrorString(net::ERR_CONNECTION_FAILED)); + EXPECT_FALSE(is_url_being_fetched()); + EXPECT_EQ(0, update_count()); + EXPECT_FALSE(last_error_page_params()); + + // Error page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + + // If probe statuses come in last from another page load, they should be + // ignored. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(0, update_count()); + EXPECT_EQ(1, error_html_update_count()); +} + +// The corrections request fails and then the error page loads before probe +// results are received. +TEST_F(NetErrorHelperCoreTest, CorrectionsFailLoadBeforeProbe) { + // Original page starts loading. + EnableNavigationCorrections(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and corrections are requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + EXPECT_TRUE(html.empty()); + + // The blank page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + + // Corrections request fails, probe pending page shown. + EXPECT_TRUE(is_url_being_fetched()); + NavigationCorrectionsLoadFailure(); + EXPECT_EQ(1, error_html_update_count()); + EXPECT_EQ(last_error_html(), + ProbeErrorString(chrome_common_net::DNS_PROBE_POSSIBLE)); + EXPECT_FALSE(is_url_being_fetched()); + EXPECT_EQ(0, update_count()); + + // Probe page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + + // Probe statuses comes in, and page is updated. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + EXPECT_EQ(1, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_STARTED), + last_error_html()); + + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(2, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN), + last_error_html()); + + // The commit results in sending a second probe status, which is ignored. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(2, update_count()); + EXPECT_EQ(1, error_html_update_count()); +} + +// The corrections request fails after receiving probe results. +TEST_F(NetErrorHelperCoreTest, CorrectionsFailAfterProbe) { + // Original page starts loading. + EnableNavigationCorrections(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and corrections are requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + EXPECT_TRUE(html.empty()); + + // The blank page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + + // Results come in, but end up being ignored. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(0, update_count()); + + // Corrections request fails, probe pending page shown. + EXPECT_TRUE(is_url_being_fetched()); + NavigationCorrectionsLoadFailure(); + EXPECT_EQ(1, error_html_update_count()); + EXPECT_EQ(last_error_html(), + ProbeErrorString(chrome_common_net::DNS_PROBE_POSSIBLE)); + EXPECT_FALSE(is_url_being_fetched()); + EXPECT_EQ(0, update_count()); + + // Probe page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + + // Probe statuses comes in, and page is updated. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(1, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN), + last_error_html()); + EXPECT_EQ(1, error_html_update_count()); +} + +// An error page load that would normally load correction is interrupted +// by a new navigation before the blank page commits. +TEST_F(NetErrorHelperCoreTest, CorrectionsInterruptedBeforeCommit) { + // Original page starts loading. + EnableNavigationCorrections(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and corrections are requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + EXPECT_TRUE(html.empty()); + + // The blank page starts loading. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + + // A new page load starts. + EXPECT_FALSE(is_url_being_fetched()); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // A new page load interrupts the original load. + EXPECT_FALSE(is_url_being_fetched()); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + EXPECT_FALSE(is_url_being_fetched()); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, default_url()); + EXPECT_FALSE(is_url_being_fetched()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + + EXPECT_FALSE(is_url_being_fetched()); + EXPECT_EQ(0, update_count()); + EXPECT_EQ(0, error_html_update_count()); +} + +// An error page load that would normally load corrections is interrupted +// by a new navigation before the blank page finishes loading. +TEST_F(NetErrorHelperCoreTest, CorrectionsInterruptedBeforeLoad) { + // Original page starts loading. + EnableNavigationCorrections(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and corrections are requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + EXPECT_TRUE(html.empty()); + + // The blank page starts loading and is committed. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + + // A new page load interrupts the original load. + EXPECT_FALSE(is_url_being_fetched()); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + EXPECT_FALSE(is_url_being_fetched()); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, default_url()); + EXPECT_FALSE(is_url_being_fetched()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + + EXPECT_FALSE(is_url_being_fetched()); + EXPECT_EQ(0, update_count()); + EXPECT_EQ(0, error_html_update_count()); +} + +// The corrections request is cancelled due to a new navigation. The new +// navigation fails and then loads corrections successfully. +TEST_F(NetErrorHelperCoreTest, CorrectionsInterrupted) { + // Original page starts loading. + EnableNavigationCorrections(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and corrections are requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + EXPECT_TRUE(html.empty()); + + // The blank page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_TRUE(is_url_being_fetched()); + + // Results come in, but end up being ignored. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(0, update_count()); + + // A new load appears! + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + EXPECT_FALSE(is_url_being_fetched()); + + // It fails, and corrections are requested again once a blank page is loaded. + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + EXPECT_TRUE(html.empty()); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + EXPECT_FALSE(is_url_being_fetched()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_TRUE(is_url_being_fetched()); + + // Corrections request succeeds. + NavigationCorrectionsLoadSuccess(kDefaultCorrections, + arraysize(kDefaultCorrections)); + EXPECT_EQ(1, error_html_update_count()); + EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), last_error_html()); + ExpectDefaultNavigationCorrections(); + EXPECT_FALSE(is_url_being_fetched()); + + // Probe statuses come in, and are ignored. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(0, update_count()); +} + +// The corrections request is cancelled due to call to Stop(). The cross +// process navigation is cancelled, and then a new load fails and tries to load +// corrections again, unsuccessfully. +TEST_F(NetErrorHelperCoreTest, CorrectionsStopped) { + // Original page starts loading. + EnableNavigationCorrections(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and corrections are requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + EXPECT_TRUE(html.empty()); + + // The blank page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + + EXPECT_TRUE(is_url_being_fetched()); + core()->OnStop(); + EXPECT_FALSE(is_url_being_fetched()); + + // Results come in, but end up being ignored. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(0, update_count()); + + // Cross process navigation must have been cancelled, and a new load appears! + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and corrections are requested again. + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + EXPECT_TRUE(html.empty()); + + // The blank page loads again. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_TRUE(is_url_being_fetched()); + + // Corrections request fails, probe pending page shown. + NavigationCorrectionsLoadFailure(); + EXPECT_EQ(1, error_html_update_count()); + EXPECT_EQ(last_error_html(), + ProbeErrorString(chrome_common_net::DNS_PROBE_POSSIBLE)); + EXPECT_FALSE(is_url_being_fetched()); + + // Probe page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + + // Probe statuses comes in, and page is updated. + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_STARTED); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_STARTED), + last_error_html()); + EXPECT_EQ(1, update_count()); + + core()->OnNetErrorInfo(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + EXPECT_EQ(2, update_count()); + EXPECT_EQ(ProbeErrorString(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN), + last_error_html()); + EXPECT_EQ(1, error_html_update_count()); +} + +// Check the case corrections are disabled while the blank page (Loaded +// before the corrections page) is being loaded. +TEST_F(NetErrorHelperCoreTest, CorrectionsDisabledBeforeFetch) { + // Original page starts loading. + EnableNavigationCorrections(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and corrections are requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + EXPECT_TRUE(html.empty()); + + // The blank page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + // Corrections is disabled. + DisableNavigationCorrections(); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_TRUE(is_url_being_fetched()); + EXPECT_FALSE(last_error_page_params()); + + // Corrections are retrieved. + NavigationCorrectionsLoadSuccess(kDefaultCorrections, + arraysize(kDefaultCorrections)); + EXPECT_EQ(1, error_html_update_count()); + EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), last_error_html()); + EXPECT_FALSE(is_url_being_fetched()); + ExpectDefaultNavigationCorrections(); + + // Corrections load. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_EQ(1, error_html_update_count()); + EXPECT_EQ(0, update_count()); +} + +// Check the case corrections is disabled while fetching the corrections for +// a failed page load. +TEST_F(NetErrorHelperCoreTest, CorrectionsDisabledDuringFetch) { + // Original page starts loading. + EnableNavigationCorrections(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and corrections are requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + EXPECT_TRUE(html.empty()); + + // The blank page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_TRUE(is_url_being_fetched()); + EXPECT_FALSE(last_error_page_params()); + + // Corrections are disabled. + DisableNavigationCorrections(); + + // Corrections are retrieved. + NavigationCorrectionsLoadSuccess(kDefaultCorrections, + arraysize(kDefaultCorrections)); + EXPECT_EQ(1, error_html_update_count()); + EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), last_error_html()); + EXPECT_FALSE(is_url_being_fetched()); + ExpectDefaultNavigationCorrections(); + + // Corrections load. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_EQ(1, error_html_update_count()); + EXPECT_EQ(0, update_count()); +} + +// Checks corrections are is used when there are no search suggestions. +TEST_F(NetErrorHelperCoreTest, CorrectionsWithoutSearch) { + const NavigationCorrection kCorrections[] = { + {"urlCorrection", "http://somewhere_else/", "btn", "data", false, false}, + }; + + // Original page starts loading. + EnableNavigationCorrections(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and corrections are requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + EXPECT_TRUE(html.empty()); + + // The blank page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_TRUE(is_url_being_fetched()); + EXPECT_FALSE(last_error_page_params()); + + // Corrections are retrieved. + NavigationCorrectionsLoadSuccess(kCorrections, arraysize(kCorrections)); + EXPECT_EQ(1, error_html_update_count()); + EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), last_error_html()); + EXPECT_FALSE(is_url_being_fetched()); + + // Check params. + ASSERT_TRUE(last_error_page_params()); + EXPECT_FALSE(last_error_page_params()->suggest_reload); + EXPECT_EQ(1u, last_error_page_params()->override_suggestions->GetSize()); + EXPECT_FALSE(last_error_page_params()->search_url.is_valid()); + EXPECT_EQ("", last_error_page_params()->search_terms); + + // Corrections load. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_EQ(1, error_html_update_count()); + EXPECT_EQ(0, update_count()); +} + +// Checks corrections are used when there are only search suggestions. +TEST_F(NetErrorHelperCoreTest, CorrectionsOnlySearchSuggestion) { + const NavigationCorrection kCorrections[] = { + {"webSearchQuery", kSuggestedSearchTerms, "frm", "data", false, false}, + }; + + // Original page starts loading. + EnableNavigationCorrections(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and corrections are requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + EXPECT_TRUE(html.empty()); + + // The blank page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_TRUE(is_url_being_fetched()); + EXPECT_FALSE(last_error_page_params()); + + // Corrections are retrieved. + NavigationCorrectionsLoadSuccess(kCorrections, arraysize(kCorrections)); + EXPECT_EQ(1, error_html_update_count()); + EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), last_error_html()); + EXPECT_FALSE(is_url_being_fetched()); + + // Check params. + ASSERT_TRUE(last_error_page_params()); + EXPECT_FALSE(last_error_page_params()->suggest_reload); + EXPECT_EQ(0u, last_error_page_params()->override_suggestions->GetSize()); + EXPECT_EQ(GURL(kSearchUrl), last_error_page_params()->search_url); + EXPECT_EQ(kSuggestedSearchTerms, last_error_page_params()->search_terms); + + // Corrections load. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_EQ(1, error_html_update_count()); + EXPECT_EQ(0, update_count()); +} + +// The correction service returns a non-JSON result. +TEST_F(NetErrorHelperCoreTest, CorrectionServiceReturnsNonJsonResult) { + // Original page starts loading. + EnableNavigationCorrections(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and corrections are requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_CONNECTION_FAILED), + false, &html); + EXPECT_TRUE(html.empty()); + + // The blank page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + + // Corrections request fails, final error page is shown. + EXPECT_TRUE(is_url_being_fetched()); + NavigationCorrectionsLoadFinished("Weird Response"); + EXPECT_EQ(1, error_html_update_count()); + EXPECT_EQ(last_error_html(), NetErrorString(net::ERR_CONNECTION_FAILED)); + EXPECT_FALSE(is_url_being_fetched()); + EXPECT_EQ(0, update_count()); + EXPECT_FALSE(last_error_page_params()); + + // Error page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); +} + +// The correction service returns a JSON result that isn't a valid list of +// corrections. +TEST_F(NetErrorHelperCoreTest, CorrectionServiceReturnsInvalidJsonResult) { + // Original page starts loading. + EnableNavigationCorrections(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails, and corrections are requested. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_CONNECTION_FAILED), + false, &html); + EXPECT_TRUE(html.empty()); + + // The blank page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + + // Corrections request fails, final error page is shown. + EXPECT_TRUE(is_url_being_fetched()); + NavigationCorrectionsLoadFinished("{\"result\": 42}"); + EXPECT_EQ(1, error_html_update_count()); + EXPECT_EQ(last_error_html(), NetErrorString(net::ERR_CONNECTION_FAILED)); + EXPECT_FALSE(is_url_being_fetched()); + EXPECT_EQ(0, update_count()); + EXPECT_FALSE(last_error_page_params()); + + // Error page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); +} + +TEST_F(NetErrorHelperCoreTest, CorrectionClickTracking) { + // Go through the standard navigation correction steps. + + // Original page starts loading. + EnableNavigationCorrections(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + + // It fails. + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_NAME_NOT_RESOLVED), + false, &html); + EXPECT_TRUE(html.empty()); + EXPECT_FALSE(is_url_being_fetched()); + EXPECT_FALSE(last_error_page_params()); + + // The blank page loads. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + + // Corrections retrieval starts when the error page finishes loading. + EXPECT_FALSE(is_url_being_fetched()); + EXPECT_FALSE(last_error_page_params()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_TRUE(is_url_being_fetched()); + EXPECT_FALSE(last_error_page_params()); + + // Corrections are retrieved. + NavigationCorrectionsLoadSuccess(kDefaultCorrections, + arraysize(kDefaultCorrections)); + EXPECT_EQ(1, error_html_update_count()); + EXPECT_EQ(NetErrorString(net::ERR_NAME_NOT_RESOLVED), last_error_html()); + ExpectDefaultNavigationCorrections(); + EXPECT_FALSE(is_url_being_fetched()); + + // Corrections load. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + + EXPECT_EQ(0, tracking_request_count()); + + // Invalid clicks should be ignored. + core()->TrackClick(-1); + core()->TrackClick(arraysize(kDefaultCorrections)); + EXPECT_EQ(0, tracking_request_count()); + + for (size_t i = 0; i < arraysize(kDefaultCorrections); ++i) { + // Skip links that do not appear in the page. + if (kDefaultCorrections[i].is_porn || kDefaultCorrections[i].is_soft_porn) + continue; + + int old_tracking_request_count = tracking_request_count(); + core()->TrackClick(i); + EXPECT_EQ(old_tracking_request_count + 1, tracking_request_count()); + EXPECT_EQ(GURL(kNavigationCorrectionUrl), last_tracking_url()); + + // Make sure all expected strings appear in output. + EXPECT_TRUE(last_tracking_request_body().find( + kDefaultCorrections[i].url_correction)); + EXPECT_TRUE(last_tracking_request_body().find( + kDefaultCorrections[i].click_data)); + EXPECT_TRUE(last_tracking_request_body().find( + kDefaultCorrections[i].click_type)); + EXPECT_TRUE(last_tracking_request_body().find( + kNavigationCorrectionEventId)); + EXPECT_TRUE(last_tracking_request_body().find( + kNavigationCorrectionFingerprint)); + } + + // Make sure duplicate tracking requests are ignored. + for (size_t i = 0; i < arraysize(kDefaultCorrections); ++i) { + // Skip links that do not appear in the page. + if (kDefaultCorrections[i].is_porn || kDefaultCorrections[i].is_soft_porn) + continue; + + int old_tracking_request_count = tracking_request_count(); + core()->TrackClick(i); + EXPECT_EQ(old_tracking_request_count, tracking_request_count()); + } + + EXPECT_EQ(0, update_count()); + EXPECT_EQ(1, error_html_update_count()); +} + +TEST_F(NetErrorHelperCoreTest, AutoReloadDisabled) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + + EXPECT_FALSE(timer()->IsRunning()); + EXPECT_EQ(0, reload_count()); +} + +class NetErrorHelperCoreAutoReloadTest : public NetErrorHelperCoreTest { + public: + virtual void SetUp() { + NetErrorHelperCoreTest::SetUp(); + SetUpCore(true, false, true); + } +}; + +TEST_F(NetErrorHelperCoreAutoReloadTest, Succeeds) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + + EXPECT_TRUE(timer()->IsRunning()); + EXPECT_EQ(0, reload_count()); + + timer()->Fire(); + EXPECT_FALSE(timer()->IsRunning()); + EXPECT_EQ(1, reload_count()); + + DoSuccessLoad(); + + EXPECT_FALSE(timer()->IsRunning()); +} + +TEST_F(NetErrorHelperCoreAutoReloadTest, Retries) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + + EXPECT_TRUE(timer()->IsRunning()); + base::TimeDelta first_delay = timer()->GetCurrentDelay(); + EXPECT_EQ(0, reload_count()); + + timer()->Fire(); + EXPECT_FALSE(timer()->IsRunning()); + EXPECT_EQ(1, reload_count()); + + DoErrorLoad(net::ERR_CONNECTION_RESET); + + EXPECT_TRUE(timer()->IsRunning()); + EXPECT_GT(timer()->GetCurrentDelay(), first_delay); +} + +TEST_F(NetErrorHelperCoreAutoReloadTest, StopsTimerOnStop) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + EXPECT_TRUE(timer()->IsRunning()); + core()->OnStop(); + EXPECT_FALSE(timer()->IsRunning()); +} + +TEST_F(NetErrorHelperCoreAutoReloadTest, StopsLoadingOnStop) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + EXPECT_EQ(0, core()->auto_reload_count()); + timer()->Fire(); + EXPECT_EQ(1, core()->auto_reload_count()); + EXPECT_EQ(1, reload_count()); + core()->OnStop(); + EXPECT_FALSE(timer()->IsRunning()); + EXPECT_EQ(0, core()->auto_reload_count()); +} + +TEST_F(NetErrorHelperCoreAutoReloadTest, StopsOnOtherLoadStart) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + EXPECT_TRUE(timer()->IsRunning()); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + EXPECT_FALSE(timer()->IsRunning()); + EXPECT_EQ(0, core()->auto_reload_count()); +} + +TEST_F(NetErrorHelperCoreAutoReloadTest, ResetsCountOnSuccess) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + base::TimeDelta delay = timer()->GetCurrentDelay(); + EXPECT_EQ(0, core()->auto_reload_count()); + timer()->Fire(); + EXPECT_EQ(1, core()->auto_reload_count()); + EXPECT_EQ(1, reload_count()); + DoSuccessLoad(); + DoErrorLoad(net::ERR_CONNECTION_RESET); + EXPECT_EQ(0, core()->auto_reload_count()); + EXPECT_EQ(timer()->GetCurrentDelay(), delay); + timer()->Fire(); + EXPECT_EQ(1, core()->auto_reload_count()); + EXPECT_EQ(2, reload_count()); + DoSuccessLoad(); + EXPECT_EQ(0, core()->auto_reload_count()); +} + +TEST_F(NetErrorHelperCoreAutoReloadTest, RestartsOnOnline) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + base::TimeDelta delay = timer()->GetCurrentDelay(); + timer()->Fire(); + DoErrorLoad(net::ERR_CONNECTION_RESET); + EXPECT_TRUE(timer()->IsRunning()); + EXPECT_NE(delay, timer()->GetCurrentDelay()); + core()->NetworkStateChanged(false); + EXPECT_FALSE(timer()->IsRunning()); + core()->NetworkStateChanged(true); + EXPECT_TRUE(timer()->IsRunning()); + EXPECT_EQ(delay, timer()->GetCurrentDelay()); +} + +TEST_F(NetErrorHelperCoreAutoReloadTest, DoesNotStartOnOnline) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + timer()->Fire(); + DoSuccessLoad(); + EXPECT_FALSE(timer()->IsRunning()); + core()->NetworkStateChanged(true); + EXPECT_FALSE(timer()->IsRunning()); +} + +TEST_F(NetErrorHelperCoreAutoReloadTest, DoesNotStartOffline) { + core()->NetworkStateChanged(false); + DoErrorLoad(net::ERR_CONNECTION_RESET); + EXPECT_FALSE(timer()->IsRunning()); + core()->NetworkStateChanged(true); + EXPECT_TRUE(timer()->IsRunning()); +} + +TEST_F(NetErrorHelperCoreAutoReloadTest, DoesNotRestartOnOnlineAfterStop) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + timer()->Fire(); + core()->OnStop(); + core()->NetworkStateChanged(true); + EXPECT_FALSE(timer()->IsRunning()); +} + +TEST_F(NetErrorHelperCoreAutoReloadTest, WithDnsProbes) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + DoDnsProbe(chrome_common_net::DNS_PROBE_FINISHED_NXDOMAIN); + timer()->Fire(); + EXPECT_EQ(1, reload_count()); +} + +TEST_F(NetErrorHelperCoreAutoReloadTest, ExponentialBackoffLevelsOff) { + base::TimeDelta previous = base::TimeDelta::FromMilliseconds(0); + const int kMaxTries = 50; + int tries = 0; + for (tries = 0; tries < kMaxTries; tries++) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + EXPECT_TRUE(timer()->IsRunning()); + if (previous == timer()->GetCurrentDelay()) + break; + previous = timer()->GetCurrentDelay(); + timer()->Fire(); + } + + EXPECT_LT(tries, kMaxTries); +} + +TEST_F(NetErrorHelperCoreAutoReloadTest, SlowError) { + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_CONNECTION_RESET), false, &html); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + EXPECT_FALSE(timer()->IsRunning()); + // Start a new non-error page load. + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + EXPECT_FALSE(timer()->IsRunning()); + // Finish the error page load. + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_FALSE(timer()->IsRunning()); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_FALSE(timer()->IsRunning()); +} + +TEST_F(NetErrorHelperCoreAutoReloadTest, OnlineSlowError) { + core()->NetworkStateChanged(false); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_CONNECTION_RESET), false, &html); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + EXPECT_FALSE(timer()->IsRunning()); + core()->NetworkStateChanged(true); + EXPECT_FALSE(timer()->IsRunning()); + core()->NetworkStateChanged(false); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_FALSE(timer()->IsRunning()); + core()->NetworkStateChanged(true); + EXPECT_TRUE(timer()->IsRunning()); +} + +TEST_F(NetErrorHelperCoreAutoReloadTest, OnlinePendingError) { + core()->NetworkStateChanged(false); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_CONNECTION_RESET), false, &html); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + EXPECT_FALSE(timer()->IsRunning()); + core()->NetworkStateChanged(true); + EXPECT_FALSE(timer()->IsRunning()); + core()->NetworkStateChanged(false); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + EXPECT_FALSE(timer()->IsRunning()); + core()->NetworkStateChanged(true); + EXPECT_TRUE(timer()->IsRunning()); +} + +TEST_F(NetErrorHelperCoreAutoReloadTest, OnlinePartialErrorReplacement) { + core()->NetworkStateChanged(false); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + std::string html; + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_CONNECTION_RESET), false, &html); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, error_url()); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + core()->GetErrorHTML(NetErrorHelperCore::MAIN_FRAME, + NetError(net::ERR_CONNECTION_RESET), false, &html); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::ERROR_PAGE); + EXPECT_FALSE(timer()->IsRunning()); + core()->NetworkStateChanged(true); + EXPECT_FALSE(timer()->IsRunning()); +} + +TEST_F(NetErrorHelperCoreAutoReloadTest, ShouldSuppressNonReloadableErrorPage) { + DoErrorLoad(net::ERR_ABORTED); + EXPECT_FALSE(core()->ShouldSuppressErrorPage(NetErrorHelperCore::MAIN_FRAME, + GURL(kFailedUrl))); +} + +TEST_F(NetErrorHelperCoreAutoReloadTest, ShouldSuppressErrorPage) { + // Set up the environment to test ShouldSuppressErrorPage: auto-reload is + // enabled, an error page is loaded, and the auto-reload callback is running. + DoErrorLoad(net::ERR_CONNECTION_RESET); + timer()->Fire(); + + EXPECT_FALSE(core()->ShouldSuppressErrorPage(NetErrorHelperCore::SUB_FRAME, + GURL(kFailedUrl))); + EXPECT_FALSE(core()->ShouldSuppressErrorPage(NetErrorHelperCore::MAIN_FRAME, + GURL("http://some.other.url"))); + EXPECT_TRUE(core()->ShouldSuppressErrorPage(NetErrorHelperCore::MAIN_FRAME, + GURL(kFailedUrl))); +} + +TEST_F(NetErrorHelperCoreAutoReloadTest, HiddenAndShown) { + SetUpCore(true, true, true); + DoErrorLoad(net::ERR_CONNECTION_RESET); + EXPECT_TRUE(timer()->IsRunning()); + core()->OnWasHidden(); + EXPECT_FALSE(timer()->IsRunning()); + core()->OnWasShown(); + EXPECT_TRUE(timer()->IsRunning()); +} + +TEST_F(NetErrorHelperCoreAutoReloadTest, HiddenWhileOnline) { + SetUpCore(true, true, true); + core()->NetworkStateChanged(false); + DoErrorLoad(net::ERR_CONNECTION_RESET); + EXPECT_FALSE(timer()->IsRunning()); + core()->OnWasHidden(); + core()->NetworkStateChanged(true); + EXPECT_FALSE(timer()->IsRunning()); + core()->NetworkStateChanged(false); + core()->OnWasShown(); + EXPECT_FALSE(timer()->IsRunning()); + core()->NetworkStateChanged(true); + EXPECT_TRUE(timer()->IsRunning()); + core()->NetworkStateChanged(false); + core()->OnWasHidden(); + EXPECT_FALSE(timer()->IsRunning()); + core()->NetworkStateChanged(true); + EXPECT_FALSE(timer()->IsRunning()); + core()->OnWasShown(); + EXPECT_TRUE(timer()->IsRunning()); +} + +TEST_F(NetErrorHelperCoreAutoReloadTest, ShownWhileNotReloading) { + SetUpCore(true, true, false); + DoErrorLoad(net::ERR_CONNECTION_RESET); + EXPECT_FALSE(timer()->IsRunning()); + core()->OnWasShown(); + EXPECT_TRUE(timer()->IsRunning()); +} + +class NetErrorHelperCoreHistogramTest + : public NetErrorHelperCoreAutoReloadTest { + public: + virtual void SetUp() override { + NetErrorHelperCoreAutoReloadTest::SetUp(); + } + + static const char kCountAtStop[]; + static const char kErrorAtStop[]; + static const char kCountAtSuccess[]; + static const char kErrorAtSuccess[]; + static const char kErrorAtFirstSuccess[]; + + protected: + base::HistogramTester histogram_tester_; +}; + +const char NetErrorHelperCoreHistogramTest::kCountAtStop[] = + "Net.AutoReload.CountAtStop"; +const char NetErrorHelperCoreHistogramTest::kErrorAtStop[] = + "Net.AutoReload.ErrorAtStop"; +const char NetErrorHelperCoreHistogramTest::kCountAtSuccess[] = + "Net.AutoReload.CountAtSuccess"; +const char NetErrorHelperCoreHistogramTest::kErrorAtSuccess[] = + "Net.AutoReload.ErrorAtSuccess"; +const char NetErrorHelperCoreHistogramTest::kErrorAtFirstSuccess[] = + "Net.AutoReload.ErrorAtFirstSuccess"; + +// Test that the success histograms are updated when auto-reload succeeds at the +// first attempt, and that the failure histograms are not updated. +TEST_F(NetErrorHelperCoreHistogramTest, SuccessAtFirstAttempt) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + timer()->Fire(); + DoSuccessLoad(); + + // All of CountAtSuccess, ErrorAtSuccess, and ErrorAtFirstSuccess should + // reflect this successful load. The failure histograms should be unchanged. + histogram_tester_.ExpectTotalCount(kCountAtSuccess, 1); + histogram_tester_.ExpectTotalCount(kErrorAtSuccess, 1); + histogram_tester_.ExpectTotalCount(kErrorAtFirstSuccess, 1); + histogram_tester_.ExpectTotalCount(kCountAtStop, 0); + histogram_tester_.ExpectTotalCount(kErrorAtStop, 0); +} + +// Test that the success histograms are updated when auto-reload succeeds but +// not on the first attempt, and that the first-success histogram is not +// updated. +TEST_F(NetErrorHelperCoreHistogramTest, SuccessAtSecondAttempt) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + timer()->Fire(); + EXPECT_TRUE(core()->ShouldSuppressErrorPage(NetErrorHelperCore::MAIN_FRAME, + default_url())); +// DoErrorLoad(net::ERR_CONNECTION_RESET); + timer()->Fire(); + DoSuccessLoad(); + + // CountAtSuccess and ErrorAtSuccess should reflect this successful load, but + // not ErrorAtFirstSuccess since it wasn't a first success. + histogram_tester_.ExpectTotalCount(kCountAtSuccess, 1); + histogram_tester_.ExpectTotalCount(kErrorAtSuccess, 1); + histogram_tester_.ExpectTotalCount(kErrorAtFirstSuccess, 0); + histogram_tester_.ExpectTotalCount(kCountAtStop, 0); + histogram_tester_.ExpectTotalCount(kErrorAtStop, 0); +} + +// Test that a user stop (caused by the user pressing the 'Stop' button) +// registers as an auto-reload failure if an auto-reload attempt is in flight. +// Note that "user stop" is also caused by a cross-process navigation, for which +// the browser process will send an OnStop to the old process. +TEST_F(NetErrorHelperCoreHistogramTest, UserStop) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + timer()->Fire(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + core()->OnStop(); + + // CountAtStop and ErrorAtStop should reflect the failure. + histogram_tester_.ExpectTotalCount(kCountAtSuccess, 0); + histogram_tester_.ExpectTotalCount(kErrorAtSuccess, 0); + histogram_tester_.ExpectTotalCount(kErrorAtFirstSuccess, 0); + histogram_tester_.ExpectTotalCount(kCountAtStop, 1); + histogram_tester_.ExpectTotalCount(kErrorAtStop, 1); +} + +// Test that a user stop (caused by the user pressing the 'Stop' button) +// registers as an auto-reload failure even if an auto-reload attempt has not +// been launched yet (i.e., if the timer is running, but no reload is in +// flight), because this means auto-reload didn't successfully replace the error +// page. +TEST_F(NetErrorHelperCoreHistogramTest, OtherPageLoaded) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + core()->OnStop(); + + histogram_tester_.ExpectTotalCount(kCountAtSuccess, 0); + histogram_tester_.ExpectTotalCount(kErrorAtSuccess, 0); + histogram_tester_.ExpectTotalCount(kErrorAtFirstSuccess, 0); + histogram_tester_.ExpectTotalCount(kCountAtStop, 1); + histogram_tester_.ExpectTotalCount(kErrorAtStop, 1); +} + +// Test that a commit of a different URL (caused by the user navigating to a +// different page) with an auto-reload attempt in flight registers as an +// auto-reload failure. +TEST_F(NetErrorHelperCoreHistogramTest, OtherPageLoadedAfterTimerFires) { + const GURL kTestUrl("https://anotherurl"); + DoErrorLoad(net::ERR_CONNECTION_RESET); + timer()->Fire(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, + kTestUrl); + core()->OnFinishLoad(NetErrorHelperCore::MAIN_FRAME); + + histogram_tester_.ExpectTotalCount(kCountAtSuccess, 0); + histogram_tester_.ExpectTotalCount(kErrorAtSuccess, 0); + histogram_tester_.ExpectTotalCount(kErrorAtFirstSuccess, 0); + histogram_tester_.ExpectTotalCount(kCountAtStop, 1); + histogram_tester_.ExpectTotalCount(kErrorAtStop, 1); +} + +// Test that a commit of the same URL with an auto-reload attempt in flight +// registers as an auto-reload success. +TEST_F(NetErrorHelperCoreHistogramTest, SamePageLoadedAfterTimerFires) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + timer()->Fire(); + DoSuccessLoad(); + + histogram_tester_.ExpectTotalCount(kCountAtSuccess, 1); + histogram_tester_.ExpectTotalCount(kErrorAtSuccess, 1); + histogram_tester_.ExpectTotalCount(kErrorAtFirstSuccess, 1); + histogram_tester_.ExpectTotalCount(kCountAtStop, 0); + histogram_tester_.ExpectTotalCount(kErrorAtStop, 0); +} + +TEST_F(NetErrorHelperCoreHistogramTest, SamePageLoadedAfterLoadStarts) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + timer()->Fire(); + // Autoreload attempt starts + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + // User does a manual reload + DoSuccessLoad(); + + histogram_tester_.ExpectTotalCount(kCountAtSuccess, 1); + histogram_tester_.ExpectTotalCount(kErrorAtSuccess, 1); + histogram_tester_.ExpectTotalCount(kErrorAtFirstSuccess, 1); + histogram_tester_.ExpectTotalCount(kCountAtStop, 0); + histogram_tester_.ExpectTotalCount(kErrorAtStop, 0); +} + +// In this test case, the user presses the reload button manually after an +// auto-reload fails and the error page is suppressed. +TEST_F(NetErrorHelperCoreHistogramTest, ErrorPageLoadedAfterTimerFires) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + timer()->Fire(); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + EXPECT_TRUE(core()->ShouldSuppressErrorPage(NetErrorHelperCore::MAIN_FRAME, + default_url())); + DoErrorLoad(net::ERR_CONNECTION_RESET); + + histogram_tester_.ExpectTotalCount(kCountAtSuccess, 0); + histogram_tester_.ExpectTotalCount(kErrorAtSuccess, 0); + histogram_tester_.ExpectTotalCount(kErrorAtFirstSuccess, 0); + histogram_tester_.ExpectTotalCount(kCountAtStop, 0); + histogram_tester_.ExpectTotalCount(kErrorAtStop, 0); +} + +TEST_F(NetErrorHelperCoreHistogramTest, SuccessPageLoadedBeforeTimerFires) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + core()->OnStartLoad(NetErrorHelperCore::MAIN_FRAME, + NetErrorHelperCore::NON_ERROR_PAGE); + core()->OnCommitLoad(NetErrorHelperCore::MAIN_FRAME, + GURL(kFailedHttpsUrl)); + + histogram_tester_.ExpectTotalCount(kCountAtSuccess, 0); + histogram_tester_.ExpectTotalCount(kErrorAtSuccess, 0); + histogram_tester_.ExpectTotalCount(kErrorAtFirstSuccess, 0); + histogram_tester_.ExpectTotalCount(kCountAtStop, 1); + histogram_tester_.ExpectTotalCount(kErrorAtStop, 1); +} + + +TEST_F(NetErrorHelperCoreTest, ExplicitReloadSucceeds) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + EXPECT_EQ(0, reload_count()); + core()->ExecuteButtonPress(NetErrorHelperCore::RELOAD_BUTTON); + EXPECT_EQ(1, reload_count()); +} + +TEST_F(NetErrorHelperCoreTest, ExplicitLoadStaleSucceeds) { + DoErrorLoad(net::ERR_CONNECTION_RESET); + EXPECT_EQ(0, load_stale_count()); + core()->ExecuteButtonPress(NetErrorHelperCore::LOAD_STALE_BUTTON); + EXPECT_EQ(1, load_stale_count()); + EXPECT_EQ(GURL(kFailedUrl), load_stale_url()); +} + +} // namespace +} // namespace error_page |