Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* Any copyright is dedicated to the Public Domain.
"use strict";
ChromeUtils.defineESModuleGetters(this, {
ExtensionDNRLimits: "resource://gre/modules/ExtensionDNRLimits.sys.mjs",
ExtensionDNRStore: "resource://gre/modules/ExtensionDNRStore.sys.mjs",
});
AddonTestUtils.init(this);
AddonTestUtils.overrideCertDB();
Services.scriptloader.loadSubScript(
Services.io.newFileURI(do_get_file("head_dnr.js")).spec,
this
);
Services.scriptloader.loadSubScript(
Services.io.newFileURI(do_get_file("head_dnr_static_rules.js")).spec,
this
);
const server = createHttpServer({ hosts: ["example.com"] });
server.registerPathHandler("/", (req, res) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.write("response from server");
});
add_setup(async () => {
Services.prefs.setBoolPref("extensions.manifestV3.enabled", true);
Services.prefs.setBoolPref("extensions.dnr.enabled", true);
Services.prefs.setBoolPref("extensions.dnr.feedback", true);
// NOTE: this test is going to load and validate the maximum amount of static rules
// that an extension can enable, which on slower builds (in particular in tsan builds,
// e.g. see Bug 1803801) have a higher chance that the test extension may have hit the
// idle timeout and being suspended by the time the test is going to trigger API method
// calls through test API events (which do not expect the lifetime of the event page).
Services.prefs.setIntPref("extensions.background.idle.timeout", 300_000);
// NOTE: reduce the static rules limits to reduce the amount of time needed to run
// this xpcshell test.
Services.prefs.setIntPref(
"extensions.dnr.guaranteed_minimum_static_rules",
30
);
Services.prefs.setIntPref("extensions.dnr.max_number_of_static_rulesets", 10);
Services.prefs.setIntPref(
"extensions.dnr.max_number_of_enabled_static_rulesets",
2
);
// Sanity-check: confirm the custom limits prefs set are matched by the ExtensionDNRLimits
// properties.
const {
GUARANTEED_MINIMUM_STATIC_RULES,
MAX_NUMBER_OF_ENABLED_STATIC_RULESETS,
MAX_NUMBER_OF_STATIC_RULESETS,
} = ExtensionDNRLimits;
equal(
GUARANTEED_MINIMUM_STATIC_RULES,
30,
"Expect GUARANTEED_MINIMUM_STATIC_RULES to match the value set on the related pref"
);
equal(
MAX_NUMBER_OF_ENABLED_STATIC_RULESETS,
2,
"Expect MAX_NUMBER_OF_ENABLED_STATIC_RULESETS to match the value set on the related pref"
);
equal(
MAX_NUMBER_OF_STATIC_RULESETS,
10,
"Expect MAX_NUMBER_OF_STATIC_RULESETS to match the value set on the related pref"
);
setupTelemetryForTests();
await ExtensionTestUtils.startAddonManager();
});
add_task(async function test_getAvailableStaticRulesCountAndLimits() {
const dnrStore = ExtensionDNRStore._getStoreForTesting();
const { GUARANTEED_MINIMUM_STATIC_RULES } = ExtensionDNRLimits;
equal(
typeof GUARANTEED_MINIMUM_STATIC_RULES,
"number",
"Expect GUARANTEED_MINIMUM_STATIC_RULES to be a number"
);
const availableStaticRulesCount = GUARANTEED_MINIMUM_STATIC_RULES;
const rule_resources = [
{
id: "ruleset_0",
path: "/ruleset_0.json",
enabled: true,
},
{
id: "ruleset_1",
path: "/ruleset_1.json",
enabled: true,
},
// A ruleset initially disabled (to make sure it doesn't count for the
// rules count limit).
{
id: "ruleset_disabled",
path: "/ruleset_disabled.json",
enabled: false,
},
// A ruleset including an invalid rule and valid rule.
{
id: "ruleset_withInvalid",
path: "/ruleset_withInvalid.json",
enabled: false,
},
// An empty ruleset (to make sure it can still be enabled/disabled just fine,
// e.g. in case on some browser version all rules are technically invalid).
{
id: "ruleset_empty",
path: "/ruleset_empty.json",
enabled: false,
},
];
const files = {};
const rules = {};
const rulesetDisabledData = [getDNRRule({ id: 1 })];
const ruleValid = getDNRRule({ id: 2, action: { type: "allow" } });
const rulesetWithInvalidData = [
getDNRRule({ id: 1, action: { type: "invalid_action" } }),
ruleValid,
];
rules.ruleset_0 = [getDNRRule({ id: 1 }), getDNRRule({ id: 2 })];
rules.ruleset_1 = [];
for (let i = 0; i < availableStaticRulesCount; i++) {
rules.ruleset_1.push(getDNRRule({ id: i + 1 }));
}
for (const [k, v] of Object.entries(rules)) {
files[`${k}.json`] = JSON.stringify(v);
}
files[`ruleset_disabled.json`] = JSON.stringify(rulesetDisabledData);
files[`ruleset_withInvalid.json`] = JSON.stringify(rulesetWithInvalidData);
files[`ruleset_empty.json`] = JSON.stringify([]);
const extension = ExtensionTestUtils.loadExtension(
getDNRExtension({
id: "dnr-getAvailable-count-@mochitest",
rule_resources,
files,
})
);
await extension.startup();
await extension.awaitMessage("bgpage:ready");
async function updateEnabledRulesets({ expectedErrorMessage, ...options }) {
// Note: options = { disableRulesetIds, enableRulesetIds }
extension.sendMessage("updateEnabledRulesets", options);
let [result] = await extension.awaitMessage("updateEnabledRulesets:done");
if (expectedErrorMessage) {
Assert.deepEqual(
result,
{ rejectedWithErrorMessage: expectedErrorMessage },
"updateEnabledRulesets() should reject with the given error"
);
} else {
Assert.deepEqual(
result,
undefined,
"updateEnabledRulesets() should resolve without error"
);
}
}
const expectedEnabledRulesets = {};
expectedEnabledRulesets.ruleset_0 = getSchemaNormalizedRules(
extension,
rules.ruleset_0
);
info(
"Expect ruleset_1 to not be enabled because along with ruleset_0 exceeded the static rules count limit"
);
await assertDNRStoreData(dnrStore, extension, expectedEnabledRulesets);
await assertDNRGetAvailableStaticRuleCount(
extension,
availableStaticRulesCount - rules.ruleset_0.length,
"Got the available static rule count on ruleset_0 initially enabled"
);
// Try to enable ruleset_1 again from the API method.
await updateEnabledRulesets({
enableRulesetIds: ["ruleset_1"],
expectedErrorMessage: `Number of rules across all enabled static rulesets exceeds GUARANTEED_MINIMUM_STATIC_RULES if ruleset "ruleset_1" were to be enabled.`,
});
info(
"Expect ruleset_1 to not be enabled because still exceeded the static rules count limit"
);
await assertDNRGetEnabledRulesets(extension, ["ruleset_0"]);
await assertDNRStoreData(dnrStore, extension, expectedEnabledRulesets);
await assertDNRGetAvailableStaticRuleCount(
extension,
availableStaticRulesCount - rules.ruleset_0.length,
"Got the available static rule count on ruleset_0 still the only one enabled"
);
await updateEnabledRulesets({
disableRulesetIds: ["ruleset_0"],
enableRulesetIds: ["ruleset_1"],
});
info("Expect ruleset_1 to be enabled along with disabling ruleset_0");
await assertDNRGetEnabledRulesets(extension, ["ruleset_1"]);
delete expectedEnabledRulesets.ruleset_0;
expectedEnabledRulesets.ruleset_1 = getSchemaNormalizedRules(
extension,
rules.ruleset_1
);
await assertDNRStoreData(dnrStore, extension, expectedEnabledRulesets, {
// Assert total amount of expected rules and only the first and last rule
// individually, to avoid generating a huge amount of logs and potential
// timeout failures on slower builds.
assertIndividualRules: false,
});
await assertDNRGetAvailableStaticRuleCount(
extension,
0,
"Expect no additional static rules count available when ruleset_1 is enabled"
);
info(
"Expect ruleset_disabled to stay disabled because along with ruleset_1 exceeeds the limits"
);
await updateEnabledRulesets({
enableRulesetIds: ["ruleset_disabled"],
expectedErrorMessage: `Number of rules across all enabled static rulesets exceeds GUARANTEED_MINIMUM_STATIC_RULES if ruleset "ruleset_disabled" were to be enabled.`,
});
await assertDNRGetEnabledRulesets(extension, ["ruleset_1"]);
await assertDNRStoreData(dnrStore, extension, expectedEnabledRulesets, {
// Assert total amount of expected rules and only the first and last rule
// individually, to avoid generating a huge amount of logs and potential
// timeout failures on slower builds.
assertIndividualRules: false,
});
await assertDNRGetAvailableStaticRuleCount(
extension,
0,
"Expect no additional static rules count available"
);
info("Expect ruleset_empty to be enabled despite having reached the limit");
await updateEnabledRulesets({
enableRulesetIds: ["ruleset_empty"],
});
await assertDNRGetEnabledRulesets(extension, ["ruleset_1", "ruleset_empty"]);
await assertDNRStoreData(
dnrStore,
extension,
{
...expectedEnabledRulesets,
ruleset_empty: [],
},
// Assert total amount of expected rules and only the first and last rule
// individually, to avoid generating a huge amount of logs and potential
// timeout failures on slower builds.
{ assertIndividualRules: false }
);
await assertDNRGetAvailableStaticRuleCount(
extension,
0,
"Expect no additional static rules count available"
);
info("Expect invalid rules to not be counted towards the limits");
await updateEnabledRulesets({
disableRulesetIds: ["ruleset_1", "ruleset_empty"],
enableRulesetIds: ["ruleset_withInvalid"],
});
await assertDNRGetEnabledRulesets(extension, ["ruleset_withInvalid"]);
await assertDNRStoreData(dnrStore, extension, {
// Only the valid rule has been actually loaded, and the invalid one
// ignored.
ruleset_withInvalid: [ruleValid],
});
await assertDNRGetAvailableStaticRuleCount(
extension,
availableStaticRulesCount - 1,
"Expect only valid rules to be counted"
);
await extension.unload();
Services.prefs.clearUserPref("extensions.background.idle.enabled");
});
add_task(async function test_static_rulesets_limits() {
const dnrStore = ExtensionDNRStore._getStoreForTesting();
const getRulesetManifestData = (rulesetNumber, enabled) => {
return {
id: `ruleset_${rulesetNumber}`,
enabled,
path: `ruleset_${rulesetNumber}.json`,
};
};
const {
MAX_NUMBER_OF_STATIC_RULESETS,
MAX_NUMBER_OF_ENABLED_STATIC_RULESETS,
} = ExtensionDNRLimits;
equal(
typeof MAX_NUMBER_OF_STATIC_RULESETS,
"number",
"Expect MAX_NUMBER_OF_STATIC_RULESETS to be a number"
);
equal(
typeof MAX_NUMBER_OF_ENABLED_STATIC_RULESETS,
"number",
"Expect MAX_NUMBER_OF_ENABLED_STATIC_RULESETS to be a number"
);
Assert.greater(
MAX_NUMBER_OF_STATIC_RULESETS,
MAX_NUMBER_OF_ENABLED_STATIC_RULESETS,
"Expect MAX_NUMBER_OF_STATIC_RULESETS to be greater"
);
const rules = [getDNRRule()];
const rule_resources = [];
const files = {};
for (let i = 0; i < MAX_NUMBER_OF_STATIC_RULESETS + 1; i++) {
const enabled = i < MAX_NUMBER_OF_ENABLED_STATIC_RULESETS + 1;
files[`ruleset_${i}.json`] = JSON.stringify(rules);
rule_resources.push(getRulesetManifestData(i, enabled));
}
let extension = ExtensionTestUtils.loadExtension(
getDNRExtension({
rule_resources,
files,
})
);
const expectedEnabledRulesets = {};
const { messages } = await AddonTestUtils.promiseConsoleOutput(async () => {
ExtensionTestUtils.failOnSchemaWarnings(false);
await extension.startup();
ExtensionTestUtils.failOnSchemaWarnings(true);
await extension.awaitMessage("bgpage:ready");
for (let i = 0; i < MAX_NUMBER_OF_ENABLED_STATIC_RULESETS; i++) {
expectedEnabledRulesets[`ruleset_${i}`] = getSchemaNormalizedRules(
extension,
rules
);
}
await assertDNRGetEnabledRulesets(
extension,
Array.from(Object.keys(expectedEnabledRulesets))
);
await assertDNRStoreData(dnrStore, extension, expectedEnabledRulesets);
});
AddonTestUtils.checkMessages(messages, {
expected: [
// Warnings emitted from the manifest schema validation.
{
message:
/declarative_net_request: Static rulesets are exceeding the MAX_NUMBER_OF_STATIC_RULESETS limit/,
},
{
message:
/declarative_net_request: Enabled static rulesets are exceeding the MAX_NUMBER_OF_ENABLED_STATIC_RULESETS limit .* "ruleset_2"/,
},
// Error reported on the browser console as part of loading enabled rulesets)
// on enabled rulesets being ignored because exceeding the limit.
{
message:
/Ignoring enabled static ruleset exceeding the MAX_NUMBER_OF_ENABLED_STATIC_RULESETS .* "ruleset_2"/,
},
],
});
info(
"Verify updateEnabledRulesets reject when the request is exceeding the enabled rulesets count limit"
);
extension.sendMessage("updateEnabledRulesets", {
disableRulesetIds: ["ruleset_0"],
enableRulesetIds: ["ruleset_2", "ruleset_3"],
});
await Assert.rejects(
extension.awaitMessage("updateEnabledRulesets:done").then(results => {
if (results[0].rejectedWithErrorMessage) {
return Promise.reject(new Error(results[0].rejectedWithErrorMessage));
}
return results[0];
}),
/updatedEnabledRulesets request is exceeding MAX_NUMBER_OF_ENABLED_STATIC_RULESETS/,
"Expected rejection on updateEnabledRulesets exceeting enabled rulesets count limit"
);
// Confirm that the expected rulesets didn't change neither.
await assertDNRGetEnabledRulesets(
extension,
Array.from(Object.keys(expectedEnabledRulesets))
);
await assertDNRStoreData(dnrStore, extension, expectedEnabledRulesets);
info(
"Verify updateEnabledRulesets applies the expected changes when resolves successfully"
);
extension.sendMessage(
"updateEnabledRulesets",
{
disableRulesetIds: ["ruleset_0"],
enableRulesetIds: ["ruleset_2"],
},
{
disableRulesetIds: ["ruleset_2"],
enableRulesetIds: ["ruleset_3"],
}
);
await extension.awaitMessage("updateEnabledRulesets:done");
// Expect ruleset_0 disabled, ruleset_2 to be enabled but then disabled by the
// second update queued after the first one, and ruleset_3 to be enabled.
delete expectedEnabledRulesets.ruleset_0;
expectedEnabledRulesets.ruleset_3 = getSchemaNormalizedRules(
extension,
rules
);
await assertDNRGetEnabledRulesets(
extension,
Array.from(Object.keys(expectedEnabledRulesets))
);
await assertDNRStoreData(dnrStore, extension, expectedEnabledRulesets);
// Ensure all changes were stored and reloaded from disk store and the
// DNR store update queue can accept new updates.
info("Verify static rules load and updates after extension is restarted");
// NOTE: promiseRestartManager will not be enough to make sure the
// DNR store data for the test extension is going to be loaded from
// the DNR startup cache file.
// See test_ext_dnr_startup_cache.js for a test case that more completely
// simulates ExtensionDNRStore initialization on browser restart.
await AddonTestUtils.promiseRestartManager();
await extension.awaitStartup();
await extension.awaitMessage("bgpage:ready");
await assertDNRGetEnabledRulesets(
extension,
Array.from(Object.keys(expectedEnabledRulesets))
);
await assertDNRStoreData(dnrStore, extension, expectedEnabledRulesets);
extension.sendMessage("updateEnabledRulesets", {
disableRulesetIds: ["ruleset_3"],
});
await extension.awaitMessage("updateEnabledRulesets:done");
delete expectedEnabledRulesets.ruleset_3;
await assertDNRGetEnabledRulesets(
extension,
Array.from(Object.keys(expectedEnabledRulesets))
);
await assertDNRStoreData(dnrStore, extension, expectedEnabledRulesets);
await extension.unload();
});