1 // Copyright (c) 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.h"
6
7 #include <string>
8
9 #include "base/json/json_writer.h"
10 #include "base/metrics/histogram.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/values.h"
13 #include "chrome/common/localized_error.h"
14 #include "chrome/common/net/net_error_info.h"
15 #include "chrome/common/render_messages.h"
16 #include "content/public/common/content_client.h"
17 #include "content/public/common/url_constants.h"
18 #include "content/public/renderer/content_renderer_client.h"
19 #include "content/public/renderer/render_thread.h"
20 #include "content/public/renderer/render_view.h"
21 #include "ipc/ipc_message.h"
22 #include "ipc/ipc_message_macros.h"
23 #include "net/base/net_errors.h"
24 #include "third_party/WebKit/public/platform/WebURL.h"
25 #include "third_party/WebKit/public/platform/WebURLRequest.h"
26 #include "third_party/WebKit/public/web/WebDataSource.h"
27 #include "third_party/WebKit/public/web/WebFrame.h"
28 #include "url/gurl.h"
29
30 using base::JSONWriter;
31 using chrome_common_net::DnsProbeStatus;
32 using chrome_common_net::DnsProbeStatusIsFinished;
33 using chrome_common_net::DnsProbeStatusToString;
34 using content::RenderThread;
35 using content::RenderView;
36 using content::RenderViewObserver;
37 using content::kUnreachableWebDataURL;
38
39 namespace {
40
IsLoadingErrorPage(blink::WebFrame * frame)41 bool IsLoadingErrorPage(blink::WebFrame* frame) {
42 GURL url = frame->provisionalDataSource()->request().url();
43 if (!url.is_valid())
44 return false;
45 return url.spec() == kUnreachableWebDataURL;
46 }
47
IsMainFrame(const blink::WebFrame * frame)48 bool IsMainFrame(const blink::WebFrame* frame) {
49 return !frame->parent();
50 }
51
52 // Returns whether |net_error| is a DNS-related error (and therefore whether
53 // the tab helper should start a DNS probe after receiving it.)
IsDnsError(const blink::WebURLError & error)54 bool IsDnsError(const blink::WebURLError& error) {
55 return std::string(error.domain.utf8()) == net::kErrorDomain &&
56 (error.reason == net::ERR_NAME_NOT_RESOLVED ||
57 error.reason == net::ERR_NAME_RESOLUTION_FAILED);
58 }
59
60 } // namespace
61
NetErrorHelper(RenderView * render_view)62 NetErrorHelper::NetErrorHelper(RenderView* render_view)
63 : RenderViewObserver(render_view),
64 last_probe_status_(chrome_common_net::DNS_PROBE_POSSIBLE),
65 last_start_was_error_page_(false),
66 last_fail_was_dns_error_(false),
67 forwarding_probe_results_(false),
68 is_failed_post_(false) {
69 }
70
~NetErrorHelper()71 NetErrorHelper::~NetErrorHelper() {
72 }
73
DidStartProvisionalLoad(blink::WebFrame * frame)74 void NetErrorHelper::DidStartProvisionalLoad(blink::WebFrame* frame) {
75 OnStartLoad(IsMainFrame(frame), IsLoadingErrorPage(frame));
76 }
77
DidFailProvisionalLoad(blink::WebFrame * frame,const blink::WebURLError & error)78 void NetErrorHelper::DidFailProvisionalLoad(blink::WebFrame* frame,
79 const blink::WebURLError& error) {
80 const bool main_frame = IsMainFrame(frame);
81 const bool dns_error = IsDnsError(error);
82
83 OnFailLoad(main_frame, dns_error);
84
85 if (main_frame && dns_error) {
86 last_error_ = error;
87
88 blink::WebDataSource* data_source = frame->provisionalDataSource();
89 const blink::WebURLRequest& failed_request = data_source->request();
90 is_failed_post_ = EqualsASCII(failed_request.httpMethod(), "POST");
91 }
92 }
93
DidCommitProvisionalLoad(blink::WebFrame * frame,bool is_new_navigation)94 void NetErrorHelper::DidCommitProvisionalLoad(blink::WebFrame* frame,
95 bool is_new_navigation) {
96 OnCommitLoad(IsMainFrame(frame));
97 }
98
DidFinishLoad(blink::WebFrame * frame)99 void NetErrorHelper::DidFinishLoad(blink::WebFrame* frame) {
100 OnFinishLoad(IsMainFrame(frame));
101 }
102
OnStartLoad(bool is_main_frame,bool is_error_page)103 void NetErrorHelper::OnStartLoad(bool is_main_frame, bool is_error_page) {
104 DVLOG(1) << "OnStartLoad(is_main_frame=" << is_main_frame
105 << ", is_error_page=" << is_error_page << ")";
106 if (!is_main_frame)
107 return;
108
109 last_start_was_error_page_ = is_error_page;
110 }
111
OnFailLoad(bool is_main_frame,bool is_dns_error)112 void NetErrorHelper::OnFailLoad(bool is_main_frame, bool is_dns_error) {
113 DVLOG(1) << "OnFailLoad(is_main_frame=" << is_main_frame
114 << ", is_dns_error=" << is_dns_error << ")";
115
116 if (!is_main_frame)
117 return;
118
119 last_fail_was_dns_error_ = is_dns_error;
120
121 if (is_dns_error) {
122 last_probe_status_ = chrome_common_net::DNS_PROBE_POSSIBLE;
123 // If the helper was forwarding probe results and another DNS error has
124 // occurred, stop forwarding probe results until the corresponding (new)
125 // error page loads.
126 forwarding_probe_results_ = false;
127 }
128 }
129
OnCommitLoad(bool is_main_frame)130 void NetErrorHelper::OnCommitLoad(bool is_main_frame) {
131 DVLOG(1) << "OnCommitLoad(is_main_frame=" << is_main_frame << ")";
132
133 if (!is_main_frame)
134 return;
135
136 // Stop forwarding results. If the page is a DNS error page, forwarding
137 // will resume once the page is loaded; if not, it should stay stopped until
138 // the next DNS error page.
139 forwarding_probe_results_ = false;
140 }
141
OnFinishLoad(bool is_main_frame)142 void NetErrorHelper::OnFinishLoad(bool is_main_frame) {
143 DVLOG(1) << "OnFinishLoad(is_main_frame=" << is_main_frame << ")";
144
145 if (!is_main_frame)
146 return;
147
148 // If a DNS error page just finished loading, start forwarding probe results
149 // to it.
150 forwarding_probe_results_ =
151 last_fail_was_dns_error_ && last_start_was_error_page_;
152
153 if (forwarding_probe_results_ &&
154 last_probe_status_ != chrome_common_net::DNS_PROBE_POSSIBLE) {
155 DVLOG(1) << "Error page finished loading; sending saved status.";
156 UpdateErrorPage();
157 }
158 }
159
OnMessageReceived(const IPC::Message & message)160 bool NetErrorHelper::OnMessageReceived(const IPC::Message& message) {
161 bool handled = true;
162
163 IPC_BEGIN_MESSAGE_MAP(NetErrorHelper, message)
164 IPC_MESSAGE_HANDLER(ChromeViewMsg_NetErrorInfo, OnNetErrorInfo)
165 IPC_MESSAGE_UNHANDLED(handled = false)
166 IPC_END_MESSAGE_MAP()
167
168 return handled;
169 }
170
171 // static
GetErrorStringsForDnsProbe(blink::WebFrame * frame,const blink::WebURLError & error,bool is_failed_post,const std::string & locale,const std::string & accept_languages,base::DictionaryValue * error_strings)172 bool NetErrorHelper::GetErrorStringsForDnsProbe(
173 blink::WebFrame* frame,
174 const blink::WebURLError& error,
175 bool is_failed_post,
176 const std::string& locale,
177 const std::string& accept_languages,
178 base::DictionaryValue* error_strings) {
179 if (!IsMainFrame(frame))
180 return false;
181
182 if (!IsDnsError(error))
183 return false;
184
185 // Get the strings for a fake "DNS probe possible" error.
186 LocalizedError::GetStrings(
187 chrome_common_net::DNS_PROBE_POSSIBLE,
188 chrome_common_net::kDnsProbeErrorDomain,
189 error.unreachableURL,
190 is_failed_post, locale, accept_languages, error_strings);
191 return true;
192 }
193
OnNetErrorInfo(int status_num)194 void NetErrorHelper::OnNetErrorInfo(int status_num) {
195 DCHECK(status_num >= 0 && status_num < chrome_common_net::DNS_PROBE_MAX);
196
197 DVLOG(1) << "Received status " << DnsProbeStatusToString(status_num);
198
199 DnsProbeStatus status = static_cast<DnsProbeStatus>(status_num);
200 DCHECK_NE(chrome_common_net::DNS_PROBE_POSSIBLE, status);
201
202 if (!(last_fail_was_dns_error_ || forwarding_probe_results_)) {
203 DVLOG(1) << "Ignoring NetErrorInfo: no DNS error";
204 return;
205 }
206
207 last_probe_status_ = status;
208
209 if (forwarding_probe_results_)
210 UpdateErrorPage();
211 }
212
UpdateErrorPage()213 void NetErrorHelper::UpdateErrorPage() {
214 DCHECK(forwarding_probe_results_);
215
216 blink::WebURLError error = GetUpdatedError();
217 base::DictionaryValue error_strings;
218 LocalizedError::GetStrings(error.reason,
219 error.domain.utf8(),
220 error.unreachableURL,
221 is_failed_post_,
222 RenderThread::Get()->GetLocale(),
223 render_view()->GetAcceptLanguages(),
224 &error_strings);
225
226 std::string json;
227 JSONWriter::Write(&error_strings, &json);
228
229 std::string js = "if (window.updateForDnsProbe) "
230 "updateForDnsProbe(" + json + ");";
231 base::string16 js16;
232 if (!UTF8ToUTF16(js.c_str(), js.length(), &js16)) {
233 NOTREACHED();
234 return;
235 }
236
237 DVLOG(1) << "Updating error page with status "
238 << chrome_common_net::DnsProbeStatusToString(last_probe_status_);
239 DVLOG(2) << "New strings: " << js;
240
241 base::string16 frame_xpath;
242 render_view()->EvaluateScript(frame_xpath, js16, 0, false);
243
244 UMA_HISTOGRAM_ENUMERATION("DnsProbe.ErrorPageUpdateStatus",
245 last_probe_status_,
246 chrome_common_net::DNS_PROBE_MAX);
247 }
248
GetUpdatedError() const249 blink::WebURLError NetErrorHelper::GetUpdatedError() const {
250 // If a probe didn't run or wasn't conclusive, restore the original error.
251 if (last_probe_status_ == chrome_common_net::DNS_PROBE_NOT_RUN ||
252 last_probe_status_ ==
253 chrome_common_net::DNS_PROBE_FINISHED_INCONCLUSIVE) {
254 return last_error_;
255 }
256
257 blink::WebURLError error;
258 error.domain = blink::WebString::fromUTF8(
259 chrome_common_net::kDnsProbeErrorDomain);
260 error.reason = last_probe_status_;
261 error.unreachableURL = last_error_.unreachableURL;
262
263 return error;
264 }
265