blob: bfe32a14c586ec88693f60fc5c7ef6c43c75197a [file] [log] [blame]
// Copyright 2016 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 "components/previews/content/previews_decider_impl.h"
#include <initializer_list>
#include <map>
#include <memory>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/field_trial_params.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/task/post_task.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_clock.h"
#include "base/test/task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/default_clock.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/blocklist/opt_out_blocklist/opt_out_blocklist_data.h"
#include "components/blocklist/opt_out_blocklist/opt_out_blocklist_delegate.h"
#include "components/blocklist/opt_out_blocklist/opt_out_blocklist_item.h"
#include "components/blocklist/opt_out_blocklist/opt_out_store.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_switches.h"
#include "components/optimization_guide/content/test_optimization_guide_decider.h"
#include "components/prefs/testing_pref_service.h"
#include "components/previews/content/previews_ui_service.h"
#include "components/previews/content/previews_user_data.h"
#include "components/previews/core/previews_block_list.h"
#include "components/previews/core/previews_experiments.h"
#include "components/previews/core/previews_features.h"
#include "components/previews/core/previews_switches.h"
#include "components/variations/variations_associated_data.h"
#include "content/public/test/mock_navigation_handle.h"
#include "net/nqe/effective_connection_type.h"
#include "net/nqe/network_quality_estimator_test_util.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/test/test_network_quality_tracker.h"
#include "services/network/test/test_shared_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace previews {
namespace {
// A fake default page_id for testing.
const uint64_t kDefaultPageId = 123456;
// This method simulates the actual behavior of the passed in callback, which is
// validated in other tests. For simplicity, offline, and lite page use the
// offline previews check.
bool IsPreviewFieldTrialEnabled(PreviewsType type) {
switch (type) {
case PreviewsType::DEFER_ALL_SCRIPT:
return params::IsDeferAllScriptPreviewsEnabled();
case PreviewsType::NONE:
case PreviewsType::UNSPECIFIED:
case PreviewsType::LAST:
break;
}
NOTREACHED();
return false;
}
// Stub class of PreviewsBlockList to control IsLoadedAndAllowed outcome when
// testing PreviewsDeciderImpl.
class TestPreviewsBlockList : public PreviewsBlockList {
public:
TestPreviewsBlockList(PreviewsEligibilityReason status,
blocklist::OptOutBlocklistDelegate* blocklist_delegate)
: PreviewsBlockList(nullptr,
base::DefaultClock::GetInstance(),
blocklist_delegate,
{}),
status_(status) {}
~TestPreviewsBlockList() override = default;
// PreviewsBlockList:
PreviewsEligibilityReason IsLoadedAndAllowed(
const GURL& url,
PreviewsType type,
std::vector<PreviewsEligibilityReason>* passed_reasons) const override {
std::vector<PreviewsEligibilityReason> ordered_reasons = {
PreviewsEligibilityReason::BLOCKLIST_DATA_NOT_LOADED,
PreviewsEligibilityReason::USER_RECENTLY_OPTED_OUT,
PreviewsEligibilityReason::USER_BLOCKLISTED,
PreviewsEligibilityReason::HOST_BLOCKLISTED};
for (auto reason : ordered_reasons) {
if (status_ == reason) {
return status_;
}
passed_reasons->push_back(reason);
}
return PreviewsEligibilityReason::ALLOWED;
}
private:
PreviewsEligibilityReason status_;
};
// Stub class of PreviewsOptimizationGuide to control what is allowed when
// testing PreviewsDecider.
class TestPreviewsOptimizationGuide
: public PreviewsOptimizationGuide,
public network::NetworkQualityTracker::EffectiveConnectionTypeObserver {
public:
TestPreviewsOptimizationGuide(
optimization_guide::OptimizationGuideDecider* optimization_guide_decider,
network::NetworkQualityTracker* network_quality_tracker)
: PreviewsOptimizationGuide(optimization_guide_decider),
network_quality_tracker_(network_quality_tracker) {
network_quality_tracker_->AddEffectiveConnectionTypeObserver(this);
}
~TestPreviewsOptimizationGuide() override {
network_quality_tracker_->RemoveEffectiveConnectionTypeObserver(this);
}
void OnEffectiveConnectionTypeChanged(
net::EffectiveConnectionType ect) override {
current_ect_ = ect;
}
bool ShouldShowPreview(
content::NavigationHandle* navigation_handle) override {
return current_ect_ <= net::EFFECTIVE_CONNECTION_TYPE_2G;
}
// PreviewsOptimizationGuide:
bool CanApplyPreview(PreviewsUserData* previews_user_data,
content::NavigationHandle* navigation_handle,
PreviewsType type) override {
EXPECT_TRUE(type == PreviewsType::DEFER_ALL_SCRIPT);
const GURL url = navigation_handle->GetURL();
if (type == PreviewsType::DEFER_ALL_SCRIPT) {
return url.host().compare("allowlisted.example.com") == 0;
}
return false;
}
// Returns whether the URL associated with |navigation_handle| should be
// blocklisted from |type|.
bool IsBlocklisted(content::NavigationHandle* navigation_handle,
PreviewsType type) const {
return false;
}
private:
network::NetworkQualityTracker* network_quality_tracker_;
net::EffectiveConnectionType current_ect_ =
net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
};
// Stub class of PreviewsUIService to test logging functionalities in
// PreviewsDeciderImpl.
class TestPreviewsUIService : public PreviewsUIService {
public:
TestPreviewsUIService(
std::unique_ptr<PreviewsDeciderImpl> previews_decider_impl,
std::unique_ptr<blocklist::OptOutStore> previews_opt_out_store,
std::unique_ptr<PreviewsOptimizationGuide> previews_opt_guide,
const PreviewsIsEnabledCallback& is_enabled_callback,
blocklist::BlocklistData::AllowedTypesAndVersions allowed_types,
network::NetworkQualityTracker* network_quality_tracker)
: PreviewsUIService(std::move(previews_decider_impl),
std::move(previews_opt_out_store),
std::move(previews_opt_guide),
is_enabled_callback,
std::move(allowed_types),
network_quality_tracker),
user_blocklisted_(false),
blocklist_ignored_(false) {}
// PreviewsUIService:
void OnNewBlocklistedHost(const std::string& host, base::Time time) override {
host_blocklisted_ = host;
host_blocklisted_time_ = time;
}
void OnUserBlocklistedStatusChange(bool blocklisted) override {
user_blocklisted_ = blocklisted;
}
void OnBlocklistCleared(base::Time time) override {
blocklist_cleared_time_ = time;
}
void OnIgnoreBlocklistDecisionStatusChanged(bool ignored) override {
blocklist_ignored_ = ignored;
}
// Expose passed in LogPreviewDecision parameters.
const std::vector<PreviewsEligibilityReason>& decision_reasons() const {
return decision_reasons_;
}
const std::vector<GURL>& decision_urls() const { return decision_urls_; }
const std::vector<PreviewsType>& decision_types() const {
return decision_types_;
}
const std::vector<base::Time>& decision_times() const {
return decision_times_;
}
const std::vector<std::vector<PreviewsEligibilityReason>>&
decision_passed_reasons() const {
return decision_passed_reasons_;
}
const std::vector<uint64_t>& decision_ids() const { return decision_ids_; }
// Expose passed in LogPreviewsNavigation parameters.
const std::vector<GURL>& navigation_urls() const { return navigation_urls_; }
const std::vector<bool>& navigation_opt_outs() const {
return navigation_opt_outs_;
}
const std::vector<base::Time>& navigation_times() const {
return navigation_times_;
}
const std::vector<PreviewsType>& navigation_types() const {
return navigation_types_;
}
const std::vector<uint64_t>& navigation_page_ids() const {
return navigation_page_ids_;
}
// Expose passed in params for hosts and user blocklist event.
std::string host_blocklisted() const { return host_blocklisted_; }
base::Time host_blocklisted_time() const { return host_blocklisted_time_; }
bool user_blocklisted() const { return user_blocklisted_; }
base::Time blocklist_cleared_time() const { return blocklist_cleared_time_; }
// Expose the status of blocklist decisions ignored.
bool blocklist_ignored() const { return blocklist_ignored_; }
private:
// PreviewsUIService:
void LogPreviewNavigation(const GURL& url,
PreviewsType type,
bool opt_out,
base::Time time,
uint64_t page_id) override {
navigation_urls_.push_back(url);
navigation_opt_outs_.push_back(opt_out);
navigation_types_.push_back(type);
navigation_times_.push_back(time);
navigation_page_ids_.push_back(page_id);
}
void LogPreviewDecisionMade(
PreviewsEligibilityReason reason,
const GURL& url,
base::Time time,
PreviewsType type,
std::vector<PreviewsEligibilityReason>&& passed_reasons,
uint64_t page_id) override {
LOG(INFO) << "Decision is logged";
decision_reasons_.push_back(reason);
decision_urls_.push_back(GURL(url));
decision_times_.push_back(time);
decision_types_.push_back(type);
decision_passed_reasons_.push_back(std::move(passed_reasons));
decision_ids_.push_back(page_id);
}
// Passed in params for blocklist status events.
std::string host_blocklisted_;
base::Time host_blocklisted_time_;
bool user_blocklisted_;
base::Time blocklist_cleared_time_;
// Passed in LogPreviewDecision parameters.
std::vector<PreviewsEligibilityReason> decision_reasons_;
std::vector<GURL> decision_urls_;
std::vector<PreviewsType> decision_types_;
std::vector<base::Time> decision_times_;
std::vector<std::vector<PreviewsEligibilityReason>> decision_passed_reasons_;
std::vector<uint64_t> decision_ids_;
// Passed in LogPreviewsNavigation parameters.
std::vector<GURL> navigation_urls_;
std::vector<bool> navigation_opt_outs_;
std::vector<base::Time> navigation_times_;
std::vector<PreviewsType> navigation_types_;
std::vector<uint64_t> navigation_page_ids_;
// Whether the blocklist decisions are ignored or not.
bool blocklist_ignored_;
};
class TestPreviewsDeciderImpl : public PreviewsDeciderImpl {
public:
explicit TestPreviewsDeciderImpl(base::Clock* clock)
: PreviewsDeciderImpl(clock) {}
~TestPreviewsDeciderImpl() override {}
// Expose the injecting blocklist method from PreviewsDeciderImpl, and inject
// |blocklist| into |this|.
void InjectTestBlocklist(std::unique_ptr<PreviewsBlockList> blocklist) {
SetPreviewsBlocklistForTesting(std::move(blocklist));
}
};
void RunLoadCallback(blocklist::LoadBlockListCallback callback,
std::unique_ptr<blocklist::BlocklistData> data) {
std::move(callback).Run(std::move(data));
}
class TestOptOutStore : public blocklist::OptOutStore {
public:
TestOptOutStore() {}
~TestOptOutStore() override {}
private:
// blocklist::OptOutStore implementation:
void AddEntry(bool opt_out,
const std::string& host_name,
int type,
base::Time now) override {}
void LoadBlockList(std::unique_ptr<blocklist::BlocklistData> data,
blocklist::LoadBlockListCallback callback) override {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&RunLoadCallback, std::move(callback), std::move(data)));
}
void ClearBlockList(base::Time begin_time, base::Time end_time) override {}
};
class PreviewsDeciderImplTest : public testing::Test {
public:
PreviewsDeciderImplTest() : previews_decider_impl_(nullptr) {
clock_.SetNow(base::Time::Now());
network_quality_tracker_.ReportEffectiveConnectionTypeForTesting(
net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G);
}
~PreviewsDeciderImplTest() override {
// TODO(dougarnett) bug 781975: Consider switching to Feature API and
// ScopedFeatureList (and dropping components/variations dep).
variations::testing::ClearAllVariationParams();
}
void SetUp() override {
// Enable DataSaver for checks with PreviewsOptimizationGuide.
base::CommandLine::ForCurrentProcess()->AppendSwitch(
data_reduction_proxy::switches::kEnableDataReductionProxy);
}
void TearDown() override {
ui_service_.reset();
}
void InitializeUIServiceWithoutWaitingForBlockList(
bool include_previews_opt_guide) {
blocklist::BlocklistData::AllowedTypesAndVersions allowed_types;
allowed_types[static_cast<int>(PreviewsType::DEFER_ALL_SCRIPT)] = 0;
std::unique_ptr<TestPreviewsDeciderImpl> previews_decider_impl =
std::make_unique<TestPreviewsDeciderImpl>(&clock_);
previews_decider_impl_ = previews_decider_impl.get();
pref_service_ = std::make_unique<TestingPrefServiceSimple>();
std::unique_ptr<TestPreviewsOptimizationGuide> previews_opt_guide =
std::make_unique<TestPreviewsOptimizationGuide>(
&optimization_guide_decider_, &network_quality_tracker_);
previews_opt_guide_ = previews_opt_guide.get();
ui_service_.reset(new TestPreviewsUIService(
std::move(previews_decider_impl), std::make_unique<TestOptOutStore>(),
include_previews_opt_guide ? std::move(previews_opt_guide) : nullptr,
base::BindRepeating(&IsPreviewFieldTrialEnabled),
std::move(allowed_types), &network_quality_tracker_));
}
void InitializeUIService(bool include_previews_opt_guide = true) {
InitializeUIServiceWithoutWaitingForBlockList(include_previews_opt_guide);
task_environment_.RunUntilIdle();
base::RunLoop().RunUntilIdle();
}
TestPreviewsDeciderImpl* previews_decider_impl() {
return previews_decider_impl_;
}
TestPreviewsUIService* ui_service() { return ui_service_.get(); }
void ReportEffectiveConnectionType(
net::EffectiveConnectionType effective_connection_type) {
network_quality_tracker_.ReportEffectiveConnectionTypeForTesting(
effective_connection_type);
base::RunLoop().RunUntilIdle();
}
protected:
base::SimpleTestClock clock_;
private:
base::test::TaskEnvironment task_environment_;
TestPreviewsDeciderImpl* previews_decider_impl_;
optimization_guide::TestOptimizationGuideDecider optimization_guide_decider_;
TestPreviewsOptimizationGuide* previews_opt_guide_;
std::unique_ptr<TestPreviewsUIService> ui_service_;
network::TestNetworkQualityTracker network_quality_tracker_;
std::unique_ptr<TestingPrefServiceSimple> pref_service_;
scoped_refptr<network::TestSharedURLLoaderFactory> url_loader_factory_;
};
TEST_F(PreviewsDeciderImplTest, AllPreviewsDisabledByFeature) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kDeferAllScriptPreviews},
{features::kPreviews} /* disable_features */);
InitializeUIService();
PreviewsUserData user_data(kDefaultPageId);
PreviewsUserData user_data2(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("https://www.google.com"));
EXPECT_FALSE(previews_decider_impl()->ShouldAllowPreviewAtNavigationStart(
&user_data2, &navigation_handle, false, PreviewsType::DEFER_ALL_SCRIPT));
}
TEST_F(PreviewsDeciderImplTest, TestDisallowBasicAuthentication) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kPreviews, features::kDeferAllScriptPreviews}, {});
InitializeUIService();
ReportEffectiveConnectionType(net::EFFECTIVE_CONNECTION_TYPE_2G);
base::HistogramTester histogram_tester;
PreviewsUserData user_data(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("https://user:pass@www.google.com"));
EXPECT_FALSE(previews_decider_impl()->ShouldAllowPreviewAtNavigationStart(
&user_data, &navigation_handle, false, PreviewsType::DEFER_ALL_SCRIPT));
histogram_tester.ExpectBucketCount(
"Previews.EligibilityReason",
static_cast<int>(PreviewsEligibilityReason::URL_HAS_BASIC_AUTH), 1);
histogram_tester.ExpectBucketCount(
"Previews.EligibilityReason.DeferAllScript",
static_cast<int>(PreviewsEligibilityReason::URL_HAS_BASIC_AUTH), 1);
}
TEST_F(PreviewsDeciderImplTest, TestDisallowOnReload) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kPreviews, features::kDeferAllScriptPreviews}, {});
InitializeUIService();
ReportEffectiveConnectionType(net::EFFECTIVE_CONNECTION_TYPE_2G);
PreviewsUserData user_data(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("https://www.google.com"));
base::HistogramTester histogram_tester;
EXPECT_FALSE(previews_decider_impl()->ShouldAllowPreviewAtNavigationStart(
&user_data, &navigation_handle, true, PreviewsType::DEFER_ALL_SCRIPT));
histogram_tester.ExpectUniqueSample(
"Previews.EligibilityReason",
static_cast<int>(PreviewsEligibilityReason::RELOAD_DISALLOWED), 1);
histogram_tester.ExpectUniqueSample(
"Previews.EligibilityReason.DeferAllScript",
static_cast<int>(PreviewsEligibilityReason::RELOAD_DISALLOWED), 1);
}
TEST_F(PreviewsDeciderImplTest, MissingHostDisallowed) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kPreviews, features::kDeferAllScriptPreviews}, {});
InitializeUIService();
EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_2G,
params::GetECTThresholdForPreview(PreviewsType::DEFER_ALL_SCRIPT));
ReportEffectiveConnectionType(net::EFFECTIVE_CONNECTION_TYPE_2G);
PreviewsUserData user_data(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("file:///sdcard"));
EXPECT_FALSE(previews_decider_impl()->ShouldAllowPreviewAtNavigationStart(
&user_data, &navigation_handle, false, PreviewsType::DEFER_ALL_SCRIPT));
}
TEST_F(PreviewsDeciderImplTest, DeferAllScriptDefaultBehavior) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(features::kPreviews);
InitializeUIService();
base::HistogramTester histogram_tester;
PreviewsUserData user_data(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("https://www.google.com"));
#if defined(OS_ANDROID)
bool expected = true;
#else // !defined(OS_ANDROID)
bool expected = false;
#endif // defined(OS_ANDROID)
EXPECT_EQ(expected,
previews_decider_impl()->ShouldAllowPreviewAtNavigationStart(
&user_data, &navigation_handle, false,
PreviewsType::DEFER_ALL_SCRIPT));
}
TEST_F(PreviewsDeciderImplTest,
DeferAllScriptDisallowedWithoutOptimizationHints) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kPreviews, features::kDeferAllScriptPreviews}, {});
InitializeUIService(/*include_previews_opt_guide=*/false);
base::HistogramTester histogram_tester;
PreviewsUserData user_data(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("https://allowlisted.example.com"));
EXPECT_FALSE(previews_decider_impl()->ShouldAllowPreviewAtNavigationStart(
&user_data, &navigation_handle, false, PreviewsType::DEFER_ALL_SCRIPT));
histogram_tester.ExpectUniqueSample(
"Previews.EligibilityReason",
static_cast<int>(
PreviewsEligibilityReason::OPTIMIZATION_HINTS_NOT_AVAILABLE),
1);
histogram_tester.ExpectUniqueSample(
"Previews.EligibilityReason.DeferAllScript",
static_cast<int>(
PreviewsEligibilityReason::OPTIMIZATION_HINTS_NOT_AVAILABLE),
1);
}
TEST_F(PreviewsDeciderImplTest, DeferAllScriptAllowedByFeatureAndAllowlist) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kPreviews, features::kDeferAllScriptPreviews}, {});
InitializeUIService();
for (const auto& test_ect : {net::EFFECTIVE_CONNECTION_TYPE_OFFLINE,
net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G,
net::EFFECTIVE_CONNECTION_TYPE_3G}) {
ReportEffectiveConnectionType(test_ect);
base::HistogramTester histogram_tester;
PreviewsUserData user_data(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("https://allowlisted.example.com"));
// Check allowlisted URL.
EXPECT_TRUE(previews_decider_impl()->ShouldAllowPreviewAtNavigationStart(
&user_data, &navigation_handle, false, PreviewsType::DEFER_ALL_SCRIPT));
EXPECT_EQ(test_ect, user_data.navigation_ect());
histogram_tester.ExpectUniqueSample(
"Previews.EligibilityReason.DeferAllScript",
static_cast<int>(PreviewsEligibilityReason::ALLOWED), 1);
}
}
TEST_F(PreviewsDeciderImplTest,
DeferAllScriptAllowedByFeatureWithoutKnownHints) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kPreviews, features::kDeferAllScriptPreviews}, {});
InitializeUIService();
base::HistogramTester histogram_tester;
PreviewsUserData user_data(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("https://www.google.com"));
// Verify preview allowed initially for url without known hints.
EXPECT_TRUE(previews_decider_impl()->ShouldAllowPreviewAtNavigationStart(
&user_data, &navigation_handle, false, PreviewsType::DEFER_ALL_SCRIPT));
histogram_tester.ExpectBucketCount(
"Previews.EligibilityReason.DeferAllScript",
static_cast<int>(PreviewsEligibilityReason::ALLOWED), 1);
}
TEST_F(PreviewsDeciderImplTest, DeferAllScriptCommitTimeAllowlistCheck) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kPreviews, features::kDeferAllScriptPreviews}, {});
InitializeUIService();
// First verify not allowed for non-allowlisted url.
{
ReportEffectiveConnectionType(net::EFFECTIVE_CONNECTION_TYPE_2G);
base::HistogramTester histogram_tester;
PreviewsUserData user_data(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("https://www.google.com"));
EXPECT_FALSE(previews_decider_impl()->ShouldCommitPreview(
&user_data, &navigation_handle, PreviewsType::DEFER_ALL_SCRIPT));
histogram_tester.ExpectUniqueSample(
"Previews.EligibilityReason.DeferAllScript",
static_cast<int>(
PreviewsEligibilityReason::NOT_ALLOWED_BY_OPTIMIZATION_GUIDE),
1);
}
// Now verify preview for allowlisted url.
{
ReportEffectiveConnectionType(net::EFFECTIVE_CONNECTION_TYPE_2G);
base::HistogramTester histogram_tester;
PreviewsUserData user_data(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("https://allowlisted.example.com"));
EXPECT_TRUE(previews_decider_impl()->ShouldCommitPreview(
&user_data, &navigation_handle, PreviewsType::DEFER_ALL_SCRIPT));
// Expect no eligibility logging.
histogram_tester.ExpectTotalCount(
"Previews.EligibilityReason.DeferAllScript", 0);
}
// Verify preview not allowed for allowlisted url when network is not slow.
{
ReportEffectiveConnectionType(net::EFFECTIVE_CONNECTION_TYPE_4G);
base::HistogramTester histogram_tester;
PreviewsUserData user_data(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("https://allowlisted.example.com"));
EXPECT_FALSE(previews_decider_impl()->ShouldCommitPreview(
&user_data, &navigation_handle, PreviewsType::DEFER_ALL_SCRIPT));
histogram_tester.ExpectUniqueSample(
"Previews.EligibilityReason.DeferAllScript",
static_cast<int>(
PreviewsEligibilityReason::PAGE_LOAD_PREDICTION_NOT_PAINFUL),
1);
}
// Verify preview not allowed for allowlisted url for offline network quality.
{
ReportEffectiveConnectionType(net::EFFECTIVE_CONNECTION_TYPE_OFFLINE);
base::HistogramTester histogram_tester;
PreviewsUserData user_data(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("https://allowlisted.example.com"));
EXPECT_FALSE(previews_decider_impl()->ShouldCommitPreview(
&user_data, &navigation_handle, PreviewsType::DEFER_ALL_SCRIPT));
histogram_tester.ExpectUniqueSample(
"Previews.EligibilityReason.DeferAllScript",
static_cast<int>(PreviewsEligibilityReason::DEVICE_OFFLINE), 1);
}
}
TEST_F(PreviewsDeciderImplTest, LogPreviewNavigationPassInCorrectParams) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(features::kPreviews);
InitializeUIService();
const GURL url("http://www.url_a.com/url_a");
const bool opt_out = true;
const PreviewsType type = PreviewsType::DEFER_ALL_SCRIPT;
const base::Time time = base::Time::Now();
const uint64_t page_id = 1234;
previews_decider_impl()->LogPreviewNavigation(url, opt_out, type, time,
page_id);
base::RunLoop().RunUntilIdle();
EXPECT_THAT(ui_service()->navigation_urls(), ::testing::ElementsAre(url));
EXPECT_THAT(ui_service()->navigation_opt_outs(),
::testing::ElementsAre(opt_out));
EXPECT_THAT(ui_service()->navigation_types(), ::testing::ElementsAre(type));
EXPECT_THAT(ui_service()->navigation_times(), ::testing::ElementsAre(time));
EXPECT_THAT(ui_service()->navigation_page_ids(),
::testing::ElementsAre(page_id));
}
TEST_F(PreviewsDeciderImplTest, LogPreviewDecisionMadePassInCorrectParams) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(features::kPreviews);
InitializeUIService();
const PreviewsEligibilityReason reason(
PreviewsEligibilityReason::BLOCKLIST_UNAVAILABLE);
const GURL url("http://www.url_a.com/url_a");
const base::Time time = base::Time::Now();
const PreviewsType type = PreviewsType::DEFER_ALL_SCRIPT;
std::vector<PreviewsEligibilityReason> passed_reasons = {
PreviewsEligibilityReason::PAGE_LOAD_PREDICTION_NOT_PAINFUL,
PreviewsEligibilityReason::USER_RECENTLY_OPTED_OUT,
PreviewsEligibilityReason::RELOAD_DISALLOWED,
};
const std::vector<PreviewsEligibilityReason> expected_passed_reasons(
passed_reasons);
const uint64_t page_id = 1234;
PreviewsUserData data(page_id);
previews_decider_impl()->LogPreviewDecisionMade(
reason, url, time, type, std::move(passed_reasons), &data);
base::RunLoop().RunUntilIdle();
EXPECT_THAT(ui_service()->decision_reasons(), ::testing::ElementsAre(reason));
EXPECT_THAT(ui_service()->decision_urls(), ::testing::ElementsAre(url));
EXPECT_THAT(ui_service()->decision_types(), ::testing::ElementsAre(type));
EXPECT_THAT(ui_service()->decision_times(), ::testing::ElementsAre(time));
EXPECT_THAT(ui_service()->decision_ids(), ::testing::ElementsAre(page_id));
EXPECT_EQ(data.EligibilityReasonForPreview(type).value(), reason);
auto actual_passed_reasons = ui_service()->decision_passed_reasons();
EXPECT_EQ(1UL, actual_passed_reasons.size());
EXPECT_EQ(expected_passed_reasons.size(), actual_passed_reasons[0].size());
for (size_t i = 0; i < actual_passed_reasons[0].size(); i++) {
EXPECT_EQ(expected_passed_reasons[i], actual_passed_reasons[0][i]);
}
}
TEST_F(
PreviewsDeciderImplTest,
LogDecisionMadeBlocklistUnavailableAtNavigationStartForCommitTimePreview) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kPreviews, features::kDeferAllScriptPreviews}, {});
InitializeUIService();
auto expected_reason = PreviewsEligibilityReason::ALLOWED;
auto expected_type = PreviewsType::DEFER_ALL_SCRIPT;
previews_decider_impl()->InjectTestBlocklist(nullptr /* blocklist */);
PreviewsUserData user_data(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("https://www.google.com"));
previews_decider_impl()->ShouldAllowPreviewAtNavigationStart(
&user_data, &navigation_handle, false, expected_type);
base::RunLoop().RunUntilIdle();
// Testing correct log method is called.
EXPECT_THAT(ui_service()->decision_reasons(),
::testing::Contains(expected_reason));
EXPECT_THAT(ui_service()->decision_types(),
::testing::Contains(expected_type));
}
TEST_F(PreviewsDeciderImplTest, ShouldCommitPreviewBlocklistStatuses) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kPreviews, features::kDeferAllScriptPreviews}, {});
InitializeUIService(/*include_previews_opt_guide=*/false);
auto expected_type = PreviewsType::DEFER_ALL_SCRIPT;
PreviewsUserData user_data(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("https://www.google.com"));
// First verify URL is allowed for no blocklist status.
EXPECT_TRUE(previews_decider_impl()->ShouldCommitPreview(
&user_data, &navigation_handle, expected_type));
PreviewsEligibilityReason expected_reasons[] = {
PreviewsEligibilityReason::BLOCKLIST_DATA_NOT_LOADED,
PreviewsEligibilityReason::USER_RECENTLY_OPTED_OUT,
PreviewsEligibilityReason::USER_BLOCKLISTED,
PreviewsEligibilityReason::HOST_BLOCKLISTED,
};
const size_t reasons_size = 4;
for (size_t i = 0; i < reasons_size; i++) {
auto expected_reason = expected_reasons[i];
std::unique_ptr<TestPreviewsBlockList> blocklist =
std::make_unique<TestPreviewsBlockList>(expected_reason,
previews_decider_impl());
previews_decider_impl()->InjectTestBlocklist(std::move(blocklist));
PreviewsUserData user_data(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("https://www.google.com"));
EXPECT_FALSE(previews_decider_impl()->ShouldCommitPreview(
&user_data, &navigation_handle, expected_type));
base::RunLoop().RunUntilIdle();
// Testing correct log method is called.
// Check for all decision upto current decision is logged.
for (size_t j = 0; j <= i; j++) {
EXPECT_THAT(ui_service()->decision_reasons(),
::testing::Contains(expected_reasons[j]));
}
EXPECT_THAT(ui_service()->decision_types(),
::testing::Contains(expected_type));
}
}
TEST_F(PreviewsDeciderImplTest, LogDecisionMadeMediaSuffixesAreExcluded) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kPreviews, features::kDeferAllScriptPreviews}, {});
InitializeUIService();
auto expected_reason = PreviewsEligibilityReason::EXCLUDED_BY_MEDIA_SUFFIX;
auto expected_type = PreviewsType::DEFER_ALL_SCRIPT;
PreviewsEligibilityReason blocklist_decisions[] = {
PreviewsEligibilityReason::BLOCKLIST_DATA_NOT_LOADED,
};
for (auto blocklist_decision : blocklist_decisions) {
std::unique_ptr<TestPreviewsBlockList> blocklist =
std::make_unique<TestPreviewsBlockList>(blocklist_decision,
previews_decider_impl());
previews_decider_impl()->InjectTestBlocklist(std::move(blocklist));
PreviewsUserData user_data(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("https://www.google.com/video.mp4"));
previews_decider_impl()->ShouldAllowPreviewAtNavigationStart(
&user_data, &navigation_handle, false, expected_type);
base::RunLoop().RunUntilIdle();
// Testing correct log method is called.
EXPECT_THAT(ui_service()->decision_reasons(),
::testing::Contains(expected_reason));
EXPECT_THAT(ui_service()->decision_types(),
::testing::Contains(expected_type));
}
}
TEST_F(PreviewsDeciderImplTest, IgnoreFlagDoesNotCheckBlocklist) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kPreviews, features::kDeferAllScriptPreviews}, {});
InitializeUIService();
ReportEffectiveConnectionType(net::EFFECTIVE_CONNECTION_TYPE_2G);
previews_decider_impl()->SetIgnorePreviewsBlocklistDecision(
true /* ignored */);
PreviewsUserData user_data(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("https://allowlisted.example.com"));
EXPECT_TRUE(previews_decider_impl()->ShouldCommitPreview(
&user_data, &navigation_handle, PreviewsType::DEFER_ALL_SCRIPT));
previews_decider_impl()->AddPreviewReload();
EXPECT_TRUE(previews_decider_impl()->ShouldCommitPreview(
&user_data, &navigation_handle, PreviewsType::DEFER_ALL_SCRIPT));
}
TEST_F(PreviewsDeciderImplTest, ReloadsTriggerFiveMinuteRule) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kPreviews, features::kDeferAllScriptPreviews}, {});
InitializeUIService();
ReportEffectiveConnectionType(net::EFFECTIVE_CONNECTION_TYPE_2G);
PreviewsUserData user_data(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("https://allowlisted.example.com"));
EXPECT_TRUE(previews_decider_impl()->ShouldCommitPreview(
&user_data, &navigation_handle, PreviewsType::DEFER_ALL_SCRIPT));
previews_decider_impl()->AddPreviewNavigation(
GURL("http://wwww.somedomain.com"), false, PreviewsType::DEFER_ALL_SCRIPT,
1);
previews_decider_impl()->AddPreviewReload();
EXPECT_FALSE(previews_decider_impl()->ShouldCommitPreview(
&user_data, &navigation_handle, PreviewsType::DEFER_ALL_SCRIPT));
EXPECT_EQ(PreviewsEligibilityReason::USER_RECENTLY_OPTED_OUT,
ui_service()->decision_reasons().back());
clock_.Advance(base::TimeDelta::FromMinutes(6));
EXPECT_TRUE(previews_decider_impl()->ShouldCommitPreview(
&user_data, &navigation_handle, PreviewsType::DEFER_ALL_SCRIPT));
}
TEST_F(PreviewsDeciderImplTest, LogDecisionMadeReloadDisallowed) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kPreviews, features::kDeferAllScriptPreviews}, {});
InitializeUIService();
std::unique_ptr<TestPreviewsBlockList> blocklist =
std::make_unique<TestPreviewsBlockList>(
PreviewsEligibilityReason::ALLOWED, previews_decider_impl());
previews_decider_impl()->InjectTestBlocklist(std::move(blocklist));
ReportEffectiveConnectionType(net::EFFECTIVE_CONNECTION_TYPE_2G);
PreviewsUserData user_data(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("https://www.google.com"));
auto expected_reason = PreviewsEligibilityReason::RELOAD_DISALLOWED;
auto expected_type = PreviewsType::DEFER_ALL_SCRIPT;
std::vector<PreviewsEligibilityReason> checked_decisions = {
PreviewsEligibilityReason::URL_HAS_BASIC_AUTH,
PreviewsEligibilityReason::EXCLUDED_BY_MEDIA_SUFFIX,
};
previews_decider_impl()->ShouldAllowPreviewAtNavigationStart(
&user_data, &navigation_handle, true, expected_type);
base::RunLoop().RunUntilIdle();
// Testing correct log method is called.
EXPECT_THAT(ui_service()->decision_reasons(),
::testing::Contains(expected_reason));
EXPECT_THAT(ui_service()->decision_types(),
::testing::Contains(expected_type));
EXPECT_EQ(1UL, ui_service()->decision_passed_reasons().size());
auto actual_passed_reasons = ui_service()->decision_passed_reasons()[0];
EXPECT_EQ(checked_decisions.size(), actual_passed_reasons.size());
for (size_t i = 0; i < actual_passed_reasons.size(); i++) {
EXPECT_EQ(checked_decisions[i], actual_passed_reasons[i]);
}
}
TEST_F(PreviewsDeciderImplTest, IgnoreBlocklistEnabledViaFlag) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kPreviews, features::kDeferAllScriptPreviews}, {});
base::test::ScopedCommandLine scoped_command_line;
base::CommandLine* command_line = scoped_command_line.GetProcessCommandLine();
command_line->AppendSwitch(switches::kIgnorePreviewsBlocklist);
ASSERT_TRUE(switches::ShouldIgnorePreviewsBlocklist());
InitializeUIService();
std::unique_ptr<TestPreviewsBlockList> blocklist =
std::make_unique<TestPreviewsBlockList>(
PreviewsEligibilityReason::HOST_BLOCKLISTED, previews_decider_impl());
previews_decider_impl()->InjectTestBlocklist(std::move(blocklist));
ReportEffectiveConnectionType(net::EFFECTIVE_CONNECTION_TYPE_2G);
PreviewsUserData user_data(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("https://allowlisted.example.com"));
EXPECT_TRUE(previews_decider_impl()->ShouldCommitPreview(
&user_data, &navigation_handle, PreviewsType::DEFER_ALL_SCRIPT));
}
TEST_F(PreviewsDeciderImplTest, LogDecisionMadeAllowHintPreviewWithoutECT) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kPreviews, features::kDeferAllScriptPreviews}, {});
InitializeUIService();
std::unique_ptr<TestPreviewsBlockList> blocklist =
std::make_unique<TestPreviewsBlockList>(
PreviewsEligibilityReason::ALLOWED, previews_decider_impl());
previews_decider_impl()->InjectTestBlocklist(std::move(blocklist));
ReportEffectiveConnectionType(net::EFFECTIVE_CONNECTION_TYPE_2G);
auto expected_reason = PreviewsEligibilityReason::ALLOWED;
auto expected_type = PreviewsType::DEFER_ALL_SCRIPT;
std::vector<PreviewsEligibilityReason> checked_decisions = {
PreviewsEligibilityReason::URL_HAS_BASIC_AUTH,
PreviewsEligibilityReason::EXCLUDED_BY_MEDIA_SUFFIX,
PreviewsEligibilityReason::RELOAD_DISALLOWED,
};
PreviewsUserData user_data(kDefaultPageId);
content::MockNavigationHandle navigation_handle;
navigation_handle.set_url(GURL("https://allowlisted.example.com"));
EXPECT_TRUE(previews_decider_impl()->ShouldAllowPreviewAtNavigationStart(
&user_data, &navigation_handle, false, expected_type));
base::RunLoop().RunUntilIdle();
// Testing correct log method is called.
EXPECT_THAT(ui_service()->decision_reasons(),
::testing::Contains(expected_reason));
EXPECT_THAT(ui_service()->decision_types(),
::testing::Contains(expected_type));
EXPECT_EQ(1UL, ui_service()->decision_passed_reasons().size());
auto actual_passed_reasons = ui_service()->decision_passed_reasons()[0];
EXPECT_EQ(checked_decisions.size(), actual_passed_reasons.size());
for (size_t i = 0; i < actual_passed_reasons.size(); i++) {
EXPECT_EQ(checked_decisions[i], actual_passed_reasons[i]);
}
}
TEST_F(PreviewsDeciderImplTest, OnNewBlocklistedHostCallsUIMethodCorrectly) {
InitializeUIService();
std::string expected_host = "example.com";
base::Time expected_time = base::Time::Now();
previews_decider_impl()->OnNewBlocklistedHost(expected_host, expected_time);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(expected_host, ui_service()->host_blocklisted());
EXPECT_EQ(expected_time, ui_service()->host_blocklisted_time());
}
TEST_F(PreviewsDeciderImplTest, OnUserBlocklistedCallsUIMethodCorrectly) {
InitializeUIService();
previews_decider_impl()->OnUserBlocklistedStatusChange(
true /* blocklisted */);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(ui_service()->user_blocklisted());
previews_decider_impl()->OnUserBlocklistedStatusChange(
false /* blocklisted */);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(ui_service()->user_blocklisted());
}
TEST_F(PreviewsDeciderImplTest, OnBlocklistClearedCallsUIMethodCorrectly) {
InitializeUIService();
base::Time expected_time = base::Time::Now();
previews_decider_impl()->OnBlocklistCleared(expected_time);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(expected_time, ui_service()->blocklist_cleared_time());
}
TEST_F(PreviewsDeciderImplTest,
OnIgnoreBlocklistDecisionStatusChangedCalledCorrect) {
InitializeUIService();
previews_decider_impl()->SetIgnorePreviewsBlocklistDecision(
true /* ignored */);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(ui_service()->blocklist_ignored());
previews_decider_impl()->SetIgnorePreviewsBlocklistDecision(
false /* ignored */);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(ui_service()->blocklist_ignored());
}
TEST_F(PreviewsDeciderImplTest, GeneratePageIdMakesUniqueNonZero) {
InitializeUIService();
std::unordered_set<uint64_t> page_id_set;
size_t number_of_generated_ids = 10;
for (size_t i = 0; i < number_of_generated_ids; i++) {
page_id_set.insert(previews_decider_impl()->GeneratePageId());
}
EXPECT_EQ(number_of_generated_ids, page_id_set.size());
EXPECT_EQ(page_id_set.end(), page_id_set.find(0u));
}
} // namespace
} // namespace previews