Upstream version 7.35.139.0
[platform/framework/web/crosswalk.git] / src / chrome / renderer / net / net_error_helper_core.cc
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 <string>
8
9 #include "base/bind.h"
10 #include "base/callback.h"
11 #include "base/location.h"
12 #include "base/metrics/histogram.h"
13 #include "chrome/common/localized_error.h"
14 #include "net/base/escape.h"
15 #include "net/base/net_errors.h"
16 #include "third_party/WebKit/public/platform/WebString.h"
17 #include "third_party/WebKit/public/platform/WebURLError.h"
18 #include "url/gurl.h"
19
20 namespace {
21
22 base::TimeDelta GetAutoReloadTime(size_t reload_count) {
23   static const int kDelaysMs[] = {
24     0, 5000, 30000, 60000, 300000, 600000, 1800000
25   };
26   if (reload_count >= arraysize(kDelaysMs))
27     reload_count = arraysize(kDelaysMs) - 1;
28   return base::TimeDelta::FromMilliseconds(kDelaysMs[reload_count]);
29 }
30
31 // Returns whether |net_error| is a DNS-related error (and therefore whether
32 // the tab helper should start a DNS probe after receiving it.)
33 bool IsDnsError(const blink::WebURLError& error) {
34   return error.domain.utf8() == net::kErrorDomain &&
35          (error.reason == net::ERR_NAME_NOT_RESOLVED ||
36           error.reason == net::ERR_NAME_RESOLUTION_FAILED);
37 }
38
39 // If an alternate error page should be retrieved remotely for a main frame load
40 // that failed with |error|, returns true and sets |error_page_url| to the URL
41 // of the remote error page.
42 bool GetErrorPageURL(const blink::WebURLError& error,
43                      const GURL& alt_error_page_url,
44                      GURL* error_page_url) {
45   if (!alt_error_page_url.is_valid())
46     return false;
47
48   // Parameter to send to the error page indicating the error type.
49   std::string error_param;
50
51   std::string domain = error.domain.utf8();
52   if (domain == "http" && error.reason == 404) {
53     error_param = "http404";
54   } else if (IsDnsError(error)) {
55     error_param = "dnserror";
56   } else if (domain == net::kErrorDomain &&
57              (error.reason == net::ERR_CONNECTION_FAILED ||
58               error.reason == net::ERR_CONNECTION_REFUSED ||
59               error.reason == net::ERR_ADDRESS_UNREACHABLE ||
60               error.reason == net::ERR_CONNECTION_TIMED_OUT)) {
61     error_param = "connectionfailure";
62   } else {
63     return false;
64   }
65
66   // Don't use the Link Doctor for HTTPS (for privacy reasons).
67   GURL unreachable_url(error.unreachableURL);
68   if (unreachable_url.SchemeIsSecure())
69     return false;
70
71   // Sanitize the unreachable URL.
72   GURL::Replacements remove_params;
73   remove_params.ClearUsername();
74   remove_params.ClearPassword();
75   remove_params.ClearQuery();
76   remove_params.ClearRef();
77   // TODO(yuusuke): change to net::FormatUrl when Link Doctor becomes
78   // unicode-capable.
79   std::string spec_to_send =
80       unreachable_url.ReplaceComponents(remove_params).spec();
81
82   // Notify Link Doctor of the url truncation by sending of "?" at the end.
83   if (unreachable_url.has_query())
84     spec_to_send.append("?");
85
86   std::string params(alt_error_page_url.query());
87   params.append("&url=");
88   params.append(net::EscapeQueryParamValue(spec_to_send, true));
89   params.append("&sourceid=chrome");
90   params.append("&error=");
91   params.append(error_param);
92
93   // Build the final url to request.
94   GURL::Replacements link_doctor_params;
95   link_doctor_params.SetQueryStr(params);
96   *error_page_url = alt_error_page_url.ReplaceComponents(link_doctor_params);
97   return true;
98 }
99
100 }  // namespace
101
102 struct NetErrorHelperCore::ErrorPageInfo {
103   ErrorPageInfo(blink::WebURLError error, bool was_failed_post)
104       : error(error),
105         was_failed_post(was_failed_post),
106         needs_dns_updates(false),
107         is_finished_loading(false) {
108   }
109
110   // Information about the failed page load.
111   blink::WebURLError error;
112   bool was_failed_post;
113
114   // Information about the status of the error page.
115
116   // True if a page is a DNS error page and has not yet received a final DNS
117   // probe status.
118   bool needs_dns_updates;
119
120   // URL of an alternate error page to repace this error page with, if it's a
121   // valid URL.  Request will be issued when the error page finishes loading.
122   // This is done on load complete to ensure that there are two complete loads
123   // for tests to wait for.
124   GURL alternate_error_page_url;
125
126   // True if a page has completed loading, at which point it can receive
127   // updates.
128   bool is_finished_loading;
129 };
130
131 bool NetErrorHelperCore::IsReloadableError(
132     const NetErrorHelperCore::ErrorPageInfo& info) {
133   return info.error.domain.utf8() == net::kErrorDomain &&
134          info.error.reason != net::ERR_ABORTED &&
135          !info.was_failed_post;
136 }
137
138 NetErrorHelperCore::NetErrorHelperCore(Delegate* delegate)
139     : delegate_(delegate),
140       last_probe_status_(chrome_common_net::DNS_PROBE_POSSIBLE),
141       auto_reload_enabled_(false),
142       auto_reload_timer_(new base::Timer(false, false)),
143       // TODO(ellyjones): Make online_ accurate at object creation.
144       online_(true),
145       auto_reload_count_(0),
146       can_auto_reload_page_(false) {
147 }
148
149 NetErrorHelperCore::~NetErrorHelperCore() {
150   if (committed_error_page_info_ && can_auto_reload_page_) {
151     UMA_HISTOGRAM_CUSTOM_ENUMERATION("Net.AutoReload.ErrorAtStop",
152                                      -committed_error_page_info_->error.reason,
153                                      net::GetAllErrorCodesForUma());
154     UMA_HISTOGRAM_COUNTS("Net.AutoReload.CountAtStop", auto_reload_count_);
155   }
156 }
157
158 void NetErrorHelperCore::CancelPendingFetches() {
159   // Cancel loading the alternate error page, and prevent any pending error page
160   // load from starting a new error page load.  Swapping in the error page when
161   // it's finished loading could abort the navigation, otherwise.
162   if (committed_error_page_info_ && can_auto_reload_page_) {
163     UMA_HISTOGRAM_CUSTOM_ENUMERATION("Net.AutoReload.ErrorAtStop",
164                                      -committed_error_page_info_->error.reason,
165                                      net::GetAllErrorCodesForUma());
166     UMA_HISTOGRAM_COUNTS("Net.AutoReload.CountAtStop", auto_reload_count_);
167   }
168   if (committed_error_page_info_)
169     committed_error_page_info_->alternate_error_page_url = GURL();
170   if (pending_error_page_info_)
171     pending_error_page_info_->alternate_error_page_url = GURL();
172   delegate_->CancelFetchErrorPage();
173   auto_reload_timer_->Stop();
174   can_auto_reload_page_ = false;
175 }
176
177 void NetErrorHelperCore::OnStop() {
178   CancelPendingFetches();
179   auto_reload_count_ = 0;
180 }
181
182 void NetErrorHelperCore::OnStartLoad(FrameType frame_type, PageType page_type) {
183   if (frame_type != MAIN_FRAME)
184     return;
185
186   // If there's no pending error page information associated with the page load,
187   // or the new page is not an error page, then reset pending error page state.
188   if (!pending_error_page_info_ || page_type != ERROR_PAGE) {
189     CancelPendingFetches();
190   } else if (auto_reload_enabled_) {
191     // If an error load is starting, the resulting error page is autoreloadable.
192     can_auto_reload_page_ = IsReloadableError(*pending_error_page_info_);
193   }
194 }
195
196 void NetErrorHelperCore::OnCommitLoad(FrameType frame_type) {
197   if (frame_type != MAIN_FRAME)
198     return;
199
200   if (committed_error_page_info_ && !pending_error_page_info_ &&
201       can_auto_reload_page_) {
202     int reason = committed_error_page_info_->error.reason;
203     UMA_HISTOGRAM_CUSTOM_ENUMERATION("Net.AutoReload.ErrorAtSuccess",
204                                      -reason,
205                                      net::GetAllErrorCodesForUma());
206     UMA_HISTOGRAM_COUNTS("Net.AutoReload.CountAtSuccess", auto_reload_count_);
207     if (auto_reload_count_ == 1) {
208       UMA_HISTOGRAM_CUSTOM_ENUMERATION("Net.AutoReload.ErrorAtFirstSuccess",
209                                        -reason,
210                                        net::GetAllErrorCodesForUma());
211     }
212   }
213
214   committed_error_page_info_.reset(pending_error_page_info_.release());
215 }
216
217 void NetErrorHelperCore::OnFinishLoad(FrameType frame_type) {
218   if (frame_type != MAIN_FRAME)
219     return;
220
221   if (!committed_error_page_info_) {
222     auto_reload_count_ = 0;
223     return;
224   }
225
226   committed_error_page_info_->is_finished_loading = true;
227
228   // Only enable stale cache JS bindings if this wasn't a post.
229   if (!committed_error_page_info_->was_failed_post) {
230     delegate_->EnableStaleLoadBindings(
231         committed_error_page_info_->error.unreachableURL);
232   }
233
234   if (committed_error_page_info_->alternate_error_page_url.is_valid()) {
235     // If there is another pending error page load,
236     // |replace_with_alternate_error_page| should have been set to false.
237     DCHECK(!pending_error_page_info_);
238     DCHECK(!committed_error_page_info_->needs_dns_updates);
239     GURL error_page_url;
240     delegate_->FetchErrorPage(
241         committed_error_page_info_->alternate_error_page_url);
242   } else if (auto_reload_enabled_ &&
243              IsReloadableError(*committed_error_page_info_)) {
244     MaybeStartAutoReloadTimer();
245   }
246
247   if (!committed_error_page_info_->needs_dns_updates ||
248       last_probe_status_ == chrome_common_net::DNS_PROBE_POSSIBLE) {
249     return;
250   }
251   DVLOG(1) << "Error page finished loading; sending saved status.";
252   UpdateErrorPage();
253 }
254
255 void NetErrorHelperCore::GetErrorHTML(
256     FrameType frame_type,
257     const blink::WebURLError& error,
258     bool is_failed_post,
259     std::string* error_html) {
260   if (frame_type == MAIN_FRAME) {
261     // If an alternate error page was going to be fetched, that should have been
262     // cancelled by loading a new page load (Which has now failed to load).
263     DCHECK(!committed_error_page_info_ ||
264            !committed_error_page_info_->alternate_error_page_url.is_valid());
265
266     // The last probe status needs to be reset if this is a DNS error.  This
267     // means that if a DNS error page is committed but has not yet finished
268     // loading, a DNS probe status scheduled to be sent to it may be thrown
269     // out, but since the new error page should trigger a new DNS probe, it
270     // will just get the results for the next page load.
271     if (IsDnsError(error))
272       last_probe_status_ = chrome_common_net::DNS_PROBE_POSSIBLE;
273
274     GURL error_page_url;
275     if (GetErrorPageURL(error, alt_error_page_url_, &error_page_url)) {
276       pending_error_page_info_.reset(new ErrorPageInfo(error, is_failed_post));
277       pending_error_page_info_->alternate_error_page_url = error_page_url;
278       return;
279     }
280   }
281
282   GenerateLocalErrorPage(frame_type, error, is_failed_post, error_html);
283 }
284
285 void NetErrorHelperCore::GenerateLocalErrorPage(
286     FrameType frame_type,
287     const blink::WebURLError& error,
288     bool is_failed_post,
289     std::string* error_html) {
290   if (frame_type == MAIN_FRAME) {
291     pending_error_page_info_.reset(new ErrorPageInfo(error, is_failed_post));
292     if (IsDnsError(error)) {
293       // This is not strictly necessary, but waiting for a new status to be
294       // sent as a result of the DidFinishLoading call keeps the histograms
295       // consistent with older versions of the code, at no real cost.
296       last_probe_status_ = chrome_common_net::DNS_PROBE_POSSIBLE;
297
298       delegate_->GenerateLocalizedErrorPage(
299           GetUpdatedError(error), is_failed_post, error_html);
300       pending_error_page_info_->needs_dns_updates = true;
301       return;
302     }
303   }
304   delegate_->GenerateLocalizedErrorPage(error, is_failed_post, error_html);
305 }
306
307 void NetErrorHelperCore::OnNetErrorInfo(
308     chrome_common_net::DnsProbeStatus status) {
309   DCHECK_NE(chrome_common_net::DNS_PROBE_POSSIBLE, status);
310
311   last_probe_status_ = status;
312
313   if (!committed_error_page_info_ ||
314       !committed_error_page_info_->needs_dns_updates ||
315       !committed_error_page_info_->is_finished_loading) {
316     return;
317   }
318
319   UpdateErrorPage();
320 }
321
322 void NetErrorHelperCore::UpdateErrorPage() {
323   DCHECK(committed_error_page_info_->needs_dns_updates);
324   DCHECK(committed_error_page_info_->is_finished_loading);
325   DCHECK_NE(chrome_common_net::DNS_PROBE_POSSIBLE, last_probe_status_);
326
327   UMA_HISTOGRAM_ENUMERATION("DnsProbe.ErrorPageUpdateStatus",
328                             last_probe_status_,
329                             chrome_common_net::DNS_PROBE_MAX);
330   // Every status other than DNS_PROBE_POSSIBLE and DNS_PROBE_STARTED is a
331   // final status code.  Once one is reached, the page does not need further
332   // updates.
333   if (last_probe_status_ != chrome_common_net::DNS_PROBE_STARTED)
334     committed_error_page_info_->needs_dns_updates = false;
335
336   delegate_->UpdateErrorPage(
337       GetUpdatedError(committed_error_page_info_->error),
338       committed_error_page_info_->was_failed_post);
339 }
340
341 void NetErrorHelperCore::OnAlternateErrorPageFetched(const std::string& data) {
342   // Alternate error page load only starts when an error page finishes loading,
343   // and is cancelled with a new load
344   DCHECK(!pending_error_page_info_);
345   DCHECK(committed_error_page_info_->is_finished_loading);
346
347   const std::string* error_html = NULL;
348   std::string generated_html;
349   if (!data.empty()) {
350     // If the request succeeded, use the response in place of a generated error
351     // page.
352     pending_error_page_info_.reset(
353         new ErrorPageInfo(committed_error_page_info_->error,
354                           committed_error_page_info_->was_failed_post));
355     error_html = &data;
356   } else {
357     // Otherwise, generate a local error page.  |pending_error_page_info_| will
358     // be set by GenerateLocalErrorPage.
359     GenerateLocalErrorPage(MAIN_FRAME,
360                            committed_error_page_info_->error,
361                            committed_error_page_info_->was_failed_post,
362                            &generated_html);
363     error_html = &generated_html;
364   }
365
366   // |error_page_info| may have been destroyed by this point, since
367   // |pending_error_page_info_| was set to a new ErrorPageInfo.
368
369   // TODO(mmenke):  Once the new API is in place, look into replacing this
370   //                double page load by just updating the error page, like DNS
371   //                probes do.
372   delegate_->LoadErrorPageInMainFrame(
373       *error_html,
374       pending_error_page_info_->error.unreachableURL);
375 }
376
377 blink::WebURLError NetErrorHelperCore::GetUpdatedError(
378     const blink::WebURLError& error) const {
379   // If a probe didn't run or wasn't conclusive, restore the original error.
380   if (last_probe_status_ == chrome_common_net::DNS_PROBE_NOT_RUN ||
381       last_probe_status_ ==
382           chrome_common_net::DNS_PROBE_FINISHED_INCONCLUSIVE) {
383     return error;
384   }
385
386   blink::WebURLError updated_error;
387   updated_error.domain = blink::WebString::fromUTF8(
388       chrome_common_net::kDnsProbeErrorDomain);
389   updated_error.reason = last_probe_status_;
390   updated_error.unreachableURL = error.unreachableURL;
391   updated_error.staleCopyInCache = error.staleCopyInCache;
392
393   return updated_error;
394 }
395
396 void NetErrorHelperCore::Reload() {
397   if (!committed_error_page_info_) {
398     return;
399   }
400   delegate_->ReloadPage();
401 }
402
403 bool NetErrorHelperCore::MaybeStartAutoReloadTimer() {
404   if (!committed_error_page_info_ ||
405       !committed_error_page_info_->is_finished_loading ||
406       !can_auto_reload_page_ ||
407       pending_error_page_info_) {
408     return false;
409   }
410
411   DCHECK(IsReloadableError(*committed_error_page_info_));
412
413   if (!online_)
414     return false;
415
416   StartAutoReloadTimer();
417   return true;
418 }
419
420 void NetErrorHelperCore::StartAutoReloadTimer() {
421   DCHECK(committed_error_page_info_);
422   DCHECK(can_auto_reload_page_);
423   base::TimeDelta delay = GetAutoReloadTime(auto_reload_count_);
424   auto_reload_count_++;
425   auto_reload_timer_->Stop();
426   auto_reload_timer_->Start(FROM_HERE, delay,
427       base::Bind(&NetErrorHelperCore::Reload,
428                  base::Unretained(this)));
429 }
430
431 void NetErrorHelperCore::NetworkStateChanged(bool online) {
432   online_ = online;
433   if (auto_reload_timer_->IsRunning()) {
434     DCHECK(committed_error_page_info_);
435     // If there's an existing timer running, stop it and reset the retry count.
436     auto_reload_timer_->Stop();
437     auto_reload_count_ = 0;
438   }
439
440   // If the network state changed to online, maybe start auto-reloading again.
441   if (online)
442     MaybeStartAutoReloadTimer();
443 }
444
445 bool NetErrorHelperCore::ShouldSuppressErrorPage(FrameType frame_type,
446                                                  const GURL& url) {
447   // Don't suppress child frame errors.
448   if (frame_type != MAIN_FRAME)
449     return false;
450
451   // If |auto_reload_timer_| is still running, this error page isn't from an
452   // auto reload.
453   if (auto_reload_timer_->IsRunning())
454     return false;
455
456   // If there's no committed error page, this error page wasn't from an auto
457   // reload.
458   if (!committed_error_page_info_ || !can_auto_reload_page_)
459     return false;
460
461   GURL error_url = committed_error_page_info_->error.unreachableURL;
462   // TODO(ellyjones): also plumb the error code down to CCRC and check that
463   if (error_url != url)
464     return false;
465
466   // The first iteration of the timer is started by OnFinishLoad calling
467   // MaybeStartAutoReloadTimer, but since error pages for subsequent loads are
468   // suppressed in this function, subsequent iterations of the timer have to be
469   // started here.
470   MaybeStartAutoReloadTimer();
471   return true;
472 }