1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/renderer/net/net_error_helper_core.h"
6
7 #include <stddef.h>
8
9 #include <memory>
10 #include <set>
11 #include <string>
12 #include <utility>
13 #include <vector>
14
15 #include "base/bind.h"
16 #include "base/callback.h"
17 #include "base/command_line.h"
18 #include "base/feature_list.h"
19 #include "base/i18n/rtl.h"
20 #include "base/json/json_reader.h"
21 #include "base/json/json_value_converter.h"
22 #include "base/json/json_writer.h"
23 #include "base/location.h"
24 #include "base/logging.h"
25 #include "base/metrics/histogram_functions.h"
26 #include "base/metrics/histogram_macros.h"
27 #include "base/stl_util.h"
28 #include "base/strings/string16.h"
29 #include "base/strings/string_util.h"
30 #include "base/values.h"
31 #include "build/build_config.h"
32 #include "chrome/common/chrome_features.h"
33 #include "components/error_page/common/error_page_params.h"
34 #include "components/error_page/common/localized_error.h"
35 #include "components/strings/grit/components_strings.h"
36 #include "components/url_formatter/url_formatter.h"
37 #include "content/public/common/content_switches.h"
38 #include "content/public/common/url_constants.h"
39 #include "content/public/renderer/render_thread.h"
40 #include "net/base/escape.h"
41 #include "net/base/net_errors.h"
42 #include "third_party/blink/public/platform/web_string.h"
43 #include "ui/base/l10n/l10n_util.h"
44 #include "url/gurl.h"
45 #include "url/url_constants.h"
46
47 namespace {
48
49 // |NetErrorNavigationCorrectionTypes| enum id for Web search query.
50 // Other correction types uses the |kCorrectionResourceTable| array order.
51 const int kWebSearchQueryUMAId = 100;
52
53 // Number of URL correction suggestions to display.
54 const int kMaxUrlCorrectionsToDisplay = 1;
55
56 struct CorrectionTypeToResourceTable {
57 int resource_id;
58 const char* correction_type;
59 };
60
61 // Note: Ordering should be the same as |NetErrorNavigationCorrectionTypes| enum
62 // in histograms.xml.
63 const CorrectionTypeToResourceTable kCorrectionResourceTable[] = {
64 {IDS_ERRORPAGES_SUGGESTION_VISIT_GOOGLE_CACHE, "cachedPage"},
65 // "reloadPage" is has special handling.
66 {IDS_ERRORPAGES_SUGGESTION_CORRECTED_URL, "urlCorrection"},
67 {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "siteDomain"},
68 {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "host"},
69 {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "sitemap"},
70 {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "pathParentFolder"},
71 // "siteSearchQuery" is not yet supported.
72 // TODO(mmenke): Figure out what format "siteSearchQuery" uses for its
73 // suggestions.
74 // "webSearchQuery" has special handling.
75 {IDS_ERRORPAGES_SUGGESTION_ALTERNATE_URL, "contentOverlap"},
76 {IDS_ERRORPAGES_SUGGESTION_CORRECTED_URL, "emphasizedUrlCorrection"},
77 };
78
79 struct NavigationCorrection {
NavigationCorrection__anond57e67b40111::NavigationCorrection80 NavigationCorrection() : is_porn(false), is_soft_porn(false) {}
81
RegisterJSONConverter__anond57e67b40111::NavigationCorrection82 static void RegisterJSONConverter(
83 base::JSONValueConverter<NavigationCorrection>* converter) {
84 converter->RegisterStringField("correctionType",
85 &NavigationCorrection::correction_type);
86 converter->RegisterStringField("urlCorrection",
87 &NavigationCorrection::url_correction);
88 converter->RegisterStringField("clickType",
89 &NavigationCorrection::click_type);
90 converter->RegisterStringField("clickData",
91 &NavigationCorrection::click_data);
92 converter->RegisterBoolField("isPorn", &NavigationCorrection::is_porn);
93 converter->RegisterBoolField("isSoftPorn",
94 &NavigationCorrection::is_soft_porn);
95 }
96
97 std::string correction_type;
98 std::string url_correction;
99 std::string click_type;
100 std::string click_data;
101 bool is_porn;
102 bool is_soft_porn;
103 };
104
105 struct NavigationCorrectionResponse {
106 std::string event_id;
107 std::string fingerprint;
108 std::vector<std::unique_ptr<NavigationCorrection>> corrections;
109
RegisterJSONConverter__anond57e67b40111::NavigationCorrectionResponse110 static void RegisterJSONConverter(
111 base::JSONValueConverter<NavigationCorrectionResponse>* converter) {
112 converter->RegisterStringField("result.eventId",
113 &NavigationCorrectionResponse::event_id);
114 converter->RegisterStringField("result.fingerprint",
115 &NavigationCorrectionResponse::fingerprint);
116 converter->RegisterRepeatedMessage(
117 "result.UrlCorrections", &NavigationCorrectionResponse::corrections);
118 }
119 };
120
GetAutoReloadTime(size_t reload_count)121 base::TimeDelta GetAutoReloadTime(size_t reload_count) {
122 static const int kDelaysMs[] = {0, 5000, 30000, 60000,
123 300000, 600000, 1800000};
124 if (reload_count >= base::size(kDelaysMs))
125 reload_count = base::size(kDelaysMs) - 1;
126 return base::TimeDelta::FromMilliseconds(kDelaysMs[reload_count]);
127 }
128
129 // Returns whether |error| is a DNS-related error (and therefore whether
130 // the tab helper should start a DNS probe after receiving it).
IsNetDnsError(const error_page::Error & error)131 bool IsNetDnsError(const error_page::Error& error) {
132 return error.domain() == error_page::Error::kNetErrorDomain &&
133 net::IsHostnameResolutionError(error.reason());
134 }
135
SanitizeURL(const GURL & url)136 GURL SanitizeURL(const GURL& url) {
137 GURL::Replacements remove_params;
138 remove_params.ClearUsername();
139 remove_params.ClearPassword();
140 remove_params.ClearQuery();
141 remove_params.ClearRef();
142 return url.ReplaceComponents(remove_params);
143 }
144
145 // Sanitizes and formats a URL for upload to the error correction service.
PrepareUrlForUpload(const GURL & url)146 std::string PrepareUrlForUpload(const GURL& url) {
147 // TODO(yuusuke): Change to url_formatter::FormatUrl when Link Doctor becomes
148 // unicode-capable.
149 std::string spec_to_send = SanitizeURL(url).spec();
150
151 // Notify navigation correction service of the url truncation by sending of
152 // "?" at the end.
153 if (url.has_query())
154 spec_to_send.append("?");
155 return spec_to_send;
156 }
157
158 // Given an Error, returns true if the FixURL service should be used
159 // for that error. Also sets |error_param| to the string that should be sent to
160 // the FixURL service to identify the error type.
ShouldUseFixUrlServiceForError(const error_page::Error & error,std::string * error_param)161 bool ShouldUseFixUrlServiceForError(const error_page::Error& error,
162 std::string* error_param) {
163 error_param->clear();
164
165 // Don't use the correction service for HTTPS (for privacy reasons).
166 GURL unreachable_url(error.url());
167 if (GURL(unreachable_url).SchemeIsCryptographic())
168 return false;
169
170 const auto& domain = error.domain();
171 if (domain == error_page::Error::kHttpErrorDomain && error.reason() == 404) {
172 *error_param = "http404";
173 return true;
174 }
175 // Don't use the link doctor for secure DNS network errors, since the
176 // additional navigation may interfere with the captive portal probe state.
177 if (IsNetDnsError(error) &&
178 !error.resolve_error_info().is_secure_network_error) {
179 *error_param = "dnserror";
180 return true;
181 }
182 if (domain == error_page::Error::kNetErrorDomain &&
183 (error.reason() == net::ERR_CONNECTION_FAILED ||
184 error.reason() == net::ERR_CONNECTION_REFUSED ||
185 error.reason() == net::ERR_ADDRESS_UNREACHABLE ||
186 error.reason() == net::ERR_CONNECTION_TIMED_OUT)) {
187 *error_param = "connectionFailure";
188 return true;
189 }
190 return false;
191 }
192
193 // Creates a request body for use with the fixurl service. Sets parameters
194 // shared by all types of requests to the service. |correction_params| must
195 // contain the parameters specific to the actual request type.
CreateRequestBody(const std::string & method,const std::string & error_param,const NetErrorHelperCore::NavigationCorrectionParams & correction_params,std::unique_ptr<base::DictionaryValue> params_dict)196 std::string CreateRequestBody(
197 const std::string& method,
198 const std::string& error_param,
199 const NetErrorHelperCore::NavigationCorrectionParams& correction_params,
200 std::unique_ptr<base::DictionaryValue> params_dict) {
201 // Set params common to all request types.
202 params_dict->SetString("key", correction_params.api_key);
203 params_dict->SetString("clientName", "chrome");
204 params_dict->SetString("error", error_param);
205
206 if (!correction_params.language.empty())
207 params_dict->SetString("language", correction_params.language);
208
209 if (!correction_params.country_code.empty())
210 params_dict->SetString("originCountry", correction_params.country_code);
211
212 base::DictionaryValue request_dict;
213 request_dict.SetString("method", method);
214 request_dict.SetString("apiVersion", "v1");
215 request_dict.Set("params", std::move(params_dict));
216
217 std::string request_body;
218 bool success = base::JSONWriter::Write(request_dict, &request_body);
219 DCHECK(success);
220 return request_body;
221 }
222
223 // If URL correction information should be retrieved remotely for a main frame
224 // load that failed with |error|, returns true and sets
225 // |correction_request_body| to be the body for the correction request.
CreateFixUrlRequestBody(const error_page::Error & error,const NetErrorHelperCore::NavigationCorrectionParams & correction_params)226 std::string CreateFixUrlRequestBody(
227 const error_page::Error& error,
228 const NetErrorHelperCore::NavigationCorrectionParams& correction_params) {
229 std::string error_param;
230 bool result = ShouldUseFixUrlServiceForError(error, &error_param);
231 DCHECK(result);
232
233 // TODO(mmenke): Investigate open sourcing the relevant protocol buffers and
234 // using those directly instead.
235 std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue());
236 params->SetString("urlQuery", PrepareUrlForUpload(error.url()));
237 return CreateRequestBody("linkdoctor.fixurl.fixurl", error_param,
238 correction_params, std::move(params));
239 }
240
CreateClickTrackingUrlRequestBody(const error_page::Error & error,const NetErrorHelperCore::NavigationCorrectionParams & correction_params,const NavigationCorrectionResponse & response,const NavigationCorrection & correction)241 std::string CreateClickTrackingUrlRequestBody(
242 const error_page::Error& error,
243 const NetErrorHelperCore::NavigationCorrectionParams& correction_params,
244 const NavigationCorrectionResponse& response,
245 const NavigationCorrection& correction) {
246 std::string error_param;
247 bool result = ShouldUseFixUrlServiceForError(error, &error_param);
248 DCHECK(result);
249
250 std::unique_ptr<base::DictionaryValue> params(new base::DictionaryValue());
251
252 params->SetString("originalUrlQuery", PrepareUrlForUpload(error.url()));
253
254 params->SetString("clickedUrlCorrection", correction.url_correction);
255 params->SetString("clickType", correction.click_type);
256 params->SetString("clickData", correction.click_data);
257
258 params->SetString("eventId", response.event_id);
259 params->SetString("fingerprint", response.fingerprint);
260
261 return CreateRequestBody("linkdoctor.fixurl.clicktracking", error_param,
262 correction_params, std::move(params));
263 }
264
FormatURLForDisplay(const GURL & url,bool is_rtl)265 base::string16 FormatURLForDisplay(const GURL& url, bool is_rtl) {
266 // Translate punycode into UTF8, unescape UTF8 URLs.
267 base::string16 url_for_display(url_formatter::FormatUrl(
268 url, url_formatter::kFormatUrlOmitNothing, net::UnescapeRule::NORMAL,
269 nullptr, nullptr, nullptr));
270 // URLs are always LTR.
271 if (is_rtl)
272 base::i18n::WrapStringWithLTRFormatting(&url_for_display);
273 return url_for_display;
274 }
275
ParseNavigationCorrectionResponse(const std::string raw_response)276 std::unique_ptr<NavigationCorrectionResponse> ParseNavigationCorrectionResponse(
277 const std::string raw_response) {
278 // TODO(mmenke): Open source related protocol buffers and use them directly.
279 std::unique_ptr<base::Value> parsed =
280 base::JSONReader::ReadDeprecated(raw_response);
281 std::unique_ptr<NavigationCorrectionResponse> response(
282 new NavigationCorrectionResponse());
283 base::JSONValueConverter<NavigationCorrectionResponse> converter;
284 if (!parsed || !converter.Convert(*parsed, response.get()))
285 response.reset();
286 return response;
287 }
288
LogCorrectionTypeShown(int type_id)289 void LogCorrectionTypeShown(int type_id) {
290 UMA_HISTOGRAM_ENUMERATION(
291 "Net.ErrorPageCounts.NavigationCorrectionLinksShown", type_id,
292 kWebSearchQueryUMAId + 1);
293 }
294
CreateErrorPageParams(const NavigationCorrectionResponse & response,const error_page::Error & error,const NetErrorHelperCore::NavigationCorrectionParams & correction_params,bool is_rtl)295 std::unique_ptr<error_page::ErrorPageParams> CreateErrorPageParams(
296 const NavigationCorrectionResponse& response,
297 const error_page::Error& error,
298 const NetErrorHelperCore::NavigationCorrectionParams& correction_params,
299 bool is_rtl) {
300 // Version of URL for display in suggestions. It has to be sanitized first
301 // because any received suggestions will be relative to the sanitized URL.
302 base::string16 original_url_for_display =
303 FormatURLForDisplay(SanitizeURL(GURL(error.url())), is_rtl);
304
305 std::unique_ptr<error_page::ErrorPageParams> params(
306 new error_page::ErrorPageParams());
307 params->override_suggestions.reset(new base::ListValue());
308 std::unique_ptr<base::ListValue> parsed_corrections(new base::ListValue());
309 for (auto it = response.corrections.begin(); it != response.corrections.end();
310 ++it) {
311 // Doesn't seem like a good idea to show these.
312 if ((*it)->is_porn || (*it)->is_soft_porn)
313 continue;
314
315 int tracking_id = it - response.corrections.begin();
316
317 if ((*it)->correction_type == "reloadPage") {
318 params->suggest_reload = true;
319 params->reload_tracking_id = tracking_id;
320 continue;
321 }
322
323 if ((*it)->correction_type == "webSearchQuery") {
324 // If there are multiple searches suggested, use the first suggestion.
325 if (params->search_terms.empty()) {
326 params->search_url = correction_params.search_url;
327 params->search_terms = (*it)->url_correction;
328 params->search_tracking_id = tracking_id;
329 LogCorrectionTypeShown(kWebSearchQueryUMAId);
330 }
331 continue;
332 }
333
334 // Allow reload page and web search query to be empty strings, but not
335 // links.
336 if ((*it)->url_correction.empty() ||
337 (params->override_suggestions->GetSize() >=
338 kMaxUrlCorrectionsToDisplay)) {
339 continue;
340 }
341
342 size_t correction_index;
343 for (correction_index = 0;
344 correction_index < base::size(kCorrectionResourceTable);
345 ++correction_index) {
346 if ((*it)->correction_type !=
347 kCorrectionResourceTable[correction_index].correction_type) {
348 continue;
349 }
350 std::unique_ptr<base::DictionaryValue> suggest(
351 new base::DictionaryValue());
352 suggest->SetString(
353 "summary",
354 l10n_util::GetStringUTF16(
355 kCorrectionResourceTable[correction_index].resource_id));
356 suggest->SetString("urlCorrection", (*it)->url_correction);
357 suggest->SetString(
358 "urlCorrectionForDisplay",
359 FormatURLForDisplay(GURL((*it)->url_correction), is_rtl));
360 suggest->SetString("originalUrlForDisplay", original_url_for_display);
361 suggest->SetInteger("trackingId", tracking_id);
362 suggest->SetInteger("type", static_cast<int>(correction_index));
363
364 params->override_suggestions->Append(std::move(suggest));
365 LogCorrectionTypeShown(static_cast<int>(correction_index));
366 break;
367 }
368 }
369
370 if (params->override_suggestions->empty() && !params->search_url.is_valid())
371 params.reset();
372 return params;
373 }
374
375 // Tracks navigation correction service usage in UMA to enable more in depth
376 // analysis.
TrackClickUMA(std::string type_id)377 void TrackClickUMA(std::string type_id) {
378 // Web search suggestion isn't in |kCorrectionResourceTable| array.
379 if (type_id == "webSearchQuery") {
380 UMA_HISTOGRAM_ENUMERATION(
381 "Net.ErrorPageCounts.NavigationCorrectionLinksUsed",
382 kWebSearchQueryUMAId, kWebSearchQueryUMAId + 1);
383 return;
384 }
385
386 size_t correction_index;
387 for (correction_index = 0;
388 correction_index < base::size(kCorrectionResourceTable);
389 ++correction_index) {
390 if (kCorrectionResourceTable[correction_index].correction_type == type_id) {
391 UMA_HISTOGRAM_ENUMERATION(
392 "Net.ErrorPageCounts.NavigationCorrectionLinksUsed",
393 static_cast<int>(correction_index), kWebSearchQueryUMAId + 1);
394 break;
395 }
396 }
397 }
398
399 } // namespace
400
401 struct NetErrorHelperCore::ErrorPageInfo {
ErrorPageInfoNetErrorHelperCore::ErrorPageInfo402 ErrorPageInfo(error_page::Error error, bool was_failed_post)
403 : error(error),
404 was_failed_post(was_failed_post),
405 needs_dns_updates(false),
406 needs_load_navigation_corrections(false),
407 is_finished_loading(false),
408 auto_reload_triggered(false) {}
409
410 // Information about the failed page load.
411 error_page::Error error;
412 bool was_failed_post;
413
414 // Information about the status of the error page.
415
416 // True if a page is a DNS error page and has not yet received a final DNS
417 // probe status.
418 bool needs_dns_updates;
419 bool dns_probe_complete = false;
420
421 // True if a blank page was loaded, and navigation corrections need to be
422 // loaded to generate the real error page.
423 bool needs_load_navigation_corrections;
424
425 // Navigation correction service paramers, which will be used in response to
426 // certain types of network errors. They are all stored here in case they
427 // change over the course of displaying the error page.
428 std::unique_ptr<NetErrorHelperCore::NavigationCorrectionParams>
429 navigation_correction_params;
430
431 std::unique_ptr<NavigationCorrectionResponse> navigation_correction_response;
432
433 // All the navigation corrections that have been clicked, for tracking
434 // purposes.
435 std::set<int> clicked_corrections;
436
437 // True if a page has completed loading, at which point it can receive
438 // updates.
439 bool is_finished_loading;
440
441 // True if the auto-reload timer has fired and a reload is or has been in
442 // flight.
443 bool auto_reload_triggered;
444
445 error_page::LocalizedError::PageState page_state;
446 };
447
NavigationCorrectionParams()448 NetErrorHelperCore::NavigationCorrectionParams::NavigationCorrectionParams() {}
449
450 NetErrorHelperCore::NavigationCorrectionParams::NavigationCorrectionParams(
451 const NavigationCorrectionParams& other) = default;
452
~NavigationCorrectionParams()453 NetErrorHelperCore::NavigationCorrectionParams::~NavigationCorrectionParams() {}
454
IsReloadableError(const NetErrorHelperCore::ErrorPageInfo & info)455 bool NetErrorHelperCore::IsReloadableError(
456 const NetErrorHelperCore::ErrorPageInfo& info) {
457 GURL url = info.error.url();
458 return info.error.domain() == error_page::Error::kNetErrorDomain &&
459 info.error.reason() != net::ERR_ABORTED &&
460 // For now, net::ERR_UNKNOWN_URL_SCHEME is only being displayed on
461 // Chrome for Android.
462 info.error.reason() != net::ERR_UNKNOWN_URL_SCHEME &&
463 // Do not trigger if the server rejects a client certificate.
464 // https://crbug.com/431387
465 !net::IsClientCertificateError(info.error.reason()) &&
466 // Some servers reject client certificates with a generic
467 // handshake_failure alert.
468 // https://crbug.com/431387
469 info.error.reason() != net::ERR_SSL_PROTOCOL_ERROR &&
470 // Do not trigger for blacklisted URLs.
471 // https://crbug.com/803839
472 info.error.reason() != net::ERR_BLOCKED_BY_ADMINISTRATOR &&
473 // Do not trigger for requests that were blocked by the browser itself.
474 info.error.reason() != net::ERR_BLOCKED_BY_CLIENT &&
475 !info.was_failed_post &&
476 // Do not trigger for this error code because it is used by Chrome
477 // while an auth prompt is being displayed.
478 info.error.reason() != net::ERR_INVALID_AUTH_CREDENTIALS &&
479 // Don't auto-reload non-http/https schemas.
480 // https://crbug.com/471713
481 url.SchemeIsHTTPOrHTTPS() &&
482 // Don't auto reload if the error was a secure DNS network error, since
483 // the reload may interfere with the captive portal probe state.
484 // TODO(crbug.com/1016164): Explore how to allow reloads for secure DNS
485 // network errors without interfering with the captive portal probe
486 // state.
487 !info.error.resolve_error_info().is_secure_network_error;
488 }
489
NetErrorHelperCore(Delegate * delegate,bool auto_reload_enabled,bool is_visible)490 NetErrorHelperCore::NetErrorHelperCore(Delegate* delegate,
491 bool auto_reload_enabled,
492 bool is_visible)
493 : delegate_(delegate),
494 last_probe_status_(error_page::DNS_PROBE_POSSIBLE),
495 can_show_network_diagnostics_dialog_(false),
496 auto_reload_enabled_(auto_reload_enabled),
497 auto_reload_timer_(new base::OneShotTimer()),
498 auto_reload_paused_(false),
499 auto_reload_in_flight_(false),
500 uncommitted_load_started_(false),
501 online_(content::RenderThread::Get()->IsOnline()),
502 visible_(is_visible),
503 auto_reload_count_(0),
504 navigation_from_button_(NO_BUTTON)
505 #if defined(OS_ANDROID)
506 ,
507 page_auto_fetcher_helper_(
508 std::make_unique<PageAutoFetcherHelper>(delegate->GetRenderFrame()))
509 #endif
510 {
511 }
512
513 NetErrorHelperCore::~NetErrorHelperCore() = default;
514
CancelPendingFetches()515 void NetErrorHelperCore::CancelPendingFetches() {
516 // Cancel loading the alternate error page, and prevent any pending error page
517 // load from starting a new error page load. Swapping in the error page when
518 // it's finished loading could abort the navigation, otherwise.
519 if (committed_error_page_info_)
520 committed_error_page_info_->needs_load_navigation_corrections = false;
521 if (pending_error_page_info_)
522 pending_error_page_info_->needs_load_navigation_corrections = false;
523 delegate_->CancelFetchNavigationCorrections();
524 auto_reload_timer_->Stop();
525 auto_reload_paused_ = false;
526 }
527
OnStop()528 void NetErrorHelperCore::OnStop() {
529 CancelPendingFetches();
530 uncommitted_load_started_ = false;
531 auto_reload_count_ = 0;
532 auto_reload_in_flight_ = false;
533 }
534
OnWasShown()535 void NetErrorHelperCore::OnWasShown() {
536 visible_ = true;
537 if (auto_reload_paused_)
538 MaybeStartAutoReloadTimer();
539 }
540
OnWasHidden()541 void NetErrorHelperCore::OnWasHidden() {
542 visible_ = false;
543 PauseAutoReloadTimer();
544 }
545
OnStartLoad(FrameType frame_type,PageType page_type)546 void NetErrorHelperCore::OnStartLoad(FrameType frame_type, PageType page_type) {
547 if (frame_type != MAIN_FRAME)
548 return;
549
550 uncommitted_load_started_ = true;
551
552 // If there's no pending error page information associated with the page load,
553 // or the new page is not an error page, then reset pending error page state.
554 if (!pending_error_page_info_ || page_type != ERROR_PAGE) {
555 CancelPendingFetches();
556 } else {
557 // Halt auto-reload if it's currently scheduled. OnFinishLoad will trigger
558 // auto-reload if appropriate.
559 PauseAutoReloadTimer();
560 }
561 }
562
OnCommitLoad(FrameType frame_type,const GURL & url)563 void NetErrorHelperCore::OnCommitLoad(FrameType frame_type, const GURL& url) {
564 if (frame_type != MAIN_FRAME)
565 return;
566
567 // If a page is committing, either it's an error page and autoreload will be
568 // started again below, or it's a success page and we need to clear autoreload
569 // state.
570 auto_reload_in_flight_ = false;
571
572 // uncommitted_load_started_ could already be false, since RenderFrameImpl
573 // calls OnCommitLoad once for each in-page navigation (like a fragment
574 // change) with no corresponding OnStartLoad.
575 uncommitted_load_started_ = false;
576
577 #if defined(OS_ANDROID)
578 // Don't need this state. It will be refreshed if another error page is
579 // loaded.
580 available_content_helper_.Reset();
581 page_auto_fetcher_helper_->OnCommitLoad();
582 #endif
583
584 // Track if an error occurred due to a page button press.
585 // This isn't perfect; if (for instance), the server is slow responding
586 // to a request generated from the page reload button, and the user hits
587 // the browser reload button, this code will still believe the
588 // result is from the page reload button.
589 if (committed_error_page_info_ && pending_error_page_info_ &&
590 navigation_from_button_ != NO_BUTTON &&
591 committed_error_page_info_->error.url() ==
592 pending_error_page_info_->error.url()) {
593 DCHECK(navigation_from_button_ == RELOAD_BUTTON);
594 RecordEvent(error_page::NETWORK_ERROR_PAGE_RELOAD_BUTTON_ERROR);
595 }
596 navigation_from_button_ = NO_BUTTON;
597
598 committed_error_page_info_ = std::move(pending_error_page_info_);
599 }
600
ErrorPageLoadedWithFinalErrorCode()601 void NetErrorHelperCore::ErrorPageLoadedWithFinalErrorCode() {
602 ErrorPageInfo* page_info = committed_error_page_info_.get();
603 DCHECK(page_info);
604 error_page::Error updated_error = GetUpdatedError(*page_info);
605
606 if (page_info->page_state.is_offline_error)
607 RecordEvent(error_page::NETWORK_ERROR_PAGE_OFFLINE_ERROR_SHOWN);
608
609 #if defined(OS_ANDROID)
610 // The fetch functions shouldn't be triggered multiple times per page load.
611 if (page_info->page_state.offline_content_feature_enabled) {
612 available_content_helper_.FetchAvailableContent(base::BindOnce(
613 &Delegate::OfflineContentAvailable, base::Unretained(delegate_)));
614 }
615
616 // |TrySchedule()| shouldn't be called more than once per page.
617 if (page_info->page_state.auto_fetch_allowed) {
618 page_auto_fetcher_helper_->TrySchedule(
619 false, base::BindOnce(&Delegate::SetAutoFetchState,
620 base::Unretained(delegate_)));
621 }
622 #endif // defined(OS_ANDROID)
623
624 if (page_info->page_state.download_button_shown)
625 RecordEvent(error_page::NETWORK_ERROR_PAGE_DOWNLOAD_BUTTON_SHOWN);
626
627 if (page_info->page_state.reload_button_shown)
628 RecordEvent(error_page::NETWORK_ERROR_PAGE_RELOAD_BUTTON_SHOWN);
629
630 delegate_->SetIsShowingDownloadButton(
631 page_info->page_state.download_button_shown);
632 }
633
OnFinishLoad(FrameType frame_type)634 void NetErrorHelperCore::OnFinishLoad(FrameType frame_type) {
635 if (frame_type != MAIN_FRAME)
636 return;
637
638 if (!committed_error_page_info_) {
639 auto_reload_count_ = 0;
640 return;
641 }
642 committed_error_page_info_->is_finished_loading = true;
643
644 RecordEvent(error_page::NETWORK_ERROR_PAGE_SHOWN);
645 if (committed_error_page_info_->page_state.show_cached_copy_button_shown) {
646 RecordEvent(error_page::NETWORK_ERROR_PAGE_CACHED_COPY_BUTTON_SHOWN);
647 }
648
649 delegate_->SetIsShowingDownloadButton(
650 committed_error_page_info_->page_state.download_button_shown);
651
652 delegate_->EnablePageHelperFunctions();
653
654 if (committed_error_page_info_->needs_load_navigation_corrections) {
655 // If there is another pending error page load, |fix_url| should have been
656 // cleared.
657 DCHECK(!pending_error_page_info_);
658 DCHECK(!committed_error_page_info_->needs_dns_updates);
659 delegate_->FetchNavigationCorrections(
660 committed_error_page_info_->navigation_correction_params->url,
661 CreateFixUrlRequestBody(
662 committed_error_page_info_->error,
663 *committed_error_page_info_->navigation_correction_params));
664 } else if (auto_reload_enabled_ &&
665 IsReloadableError(*committed_error_page_info_)) {
666 MaybeStartAutoReloadTimer();
667 }
668
669 DVLOG(1) << "Error page finished loading; sending saved status.";
670 if (committed_error_page_info_->needs_dns_updates) {
671 if (last_probe_status_ != error_page::DNS_PROBE_POSSIBLE)
672 UpdateErrorPage();
673 } else {
674 ErrorPageLoadedWithFinalErrorCode();
675 }
676 }
677
PrepareErrorPage(FrameType frame_type,const error_page::Error & error,bool is_failed_post,std::string * error_html)678 void NetErrorHelperCore::PrepareErrorPage(FrameType frame_type,
679 const error_page::Error& error,
680 bool is_failed_post,
681 std::string* error_html) {
682 if (frame_type == MAIN_FRAME) {
683 // If navigation corrections were needed before, that should have been
684 // cancelled earlier by starting a new page load (Which has now failed).
685 DCHECK(!committed_error_page_info_ ||
686 !committed_error_page_info_->needs_load_navigation_corrections);
687
688 pending_error_page_info_.reset(new ErrorPageInfo(error, is_failed_post));
689 pending_error_page_info_->navigation_correction_params.reset(
690 new NavigationCorrectionParams(navigation_correction_params_));
691 PrepareErrorPageForMainFrame(pending_error_page_info_.get(), error_html);
692 } else {
693 if (error_html) {
694 delegate_->GenerateLocalizedErrorPage(
695 error, is_failed_post,
696 false /* No diagnostics dialogs allowed for subframes. */, nullptr,
697 error_html);
698 }
699 }
700 }
701
OnNetErrorInfo(error_page::DnsProbeStatus status)702 void NetErrorHelperCore::OnNetErrorInfo(error_page::DnsProbeStatus status) {
703 DCHECK_NE(error_page::DNS_PROBE_POSSIBLE, status);
704
705 last_probe_status_ = status;
706
707 if (!committed_error_page_info_ ||
708 !committed_error_page_info_->needs_dns_updates ||
709 !committed_error_page_info_->is_finished_loading) {
710 return;
711 }
712
713 UpdateErrorPage();
714 }
715
OnSetCanShowNetworkDiagnosticsDialog(bool can_show_network_diagnostics_dialog)716 void NetErrorHelperCore::OnSetCanShowNetworkDiagnosticsDialog(
717 bool can_show_network_diagnostics_dialog) {
718 can_show_network_diagnostics_dialog_ = can_show_network_diagnostics_dialog;
719 }
720
OnSetNavigationCorrectionInfo(const GURL & navigation_correction_url,const std::string & language,const std::string & country_code,const std::string & api_key,const GURL & search_url)721 void NetErrorHelperCore::OnSetNavigationCorrectionInfo(
722 const GURL& navigation_correction_url,
723 const std::string& language,
724 const std::string& country_code,
725 const std::string& api_key,
726 const GURL& search_url) {
727 navigation_correction_params_.url = navigation_correction_url;
728 navigation_correction_params_.language = language;
729 navigation_correction_params_.country_code = country_code;
730 navigation_correction_params_.api_key = api_key;
731 navigation_correction_params_.search_url = search_url;
732 }
733
OnEasterEggHighScoreReceived(int high_score)734 void NetErrorHelperCore::OnEasterEggHighScoreReceived(int high_score) {
735 if (!committed_error_page_info_ ||
736 !committed_error_page_info_->is_finished_loading) {
737 return;
738 }
739
740 delegate_->InitializeErrorPageEasterEggHighScore(high_score);
741 }
742
PrepareErrorPageForMainFrame(ErrorPageInfo * pending_error_page_info,std::string * error_html)743 void NetErrorHelperCore::PrepareErrorPageForMainFrame(
744 ErrorPageInfo* pending_error_page_info,
745 std::string* error_html) {
746 std::string error_param;
747 error_page::Error error = pending_error_page_info->error;
748
749 if (pending_error_page_info->navigation_correction_params &&
750 pending_error_page_info->navigation_correction_params->url.is_valid() &&
751 ShouldUseFixUrlServiceForError(error, &error_param)) {
752 pending_error_page_info->needs_load_navigation_corrections = true;
753 return;
754 }
755
756 if (IsNetDnsError(pending_error_page_info->error)) {
757 // The last probe status needs to be reset if this is a DNS error. This
758 // means that if a DNS error page is committed but has not yet finished
759 // loading, a DNS probe status scheduled to be sent to it may be thrown
760 // out, but since the new error page should trigger a new DNS probe, it
761 // will just get the results for the next page load.
762 last_probe_status_ = error_page::DNS_PROBE_POSSIBLE;
763 pending_error_page_info->needs_dns_updates = true;
764 error = GetUpdatedError(*pending_error_page_info);
765 }
766 if (error_html) {
767 pending_error_page_info->page_state = delegate_->GenerateLocalizedErrorPage(
768 error, pending_error_page_info->was_failed_post,
769 can_show_network_diagnostics_dialog_, nullptr, error_html);
770 }
771 }
772
UpdateErrorPage()773 void NetErrorHelperCore::UpdateErrorPage() {
774 DCHECK(committed_error_page_info_->needs_dns_updates);
775 DCHECK(committed_error_page_info_->is_finished_loading);
776 DCHECK_NE(error_page::DNS_PROBE_POSSIBLE, last_probe_status_);
777
778 UMA_HISTOGRAM_ENUMERATION("DnsProbe.ErrorPageUpdateStatus",
779 last_probe_status_, error_page::DNS_PROBE_MAX);
780 // Every status other than error_page::DNS_PROBE_POSSIBLE and
781 // error_page::DNS_PROBE_STARTED is a final status code. Once one is reached,
782 // the page does not need further updates.
783 if (last_probe_status_ != error_page::DNS_PROBE_STARTED) {
784 committed_error_page_info_->needs_dns_updates = false;
785 committed_error_page_info_->dns_probe_complete = true;
786 }
787
788 error_page::LocalizedError::PageState new_state =
789 delegate_->UpdateErrorPage(GetUpdatedError(*committed_error_page_info_),
790 committed_error_page_info_->was_failed_post,
791 can_show_network_diagnostics_dialog_);
792
793 // This button can't be changed by a DNS error update, so there's no code
794 // to update the related UMA in ErrorPageLoadedWithFinalErrorCode(). Instead,
795 // verify there's no change in this button's state.
796 DCHECK_EQ(
797 committed_error_page_info_->page_state.show_cached_copy_button_shown,
798 new_state.show_cached_copy_button_shown);
799
800 committed_error_page_info_->page_state = std::move(new_state);
801 if (!committed_error_page_info_->needs_dns_updates)
802 ErrorPageLoadedWithFinalErrorCode();
803 }
804
OnNavigationCorrectionsFetched(const std::string & corrections,bool is_rtl)805 void NetErrorHelperCore::OnNavigationCorrectionsFetched(
806 const std::string& corrections,
807 bool is_rtl) {
808 // Loading suggestions only starts when a blank error page finishes loading,
809 // and is cancelled with a new load.
810 DCHECK(!pending_error_page_info_);
811 DCHECK(committed_error_page_info_->is_finished_loading);
812 DCHECK(committed_error_page_info_->needs_load_navigation_corrections);
813 DCHECK(committed_error_page_info_->navigation_correction_params);
814
815 pending_error_page_info_.reset(
816 new ErrorPageInfo(committed_error_page_info_->error,
817 committed_error_page_info_->was_failed_post));
818 pending_error_page_info_->navigation_correction_response =
819 ParseNavigationCorrectionResponse(corrections);
820
821 std::string error_html;
822 if (pending_error_page_info_->navigation_correction_response) {
823 // Copy navigation correction parameters used for the request, so tracking
824 // requests can still be sent if the configuration changes.
825 pending_error_page_info_->navigation_correction_params.reset(
826 new NavigationCorrectionParams(
827 *committed_error_page_info_->navigation_correction_params));
828 std::unique_ptr<error_page::ErrorPageParams> params = CreateErrorPageParams(
829 *pending_error_page_info_->navigation_correction_response,
830 pending_error_page_info_->error,
831 *pending_error_page_info_->navigation_correction_params, is_rtl);
832 pending_error_page_info_->page_state =
833 delegate_->GenerateLocalizedErrorPage(
834 pending_error_page_info_->error,
835 pending_error_page_info_->was_failed_post,
836 can_show_network_diagnostics_dialog_, std::move(params),
837 &error_html);
838 } else {
839 // Since |navigation_correction_params| in |pending_error_page_info_| is
840 // NULL, this won't trigger another attempt to load corrections.
841 PrepareErrorPageForMainFrame(pending_error_page_info_.get(), &error_html);
842 }
843
844 // TODO(mmenke): Once the new API is in place, look into replacing this
845 // double page load by just updating the error page, like DNS
846 // probes do.
847 delegate_->LoadErrorPage(error_html, pending_error_page_info_->error.url());
848 }
849
GetUpdatedError(const ErrorPageInfo & error_info) const850 error_page::Error NetErrorHelperCore::GetUpdatedError(
851 const ErrorPageInfo& error_info) const {
852 // If a probe didn't run or wasn't conclusive, restore the original error.
853 const bool dns_probe_used =
854 error_info.needs_dns_updates || error_info.dns_probe_complete;
855 if (!dns_probe_used || last_probe_status_ == error_page::DNS_PROBE_NOT_RUN ||
856 last_probe_status_ == error_page::DNS_PROBE_FINISHED_INCONCLUSIVE) {
857 return error_info.error;
858 }
859
860 return error_page::Error::DnsProbeError(
861 error_info.error.url(), last_probe_status_,
862 error_info.error.stale_copy_in_cache());
863 }
864
Reload()865 void NetErrorHelperCore::Reload() {
866 if (!committed_error_page_info_)
867 return;
868 delegate_->ReloadFrame();
869 }
870
MaybeStartAutoReloadTimer()871 bool NetErrorHelperCore::MaybeStartAutoReloadTimer() {
872 // Automation tools expect to be in control of reloads.
873 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
874 switches::kEnableAutomation)) {
875 return false;
876 }
877
878 if (!committed_error_page_info_ ||
879 !committed_error_page_info_->is_finished_loading ||
880 pending_error_page_info_ || uncommitted_load_started_) {
881 return false;
882 }
883
884 StartAutoReloadTimer();
885 return true;
886 }
887
StartAutoReloadTimer()888 void NetErrorHelperCore::StartAutoReloadTimer() {
889 DCHECK(committed_error_page_info_);
890 DCHECK(IsReloadableError(*committed_error_page_info_));
891
892 committed_error_page_info_->auto_reload_triggered = true;
893
894 if (!online_ || !visible_) {
895 auto_reload_paused_ = true;
896 return;
897 }
898
899 auto_reload_paused_ = false;
900 base::TimeDelta delay = GetAutoReloadTime(auto_reload_count_);
901 auto_reload_timer_->Stop();
902 auto_reload_timer_->Start(
903 FROM_HERE, delay,
904 base::BindOnce(&NetErrorHelperCore::AutoReloadTimerFired,
905 base::Unretained(this)));
906 }
907
AutoReloadTimerFired()908 void NetErrorHelperCore::AutoReloadTimerFired() {
909 // AutoReloadTimerFired only runs if:
910 // 1. StartAutoReloadTimer was previously called, which requires that
911 // committed_error_page_info_ is populated;
912 // 2. No other page load has started since (1), since OnStartLoad stops the
913 // auto-reload timer.
914 DCHECK(committed_error_page_info_);
915
916 auto_reload_count_++;
917 auto_reload_in_flight_ = true;
918 Reload();
919 }
920
PauseAutoReloadTimer()921 void NetErrorHelperCore::PauseAutoReloadTimer() {
922 if (!auto_reload_timer_->IsRunning())
923 return;
924 DCHECK(committed_error_page_info_);
925 DCHECK(!auto_reload_paused_);
926 DCHECK(committed_error_page_info_->auto_reload_triggered);
927 auto_reload_timer_->Stop();
928 auto_reload_paused_ = true;
929 }
930
NetworkStateChanged(bool online)931 void NetErrorHelperCore::NetworkStateChanged(bool online) {
932 bool was_online = online_;
933 online_ = online;
934 if (!was_online && online) {
935 // Transitioning offline -> online
936 if (auto_reload_paused_)
937 MaybeStartAutoReloadTimer();
938 } else if (was_online && !online) {
939 // Transitioning online -> offline
940 if (auto_reload_timer_->IsRunning())
941 auto_reload_count_ = 0;
942 PauseAutoReloadTimer();
943 }
944 }
945
ShouldSuppressErrorPage(FrameType frame_type,const GURL & url)946 bool NetErrorHelperCore::ShouldSuppressErrorPage(FrameType frame_type,
947 const GURL& url) {
948 // Don't suppress child frame errors.
949 if (frame_type != MAIN_FRAME)
950 return false;
951
952 // If there's no auto reload attempt in flight, this error page didn't come
953 // from auto reload, so don't suppress it.
954 if (!auto_reload_in_flight_)
955 return false;
956
957 uncommitted_load_started_ = false;
958 // This serves to terminate the auto-reload in flight attempt. If
959 // ShouldSuppressErrorPage is called, the auto-reload yielded an error, which
960 // means the request was already sent.
961 auto_reload_in_flight_ = false;
962 MaybeStartAutoReloadTimer();
963 return true;
964 }
965
966 #if defined(OS_ANDROID)
SetPageAutoFetcherHelperForTesting(std::unique_ptr<PageAutoFetcherHelper> page_auto_fetcher_helper)967 void NetErrorHelperCore::SetPageAutoFetcherHelperForTesting(
968 std::unique_ptr<PageAutoFetcherHelper> page_auto_fetcher_helper) {
969 page_auto_fetcher_helper_ = std::move(page_auto_fetcher_helper);
970 }
971 #endif
972
ExecuteButtonPress(Button button)973 void NetErrorHelperCore::ExecuteButtonPress(Button button) {
974 // If there's no committed error page, should not be invoked.
975 DCHECK(committed_error_page_info_);
976
977 switch (button) {
978 case RELOAD_BUTTON:
979 RecordEvent(error_page::NETWORK_ERROR_PAGE_RELOAD_BUTTON_CLICKED);
980 navigation_from_button_ = RELOAD_BUTTON;
981 Reload();
982 return;
983 case MORE_BUTTON:
984 // Visual effects on page are handled in Javascript code.
985 RecordEvent(error_page::NETWORK_ERROR_PAGE_MORE_BUTTON_CLICKED);
986 return;
987 case EASTER_EGG:
988 RecordEvent(error_page::NETWORK_ERROR_EASTER_EGG_ACTIVATED);
989 delegate_->RequestEasterEggHighScore();
990 return;
991 case SHOW_CACHED_COPY_BUTTON:
992 RecordEvent(error_page::NETWORK_ERROR_PAGE_CACHED_COPY_BUTTON_CLICKED);
993 return;
994 case DIAGNOSE_ERROR:
995 RecordEvent(error_page::NETWORK_ERROR_DIAGNOSE_BUTTON_CLICKED);
996 delegate_->DiagnoseError(committed_error_page_info_->error.url());
997 return;
998 case DOWNLOAD_BUTTON:
999 RecordEvent(error_page::NETWORK_ERROR_PAGE_DOWNLOAD_BUTTON_CLICKED);
1000 delegate_->DownloadPageLater();
1001 return;
1002 case NO_BUTTON:
1003 NOTREACHED();
1004 return;
1005 }
1006 }
1007
TrackClick(int tracking_id)1008 void NetErrorHelperCore::TrackClick(int tracking_id) {
1009 // It's technically possible for |navigation_correction_params| to be NULL but
1010 // for |navigation_correction_response| not to be NULL, if the paramters
1011 // changed between loading the original error page and loading the error page
1012 if (!committed_error_page_info_ ||
1013 !committed_error_page_info_->navigation_correction_response) {
1014 return;
1015 }
1016
1017 NavigationCorrectionResponse* response =
1018 committed_error_page_info_->navigation_correction_response.get();
1019
1020 // |tracking_id| is less than 0 when the error page was not generated by the
1021 // navigation correction service. |tracking_id| should never be greater than
1022 // the array size, but best to be safe, since it contains data from a remote
1023 // site, though none of that data should make it into Javascript callbacks.
1024 if (tracking_id < 0 ||
1025 static_cast<size_t>(tracking_id) >= response->corrections.size()) {
1026 return;
1027 }
1028
1029 // Only report a clicked link once.
1030 if (committed_error_page_info_->clicked_corrections.count(tracking_id))
1031 return;
1032
1033 TrackClickUMA(response->corrections[tracking_id]->correction_type);
1034
1035 committed_error_page_info_->clicked_corrections.insert(tracking_id);
1036 std::string request_body = CreateClickTrackingUrlRequestBody(
1037 committed_error_page_info_->error,
1038 *committed_error_page_info_->navigation_correction_params, *response,
1039 *response->corrections[tracking_id]);
1040 delegate_->SendTrackingRequest(
1041 committed_error_page_info_->navigation_correction_params->url,
1042 request_body);
1043 }
1044
LaunchOfflineItem(const std::string & id,const std::string & name_space)1045 void NetErrorHelperCore::LaunchOfflineItem(const std::string& id,
1046 const std::string& name_space) {
1047 #if defined(OS_ANDROID)
1048 available_content_helper_.LaunchItem(id, name_space);
1049 #endif
1050 }
1051
LaunchDownloadsPage()1052 void NetErrorHelperCore::LaunchDownloadsPage() {
1053 #if defined(OS_ANDROID)
1054 available_content_helper_.LaunchDownloadsPage();
1055 #endif
1056 }
1057
SavePageForLater()1058 void NetErrorHelperCore::SavePageForLater() {
1059 #if defined(OS_ANDROID)
1060 page_auto_fetcher_helper_->TrySchedule(
1061 /*user_requested=*/true, base::BindOnce(&Delegate::SetAutoFetchState,
1062 base::Unretained(delegate_)));
1063 #endif
1064 }
1065
CancelSavePage()1066 void NetErrorHelperCore::CancelSavePage() {
1067 #if defined(OS_ANDROID)
1068 page_auto_fetcher_helper_->CancelSchedule();
1069 #endif
1070 }
1071
ListVisibilityChanged(bool is_visible)1072 void NetErrorHelperCore::ListVisibilityChanged(bool is_visible) {
1073 #if defined(OS_ANDROID)
1074 available_content_helper_.ListVisibilityChanged(is_visible);
1075 #endif
1076 }
1077