blob: a472fd86edfb29a20d0ee9b44077ed386eecac40 [file] [log] [blame]
// Copyright 2017 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_optimization_guide.h"
#include <memory>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/optional.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/gtest_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_task_environment.h"
#include "components/optimization_guide/optimization_guide_service.h"
#include "components/optimization_guide/optimization_guide_service_observer.h"
#include "components/optimization_guide/proto/hints.pb.h"
#include "components/previews/content/previews_user_data.h"
#include "components/previews/core/bloom_filter.h"
#include "components/previews/core/previews_features.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;
} // namespace
class TestOptimizationGuideService
: public optimization_guide::OptimizationGuideService {
public:
explicit TestOptimizationGuideService(
const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner)
: OptimizationGuideService(io_task_runner),
remove_observer_called_(false) {}
void RemoveObserver(
optimization_guide::OptimizationGuideServiceObserver* observer) override {
remove_observer_called_ = true;
}
bool RemoveObserverCalled() { return remove_observer_called_; }
private:
bool remove_observer_called_;
};
class PreviewsOptimizationGuideTest : public testing::Test {
public:
PreviewsOptimizationGuideTest() {}
~PreviewsOptimizationGuideTest() override {}
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
optimization_guide_service_ =
std::make_unique<TestOptimizationGuideService>(
scoped_task_environment_.GetMainThreadTaskRunner());
guide_ = std::make_unique<PreviewsOptimizationGuide>(
optimization_guide_service_.get(),
scoped_task_environment_.GetMainThreadTaskRunner());
}
// Delete |guide_| if it hasn't been deleted.
void TearDown() override { ResetGuide(); }
PreviewsOptimizationGuide* guide() { return guide_.get(); }
TestOptimizationGuideService* optimization_guide_service() {
return optimization_guide_service_.get();
}
void ProcessHints(const optimization_guide::proto::Configuration& config,
std::string version) {
optimization_guide::ComponentInfo info(
base::Version(version),
temp_dir().Append(FILE_PATH_LITERAL("somefile.pb")));
guide_->OnHintsProcessed(config, info);
}
void MaybeLoadOptimizationHintsCallback(
const GURL& document_gurl,
const std::vector<std::string>& resource_patterns) {
loaded_hints_document_gurl_ = document_gurl;
loaded_hints_resource_patterns_ = resource_patterns;
}
void ResetGuide() {
guide_.reset();
RunUntilIdle();
}
base::FilePath temp_dir() const { return temp_dir_.GetPath(); }
const GURL& loaded_hints_document_gurl() const {
return loaded_hints_document_gurl_;
}
const std::vector<std::string>& loaded_hints_resource_patterns() const {
return loaded_hints_resource_patterns_;
}
protected:
void RunUntilIdle() {
scoped_task_environment_.RunUntilIdle();
base::RunLoop().RunUntilIdle();
}
void DoExperimentFlagTest(base::Optional<std::string> experiment_name,
bool expect_enabled);
// This is a helper function for initializing fixed number of ResourceLoading
// hints.
void InitializeFixedCountResourceLoadingHints();
// This is a helper function for initializing multiple ResourceLoading hints.
// The generated hint proto contains hints for |key_count| keys.
// |page_patterns_per_key| page patterns are specified per key.
// For each page pattern, 2 resource loading hints are specified in the proto.
void InitializeMultipleResourceLoadingHints(size_t key_count,
size_t page_patterns_per_key);
// This is a helper function for initializing with a LITE_PAGE_REDIRECT
// server blacklist.
void InitializeWithLitePageRedirectBlacklist();
private:
base::test::ScopedTaskEnvironment scoped_task_environment_;
base::ScopedTempDir temp_dir_;
std::unique_ptr<PreviewsOptimizationGuide> guide_;
std::unique_ptr<TestOptimizationGuideService> optimization_guide_service_;
GURL loaded_hints_document_gurl_;
std::vector<std::string> loaded_hints_resource_patterns_;
DISALLOW_COPY_AND_ASSIGN(PreviewsOptimizationGuideTest);
};
void PreviewsOptimizationGuideTest::InitializeFixedCountResourceLoadingHints() {
optimization_guide::proto::Configuration config;
optimization_guide::proto::Hint* hint1 = config.add_hints();
hint1->set_key("somedomain.org");
hint1->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
// Page hint for "/news/"
optimization_guide::proto::PageHint* page_hint1 = hint1->add_page_hints();
page_hint1->set_page_pattern("/news/");
optimization_guide::proto::Optimization* optimization1 =
page_hint1->add_whitelisted_optimizations();
optimization1->set_optimization_type(
optimization_guide::proto::RESOURCE_LOADING);
optimization_guide::proto::ResourceLoadingHint* resource_loading_hint1 =
optimization1->add_resource_loading_hints();
resource_loading_hint1->set_loading_optimization_type(
optimization_guide::proto::LOADING_BLOCK_RESOURCE);
resource_loading_hint1->set_resource_pattern("news_cruft.js");
// Page hint for "football"
optimization_guide::proto::PageHint* page_hint2 = hint1->add_page_hints();
page_hint2->set_page_pattern("football");
optimization_guide::proto::Optimization* optimization2 =
page_hint2->add_whitelisted_optimizations();
optimization2->set_optimization_type(
optimization_guide::proto::RESOURCE_LOADING);
optimization_guide::proto::ResourceLoadingHint* resource_loading_hint2 =
optimization2->add_resource_loading_hints();
resource_loading_hint2->set_loading_optimization_type(
optimization_guide::proto::LOADING_BLOCK_RESOURCE);
resource_loading_hint2->set_resource_pattern("football_cruft.js");
optimization_guide::proto::ResourceLoadingHint* resource_loading_hint3 =
optimization2->add_resource_loading_hints();
resource_loading_hint3->set_loading_optimization_type(
optimization_guide::proto::LOADING_BLOCK_RESOURCE);
resource_loading_hint3->set_resource_pattern("barball_cruft.js");
ProcessHints(config, "2.0.0");
RunUntilIdle();
}
void PreviewsOptimizationGuideTest::InitializeMultipleResourceLoadingHints(
size_t key_count,
size_t page_patterns_per_key) {
optimization_guide::proto::Configuration config;
for (size_t key_index = 0; key_index < key_count; ++key_index) {
optimization_guide::proto::Hint* hint = config.add_hints();
hint->set_key("somedomain" + base::NumberToString(key_index) + ".org");
hint->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
for (size_t page_pattern_index = 0;
page_pattern_index < page_patterns_per_key; ++page_pattern_index) {
// Page hint for "/news/"
optimization_guide::proto::PageHint* page_hint = hint->add_page_hints();
page_hint->set_page_pattern("/news/" +
base::NumberToString(page_pattern_index));
optimization_guide::proto::Optimization* optimization1 =
page_hint->add_whitelisted_optimizations();
optimization1->set_optimization_type(
optimization_guide::proto::RESOURCE_LOADING);
optimization_guide::proto::ResourceLoadingHint* resource_loading_hint_1 =
optimization1->add_resource_loading_hints();
resource_loading_hint_1->set_loading_optimization_type(
optimization_guide::proto::LOADING_BLOCK_RESOURCE);
resource_loading_hint_1->set_resource_pattern("news_cruft_1.js");
optimization_guide::proto::ResourceLoadingHint* resource_loading_hint_2 =
optimization1->add_resource_loading_hints();
resource_loading_hint_2->set_loading_optimization_type(
optimization_guide::proto::LOADING_BLOCK_RESOURCE);
resource_loading_hint_2->set_resource_pattern("news_cruft_2.js");
}
}
ProcessHints(config, "2.0.0");
RunUntilIdle();
}
void PreviewsOptimizationGuideTest::InitializeWithLitePageRedirectBlacklist() {
previews::BloomFilter blacklist_bloom_filter(7, 511);
blacklist_bloom_filter.Add("blacklisteddomain.com");
blacklist_bloom_filter.Add("blacklistedsubdomain.maindomain.co.in");
std::string blacklist_data((char*)&blacklist_bloom_filter.bytes()[0],
blacklist_bloom_filter.bytes().size());
optimization_guide::proto::Configuration config;
optimization_guide::proto::OptimizationFilter* blacklist_proto =
config.add_optimization_blacklists();
blacklist_proto->set_optimization_type(
optimization_guide::proto::LITE_PAGE_REDIRECT);
std::unique_ptr<optimization_guide::proto::BloomFilter> bloom_filter_proto =
std::make_unique<optimization_guide::proto::BloomFilter>();
bloom_filter_proto->set_num_hash_functions(7);
bloom_filter_proto->set_num_bits(511);
bloom_filter_proto->set_data(blacklist_data);
blacklist_proto->set_allocated_bloom_filter(bloom_filter_proto.release());
ProcessHints(config, "2.0.0");
RunUntilIdle();
}
TEST_F(PreviewsOptimizationGuideTest, IsWhitelistedWithoutHints) {
PreviewsUserData user_data(kDefaultPageId);
EXPECT_FALSE(guide()->IsWhitelisted(
&user_data, GURL("https://m.facebook.com"), PreviewsType::NOSCRIPT));
}
TEST_F(PreviewsOptimizationGuideTest,
ProcessHintsWhitelistForNoScriptPopulatedCorrectly) {
optimization_guide::proto::Configuration config;
optimization_guide::proto::Hint* hint1 = config.add_hints();
hint1->set_key("facebook.com");
hint1->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
optimization_guide::proto::Optimization* optimization1 =
hint1->add_whitelisted_optimizations();
optimization1->set_optimization_type(optimization_guide::proto::NOSCRIPT);
// Add a second optimization to ensure that the applicable optimizations are
// still whitelisted.
optimization_guide::proto::Optimization* optimization2 =
hint1->add_whitelisted_optimizations();
optimization2->set_optimization_type(
optimization_guide::proto::TYPE_UNSPECIFIED);
// Add a second hint.
optimization_guide::proto::Hint* hint2 = config.add_hints();
hint2->set_key("twitter.com");
hint2->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
optimization_guide::proto::Optimization* optimization3 =
hint2->add_whitelisted_optimizations();
optimization3->set_optimization_type(optimization_guide::proto::NOSCRIPT);
ProcessHints(config, "2.0.0");
RunUntilIdle();
PreviewsUserData user_data(kDefaultPageId);
// Twitter and Facebook should be whitelisted but not Google.
EXPECT_TRUE(guide()->IsWhitelisted(&user_data, GURL("https://m.facebook.com"),
PreviewsType::NOSCRIPT));
EXPECT_TRUE(guide()->IsWhitelisted(&user_data,
GURL("https://m.twitter.com/example"),
PreviewsType::NOSCRIPT));
EXPECT_FALSE(guide()->IsWhitelisted(&user_data, GURL("https://google.com"),
PreviewsType::NOSCRIPT));
}
// Test when resource loading hints are enabled.
TEST_F(PreviewsOptimizationGuideTest,
ProcessHintsWhitelistForResourceLoadingHintsPopulatedCorrectly) {
base::test::ScopedFeatureList scoped_list;
scoped_list.InitAndEnableFeature(features::kResourceLoadingHints);
optimization_guide::proto::Configuration config;
optimization_guide::proto::Hint* hint1 = config.add_hints();
hint1->set_key("facebook.com");
hint1->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
optimization_guide::proto::PageHint* page_hint1 = hint1->add_page_hints();
page_hint1->set_page_pattern("*");
optimization_guide::proto::Optimization* optimization1 =
page_hint1->add_whitelisted_optimizations();
optimization1->set_optimization_type(
optimization_guide::proto::RESOURCE_LOADING);
// Add additional optimizations to ensure that the applicable optimizations
// are still whitelisted.
optimization_guide::proto::Optimization* optimization2 =
hint1->add_whitelisted_optimizations();
optimization2->set_optimization_type(
optimization_guide::proto::TYPE_UNSPECIFIED);
optimization_guide::proto::PageHint* page_hint2 = hint1->add_page_hints();
page_hint2->set_page_pattern("*");
optimization_guide::proto::Optimization* optimization3 =
page_hint2->add_whitelisted_optimizations();
optimization3->set_optimization_type(optimization_guide::proto::NOSCRIPT);
// Add a second hint.
optimization_guide::proto::Hint* hint2 = config.add_hints();
hint2->set_key("twitter.com");
hint2->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
optimization_guide::proto::PageHint* page_hint3 = hint2->add_page_hints();
page_hint3->set_page_pattern("*");
optimization_guide::proto::Optimization* optimization4 =
page_hint3->add_whitelisted_optimizations();
optimization4->set_optimization_type(
optimization_guide::proto::RESOURCE_LOADING);
ProcessHints(config, "2.0.0");
RunUntilIdle();
PreviewsUserData user_data(kDefaultPageId);
// Twitter and Facebook should be whitelisted but not Google.
EXPECT_TRUE(guide()->IsWhitelisted(&user_data, GURL("https://m.facebook.com"),
PreviewsType::RESOURCE_LOADING_HINTS));
EXPECT_TRUE(guide()->IsWhitelisted(&user_data,
GURL("https://m.twitter.com/example"),
PreviewsType::RESOURCE_LOADING_HINTS));
EXPECT_FALSE(guide()->IsWhitelisted(&user_data, GURL("https://google.com"),
PreviewsType::RESOURCE_LOADING_HINTS));
}
// Test when both NoScript and resource loading hints are enabled.
TEST_F(
PreviewsOptimizationGuideTest,
ProcessHintsWhitelistForNoScriptAndResourceLoadingHintsPopulatedCorrectly) {
base::test::ScopedFeatureList scoped_list;
scoped_list.InitAndEnableFeature(features::kResourceLoadingHints);
optimization_guide::proto::Configuration config;
optimization_guide::proto::Hint* hint1 = config.add_hints();
hint1->set_key("facebook.com");
hint1->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
optimization_guide::proto::Optimization* optimization1 =
hint1->add_whitelisted_optimizations();
optimization1->set_optimization_type(optimization_guide::proto::NOSCRIPT);
// Add a second optimization to ensure that the applicable optimizations are
// still whitelisted.
optimization_guide::proto::Optimization* optimization2 =
hint1->add_whitelisted_optimizations();
optimization2->set_optimization_type(
optimization_guide::proto::TYPE_UNSPECIFIED);
// Add a second hint.
optimization_guide::proto::Hint* hint2 = config.add_hints();
hint2->set_key("twitter.com");
hint2->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
optimization_guide::proto::PageHint* page_hint1 = hint2->add_page_hints();
page_hint1->set_page_pattern("*");
optimization_guide::proto::Optimization* optimization3 =
page_hint1->add_whitelisted_optimizations();
optimization3->set_optimization_type(
optimization_guide::proto::RESOURCE_LOADING);
ProcessHints(config, "2.0.0");
RunUntilIdle();
PreviewsUserData user_data(kDefaultPageId);
// Twitter and Facebook should be whitelisted but not Google.
EXPECT_TRUE(guide()->IsWhitelisted(&user_data, GURL("https://m.facebook.com"),
PreviewsType::NOSCRIPT));
EXPECT_TRUE(guide()->IsWhitelisted(
&user_data, GURL("https://m.facebook.com/example.html"),
PreviewsType::NOSCRIPT));
EXPECT_TRUE(guide()->IsWhitelisted(&user_data,
GURL("https://m.twitter.com/example"),
PreviewsType::RESOURCE_LOADING_HINTS));
EXPECT_FALSE(guide()->IsWhitelisted(&user_data, GURL("https://google.com"),
PreviewsType::RESOURCE_LOADING_HINTS));
}
// This is a helper function for testing the experiment flags on the config for
// the optimization guide. It creates a test config with a hint containing
// multiple optimizations. The optimization under test will be marked with an
// experiment name if one is provided in |experiment_name|. It will then be
// tested to see if it's enabled, the expectation found in |expect_enabled|.
void PreviewsOptimizationGuideTest::DoExperimentFlagTest(
base::Optional<std::string> experiment_name,
bool expect_enabled) {
base::test::ScopedFeatureList scoped_list;
scoped_list.InitAndEnableFeature(features::kResourceLoadingHints);
optimization_guide::proto::Configuration config;
// Create a hint with two optimizations. One may be marked experimental
// depending on test configuration. The other is never marked experimental.
optimization_guide::proto::Hint* hint1 = config.add_hints();
hint1->set_key("facebook.com");
hint1->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
optimization_guide::proto::Optimization* optimization1 =
hint1->add_whitelisted_optimizations();
// NOSCRIPT is the optimization under test and may be marked experimental.
if (experiment_name.has_value()) {
optimization1->set_experiment_name(experiment_name.value());
}
optimization1->set_optimization_type(optimization_guide::proto::NOSCRIPT);
// RESOURCE_LOADING is never marked experimental.
optimization_guide::proto::PageHint* page_hint1 = hint1->add_page_hints();
page_hint1->set_page_pattern("*");
optimization_guide::proto::Optimization* optimization2 =
page_hint1->add_whitelisted_optimizations();
optimization2->set_optimization_type(
optimization_guide::proto::RESOURCE_LOADING);
// Add a second, non-experimental hint.
optimization_guide::proto::Hint* hint2 = config.add_hints();
hint2->set_key("twitter.com");
hint2->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
optimization_guide::proto::Optimization* optimization3 =
hint2->add_whitelisted_optimizations();
optimization3->set_optimization_type(optimization_guide::proto::NOSCRIPT);
ProcessHints(config, "2.0.0");
RunUntilIdle();
PreviewsUserData user_data(kDefaultPageId);
// Check to ensure the optimization under test (facebook noscript) is either
// enabled or disabled, depending on what the caller told us to expect.
EXPECT_EQ(expect_enabled,
guide()->IsWhitelisted(&user_data, GURL("https://m.facebook.com"),
PreviewsType::NOSCRIPT));
// RESOURCE_LOADING_HINTS for facebook should always be enabled.
EXPECT_TRUE(guide()->IsWhitelisted(&user_data, GURL("https://m.facebook.com"),
PreviewsType::RESOURCE_LOADING_HINTS));
// Twitter's NOSCRIPT should always be enabled; RESOURCE_LOADING_HINTS is not
// configured and should be disabled.
EXPECT_TRUE(guide()->IsWhitelisted(&user_data,
GURL("https://m.twitter.com/example"),
PreviewsType::NOSCRIPT));
// Google (which is not configured at all) should always have both NOSCRIPT
// and RESOURCE_LOADING_HINTS disabled.
EXPECT_FALSE(guide()->IsWhitelisted(&user_data, GURL("https://google.com"),
PreviewsType::NOSCRIPT));
}
TEST_F(PreviewsOptimizationGuideTest,
HandlesExperimentalFlagWithNoExperimentFlaggedOrEnabled) {
// With the optimization NOT flagged as experimental and no experiment
// enabled, the optimization should be enabled.
base::test::ScopedFeatureList scoped_list;
scoped_list.InitAndDisableFeature(features::kOptimizationHintsExperiments);
DoExperimentFlagTest(base::nullopt, true);
}
TEST_F(PreviewsOptimizationGuideTest,
HandlesExperimentalFlagWithEmptyExperimentName) {
// Empty experiment names should be equivalent to no experiment flag set.
base::test::ScopedFeatureList scoped_list;
scoped_list.InitAndDisableFeature(features::kOptimizationHintsExperiments);
DoExperimentFlagTest("", true);
}
TEST_F(PreviewsOptimizationGuideTest,
HandlesExperimentalFlagWithExperimentConfiguredAndNotRunning) {
// With the optimization flagged as experimental and no experiment
// enabled, the optimization should be disabled.
base::test::ScopedFeatureList scoped_list;
scoped_list.InitAndDisableFeature(features::kOptimizationHintsExperiments);
DoExperimentFlagTest("foo_experiment", false);
}
TEST_F(PreviewsOptimizationGuideTest,
HandlesExperimentalFlagWithExperimentConfiguredAndSameOneRunning) {
// With the optimization flagged as experimental and an experiment with that
// name running, the optimization should be enabled.
base::test::ScopedFeatureList scoped_list;
scoped_list.InitAndEnableFeatureWithParameters(
features::kOptimizationHintsExperiments,
{{"experiment_name", "foo_experiment"}});
DoExperimentFlagTest("foo_experiment", true);
}
TEST_F(PreviewsOptimizationGuideTest,
HandlesExperimentalFlagWithExperimentConfiguredAndDifferentOneRunning) {
// With the optimization flagged as experimental and a *different* experiment
// enabled, the optimization should be disabled.
base::test::ScopedFeatureList scoped_list;
scoped_list.InitAndEnableFeatureWithParameters(
features::kOptimizationHintsExperiments,
{{"experiment_name", "bar_experiment"}});
DoExperimentFlagTest("foo_experiment", false);
}
TEST_F(PreviewsOptimizationGuideTest, EnsureExperimentsDisabledByDefault) {
// Mark an optimization as experiment, and ensure it's disabled even though we
// don't explicitly enable or disable the feature as part of the test. This
// ensures the experiments feature is disabled by default.
DoExperimentFlagTest("foo_experiment", false);
}
TEST_F(PreviewsOptimizationGuideTest, ProcessHintsUnsupportedKeyRepIsIgnored) {
optimization_guide::proto::Configuration config;
optimization_guide::proto::Hint* hint = config.add_hints();
hint->set_key("facebook.com");
hint->set_key_representation(
optimization_guide::proto::REPRESENTATION_UNSPECIFIED);
optimization_guide::proto::Optimization* optimization =
hint->add_whitelisted_optimizations();
optimization->set_optimization_type(optimization_guide::proto::NOSCRIPT);
ProcessHints(config, "2.0.0");
RunUntilIdle();
PreviewsUserData user_data(kDefaultPageId);
EXPECT_FALSE(guide()->IsWhitelisted(
&user_data, GURL("https://m.facebook.com"), PreviewsType::NOSCRIPT));
}
TEST_F(PreviewsOptimizationGuideTest,
ProcessHintsUnsupportedOptimizationIsIgnored) {
optimization_guide::proto::Configuration config;
optimization_guide::proto::Hint* hint = config.add_hints();
hint->set_key("facebook.com");
hint->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
optimization_guide::proto::Optimization* optimization =
hint->add_whitelisted_optimizations();
optimization->set_optimization_type(
optimization_guide::proto::TYPE_UNSPECIFIED);
ProcessHints(config, "2.0.0");
RunUntilIdle();
PreviewsUserData user_data(kDefaultPageId);
EXPECT_FALSE(guide()->IsWhitelisted(
&user_data, GURL("https://m.facebook.com"), PreviewsType::NOSCRIPT));
}
TEST_F(PreviewsOptimizationGuideTest, ProcessHintsWithExistingSentinel) {
base::HistogramTester histogram_tester;
// Create valid config.
optimization_guide::proto::Configuration config;
optimization_guide::proto::Hint* hint1 = config.add_hints();
hint1->set_key("facebook.com");
hint1->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
optimization_guide::proto::Optimization* optimization1 =
hint1->add_whitelisted_optimizations();
optimization1->set_optimization_type(optimization_guide::proto::NOSCRIPT);
// Create sentinel file for version 2.0.0.
const base::FilePath sentinel_path =
temp_dir().Append(FILE_PATH_LITERAL("previews_config_sentinel.txt"));
base::WriteFile(sentinel_path, "2.0.0", 5);
// Verify config not processed for version 2.0.0 (same as sentinel).
ProcessHints(config, "2.0.0");
RunUntilIdle();
PreviewsUserData user_data(kDefaultPageId);
EXPECT_FALSE(guide()->IsWhitelisted(
&user_data, GURL("https://m.facebook.com"), PreviewsType::NOSCRIPT));
EXPECT_TRUE(base::PathExists(sentinel_path));
histogram_tester.ExpectUniqueSample("Previews.ProcessHintsResult",
2 /* FAILED_FINISH_PROCESSING */, 1);
// Now verify config is processed for different version and sentinel cleared.
ProcessHints(config, "3.0.0");
RunUntilIdle();
EXPECT_TRUE(guide()->IsWhitelisted(&user_data, GURL("https://m.facebook.com"),
PreviewsType::NOSCRIPT));
EXPECT_FALSE(base::PathExists(sentinel_path));
histogram_tester.ExpectBucketCount("Previews.ProcessHintsResult",
1 /* PROCESSED_PREVIEWS_HINTS */, 1);
}
TEST_F(PreviewsOptimizationGuideTest, ProcessHintsWithInvalidSentinelFile) {
base::HistogramTester histogram_tester;
// Create valid config.
optimization_guide::proto::Configuration config;
optimization_guide::proto::Hint* hint1 = config.add_hints();
hint1->set_key("facebook.com");
hint1->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
optimization_guide::proto::Optimization* optimization1 =
hint1->add_whitelisted_optimizations();
optimization1->set_optimization_type(optimization_guide::proto::NOSCRIPT);
// Create sentinel file with invalid contents.
const base::FilePath sentinel_path =
temp_dir().Append(FILE_PATH_LITERAL("previews_config_sentinel.txt"));
base::WriteFile(sentinel_path, "bad-2.0.0", 5);
// Verify config not processed for existing sentinel with bad value but
// that the existinel sentinel file is deleted.
ProcessHints(config, "2.0.0");
RunUntilIdle();
PreviewsUserData user_data(kDefaultPageId);
EXPECT_FALSE(guide()->IsWhitelisted(
&user_data, GURL("https://m.facebook.com"), PreviewsType::NOSCRIPT));
EXPECT_FALSE(base::PathExists(sentinel_path));
histogram_tester.ExpectUniqueSample("Previews.ProcessHintsResult",
2 /* FAILED_FINISH_PROCESSING */, 1);
// Now verify config is processed with sentinel cleared.
ProcessHints(config, "2.0.0");
RunUntilIdle();
EXPECT_TRUE(guide()->IsWhitelisted(&user_data, GURL("https://m.facebook.com"),
PreviewsType::NOSCRIPT));
EXPECT_FALSE(base::PathExists(sentinel_path));
histogram_tester.ExpectBucketCount("Previews.ProcessHintsResult",
1 /* PROCESSED_PREVIEWS_HINTS */, 1);
}
TEST_F(PreviewsOptimizationGuideTest, ProcessHintConfigWithNoKeyFailsDcheck) {
optimization_guide::proto::Configuration config;
optimization_guide::proto::Hint* hint = config.add_hints();
hint->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
optimization_guide::proto::Optimization* optimization =
hint->add_whitelisted_optimizations();
optimization->set_optimization_type(optimization_guide::proto::NOSCRIPT);
EXPECT_DCHECK_DEATH({
ProcessHints(config, "2.0.0");
RunUntilIdle();
});
}
TEST_F(PreviewsOptimizationGuideTest,
ProcessHintsConfigWithDuplicateKeysFailsDcheck) {
optimization_guide::proto::Configuration config;
optimization_guide::proto::Hint* hint1 = config.add_hints();
hint1->set_key("facebook.com");
hint1->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
optimization_guide::proto::Optimization* optimization1 =
hint1->add_whitelisted_optimizations();
optimization1->set_optimization_type(optimization_guide::proto::NOSCRIPT);
optimization_guide::proto::Hint* hint2 = config.add_hints();
hint2->set_key("facebook.com");
hint2->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
optimization_guide::proto::Optimization* optimization2 =
hint2->add_whitelisted_optimizations();
optimization2->set_optimization_type(optimization_guide::proto::NOSCRIPT);
EXPECT_DCHECK_DEATH({
ProcessHints(config, "2.0.0");
RunUntilIdle();
});
}
TEST_F(PreviewsOptimizationGuideTest, IsWhitelistedWithMultipleHintMatches) {
optimization_guide::proto::Configuration config;
// Whitelist NoScript for indoor.sports.yahoo.com:
optimization_guide::proto::Hint* hint1 = config.add_hints();
hint1->set_key("indoor.sports.yahoo.com");
hint1->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
optimization_guide::proto::Optimization* optimization1 =
hint1->add_whitelisted_optimizations();
optimization1->set_optimization_type(optimization_guide::proto::NOSCRIPT);
optimization1->set_inflation_percent(10);
// No optimizations for sports.yahoo.com:
optimization_guide::proto::Hint* hint2 = config.add_hints();
hint2->set_key("sports.yahoo.com");
hint2->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
// Whitelist NoScript for base domain yahoo.com:
optimization_guide::proto::Hint* hint3 = config.add_hints();
hint3->set_key("yahoo.com");
hint3->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
optimization_guide::proto::Optimization* optimization3 =
hint3->add_whitelisted_optimizations();
optimization3->set_optimization_type(optimization_guide::proto::NOSCRIPT);
optimization3->set_inflation_percent(30);
// No optimizations for mail.yahoo.com:
optimization_guide::proto::Hint* hint4 = config.add_hints();
hint4->set_key("mail.yahoo.com");
hint4->set_key_representation(optimization_guide::proto::HOST_SUFFIX);
ProcessHints(config, "2.0.0");
RunUntilIdle();
PreviewsUserData user_data(1);
EXPECT_TRUE(guide()->IsWhitelisted(&user_data, GURL("https://yahoo.com"),
PreviewsType::NOSCRIPT));
EXPECT_EQ(30, user_data.data_savings_inflation_percent());
PreviewsUserData user_data2(2);
// Uses "sports.yahoo.com" match before "yahoo.com" match.
EXPECT_FALSE(guide()->IsWhitelisted(
&user_data, GURL("https://sports.yahoo.com"), PreviewsType::NOSCRIPT));
PreviewsUserData user_data3(3);
// Uses "yahoo.com" match before "mail.yahoo.com" match.
EXPECT_TRUE(guide()->IsWhitelisted(
&user_data3, GURL("https://mail.yahoo.com"), PreviewsType::NOSCRIPT));
EXPECT_EQ(30, user_data3.data_savings_inflation_percent());
PreviewsUserData user_data4(4);
// Uses "indoor.sports.yahoo.com" match before "sports.yahoo.com" match.
EXPECT_TRUE(guide()->IsWhitelisted(&user_data4,
GURL("https://indoor.sports.yahoo.com"),
PreviewsType::NOSCRIPT));
EXPECT_EQ(10, user_data4.data_savings_inflation_percent());
PreviewsUserData user_data5(5);
// Uses "sports.yahoo.com" match before "yahoo.com" match.
EXPECT_FALSE(guide()->IsWhitelisted(&user_data5,
GURL("https://outdoor.sports.yahoo.com"),
PreviewsType::NOSCRIPT));
}
TEST_F(PreviewsOptimizationGuideTest, MaybeLoadOptimizationHints) {
base::HistogramTester histogram_tester;
base::test::ScopedFeatureList scoped_list;
scoped_list.InitAndEnableFeature(features::kResourceLoadingHints);
InitializeFixedCountResourceLoadingHints();
EXPECT_TRUE(guide()->MaybeLoadOptimizationHints(
GURL("https://somedomain.org/"), base::DoNothing()));
EXPECT_TRUE(guide()->MaybeLoadOptimizationHints(
GURL("https://www.somedomain.org/news/football"),
base::BindOnce(
&PreviewsOptimizationGuideTest::MaybeLoadOptimizationHintsCallback,
base::Unretained(this))));
EXPECT_FALSE(guide()->MaybeLoadOptimizationHints(
GURL("https://www.unknown.com"), base::DoNothing()));
RunUntilIdle();
histogram_tester.ExpectUniqueSample(
"ResourceLoadingHints.PageHints.ProcessedCount", 2, 1);
histogram_tester.ExpectUniqueSample(
"ResourceLoadingHints.ResourceHints.TotalReceived", 3, 1);
histogram_tester.ExpectUniqueSample(
"ResourceLoadingHints.PageHints.TotalReceived", 2, 1);
// Verify loaded hint data for www.somedomain.org
EXPECT_EQ(GURL("https://www.somedomain.org/news/football"),
loaded_hints_document_gurl());
EXPECT_EQ(1ul, loaded_hints_resource_patterns().size());
EXPECT_EQ("news_cruft.js", loaded_hints_resource_patterns().front());
PreviewsUserData user_data(kDefaultPageId);
// Verify whitelisting from loaded page hints.
EXPECT_TRUE(guide()->IsWhitelisted(
&user_data,
GURL("https://www.somedomain.org/news/weather/raininginseattle"),
PreviewsType::RESOURCE_LOADING_HINTS));
EXPECT_TRUE(guide()->IsWhitelisted(
&user_data,
GURL("https://www.somedomain.org/football/seahawksrebuildingyear"),
PreviewsType::RESOURCE_LOADING_HINTS));
EXPECT_FALSE(guide()->IsWhitelisted(
&user_data, GURL("https://www.somedomain.org/unhinted"),
PreviewsType::RESOURCE_LOADING_HINTS));
}
// Test that optimization hints with multiple page patterns is processed
// correctly.
TEST_F(PreviewsOptimizationGuideTest,
LoadManyResourceLoadingOptimizationHints) {
base::HistogramTester histogram_tester;
base::test::ScopedFeatureList scoped_list;
scoped_list.InitAndEnableFeature(features::kResourceLoadingHints);
const size_t key_count = 20;
const size_t page_patterns_per_key = 25;
ASSERT_EQ(previews::params::GetMaxPageHintsInMemoryThreshhold(),
key_count * page_patterns_per_key);
// Count of page patterns is within the threshold.
ASSERT_LE(key_count * page_patterns_per_key,
previews::params::GetMaxPageHintsInMemoryThreshhold());
InitializeMultipleResourceLoadingHints(key_count, page_patterns_per_key);
EXPECT_TRUE(guide()->MaybeLoadOptimizationHints(
GURL("https://somedomain0.org/"), base::DoNothing()));
EXPECT_TRUE(guide()->MaybeLoadOptimizationHints(
GURL("https://www.somedomain0.org/news0/football"),
base::BindOnce(
&PreviewsOptimizationGuideTest::MaybeLoadOptimizationHintsCallback,
base::Unretained(this))));
EXPECT_TRUE(guide()->MaybeLoadOptimizationHints(
GURL("https://www.somedomain0.org/news499/football"),
base::BindOnce(
&PreviewsOptimizationGuideTest::MaybeLoadOptimizationHintsCallback,
base::Unretained(this))));
EXPECT_TRUE(guide()->MaybeLoadOptimizationHints(
GURL("https://www.somedomain0.org/news500/football"),
base::BindOnce(
&PreviewsOptimizationGuideTest::MaybeLoadOptimizationHintsCallback,
base::Unretained(this))));
EXPECT_TRUE(guide()->MaybeLoadOptimizationHints(
GURL("https://somedomain19.org/"), base::DoNothing()));
EXPECT_FALSE(guide()->MaybeLoadOptimizationHints(
GURL("https://somedomain20.org/"), base::DoNothing()));
EXPECT_FALSE(guide()->MaybeLoadOptimizationHints(
GURL("https://www.unknown.com"), base::DoNothing()));
RunUntilIdle();
histogram_tester.ExpectUniqueSample(
"ResourceLoadingHints.PageHints.ProcessedCount", page_patterns_per_key,
key_count);
histogram_tester.ExpectUniqueSample(
"ResourceLoadingHints.ResourceHints.TotalReceived",
key_count * page_patterns_per_key * 2, 1);
histogram_tester.ExpectUniqueSample(
"ResourceLoadingHints.PageHints.TotalReceived",
key_count * page_patterns_per_key, 1);
}
// Test that only up to GetMaxPageHintsInMemoryThreshhold() page hints
// are loaded to the memory.
TEST_F(PreviewsOptimizationGuideTest,
LoadTooManyResourceLoadingOptimizationHints) {
base::HistogramTester histogram_tester;
base::test::ScopedFeatureList scoped_list;
scoped_list.InitAndEnableFeature(features::kResourceLoadingHints);
const size_t key_count = 21;
const size_t page_patterns_per_key = 25;
ASSERT_EQ(previews::params::GetMaxPageHintsInMemoryThreshhold(),
20u * page_patterns_per_key);
// Provide more page patterns than the threshold.
ASSERT_GT(key_count * page_patterns_per_key,
previews::params::GetMaxPageHintsInMemoryThreshhold());
InitializeMultipleResourceLoadingHints(key_count, page_patterns_per_key);
EXPECT_TRUE(guide()->MaybeLoadOptimizationHints(
GURL("https://somedomain0.org/"), base::DoNothing()));
EXPECT_TRUE(guide()->MaybeLoadOptimizationHints(
GURL("https://www.somedomain0.org/news0/football"),
base::BindOnce(
&PreviewsOptimizationGuideTest::MaybeLoadOptimizationHintsCallback,
base::Unretained(this))));
EXPECT_TRUE(guide()->MaybeLoadOptimizationHints(
GURL("https://somedomain19.org/"), base::DoNothing()));
EXPECT_TRUE(guide()->MaybeLoadOptimizationHints(
GURL("https://www.somedomain19.org/news0/football"),
base::BindOnce(
&PreviewsOptimizationGuideTest::MaybeLoadOptimizationHintsCallback,
base::Unretained(this))));
// The last page pattern should be dropped since it exceeds the threshold
// count.
EXPECT_FALSE(guide()->MaybeLoadOptimizationHints(
GURL("https://somedomain20.org/"), base::DoNothing()));
EXPECT_FALSE(guide()->MaybeLoadOptimizationHints(
GURL("https://www.somedomain20.org/news0/football"),
base::BindOnce(
&PreviewsOptimizationGuideTest::MaybeLoadOptimizationHintsCallback,
base::Unretained(this))));
RunUntilIdle();
histogram_tester.ExpectUniqueSample(
"ResourceLoadingHints.PageHints.ProcessedCount", page_patterns_per_key,
key_count);
histogram_tester.ExpectUniqueSample(
"ResourceLoadingHints.ResourceHints.TotalReceived",
key_count * page_patterns_per_key * 2, 1);
histogram_tester.ExpectUniqueSample(
"ResourceLoadingHints.PageHints.TotalReceived",
key_count * page_patterns_per_key, 1);
}
TEST_F(PreviewsOptimizationGuideTest,
MaybeLoadOptimizationHintsWithoutEnabledPageHintsFeature) {
// Without PageHints-oriented feature enabled, never see
// enabled, the optimization should be disabled.
base::test::ScopedFeatureList scoped_list;
scoped_list.InitAndDisableFeature(features::kResourceLoadingHints);
InitializeFixedCountResourceLoadingHints();
EXPECT_FALSE(guide()->MaybeLoadOptimizationHints(
GURL("https://www.somedomain.org"), base::DoNothing()));
RunUntilIdle();
PreviewsUserData user_data(kDefaultPageId);
EXPECT_FALSE(guide()->IsWhitelisted(
&user_data,
GURL("https://www.somedomain.org/news/weather/raininginseattle"),
PreviewsType::RESOURCE_LOADING_HINTS));
}
TEST_F(PreviewsOptimizationGuideTest, IsBlacklisted) {
base::test::ScopedFeatureList scoped_list;
scoped_list.InitAndEnableFeature(features::kLitePageServerPreviews);
EXPECT_FALSE(
guide()->IsBlacklisted(GURL("https://m.blacklisteddomain.com/path"),
PreviewsType::LITE_PAGE_REDIRECT));
InitializeWithLitePageRedirectBlacklist();
EXPECT_TRUE(
guide()->IsBlacklisted(GURL("https://m.blacklisteddomain.com/path"),
PreviewsType::LITE_PAGE_REDIRECT));
EXPECT_FALSE(guide()->IsBlacklisted(
GURL("https://m.blacklisteddomain.com/path"), PreviewsType::NOSCRIPT));
EXPECT_TRUE(guide()->IsBlacklisted(
GURL("https://blacklistedsubdomain.maindomain.co.in"),
PreviewsType::LITE_PAGE_REDIRECT));
EXPECT_FALSE(guide()->IsBlacklisted(GURL("https://maindomain.co.in"),
PreviewsType::LITE_PAGE_REDIRECT));
}
TEST_F(PreviewsOptimizationGuideTest,
IsBlacklistedWithLitePageServerPreviewsDisabled) {
base::test::ScopedFeatureList scoped_list;
scoped_list.InitAndDisableFeature(features::kLitePageServerPreviews);
InitializeWithLitePageRedirectBlacklist();
EXPECT_FALSE(
guide()->IsBlacklisted(GURL("https://m.blacklisteddomain.com/path"),
PreviewsType::LITE_PAGE_REDIRECT));
}
TEST_F(PreviewsOptimizationGuideTest, RemoveObserverCalledAtDestruction) {
EXPECT_FALSE(optimization_guide_service()->RemoveObserverCalled());
ResetGuide();
EXPECT_TRUE(optimization_guide_service()->RemoveObserverCalled());
}
} // namespace previews