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, {
});
AddonTestUtils.init(this);
AddonTestUtils.overrideCertDB();
Services.scriptloader.loadSubScript(
Services.io.newFileURI(do_get_file("head_dnr.js")).spec,
this
);
const server = createHttpServer({ hosts: ["example.com"] });
server.registerPathHandler("/", (req, res) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.write("response from server");
});
function backgroundWithDNRAPICallHandlers() {
browser.test.onMessage.addListener(async (msg, ...args) => {
let result;
switch (msg) {
case "getEnabledRulesets":
result = await browser.declarativeNetRequest.getEnabledRulesets();
break;
case "getAvailableStaticRuleCount":
result =
await browser.declarativeNetRequest.getAvailableStaticRuleCount();
break;
case "testMatchOutcome":
result = await browser.declarativeNetRequest
.testMatchOutcome(...args)
.catch(err =>
browser.test.fail(
`Unexpected rejection from testMatchOutcome call: ${err}`
)
);
break;
case "updateEnabledRulesets":
// Run (one or more than one concurrently) updateEnabledRulesets calls
// and report back the results.
result = await Promise.all(
args.map(arg => {
return browser.declarativeNetRequest
.updateEnabledRulesets(arg)
.catch(err => {
return { rejectedWithErrorMessage: err.message };
});
})
);
break;
default:
browser.test.fail(`Unexpected test message: ${msg}`);
return;
}
browser.test.sendMessage(`${msg}:done`, result);
});
browser.test.sendMessage("bgpage:ready");
}
function getDNRExtension({
id = "test-dnr-static-rules@test-extension",
version = "1.0",
background = backgroundWithDNRAPICallHandlers,
useAddonManager = "permanent",
rule_resources,
declarative_net_request,
files,
}) {
// Omit declarative_net_request if rule_resources isn't defined
// (because declarative_net_request fails the manifest validation
// if rule_resources is missing).
const dnr = rule_resources ? { rule_resources } : undefined;
return {
background,
useAddonManager,
manifest: {
manifest_version: 3,
version,
permissions: ["declarativeNetRequest", "declarativeNetRequestFeedback"],
// Needed to make sure the upgraded extension will have the same id and
// same uuid (which is mapped based on the extension id).
browser_specific_settings: {
gecko: { id },
},
declarative_net_request: declarative_net_request
? { ...declarative_net_request, ...(dnr ?? {}) }
: dnr,
},
files,
};
}
const assertDNRTestMatchOutcome = async (
{ extension, testRequest, expected },
assertMessage
) => {
extension.sendMessage("testMatchOutcome", testRequest);
Assert.deepEqual(
expected,
await extension.awaitMessage("testMatchOutcome:done"),
assertMessage ??
"Got the expected matched rules from testMatchOutcome API call"
);
};
const assertDNRGetAvailableStaticRuleCount = async (
extensionTestWrapper,
expectedCount,
assertMessage
) => {
extensionTestWrapper.sendMessage("getAvailableStaticRuleCount");
Assert.deepEqual(
await extensionTestWrapper.awaitMessage("getAvailableStaticRuleCount:done"),
expectedCount,
assertMessage ??
"Got the expected count value from dnr.getAvailableStaticRuleCount API method"
);
};
const assertDNRGetEnabledRulesets = async (
extensionTestWrapper,
expectedRulesetIds
) => {
extensionTestWrapper.sendMessage("getEnabledRulesets");
Assert.deepEqual(
await extensionTestWrapper.awaitMessage("getEnabledRulesets:done"),
expectedRulesetIds,
"Got the expected enabled ruleset ids from dnr.getEnabledRulesets API method"
);
};
add_setup(async () => {
Services.prefs.setBoolPref("extensions.manifestV3.enabled", true);
Services.prefs.setBoolPref("extensions.dnr.enabled", true);
Services.prefs.setBoolPref("extensions.dnr.feedback", true);
setupTelemetryForTests();
await ExtensionTestUtils.startAddonManager();
});
add_task(async function test_load_static_rules() {
const ruleset1Data = [
getDNRRule({
action: { type: "allow" },
condition: { resourceTypes: ["main_frame"] },
}),
];
const ruleset2Data = [
getDNRRule({
action: { type: "block" },
condition: { resourceTypes: ["main_frame", "script"] },
}),
];
const rule_resources = [
{
id: "ruleset_1",
enabled: true,
path: "ruleset_1.json",
},
{
id: "ruleset_2",
enabled: true,
path: "ruleset_2.json",
},
{
id: "ruleset_3",
enabled: false,
path: "ruleset_3.json",
},
];
const files = {
// Missing ruleset_3.json on purpose.
"ruleset_1.json": JSON.stringify(ruleset1Data),
"ruleset_2.json": JSON.stringify(ruleset2Data),
};
const extension = ExtensionTestUtils.loadExtension(
getDNRExtension({ rule_resources, files })
);
await extension.startup();
const extUUID = extension.uuid;
await extension.awaitMessage("bgpage:ready");
const dnrStore = ExtensionDNRStore._getStoreForTesting();
info("Verify DNRStore data for the test extension");
await assertDNRGetEnabledRulesets(extension, ["ruleset_1", "ruleset_2"]);
await assertDNRStoreData(dnrStore, extension, {
ruleset_1: getSchemaNormalizedRules(extension, ruleset1Data),
ruleset_2: getSchemaNormalizedRules(extension, ruleset2Data),
});
info("Verify matched rules using testMatchOutcome");
const testRequestMainFrame = {
type: "main_frame",
};
const testRequestScript = {
type: "script",
};
await assertDNRTestMatchOutcome(
{
extension,
testRequest: testRequestMainFrame,
expected: {
matchedRules: [{ ruleId: 1, rulesetId: "ruleset_1" }],
},
},
"Expect ruleset_1 to be matched on the main-frame test request"
);
await assertDNRTestMatchOutcome(
{
extension,
testRequest: testRequestScript,
expected: {
matchedRules: [{ ruleId: 1, rulesetId: "ruleset_2" }],
},
},
"Expect ruleset_2 to be matched on the script test request"
);
info("Verify DNRStore data persisted on disk for the test extension");
// The data will not be stored on disk until something is being changed
// from what was already available in the manifest and so in this
// test we save manually (a test for the updateEnabledRulesets will
// take care of asserting that the data has been stored automatically
// on disk when it is meant to).
await dnrStore.save(extension.extension);
const { storeFile } = dnrStore.getFilePaths(extUUID);
ok(await IOUtils.exists(storeFile), `DNR storeFile ${storeFile} found`);
// force deleting the data stored in memory to confirm if it being loaded again from
// the files stored on disk.
dnrStore._data.delete(extUUID);
dnrStore._dataPromises.delete(extUUID);
info("Verify the expected DNRStore data persisted on disk is loaded back");
const { AddonManager } = ChromeUtils.importESModule(
);
const addon = await AddonManager.getAddonByID(extension.id);
await addon.disable();
ok(
!dnrStore._dataPromises.has(extUUID),
"DNR store read data promise cleared after the extension has been disabled"
);
ok(
!dnrStore._data.has(extUUID),
"DNR store data cleared from memory after the extension has been disabled"
);
await addon.enable();
await extension.awaitMessage("bgpage:ready");
await assertDNRGetEnabledRulesets(extension, ["ruleset_1", "ruleset_2"]);
await assertDNRStoreData(dnrStore, extension, {
ruleset_1: getSchemaNormalizedRules(extension, ruleset1Data),
ruleset_2: getSchemaNormalizedRules(extension, ruleset2Data),
});
info("Verify matched rules using testMatchOutcome");
await assertDNRTestMatchOutcome(
{
extension,
testRequest: testRequestMainFrame,
expected: {
matchedRules: [{ ruleId: 1, rulesetId: "ruleset_1" }],
},
},
"Expect ruleset_1 to be matched on the main-frame test request"
);
info("Verify enabled static rules updated on addon updates");
await extension.upgrade(
getDNRExtension({
version: "2.0",
rule_resources: [
{
id: "ruleset_1",
enabled: false,
path: "ruleset_1.json",
},
{
id: "ruleset_2",
enabled: true,
path: "ruleset_2.json",
},
],
files: {
"ruleset_2.json": JSON.stringify(ruleset2Data),
},
})
);
await extension.awaitMessage("bgpage:ready");
await assertDNRGetEnabledRulesets(extension, ["ruleset_2"]);
await assertDNRStoreData(dnrStore, extension, {
ruleset_2: getSchemaNormalizedRules(extension, ruleset2Data),
});
info("Verify matched rules using testMatchOutcome");
await assertDNRTestMatchOutcome(
{
extension,
testRequest: testRequestMainFrame,
expected: {
matchedRules: [{ ruleId: 1, rulesetId: "ruleset_2" }],
},
},
"Expect ruleset_2 to be matched on the main-frame test request"
);
info(
"Verify enabled static rules updated on addon updates even if version in the manifest did not change"
);
await extension.upgrade(
getDNRExtension({
rule_resources: [
{
id: "ruleset_1",
enabled: true,
path: "ruleset_1.json",
},
{
id: "ruleset_2",
enabled: false,
path: "ruleset_2.json",
},
],
files: {
"ruleset_1.json": JSON.stringify(ruleset1Data),
},
})
);
await extension.awaitMessage("bgpage:ready");
await assertDNRGetEnabledRulesets(extension, ["ruleset_1"]);
await assertDNRStoreData(dnrStore, extension, {
ruleset_1: getSchemaNormalizedRules(extension, ruleset1Data),
});
info("Verify matched rules using testMatchOutcome");
await assertDNRTestMatchOutcome(
{
extension,
testRequest: testRequestMainFrame,
expected: {
matchedRules: [{ ruleId: 1, rulesetId: "ruleset_1" }],
},
},
"Expect ruleset_2 to be matched on the main-script test request"
);
info(
"Verify updated addon version with no static rules but declarativeNetRequest permission granted"
);
await extension.upgrade(
getDNRExtension({
version: "3.0",
rule_resources: undefined,
files: {},
})
);
await extension.awaitMessage("bgpage:ready");
await assertDNRGetEnabledRulesets(extension, []);
await assertDNRStoreData(dnrStore, extension, {});
info("Verify matched rules using testMatchOutcome");
await assertDNRTestMatchOutcome(
{
extension,
testRequest: testRequestScript,
expected: {
matchedRules: [],
},
},
"Expect no match on the script test request on test extension without no static rules"
);
info("Verify store file removed on addon uninstall");
await extension.unload();
ok(
!dnrStore._dataPromises.has(extUUID),
"DNR store read data promise cleared after the extension has been unloaded"
);
ok(
!dnrStore._data.has(extUUID),
"DNR store data cleared from memory after the extension has been unloaded"
);
ok(
!(await IOUtils.exists(storeFile)),
`DNR storeFile ${storeFile} removed on addon uninstalled`
);
});
add_task(async function test_load_from_corrupted_data() {
const ruleset1Data = [
getDNRRule({
action: { type: "allow" },
condition: { resourceTypes: ["main_frame"] },
}),
];
const rule_resources = [
{
id: "ruleset_1",
enabled: true,
path: "ruleset_1.json",
},
];
const files = {
"ruleset_1.json": JSON.stringify(ruleset1Data),
};
const extension = ExtensionTestUtils.loadExtension(
getDNRExtension({ rule_resources, files })
);
await extension.startup();
const extUUID = extension.uuid;
await extension.awaitMessage("bgpage:ready");
const dnrStore = ExtensionDNRStore._getStoreForTesting();
info("Verify DNRStore data for the test extension");
await assertDNRGetEnabledRulesets(extension, ["ruleset_1"]);
await assertDNRStoreData(dnrStore, extension, {
ruleset_1: getSchemaNormalizedRules(extension, ruleset1Data),
});
info("Verify DNRStore data after loading corrupted store data");
await dnrStore.save(extension.extension);
const { storeFile } = dnrStore.getFilePaths(extUUID);
ok(await IOUtils.exists(storeFile), `DNR storeFile ${storeFile} found`);
const nonCorruptedData = await IOUtils.readJSON(storeFile, {
decompress: true,
});
async function testLoadedRulesAfterDataCorruption({
name,
asyncWriteStoreFile,
expectedCorruptFile,
}) {
info(`Tempering DNR store data: ${name}`);
await extension.addon.disable();
ok(
!dnrStore._dataPromises.has(extUUID),
"DNR store read data promise cleared after the extension has been disabled"
);
ok(
!dnrStore._data.has(extUUID),
"DNR store data cleared from memory after the extension has been disabled"
);
// Make sure we remove a previous corrupt file in case there is one from a previous run.
await IOUtils.remove(expectedCorruptFile, { ignoreAbsent: true });
await asyncWriteStoreFile();
await extension.addon.enable();
await extension.awaitMessage("bgpage:ready");
info("Verify DNRStore data for the test extension");
await assertDNRGetEnabledRulesets(extension, ["ruleset_1"]);
await assertDNRStoreData(dnrStore, extension, {
ruleset_1: getSchemaNormalizedRules(extension, ruleset1Data),
});
await TestUtils.waitForCondition(
() => IOUtils.exists(`${expectedCorruptFile}`),
`Wait for the "${expectedCorruptFile}" file to have been created`
);
}
await testLoadedRulesAfterDataCorruption({
name: "invalid lz4 header",
asyncWriteStoreFile: () =>
IOUtils.writeUTF8(storeFile, "not an lz4 compressed file", {
compress: false,
}),
expectedCorruptFile: `${storeFile}.corrupt`,
});
await testLoadedRulesAfterDataCorruption({
name: "invalid json data",
asyncWriteStoreFile: () =>
IOUtils.writeUTF8(storeFile, "invalid json data", { compress: true }),
expectedCorruptFile: `${storeFile}-1.corrupt`,
});
await testLoadedRulesAfterDataCorruption({
name: "empty json data",
asyncWriteStoreFile: () =>
IOUtils.writeUTF8(storeFile, "{}", { compress: true }),
expectedCorruptFile: `${storeFile}-2.corrupt`,
});
await testLoadedRulesAfterDataCorruption({
name: "invalid staticRulesets property type",
asyncWriteStoreFile: () =>
IOUtils.writeUTF8(
storeFile,
JSON.stringify({
schemaVersion: nonCorruptedData.schemaVersion,
extVersion: extension.extension.version,
staticRulesets: "Not an array",
}),
{ compress: true }
),
expectedCorruptFile: `${storeFile}-3.corrupt`,
});
await extension.unload();
});
add_task(async function test_ruleset_validation() {
const invalidRulesetIdCases = [
{
description: "empty ruleset id",
rule_resources: [
{
// Invalid empty ruleset id.
id: "",
path: "ruleset_0.json",
enabled: true,
},
],
expected: [
// Validation error emitted from the manifest schema validation.
{
message: /rule_resources\.0\.id: String "" must match/,
},
],
},
{
description: "invalid ruleset id starting with '_'",
rule_resources: [
{
// Invalid empty ruleset id.
id: "_invalid_ruleset_id",
path: "ruleset_0.json",
enabled: true,
},
],
expected: [
// Validation error emitted from the manifest schema validation.
{
message:
/rule_resources\.0\.id: String "_invalid_ruleset_id" must match/,
},
],
},
{
description: "duplicated ruleset ids",
rule_resources: [
{
id: "ruleset_2",
path: "ruleset_2.json",
enabled: true,
},
{
// Duplicated ruleset id.
id: "ruleset_2",
path: "duplicated_ruleset_2.json",
enabled: true,
},
{
id: "ruleset_3",
path: "ruleset_3.json",
enabled: true,
},
{
// Other duplicated ruleset id.
id: "ruleset_3",
path: "duplicated_ruleset_3.json",
enabled: true,
},
],
// NOTE: this is currently a warning logged from onManifestEntry, and so it would actually
// fail in test harness due to the manifest warning, because it is too late at that point
// the addon is technically already starting at that point.
expectInstallFailed: false,
expected: [
{
message:
/declarative_net_request: Static ruleset ids should be unique.*: "ruleset_2" at index 1, "ruleset_3" at index 3/,
},
],
},
{
description: "missing mandatory path",
rule_resources: [
{
// Missing mandatory path.
id: "ruleset_3",
enabled: true,
},
],
expected: [
{
message: /rule_resources\.0: Property "path" is required/,
},
],
},
{
description: "missing mandatory id",
rule_resources: [
{
// Missing mandatory id.
enabled: true,
path: "missing_ruleset_id.json",
},
],
expected: [
{
message: /rule_resources\.0: Property "id" is required/,
},
],
},
{
description: "duplicated ruleset path",
rule_resources: [
{
id: "ruleset_2",
path: "ruleset_2.json",
enabled: true,
},
{
// Duplicate path.
id: "ruleset_3",
path: "ruleset_2.json",
enabled: true,
},
],
// NOTE: we couldn't get on agreement about making this a manifest validation error, apparently Chrome doesn't validate it and doesn't
// even report any warning, and so it is logged only as an informative warning but without triggering an install failure.
expectInstallFailed: false,
expected: [
{
message:
/declarative_net_request: Static rulesets paths are not unique.*: ".*ruleset_2.json" at index 1/,
},
],
},
{
description: "missing mandatory enabled",
rule_resources: [
{
id: "ruleset_without_enabled",
path: "ruleset.json",
},
],
expected: [
{
message: /rule_resources\.0: Property "enabled" is required/,
},
],
},
{
description: "allows and warns additional properties",
declarative_net_request: {
unexpected_prop: true,
rule_resources: [
{
id: "ruleset1",
path: "ruleset1.json",
enabled: false,
unexpected_prop: true,
},
],
},
expectInstallFailed: false,
expected: [
{
message:
/declarative_net_request.unexpected_prop: An unexpected property was found/,
},
{
message:
/rule_resources.0.unexpected_prop: An unexpected property was found/,
},
],
},
{
description: "invalid ruleset JSON - unexpected comments",
rule_resources: [
{
id: "invalid_ruleset_with_comments",
path: "invalid_ruleset_with_comments.json",
enabled: true,
},
],
files: {
"invalid_ruleset_with_comments.json":
"/* an unexpected inline comment */\n[]",
},
expectInstallFailed: false,
expected: [
{
message:
/Reading declarative_net_request .*invalid_ruleset_with_comments\.json: JSON.parse: unexpected character/,
},
],
},
{
description: "invalid ruleset JSON - empty string",
rule_resources: [
{
id: "invalid_ruleset_emptystring",
path: "invalid_ruleset_emptystring.json",
enabled: true,
},
],
files: {
"invalid_ruleset_emptystring.json": JSON.stringify(""),
},
expectInstallFailed: false,
expected: [
{
message:
/Reading declarative_net_request .*invalid_ruleset_emptystring\.json: rules file must contain an Array/,
},
],
},
{
description: "invalid ruleset JSON - object",
rule_resources: [
{
id: "invalid_ruleset_object",
path: "invalid_ruleset_object.json",
enabled: true,
},
],
files: {
"invalid_ruleset_object.json": JSON.stringify({}),
},
expectInstallFailed: false,
expected: [
{
message:
/Reading declarative_net_request .*invalid_ruleset_object\.json: rules file must contain an Array/,
},
],
},
{
description: "invalid ruleset JSON - null",
rule_resources: [
{
id: "invalid_ruleset_null",
path: "invalid_ruleset_null.json",
enabled: true,
},
],
files: {
"invalid_ruleset_null.json": JSON.stringify(null),
},
expectInstallFailed: false,
expected: [
{
message:
/Reading declarative_net_request .*invalid_ruleset_null\.json: rules file must contain an Array/,
},
],
},
];
for (const {
description,
declarative_net_request,
rule_resources,
files,
expected,
expectInstallFailed = true,
} of invalidRulesetIdCases) {
info(`Test manifest validation: ${description}`);
let extension = ExtensionTestUtils.loadExtension(
getDNRExtension({ rule_resources, declarative_net_request, files })
);
const { messages } = await AddonTestUtils.promiseConsoleOutput(async () => {
ExtensionTestUtils.failOnSchemaWarnings(false);
if (expectInstallFailed) {
await Assert.rejects(
extension.startup(),
/Install failed/,
"Expected install to fail"
);
} else {
await extension.startup();
await extension.awaitMessage("bgpage:ready");
await extension.unload();
}
ExtensionTestUtils.failOnSchemaWarnings(true);
});
AddonTestUtils.checkMessages(messages, { expected });
}
});
add_task(async function test_updateEnabledRuleset_id_validation() {
const rule_resources = [
{
id: "ruleset_1",
enabled: true,
path: "ruleset_1.json",
},
{
id: "ruleset_2",
enabled: false,
path: "ruleset_2.json",
},
];
const ruleset1Data = [
getDNRRule({
action: { type: "allow" },
condition: { resourceTypes: ["main_frame"] },
}),
];
const ruleset2Data = [
getDNRRule({
action: { type: "block" },
condition: { resourceTypes: ["main_frame", "script"] },
}),
];
const files = {
"ruleset_1.json": JSON.stringify(ruleset1Data),
"ruleset_2.json": JSON.stringify(ruleset2Data),
};
let extension = ExtensionTestUtils.loadExtension(
getDNRExtension({ rule_resources, files })
);
await extension.startup();
await extension.awaitMessage("bgpage:ready");
await assertDNRGetEnabledRulesets(extension, ["ruleset_1"]);
const dnrStore = ExtensionDNRStore._getStoreForTesting();
await assertDNRStoreData(dnrStore, extension, {
ruleset_1: getSchemaNormalizedRules(extension, ruleset1Data),
});
const invalidStaticRulesetIds = [
// The following two are reserved for session and dynamic rules.
"_session",
"_dynamic",
"ruleset_non_existing",
];
for (const invalidRSId of invalidStaticRulesetIds) {
extension.sendMessage(
"updateEnabledRulesets",
// Only in rulesets to be disabled.
{ disableRulesetIds: [invalidRSId] },
// Only in rulesets to be enabled.
{ enableRulesetIds: [invalidRSId] },
// In both rulesets to be enabled and disabled.
{ disableRulesetIds: [invalidRSId], enableRulesetIds: [invalidRSId] },
// Along with existing rulesets (and expected the existing rulesets
// to stay unchanged due to the invalid ruleset ids.)
{
disableRulesetIds: [invalidRSId, "ruleset_1"],
enableRulesetIds: [invalidRSId, "ruleset_2"],
}
);
const [
resInDisable,
resInEnable,
resInEnableAndDisable,
resInSameRequestAsValid,
] = await extension.awaitMessage("updateEnabledRulesets:done");
await Assert.rejects(
Promise.reject(resInDisable?.rejectedWithErrorMessage),
new RegExp(`Invalid ruleset id: "${invalidRSId}"`),
`Got the expected rejection on invalid ruleset id "${invalidRSId}" in disableRulesetIds`
);
await Assert.rejects(
Promise.reject(resInEnable?.rejectedWithErrorMessage),
new RegExp(`Invalid ruleset id: "${invalidRSId}"`),
`Got the expected rejection on invalid ruleset id "${invalidRSId}" in enableRulesetIds`
);
await Assert.rejects(
Promise.reject(resInEnableAndDisable?.rejectedWithErrorMessage),
new RegExp(`Invalid ruleset id: "${invalidRSId}"`),
`Got the expected rejection on invalid ruleset id "${invalidRSId}" in both enable/disableRulesetIds`
);
await Assert.rejects(
Promise.reject(resInSameRequestAsValid?.rejectedWithErrorMessage),
new RegExp(`Invalid ruleset id: "${invalidRSId}"`),
`Got the expected rejection on invalid ruleset id "${invalidRSId}" along with valid ruleset ids`
);
}
// Confirm that the expected rulesets didn't change neither.
await assertDNRGetEnabledRulesets(extension, ["ruleset_1"]);
await assertDNRStoreData(dnrStore, extension, {
ruleset_1: getSchemaNormalizedRules(extension, ruleset1Data),
});
// - List the same ruleset ids more than ones is expected to work and
// to be resulting in the same set of rules being enabled
// - Disabling and Enabling the same ruleset id should result in the
// ruleset being enabled.
await extension.sendMessage("updateEnabledRulesets", {
disableRulesetIds: [
"ruleset_1",
"ruleset_1",
"ruleset_2",
"ruleset_2",
"ruleset_2",
],
enableRulesetIds: ["ruleset_2", "ruleset_2"],
});
Assert.deepEqual(
await extension.awaitMessage("updateEnabledRulesets:done"),
[undefined],
"Expect the updateEnabledRulesets to result successfully"
);
await assertDNRGetEnabledRulesets(extension, ["ruleset_2"]);
await assertDNRStoreData(dnrStore, extension, {
ruleset_2: getSchemaNormalizedRules(extension, ruleset2Data),
});
await extension.unload();
});
add_task(async function test_getAvailableStaticRulesCountAndLimits() {
// 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.setBoolPref("extensions.background.idle.enabled", false);
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_20"/,
},
// 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_20"/,
},
],
});
info(
"Verify updateEnabledRulesets reject when the request is exceeding the enabled rulesets count limit"
);
extension.sendMessage("updateEnabledRulesets", {
disableRulesetIds: ["ruleset_0"],
enableRulesetIds: ["ruleset_20", "ruleset_21"],
});
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_20"],
},
{
disableRulesetIds: ["ruleset_20"],
enableRulesetIds: ["ruleset_21"],
}
);
await extension.awaitMessage("updateEnabledRulesets:done");
// Expect ruleset_0 disabled, ruleset_20 to be enabled but then disabled by the
// second update queued after the first one, and ruleset_21 to be enabled.
delete expectedEnabledRulesets.ruleset_0;
expectedEnabledRulesets.ruleset_21 = 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_21"],
});
await extension.awaitMessage("updateEnabledRulesets:done");
delete expectedEnabledRulesets.ruleset_21;
await assertDNRGetEnabledRulesets(
extension,
Array.from(Object.keys(expectedEnabledRulesets))
);
await assertDNRStoreData(dnrStore, extension, expectedEnabledRulesets);
await extension.unload();
});
add_task(async function test_tabId_conditions_invalid_in_static_rules() {
const ruleset1_with_tabId_condition = [
getDNRRule({ id: 1, condition: { tabIds: [1] } }),
getDNRRule({ id: 3, condition: { urlFilter: "valid-ruleset1-rule" } }),
];
const ruleset2_with_excludeTabId_condition = [
getDNRRule({ id: 2, condition: { excludedTabIds: [1] } }),
getDNRRule({ id: 3, condition: { urlFilter: "valid-ruleset2-rule" } }),
];
const rule_resources = [
{
id: "ruleset1_with_tabId_condition",
enabled: true,
path: "ruleset1.json",
},
{
id: "ruleset2_with_excludeTabId_condition",
enabled: true,
path: "ruleset2.json",
},
];
const files = {
"ruleset1.json": JSON.stringify(ruleset1_with_tabId_condition),
"ruleset2.json": JSON.stringify(ruleset2_with_excludeTabId_condition),
};
const extension = ExtensionTestUtils.loadExtension(
getDNRExtension({
id: "tabId-invalid-in-session-rules@mochitest",
rule_resources,
files,
})
);
const { messages } = await AddonTestUtils.promiseConsoleOutput(async () => {
ExtensionTestUtils.failOnSchemaWarnings(false);
await extension.startup();
ExtensionTestUtils.failOnSchemaWarnings(true);
await extension.awaitMessage("bgpage:ready");
await assertDNRGetEnabledRulesets(extension, [
"ruleset1_with_tabId_condition",
"ruleset2_with_excludeTabId_condition",
]);
});
AddonTestUtils.checkMessages(messages, {
expected: [
{
message:
/"ruleset1_with_tabId_condition": tabIds and excludedTabIds can only be specified in session rules/,
},
{
message:
/"ruleset2_with_excludeTabId_condition": tabIds and excludedTabIds can only be specified in session rules/,
},
],
});
info("Expect the invalid rule to not be enabled");
const dnrStore = ExtensionDNRStore._getStoreForTesting();
// Expect the two valid rules to have been loaded as expected.
await assertDNRStoreData(dnrStore, extension, {
ruleset1_with_tabId_condition: getSchemaNormalizedRules(extension, [
ruleset1_with_tabId_condition[1],
]),
ruleset2_with_excludeTabId_condition: getSchemaNormalizedRules(extension, [
ruleset2_with_excludeTabId_condition[1],
]),
});
await extension.unload();
});
add_task(async function test_dnr_all_rules_disabled_allowed() {
const ruleset1 = [
getDNRRule({ id: 3, condition: { urlFilter: "valid-ruleset1-rule" } }),
];
const rule_resources = [
{
id: "ruleset1",
enabled: true,
path: "ruleset1.json",
},
];
const files = {
"ruleset1.json": JSON.stringify(ruleset1),
};
const extension = ExtensionTestUtils.loadExtension(
getDNRExtension({
id: "all-static-rulesets-disabled-allowed@mochitest",
rule_resources,
files,
})
);
await extension.startup();
await extension.awaitMessage("bgpage:ready");
await assertDNRGetEnabledRulesets(extension, ["ruleset1"]);
const dnrStore = ExtensionDNRStore._getStoreForTesting();
await assertDNRStoreData(dnrStore, extension, {
ruleset1: getSchemaNormalizedRules(extension, ruleset1),
});
info("Disable static ruleset1");
extension.sendMessage("updateEnabledRulesets", {
disableRulesetIds: ["ruleset1"],
});
await extension.awaitMessage("updateEnabledRulesets:done");
await assertDNRGetEnabledRulesets(extension, []);
await assertDNRStoreData(dnrStore, extension, {});
info("Verify that static ruleset1 is still disable after browser restart");
// 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 ExtensionDNR.ensureInitialized(extension.extension);
await extension.awaitMessage("bgpage:ready");
await assertDNRGetEnabledRulesets(extension, []);
await assertDNRStoreData(dnrStore, extension, {});
await extension.unload();
});
add_task(async function test_static_rules_telemetry() {
resetTelemetryData();
const ruleset1 = [
getDNRRule({
id: 1,
action: { type: "block" },
condition: {
resourceTypes: ["xmlhttprequest"],
requestDomains: ["example.com"],
},
}),
];
const ruleset2 = [
getDNRRule({
id: 1,
action: { type: "block" },
condition: {
resourceTypes: ["xmlhttprequest"],
requestDomains: ["example.org"],
},
}),
getDNRRule({
id: 2,
action: { type: "block" },
condition: {
resourceTypes: ["xmlhttprequest"],
requestDomains: ["example2.org"],
},
}),
];
const rule_resources = [
{
id: "ruleset1",
enabled: false,
path: "ruleset1.json",
},
{
id: "ruleset2",
enabled: false,
path: "ruleset2.json",
},
];
const files = {
"ruleset1.json": JSON.stringify(ruleset1),
"ruleset2.json": JSON.stringify(ruleset2),
};
const extension = ExtensionTestUtils.loadExtension(
getDNRExtension({
id: "tabId-invalid-in-session-rules@mochitest",
rule_resources,
files,
})
);
assertDNRTelemetryMetricsNoSamples(
[
{
metric: "validateRulesTime",
mirroredName: "WEBEXT_DNR_VALIDATE_RULES_MS",
mirroredType: "histogram",
},
{
metric: "evaluateRulesTime",
mirroredName: "WEBEXT_DNR_EVALUATE_RULES_MS",
mirroredType: "histogram",
},
],
"before test extension have been loaded"
);
await extension.startup();
await extension.awaitMessage("bgpage:ready");
await assertDNRGetEnabledRulesets(extension, []);
assertDNRTelemetryMetricsNoSamples(
[
{
metric: "validateRulesTime",
mirroredName: "WEBEXT_DNR_VALIDATE_RULES_MS",
mirroredType: "histogram",
},
],
"after test extension loaded with all static rulesets disabled"
);
info("Enable static ruleset1");
extension.sendMessage("updateEnabledRulesets", {
enableRulesetIds: ["ruleset1"],
});
await extension.awaitMessage("updateEnabledRulesets:done");
await assertDNRGetEnabledRulesets(extension, ["ruleset1"]);
// Expect one sample after enabling ruleset1.
let expectedValidateRulesTimeSamples = 1;
assertDNRTelemetryMetricsSamplesCount(
[
{
metric: "validateRulesTime",
mirroredName: "WEBEXT_DNR_VALIDATE_RULES_MS",
mirroredType: "histogram",
expectedSamplesCount: expectedValidateRulesTimeSamples,
},
],
"after enabling static rulesets1"
);
info("Enable static ruleset2");
extension.sendMessage("updateEnabledRulesets", {
enableRulesetIds: ["ruleset2"],
});
await extension.awaitMessage("updateEnabledRulesets:done");
await assertDNRGetEnabledRulesets(extension, ["ruleset1", "ruleset2"]);
// Expect one new sample after enabling ruleset2.
expectedValidateRulesTimeSamples += 1;
assertDNRTelemetryMetricsSamplesCount(
[
{
metric: "validateRulesTime",
mirroredName: "WEBEXT_DNR_VALIDATE_RULES_MS",
mirroredType: "histogram",
expectedSamplesCount: expectedValidateRulesTimeSamples,
},
],
"after enabling static rulesets2"
);
await extension.addon.disable();
assertDNRTelemetryMetricsSamplesCount(
[
{
metric: "validateRulesTime",
mirroredName: "WEBEXT_DNR_VALIDATE_RULES_MS",
mirroredType: "histogram",
expectedSamplesCount: expectedValidateRulesTimeSamples,
},
],
"no new samples expected after disabling test extension"
);
await extension.addon.enable();
await extension.awaitMessage("bgpage:ready");
await ExtensionDNR.ensureInitialized(extension.extension);
// Expect 2 new samples after re-enabling the addon with
// the 2 rulesets enabled being loaded from the DNR store file.
expectedValidateRulesTimeSamples += 2;
assertDNRTelemetryMetricsSamplesCount(
[
{
metric: "validateRulesTime",
mirroredName: "WEBEXT_DNR_VALIDATE_RULES_MS",
mirroredType: "histogram",
expectedSamplesCount: expectedValidateRulesTimeSamples,
},
],
"after re-enabling test extension"
);
info("Disable static ruleset1");
extension.sendMessage("updateEnabledRulesets", {
disableRulesetIds: ["ruleset1"],
});
await extension.awaitMessage("updateEnabledRulesets:done");
await assertDNRGetEnabledRulesets(extension, ["ruleset2"]);
assertDNRTelemetryMetricsSamplesCount(
[
{
metric: "validateRulesTime",
mirroredName: "WEBEXT_DNR_VALIDATE_RULES_MS",
mirroredType: "histogram",
expectedSamplesCount: expectedValidateRulesTimeSamples,
},
],
"no new validation should be hit after disabling ruleset1"
);
info("Verify telemetry recorded on rules evaluation");
extension.sendMessage("updateEnabledRulesets", {
enableRulesetIds: ["ruleset1"],
disableRulesetIds: ["ruleset2"],
});
await extension.awaitMessage("updateEnabledRulesets:done");
await assertDNRGetEnabledRulesets(extension, ["ruleset1"]);
assertDNRTelemetryMetricsNoSamples(
[
{
metric: "evaluateRulesTime",
mirroredName: "WEBEXT_DNR_EVALUATE_RULES_MS",
mirroredType: "histogram",
},
{
metric: "evaluateRulesCountMax",
mirroredName: "extensions.apis.dnr.evaluate_rules_count_max",
mirroredType: "scalar",
},
],
"before any request have been intercepted"
);
Assert.equal(
await fetch("http://example.com/").then(res => res.text()),
"response from server",
"DNR should not block system requests"
);
assertDNRTelemetryMetricsNoSamples(
[
{
metric: "evaluateRulesTime",
mirroredName: "WEBEXT_DNR_EVALUATE_RULES_MS",
mirroredType: "histogram",
},
{
metric: "evaluateRulesCountMax",
mirroredName: "extensions.apis.dnr.evaluate_rules_count_max",
mirroredType: "scalar",
},
],
"after restricted request have been intercepted (but no rules evaluated)"
);
const page = await ExtensionTestUtils.loadContentPage("http://example.com");
const callPageFetch = async () => {
Assert.equal(
await page.spawn([], () => {
return this.content.fetch("http://example.com/").then(
res => res.text(),
err => err.message
);
}),
"NetworkError when attempting to fetch resource.",
"DNR should have blocked test request to example.com"
);
};
// Expect one sample recorded on evaluating rules for the
// top level navigation.
let expectedEvaluateRulesTimeSamples = 1;
assertDNRTelemetryMetricsSamplesCount(
[
{
metric: "evaluateRulesTime",
mirroredName: "WEBEXT_DNR_EVALUATE_RULES_MS",
mirroredType: "histogram",
expectedSamplesCount: expectedEvaluateRulesTimeSamples,
},
],
"evaluateRulesTime should be collected after evaluated rulesets"
);
// Expect same number of rules included in the single ruleset
// currently enabled.
let expectedEvaluateRulesCountMax = ruleset1.length;
assertDNRTelemetryMetricsGetValueEq(
[
{
metric: "evaluateRulesCountMax",
mirroredName: "extensions.apis.dnr.evaluate_rules_count_max",
mirroredType: "scalar",
expectedGetValue: expectedEvaluateRulesCountMax,
},
],
"evaluateRulesCountMax should be collected after evaluated rulesets1"
);
await callPageFetch();
// Expect one new sample reported on evaluating rules for the
// first fetch request originated from the test page.
expectedEvaluateRulesTimeSamples += 1;
assertDNRTelemetryMetricsSamplesCount(
[
{
metric: "evaluateRulesTime",
mirroredName: "WEBEXT_DNR_EVALUATE_RULES_MS",
mirroredType: "histogram",
expectedSamplesCount: expectedEvaluateRulesTimeSamples,
},
],
"evaluateRulesTime should be collected after evaluated rulesets"
);
extension.sendMessage("updateEnabledRulesets", {
enableRulesetIds: ["ruleset2"],
});
await extension.awaitMessage("updateEnabledRulesets:done");
await assertDNRGetEnabledRulesets(extension, ["ruleset1", "ruleset2"]);
await callPageFetch();
// Expect 3 rules with both rulesets enabled
// (1 from ruleset1 and 2 more from ruleset2).
expectedEvaluateRulesCountMax += ruleset2.length;
assertDNRTelemetryMetricsGetValueEq(
[
{
metric: "evaluateRulesCountMax",
mirroredName: "extensions.apis.dnr.evaluate_rules_count_max",
mirroredType: "scalar",
expectedGetValue: expectedEvaluateRulesCountMax,
},
],
"evaluateRulesCountMax should have been increased after enabling ruleset2"
);
extension.sendMessage("updateEnabledRulesets", {
disableRulesetIds: ["ruleset2"],
});
await extension.awaitMessage("updateEnabledRulesets:done");
await assertDNRGetEnabledRulesets(extension, ["ruleset1"]);
await callPageFetch();
assertDNRTelemetryMetricsGetValueEq(
[
{
metric: "evaluateRulesCountMax",
mirroredName: "extensions.apis.dnr.evaluate_rules_count_max",
mirroredType: "scalar",
expectedGetValue: expectedEvaluateRulesCountMax,
},
],
"evaluateRulesCountMax should have not been decreased after disabling ruleset2"
);
await page.close();
await extension.unload();
});