blob: ea3b62a023ae4dc1b1e9f095dd10be70cc38d8c8 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// 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 <memory>
#include <string>
#include <string_view>
#include <utility>
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/i18n/rtl.h"
#include "base/json/json_writer.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_resource_request_blocked_reason.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/renderer/process_state.h"
#include "components/error_page/common/error.h"
#include "components/error_page/common/localized_error.h"
#include "components/error_page/common/net_error_info.h"
#include "components/grit/components_resources.h"
#include "components/offline_pages/buildflags/buildflags.h"
#include "components/offline_pages/core/offline_page_feature.h"
#include "components/security_interstitials/content/renderer/security_interstitial_page_controller.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/renderer/content_renderer_client.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_thread.h"
#include "net/base/net_errors.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "skia/ext/skia_utils_base.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-shared.h"
#include "third_party/blink/public/platform/web_data.h"
#include "third_party/blink/public/platform/web_security_origin.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/public/platform/web_url_request.h"
#include "third_party/blink/public/platform/web_url_response.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_frame.h"
#include "third_party/blink/public/web/web_history_item.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/webui/web_ui_util.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_ANDROID)
#include "chrome/common/offline_page_auto_fetcher.mojom.h"
#endif
using content::RenderFrame;
using content::RenderFrameObserver;
using content::RenderThread;
using error_page::DnsProbeStatus;
using error_page::DnsProbeStatusToString;
using error_page::LocalizedError;
namespace {
NetErrorHelperCore::FrameType GetFrameType(RenderFrame* render_frame) {
if (render_frame->IsMainFrame())
return NetErrorHelperCore::MAIN_FRAME;
return NetErrorHelperCore::SUB_FRAME;
}
bool IsExtensionExtendedErrorCode(int extended_error_code) {
return extended_error_code ==
static_cast<int>(ChromeResourceRequestBlockedReason::kExtension);
}
bool IsAutoFetchFeatureEnabled() {
#if BUILDFLAG(IS_ANDROID)
return base::FeatureList::IsEnabled(features::kOfflineAutoFetch);
#else // BUILDFLAG(IS_ANDROID)
return false;
#endif // BUILDFLAG(IS_ANDROID)
}
bool IsRunningInForcedAppMode() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kForceAppMode);
}
} // namespace
NetErrorHelper::NetErrorHelper(RenderFrame* render_frame)
: RenderFrameObserver(render_frame),
content::RenderFrameObserverTracker<NetErrorHelper>(render_frame) {
// TODO(mmenke): Consider only creating a NetErrorHelperCore for main frames.
// subframes don't need any of the NetErrorHelperCore's extra logic.
core_ = std::make_unique<NetErrorHelperCore>(this);
render_frame->GetAssociatedInterfaceRegistry()
->AddInterface<chrome::mojom::NetworkDiagnosticsClient>(
base::BindRepeating(
&NetErrorHelper::OnNetworkDiagnosticsClientRequest,
base::Unretained(this)));
}
NetErrorHelper::~NetErrorHelper() = default;
void NetErrorHelper::ButtonPressed(NetErrorHelperCore::Button button) {
core_->ExecuteButtonPress(button);
}
void NetErrorHelper::SavePageForLater() {
core_->SavePageForLater();
}
void NetErrorHelper::CancelSavePage() {
core_->CancelSavePage();
}
content::RenderFrame* NetErrorHelper::GetRenderFrame() {
return render_frame();
}
void NetErrorHelper::DidCommitProvisionalLoad(ui::PageTransition transition) {
// Invalidate weak pointers from the old error page controller. 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()->GetDocument().Url());
}
void NetErrorHelper::DidFinishLoad() {
core_->OnFinishLoad(GetFrameType(render_frame()));
}
void NetErrorHelper::OnDestruct() {
delete this;
}
void NetErrorHelper::PrepareErrorPage(
const error_page::Error& error,
bool is_failed_post,
content::mojom::AlternativeErrorPageOverrideInfoPtr
alternative_error_page_info,
std::string* error_html) {
core_->PrepareErrorPage(GetFrameType(render_frame()), error, is_failed_post,
std::move(alternative_error_page_info), error_html);
}
chrome::mojom::NetworkDiagnostics*
NetErrorHelper::GetRemoteNetworkDiagnostics() {
if (!remote_network_diagnostics_) {
render_frame()->GetRemoteAssociatedInterfaces()
->GetInterface(&remote_network_diagnostics_);
}
return remote_network_diagnostics_.get();
}
chrome::mojom::NetworkEasterEgg* NetErrorHelper::GetRemoteNetworkEasterEgg() {
if (!remote_network_easter_egg_) {
render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(
&remote_network_easter_egg_);
}
return remote_network_easter_egg_.get();
}
chrome::mojom::NetErrorPageSupport*
NetErrorHelper::GetRemoteNetErrorPageSupport() {
if (!remote_net_error_page_support_) {
render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(
&remote_net_error_page_support_);
}
return remote_net_error_page_support_.get();
}
LocalizedError::PageState NetErrorHelper::GenerateLocalizedErrorPage(
const error_page::Error& error,
bool is_failed_post,
bool can_show_network_diagnostics_dialog,
content::mojom::AlternativeErrorPageOverrideInfoPtr
alternative_error_page_info,
std::string* error_html) {
error_html->clear();
int resource_id = IDR_NET_ERROR_HTML;
LocalizedError::PageState page_state;
// If the user is viewing an offline web app then a default page is shown
// rather than the dino.
if (alternative_error_page_info &&
alternative_error_page_info->alternative_error_page_params
.FindBool(error_page::kOverrideErrorPage)
.value_or(false)) {
base::UmaHistogramSparse("Net.ErrorPageCounts.WebAppAlternativeErrorPage",
-error.reason());
resource_id = alternative_error_page_info->resource_id;
page_state = LocalizedError::GetPageStateForOverriddenErrorPage(
std::move(alternative_error_page_info->alternative_error_page_params),
error.reason(), error.domain(), error.url(),
RenderThread::Get()->GetLocale());
} else {
if (alternative_error_page_info) {
error_page_params_ =
alternative_error_page_info->alternative_error_page_params.Clone();
} else {
error_page_params_.clear();
}
page_state = LocalizedError::GetPageState(
error.reason(), error.domain(), error.url(), is_failed_post,
error.resolve_error_info().is_secure_network_error,
error.stale_copy_in_cache(), can_show_network_diagnostics_dialog,
process_state::IsIncognitoProcess(), IsAutoFetchFeatureEnabled(),
IsRunningInForcedAppMode(), RenderThread::Get()->GetLocale(),
IsExtensionExtendedErrorCode(error.extended_reason()),
&error_page_params_);
}
std::string template_html =
ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
resource_id);
DCHECK(!template_html.empty()) << "unable to load template.";
*error_html = webui::GetLocalizedHtml(template_html, page_state.strings);
return page_state;
}
void NetErrorHelper::EnablePageHelperFunctions() {
NetErrorPageController::Install(
render_frame(), weak_controller_delegate_factory_.GetWeakPtr());
}
LocalizedError::PageState NetErrorHelper::UpdateErrorPage(
const error_page::Error& error,
bool is_failed_post,
bool can_show_network_diagnostics_dialog) {
LocalizedError::PageState page_state = LocalizedError::GetPageState(
error.reason(), error.domain(), error.url(), is_failed_post,
error.resolve_error_info().is_secure_network_error,
error.stale_copy_in_cache(), can_show_network_diagnostics_dialog,
process_state::IsIncognitoProcess(), IsAutoFetchFeatureEnabled(),
IsRunningInForcedAppMode(), RenderThread::Get()->GetLocale(),
IsExtensionExtendedErrorCode(error.extended_reason()),
&error_page_params_);
std::string json = base::WriteJson(page_state.strings).value_or("");
std::string js = "if (window.updateForDnsProbe) "
"updateForDnsProbe(" + json + ");";
std::u16string js16;
if (base::UTF8ToUTF16(js.c_str(), js.length(), &js16)) {
render_frame()->ExecuteJavaScript(js16);
} else {
NOTREACHED();
}
return page_state;
}
void NetErrorHelper::InitializeErrorPageEasterEggHighScore(int high_score) {
std::string js = base::StringPrintf(
"if (window.initializeEasterEggHighScore) "
"initializeEasterEggHighScore(%i);",
high_score);
std::u16string js16;
if (!base::UTF8ToUTF16(js.c_str(), js.length(), &js16)) {
NOTREACHED();
}
render_frame()->ExecuteJavaScript(js16);
}
void NetErrorHelper::RequestEasterEggHighScore() {
GetRemoteNetworkEasterEgg()->GetHighScore(base::BindOnce(
[](NetErrorHelper* helper, uint32_t high_score) {
helper->core_->OnEasterEggHighScoreReceived(high_score);
},
base::Unretained(this)));
}
void NetErrorHelper::UpdateEasterEggHighScore(int high_score) {
GetRemoteNetworkEasterEgg()->UpdateHighScore(high_score);
}
void NetErrorHelper::ResetEasterEggHighScore() {
GetRemoteNetworkEasterEgg()->ResetHighScore();
}
void NetErrorHelper::ReloadFrame() {
render_frame()->GetWebFrame()->StartReload(blink::WebFrameLoadType::kReload);
}
void NetErrorHelper::DiagnoseError(const GURL& page_url) {
GetRemoteNetworkDiagnostics()->RunNetworkDiagnostics(page_url);
}
void NetErrorHelper::PortalSignin() {
#if BUILDFLAG(IS_CHROMEOS)
GetRemoteNetErrorPageSupport()->ShowPortalSignin();
#endif
}
void NetErrorHelper::DownloadPageLater() {
#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
GetRemoteNetErrorPageSupport()->DownloadPageLater();
#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
}
void NetErrorHelper::SetIsShowingDownloadButton(bool show) {
#if BUILDFLAG(ENABLE_OFFLINE_PAGES)
GetRemoteNetErrorPageSupport()->SetIsShowingDownloadButtonInErrorPage(show);
#endif // BUILDFLAG(ENABLE_OFFLINE_PAGES)
}
#if BUILDFLAG(IS_ANDROID)
void NetErrorHelper::SetAutoFetchState(
chrome::mojom::OfflinePageAutoFetcherScheduleResult result) {
const char* scheduled = "false";
const char* can_schedule = "false";
switch (result) {
case chrome::mojom::OfflinePageAutoFetcherScheduleResult::kAlreadyScheduled:
case chrome::mojom::OfflinePageAutoFetcherScheduleResult::kScheduled:
scheduled = "true";
can_schedule = "true";
break;
case chrome::mojom::OfflinePageAutoFetcherScheduleResult::kOtherError:
break;
case chrome::mojom::OfflinePageAutoFetcherScheduleResult::kNotEnoughQuota:
can_schedule = "true";
break;
}
render_frame()->ExecuteJavaScript(base::UTF8ToUTF16(base::StrCat(
{"setAutoFetchState(", scheduled, ", ", can_schedule, ");"})));
}
#endif // BUILDFLAG(IS_ANDROID)
void NetErrorHelper::DNSProbeStatus(int32_t 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::OnNetworkDiagnosticsClientRequest(
mojo::PendingAssociatedReceiver<chrome::mojom::NetworkDiagnosticsClient>
receiver) {
network_diagnostics_client_receivers_.Add(this, std::move(receiver));
}
void NetErrorHelper::SetCanShowNetworkDiagnosticsDialog(bool can_show) {
core_->OnSetCanShowNetworkDiagnosticsDialog(can_show);
}