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 30using base::JSONWriter; 31using chrome_common_net::DnsProbeStatus; 32using chrome_common_net::DnsProbeStatusIsFinished; 33using chrome_common_net::DnsProbeStatusToString; 34using content::RenderThread; 35using content::RenderView; 36using content::RenderViewObserver; 37using content::kUnreachableWebDataURL; 38 39namespace { 40 41bool 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 48bool 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.) 54bool 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 62NetErrorHelper::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 71NetErrorHelper::~NetErrorHelper() { 72} 73 74void NetErrorHelper::DidStartProvisionalLoad(blink::WebFrame* frame) { 75 OnStartLoad(IsMainFrame(frame), IsLoadingErrorPage(frame)); 76} 77 78void 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 94void NetErrorHelper::DidCommitProvisionalLoad(blink::WebFrame* frame, 95 bool is_new_navigation) { 96 OnCommitLoad(IsMainFrame(frame)); 97} 98 99void NetErrorHelper::DidFinishLoad(blink::WebFrame* frame) { 100 OnFinishLoad(IsMainFrame(frame)); 101} 102 103void 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 112void 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 130void 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 142void 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 160bool 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 172bool 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 194void 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 213void 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 249blink::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