blob: 59bb789595c7a0e8c86b5e43620e0aefd980bd2e [file] [log] [blame]
// Copyright 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_core.h"
#include <stddef.h>
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "components/error_page/common/localized_error.h"
#include "content/public/common/content_switches.h"
#include "content/public/renderer/render_thread.h"
#include "net/base/net_errors.h"
#include "url/gurl.h"
namespace {
// Returns whether |error| is a DNS-related error (and therefore whether
// the tab helper should start a DNS probe after receiving it).
bool IsNetDnsError(const error_page::Error& error) {
return error.domain() == error_page::Error::kNetErrorDomain &&
net::IsHostnameResolutionError(error.reason());
}
} // namespace
struct NetErrorHelperCore::ErrorPageInfo {
ErrorPageInfo(error_page::Error error, bool was_failed_post)
: error(error), was_failed_post(was_failed_post) {}
// Information about the failed page load.
error_page::Error error;
bool was_failed_post;
// Information about the status of the error page.
// True if a page is a DNS error page and has not yet received a final DNS
// probe status.
bool needs_dns_updates = false;
bool dns_probe_complete = false;
// True if a page has completed loading, at which point it can receive
// updates.
bool is_finished_loading = false;
error_page::LocalizedError::PageState page_state;
};
NetErrorHelperCore::NetErrorHelperCore(Delegate* delegate)
: delegate_(delegate),
last_probe_status_(error_page::DNS_PROBE_POSSIBLE),
can_show_network_diagnostics_dialog_(false),
navigation_from_button_(NO_BUTTON)
#if defined(OS_ANDROID)
,
page_auto_fetcher_helper_(
std::make_unique<PageAutoFetcherHelper>(delegate->GetRenderFrame()))
#endif
{
}
NetErrorHelperCore::~NetErrorHelperCore() = default;
void NetErrorHelperCore::OnCommitLoad(FrameType frame_type, const GURL& url) {
if (frame_type != MAIN_FRAME)
return;
#if defined(OS_ANDROID)
// Don't need this state. It will be refreshed if another error page is
// loaded.
available_content_helper_.Reset();
page_auto_fetcher_helper_->OnCommitLoad();
#endif
// Track if an error occurred due to a page button press.
// This isn't perfect; if (for instance), the server is slow responding
// to a request generated from the page reload button, and the user hits
// the browser reload button, this code will still believe the
// result is from the page reload button.
if (committed_error_page_info_ && pending_error_page_info_ &&
navigation_from_button_ != NO_BUTTON &&
committed_error_page_info_->error.url() ==
pending_error_page_info_->error.url()) {
DCHECK(navigation_from_button_ == RELOAD_BUTTON);
RecordEvent(error_page::NETWORK_ERROR_PAGE_RELOAD_BUTTON_ERROR);
}
navigation_from_button_ = NO_BUTTON;
committed_error_page_info_ = std::move(pending_error_page_info_);
}
void NetErrorHelperCore::ErrorPageLoadedWithFinalErrorCode() {
ErrorPageInfo* page_info = committed_error_page_info_.get();
DCHECK(page_info);
error_page::Error updated_error = GetUpdatedError(*page_info);
if (page_info->page_state.is_offline_error)
RecordEvent(error_page::NETWORK_ERROR_PAGE_OFFLINE_ERROR_SHOWN);
#if defined(OS_ANDROID)
// The fetch functions shouldn't be triggered multiple times per page load.
if (page_info->page_state.offline_content_feature_enabled) {
available_content_helper_.FetchAvailableContent(base::BindOnce(
&Delegate::OfflineContentAvailable, base::Unretained(delegate_)));
}
// |TrySchedule()| shouldn't be called more than once per page.
if (page_info->page_state.auto_fetch_allowed) {
page_auto_fetcher_helper_->TrySchedule(
false, base::BindOnce(&Delegate::SetAutoFetchState,
base::Unretained(delegate_)));
}
#endif // defined(OS_ANDROID)
if (page_info->page_state.download_button_shown)
RecordEvent(error_page::NETWORK_ERROR_PAGE_DOWNLOAD_BUTTON_SHOWN);
if (page_info->page_state.reload_button_shown)
RecordEvent(error_page::NETWORK_ERROR_PAGE_RELOAD_BUTTON_SHOWN);
delegate_->SetIsShowingDownloadButton(
page_info->page_state.download_button_shown);
}
void NetErrorHelperCore::OnFinishLoad(FrameType frame_type) {
if (frame_type != MAIN_FRAME)
return;
if (!committed_error_page_info_)
return;
committed_error_page_info_->is_finished_loading = true;
RecordEvent(error_page::NETWORK_ERROR_PAGE_SHOWN);
delegate_->SetIsShowingDownloadButton(
committed_error_page_info_->page_state.download_button_shown);
delegate_->EnablePageHelperFunctions();
DVLOG(1) << "Error page finished loading; sending saved status.";
if (committed_error_page_info_->needs_dns_updates) {
if (last_probe_status_ != error_page::DNS_PROBE_POSSIBLE)
UpdateErrorPage();
} else {
ErrorPageLoadedWithFinalErrorCode();
}
}
void NetErrorHelperCore::PrepareErrorPage(FrameType frame_type,
const error_page::Error& error,
bool is_failed_post,
std::string* error_html) {
if (frame_type == MAIN_FRAME) {
pending_error_page_info_.reset(new ErrorPageInfo(error, is_failed_post));
PrepareErrorPageForMainFrame(pending_error_page_info_.get(), error_html);
} else if (error_html) {
delegate_->GenerateLocalizedErrorPage(
error, is_failed_post,
false /* No diagnostics dialogs allowed for subframes. */, error_html);
}
}
void NetErrorHelperCore::OnNetErrorInfo(error_page::DnsProbeStatus status) {
DCHECK_NE(error_page::DNS_PROBE_POSSIBLE, status);
last_probe_status_ = status;
if (!committed_error_page_info_ ||
!committed_error_page_info_->needs_dns_updates ||
!committed_error_page_info_->is_finished_loading) {
return;
}
UpdateErrorPage();
}
void NetErrorHelperCore::OnSetCanShowNetworkDiagnosticsDialog(
bool can_show_network_diagnostics_dialog) {
can_show_network_diagnostics_dialog_ = can_show_network_diagnostics_dialog;
}
void NetErrorHelperCore::OnEasterEggHighScoreReceived(int high_score) {
if (!committed_error_page_info_ ||
!committed_error_page_info_->is_finished_loading) {
return;
}
delegate_->InitializeErrorPageEasterEggHighScore(high_score);
}
void NetErrorHelperCore::PrepareErrorPageForMainFrame(
ErrorPageInfo* pending_error_page_info,
std::string* error_html) {
std::string error_param;
error_page::Error error = pending_error_page_info->error;
if (IsNetDnsError(pending_error_page_info->error)) {
// The last probe status needs to be reset if this is a DNS error. This
// means that if a DNS error page is committed but has not yet finished
// loading, a DNS probe status scheduled to be sent to it may be thrown
// out, but since the new error page should trigger a new DNS probe, it
// will just get the results for the next page load.
last_probe_status_ = error_page::DNS_PROBE_POSSIBLE;
pending_error_page_info->needs_dns_updates = true;
error = GetUpdatedError(*pending_error_page_info);
}
if (error_html) {
pending_error_page_info->page_state = delegate_->GenerateLocalizedErrorPage(
error, pending_error_page_info->was_failed_post,
can_show_network_diagnostics_dialog_, error_html);
}
}
void NetErrorHelperCore::UpdateErrorPage() {
DCHECK(committed_error_page_info_->needs_dns_updates);
DCHECK(committed_error_page_info_->is_finished_loading);
DCHECK_NE(error_page::DNS_PROBE_POSSIBLE, last_probe_status_);
UMA_HISTOGRAM_ENUMERATION("DnsProbe.ErrorPageUpdateStatus",
last_probe_status_, error_page::DNS_PROBE_MAX);
// Every status other than error_page::DNS_PROBE_POSSIBLE and
// error_page::DNS_PROBE_STARTED is a final status code. Once one is reached,
// the page does not need further updates.
if (last_probe_status_ != error_page::DNS_PROBE_STARTED) {
committed_error_page_info_->needs_dns_updates = false;
committed_error_page_info_->dns_probe_complete = true;
}
error_page::LocalizedError::PageState new_state =
delegate_->UpdateErrorPage(GetUpdatedError(*committed_error_page_info_),
committed_error_page_info_->was_failed_post,
can_show_network_diagnostics_dialog_);
committed_error_page_info_->page_state = std::move(new_state);
if (!committed_error_page_info_->needs_dns_updates)
ErrorPageLoadedWithFinalErrorCode();
}
error_page::Error NetErrorHelperCore::GetUpdatedError(
const ErrorPageInfo& error_info) const {
// If a probe didn't run or wasn't conclusive, restore the original error.
const bool dns_probe_used =
error_info.needs_dns_updates || error_info.dns_probe_complete;
if (!dns_probe_used || last_probe_status_ == error_page::DNS_PROBE_NOT_RUN ||
last_probe_status_ == error_page::DNS_PROBE_FINISHED_INCONCLUSIVE) {
return error_info.error;
}
return error_page::Error::DnsProbeError(
error_info.error.url(), last_probe_status_,
error_info.error.stale_copy_in_cache());
}
void NetErrorHelperCore::Reload() {
if (!committed_error_page_info_)
return;
delegate_->ReloadFrame();
}
#if defined(OS_ANDROID)
void NetErrorHelperCore::SetPageAutoFetcherHelperForTesting(
std::unique_ptr<PageAutoFetcherHelper> page_auto_fetcher_helper) {
page_auto_fetcher_helper_ = std::move(page_auto_fetcher_helper);
}
#endif
void NetErrorHelperCore::ExecuteButtonPress(Button button) {
// If there's no committed error page, should not be invoked.
DCHECK(committed_error_page_info_);
switch (button) {
case RELOAD_BUTTON:
RecordEvent(error_page::NETWORK_ERROR_PAGE_RELOAD_BUTTON_CLICKED);
navigation_from_button_ = RELOAD_BUTTON;
Reload();
return;
case MORE_BUTTON:
// Visual effects on page are handled in Javascript code.
RecordEvent(error_page::NETWORK_ERROR_PAGE_MORE_BUTTON_CLICKED);
return;
case EASTER_EGG:
RecordEvent(error_page::NETWORK_ERROR_EASTER_EGG_ACTIVATED);
delegate_->RequestEasterEggHighScore();
return;
case DIAGNOSE_ERROR:
RecordEvent(error_page::NETWORK_ERROR_DIAGNOSE_BUTTON_CLICKED);
delegate_->DiagnoseError(committed_error_page_info_->error.url());
return;
case DOWNLOAD_BUTTON:
RecordEvent(error_page::NETWORK_ERROR_PAGE_DOWNLOAD_BUTTON_CLICKED);
delegate_->DownloadPageLater();
return;
case NO_BUTTON:
NOTREACHED();
return;
}
}
void NetErrorHelperCore::LaunchOfflineItem(const std::string& id,
const std::string& name_space) {
#if defined(OS_ANDROID)
available_content_helper_.LaunchItem(id, name_space);
#endif
}
void NetErrorHelperCore::LaunchDownloadsPage() {
#if defined(OS_ANDROID)
available_content_helper_.LaunchDownloadsPage();
#endif
}
void NetErrorHelperCore::SavePageForLater() {
#if defined(OS_ANDROID)
page_auto_fetcher_helper_->TrySchedule(
/*user_requested=*/true, base::BindOnce(&Delegate::SetAutoFetchState,
base::Unretained(delegate_)));
#endif
}
void NetErrorHelperCore::CancelSavePage() {
#if defined(OS_ANDROID)
page_auto_fetcher_helper_->CancelSchedule();
#endif
}
void NetErrorHelperCore::ListVisibilityChanged(bool is_visible) {
#if defined(OS_ANDROID)
available_content_helper_.ListVisibilityChanged(is_visible);
#endif
}