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