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/command_line.h"
10#include "base/i18n/rtl.h"
11#include "base/json/json_writer.h"
12#include "base/metrics/histogram.h"
13#include "base/strings/utf_string_conversions.h"
14#include "base/values.h"
15#include "chrome/common/chrome_switches.h"
16#include "chrome/common/localized_error.h"
17#include "chrome/common/net/net_error_info.h"
18#include "chrome/common/render_messages.h"
19#include "chrome/grit/renderer_resources.h"
20#include "chrome/renderer/net/net_error_page_controller.h"
21#include "content/public/common/content_client.h"
22#include "content/public/common/url_constants.h"
23#include "content/public/renderer/content_renderer_client.h"
24#include "content/public/renderer/document_state.h"
25#include "content/public/renderer/render_frame.h"
26#include "content/public/renderer/render_thread.h"
27#include "content/public/renderer/render_view.h"
28#include "content/public/renderer/resource_fetcher.h"
29#include "ipc/ipc_message.h"
30#include "ipc/ipc_message_macros.h"
31#include "third_party/WebKit/public/platform/WebURL.h"
32#include "third_party/WebKit/public/platform/WebURLError.h"
33#include "third_party/WebKit/public/platform/WebURLRequest.h"
34#include "third_party/WebKit/public/platform/WebURLResponse.h"
35#include "third_party/WebKit/public/web/WebDataSource.h"
36#include "third_party/WebKit/public/web/WebDocument.h"
37#include "third_party/WebKit/public/web/WebLocalFrame.h"
38#include "third_party/WebKit/public/web/WebView.h"
39#include "ui/base/resource/resource_bundle.h"
40#include "ui/base/webui/jstemplate_builder.h"
41#include "url/gurl.h"
42
43using base::JSONWriter;
44using chrome_common_net::DnsProbeStatus;
45using chrome_common_net::DnsProbeStatusToString;
46using content::DocumentState;
47using content::RenderFrame;
48using content::RenderFrameObserver;
49using content::RenderThread;
50using content::kUnreachableWebDataURL;
51
52namespace {
53
54// Number of seconds to wait for the navigation correction service to return
55// suggestions.  If it takes too long, just use the local error page.
56static const int kNavigationCorrectionFetchTimeoutSec = 3;
57
58NetErrorHelperCore::PageType GetLoadingPageType(const blink::WebFrame* frame) {
59  GURL url = frame->provisionalDataSource()->request().url();
60  if (!url.is_valid() || url.spec() != kUnreachableWebDataURL)
61    return NetErrorHelperCore::NON_ERROR_PAGE;
62  return NetErrorHelperCore::ERROR_PAGE;
63}
64
65NetErrorHelperCore::FrameType GetFrameType(const blink::WebFrame* frame) {
66  if (!frame->parent())
67    return NetErrorHelperCore::MAIN_FRAME;
68  return NetErrorHelperCore::SUB_FRAME;
69}
70
71}  // namespace
72
73NetErrorHelper::NetErrorHelper(RenderFrame* render_frame)
74    : RenderFrameObserver(render_frame),
75      content::RenderFrameObserverTracker<NetErrorHelper>(render_frame) {
76  RenderThread::Get()->AddObserver(this);
77  CommandLine* command_line = CommandLine::ForCurrentProcess();
78  bool auto_reload_enabled =
79      command_line->HasSwitch(switches::kEnableOfflineAutoReload);
80  bool auto_reload_visible_only =
81      command_line->HasSwitch(switches::kEnableOfflineAutoReloadVisibleOnly);
82  core_.reset(new NetErrorHelperCore(this,
83                                     auto_reload_enabled,
84                                     auto_reload_visible_only,
85                                     !render_frame->IsHidden()));
86}
87
88NetErrorHelper::~NetErrorHelper() {
89  RenderThread::Get()->RemoveObserver(this);
90}
91
92void NetErrorHelper::ReloadButtonPressed() {
93  core_->ExecuteButtonPress(NetErrorHelperCore::RELOAD_BUTTON);
94}
95
96void NetErrorHelper::LoadStaleButtonPressed() {
97  core_->ExecuteButtonPress(NetErrorHelperCore::LOAD_STALE_BUTTON);
98}
99
100void NetErrorHelper::MoreButtonPressed() {
101  core_->ExecuteButtonPress(NetErrorHelperCore::MORE_BUTTON);
102}
103
104void NetErrorHelper::DidStartProvisionalLoad() {
105  blink::WebFrame* frame = render_frame()->GetWebFrame();
106  core_->OnStartLoad(GetFrameType(frame), GetLoadingPageType(frame));
107}
108
109void NetErrorHelper::DidCommitProvisionalLoad(bool is_new_navigation) {
110  blink::WebFrame* frame = render_frame()->GetWebFrame();
111  core_->OnCommitLoad(GetFrameType(frame), frame->document().url());
112}
113
114void NetErrorHelper::DidFinishLoad() {
115  blink::WebFrame* frame = render_frame()->GetWebFrame();
116  core_->OnFinishLoad(GetFrameType(frame));
117}
118
119void NetErrorHelper::OnStop() {
120  core_->OnStop();
121}
122
123void NetErrorHelper::WasShown() {
124  core_->OnWasShown();
125}
126
127void NetErrorHelper::WasHidden() {
128  core_->OnWasHidden();
129}
130
131bool NetErrorHelper::OnMessageReceived(const IPC::Message& message) {
132  bool handled = true;
133
134  IPC_BEGIN_MESSAGE_MAP(NetErrorHelper, message)
135    IPC_MESSAGE_HANDLER(ChromeViewMsg_NetErrorInfo, OnNetErrorInfo)
136    IPC_MESSAGE_HANDLER(ChromeViewMsg_SetNavigationCorrectionInfo,
137                        OnSetNavigationCorrectionInfo);
138    IPC_MESSAGE_UNHANDLED(handled = false)
139  IPC_END_MESSAGE_MAP()
140
141  return handled;
142}
143
144void NetErrorHelper::NetworkStateChanged(bool enabled) {
145  core_->NetworkStateChanged(enabled);
146}
147
148void NetErrorHelper::GetErrorHTML(
149    blink::WebFrame* frame,
150    const blink::WebURLError& error,
151    bool is_failed_post,
152    std::string* error_html) {
153  core_->GetErrorHTML(GetFrameType(frame), error, is_failed_post, error_html);
154}
155
156bool NetErrorHelper::ShouldSuppressErrorPage(blink::WebFrame* frame,
157                                             const GURL& url) {
158  return core_->ShouldSuppressErrorPage(GetFrameType(frame), url);
159}
160
161void NetErrorHelper::TrackClick(int tracking_id) {
162  core_->TrackClick(tracking_id);
163}
164
165void NetErrorHelper::GenerateLocalizedErrorPage(
166    const blink::WebURLError& error,
167    bool is_failed_post,
168    scoped_ptr<LocalizedError::ErrorPageParams> params,
169    bool* reload_button_shown,
170    bool* load_stale_button_shown,
171    std::string* error_html) const {
172  error_html->clear();
173
174  int resource_id = IDR_NET_ERROR_HTML;
175  const base::StringPiece template_html(
176      ResourceBundle::GetSharedInstance().GetRawDataResource(resource_id));
177  if (template_html.empty()) {
178    NOTREACHED() << "unable to load template.";
179  } else {
180    CommandLine* command_line = CommandLine::ForCurrentProcess();
181    bool load_stale_cache_enabled =
182        command_line->HasSwitch(switches::kEnableOfflineLoadStaleCache);
183
184    base::DictionaryValue error_strings;
185    LocalizedError::GetStrings(error.reason, error.domain.utf8(),
186                               error.unreachableURL, is_failed_post,
187                               (load_stale_cache_enabled &&
188                                error.staleCopyInCache && !is_failed_post),
189                               RenderThread::Get()->GetLocale(),
190                               render_frame()->GetRenderView()->
191                                   GetAcceptLanguages(),
192                               params.Pass(), &error_strings);
193    *reload_button_shown = error_strings.Get("reloadButton", NULL);
194    *load_stale_button_shown = error_strings.Get("staleLoadButton", NULL);
195
196    // "t" is the id of the template's root node.
197    *error_html = webui::GetTemplatesHtml(template_html, &error_strings, "t");
198  }
199}
200
201void NetErrorHelper::LoadErrorPageInMainFrame(const std::string& html,
202                                              const GURL& failed_url) {
203  blink::WebView* web_view = render_frame()->GetRenderView()->GetWebView();
204  if (!web_view)
205    return;
206  blink::WebFrame* frame = web_view->mainFrame();
207  frame->loadHTMLString(html, GURL(kUnreachableWebDataURL), failed_url, true);
208}
209
210void NetErrorHelper::EnablePageHelperFunctions() {
211  NetErrorPageController::Install(render_frame());
212}
213
214void NetErrorHelper::UpdateErrorPage(const blink::WebURLError& error,
215                                     bool is_failed_post) {
216    CommandLine* command_line = CommandLine::ForCurrentProcess();
217    bool load_stale_cache_enabled =
218        command_line->HasSwitch(switches::kEnableOfflineLoadStaleCache);
219
220  base::DictionaryValue error_strings;
221  LocalizedError::GetStrings(error.reason,
222                             error.domain.utf8(),
223                             error.unreachableURL,
224                             is_failed_post,
225                             (load_stale_cache_enabled &&
226                              error.staleCopyInCache && !is_failed_post),
227                             RenderThread::Get()->GetLocale(),
228                             render_frame()->GetRenderView()->
229                                 GetAcceptLanguages(),
230                             scoped_ptr<LocalizedError::ErrorPageParams>(),
231                             &error_strings);
232
233  std::string json;
234  JSONWriter::Write(&error_strings, &json);
235
236  std::string js = "if (window.updateForDnsProbe) "
237                   "updateForDnsProbe(" + json + ");";
238  base::string16 js16;
239  if (!base::UTF8ToUTF16(js.c_str(), js.length(), &js16)) {
240    NOTREACHED();
241    return;
242  }
243
244  render_frame()->ExecuteJavaScript(js16);
245}
246
247void NetErrorHelper::FetchNavigationCorrections(
248    const GURL& navigation_correction_url,
249    const std::string& navigation_correction_request_body) {
250  DCHECK(!correction_fetcher_.get());
251
252  blink::WebView* web_view = render_frame()->GetRenderView()->GetWebView();
253  if (!web_view)
254    return;
255  blink::WebFrame* frame = web_view->mainFrame();
256
257  correction_fetcher_.reset(
258      content::ResourceFetcher::Create(navigation_correction_url));
259  correction_fetcher_->SetMethod("POST");
260  correction_fetcher_->SetBody(navigation_correction_request_body);
261  correction_fetcher_->SetHeader("Content-Type", "application/json");
262
263  correction_fetcher_->Start(
264      frame,
265      blink::WebURLRequest::RequestContextInternal,
266      blink::WebURLRequest::FrameTypeTopLevel,
267      content::ResourceFetcher::PLATFORM_LOADER,
268      base::Bind(&NetErrorHelper::OnNavigationCorrectionsFetched,
269                 base::Unretained(this)));
270
271  correction_fetcher_->SetTimeout(
272      base::TimeDelta::FromSeconds(kNavigationCorrectionFetchTimeoutSec));
273}
274
275void NetErrorHelper::CancelFetchNavigationCorrections() {
276  correction_fetcher_.reset();
277}
278
279void NetErrorHelper::SendTrackingRequest(
280    const GURL& tracking_url,
281    const std::string& tracking_request_body) {
282  blink::WebView* web_view = render_frame()->GetRenderView()->GetWebView();
283  if (!web_view)
284    return;
285  blink::WebFrame* frame = web_view->mainFrame();
286
287  // If there's already a pending tracking request, this will cancel it.
288  tracking_fetcher_.reset(content::ResourceFetcher::Create(tracking_url));
289  tracking_fetcher_->SetMethod("POST");
290  tracking_fetcher_->SetBody(tracking_request_body);
291  tracking_fetcher_->SetHeader("Content-Type", "application/json");
292
293  tracking_fetcher_->Start(
294      frame,
295      blink::WebURLRequest::RequestContextInternal,
296      blink::WebURLRequest::FrameTypeTopLevel,
297      content::ResourceFetcher::PLATFORM_LOADER,
298      base::Bind(&NetErrorHelper::OnTrackingRequestComplete,
299                 base::Unretained(this)));
300}
301
302void NetErrorHelper::ReloadPage() {
303  render_frame()->GetWebFrame()->reload(false);
304}
305
306void NetErrorHelper::LoadPageFromCache(const GURL& page_url) {
307  blink::WebFrame* web_frame = render_frame()->GetWebFrame();
308  DCHECK(!EqualsASCII(web_frame->dataSource()->request().httpMethod(), "POST"));
309
310  blink::WebURLRequest request(page_url);
311  request.setCachePolicy(blink::WebURLRequest::ReturnCacheDataDontLoad);
312
313  web_frame->loadRequest(request);
314}
315
316void NetErrorHelper::OnNetErrorInfo(int status_num) {
317  DCHECK(status_num >= 0 && status_num < chrome_common_net::DNS_PROBE_MAX);
318
319  DVLOG(1) << "Received status " << DnsProbeStatusToString(status_num);
320
321  core_->OnNetErrorInfo(static_cast<DnsProbeStatus>(status_num));
322}
323
324void NetErrorHelper::OnSetNavigationCorrectionInfo(
325    const GURL& navigation_correction_url,
326    const std::string& language,
327    const std::string& country_code,
328    const std::string& api_key,
329    const GURL& search_url) {
330  core_->OnSetNavigationCorrectionInfo(navigation_correction_url, language,
331                                      country_code, api_key, search_url);
332}
333
334void NetErrorHelper::OnNavigationCorrectionsFetched(
335    const blink::WebURLResponse& response,
336    const std::string& data) {
337  // The fetcher may only be deleted after |data| is passed to |core_|.  Move
338  // it to a temporary to prevent any potential re-entrancy issues.
339  scoped_ptr<content::ResourceFetcher> fetcher(
340      correction_fetcher_.release());
341  bool success = (!response.isNull() && response.httpStatusCode() == 200);
342  core_->OnNavigationCorrectionsFetched(
343      success ? data : "",
344      render_frame()->GetRenderView()->GetAcceptLanguages(),
345      base::i18n::IsRTL());
346}
347
348void NetErrorHelper::OnTrackingRequestComplete(
349    const blink::WebURLResponse& response,
350    const std::string& data) {
351  tracking_fetcher_.reset();
352}
353