// 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 #include #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 "build/build_config.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/render_messages.h" #include "components/error_page/common/error_page_params.h" #include "components/error_page/common/localized_error.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 "grit/components_resources.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::LocalizedError; 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(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) { // If this is a "same page" navigation, it's not a real navigation. There // wasn't a start event for it, either, so just ignore it. if (is_same_page_navigation) return; // 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); #if defined(OS_ANDROID) IPC_MESSAGE_HANDLER(ChromeViewMsg_SetHasOfflinePages, OnSetHasOfflinePages) #endif 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, bool is_ignoring_cache, std::string* error_html) { core_->GetErrorHTML(GetFrameType(render_frame()), error, is_failed_post, is_ignoring_cache, 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, bool has_offline_pages, scoped_ptr params, bool* reload_button_shown, bool* show_saved_copy_button_shown, bool* show_cached_copy_button_shown, bool* show_offline_pages_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, has_offline_pages, RenderThread::Get()->GetLocale(), render_frame()->GetRenderView()->GetAcceptLanguages(), std::move(params), &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); *show_offline_pages_button_shown = error_strings.Get("showOfflinePagesButton", 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, bool has_offline_pages) { base::DictionaryValue error_strings; LocalizedError::GetStrings( error.reason, error.domain.utf8(), error.unreachableURL, is_failed_post, error.staleCopyInCache, can_show_network_diagnostics_dialog, has_offline_pages, RenderThread::Get()->GetLocale(), render_frame()->GetRenderView()->GetAcceptLanguages(), scoped_ptr(), &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(bool ignore_cache) { render_frame()->GetWebFrame()->reload(ignore_cache); } 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::ShowOfflinePages() { #if defined(OS_ANDROID) render_frame()->Send(new ChromeViewHostMsg_ShowOfflinePages( render_frame()->GetRoutingID())); #endif // defined(OS_ANDROID) } 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(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 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(); } #if defined(OS_ANDROID) void NetErrorHelper::OnSetHasOfflinePages(bool has_offline_pages) { core_->OnSetHasOfflinePages(has_offline_pages); } #endif // defined(OS_ANDROID)