| // Copyright (c) 2012 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/browser/net/net_error_tab_helper.h" |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "chrome/browser/net/dns_probe_service.h" |
| #include "chrome/browser/net/dns_probe_service_factory.h" |
| #include "chrome/browser/net/net_error_diagnostics_dialog.h" |
| #include "chrome/browser/platform_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/common/render_messages.h" |
| #include "components/error_page/common/net_error_info.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "ipc/ipc_message_macros.h" |
| #include "net/base/net_errors.h" |
| #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" |
| #include "url/gurl.h" |
| |
| #if BUILDFLAG(ENABLE_OFFLINE_PAGES) |
| #include "chrome/browser/offline_pages/offline_page_utils.h" |
| #include "components/offline_pages/core/client_namespace_constants.h" |
| #endif // BUILDFLAG(ENABLE_OFFLINE_PAGES) |
| |
| using content::BrowserContext; |
| using content::BrowserThread; |
| using content::WebContents; |
| using content::WebContentsObserver; |
| using error_page::DnsProbeStatus; |
| using error_page::DnsProbeStatusToString; |
| using ui::PageTransition; |
| |
| namespace chrome_browser_net { |
| |
| namespace { |
| |
| static NetErrorTabHelper::TestingState testing_state_ = |
| NetErrorTabHelper::TESTING_DEFAULT; |
| |
| } // namespace |
| |
| NetErrorTabHelper::~NetErrorTabHelper() { |
| } |
| |
| // static |
| void NetErrorTabHelper::set_state_for_testing(TestingState state) { |
| testing_state_ = state; |
| } |
| |
| void NetErrorTabHelper::RenderFrameCreated( |
| content::RenderFrameHost* render_frame_host) { |
| // Ignore subframe creation - only main frame error pages can link to the |
| // platform's network diagnostics dialog. |
| if (render_frame_host->GetParent()) |
| return; |
| |
| chrome::mojom::NetworkDiagnosticsClientAssociatedPtr client; |
| render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(&client); |
| client->SetCanShowNetworkDiagnosticsDialog( |
| CanShowNetworkDiagnosticsDialog(web_contents())); |
| } |
| |
| void NetErrorTabHelper::DidStartNavigation( |
| content::NavigationHandle* navigation_handle) { |
| if (!navigation_handle->IsInMainFrame()) |
| return; |
| |
| if (navigation_handle->IsErrorPage() && |
| PageTransitionCoreTypeIs(navigation_handle->GetPageTransition(), |
| ui::PAGE_TRANSITION_RELOAD)) { |
| error_page::RecordEvent( |
| error_page::NETWORK_ERROR_PAGE_BROWSER_INITIATED_RELOAD); |
| } |
| } |
| |
| void NetErrorTabHelper::DidFinishNavigation( |
| content::NavigationHandle* navigation_handle) { |
| if (!navigation_handle->IsInMainFrame()) |
| return; |
| |
| if (net::IsDnsError(navigation_handle->GetNetErrorCode())) { |
| dns_error_active_ = true; |
| OnMainFrameDnsError(); |
| } |
| |
| // Resend status every time an error page commits; this is somewhat spammy, |
| // but ensures that the status will make it to the real error page, even if |
| // the link doctor loads a blank intermediate page or the tab switches |
| // renderer processes. |
| if (navigation_handle->IsErrorPage() && dns_error_active_) { |
| dns_error_page_committed_ = true; |
| DVLOG(1) << "Committed error page; resending status."; |
| SendInfo(); |
| } else if (navigation_handle->HasCommitted() && |
| !navigation_handle->IsErrorPage()) { |
| dns_error_active_ = false; |
| dns_error_page_committed_ = false; |
| #if BUILDFLAG(ENABLE_OFFLINE_PAGES) |
| is_showing_download_button_in_error_page_ = false; |
| #endif // BUILDFLAG(ENABLE_OFFLINE_PAGES) |
| } |
| } |
| |
| bool NetErrorTabHelper::OnMessageReceived( |
| const IPC::Message& message, |
| content::RenderFrameHost* render_frame_host) { |
| if (render_frame_host != web_contents()->GetMainFrame()) |
| return false; |
| #if BUILDFLAG(ENABLE_OFFLINE_PAGES) |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP(NetErrorTabHelper, message) |
| IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DownloadPageLater, |
| OnDownloadPageLater) |
| IPC_MESSAGE_HANDLER(ChromeViewHostMsg_SetIsShowingDownloadButtonInErrorPage, |
| OnSetIsShowingDownloadButtonInErrorPage) |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| |
| return handled; |
| #else |
| return false; |
| #endif // BUILDFLAG(ENABLE_OFFLINE_PAGES) |
| } |
| |
| NetErrorTabHelper::NetErrorTabHelper(WebContents* contents) |
| : WebContentsObserver(contents), |
| network_diagnostics_bindings_(contents, this), |
| network_easter_egg_bindings_(contents, this), |
| is_error_page_(false), |
| dns_error_active_(false), |
| dns_error_page_committed_(false), |
| #if BUILDFLAG(ENABLE_OFFLINE_PAGES) |
| is_showing_download_button_in_error_page_(false), |
| #endif // BUILDFLAG(ENABLE_OFFLINE_PAGES) |
| dns_probe_status_(error_page::DNS_PROBE_POSSIBLE), |
| weak_factory_(this) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| // If this helper is under test, it won't have a WebContents. |
| if (contents) |
| InitializePref(contents); |
| } |
| |
| void NetErrorTabHelper::OnMainFrameDnsError() { |
| if (ProbesAllowed()) { |
| // Don't start more than one probe at a time. |
| if (dns_probe_status_ != error_page::DNS_PROBE_STARTED) { |
| StartDnsProbe(); |
| dns_probe_status_ = error_page::DNS_PROBE_STARTED; |
| } |
| } else { |
| dns_probe_status_ = error_page::DNS_PROBE_NOT_RUN; |
| } |
| } |
| |
| void NetErrorTabHelper::StartDnsProbe() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(dns_error_active_); |
| DCHECK_NE(error_page::DNS_PROBE_STARTED, dns_probe_status_); |
| |
| DVLOG(1) << "Starting DNS probe."; |
| |
| DnsProbeService* probe_service = DnsProbeServiceFactory::GetForContext( |
| web_contents()->GetBrowserContext()); |
| probe_service->ProbeDns(base::BindOnce(&NetErrorTabHelper::OnDnsProbeFinished, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void NetErrorTabHelper::OnDnsProbeFinished(DnsProbeStatus result) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK_EQ(error_page::DNS_PROBE_STARTED, dns_probe_status_); |
| DCHECK(error_page::DnsProbeStatusIsFinished(result)); |
| |
| DVLOG(1) << "Finished DNS probe with result " |
| << DnsProbeStatusToString(result) << "."; |
| |
| dns_probe_status_ = result; |
| |
| if (dns_error_page_committed_) |
| SendInfo(); |
| } |
| |
| #if BUILDFLAG(ENABLE_OFFLINE_PAGES) |
| void NetErrorTabHelper::OnDownloadPageLater() { |
| // Makes sure that this is coming from an error page. |
| content::NavigationEntry* entry = |
| web_contents()->GetController().GetLastCommittedEntry(); |
| if (!entry || entry->GetPageType() != content::PAGE_TYPE_ERROR) |
| return; |
| |
| // Only download the page for HTTP/HTTPS URLs. |
| GURL url(entry->GetVirtualURL()); |
| if (!url.SchemeIsHTTPOrHTTPS()) |
| return; |
| |
| DownloadPageLaterHelper(url); |
| } |
| |
| void NetErrorTabHelper::OnSetIsShowingDownloadButtonInErrorPage( |
| bool is_showing_download_button) { |
| is_showing_download_button_in_error_page_ = is_showing_download_button; |
| } |
| #endif // BUILDFLAG(ENABLE_OFFLINE_PAGES) |
| |
| // static |
| void NetErrorTabHelper::RegisterProfilePrefs( |
| user_prefs::PrefRegistrySyncable* prefs) { |
| // prefs::kAlternateErrorPagesEnabled is registered by |
| // NavigationCorrectionTabObserver. |
| |
| prefs->RegisterIntegerPref(prefs::kNetworkEasterEggHighScore, 0, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); |
| } |
| |
| void NetErrorTabHelper::InitializePref(WebContents* contents) { |
| DCHECK(contents); |
| |
| BrowserContext* browser_context = contents->GetBrowserContext(); |
| Profile* profile = Profile::FromBrowserContext(browser_context); |
| resolve_errors_with_web_service_.Init( |
| prefs::kAlternateErrorPagesEnabled, |
| profile->GetPrefs()); |
| easter_egg_high_score_.Init(prefs::kNetworkEasterEggHighScore, |
| profile->GetPrefs()); |
| } |
| |
| bool NetErrorTabHelper::ProbesAllowed() const { |
| if (testing_state_ != TESTING_DEFAULT) |
| return testing_state_ == TESTING_FORCE_ENABLED; |
| |
| // TODO(juliatuttle): Disable on mobile? |
| return *resolve_errors_with_web_service_; |
| } |
| |
| void NetErrorTabHelper::SendInfo() { |
| DCHECK_NE(error_page::DNS_PROBE_POSSIBLE, dns_probe_status_); |
| DCHECK(dns_error_page_committed_); |
| |
| DVLOG(1) << "Sending status " << DnsProbeStatusToString(dns_probe_status_); |
| content::RenderFrameHost* rfh = web_contents()->GetMainFrame(); |
| |
| chrome::mojom::NetworkDiagnosticsClientAssociatedPtr client; |
| rfh->GetRemoteAssociatedInterfaces()->GetInterface(&client); |
| client->DNSProbeStatus(dns_probe_status_); |
| |
| if (!dns_probe_status_snoop_callback_.is_null()) |
| dns_probe_status_snoop_callback_.Run(dns_probe_status_); |
| } |
| |
| void NetErrorTabHelper::RunNetworkDiagnostics(const GURL& url) { |
| // Only run diagnostics on HTTP or HTTPS URLs. Shouldn't receive URLs with |
| // any other schemes, but the renderer is not trusted. |
| if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS()) |
| return; |
| |
| // Sanitize URL prior to running diagnostics on it. |
| RunNetworkDiagnosticsHelper(url.GetOrigin().spec()); |
| } |
| |
| void NetErrorTabHelper::RunNetworkDiagnosticsHelper( |
| const std::string& sanitized_url) { |
| // The button shouldn't even be shown in this case, but still best to be safe, |
| // since the renderer isn't trusted. |
| if (!CanShowNetworkDiagnosticsDialog(web_contents())) |
| return; |
| |
| if (network_diagnostics_bindings_.GetCurrentTargetFrame() |
| != web_contents()->GetMainFrame()) { |
| return; |
| } |
| |
| ShowNetworkDiagnosticsDialog(web_contents(), sanitized_url); |
| } |
| |
| #if BUILDFLAG(ENABLE_OFFLINE_PAGES) |
| void NetErrorTabHelper::DownloadPageLaterHelper(const GURL& page_url) { |
| offline_pages::OfflinePageUtils::ScheduleDownload( |
| web_contents(), offline_pages::kAsyncNamespace, page_url, |
| offline_pages::OfflinePageUtils::DownloadUIActionFlags::PROMPT_DUPLICATE); |
| } |
| #endif // BUILDFLAG(ENABLE_OFFLINE_PAGES) |
| |
| void NetErrorTabHelper::GetHighScore(GetHighScoreCallback callback) { |
| std::move(callback).Run( |
| static_cast<uint32_t>(easter_egg_high_score_.GetValue())); |
| } |
| |
| void NetErrorTabHelper::UpdateHighScore(uint32_t high_score) { |
| if (high_score <= static_cast<uint32_t>(easter_egg_high_score_.GetValue())) |
| return; |
| easter_egg_high_score_.SetValue(static_cast<int>(high_score)); |
| } |
| |
| void NetErrorTabHelper::ResetHighScore() { |
| easter_egg_high_score_.SetValue(0); |
| } |
| |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(NetErrorTabHelper) |
| |
| } // namespace chrome_browser_net |