| // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/renderer/net/net_error_helper.h" |
| |
| #include <string> |
| |
| #include "base/command_line.h" |
| #include "base/i18n/rtl.h" |
| #include "base/json/json_writer.h" |
| #include "base/metrics/histogram.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/localized_error.h" |
| #include "chrome/common/render_messages.h" |
| #include "chrome/grit/renderer_resources.h" |
| #include "components/error_page/common/error_page_params.h" |
| #include "components/error_page/common/net_error_info.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/url_constants.h" |
| #include "content/public/renderer/content_renderer_client.h" |
| #include "content/public/renderer/document_state.h" |
| #include "content/public/renderer/render_frame.h" |
| #include "content/public/renderer/render_thread.h" |
| #include "content/public/renderer/render_view.h" |
| #include "content/public/renderer/resource_fetcher.h" |
| #include "ipc/ipc_message.h" |
| #include "ipc/ipc_message_macros.h" |
| #include "third_party/WebKit/public/platform/WebURL.h" |
| #include "third_party/WebKit/public/platform/WebURLError.h" |
| #include "third_party/WebKit/public/platform/WebURLRequest.h" |
| #include "third_party/WebKit/public/platform/WebURLResponse.h" |
| #include "third_party/WebKit/public/web/WebDataSource.h" |
| #include "third_party/WebKit/public/web/WebDocument.h" |
| #include "third_party/WebKit/public/web/WebFrame.h" |
| #include "third_party/WebKit/public/web/WebLocalFrame.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/base/webui/jstemplate_builder.h" |
| #include "url/gurl.h" |
| |
| using base::JSONWriter; |
| using content::DocumentState; |
| using content::RenderFrame; |
| using content::RenderFrameObserver; |
| using content::RenderThread; |
| using content::kUnreachableWebDataURL; |
| using error_page::DnsProbeStatus; |
| using error_page::DnsProbeStatusToString; |
| using error_page::ErrorPageParams; |
| using error_page::NetErrorHelperCore; |
| |
| namespace { |
| |
| // Number of seconds to wait for the navigation correction service to return |
| // suggestions. If it takes too long, just use the local error page. |
| const int kNavigationCorrectionFetchTimeoutSec = 3; |
| |
| NetErrorHelperCore::PageType GetLoadingPageType(RenderFrame* render_frame) { |
| blink::WebFrame* web_frame = render_frame->GetWebFrame(); |
| GURL url = web_frame->provisionalDataSource()->request().url(); |
| if (!url.is_valid() || url.spec() != kUnreachableWebDataURL) |
| return NetErrorHelperCore::NON_ERROR_PAGE; |
| return NetErrorHelperCore::ERROR_PAGE; |
| } |
| |
| NetErrorHelperCore::FrameType GetFrameType(RenderFrame* render_frame) { |
| if (render_frame->IsMainFrame()) |
| return NetErrorHelperCore::MAIN_FRAME; |
| return NetErrorHelperCore::SUB_FRAME; |
| } |
| |
| } // namespace |
| |
| NetErrorHelper::NetErrorHelper(RenderFrame* render_frame) |
| : RenderFrameObserver(render_frame), |
| content::RenderFrameObserverTracker<NetErrorHelper>(render_frame), |
| weak_controller_delegate_factory_(this) { |
| RenderThread::Get()->AddObserver(this); |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| bool auto_reload_enabled = |
| command_line->HasSwitch(switches::kEnableOfflineAutoReload); |
| bool auto_reload_visible_only = |
| command_line->HasSwitch(switches::kEnableOfflineAutoReloadVisibleOnly); |
| // TODO(mmenke): Consider only creating a NetErrorHelperCore for main frames. |
| // subframes don't need any of the NetErrorHelperCore's extra logic. |
| core_.reset(new NetErrorHelperCore(this, |
| auto_reload_enabled, |
| auto_reload_visible_only, |
| !render_frame->IsHidden())); |
| } |
| |
| NetErrorHelper::~NetErrorHelper() { |
| RenderThread::Get()->RemoveObserver(this); |
| } |
| |
| void NetErrorHelper::ButtonPressed( |
| error_page::NetErrorHelperCore::Button button) { |
| core_->ExecuteButtonPress(button); |
| } |
| |
| void NetErrorHelper::TrackClick(int tracking_id) { |
| core_->TrackClick(tracking_id); |
| } |
| |
| void NetErrorHelper::DidStartProvisionalLoad() { |
| core_->OnStartLoad(GetFrameType(render_frame()), |
| GetLoadingPageType(render_frame())); |
| } |
| |
| void NetErrorHelper::DidCommitProvisionalLoad(bool is_new_navigation, |
| bool is_same_page_navigation) { |
| // Invalidate weak pointers from old error page controllers. If loading a new |
| // error page, the controller has not yet been attached, so this won't affect |
| // it. |
| weak_controller_delegate_factory_.InvalidateWeakPtrs(); |
| |
| core_->OnCommitLoad(GetFrameType(render_frame()), |
| render_frame()->GetWebFrame()->document().url()); |
| } |
| |
| void NetErrorHelper::DidFinishLoad() { |
| core_->OnFinishLoad(GetFrameType(render_frame())); |
| } |
| |
| void NetErrorHelper::OnStop() { |
| core_->OnStop(); |
| } |
| |
| void NetErrorHelper::WasShown() { |
| core_->OnWasShown(); |
| } |
| |
| void NetErrorHelper::WasHidden() { |
| core_->OnWasHidden(); |
| } |
| |
| bool NetErrorHelper::OnMessageReceived(const IPC::Message& message) { |
| bool handled = true; |
| |
| IPC_BEGIN_MESSAGE_MAP(NetErrorHelper, message) |
| IPC_MESSAGE_HANDLER(ChromeViewMsg_NetErrorInfo, OnNetErrorInfo) |
| IPC_MESSAGE_HANDLER(ChromeViewMsg_SetCanShowNetworkDiagnosticsDialog, |
| OnSetCanShowNetworkDiagnosticsDialog); |
| IPC_MESSAGE_HANDLER(ChromeViewMsg_SetNavigationCorrectionInfo, |
| OnSetNavigationCorrectionInfo); |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| |
| return handled; |
| } |
| |
| void NetErrorHelper::NetworkStateChanged(bool enabled) { |
| core_->NetworkStateChanged(enabled); |
| } |
| |
| void NetErrorHelper::GetErrorHTML( |
| const blink::WebURLError& error, |
| bool is_failed_post, |
| std::string* error_html) { |
| core_->GetErrorHTML(GetFrameType(render_frame()), error, is_failed_post, |
| error_html); |
| } |
| |
| bool NetErrorHelper::ShouldSuppressErrorPage(const GURL& url) { |
| return core_->ShouldSuppressErrorPage(GetFrameType(render_frame()), url); |
| } |
| |
| void NetErrorHelper::GenerateLocalizedErrorPage( |
| const blink::WebURLError& error, |
| bool is_failed_post, |
| bool can_show_network_diagnostics_dialog, |
| scoped_ptr<ErrorPageParams> params, |
| bool* reload_button_shown, |
| bool* show_saved_copy_button_shown, |
| bool* show_cached_copy_button_shown, |
| std::string* error_html) const { |
| error_html->clear(); |
| |
| int resource_id = IDR_NET_ERROR_HTML; |
| const base::StringPiece template_html( |
| ResourceBundle::GetSharedInstance().GetRawDataResource(resource_id)); |
| if (template_html.empty()) { |
| NOTREACHED() << "unable to load template."; |
| } else { |
| base::DictionaryValue error_strings; |
| LocalizedError::GetStrings(error.reason, error.domain.utf8(), |
| error.unreachableURL, is_failed_post, |
| error.staleCopyInCache, |
| can_show_network_diagnostics_dialog, |
| RenderThread::Get()->GetLocale(), |
| render_frame()->GetRenderView()-> |
| GetAcceptLanguages(), |
| params.Pass(), &error_strings); |
| *reload_button_shown = error_strings.Get("reloadButton", nullptr); |
| *show_saved_copy_button_shown = |
| error_strings.Get("showSavedCopyButton", nullptr); |
| *show_cached_copy_button_shown = |
| error_strings.Get("cacheButton", nullptr); |
| // "t" is the id of the template's root node. |
| *error_html = webui::GetTemplatesHtml(template_html, &error_strings, "t"); |
| } |
| } |
| |
| void NetErrorHelper::LoadErrorPage(const std::string& html, |
| const GURL& failed_url) { |
| render_frame()->GetWebFrame()->loadHTMLString( |
| html, GURL(kUnreachableWebDataURL), failed_url, true); |
| } |
| |
| void NetErrorHelper::EnablePageHelperFunctions() { |
| NetErrorPageController::Install( |
| render_frame(), weak_controller_delegate_factory_.GetWeakPtr()); |
| } |
| |
| void NetErrorHelper::UpdateErrorPage(const blink::WebURLError& error, |
| bool is_failed_post, |
| bool can_show_network_diagnostics_dialog) { |
| base::DictionaryValue error_strings; |
| LocalizedError::GetStrings(error.reason, |
| error.domain.utf8(), |
| error.unreachableURL, |
| is_failed_post, |
| error.staleCopyInCache, |
| can_show_network_diagnostics_dialog, |
| RenderThread::Get()->GetLocale(), |
| render_frame()->GetRenderView()-> |
| GetAcceptLanguages(), |
| scoped_ptr<ErrorPageParams>(), |
| &error_strings); |
| |
| std::string json; |
| JSONWriter::Write(error_strings, &json); |
| |
| std::string js = "if (window.updateForDnsProbe) " |
| "updateForDnsProbe(" + json + ");"; |
| base::string16 js16; |
| if (!base::UTF8ToUTF16(js.c_str(), js.length(), &js16)) { |
| NOTREACHED(); |
| return; |
| } |
| |
| render_frame()->ExecuteJavaScript(js16); |
| } |
| |
| void NetErrorHelper::FetchNavigationCorrections( |
| const GURL& navigation_correction_url, |
| const std::string& navigation_correction_request_body) { |
| DCHECK(!correction_fetcher_.get()); |
| |
| correction_fetcher_.reset( |
| content::ResourceFetcher::Create(navigation_correction_url)); |
| correction_fetcher_->SetMethod("POST"); |
| correction_fetcher_->SetBody(navigation_correction_request_body); |
| correction_fetcher_->SetHeader("Content-Type", "application/json"); |
| |
| correction_fetcher_->Start( |
| render_frame()->GetWebFrame(), |
| blink::WebURLRequest::RequestContextInternal, |
| blink::WebURLRequest::FrameTypeNone, |
| content::ResourceFetcher::PLATFORM_LOADER, |
| base::Bind(&NetErrorHelper::OnNavigationCorrectionsFetched, |
| base::Unretained(this))); |
| |
| correction_fetcher_->SetTimeout( |
| base::TimeDelta::FromSeconds(kNavigationCorrectionFetchTimeoutSec)); |
| } |
| |
| void NetErrorHelper::CancelFetchNavigationCorrections() { |
| correction_fetcher_.reset(); |
| } |
| |
| void NetErrorHelper::SendTrackingRequest( |
| const GURL& tracking_url, |
| const std::string& tracking_request_body) { |
| // If there's already a pending tracking request, this will cancel it. |
| tracking_fetcher_.reset(content::ResourceFetcher::Create(tracking_url)); |
| tracking_fetcher_->SetMethod("POST"); |
| tracking_fetcher_->SetBody(tracking_request_body); |
| tracking_fetcher_->SetHeader("Content-Type", "application/json"); |
| |
| tracking_fetcher_->Start( |
| render_frame()->GetWebFrame(), |
| blink::WebURLRequest::RequestContextInternal, |
| blink::WebURLRequest::FrameTypeTopLevel, |
| content::ResourceFetcher::PLATFORM_LOADER, |
| base::Bind(&NetErrorHelper::OnTrackingRequestComplete, |
| base::Unretained(this))); |
| } |
| |
| void NetErrorHelper::ReloadPage() { |
| render_frame()->GetWebFrame()->reload(false); |
| } |
| |
| void NetErrorHelper::LoadPageFromCache(const GURL& page_url) { |
| blink::WebFrame* web_frame = render_frame()->GetWebFrame(); |
| DCHECK(!base::EqualsASCII( |
| base::StringPiece16(web_frame->dataSource()->request().httpMethod()), |
| "POST")); |
| |
| blink::WebURLRequest request(page_url); |
| request.setCachePolicy(blink::WebURLRequest::ReturnCacheDataDontLoad); |
| |
| web_frame->loadRequest(request); |
| } |
| |
| void NetErrorHelper::DiagnoseError(const GURL& page_url) { |
| render_frame()->Send(new ChromeViewHostMsg_RunNetworkDiagnostics( |
| render_frame()->GetRoutingID(), page_url)); |
| } |
| |
| void NetErrorHelper::OnNetErrorInfo(int status_num) { |
| DCHECK(status_num >= 0 && status_num < error_page::DNS_PROBE_MAX); |
| |
| DVLOG(1) << "Received status " << DnsProbeStatusToString(status_num); |
| |
| core_->OnNetErrorInfo(static_cast<DnsProbeStatus>(status_num)); |
| } |
| |
| void NetErrorHelper::OnSetCanShowNetworkDiagnosticsDialog( |
| bool can_use_local_diagnostics_service) { |
| core_->OnSetCanShowNetworkDiagnosticsDialog( |
| can_use_local_diagnostics_service); |
| } |
| |
| void NetErrorHelper::OnSetNavigationCorrectionInfo( |
| const GURL& navigation_correction_url, |
| const std::string& language, |
| const std::string& country_code, |
| const std::string& api_key, |
| const GURL& search_url) { |
| core_->OnSetNavigationCorrectionInfo(navigation_correction_url, language, |
| country_code, api_key, search_url); |
| } |
| |
| void NetErrorHelper::OnNavigationCorrectionsFetched( |
| const blink::WebURLResponse& response, |
| const std::string& data) { |
| // The fetcher may only be deleted after |data| is passed to |core_|. Move |
| // it to a temporary to prevent any potential re-entrancy issues. |
| scoped_ptr<content::ResourceFetcher> fetcher( |
| correction_fetcher_.release()); |
| bool success = (!response.isNull() && response.httpStatusCode() == 200); |
| core_->OnNavigationCorrectionsFetched( |
| success ? data : "", |
| render_frame()->GetRenderView()->GetAcceptLanguages(), |
| base::i18n::IsRTL()); |
| } |
| |
| void NetErrorHelper::OnTrackingRequestComplete( |
| const blink::WebURLResponse& response, |
| const std::string& data) { |
| tracking_fetcher_.reset(); |
| } |