blob: ae8569c175ca489dca3b8f4af9cf27dde5e619fe [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include <algorithm>
#include <functional>
#include <string>
#include <utility>
#include <vector>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_run_loop_timeout.h"
#include "base/test/test_timeouts.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/api/declarative_net_request/dnr_test_base.h"
#include "chrome/browser/extensions/chrome_test_extension_loader.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/load_error_reporter.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/api/declarative_net_request/composite_matcher.h"
#include "extensions/browser/api/declarative_net_request/constants.h"
#include "extensions/browser/api/declarative_net_request/declarative_net_request_api.h"
#include "extensions/browser/api/declarative_net_request/declarative_net_request_prefs_helper.h"
#include "extensions/browser/api/declarative_net_request/file_backed_ruleset_source.h"
#include "extensions/browser/api/declarative_net_request/parse_info.h"
#include "extensions/browser/api/declarative_net_request/rules_count_pair.h"
#include "extensions/browser/api/declarative_net_request/rules_monitor_service.h"
#include "extensions/browser/api/declarative_net_request/ruleset_manager.h"
#include "extensions/browser/api/declarative_net_request/ruleset_matcher.h"
#include "extensions/browser/api/declarative_net_request/test_utils.h"
#include "extensions/browser/api/declarative_net_request/utils.h"
#include "extensions/browser/api_test_utils.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "extensions/common/api/declarative_net_request.h"
#include "extensions/common/api/declarative_net_request/constants.h"
#include "extensions/common/api/declarative_net_request/test_utils.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/file_util.h"
#include "extensions/common/install_warning.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/url_pattern.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace extensions {
namespace declarative_net_request {
namespace {
constexpr char kJSONRulesFilename[] = "rules_file.json";
constexpr char kSmallRegexFilter[] = "http://(yahoo|google)\\.com";
constexpr char kLargeRegexFilter[] = ".{512}x";
constexpr char kId1[] = "1.json";
constexpr char kId2[] = "2.json";
constexpr char kId3[] = "3.json";
constexpr char kId4[] = "4.json";
constexpr char kDefaultRulesetID[] = "id";
namespace dnr_api = extensions::api::declarative_net_request;
using ::testing::ElementsAreArray;
using ::testing::Field;
using ::testing::Pointee;
using ::testing::Property;
using ::testing::UnorderedElementsAre;
using ::testing::UnorderedElementsAreArray;
template <class T>
base::Value::List VectorToList(const std::vector<T>& values) {
base::Value::List lv;
for (const auto& value : values) {
lv.Append(value);
}
return lv;
}
std::string GetErrorWithFilename(
const std::string& error,
const std::string& filename = kJSONRulesFilename) {
return base::StringPrintf("%s: %s", filename.c_str(), error.c_str());
}
InstallWarning GetLargeRegexWarning(
int rule_id,
const std::string& filename = kJSONRulesFilename) {
return InstallWarning(ErrorUtils::FormatErrorMessage(
GetErrorWithFilename(kErrorRegexTooLarge, filename),
base::NumberToString(rule_id), kRegexFilterKey),
dnr_api::ManifestKeys::kDeclarativeNetRequest,
dnr_api::DNRInfo::kRuleResources);
}
// Returns the vector of install warnings, filtering out the one associated with
// a deprecated manifest version.
// TODO(https://crbug.com/1269161): Remove this method when the associated tests
// are updated to MV3.
std::vector<InstallWarning> GetFilteredInstallWarnings(
const Extension& extension) {
std::vector<InstallWarning> filtered_warnings;
// InstallWarning isn't copyable (but is movable), so we have to do a bit of
// extra legwork to get a vector here.
for (const auto& warning : extension.install_warnings()) {
if (warning.message == manifest_errors::kManifestV2IsDeprecatedWarning) {
continue;
}
filtered_warnings.emplace_back(warning.message, warning.key,
warning.specific);
}
return filtered_warnings;
}
// Base test fixture to test indexing of rulesets.
class DeclarativeNetRequestUnittest : public DNRTestBase {
public:
DeclarativeNetRequestUnittest() = default;
// DNRTestBase override.
void SetUp() override {
DNRTestBase::SetUp();
RulesMonitorService::GetFactoryInstance()->SetTestingFactory(
browser_context(),
base::BindRepeating([](content::BrowserContext* context) {
return static_cast<std::unique_ptr<KeyedService>>(
RulesMonitorService::CreateInstanceForTesting(context));
}));
ASSERT_TRUE(RulesMonitorService::Get(browser_context()));
extension_prefs_ = ExtensionPrefs::Get(browser_context());
loader_ = CreateExtensionLoader();
extension_dir_ =
temp_dir().GetPath().Append(FILE_PATH_LITERAL("test_extension"));
// Create extension directory.
ASSERT_TRUE(base::CreateDirectory(extension_dir_));
// Sanity check that the extension can index and enable up to
// |rule_limit_override_| + |global_limit_override_| rules.
ASSERT_EQ(300, GetMaximumRulesPerRuleset());
}
protected:
RulesetManager* manager() {
return RulesMonitorService::Get(browser_context())->ruleset_manager();
}
// Loads the extension and verifies the indexed ruleset location and histogram
// counts.
void LoadAndExpectSuccess(size_t expected_rules_count,
size_t expected_enabled_rules_count,
bool expect_rulesets_indexed) {
base::HistogramTester tester;
WriteExtensionData();
loader_->set_should_fail(false);
// Clear all load errors before loading the extension.
error_reporter()->ClearErrors();
extension_ = loader_->LoadExtension(extension_dir_);
ASSERT_TRUE(extension_.get());
auto ruleset_filter = FileBackedRulesetSource::RulesetFilter::kIncludeAll;
if (GetParam() == ExtensionLoadType::PACKED) {
ruleset_filter =
FileBackedRulesetSource::RulesetFilter::kIncludeManifestEnabled;
}
EXPECT_TRUE(AreAllIndexedStaticRulesetsValid(*extension_, browser_context(),
ruleset_filter));
// Ensure no load errors were reported.
EXPECT_TRUE(error_reporter()->GetErrors()->empty());
// The histograms below are not logged for unpacked extensions.
if (GetParam() == ExtensionLoadType::PACKED) {
int expected_samples = expect_rulesets_indexed ? 1 : 0;
tester.ExpectTotalCount(kIndexAndPersistRulesTimeHistogram,
expected_samples);
tester.ExpectUniqueSample(kManifestEnabledRulesCountHistogram,
expected_enabled_rules_count, expected_samples);
}
}
void LoadAndExpectError(const std::string& expected_error,
const std::string& filename) {
// The error should be prepended with the JSON filename.
std::string error_with_filename =
GetErrorWithFilename(expected_error, filename);
base::HistogramTester tester;
WriteExtensionData();
loader_->set_should_fail(true);
// Clear all load errors before loading the extension.
error_reporter()->ClearErrors();
extension_ = loader_->LoadExtension(extension_dir_);
EXPECT_FALSE(extension_.get());
// Verify the error. Only verify if the |expected_error| is a substring of
// the actual error, since some string may be prepended/appended while
// creating the actual error.
const std::vector<std::u16string>* errors = error_reporter()->GetErrors();
ASSERT_EQ(1u, errors->size());
EXPECT_NE(std::u16string::npos,
errors->at(0).find(base::UTF8ToUTF16(error_with_filename)))
<< "expected: " << error_with_filename << " actual: " << errors->at(0);
tester.ExpectTotalCount(kIndexAndPersistRulesTimeHistogram, 0u);
tester.ExpectTotalCount(kManifestEnabledRulesCountHistogram, 0u);
}
void LoadAndExpectParseFailure(ParseResult parse_result,
int rule_id,
const std::string& filename) {
std::string expected_error = GetParseError(parse_result, rule_id);
if (GetParam() == ExtensionLoadType::UNPACKED) {
// All static rulesets are indexed (and therefore all static rules are
// parsed) at installation time for unpacked extensions, with invalid
// rules resulting in a hard installation error where possible.
LoadAndExpectError(expected_error, filename);
return;
}
// Static ruleset indexing for packed extensions is deferred until the
// ruleset is enabled. Invalid static rules are ignored with a warning
// raised.
base::HistogramTester tester;
WriteExtensionData();
loader_->set_should_fail(false);
// Clear all load errors before loading the extension.
error_reporter()->ClearErrors();
extension_ = loader_->LoadExtension(extension_dir_);
// Neither warnings nor errors are raised for problematic static rules for
// packed extensions.
EXPECT_TRUE(extension_.get());
const std::vector<std::u16string>* errors = error_reporter()->GetErrors();
ASSERT_TRUE(errors->empty());
}
enum class RulesetScope { kDynamic, kSession };
// Runs the updateDynamicRules/updateSessionRules extension function based on
// |scope| and verifies the success/failure based on |expected_error|.
void RunUpdateRulesFunction(const Extension& extension,
const std::vector<int>& rule_ids_to_remove,
const std::vector<TestRule>& rules_to_add,
RulesetScope scope,
const std::string* expected_error = nullptr) {
base::Value::List ids_to_remove_value = VectorToList(rule_ids_to_remove);
base::Value::List rules_to_add_value = ToListValue(rules_to_add);
constexpr const char kParams[] = R"(
[{
"addRules": $1,
"removeRuleIds": $2
}]
)";
const std::string json_args = content::JsReplace(
kParams, std::move(rules_to_add_value), std::move(ids_to_remove_value));
scoped_refptr<ExtensionFunction> update_function;
switch (scope) {
case RulesetScope::kDynamic:
update_function = base::MakeRefCounted<
DeclarativeNetRequestUpdateDynamicRulesFunction>();
break;
case RulesetScope::kSession:
update_function = base::MakeRefCounted<
DeclarativeNetRequestUpdateSessionRulesFunction>();
break;
}
update_function->set_extension(&extension);
update_function->set_has_callback(true);
if (!expected_error) {
ASSERT_TRUE(api_test_utils::RunFunction(update_function.get(), json_args,
browser_context()));
return;
}
ASSERT_EQ(*expected_error,
api_test_utils::RunFunctionAndReturnError(
update_function.get(), json_args, browser_context()));
}
// Runs getDynamicRules/getSessionRules extension function and populates
// |result|.
void RunGetRulesFunction(const Extension& extension,
RulesetScope scope,
base::Value* result) {
RunGetRulesFunction(extension, scope, absl::nullopt /* rule_ids */, result);
}
void RunGetRulesFunction(
const Extension& extension,
RulesetScope scope,
const absl::optional<const std::vector<int>>& rule_ids,
base::Value* result) {
CHECK(result);
std::string json_args = "[]";
if (rule_ids) {
constexpr const char kParams[] = R"(
[{
"ruleIds": $1
}]
)";
base::Value::List rule_ids_value = VectorToList(rule_ids.value());
json_args = content::JsReplace(kParams, std::move(rule_ids_value));
}
scoped_refptr<ExtensionFunction> function;
switch (scope) {
case RulesetScope::kDynamic:
function = base::MakeRefCounted<
DeclarativeNetRequestGetDynamicRulesFunction>();
break;
case RulesetScope::kSession:
function = base::MakeRefCounted<
DeclarativeNetRequestGetSessionRulesFunction>();
break;
}
function->set_extension(&extension);
function->set_has_callback(true);
auto result_ptr = api_test_utils::RunFunctionAndReturnSingleResult(
function.get(), json_args, browser_context());
ASSERT_TRUE(result_ptr);
ASSERT_TRUE(result_ptr->is_list());
*result = std::move(*result_ptr);
}
void RunUpdateEnabledRulesetsFunction(
const Extension& extension,
const std::vector<std::string>& ruleset_ids_to_remove,
const std::vector<std::string>& ruleset_ids_to_add,
absl::optional<std::string> expected_error) {
base::Value::List ids_to_remove_value = ToListValue(ruleset_ids_to_remove);
base::Value::List ids_to_add_value = ToListValue(ruleset_ids_to_add);
constexpr const char kParams[] = R"(
[{
"disableRulesetIds": $1,
"enableRulesetIds": $2
}]
)";
const std::string json_args = content::JsReplace(
kParams, std::move(ids_to_remove_value), std::move(ids_to_add_value));
auto function = base::MakeRefCounted<
DeclarativeNetRequestUpdateEnabledRulesetsFunction>();
function->set_extension(&extension);
function->set_has_callback(true);
if (!expected_error) {
EXPECT_TRUE(api_test_utils::RunFunction(function.get(), json_args,
browser_context()));
return;
}
EXPECT_EQ(expected_error,
api_test_utils::RunFunctionAndReturnError(
function.get(), json_args, browser_context()));
}
void VerifyGetEnabledRulesetsFunction(
const Extension& extension,
const std::vector<std::string>& expected_ids) {
auto function =
base::MakeRefCounted<DeclarativeNetRequestGetEnabledRulesetsFunction>();
function->set_extension(&extension);
function->set_has_callback(true);
absl::optional<base::Value> result =
api_test_utils::RunFunctionAndReturnSingleResult(
function.get(), "[]" /* args */, browser_context());
ASSERT_TRUE(result);
ASSERT_TRUE(result->is_list());
std::u16string error;
std::vector<std::string> actual_ids;
for (const auto& val : result->GetList())
actual_ids.push_back(val.GetString());
EXPECT_THAT(expected_ids, UnorderedElementsAreArray(actual_ids));
}
void RunUpdateStaticRulesFunction(
const Extension& extension,
const std::string& ruleset_id,
const std::vector<int>& rule_ids_to_disable,
const std::vector<int>& rule_ids_to_enable,
absl::optional<std::string> expected_error) {
base::Value::List ids_to_disable = VectorToList(rule_ids_to_disable);
base::Value::List ids_to_enable = VectorToList(rule_ids_to_enable);
constexpr const char kParams[] = R"([{ "rulesetId": $1,
"disableRuleIds": $2,
"enableRuleIds": $3 }])";
const std::string json_args = content::JsReplace(
kParams, ruleset_id, base::Value(std::move(ids_to_disable)),
base::Value(std::move(ids_to_enable)));
auto function =
base::MakeRefCounted<DeclarativeNetRequestUpdateStaticRulesFunction>();
function->set_extension(&extension);
function->set_has_callback(true);
if (!expected_error) {
EXPECT_TRUE(api_test_utils::RunFunction(function.get(), json_args,
browser_context()));
return;
}
EXPECT_EQ(expected_error,
api_test_utils::RunFunctionAndReturnError(
function.get(), json_args, browser_context()));
}
base::flat_set<int> GetDisabledRuleIdsFromMatcher(
const std::string& ruleset_id_string) {
return GetDisabledRuleIdsFromMatcherForTesting(*manager(), *extension(),
ruleset_id_string);
}
bool RulesetExists(const std::string& ruleset_id_string) {
const DNRManifestData::ManifestIDToRulesetMap& public_id_map =
DNRManifestData::GetManifestIDToRulesetMap(*extension());
return base::Contains(public_id_map, ruleset_id_string);
}
void VerifyGetDisabledRuleIdsFunction(
const Extension& extension,
const std::string& ruleset_id,
const std::vector<int>& expected_disabled_rule_ids) {
constexpr const char kParams[] = R"([{ "rulesetId": $1 }])";
const std::string json_args = content::JsReplace(kParams, ruleset_id);
auto function =
base::MakeRefCounted<DeclarativeNetRequestGetDisabledRuleIdsFunction>();
function->set_extension(&extension);
function->set_has_callback(true);
absl::optional<base::Value> result =
api_test_utils::RunFunctionAndReturnSingleResult(
function.get(), json_args, browser_context());
ASSERT_TRUE(result);
ASSERT_TRUE(result->is_list());
std::u16string error;
std::vector<int> actual_disabled_rule_ids;
for (const auto& val : result->GetList()) {
actual_disabled_rule_ids.push_back(val.GetInt());
}
EXPECT_EQ(expected_disabled_rule_ids, actual_disabled_rule_ids);
}
void VerifyGetDisabledRuleIdsFunctionError(
const Extension& extension,
const std::string& ruleset_id,
absl::optional<std::string> expected_error) {
constexpr const char kParams[] = R"([{ "rulesetId": $1 }])";
const std::string json_args = content::JsReplace(kParams, ruleset_id);
auto function =
base::MakeRefCounted<DeclarativeNetRequestGetDisabledRuleIdsFunction>();
function->set_extension(&extension);
function->set_has_callback(true);
EXPECT_EQ(expected_error,
api_test_utils::RunFunctionAndReturnError(
function.get(), json_args, browser_context()));
}
size_t GetDisabledStaticRuleCount() const {
const DeclarativeNetRequestPrefsHelper helper(*extension_prefs_);
return helper.GetDisabledStaticRuleCount(extension()->id());
}
void VerifyPublicRulesetIDs(
const Extension& extension,
const std::vector<std::string>& expected_public_ruleset_ids) {
CompositeMatcher* matcher =
manager()->GetMatcherForExtension(extension.id());
ASSERT_TRUE(matcher);
EXPECT_THAT(
expected_public_ruleset_ids,
UnorderedElementsAreArray(GetPublicRulesetIDs(extension, *matcher)));
}
void UpdateExtensionLoaderAndPath(const base::FilePath& file_path) {
loader_ = CreateExtensionLoader();
extension_ = nullptr;
extension_dir_ = file_path;
ASSERT_TRUE(base::CreateDirectory(extension_dir_));
}
void CheckExtensionAllocationInPrefs(
const ExtensionId& extension_id,
absl::optional<size_t> expected_rules_count) {
size_t actual_rules_count = 0;
bool has_allocated_rules_count =
extension_prefs_->GetDNRAllocatedGlobalRuleCount(extension_id,
&actual_rules_count);
EXPECT_EQ(expected_rules_count.has_value(), has_allocated_rules_count);
if (expected_rules_count.has_value())
EXPECT_EQ(*expected_rules_count, actual_rules_count);
}
void VerifyGetAvailableStaticRuleCountFunction(
const Extension& extension,
size_t expected_available_rule_count) {
auto function = base::MakeRefCounted<
DeclarativeNetRequestGetAvailableStaticRuleCountFunction>();
function->set_extension(&extension);
function->set_has_callback(true);
absl::optional<base::Value> result =
api_test_utils::RunFunctionAndReturnSingleResult(
function.get(), "[]" /* args */, browser_context());
ASSERT_TRUE(result);
EXPECT_EQ(expected_available_rule_count,
static_cast<size_t>(result->GetInt()));
}
const ExtensionPrefs* extension_prefs() { return extension_prefs_; }
ChromeTestExtensionLoader* extension_loader() { return loader_.get(); }
const Extension* extension() const { return extension_.get(); }
const base::FilePath& extension_dir() const { return extension_dir_; }
LoadErrorReporter* error_reporter() {
return LoadErrorReporter::GetInstance();
}
private:
// Derived classes must override this to persist the extension to disk.
virtual void WriteExtensionData() = 0;
base::FilePath extension_dir_;
std::unique_ptr<ChromeTestExtensionLoader> loader_;
scoped_refptr<const Extension> extension_;
raw_ptr<ExtensionPrefs> extension_prefs_ = nullptr;
// Override the various API rule limits to prevent a timeout.
base::AutoReset<int> guaranteed_minimum_override_ =
CreateScopedStaticGuaranteedMinimumOverrideForTesting(100);
base::AutoReset<int> global_limit_override_ =
CreateScopedGlobalStaticRuleLimitOverrideForTesting(200);
base::AutoReset<int> regex_rule_limit_override_ =
CreateScopedRegexRuleLimitOverrideForTesting(100);
base::AutoReset<int> dynamic_and_session_rule_limit_override_ =
CreateScopedDynamicAndSessionRuleLimitOverrideForTesting(200);
};
// Fixture testing that declarative rules corresponding to the Declarative Net
// Request API are correctly indexed, for both packed and unpacked extensions.
// This only tests a single ruleset.
class SingleRulesetTest : public DeclarativeNetRequestUnittest {
public:
SingleRulesetTest() = default;
protected:
void AddRule(const TestRule& rule) { rules_list_.push_back(rule); }
// This takes precedence over the AddRule method.
void SetRules(base::Value rules) { rules_value_ = std::move(rules); }
void set_persist_invalid_json_file() { persist_invalid_json_file_ = true; }
void set_persist_initial_indexed_ruleset() {
persist_initial_indexed_ruleset_ = true;
}
void LoadAndExpectError(const std::string& expected_error) {
DeclarativeNetRequestUnittest::LoadAndExpectError(expected_error,
kJSONRulesFilename);
}
void LoadAndExpectParseFailure(ParseResult parse_result, int rule_id) {
DeclarativeNetRequestUnittest::LoadAndExpectParseFailure(
parse_result, rule_id, kJSONRulesFilename);
}
// |expected_rules_count| refers to the count of indexed rules. When
// |expected_rules_count| is not set, it is inferred from the added rules.
void LoadAndExpectSuccess(
const absl::optional<size_t>& expected_rules_count = absl::nullopt) {
size_t rules_count = 0;
if (expected_rules_count)
rules_count = *expected_rules_count;
else if (rules_value_ && rules_value_->is_list())
rules_count = rules_value_->GetList().size();
else
rules_count = rules_list_.size();
// We only index up to GetMaximumRulesPerRuleset() rules per ruleset.
rules_count =
std::min(rules_count, static_cast<size_t>(GetMaximumRulesPerRuleset()));
DeclarativeNetRequestUnittest::LoadAndExpectSuccess(rules_count,
rules_count, true);
}
private:
// DeclarativeNetRequestUnittest override:
void WriteExtensionData() override {
if (!rules_value_)
rules_value_ = base::Value(ToListValue(rules_list_));
WriteManifestAndRuleset(
extension_dir(),
TestRulesetInfo(kDefaultRulesetID, kJSONRulesFilename,
rules_value_->Clone()),
{} /* hosts */);
// Overwrite the JSON rules file with some invalid json.
if (persist_invalid_json_file_) {
std::string data = "invalid json";
ASSERT_TRUE(base::WriteFile(
extension_dir().AppendASCII(kJSONRulesFilename), data));
}
if (persist_initial_indexed_ruleset_) {
std::string data = "user ruleset";
base::FilePath ruleset_path =
extension_dir().Append(file_util::GetIndexedRulesetRelativePath(
kMinValidStaticRulesetID.value()));
ASSERT_TRUE(base::CreateDirectory(ruleset_path.DirName()));
ASSERT_TRUE(base::WriteFile(ruleset_path, data));
}
}
std::vector<TestRule> rules_list_;
absl::optional<base::Value> rules_value_;
bool persist_invalid_json_file_ = false;
bool persist_initial_indexed_ruleset_ = false;
};
TEST_P(SingleRulesetTest, DuplicateResourceTypes) {
TestRule rule = CreateGenericRule();
rule.condition->resource_types =
std::vector<std::string>({"image", "stylesheet"});
rule.condition->excluded_resource_types = std::vector<std::string>({"image"});
AddRule(rule);
LoadAndExpectParseFailure(ParseResult::ERROR_RESOURCE_TYPE_DUPLICATED,
*rule.id);
}
TEST_P(SingleRulesetTest, EmptyRedirectRuleUrl) {
TestRule rule = CreateGenericRule();
rule.id = kMinValidID;
AddRule(rule);
rule.id = kMinValidID + 1;
rule.action->type = std::string("redirect");
rule.priority = kMinValidPriority;
AddRule(rule);
LoadAndExpectParseFailure(ParseResult::ERROR_INVALID_REDIRECT, *rule.id);
}
TEST_P(SingleRulesetTest, InvalidRuleID) {
TestRule rule = CreateGenericRule();
rule.id = kMinValidID - 1;
AddRule(rule);
LoadAndExpectParseFailure(ParseResult::ERROR_INVALID_RULE_ID, *rule.id);
}
TEST_P(SingleRulesetTest, InvalidRedirectRulePriority) {
TestRule rule = CreateGenericRule();
rule.action->type = std::string("redirect");
rule.action->redirect.emplace();
rule.action->redirect->url = std::string("https://google.com");
rule.priority = kMinValidPriority - 1;
AddRule(rule);
LoadAndExpectParseFailure(ParseResult::ERROR_INVALID_RULE_PRIORITY, *rule.id);
}
TEST_P(SingleRulesetTest, NoApplicableResourceTypes) {
TestRule rule = CreateGenericRule();
rule.condition->excluded_resource_types = std::vector<std::string>(
{"main_frame", "sub_frame", "stylesheet", "script", "image", "font",
"object", "xmlhttprequest", "ping", "csp_report", "media", "websocket",
"webtransport", "webbundle", "other"});
AddRule(rule);
LoadAndExpectParseFailure(ParseResult::ERROR_NO_APPLICABLE_RESOURCE_TYPES,
*rule.id);
}
// Ensure that rules with both "domains" and "initiator_domains" conditions fail
// parsing.
TEST_P(SingleRulesetTest, DuplicateDomainsList) {
TestRule rule = CreateGenericRule();
rule.condition->domains = std::vector<std::string>({"domain.example"});
rule.condition->initiator_domains =
std::vector<std::string>({"domain.example"});
AddRule(rule);
LoadAndExpectParseFailure(
ParseResult::ERROR_DOMAINS_AND_INITIATOR_DOMAINS_BOTH_SPECIFIED,
*rule.id);
}
// Ensure that rules with both "excluded_domains" and
// "excluded_initiator_domains" conditions fail parsing.
TEST_P(SingleRulesetTest, DuplicateExcludedDomainsList) {
TestRule rule = CreateGenericRule();
rule.condition->excluded_domains =
std::vector<std::string>({"domain.example"});
rule.condition->excluded_initiator_domains =
std::vector<std::string>({"domain.example"});
AddRule(rule);
LoadAndExpectParseFailure(
ParseResult::
ERROR_EXCLUDED_DOMAINS_AND_EXCLUDED_INITIATOR_DOMAINS_BOTH_SPECIFIED,
*rule.id);
}
// Ensure that rules with an empty "domains" condition fail parsing.
TEST_P(SingleRulesetTest, EmptyDomainsList) {
TestRule rule = CreateGenericRule();
rule.condition->domains = std::vector<std::string>();
AddRule(rule);
LoadAndExpectParseFailure(ParseResult::ERROR_EMPTY_DOMAINS_LIST, *rule.id);
}
// Ensure that rules with an empty "initiator_domains" condition fail parsing.
TEST_P(SingleRulesetTest, EmptyInitiatorDomainsList) {
TestRule rule = CreateGenericRule();
rule.condition->initiator_domains = std::vector<std::string>();
AddRule(rule);
LoadAndExpectParseFailure(ParseResult::ERROR_EMPTY_INITIATOR_DOMAINS_LIST,
*rule.id);
}
// Ensure that rules with an empty "request_domains" condition fail parsing.
TEST_P(SingleRulesetTest, EmptyRequestDomainsList) {
TestRule rule = CreateGenericRule();
rule.condition->request_domains = std::vector<std::string>();
AddRule(rule);
LoadAndExpectParseFailure(ParseResult::ERROR_EMPTY_REQUEST_DOMAINS_LIST,
*rule.id);
}
// Ensure that rules with a "domains" condition that contains non-ascii
// characters fail parsing.
TEST_P(SingleRulesetTest, NonAsciiDomainsList) {
TestRule rule = CreateGenericRule();
rule.condition->domains = std::vector<std::string>({"😎.example"});
AddRule(rule);
LoadAndExpectParseFailure(ParseResult::ERROR_NON_ASCII_DOMAIN, *rule.id);
}
// Ensure that rules with a "excluded_domains" condition that contains non-ascii
// characters fail parsing.
TEST_P(SingleRulesetTest, NonAsciiExcludedDomainsList) {
TestRule rule = CreateGenericRule();
rule.condition->excluded_domains = std::vector<std::string>({"😎.example"});
AddRule(rule);
LoadAndExpectParseFailure(ParseResult::ERROR_NON_ASCII_EXCLUDED_DOMAIN,
*rule.id);
}
// Ensure that rules with a "initiator_domains" condition that contains
// non-ascii characters fail parsing.
TEST_P(SingleRulesetTest, NonAsciiInitiatorDomainsList) {
TestRule rule = CreateGenericRule();
rule.condition->initiator_domains = std::vector<std::string>({"😎.example"});
AddRule(rule);
LoadAndExpectParseFailure(ParseResult::ERROR_NON_ASCII_INITIATOR_DOMAIN,
*rule.id);
}
// Ensure that rules with a "excluded_initiator_domains" condition that contains
// non-ascii characters fail parsing.
TEST_P(SingleRulesetTest, NonAsciiExcludedInitiatorDomainsList) {
TestRule rule = CreateGenericRule();
rule.condition->excluded_initiator_domains =
std::vector<std::string>({"😎.example"});
AddRule(rule);
LoadAndExpectParseFailure(
ParseResult::ERROR_NON_ASCII_EXCLUDED_INITIATOR_DOMAIN, *rule.id);
}
// Ensure that rules with a "request_domains" condition that contains non-ascii
// characters fail parsing.
TEST_P(SingleRulesetTest, NonAsciiRequestDomainsList) {
TestRule rule = CreateGenericRule();
rule.condition->request_domains = std::vector<std::string>({"😎.example"});
AddRule(rule);
LoadAndExpectParseFailure(ParseResult::ERROR_NON_ASCII_REQUEST_DOMAIN,
*rule.id);
}
// Ensure that rules with a "excluded_request_domains" condition that contains
// non-ascii characters fail parsing.
TEST_P(SingleRulesetTest, NonAsciiExcludedRequestDomainsList) {
TestRule rule = CreateGenericRule();
rule.condition->excluded_request_domains =
std::vector<std::string>({"😎.example"});
AddRule(rule);
LoadAndExpectParseFailure(
ParseResult::ERROR_NON_ASCII_EXCLUDED_REQUEST_DOMAIN, *rule.id);
}
TEST_P(SingleRulesetTest, EmptyResourceTypeList) {
TestRule rule = CreateGenericRule();
rule.condition->resource_types = std::vector<std::string>();
AddRule(rule);
LoadAndExpectParseFailure(ParseResult::ERROR_EMPTY_RESOURCE_TYPES_LIST,
*rule.id);
}
TEST_P(SingleRulesetTest, EmptyURLFilter) {
TestRule rule = CreateGenericRule();
rule.condition->url_filter = std::string();
AddRule(rule);
LoadAndExpectParseFailure(ParseResult::ERROR_EMPTY_URL_FILTER, *rule.id);
}
TEST_P(SingleRulesetTest, InvalidRedirectURL) {
TestRule rule = CreateGenericRule();
rule.action->type = std::string("redirect");
rule.action->redirect.emplace();
rule.action->redirect->url = std::string("google");
rule.priority = kMinValidPriority;
AddRule(rule);
LoadAndExpectParseFailure(ParseResult::ERROR_INVALID_REDIRECT_URL, *rule.id);
}
TEST_P(SingleRulesetTest, ListNotPassed) {
SetRules(base::Value(base::Value::Dict()));
LoadAndExpectError(kErrorListNotPassed);
}
TEST_P(SingleRulesetTest, DuplicateIDS) {
TestRule rule = CreateGenericRule();
AddRule(rule);
AddRule(rule);
LoadAndExpectParseFailure(ParseResult::ERROR_DUPLICATE_IDS, *rule.id);
}
// Ensure that we limit the number of parse failure warnings shown.
TEST_P(SingleRulesetTest, TooManyParseFailures) {
const size_t kNumInvalidRules = 10;
const size_t kNumValidRules = 6;
const size_t kMaxUnparsedRulesWarnings = 5;
size_t rule_id = kMinValidID;
for (size_t i = 0; i < kNumInvalidRules; i++) {
TestRule rule = CreateGenericRule();
rule.id = rule_id++;
rule.action->type = std::string("invalid_action_type");
AddRule(rule);
}
for (size_t i = 0; i < kNumValidRules; i++) {
TestRule rule = CreateGenericRule();
rule.id = rule_id++;
AddRule(rule);
}
extension_loader()->set_ignore_manifest_warnings(true);
LoadAndExpectSuccess(kNumValidRules);
// TODO(crbug.com/879355): CrxInstaller reloads the extension after moving it,
// which causes it to lose the install warning. This should be fixed.
if (GetParam() != ExtensionLoadType::PACKED) {
std::vector<InstallWarning> expected_warnings =
GetFilteredInstallWarnings(*extension());
ASSERT_EQ(1u + kMaxUnparsedRulesWarnings, expected_warnings.size());
InstallWarning warning("");
warning.key = dnr_api::ManifestKeys::kDeclarativeNetRequest;
warning.specific = dnr_api::DNRInfo::kRuleResources;
// The initial warnings should correspond to the first
// |kMaxUnparsedRulesWarnings| rules, which couldn't be parsed.
for (size_t i = 0; i < kMaxUnparsedRulesWarnings; i++) {
EXPECT_EQ(expected_warnings[i].key, warning.key);
EXPECT_EQ(expected_warnings[i].specific, warning.specific);
EXPECT_THAT(expected_warnings[i].message,
::testing::HasSubstr("Parse error"));
}
warning.message = ErrorUtils::FormatErrorMessage(
GetErrorWithFilename(kTooManyParseFailuresWarning),
std::to_string(kMaxUnparsedRulesWarnings));
EXPECT_EQ(warning, expected_warnings[kMaxUnparsedRulesWarnings]);
}
}
// Ensures that rules which can't be parsed are ignored and cause an install
// warning.
TEST_P(SingleRulesetTest, InvalidJSONRules_StrongTypes) {
{
TestRule rule = CreateGenericRule();
rule.id = 1;
AddRule(rule);
}
{
TestRule rule = CreateGenericRule();
rule.id = 2;
rule.action->type = std::string("invalid action");
AddRule(rule);
}
{
TestRule rule = CreateGenericRule();
rule.id = 3;
AddRule(rule);
}
{
TestRule rule = CreateGenericRule();
rule.id = 4;
rule.condition->domain_type = std::string("invalid_domain_type");
AddRule(rule);
}
extension_loader()->set_ignore_manifest_warnings(true);
LoadAndExpectSuccess(2u);
// TODO(crbug.com/879355): CrxInstaller reloads the extension after moving it,
// which causes it to lose the install warning. This should be fixed.
if (GetParam() != ExtensionLoadType::PACKED) {
std::vector<InstallWarning> install_warnings =
GetFilteredInstallWarnings(*extension());
ASSERT_EQ(2u, install_warnings.size());
std::vector<InstallWarning> expected_warnings;
for (const auto& warning : install_warnings) {
EXPECT_EQ(dnr_api::ManifestKeys::kDeclarativeNetRequest, warning.key);
EXPECT_EQ(dnr_api::DNRInfo::kRuleResources, warning.specific);
EXPECT_THAT(warning.message, ::testing::HasSubstr("Parse error"));
}
}
}
// Ensures that rules which can't be parsed are ignored and cause an install
// warning.
TEST_P(SingleRulesetTest, InvalidJSONRules_Parsed) {
const char* kRules = R"(
[
{
"id" : 1,
"priority": 1,
"condition" : [],
"action" : {"type" : "block" }
},
{
"id" : 2,
"priority": 1,
"condition" : {"urlFilter" : "abc"},
"action" : {"type" : "block" }
},
{
"id" : 3,
"priority": 1,
"invalidKey" : "invalidKeyValue",
"condition" : {"urlFilter" : "example"},
"action" : {"type" : "block" }
},
{
"id" : "6",
"priority": 1,
"condition" : {"urlFilter" : "google"},
"action" : {"type" : "block" }
}
]
)";
SetRules(*base::JSONReader::Read(kRules));
extension_loader()->set_ignore_manifest_warnings(true);
// Rules with ids "2" and "3" will be successfully indexed. Note that for rule
// with id "3", the "invalidKey" will simply be ignored during parsing
// (without causing any install warning).
size_t expected_rule_count = 2;
LoadAndExpectSuccess(expected_rule_count);
// TODO(crbug.com/879355): CrxInstaller reloads the extension after moving it,
// which causes it to lose the install warning. This should be fixed.
if (GetParam() != ExtensionLoadType::PACKED) {
std::vector<InstallWarning> install_warnings =
GetFilteredInstallWarnings(*extension());
ASSERT_EQ(2u, install_warnings.size());
std::vector<InstallWarning> expected_warnings;
expected_warnings.emplace_back(
ErrorUtils::FormatErrorMessage(
GetErrorWithFilename(kRuleNotParsedWarning), "id 1",
"'condition': expected dictionary, got list"),
dnr_api::ManifestKeys::kDeclarativeNetRequest,
dnr_api::DNRInfo::kRuleResources);
expected_warnings.emplace_back(
ErrorUtils::FormatErrorMessage(
GetErrorWithFilename(kRuleNotParsedWarning), "index 4",
"'id': expected id, got string"),
dnr_api::ManifestKeys::kDeclarativeNetRequest,
dnr_api::DNRInfo::kRuleResources);
EXPECT_EQ(expected_warnings, install_warnings);
}
}
// Ensure that regex rules which exceed the per rule memory limit are ignored
// and raise an install warning.
TEST_P(SingleRulesetTest, LargeRegexIgnored) {
TestRule rule = CreateGenericRule();
rule.condition->url_filter.reset();
int id = kMinValidID;
const int kNumSmallRegex = 5;
std::string small_regex = kSmallRegexFilter;
for (int i = 0; i < kNumSmallRegex; i++, id++) {
rule.id = id;
rule.condition->regex_filter = small_regex;
AddRule(rule);
}
const int kNumLargeRegex = 2;
for (int i = 0; i < kNumLargeRegex; i++, id++) {
rule.id = id;
rule.condition->regex_filter = kLargeRegexFilter;
AddRule(rule);
}
base::HistogramTester tester;
extension_loader()->set_ignore_manifest_warnings(true);
LoadAndExpectSuccess(kNumSmallRegex);
tester.ExpectBucketCount(kIsLargeRegexHistogram, true, kNumLargeRegex);
tester.ExpectBucketCount(kIsLargeRegexHistogram, false, kNumSmallRegex);
// TODO(crbug.com/879355): CrxInstaller reloads the extension after moving it,
// which causes it to lose the install warning. This should be fixed.
if (GetParam() != ExtensionLoadType::PACKED) {
InstallWarning warning_1 = GetLargeRegexWarning(kMinValidID + 5);
InstallWarning warning_2 = GetLargeRegexWarning(kMinValidID + 6);
EXPECT_THAT(GetFilteredInstallWarnings(*extension()),
UnorderedElementsAre(::testing::Eq(std::cref(warning_1)),
::testing::Eq(std::cref(warning_2))));
}
}
// Test an extension with both an error and an install warning.
TEST_P(SingleRulesetTest, WarningAndError) {
// Add a large regex rule which will exceed the per rule memory limit and
// cause an install warning.
TestRule rule = CreateGenericRule();
rule.condition->url_filter.reset();
rule.id = kMinValidID;
rule.condition->regex_filter = kLargeRegexFilter;
AddRule(rule);
// Add a regex rule with a syntax error.
rule.condition->regex_filter = "abc(";
rule.id = kMinValidID + 1;
AddRule(rule);
LoadAndExpectParseFailure(ParseResult::ERROR_INVALID_REGEX_FILTER,
kMinValidID + 1);
}
// Ensure that we get an install warning on exceeding the regex rule count
// limit.
TEST_P(SingleRulesetTest, RegexRuleCountExceeded) {
TestRule regex_rule = CreateRegexRule();
int rule_id = kMinValidID;
for (int i = 1; i <= GetRegexRuleLimit() + 5; ++i, ++rule_id) {
regex_rule.id = rule_id;
regex_rule.condition->regex_filter = std::to_string(i);
AddRule(regex_rule);
}
const int kCountNonRegexRules = 5;
TestRule rule = CreateGenericRule();
for (int i = 1; i <= kCountNonRegexRules; i++, ++rule_id) {
rule.id = rule_id;
rule.condition->url_filter = std::to_string(i);
AddRule(rule);
}
extension_loader()->set_ignore_manifest_warnings(true);
LoadAndExpectSuccess(GetRegexRuleLimit() + kCountNonRegexRules);
// TODO(crbug.com/879355): CrxInstaller reloads the extension after moving it,
// which causes it to lose the install warning. This should be fixed.
if (GetParam() != ExtensionLoadType::PACKED) {
std::vector<InstallWarning> install_warnings =
GetFilteredInstallWarnings(*extension());
ASSERT_EQ(1u, install_warnings.size());
EXPECT_EQ(InstallWarning(GetErrorWithFilename(kRegexRuleCountExceeded),
dnr_api::ManifestKeys::kDeclarativeNetRequest,
dnr_api::DNRInfo::kRuleResources),
install_warnings[0]);
}
}
TEST_P(SingleRulesetTest, InvalidJSONFile) {
set_persist_invalid_json_file();
// The error is returned by the JSON parser we use. Hence just test an error
// is raised.
LoadAndExpectError("");
}
TEST_P(SingleRulesetTest, EmptyRuleset) {
LoadAndExpectSuccess();
}
TEST_P(SingleRulesetTest, AddSingleRule) {
AddRule(CreateGenericRule());
LoadAndExpectSuccess();
}
TEST_P(SingleRulesetTest, AddTwoRules) {
TestRule rule = CreateGenericRule();
AddRule(rule);
rule.id = kMinValidID + 1;
AddRule(rule);
LoadAndExpectSuccess();
}
// Test that we do not use an extension provided indexed ruleset.
TEST_P(SingleRulesetTest, ExtensionWithIndexedRuleset) {
set_persist_initial_indexed_ruleset();
AddRule(CreateGenericRule());
LoadAndExpectSuccess();
}
// Test for crbug.com/931967. Ensures that adding dynamic rules in the midst of
// an initial ruleset load (in response to OnExtensionLoaded) behaves
// predictably and doesn't DCHECK.
TEST_P(SingleRulesetTest, DynamicRulesetRace) {
RulesetManagerObserver ruleset_waiter(manager());
AddRule(CreateGenericRule());
LoadAndExpectSuccess();
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
const ExtensionId extension_id = extension()->id();
service()->DisableExtension(extension_id,
disable_reason::DISABLE_USER_ACTION);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(0);
// Simulate indexed ruleset format version change. This will cause a re-index
// on subsequent extension load. Since this will further delay the initial
// ruleset load, it helps test that the ruleset loading doesn't race with
// updating dynamic rules.
ScopedIncrementRulesetVersion scoped_version_change =
CreateScopedIncrementRulesetVersionForTesting();
TestExtensionRegistryObserver registry_observer(registry());
service()->EnableExtension(extension_id);
scoped_refptr<const Extension> extension =
registry_observer.WaitForExtensionLoaded();
ASSERT_TRUE(extension);
ASSERT_EQ(extension_id, extension->id());
// At this point, the ruleset will still be loading.
ASSERT_FALSE(manager()->GetMatcherForExtension(extension_id));
// Add some dynamic rules.
std::vector<TestRule> dynamic_rules({CreateGenericRule()});
ASSERT_NO_FATAL_FAILURE(
RunUpdateRulesFunction(*extension, {} /* rule_ids_to_remove */,
dynamic_rules, RulesetScope::kDynamic));
// The API function to update the dynamic ruleset should only complete once
// the initial ruleset loading (in response to OnExtensionLoaded) is complete.
// Hence by now, both the static and dynamic matchers must be loaded.
VerifyPublicRulesetIDs(*extension,
{kDefaultRulesetID, dnr_api::DYNAMIC_RULESET_ID});
}
// Ensures that an updateEnabledRulesets call in the midst of an initial ruleset
// load (in response to OnExtensionLoaded) behaves predictably and doesn't
// DCHECK.
TEST_P(SingleRulesetTest, UpdateEnabledRulesetsRace) {
RulesetManagerObserver ruleset_waiter(manager());
AddRule(CreateGenericRule());
LoadAndExpectSuccess();
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
const ExtensionId extension_id = extension()->id();
service()->DisableExtension(extension_id,
disable_reason::DISABLE_USER_ACTION);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(0);
// Simulate indexed ruleset format version change. This will cause a re-index
// on subsequent extension load. Since this will further delay the initial
// ruleset load, it helps test that the ruleset loading doesn't race with the
// updateEnabledRulesets call.
ScopedIncrementRulesetVersion scoped_version_change =
CreateScopedIncrementRulesetVersionForTesting();
TestExtensionRegistryObserver registry_observer(registry());
service()->EnableExtension(extension_id);
scoped_refptr<const Extension> extension =
registry_observer.WaitForExtensionLoaded();
ASSERT_TRUE(extension);
ASSERT_EQ(extension_id, extension->id());
// At this point, the ruleset will still be loading.
ASSERT_FALSE(manager()->GetMatcherForExtension(extension_id));
// Disable the sole extension ruleset.
RunUpdateEnabledRulesetsFunction(*extension, {kDefaultRulesetID}, {},
absl::nullopt);
// Wait for any pending tasks. This isn't actually necessary for this test
// (there shouldn't be any pending tasks at this point). However still do this
// to not rely on any task ordering assumption.
content::RunAllTasksUntilIdle();
// The API function to update the enabled rulesets should only complete after
// the initial ruleset loading (in response to OnExtensionLoaded) is complete.
// Hence by now, the extension shouldn't have any active rulesets.
VerifyPublicRulesetIDs(*extension, {});
}
// Tests updateSessionRules and getSessionRules extension function calls.
TEST_P(SingleRulesetTest, SessionRules) {
// Load an extension with an empty static ruleset.
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess();
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
VerifyPublicRulesetIDs(*extension(), {kDefaultRulesetID});
base::Value result(base::Value::Type::LIST);
RunGetRulesFunction(*extension(), RulesetScope::kSession, &result);
EXPECT_TRUE(result.GetList().empty());
TestRule rule_1 = CreateGenericRule();
rule_1.id = 1;
TestRule rule_2 = CreateGenericRule();
rule_2.id = 2;
ASSERT_NO_FATAL_FAILURE(RunUpdateRulesFunction(
*extension(), {}, {rule_1, rule_2}, RulesetScope::kSession));
RunGetRulesFunction(*extension(), RulesetScope::kSession, &result);
base::Value::Dict rule_1_value = rule_1.ToValue();
base::Value::Dict rule_2_value = rule_2.ToValue();
EXPECT_THAT(result.GetList(), ::testing::UnorderedElementsAre(
::testing::Eq(std::cref(rule_1_value)),
::testing::Eq(std::cref(rule_2_value))));
VerifyPublicRulesetIDs(*extension(),
{kDefaultRulesetID, dnr_api::SESSION_RULESET_ID});
// No rule ID filter specified, return all rules.
RunGetRulesFunction(*extension(), RulesetScope::kSession, absl::nullopt,
&result);
EXPECT_THAT(result.GetList(), ::testing::UnorderedElementsAre(
::testing::Eq(std::cref(rule_1_value)),
::testing::Eq(std::cref(rule_2_value))));
// Empty rule ID filter, return no rules.
RunGetRulesFunction(*extension(), RulesetScope::kSession,
absl::make_optional<std::vector<int>>({}), &result);
EXPECT_THAT(result.GetList(), ::testing::IsEmpty());
// Rule ID filter includes both rules, return them both.
RunGetRulesFunction(*extension(), RulesetScope::kSession,
absl::make_optional<std::vector<int>>({1, 2, 3, 4}),
&result);
EXPECT_THAT(result.GetList(), ::testing::UnorderedElementsAre(
::testing::Eq(std::cref(rule_1_value)),
::testing::Eq(std::cref(rule_2_value))));
// Rule ID filter only matches rule 1, return that.
RunGetRulesFunction(*extension(), RulesetScope::kSession,
absl::make_optional<std::vector<int>>({1}), &result);
EXPECT_THAT(result.GetList(), ::testing::UnorderedElementsAre(
::testing::Eq(std::cref(rule_1_value))));
// Rule ID filter only matches rule 2, return that.
RunGetRulesFunction(*extension(), RulesetScope::kSession,
absl::make_optional<std::vector<int>>({2}), &result);
EXPECT_THAT(result.GetList(), ::testing::UnorderedElementsAre(
::testing::Eq(std::cref(rule_2_value))));
// Rule ID filter doesn't match any rules, return no rules.
RunGetRulesFunction(*extension(), RulesetScope::kSession,
absl::make_optional<std::vector<int>>({3}), &result);
EXPECT_THAT(result.GetList(), ::testing::IsEmpty());
// No dynamic rules should be returned.
RunGetRulesFunction(*extension(), RulesetScope::kDynamic, &result);
EXPECT_TRUE(result.GetList().empty());
ASSERT_NO_FATAL_FAILURE(RunUpdateRulesFunction(*extension(), {*rule_2.id}, {},
RulesetScope::kSession));
RunGetRulesFunction(*extension(), RulesetScope::kSession, &result);
rule_1_value = rule_1.ToValue();
EXPECT_THAT(result.GetList(), ::testing::UnorderedElementsAre(
::testing::Eq(std::cref(rule_1_value))));
RunGetRulesFunction(*extension(), RulesetScope::kDynamic, &result);
EXPECT_TRUE(result.GetList().empty());
}
// Ensure an error is raised when an extension adds a session-scoped regex rule
// which consumes more memory than allowed.
TEST_P(SingleRulesetTest, LargeRegexError_SessionRules) {
// Load an extension with an empty static ruleset.
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess();
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
// Ensure adding a normal regex rule succeeds.
TestRule normal_regex_rule = CreateGenericRule(1);
normal_regex_rule.condition->url_filter.reset();
normal_regex_rule.condition->regex_filter = std::string(kSmallRegexFilter);
ASSERT_NO_FATAL_FAILURE(RunUpdateRulesFunction(
*extension(), {}, {normal_regex_rule}, RulesetScope::kSession));
// Ensure an error is raised on adding a large regex rule.
TestRule large_regex_rule = CreateGenericRule(2);
large_regex_rule.condition->url_filter.reset();
large_regex_rule.condition->regex_filter = std::string(kLargeRegexFilter);
std::string expected_error =
ErrorUtils::FormatErrorMessage(kErrorRegexTooLarge, "2", kRegexFilterKey);
ASSERT_NO_FATAL_FAILURE(
RunUpdateRulesFunction(*extension(), {}, {large_regex_rule},
RulesetScope::kSession, &expected_error));
}
// Ensure that we can add up to the |dnr_api::GUARANTEED_MINIMUM_STATIC_RULES| +
// |kMaxStaticRulesPerProfile| rules if the global rules feature is enabled.
TEST_P(SingleRulesetTest, RuleCountLimitMatched) {
TestRule rule = CreateGenericRule();
for (int i = 0; i < GetMaximumRulesPerRuleset(); ++i) {
rule.id = kMinValidID + i;
rule.condition->url_filter = std::to_string(i);
AddRule(rule);
}
extension_loader()->set_ignore_manifest_warnings(true);
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess(300);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
std::vector<FileBackedRulesetSource> static_sources =
FileBackedRulesetSource::CreateStatic(
*extension(), FileBackedRulesetSource::RulesetFilter::kIncludeAll);
ASSERT_EQ(1u, static_sources.size());
EXPECT_TRUE(base::PathExists(static_sources[0].indexed_path()));
// The ruleset's ID should not be marked as ignored in prefs.
EXPECT_FALSE(extension_prefs()->ShouldIgnoreDNRRuleset(
extension()->id(), static_sources[0].id()));
}
// Ensure that an extension's allocation will be kept when it is disabled.
TEST_P(SingleRulesetTest, AllocationKeptWhenDisabled) {
TestRule rule = CreateGenericRule();
for (int i = 0; i < GetMaximumRulesPerRuleset(); ++i) {
rule.id = kMinValidID + i;
rule.condition->url_filter = std::to_string(i);
AddRule(rule);
}
extension_loader()->set_ignore_manifest_warnings(true);
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess(300);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
// The 200 rules that contribute to the global pool should be tracked.
GlobalRulesTracker& global_rules_tracker =
RulesMonitorService::Get(browser_context())->global_rules_tracker();
EXPECT_EQ(200u, global_rules_tracker.GetAllocatedGlobalRuleCountForTesting());
// An entry for these 200 rules should be persisted for the extension in
// prefs.
CheckExtensionAllocationInPrefs(extension()->id(), 200);
service()->DisableExtension(extension()->id(),
disable_reason::DISABLE_USER_ACTION);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(0);
// The extension's last known extra rule count should be persisted after it is
// disabled.
EXPECT_EQ(200u, global_rules_tracker.GetAllocatedGlobalRuleCountForTesting());
CheckExtensionAllocationInPrefs(extension()->id(), 200);
// Now re-enable the extension. The extension should load all of its rules
// without any problems.
service()->EnableExtension(extension()->id());
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
EXPECT_EQ(200u, global_rules_tracker.GetAllocatedGlobalRuleCountForTesting());
CheckExtensionAllocationInPrefs(extension()->id(), 200);
}
// Ensure that we get an install warning on exceeding the rule count limit and
// that no rules are indexed.
TEST_P(SingleRulesetTest, RuleCountLimitExceeded) {
TestRule rule = CreateGenericRule();
for (int i = 1; i <= GetMaximumRulesPerRuleset() + 1; ++i) {
rule.id = kMinValidID + i;
rule.condition->url_filter = std::to_string(i);
AddRule(rule);
}
extension_loader()->set_ignore_manifest_warnings(true);
DeclarativeNetRequestUnittest::LoadAndExpectSuccess(
0, 0, false /* expect_rulesets_indexed */);
std::vector<FileBackedRulesetSource> static_sources =
FileBackedRulesetSource::CreateStatic(
*extension(), FileBackedRulesetSource::RulesetFilter::kIncludeAll);
// Since the ruleset was ignored and not indexed, it should not be persisted
// to a file.
ASSERT_EQ(1u, static_sources.size());
EXPECT_FALSE(base::PathExists(static_sources[0].indexed_path()));
// TODO(crbug.com/879355): CrxInstaller reloads the extension after moving it,
// which causes it to lose the install warning. This should be fixed.
if (GetParam() != ExtensionLoadType::PACKED) {
std::vector<InstallWarning> install_warnings =
GetFilteredInstallWarnings(*extension());
ASSERT_EQ(1u, install_warnings.size());
InstallWarning expected_warning =
InstallWarning(GetErrorWithFilename(ErrorUtils::FormatErrorMessage(
kIndexingRuleLimitExceeded,
std::to_string(static_sources[0].id().value()))),
dnr_api::ManifestKeys::kDeclarativeNetRequest,
dnr_api::DNRInfo::kRuleResources);
EXPECT_EQ(expected_warning, install_warnings[0]);
}
// The ruleset's ID should be persisted in the ignored rulesets pref.
EXPECT_TRUE(extension_prefs()->ShouldIgnoreDNRRuleset(
extension()->id(), static_sources[0].id()));
// Since the ruleset was not indexed, no rules should contribute to the extra
// static rule count.
GlobalRulesTracker& global_rules_tracker =
RulesMonitorService::Get(browser_context())->global_rules_tracker();
EXPECT_EQ(0u, global_rules_tracker.GetAllocatedGlobalRuleCountForTesting());
// Likewise, no entry should be persisted in prefs.
CheckExtensionAllocationInPrefs(extension()->id(), absl::nullopt);
}
// Tests that the rule limit is correctly shared between dynamic and session
// rulesets of an extension.
TEST_P(SingleRulesetTest, SharedDynamicAndSessionRuleLimits) {
ASSERT_EQ(200, GetDynamicAndSessionRuleLimit());
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess();
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
// Add 100 dynamic rules, it should succeed.
std::vector<TestRule> dynamic_rules;
int rule_id = kMinValidID;
for (size_t i = 0; i < 100; ++i)
dynamic_rules.push_back(CreateGenericRule(rule_id++));
ASSERT_NO_FATAL_FAILURE(
RunUpdateRulesFunction(*extension(), {} /* rule_ids_to_remove */,
dynamic_rules, RulesetScope::kDynamic));
// Add 101 more session rules, it should fail since the 200 rule limit is
// shared between dynamic and session-scoped rules.
std::vector<TestRule> session_rules = dynamic_rules;
session_rules.push_back(CreateGenericRule(rule_id++));
std::string expected_error = kSessionRuleCountExceeded;
ASSERT_NO_FATAL_FAILURE(RunUpdateRulesFunction(
*extension(), {} /* rule_ids_to_remove */, session_rules,
RulesetScope::kSession, &expected_error));
// Adding 100 session rules should succeed.
session_rules.pop_back();
ASSERT_NO_FATAL_FAILURE(
RunUpdateRulesFunction(*extension(), {} /* rule_ids_to_remove */,
session_rules, RulesetScope::kSession));
const RulesMonitorService* service =
RulesMonitorService::Get(browser_context());
RulesCountPair expected_count(100 /* rule_count */, 0 /* regex_rule_count */);
EXPECT_EQ(expected_count,
service->GetRulesCountPair(extension()->id(), kDynamicRulesetID));
EXPECT_EQ(expected_count,
service->GetRulesCountPair(extension()->id(), kSessionRulesetID));
// Adding any more dynamic rules will fail.
expected_error = kDynamicRuleCountExceeded;
ASSERT_NO_FATAL_FAILURE(RunUpdateRulesFunction(
*extension(), {} /* rule_ids_to_remove */, {CreateGenericRule(rule_id++)},
RulesetScope::kDynamic, &expected_error));
}
// Tests that the regex rule limit is correctly shared between dynamic and
// session rulesets of an extension.
TEST_P(SingleRulesetTest, SharedDynamicAndSessionRegexRuleLimits) {
ASSERT_EQ(100, GetRegexRuleLimit());
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess();
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
// Add 50 session-scoped regex rules, along with 10 non-regex rules.
std::vector<TestRule> session_rules;
int rule_id = kMinValidID;
for (size_t i = 0; i < 50; ++i)
session_rules.push_back(CreateRegexRule(rule_id++));
for (size_t i = 0; i < 10; ++i)
session_rules.push_back(CreateGenericRule(rule_id++));
ASSERT_NO_FATAL_FAILURE(
RunUpdateRulesFunction(*extension(), {} /* rule_ids_to_remove */,
session_rules, RulesetScope::kSession));
// Add the same number of dynamic rules, it should succeed as well.
std::vector<TestRule> dynamic_rules = session_rules;
ASSERT_NO_FATAL_FAILURE(
RunUpdateRulesFunction(*extension(), {} /* rule_ids_to_remove */,
dynamic_rules, RulesetScope::kDynamic));
const RulesMonitorService* service =
RulesMonitorService::Get(browser_context());
RulesCountPair expected_count(60 /* rule_count */, 50 /* regex_rule_count */);
EXPECT_EQ(expected_count,
service->GetRulesCountPair(extension()->id(), kDynamicRulesetID));
EXPECT_EQ(expected_count,
service->GetRulesCountPair(extension()->id(), kSessionRulesetID));
// Adding more regex based dynamic or session rules should fail.
std::string expected_error = kDynamicRegexRuleCountExceeded;
ASSERT_NO_FATAL_FAILURE(RunUpdateRulesFunction(
*extension(), {} /* rule_ids_to_remove */, {CreateRegexRule(rule_id++)},
RulesetScope::kDynamic, &expected_error));
expected_error = kSessionRegexRuleCountExceeded;
ASSERT_NO_FATAL_FAILURE(RunUpdateRulesFunction(
*extension(), {} /* rule_ids_to_remove */, {CreateRegexRule(rule_id++)},
RulesetScope::kSession, &expected_error));
// Adding non-regex dynamic or session rules should still succeed.
ASSERT_NO_FATAL_FAILURE(RunUpdateRulesFunction(
*extension(), {} /* rule_ids_to_remove */,
{CreateGenericRule(rule_id++)}, RulesetScope::kDynamic));
ASSERT_NO_FATAL_FAILURE(RunUpdateRulesFunction(
*extension(), {} /* rule_ids_to_remove */,
{CreateGenericRule(rule_id++)}, RulesetScope::kSession));
expected_count.rule_count++;
EXPECT_EQ(expected_count,
service->GetRulesCountPair(extension()->id(), kDynamicRulesetID));
EXPECT_EQ(expected_count,
service->GetRulesCountPair(extension()->id(), kSessionRulesetID));
}
// Test that getMatchedRules will return an error if an invalid tab id is
// specified.
TEST_P(SingleRulesetTest, GetMatchedRulesInvalidTabID) {
LoadAndExpectSuccess();
const ExtensionId extension_id = extension()->id();
auto function =
base::MakeRefCounted<DeclarativeNetRequestGetMatchedRulesFunction>();
function->set_extension(extension());
std::string expected_error = ErrorUtils::FormatErrorMessage(
declarative_net_request::kTabNotFoundError, "-9001");
std::string error = api_test_utils::RunFunctionAndReturnError(
function.get(), R"([{ "tabId": -9001 }])" /* args */, browser_context());
EXPECT_EQ(expected_error, error);
}
// Tests that multiple static rulesets are correctly indexed.
class MultipleRulesetsTest : public DeclarativeNetRequestUnittest {
public:
MultipleRulesetsTest() = default;
protected:
// DeclarativeNetRequestUnittest override:
void WriteExtensionData() override {
WriteManifestAndRulesets(extension_dir(), rulesets_, {} /* hosts */);
}
void AddRuleset(const TestRulesetInfo& info) { rulesets_.push_back(info); }
void ClearRulesets() { rulesets_.clear(); }
TestRulesetInfo CreateRuleset(const std::string& manifest_id_and_path,
size_t num_non_regex_rules,
size_t num_regex_rules,
bool enabled) {
std::vector<TestRule> rules;
TestRule rule = CreateGenericRule();
int id = kMinValidID;
for (size_t i = 0; i < num_non_regex_rules; ++i, ++id) {
rule.id = id;
rules.push_back(rule);
}
for (size_t i = 0; i < num_regex_rules; ++i, ++id)
rules.push_back(CreateRegexRule(id));
return TestRulesetInfo(manifest_id_and_path, ToListValue(rules), enabled);
}
// |expected_rules_count| and |expected_enabled_rules_count| refer to the
// counts of indexed rules. When not set, these are inferred from the added
// rulesets.
void LoadAndExpectSuccess(
const absl::optional<size_t>& expected_rules_count = absl::nullopt,
const absl::optional<size_t>& expected_enabled_rules_count =
absl::nullopt) {
size_t static_rule_limit = GetMaximumRulesPerRuleset();
size_t rules_count = 0u;
size_t rules_enabled_count = 0u;
for (const TestRulesetInfo& info : rulesets_) {
size_t count = info.rules_value.GetList().size();
// We only index up to |static_rule_limit| rules per ruleset, but
// may index more rules than this limit across rulesets.
count = std::min(count, static_rule_limit);
rules_count += count;
if (info.enabled)
rules_enabled_count += count;
}
DeclarativeNetRequestUnittest::LoadAndExpectSuccess(
expected_rules_count.value_or(rules_count),
expected_enabled_rules_count.value_or(rules_enabled_count),
!rulesets_.empty());
}
private:
std::vector<TestRulesetInfo> rulesets_;
};
// Tests an extension with multiple static rulesets.
TEST_P(MultipleRulesetsTest, Success) {
size_t kNumRulesets = 7;
size_t kRulesPerRuleset = 10;
for (size_t i = 0; i < kNumRulesets; ++i) {
AddRuleset(CreateRuleset(std::to_string(i), kRulesPerRuleset, 0, true));
}
LoadAndExpectSuccess();
}
// Tests an extension with no static rulesets.
TEST_P(MultipleRulesetsTest, ZeroRulesets) {
LoadAndExpectSuccess();
VerifyGetEnabledRulesetsFunction(*extension(), {});
}
// Tests an extension with multiple empty rulesets.
TEST_P(MultipleRulesetsTest, EmptyRulesets) {
size_t kNumRulesets = 7;
for (size_t i = 0; i < kNumRulesets; ++i)
AddRuleset(CreateRuleset(std::to_string(i), 0, 0, true));
LoadAndExpectSuccess();
}
// Tests an extension with multiple static rulesets, with one of rulesets
// specifying an invalid rules file.
TEST_P(MultipleRulesetsTest, ListNotPassed) {
std::vector<TestRule> rules({CreateGenericRule()});
AddRuleset(TestRulesetInfo(kId1, "path1", ToListValue(rules)));
// Persist a ruleset with an invalid rules file.
AddRuleset(
TestRulesetInfo(kId2, "path2", base::Value(base::Value::Type::DICT)));
AddRuleset(TestRulesetInfo(kId3, "path3", base::Value::List()));
LoadAndExpectError(kErrorListNotPassed, "path2" /* filename */);
}
// Tests an extension with multiple static rulesets with each ruleset generating
// some install warnings.
TEST_P(MultipleRulesetsTest, InstallWarnings) {
size_t expected_rule_count = 0;
size_t enabled_rule_count = 0;
std::vector<std::string> expected_warnings;
{
// Persist a ruleset with an install warning for a large regex.
std::vector<TestRule> rules;
TestRule rule = CreateGenericRule();
rule.id = kMinValidID;
rules.push_back(rule);
rule.id = kMinValidID + 1;
rule.condition->url_filter.reset();
rule.condition->regex_filter = kLargeRegexFilter;
rules.push_back(rule);
TestRulesetInfo info(kId1, "path1", ToListValue(rules), true);
AddRuleset(info);
expected_warnings.push_back(
GetLargeRegexWarning(*rule.id, info.relative_file_path).message);
expected_rule_count += rules.size();
enabled_rule_count += 1;
}
{
// Persist a ruleset with an install warning for exceeding the regex rule
// count.
size_t kCountNonRegexRules = 5;
TestRulesetInfo info = CreateRuleset(kId3, kCountNonRegexRules,
GetRegexRuleLimit() + 1, false);
AddRuleset(info);
expected_warnings.push_back(
GetErrorWithFilename(kRegexRuleCountExceeded, info.relative_file_path));
expected_rule_count += kCountNonRegexRules + GetRegexRuleLimit();
}
extension_loader()->set_ignore_manifest_warnings(true);
LoadAndExpectSuccess(expected_rule_count, enabled_rule_count);
// TODO(crbug.com/879355): CrxInstaller reloads the extension after moving it,
// which causes it to lose the install warning. This should be fixed.
if (GetParam() != ExtensionLoadType::PACKED) {
std::vector<InstallWarning> warnings =
GetFilteredInstallWarnings(*extension());
std::vector<std::string> warning_strings;
for (const InstallWarning& warning : warnings)
warning_strings.push_back(warning.message);
EXPECT_THAT(warning_strings, UnorderedElementsAreArray(expected_warnings));
}
}
TEST_P(MultipleRulesetsTest, EnabledRulesCount) {
AddRuleset(CreateRuleset(kId1, 100, 10, true));
AddRuleset(CreateRuleset(kId2, 200, 20, false));
AddRuleset(CreateRuleset(kId3, 150, 30, true));
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess();
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
// Only the first and third rulesets should be enabled.
CompositeMatcher* composite_matcher =
manager()->GetMatcherForExtension(extension()->id());
ASSERT_TRUE(composite_matcher);
VerifyPublicRulesetIDs(*extension(), {kId1, kId3});
EXPECT_THAT(composite_matcher->matchers(),
UnorderedElementsAre(
Pointee(Property(&RulesetMatcher::GetRulesCount, 100 + 10)),
Pointee(Property(&RulesetMatcher::GetRulesCount, 150 + 30))));
}
// Ensure that exceeding the regex rules limit across rulesets raises a warning.
TEST_P(MultipleRulesetsTest, RegexRuleCountExceeded) {
// Enabled on load.
AddRuleset(CreateRuleset(kId1, 210, 50, true));
// Won't be enabled on load since including it will exceed the regex rule
// count.
AddRuleset(CreateRuleset(kId2, 1, GetRegexRuleLimit(), true));
// Won't be enabled on load since it is disabled by default.
AddRuleset(CreateRuleset(kId3, 10, 10, false));
// Enabled on load.
AddRuleset(CreateRuleset(kId4, 20, 20, true));
RulesetManagerObserver ruleset_waiter(manager());
extension_loader()->set_ignore_manifest_warnings(true);
LoadAndExpectSuccess();
// Installing the extension causes an install warning since the set of enabled
// rulesets exceed the regex rules limit.
if (GetParam() != ExtensionLoadType::PACKED) {
EXPECT_THAT(GetFilteredInstallWarnings(*extension()),
UnorderedElementsAre(Field(&InstallWarning::message,
kEnabledRegexRuleCountExceeded)));
}
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
CompositeMatcher* composite_matcher =
manager()->GetMatcherForExtension(extension()->id());
ASSERT_TRUE(composite_matcher);
VerifyPublicRulesetIDs(*extension(), {kId1, kId4});
EXPECT_THAT(composite_matcher->matchers(),
UnorderedElementsAre(
Pointee(Property(&RulesetMatcher::GetRulesCount, 210 + 50)),
Pointee(Property(&RulesetMatcher::GetRulesCount, 20 + 20))));
}
TEST_P(MultipleRulesetsTest, UpdateEnabledRulesets_InvalidRulesetID) {
AddRuleset(CreateRuleset(kId1, 10, 10, true));
AddRuleset(CreateRuleset(kId2, 10, 10, false));
AddRuleset(CreateRuleset(kId3, 10, 10, true));
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess();
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
constexpr char kInvalidRulesetId[] = "invalid_id";
RunUpdateEnabledRulesetsFunction(
*extension(), {kId1, kInvalidRulesetId}, {},
ErrorUtils::FormatErrorMessage(kInvalidRulesetIDError,
kInvalidRulesetId));
VerifyPublicRulesetIDs(*extension(), {kId1, kId3});
RunUpdateEnabledRulesetsFunction(
*extension(), {kId1}, {kId2, kInvalidRulesetId},
ErrorUtils::FormatErrorMessage(kInvalidRulesetIDError,
kInvalidRulesetId));
VerifyPublicRulesetIDs(*extension(), {kId1, kId3});
// Ensure we can't enable/disable dynamic or session-scoped rulesets using
// updateEnabledRulesets.
ASSERT_NO_FATAL_FAILURE(RunUpdateRulesFunction(
*extension(), {}, {CreateGenericRule()}, RulesetScope::kDynamic));
ASSERT_NO_FATAL_FAILURE(RunUpdateRulesFunction(
*extension(), {}, {CreateGenericRule()}, RulesetScope::kSession));
VerifyPublicRulesetIDs(*extension(), {kId1, kId3, dnr_api::DYNAMIC_RULESET_ID,
dnr_api::SESSION_RULESET_ID});
RunUpdateEnabledRulesetsFunction(
*extension(), {}, {kId2, dnr_api::DYNAMIC_RULESET_ID},
ErrorUtils::FormatErrorMessage(kInvalidRulesetIDError,
dnr_api::DYNAMIC_RULESET_ID));
RunUpdateEnabledRulesetsFunction(
*extension(), {kId1, dnr_api::SESSION_RULESET_ID}, {},
ErrorUtils::FormatErrorMessage(kInvalidRulesetIDError,
dnr_api::SESSION_RULESET_ID));
VerifyPublicRulesetIDs(*extension(), {kId1, kId3, dnr_api::DYNAMIC_RULESET_ID,
dnr_api::SESSION_RULESET_ID});
}
// Ensure we correctly enforce the limit on the maximum number of static
// rulesets that can be enabled at a time
TEST_P(MultipleRulesetsTest,
UpdateEnabledRulesets_EnabledRulesetCountExceeded) {
int kMaxEnabledRulesetCount =
api::declarative_net_request::MAX_NUMBER_OF_ENABLED_STATIC_RULESETS;
std::vector<std::string> ruleset_ids;
std::vector<std::string> expected_enabled_ruleset_ids;
// Create kMaxEnabledRulesetCount + 1 rulesets, with all but the last two
// enabled.
for (int i = 0; i <= kMaxEnabledRulesetCount; i++) {
bool enabled = i < kMaxEnabledRulesetCount - 1;
std::string id = base::StringPrintf("%d.json", i);
ruleset_ids.push_back(id);
if (enabled)
expected_enabled_ruleset_ids.push_back(id);
AddRuleset(CreateRuleset(id, 10, 10, enabled));
}
std::string first_ruleset_id = ruleset_ids[0];
std::string second_last_ruleset_id = ruleset_ids[kMaxEnabledRulesetCount - 1];
std::string last_ruleset_id = ruleset_ids[kMaxEnabledRulesetCount];
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess();
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
// Since we're not yet at our limit of enabled rulesets, enabling one more
// should succeed.
RunUpdateEnabledRulesetsFunction(*extension(), {}, {second_last_ruleset_id},
absl::nullopt /* expected_error */);
expected_enabled_ruleset_ids.push_back(second_last_ruleset_id);
VerifyPublicRulesetIDs(*extension(), expected_enabled_ruleset_ids);
// We're now at our limit of enabled rulesets, so enabling another should
// raise an error.
RunUpdateEnabledRulesetsFunction(*extension(), {}, {last_ruleset_id},
kEnabledRulesetCountExceeded);
VerifyPublicRulesetIDs(*extension(), expected_enabled_ruleset_ids);
// Since this ruleset is already enabled, attempting to enable it again
// shouldn't raise an error (or do anything).
RunUpdateEnabledRulesetsFunction(*extension(), {}, {second_last_ruleset_id},
absl::nullopt /* expected_error */);
VerifyPublicRulesetIDs(*extension(), expected_enabled_ruleset_ids);
// When enabling and disabling a ruleset at the same time, enabling takes
// precedence. Since we're still at the limit, that should raise an error.
RunUpdateEnabledRulesetsFunction(*extension(), {last_ruleset_id},
{last_ruleset_id},
kEnabledRulesetCountExceeded);
VerifyPublicRulesetIDs(*extension(), expected_enabled_ruleset_ids);
// Since we're disabling one ruleset, enabling another should not exceed the
// limit.
RunUpdateEnabledRulesetsFunction(*extension(), {first_ruleset_id},
{last_ruleset_id},
absl::nullopt /* expected_error */);
expected_enabled_ruleset_ids.erase(expected_enabled_ruleset_ids.begin());
expected_enabled_ruleset_ids.push_back(last_ruleset_id);
VerifyPublicRulesetIDs(*extension(), expected_enabled_ruleset_ids);
}
TEST_P(MultipleRulesetsTest, UpdateEnabledRulesets_RegexRuleCountExceeded) {
AddRuleset(CreateRuleset(kId1, 0, 10, false));
AddRuleset(CreateRuleset(kId2, 0, GetRegexRuleLimit(), true));
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess();
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
RunUpdateEnabledRulesetsFunction(*extension(), {}, {kId1},
kEnabledRulesetsRegexRuleCountExceeded);
VerifyPublicRulesetIDs(*extension(), {kId2});
}
TEST_P(MultipleRulesetsTest, UpdateEnabledRulesets_InternalError) {
AddRuleset(CreateRuleset(kId1, 10, 10, true));
AddRuleset(CreateRuleset(kId2, 10, 10, true));
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess();
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
std::vector<FileBackedRulesetSource> static_sources =
FileBackedRulesetSource::CreateStatic(
*extension(), FileBackedRulesetSource::RulesetFilter::kIncludeAll);
ASSERT_EQ(2u, static_sources.size());
constexpr char kReindexHistogram[] =
"Extensions.DeclarativeNetRequest.RulesetReindexSuccessful";
{
// First disable the second ruleset and then delete its indexed ruleset
// file.
RunUpdateEnabledRulesetsFunction(*extension(), {kId2}, {}, absl::nullopt);
ASSERT_TRUE(base::DeleteFile(static_sources[1].indexed_path()));
// Enabling it again should cause re-indexing and succeed in enabling the
// ruleset.
base::HistogramTester tester;
ASSERT_TRUE(base::DeleteFile(static_sources[1].indexed_path()));
RunUpdateEnabledRulesetsFunction(*extension(), {kId1}, {kId2},
absl::nullopt);
VerifyPublicRulesetIDs(*extension(), {kId2});
tester.ExpectBucketCount(kReindexHistogram, true /*sample*/, 1 /*count*/);
EXPECT_TRUE(base::PathExists(static_sources[1].indexed_path()));
}
{
// Now delete both the indexed and json ruleset file for the first ruleset.
// This will prevent enabling the first ruleset since re-indexing will fail.
base::HistogramTester tester;
ASSERT_TRUE(base::DeleteFile(static_sources[0].indexed_path()));
ASSERT_TRUE(base::DeleteFile(static_sources[0].json_path()));
RunUpdateEnabledRulesetsFunction(*extension(), {}, {kId1},
kInternalErrorUpdatingEnabledRulesets);
VerifyPublicRulesetIDs(*extension(), {kId2});
tester.ExpectBucketCount(kReindexHistogram, false /*sample*/, 1 /*count*/);
}
}
TEST_P(MultipleRulesetsTest, UpdateAndGetEnabledRulesets_Success) {
AddRuleset(CreateRuleset(kId1, 10, 10, true));
AddRuleset(CreateRuleset(kId2, 10, 10, false));
AddRuleset(CreateRuleset(kId3, 10, 10, true));
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess();
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
RunUpdateEnabledRulesetsFunction(*extension(), {kId1, kId3}, {kId2},
absl::nullopt /* expected_error */);
VerifyPublicRulesetIDs(*extension(), {kId2});
VerifyGetEnabledRulesetsFunction(*extension(), {kId2});
RunUpdateEnabledRulesetsFunction(*extension(), {}, {kId3, kId3},
absl::nullopt /* expected_error */);
VerifyPublicRulesetIDs(*extension(), {kId2, kId3});
VerifyGetEnabledRulesetsFunction(*extension(), {kId2, kId3});
// Ensure no-op calls succeed.
RunUpdateEnabledRulesetsFunction(*extension(), {}, {kId2, kId3},
absl::nullopt /* expected_error */);
VerifyPublicRulesetIDs(*extension(), {kId2, kId3});
VerifyGetEnabledRulesetsFunction(*extension(), {kId2, kId3});
RunUpdateEnabledRulesetsFunction(*extension(), {kId1}, {},
absl::nullopt /* expected_error */);
VerifyPublicRulesetIDs(*extension(), {kId2, kId3});
VerifyGetEnabledRulesetsFunction(*extension(), {kId2, kId3});
// Add dynamic and session-scoped rules and ensure that the setEnabledRulesets
// call doesn't have any effect on their associated rulesets. Also ensure that
// the getEnabledRulesets call excludes these rulesets.
ASSERT_NO_FATAL_FAILURE(RunUpdateRulesFunction(
*extension(), {}, {CreateGenericRule()}, RulesetScope::kDynamic));
ASSERT_NO_FATAL_FAILURE(RunUpdateRulesFunction(
*extension(), {}, {CreateGenericRule()}, RulesetScope::kSession));
VerifyPublicRulesetIDs(*extension(), {kId2, kId3, dnr_api::DYNAMIC_RULESET_ID,
dnr_api::SESSION_RULESET_ID});
VerifyGetEnabledRulesetsFunction(*extension(), {kId2, kId3});
// Ensure enabling a ruleset takes priority over disabling.
RunUpdateEnabledRulesetsFunction(*extension(), {kId1}, {kId1},
absl::nullopt /* expected_error */);
VerifyPublicRulesetIDs(*extension(),
{kId1, kId2, kId3, dnr_api::DYNAMIC_RULESET_ID,
dnr_api::SESSION_RULESET_ID});
VerifyGetEnabledRulesetsFunction(*extension(), {kId1, kId2, kId3});
// Ensure the set of enabled rulesets persists across extension reloads.
const ExtensionId extension_id = extension()->id();
service()->DisableExtension(extension_id,
disable_reason::DISABLE_USER_ACTION);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(0);
service()->EnableExtension(extension_id);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
const Extension* extension =
registry()->GetExtensionById(extension_id, ExtensionRegistry::ENABLED);
ASSERT_TRUE(extension);
VerifyPublicRulesetIDs(*extension,
{kId1, kId2, kId3, dnr_api::DYNAMIC_RULESET_ID,
dnr_api::SESSION_RULESET_ID});
VerifyGetEnabledRulesetsFunction(*extension, {kId1, kId2, kId3});
}
// Ensure that only rulesets which exceed the rules count limit will not have
// their rules indexed and will raise an install warning.
TEST_P(MultipleRulesetsTest, StaticRuleCountExceeded) {
// Ruleset should not be indexed as it exceeds the limit.
AddRuleset(CreateRuleset(kId1, 301, 0, true));
// Ruleset should be indexed as it is within the limit.
AddRuleset(CreateRuleset(kId2, 250, 0, true));
RulesetManagerObserver ruleset_waiter(manager());
extension_loader()->set_ignore_manifest_warnings(true);
DeclarativeNetRequestUnittest::LoadAndExpectSuccess(
250, 250, true /* expect_rulesets_indexed */);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
CompositeMatcher* composite_matcher =
manager()->GetMatcherForExtension(extension()->id());
ASSERT_TRUE(composite_matcher);
VerifyPublicRulesetIDs(*extension(), {kId2});
EXPECT_THAT(composite_matcher->matchers(),
UnorderedElementsAre(
Pointee(Property(&RulesetMatcher::GetRulesCount, 250))));
std::vector<FileBackedRulesetSource> static_sources =
FileBackedRulesetSource::CreateStatic(
*extension(), FileBackedRulesetSource::RulesetFilter::kIncludeAll);
ASSERT_EQ(2u, static_sources.size());
if (GetParam() != ExtensionLoadType::PACKED) {
std::string expected_warning = GetErrorWithFilename(
ErrorUtils::FormatErrorMessage(
kIndexingRuleLimitExceeded,
std::to_string(static_sources[0].id().value())),
kId1);
EXPECT_THAT(GetFilteredInstallWarnings(*extension()),
UnorderedElementsAre(
Field(&InstallWarning::message, expected_warning)));
}
// Since the first ruleset was ignored and not indexed, it should not be
// persisted to a file.
EXPECT_FALSE(base::PathExists(static_sources[0].indexed_path()));
// The second ruleset was indexed and it should be persisted.
EXPECT_TRUE(base::PathExists(static_sources[1].indexed_path()));
// The first ruleset's ID should be persisted in the ignored rulesets pref.
EXPECT_TRUE(extension_prefs()->ShouldIgnoreDNRRuleset(
extension()->id(), static_sources[0].id()));
// The second ruleset's ID should not be marked as ignored in prefs.
EXPECT_FALSE(extension_prefs()->ShouldIgnoreDNRRuleset(
extension()->id(), static_sources[1].id()));
}
// Ensure that a ruleset which causes the extension to go over the global rule
// limit is correctly ignored.
TEST_P(MultipleRulesetsTest, RulesetIgnored) {
AddRuleset(CreateRuleset(kId1, 90, 0, true));
AddRuleset(CreateRuleset(kId2, 150, 0, true));
// This ruleset should not be loaded because it would exceed the global limit.
AddRuleset(CreateRuleset(kId3, 100, 0, true));
AddRuleset(CreateRuleset(kId4, 60, 0, true));
RulesetManagerObserver ruleset_waiter(manager());
// This logs the number of rules the extension has specified to be enabled in
// the manifest, which may be different than the actual number of rules
// enabled.
DeclarativeNetRequestUnittest::LoadAndExpectSuccess(
400, 400, true /* expect_rulesets_indexed */);
ExtensionId extension_id = extension()->id();
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
CompositeMatcher* composite_matcher =
manager()->GetMatcherForExtension(extension_id);
ASSERT_TRUE(composite_matcher);
VerifyPublicRulesetIDs(*extension(), {kId1, kId2, kId4});
EXPECT_THAT(composite_matcher->matchers(),
UnorderedElementsAre(
Pointee(Property(&RulesetMatcher::GetRulesCount, 90)),
Pointee(Property(&RulesetMatcher::GetRulesCount, 150)),
Pointee(Property(&RulesetMatcher::GetRulesCount, 60))));
// 200 rules should contribute to the global pool.
const GlobalRulesTracker& global_rules_tracker =
RulesMonitorService::Get(browser_context())->global_rules_tracker();
EXPECT_EQ(200u, global_rules_tracker.GetAllocatedGlobalRuleCountForTesting());
// Check that the extra static rule count is also persisted in prefs.
CheckExtensionAllocationInPrefs(extension_id, 200);
}
// Ensure that the global rule count is counted correctly for multiple
// extensions.
TEST_P(MultipleRulesetsTest, MultipleExtensions) {
// Load an extension with 90 rules.
AddRuleset(CreateRuleset(kId1, 90, 0, true));
RulesetManagerObserver ruleset_waiter(manager());
DeclarativeNetRequestUnittest::LoadAndExpectSuccess(
90, 90, true /* expect_rulesets_indexed */);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
VerifyPublicRulesetIDs(*extension(), {kId1});
scoped_refptr<const Extension> first_extension = extension();
ASSERT_TRUE(first_extension.get());
// The first extension should not have any rules count towards the global
// pool.
const GlobalRulesTracker& global_rules_tracker =
RulesMonitorService::Get(browser_context())->global_rules_tracker();
EXPECT_EQ(0u, global_rules_tracker.GetAllocatedGlobalRuleCountForTesting());
// Load an extension with 201 rules.
UpdateExtensionLoaderAndPath(
temp_dir().GetPath().Append(FILE_PATH_LITERAL("test_extension_2")));
ClearRulesets();
AddRuleset(CreateRuleset(kId2, 201, 0, true));
DeclarativeNetRequestUnittest::LoadAndExpectSuccess(
201, 201, true /* expect_rulesets_indexed */);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(2);
VerifyPublicRulesetIDs(*extension(), {kId2});
scoped_refptr<const Extension> second_extension = extension();
ASSERT_TRUE(second_extension.get());
// The second extension should have 101 rules count towards the global pool.
EXPECT_EQ(101u, global_rules_tracker.GetAllocatedGlobalRuleCountForTesting());
// Load an extension with 150 rules.
UpdateExtensionLoaderAndPath(
temp_dir().GetPath().Append(FILE_PATH_LITERAL("test_extension_3")));
ClearRulesets();
AddRuleset(CreateRuleset(kId3, 150, 0, true));
DeclarativeNetRequestUnittest::LoadAndExpectSuccess(
150, 150, true /* expect_rulesets_indexed */);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(3);
VerifyPublicRulesetIDs(*extension(), {kId3});
scoped_refptr<const Extension> third_extension = extension();
ASSERT_TRUE(third_extension.get());
// Combined, the second and third extensions should have 151 rules count
// towards the global pool.
EXPECT_EQ(151u, global_rules_tracker.GetAllocatedGlobalRuleCountForTesting());
// Check that the prefs entry (or lack thereof) for extra static rule count is
// correct for each extension.
CheckExtensionAllocationInPrefs(first_extension.get()->id(), absl::nullopt);
CheckExtensionAllocationInPrefs(second_extension.get()->id(), 101);
CheckExtensionAllocationInPrefs(third_extension.get()->id(), 50);
}
// Ensure that the global rules limit is enforced correctly for multiple
// extensions.
TEST_P(MultipleRulesetsTest, MultipleExtensionsRuleLimitExceeded) {
// Load an extension with 300 rules, which reaches the global rules limit.
AddRuleset(CreateRuleset(kId1, 300, 0, true));
RulesetManagerObserver ruleset_waiter(manager());
DeclarativeNetRequestUnittest::LoadAndExpectSuccess(
300, 300, true /* expect_rulesets_indexed */);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
scoped_refptr<const Extension> first_extension = extension();
ASSERT_TRUE(first_extension.get());
ExtensionId first_extension_id = first_extension.get()->id();
VerifyPublicRulesetIDs(*first_extension.get(), {kId1});
CheckExtensionAllocationInPrefs(first_extension_id, 200);
// Load a second extension. Only one of its rulesets should be loaded.
UpdateExtensionLoaderAndPath(
temp_dir().GetPath().Append(FILE_PATH_LITERAL("test_extension_2")));
ClearRulesets();
AddRuleset(
CreateRuleset(kId2, GetStaticGuaranteedMinimumRuleCount(), 0, true));
AddRuleset(CreateRuleset(kId3, 1, 0, true));
DeclarativeNetRequestUnittest::LoadAndExpectSuccess(
GetStaticGuaranteedMinimumRuleCount() + 1,
GetStaticGuaranteedMinimumRuleCount() + 1,
true /* expect_rulesets_indexed */);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(2);
scoped_refptr<const Extension> second_extension = extension();
ASSERT_TRUE(second_extension.get());
ExtensionId second_extension_id = second_extension.get()->id();
// Only |kId2| should be enabled as |kId3| causes the global rule limit to be
// exceeded.
VerifyPublicRulesetIDs(*second_extension.get(), {kId2});
CheckExtensionAllocationInPrefs(second_extension_id, absl::nullopt);
// Since the ID of the second extension is known only after it was installed,
// disable then enable the extension so the ID can be used for the
// WarningServiceObserver.
service()->DisableExtension(second_extension_id,
disable_reason::DISABLE_USER_ACTION);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
WarningService* warning_service = WarningService::Get(browser_context());
WarningServiceObserver warning_observer(warning_service, second_extension_id);
service()->EnableExtension(second_extension_id);
// Wait until we surface a warning.
warning_observer.WaitForWarning();
ruleset_waiter.WaitForExtensionsWithRulesetsCount(2);
// Ensure that a warning was raised for the second extension.
EXPECT_THAT(
warning_service->GetWarningTypesAffectingExtension(second_extension_id),
::testing::ElementsAre(Warning::kEnabledRuleCountExceeded));
service()->UninstallExtension(first_extension_id,
UNINSTALL_REASON_FOR_TESTING, nullptr);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
service()->DisableExtension(second_extension_id,
disable_reason::DISABLE_USER_ACTION);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(0);
CheckExtensionAllocationInPrefs(first_extension_id, absl::nullopt);
CheckExtensionAllocationInPrefs(second_extension_id, absl::nullopt);
service()->EnableExtension(second_extension_id);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
// Once the first extension is uninstalled, both |kId2| and |kId3| should be
// enabled.
VerifyPublicRulesetIDs(*second_extension.get(), {kId2, kId3});
CheckExtensionAllocationInPrefs(second_extension_id, 1);
EXPECT_TRUE(
warning_service->GetWarningTypesAffectingExtension(second_extension_id)
.empty());
}
TEST_P(MultipleRulesetsTest, UpdateAndGetEnabledRulesets_RuleCountAllocation) {
AddRuleset(CreateRuleset(kId1, 90, 0, false));
AddRuleset(CreateRuleset(kId2, 60, 0, true));
AddRuleset(CreateRuleset(kId3, 150, 0, true));
RulesetManagerObserver ruleset_waiter(manager());
DeclarativeNetRequestUnittest::LoadAndExpectSuccess(
300, 210, true /* expect_rulesets_indexed */);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
CompositeMatcher* composite_matcher =
manager()->GetMatcherForExtension(extension()->id());
ASSERT_TRUE(composite_matcher);
VerifyPublicRulesetIDs(*extension(), {kId2, kId3});
CheckExtensionAllocationInPrefs(extension()->id(), 110);
// Disable |kId2|.
RunUpdateEnabledRulesetsFunction(*extension(), {kId2}, {},
absl::nullopt /* expected_error */);
VerifyPublicRulesetIDs(*extension(), {kId3});
VerifyGetEnabledRulesetsFunction(*extension(), {kId3});
// After |kId2| is disabled, 50 rules should contribute to the global pool.
GlobalRulesTracker& global_rules_tracker =
RulesMonitorService::Get(browser_context())->global_rules_tracker();
EXPECT_EQ(50u, global_rules_tracker.GetAllocatedGlobalRuleCountForTesting());
// Check that the extra static rule count is also persisted in prefs.
CheckExtensionAllocationInPrefs(extension()->id(), 50);
// Enable |kId1|.
RunUpdateEnabledRulesetsFunction(*extension(), {}, {kId1},
absl::nullopt /* expected_error */);
VerifyPublicRulesetIDs(*extension(), {kId1, kId3});
VerifyGetEnabledRulesetsFunction(*extension(), {kId1, kId3});
// After |kId1| is enabled, 140 rules should contribute to the global pool.
EXPECT_EQ(140u, global_rules_tracker.GetAllocatedGlobalRuleCountForTesting());
CheckExtensionAllocationInPrefs(extension()->id(), 140);
// Disable |kId3|.
RunUpdateEnabledRulesetsFunction(*extension(), {kId3}, {},
absl::nullopt /* expected_error */);
VerifyPublicRulesetIDs(*extension(), {kId1});
VerifyGetEnabledRulesetsFunction(*extension(), {kId1});
// After |kId3| is disabled, no rules should contribute to the global pool and
// there should not be an entry for the extension in prefs.
EXPECT_EQ(0u, global_rules_tracker.GetAllocatedGlobalRuleCountForTesting());
CheckExtensionAllocationInPrefs(extension()->id(), absl::nullopt);
}
TEST_P(MultipleRulesetsTest, UpdateAndGetEnabledRulesets_RuleCountExceeded) {
AddRuleset(CreateRuleset(kId1, 250, 0, true));
AddRuleset(CreateRuleset(kId2, 40, 0, true));
AddRuleset(CreateRuleset(kId3, 50, 0, false));
RulesetManagerObserver ruleset_waiter(manager());
DeclarativeNetRequestUnittest::LoadAndExpectSuccess(
340, 290, true /* expect_rulesets_indexed */);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
CompositeMatcher* composite_matcher =
manager()->GetMatcherForExtension(extension()->id());
ASSERT_TRUE(composite_matcher);
VerifyPublicRulesetIDs(*extension(), {kId1, kId2});
CheckExtensionAllocationInPrefs(extension()->id(), 190);
// Disable |kId2| and enable |kId3|.
RunUpdateEnabledRulesetsFunction(*extension(), {kId2}, {kId3},
absl::nullopt /* expected_error */);
// updateEnabledRulesets looks at the rule counts at the end of the update, so
// disabling |kId2| and enabling |kId3| works (because the total rule count is
// under the limit).
VerifyPublicRulesetIDs(*extension(), {kId1, kId3});
VerifyGetEnabledRulesetsFunction(*extension(), {kId1, kId3});
CheckExtensionAllocationInPrefs(extension()->id(), 200);
// Enable |kId2|. This should not succeed because the global rule limit would
// be exceeded.
RunUpdateEnabledRulesetsFunction(*extension(), {}, {kId2},
kEnabledRulesetsRuleCountExceeded);
VerifyPublicRulesetIDs(*extension(), {kId1, kId3});
VerifyGetEnabledRulesetsFunction(*extension(), {kId1, kId3});
CheckExtensionAllocationInPrefs(extension()->id(), 200);
}
TEST_P(MultipleRulesetsTest,
UpdateAndGetEnabledRulesets_KeepEnabledStaticRulesetsAfterReload) {
AddRuleset(CreateRuleset(kId1, 90, 0, false));
AddRuleset(CreateRuleset(kId2, 60, 0, false));
AddRuleset(CreateRuleset(kId3, 150, 0, false));
RulesetManagerObserver ruleset_waiter(manager());
DeclarativeNetRequestUnittest::LoadAndExpectSuccess(
300, 0, false /* expect_rulesets_indexed */);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(0);
RunUpdateEnabledRulesetsFunction(*extension(), {}, {kId2, kId3},
absl::nullopt /* expected_error */);
VerifyPublicRulesetIDs(*extension(), {kId2, kId3});
VerifyGetEnabledRulesetsFunction(*extension(), {kId2, kId3});
// Ensure the set of enabled rulesets persists across extension reloads.
// Regression test for crbug.com/1346185.
const ExtensionId extension_id = extension()->id();
service()->DisableExtension(extension_id,
disable_reason::DISABLE_USER_ACTION);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(0);
service()->EnableExtension(extension_id);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
const Extension* extension =
registry()->GetExtensionById(extension_id, ExtensionRegistry::ENABLED);
ASSERT_TRUE(extension);
VerifyPublicRulesetIDs(*extension, {kId2, kId3});
VerifyGetEnabledRulesetsFunction(*extension, {kId2, kId3});
}
// Tests attempting to disable rulesets when there are no rulesets active.
// Regression test for https://crbug.com/1354385.
TEST_P(MultipleRulesetsTest,
UpdateAndGetEnabledRulesets_DisableRulesetsWhenEmptyEnabledRulesets) {
AddRuleset(CreateRuleset(kId1, 90, 0, false));
AddRuleset(CreateRuleset(kId2, 60, 0, false));
AddRuleset(CreateRuleset(kId3, 150, 0, false));
RulesetManagerObserver ruleset_waiter(manager());
DeclarativeNetRequestUnittest::LoadAndExpectSuccess(
300, 0, false /* expect_rulesets_indexed */);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(0);
// Even though rulesets kId2 and kId3 are already disabled, the service
// can't know about that right away because there could be pending calls to
// complete. This means the service will still (appropriately) try and
// disable these rulesets.
RunUpdateEnabledRulesetsFunction(*extension(), {kId2, kId3}, {},
absl::nullopt /* expected_error */);
ASSERT_FALSE(manager()->GetMatcherForExtension(extension()->id()));
VerifyGetEnabledRulesetsFunction(*extension(), {});
}
// Test that getAvailableStaticRuleCount returns the correct number of rules an
// extension can still enable.
TEST_P(MultipleRulesetsTest, GetAvailableStaticRuleCount) {
AddRuleset(CreateRuleset(kId1, 50, 0, true));
AddRuleset(CreateRuleset(kId2, 100, 0, false));
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess(150, 50);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
scoped_refptr<const Extension> first_extension = extension();
ASSERT_TRUE(first_extension.get());
ExtensionId first_extension_id = first_extension.get()->id();
// Initially, the extension should have 250 more static rules available, and
// no rules allocated from the global pool.
VerifyPublicRulesetIDs(*first_extension.get(), {kId1});
CheckExtensionAllocationInPrefs(first_extension_id, absl::nullopt);
VerifyGetAvailableStaticRuleCountFunction(*first_extension.get(), 250);
// Enabling |kId2| should result in 50 rules allocated in the global pool, and
// 150 more rules available for the extension to enable.
RunUpdateEnabledRulesetsFunction(*first_extension.get(), {}, {kId2},
absl::nullopt /* expected_error */);
VerifyPublicRulesetIDs(*first_extension.get(), {kId1, kId2});
CheckExtensionAllocationInPrefs(first_extension_id, 50);
VerifyGetAvailableStaticRuleCountFunction(*first_extension.get(), 150);
// Disabling all rulesets should result in 300 rules available.
RunUpdateEnabledRulesetsFunction(*first_extension.get(), {kId1, kId2}, {},
absl::nullopt /* expected_error */);
VerifyPublicRulesetIDs(*first_extension.get(), {});
CheckExtensionAllocationInPrefs(first_extension_id, absl::nullopt);
VerifyGetAvailableStaticRuleCountFunction(*first_extension.get(), 300);
// Load another extension with one ruleset with 300 rules.
UpdateExtensionLoaderAndPath(
temp_dir().GetPath().Append(FILE_PATH_LITERAL("test_extension_2")));
ClearRulesets();
AddRuleset(CreateRuleset(kId3, GetMaximumRulesPerRuleset(), 0, true));
DeclarativeNetRequestUnittest::LoadAndExpectSuccess(
GetMaximumRulesPerRuleset(), GetMaximumRulesPerRuleset(),
true /* expect_rulesets_indexed */);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(2);
scoped_refptr<const Extension> second_extension = extension();
ASSERT_TRUE(second_extension.get());
ExtensionId second_extension_id = second_extension.get()->id();
VerifyPublicRulesetIDs(*second_extension.get(), {kId3});
CheckExtensionAllocationInPrefs(second_extension_id, 200);
// The first extension should still have GetStaticGuaranteedMinimumRuleCount()
// rules available as it has no rules enabled and the global pool is full.
VerifyGetAvailableStaticRuleCountFunction(
*first_extension.get(), GetStaticGuaranteedMinimumRuleCount());
// The second extension should not have any rules available since its
// allocation consists of the entire global pool.
VerifyGetAvailableStaticRuleCountFunction(*second_extension.get(), 0);
}
// Test to update disabled rule ids of static rulesets.
TEST_P(MultipleRulesetsTest, UpdateStaticRulesDisableAndEnableRules) {
AddRuleset(CreateRuleset(kId1, 5, 0, true));
AddRuleset(CreateRuleset(kId2, 5, 0, true));
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess(10, 10);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
VerifyPublicRulesetIDs(*extension(), {kId1, kId2});
// The initial disabled rule ids set is empty.
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId1), testing::IsEmpty());
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId2), testing::IsEmpty());
VerifyGetDisabledRuleIdsFunction(*extension(), kId1, {});
VerifyGetDisabledRuleIdsFunction(*extension(), kId2, {});
EXPECT_EQ(0u, GetDisabledStaticRuleCount());
// Disable rule 1, rule 2 and rule 3 of ruleset1.
RunUpdateStaticRulesFunction(*extension(), kId1, {1, 2, 3}, {},
absl::nullopt /* expected_error */);
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId1),
UnorderedElementsAre(1, 2, 3));
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId2), testing::IsEmpty());
VerifyGetDisabledRuleIdsFunction(*extension(), kId1, {1, 2, 3});
VerifyGetDisabledRuleIdsFunction(*extension(), kId2, {});
EXPECT_EQ(3u, GetDisabledStaticRuleCount());
// Disable rule 3, rule 4 and rule 5 of ruleset2.
RunUpdateStaticRulesFunction(*extension(), kId2, {3, 4, 5}, {},
absl::nullopt /* expected_error */);
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId1),
UnorderedElementsAre(1, 2, 3));
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId2),
UnorderedElementsAre(3, 4, 5));
VerifyGetDisabledRuleIdsFunction(*extension(), kId1, {1, 2, 3});
VerifyGetDisabledRuleIdsFunction(*extension(), kId2, {3, 4, 5});
EXPECT_EQ(6u, GetDisabledStaticRuleCount());
// Enable rule 1, rule 2 rule 3 and rule 4 of ruleset1. Enabling rule 4
// doesn't make any change since rule 4 is not disabled.
RunUpdateStaticRulesFunction(*extension(), kId1, {}, {1, 2, 3, 4},
absl::nullopt /* expected_error */);
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId1), testing::IsEmpty());
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId2),
UnorderedElementsAre(3, 4, 5));
VerifyGetDisabledRuleIdsFunction(*extension(), kId1, {});
VerifyGetDisabledRuleIdsFunction(*extension(), kId2, {3, 4, 5});
EXPECT_EQ(3u, GetDisabledStaticRuleCount());
// Enable rule 3, rule 4, rule 5 and rule 6 of ruleset2. Enabling
// rule 6 doesn't make any change since rule 6 is not disabled.
RunUpdateStaticRulesFunction(*extension(), kId2, {}, {3, 4, 5, 6},
absl::nullopt /* expected_error */);
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId1), testing::IsEmpty());
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId2), testing::IsEmpty());
VerifyGetDisabledRuleIdsFunction(*extension(), kId1, {});
VerifyGetDisabledRuleIdsFunction(*extension(), kId2, {});
EXPECT_EQ(0u, GetDisabledStaticRuleCount());
}
// Test UpdateStaticRules making no change.
TEST_P(MultipleRulesetsTest, UpdateStaticRulesMakingNoChange) {
AddRuleset(CreateRuleset(kId1, 5, 0, true));
AddRuleset(CreateRuleset(kId2, 5, 0, true));
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess(10, 10);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
VerifyPublicRulesetIDs(*extension(), {kId1, kId2});
// Disable rule 1, rule 2 and rule 3 of ruleset1.
// Disable rule 3, rule 4 and rule 5 of ruleset2.
RunUpdateStaticRulesFunction(*extension(), kId1, {1, 2, 3}, {},
absl::nullopt /* expected_error */);
RunUpdateStaticRulesFunction(*extension(), kId2, {3, 4, 5}, {},
absl::nullopt /* expected_error */);
// Updating disabled rule ids with null set doesn't make any change.
RunUpdateStaticRulesFunction(*extension(), kId2, {}, {},
absl::nullopt /* expected_error */);
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId1),
UnorderedElementsAre(1, 2, 3));
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId2),
UnorderedElementsAre(3, 4, 5));
VerifyGetDisabledRuleIdsFunction(*extension(), kId1, {1, 2, 3});
VerifyGetDisabledRuleIdsFunction(*extension(), kId2, {3, 4, 5});
EXPECT_EQ(6u, GetDisabledStaticRuleCount());
// Fails to enable rule 8, rule 9 and rule 10 of ruleset3 since ruleset3 is
// invalid ruleset id.
RunUpdateStaticRulesFunction(
*extension(), kId3, {3, 4, 5}, {},
ErrorUtils::FormatErrorMessage(kInvalidRulesetIDError, kId3));
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId1),
UnorderedElementsAre(1, 2, 3));
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId2),
UnorderedElementsAre(3, 4, 5));
VerifyGetDisabledRuleIdsFunction(*extension(), kId1, {1, 2, 3});
VerifyGetDisabledRuleIdsFunction(*extension(), kId2, {3, 4, 5});
EXPECT_FALSE(RulesetExists(kId3));
EXPECT_EQ(6u, GetDisabledStaticRuleCount());
}
// Test to check UpdateStaticRules argument priority.
TEST_P(MultipleRulesetsTest, UpdateStaticRulesArgumentPriority) {
AddRuleset(CreateRuleset(kId1, 5, 0, true));
AddRuleset(CreateRuleset(kId2, 5, 0, true));
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess(10, 10);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
VerifyPublicRulesetIDs(*extension(), {kId1, kId2});
// Disable rule 1, rule 2 and rule 3 of ruleset1.
// Disable rule 3, rule 4 and rule 5 of ruleset2.
RunUpdateStaticRulesFunction(*extension(), kId1, {1, 2, 3}, {},
absl::nullopt /* expected_error */);
RunUpdateStaticRulesFunction(*extension(), kId2, {3, 4, 5}, {},
absl::nullopt /* expected_error */);
// Disable rule 4 and rule 5 of ruleset2 but it doesn't make any change since
// they are already disabled. Ignore enabling rule 5 since |ids_to_disable|
// takes priority over |ids_to_enable|.
RunUpdateStaticRulesFunction(*extension(), kId2, {4, 5}, {5},
absl::nullopt /* expected_error */);
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId1),
UnorderedElementsAre(1, 2, 3));
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId2),
UnorderedElementsAre(3, 4, 5));
VerifyGetDisabledRuleIdsFunction(*extension(), kId1, {1, 2, 3});
VerifyGetDisabledRuleIdsFunction(*extension(), kId2, {3, 4, 5});
EXPECT_EQ(6u, GetDisabledStaticRuleCount());
// Enable rule 4 and disable rule 5, rule 6 and rule 7 of ruleset2. Ignore
// enabling rule 5 since |ids_to_disable| takes priority over |ids_to_enable|.
// Disabling rule 5 doesn't make any change since rule 5 is already disabled.
RunUpdateStaticRulesFunction(*extension(), kId2, {5, 6, 7}, {4, 5},
absl::nullopt /* expected_error */);
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId1),
UnorderedElementsAre(1, 2, 3));
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId2),
UnorderedElementsAre(3, 5, 6, 7));
VerifyGetDisabledRuleIdsFunction(*extension(), kId1, {1, 2, 3});
VerifyGetDisabledRuleIdsFunction(*extension(), kId2, {3, 5, 6, 7});
EXPECT_EQ(7u, GetDisabledStaticRuleCount());
}
// Test to check UpdateStaticRules error when rule limit exceeded.
TEST_P(MultipleRulesetsTest, UpdateStaticRulesErrorWhenRuleLimitExceeded) {
// Set the disabled static rule limit as 6.
ScopedRuleLimitOverride scoped_disabled_static_rule_limit_override =
CreateScopedDisabledStaticRuleLimitOverrideForTesting(6);
AddRuleset(CreateRuleset(kId1, 5, 0, true));
AddRuleset(CreateRuleset(kId2, 5, 0, true));
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess(10, 10);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
VerifyPublicRulesetIDs(*extension(), {kId1, kId2});
// Disable rule 1, rule 2 and rule 3 of ruleset1.
// Disable rule 3, rule 4 and rule 5 of ruleset2.
RunUpdateStaticRulesFunction(*extension(), kId1, {1, 2, 3}, {},
absl::nullopt /* expected_error */);
RunUpdateStaticRulesFunction(*extension(), kId2, {3, 4, 5}, {},
absl::nullopt /* expected_error */);
// Enable rule 1 and disable rule 3, rule 4 and rule 5 of ruleset2. Ignore
// enabling rule 3 since |ids_to_disable| takes priority over |ids_to_enable|.
// This operation fails since it exceeds the disabled static rule count limit.
RunUpdateStaticRulesFunction(*extension(), kId1, {3, 4, 5}, {1, 3},
kDisabledStaticRuleCountExceeded);
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId1),
UnorderedElementsAre(1, 2, 3));
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId2),
UnorderedElementsAre(3, 4, 5));
VerifyGetDisabledRuleIdsFunction(*extension(), kId1, {1, 2, 3});
VerifyGetDisabledRuleIdsFunction(*extension(), kId2, {3, 4, 5});
EXPECT_EQ(6u, GetDisabledStaticRuleCount());
}
// Test to check GetDisabledRuleIds error for invalid ruleset id.
TEST_P(MultipleRulesetsTest, GetDisabledStaticRuleIdsErrorForInvalidRuleset) {
AddRuleset(CreateRuleset(kId1, 5, 0, true));
AddRuleset(CreateRuleset(kId2, 5, 0, true));
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess(10, 10);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
VerifyPublicRulesetIDs(*extension(), {kId1, kId2});
// Disable rule 1, rule 2 and rule 3 of ruleset1.
// Disable rule 3, rule 4 and rule 5 of ruleset2.
RunUpdateStaticRulesFunction(*extension(), kId1, {1, 2, 3}, {},
absl::nullopt /* expected_error */);
RunUpdateStaticRulesFunction(*extension(), kId2, {3, 4, 5}, {},
absl::nullopt /* expected_error */);
VerifyGetDisabledRuleIdsFunctionError(
*extension(), kId3,
ErrorUtils::FormatErrorMessage(kInvalidRulesetIDError, kId3));
}
// Test the disabled rule ids when the extension is disabled and enabled.
TEST_P(MultipleRulesetsTest,
KeepDisabledStaticRulesWhenExtensionDisabledAndEnabled) {
AddRuleset(CreateRuleset(kId1, 5, 0, true));
AddRuleset(CreateRuleset(kId2, 5, 0, true));
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess(10, 10);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
VerifyPublicRulesetIDs(*extension(), {kId1, kId2});
// Disable rule 1, rule 2 and rule 3 of ruleset1.
// Disable rule 3, rule 4 and rule 5 of ruleset2.
RunUpdateStaticRulesFunction(*extension(), kId1, {1, 2, 3}, {},
absl::nullopt /* expected_error */);
RunUpdateStaticRulesFunction(*extension(), kId2, {3, 4, 5}, {},
absl::nullopt /* expected_error */);
// Check disabled rules after disabling and enabling extension.
auto extension_id = extension()->id();
service()->DisableExtension(extension_id,
disable_reason::DISABLE_USER_ACTION);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(0);
TestExtensionRegistryObserver registry_observer(registry());
service()->EnableExtension(extension_id);
scoped_refptr<const Extension> extension =
registry_observer.WaitForExtensionLoaded();
ASSERT_TRUE(extension);
ASSERT_EQ(extension_id, extension->id());
content::RunAllTasksUntilIdle();
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId1),
UnorderedElementsAre(1, 2, 3));
EXPECT_THAT(GetDisabledRuleIdsFromMatcher(kId2),
UnorderedElementsAre(3, 4, 5));
VerifyGetDisabledRuleIdsFunction(*extension, kId1, {1, 2, 3});
VerifyGetDisabledRuleIdsFunction(*extension, kId2, {3, 4, 5});
EXPECT_EQ(6u, GetDisabledStaticRuleCount());
}
// Test that an extension's allocation is reclaimed when unloaded in certain
// scenarios.
TEST_P(MultipleRulesetsTest, ReclaimAllocationOnUnload) {
const size_t ext_1_allocation = 50;
AddRuleset(CreateRuleset(
kId1, GetStaticGuaranteedMinimumRuleCount() + ext_1_allocation, 0, true));
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess(GetStaticGuaranteedMinimumRuleCount() +
ext_1_allocation);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
ExtensionId first_extension_id = extension()->id();
// The |ext_1_allocation| rules that contribute to the global pool should be
// tracked.
GlobalRulesTracker& global_rules_tracker =
RulesMonitorService::Get(browser_context())->global_rules_tracker();
EXPECT_EQ(ext_1_allocation,
global_rules_tracker.GetAllocatedGlobalRuleCountForTesting());
// An entry for these |ext_1_allocation| rules should be persisted for the
// extension in prefs.
CheckExtensionAllocationInPrefs(first_extension_id, ext_1_allocation);
auto disable_extension_and_check_allocation =
[this, &ext_1_allocation, &global_rules_tracker, &ruleset_waiter,
&first_extension_id](int disable_reasons,
bool expect_allocation_released) {
service()->DisableExtension(first_extension_id, disable_reasons);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(0);
size_t expected_tracker_allocation =
expect_allocation_released ? 0 : ext_1_allocation;
absl::optional<size_t> expected_pref_allocation =
expect_allocation_released
? absl::nullopt
: absl::make_optional<size_t>(ext_1_allocation);
EXPECT_EQ(expected_tracker_allocation,
global_rules_tracker.GetAllocatedGlobalRuleCountForTesting());
CheckExtensionAllocationInPrefs(first_extension_id,
expected_pref_allocation);
service()->EnableExtension(first_extension_id);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
EXPECT_EQ(ext_1_allocation,
global_rules_tracker.GetAllocatedGlobalRuleCountForTesting());
CheckExtensionAllocationInPrefs(first_extension_id, ext_1_allocation);
};
// Test some DisableReasons that shouldn't cause the allocation to be
// released.
disable_extension_and_check_allocation(disable_reason::DISABLE_USER_ACTION,
false);
disable_extension_and_check_allocation(
disable_reason::DISABLE_PERMISSIONS_INCREASE |
disable_reason::DISABLE_GREYLIST,
false);
// Test the DisableReasons that should cause the allocation to be released.
disable_extension_and_check_allocation(
disable_reason::DISABLE_BLOCKED_BY_POLICY, true);
disable_extension_and_check_allocation(
disable_reason::DISABLE_BLOCKED_BY_POLICY |
disable_reason::DISABLE_USER_ACTION,
true);
// We should reclaim the extension's allocation if it is blocklisted.
service()->BlocklistExtensionForTest(first_extension_id);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(0);
EXPECT_EQ(0u, global_rules_tracker.GetAllocatedGlobalRuleCountForTesting());
CheckExtensionAllocationInPrefs(first_extension_id, absl::nullopt);
// Load another extension, only to have it be terminated.
const size_t ext_2_allocation = 50;
UpdateExtensionLoaderAndPath(
temp_dir().GetPath().Append(FILE_PATH_LITERAL("test_extension_2")));
ClearRulesets();
AddRuleset(CreateRuleset(
kId2, GetStaticGuaranteedMinimumRuleCount() + ext_2_allocation, 0, true));
LoadAndExpectSuccess(GetStaticGuaranteedMinimumRuleCount() +
ext_2_allocation);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
ExtensionId second_extension_id = extension()->id();
// The extension should have its allocation kept when it is terminated.
service()->TerminateExtension(second_extension_id);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(0);
EXPECT_EQ(ext_2_allocation,
global_rules_tracker.GetAllocatedGlobalRuleCountForTesting());
CheckExtensionAllocationInPrefs(second_extension_id, ext_2_allocation);
}
using MultipleRulesetsTest_Unpacked = MultipleRulesetsTest;
// Test that reloading an unpacked extension is functionally identical to
// uninstalling then reinstalling it for the purpose of global rule allocation,
// and the allocation should reflect changes made to the extension.
TEST_P(MultipleRulesetsTest_Unpacked, UpdateAllocationOnReload) {
AddRuleset(CreateRuleset(kId1, 250, 0, true));
RulesetManagerObserver ruleset_waiter(manager());
LoadAndExpectSuccess(250);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
ExtensionId extension_id = extension()->id();
// The 150 rules that contribute to the global pool should be
// tracked.
GlobalRulesTracker& global_rules_tracker =
RulesMonitorService::Get(browser_context())->global_rules_tracker();
EXPECT_EQ(150u, global_rules_tracker.GetAllocatedGlobalRuleCountForTesting());
// An entry for these 150 rules should be persisted for the extension in
// prefs.
CheckExtensionAllocationInPrefs(extension_id, 150);
// Replace ruleset |kId1| with a smaller ruleset |kId2| and persist the
// ruleset to the extension's directory via WriteExtensionData().
ClearRulesets();
AddRuleset(CreateRuleset(kId2, 150, 0, true));
WriteExtensionData();
// Reload the extension. For unpacked extensions this is functionally
// equivalent to uninstalling the extension then installing it again based on
// the contents of the extension's directory.
service()->ReloadExtension(extension_id);
ruleset_waiter.WaitForExtensionsWithRulesetsCount(1);
// File changes to the extension's ruleset should take effect after it is
// reloaded.
EXPECT_EQ(50u, global_rules_tracker.GetAllocatedGlobalRuleCountForTesting());
CheckExtensionAllocationInPrefs(extension_id, 50);
}
INSTANTIATE_TEST_SUITE_P(All,
SingleRulesetTest,
::testing::Values(ExtensionLoadType::PACKED,
ExtensionLoadType::UNPACKED));
INSTANTIATE_TEST_SUITE_P(All,
MultipleRulesetsTest,
::testing::Values(ExtensionLoadType::PACKED,
ExtensionLoadType::UNPACKED));
INSTANTIATE_TEST_SUITE_P(All,
MultipleRulesetsTest_Unpacked,
::testing::Values(ExtensionLoadType::UNPACKED));
} // namespace
} // namespace declarative_net_request
} // namespace extensions