#include "chrome/renderer/net/net_error_helper_core.h"
#include <stddef.h>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_util.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/common/available_offline_content.mojom.h"
#include "chrome/renderer/net/available_offline_content_helper.h"
#include "components/error_page/common/error.h"
#include "components/error_page/common/net_error_info.h"
#include "components/grit/components_resources.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/mock_render_thread.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "net/base/net_errors.h"
#include "net/dns/public/resolve_error_info.h"
#include "skia/ext/skia_utils_base.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_ANDROID)
#include "chrome/common/offline_page_auto_fetcher.mojom.h"
#endif
namespace {
const char kFailedUrl[] = …;
std::string ErrorToString(const error_page::Error& error, bool is_failed_post) { … }
error_page::Error ProbeError(error_page::DnsProbeStatus status) { … }
error_page::Error NetErrorForURL(net::Error net_error, const GURL& url) { … }
error_page::Error NetError(net::Error net_error) { … }
std::string ProbeErrorString(error_page::DnsProbeStatus status) { … }
std::string NetErrorStringForURL(net::Error net_error, const GURL& url) { … }
std::string NetErrorString(net::Error net_error) { … }
error_page::LocalizedError::PageState GetErrorPageState(int error_code,
bool is_kiosk_mode) { … }
class NetErrorHelperCoreTest : public testing::Test,
public NetErrorHelperCore::Delegate { … };
TEST_F(NetErrorHelperCoreTest, Null) { … }
TEST_F(NetErrorHelperCoreTest, SuccessfulPageLoad) { … }
TEST_F(NetErrorHelperCoreTest, MainFrameNonDnsError) { … }
TEST_F(NetErrorHelperCoreTest, MainFrameNonDnsErrorSpuriousStatus) { … }
TEST_F(NetErrorHelperCoreTest,
UserModeErrBlockedByAdministratorContainsDetails) { … }
TEST_F(NetErrorHelperCoreTest,
KioskModeErrBlockedByAdministratorDoenNotContainDetails) { … }
TEST_F(NetErrorHelperCoreTest, SubFrameErrorWithCustomErrorPage) { … }
TEST_F(NetErrorHelperCoreTest, SubFrameDnsError) { … }
TEST_F(NetErrorHelperCoreTest, SubFrameDnsErrorSpuriousStatus) { … }
TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbe) { … }
TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbeNotRun) { … }
TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbeInconclusive) { … }
TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbeNoInternet) { … }
TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbeBadConfig) { … }
TEST_F(NetErrorHelperCoreTest, FinishedAfterStartProbe) { … }
TEST_F(NetErrorHelperCoreTest, FinishedBeforeProbePost) { … }
TEST_F(NetErrorHelperCoreTest, ProbeFinishesEarly) { … }
TEST_F(NetErrorHelperCoreTest, TwoErrorsWithProbes) { … }
TEST_F(NetErrorHelperCoreTest, TwoErrorsWithProbesAfterSecondStarts) { … }
TEST_F(NetErrorHelperCoreTest, ErrorPageLoadInterrupted) { … }
TEST_F(NetErrorHelperCoreTest, ExplicitReloadSucceeds) { … }
TEST_F(NetErrorHelperCoreTest, CanNotShowNetworkDiagnostics) { … }
TEST_F(NetErrorHelperCoreTest, CanShowNetworkDiagnostics) { … }
TEST_F(NetErrorHelperCoreTest, AlternativeErrorPageNoUpdates) { … }
#if BUILDFLAG(IS_ANDROID)
TEST_F(NetErrorHelperCoreTest, Download) {
DoErrorLoad(net::ERR_INTERNET_DISCONNECTED);
EXPECT_EQ(0, download_count());
core()->ExecuteButtonPress(NetErrorHelperCore::DOWNLOAD_BUTTON);
EXPECT_EQ(1, download_count());
}
const char kThumbnailDataURI[] = "data:image/png;base64,abc";
const char kFaviconDataURI[] = "data:image/png;base64,def";
std::vector<chrome::mojom::AvailableOfflineContentPtr>
GetFakeAvailableContent() {
std::vector<chrome::mojom::AvailableOfflineContentPtr> content;
content.push_back(chrome::mojom::AvailableOfflineContent::New(
"ID", "name_space", "title", "snippet", "date_modified", "attribution",
GURL(kThumbnailDataURI), GURL(kFaviconDataURI),
chrome::mojom::AvailableContentType::kPrefetchedPage));
content.push_back(chrome::mojom::AvailableOfflineContent::New(
"ID2", "name_space2", "title2", "snippet2", "date_modified2",
"attribution2", GURL(kThumbnailDataURI), GURL(kFaviconDataURI),
chrome::mojom::AvailableContentType::kOtherPage));
return content;
}
const std::string GetExpectedAvailableContentAsJson() {
std::string want_json = R"([
{
"ID": "ID",
"attribution_base64": "AGEAdAB0AHIAaQBiAHUAdABpAG8Abg==",
"content_type": 0,
"date_modified": "date_modified",
"favicon_data_uri": "data:image/png;base64,def",
"name_space": "name_space",
"snippet_base64": "AHMAbgBpAHAAcABlAHQ=",
"thumbnail_data_uri": "data:image/png;base64,abc",
"title_base64": "AHQAaQB0AGwAZQ=="
},
{
"ID": "ID2",
"attribution_base64": "AGEAdAB0AHIAaQBiAHUAdABpAG8AbgAy",
"content_type": 3,
"date_modified": "date_modified2",
"favicon_data_uri": "data:image/png;base64,def",
"name_space": "name_space2",
"snippet_base64": "AHMAbgBpAHAAcABlAHQAMg==",
"thumbnail_data_uri": "data:image/png;base64,abc",
"title_base64": "AHQAaQB0AGwAZQAy"
}
])";
base::ReplaceChars(want_json, base::kWhitespaceASCII, "", &want_json);
return want_json;
}
class FakeAvailableOfflineContentProvider
: public chrome::mojom::AvailableOfflineContentProvider {
public:
FakeAvailableOfflineContentProvider() = default;
FakeAvailableOfflineContentProvider(
const FakeAvailableOfflineContentProvider&) = delete;
FakeAvailableOfflineContentProvider& operator=(
const FakeAvailableOfflineContentProvider&) = delete;
void List(ListCallback callback) override {
if (return_content_) {
std::move(callback).Run(list_visible_by_prefs_,
GetFakeAvailableContent());
} else {
std::move(callback).Run(list_visible_by_prefs_, {});
}
}
MOCK_METHOD2(LaunchItem,
void(const std::string& item_ID, const std::string& name_space));
MOCK_METHOD1(LaunchDownloadsPage, void(bool open_prefetched_articles_tab));
MOCK_METHOD1(ListVisibilityChanged, void(bool is_visible));
void AddBinding(
mojo::PendingReceiver<chrome::mojom::AvailableOfflineContentProvider>
receiver) {
receivers_.Add(this, std::move(receiver));
}
void set_return_content(bool return_content) {
return_content_ = return_content;
}
void set_list_visible_by_prefs(bool list_visible_by_prefs) {
list_visible_by_prefs_ = list_visible_by_prefs;
}
private:
bool return_content_ = true;
bool list_visible_by_prefs_ = true;
mojo::ReceiverSet<chrome::mojom::AvailableOfflineContentProvider> receivers_;
};
class NetErrorHelperCoreAvailableOfflineContentTest
: public NetErrorHelperCoreTest {
public:
void SetUp() override {
NetErrorHelperCoreTest::SetUp();
AvailableOfflineContentHelper::OverrideBinderForTesting(
base::BindRepeating(&FakeAvailableOfflineContentProvider::AddBinding,
base::Unretained(&fake_provider_)));
}
void TearDown() override {
AvailableOfflineContentHelper::OverrideBinderForTesting(
base::NullCallback());
}
protected:
FakeAvailableOfflineContentProvider fake_provider_;
base::HistogramTester histogram_tester_;
};
TEST_F(NetErrorHelperCoreAvailableOfflineContentTest, ListAvailableContent) {
set_offline_content_feature_enabled(true);
fake_provider_.set_return_content(true);
DoErrorLoad(net::ERR_INTERNET_DISCONNECTED);
task_environment()->RunUntilIdle();
EXPECT_TRUE(list_visible_by_prefs());
EXPECT_EQ(GetExpectedAvailableContentAsJson(), offline_content_json());
histogram_tester_.ExpectBucketCount(
"Net.ErrorPageCounts",
error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTIONS_SHOWN, 1);
histogram_tester_.ExpectBucketCount(
"Net.ErrorPageCounts",
error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTIONS_SHOWN_COLLAPSED, 0);
core()->LaunchOfflineItem("ID", "name_space");
histogram_tester_.ExpectBucketCount(
"Net.ErrorPageCounts",
error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTION_CLICKED, 1);
core()->LaunchDownloadsPage();
histogram_tester_.ExpectBucketCount(
"Net.ErrorPageCounts",
error_page::NETWORK_ERROR_PAGE_OFFLINE_DOWNLOADS_PAGE_CLICKED, 1);
}
TEST_F(NetErrorHelperCoreAvailableOfflineContentTest, ListHiddenByPrefs) {
set_offline_content_feature_enabled(true);
fake_provider_.set_return_content(true);
fake_provider_.set_list_visible_by_prefs(false);
DoErrorLoad(net::ERR_INTERNET_DISCONNECTED);
task_environment()->RunUntilIdle();
EXPECT_FALSE(list_visible_by_prefs());
EXPECT_EQ(GetExpectedAvailableContentAsJson(), offline_content_json());
histogram_tester_.ExpectBucketCount(
"Net.ErrorPageCounts",
error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTIONS_SHOWN, 0);
histogram_tester_.ExpectBucketCount(
"Net.ErrorPageCounts",
error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTIONS_SHOWN_COLLAPSED, 1);
core()->LaunchOfflineItem("ID", "name_space");
histogram_tester_.ExpectBucketCount(
"Net.ErrorPageCounts",
error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTION_CLICKED, 1);
core()->LaunchDownloadsPage();
histogram_tester_.ExpectBucketCount(
"Net.ErrorPageCounts",
error_page::NETWORK_ERROR_PAGE_OFFLINE_DOWNLOADS_PAGE_CLICKED, 1);
}
TEST_F(NetErrorHelperCoreAvailableOfflineContentTest, ListNoAvailableContent) {
set_offline_content_feature_enabled(true);
fake_provider_.set_return_content(false);
DoErrorLoad(net::ERR_INTERNET_DISCONNECTED);
task_environment()->RunUntilIdle();
EXPECT_TRUE(list_visible_by_prefs());
EXPECT_EQ("", offline_content_json());
histogram_tester_.ExpectBucketCount(
"Net.ErrorPageCounts",
error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTIONS_SHOWN, 0);
histogram_tester_.ExpectBucketCount(
"Net.ErrorPageCounts",
error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTIONS_SHOWN_COLLAPSED, 0);
}
TEST_F(NetErrorHelperCoreAvailableOfflineContentTest, NotAllowed) {
set_offline_content_feature_enabled(false);
fake_provider_.set_return_content(true);
DoErrorLoad(net::ERR_INTERNET_DISCONNECTED);
task_environment()->RunUntilIdle();
EXPECT_TRUE(list_visible_by_prefs());
EXPECT_EQ("", offline_content_json());
histogram_tester_.ExpectBucketCount(
"Net.ErrorPageCounts",
error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTIONS_SHOWN, 0);
histogram_tester_.ExpectBucketCount(
"Net.ErrorPageCounts",
error_page::NETWORK_ERROR_PAGE_OFFLINE_SUGGESTIONS_SHOWN_COLLAPSED, 0);
}
class FakeOfflinePageAutoFetcher
: public chrome::mojom::OfflinePageAutoFetcher {
public:
FakeOfflinePageAutoFetcher() = default;
FakeOfflinePageAutoFetcher(const FakeOfflinePageAutoFetcher&) = delete;
FakeOfflinePageAutoFetcher& operator=(const FakeOfflinePageAutoFetcher&) =
delete;
struct TryScheduleParameters {
bool user_requested;
TryScheduleCallback callback;
};
void TrySchedule(bool user_requested, TryScheduleCallback callback) override {
try_schedule_calls_.push_back({user_requested, std::move(callback)});
}
void CancelSchedule() override { cancel_calls_++; }
void AddReceiver(
mojo::PendingReceiver<chrome::mojom::OfflinePageAutoFetcher> receiver) {
receivers_.Add(this, std::move(receiver));
}
int cancel_calls() const { return cancel_calls_; }
std::vector<TryScheduleParameters> take_try_schedule_calls() {
std::vector<TryScheduleParameters> result = std::move(try_schedule_calls_);
try_schedule_calls_.clear();
return result;
}
private:
mojo::ReceiverSet<chrome::mojom::OfflinePageAutoFetcher> receivers_;
int cancel_calls_ = 0;
std::vector<TryScheduleParameters> try_schedule_calls_;
};
class TestPageAutoFetcherHelper : public PageAutoFetcherHelper {
public:
explicit TestPageAutoFetcherHelper(
base::RepeatingCallback<
mojo::PendingRemote<chrome::mojom::OfflinePageAutoFetcher>()> binder)
: PageAutoFetcherHelper(nullptr), binder_(binder) {}
bool Bind() override {
if (!fetcher_)
fetcher_.Bind(binder_.Run());
return true;
}
private:
base::RepeatingCallback<
mojo::PendingRemote<chrome::mojom::OfflinePageAutoFetcher>()>
binder_;
};
class NetErrorHelperCoreAutoFetchTest : public NetErrorHelperCoreTest {
public:
void SetUp() override {
NetErrorHelperCoreTest::SetUp();
auto binder = base::BindLambdaForTesting([&]() {
mojo::PendingRemote<chrome::mojom::OfflinePageAutoFetcher> fetcher_remote;
fake_fetcher_.AddReceiver(
fetcher_remote.InitWithNewPipeAndPassReceiver());
return fetcher_remote;
});
core()->SetPageAutoFetcherHelperForTesting(
std::make_unique<TestPageAutoFetcherHelper>(binder));
}
void TearDown() override {
AvailableOfflineContentHelper::OverrideBinderForTesting(
base::NullCallback());
}
protected:
FakeOfflinePageAutoFetcher fake_fetcher_;
};
TEST_F(NetErrorHelperCoreAutoFetchTest, NotAllowed) {
set_auto_fetch_allowed(false);
DoErrorLoad(net::ERR_INTERNET_DISCONNECTED);
task_environment()->RunUntilIdle();
std::vector<FakeOfflinePageAutoFetcher::TryScheduleParameters> calls =
fake_fetcher_.take_try_schedule_calls();
EXPECT_EQ(0ul, calls.size());
}
TEST_F(NetErrorHelperCoreAutoFetchTest, AutoFetchTriggered) {
set_auto_fetch_allowed(true);
DoErrorLoad(net::ERR_INTERNET_DISCONNECTED);
task_environment()->RunUntilIdle();
std::vector<FakeOfflinePageAutoFetcher::TryScheduleParameters> calls =
fake_fetcher_.take_try_schedule_calls();
EXPECT_EQ(1ul, calls.size());
std::move(calls[0].callback)
.Run(chrome::mojom::OfflinePageAutoFetcherScheduleResult::kScheduled);
task_environment()->RunUntilIdle();
EXPECT_EQ(chrome::mojom::OfflinePageAutoFetcherScheduleResult::kScheduled,
auto_fetch_state());
}
#endif
}