Loading toolkit/components/extensions/ExtensionDNR.sys.mjs +11 −0 Original line number Diff line number Diff line Loading @@ -1140,6 +1140,17 @@ class RuleManager { this.hasRulesWithTabIds = false; } get availableStaticRuleCount() { return Math.max( GUARANTEED_MINIMUM_STATIC_RULES - this.enabledStaticRules.reduce( (acc, ruleset) => acc + ruleset.rules.length, 0 ), 0 ); } get enabledStaticRulesetIds() { return this.enabledStaticRules.map(ruleset => ruleset.id); } Loading toolkit/components/extensions/ExtensionDNRStore.sys.mjs +74 −12 Original line number Diff line number Diff line Loading @@ -246,6 +246,25 @@ class RulesetsStore { return data?.staticRulesets; } async getAvailableStaticRuleCount(extension) { const { GUARANTEED_MINIMUM_STATIC_RULES } = lazy.ExtensionDNR.limits; const ruleResources = extension.manifest.declarative_net_request?.rule_resources; // TODO: return maximum rules count when no static rules is listed in the manifest? if (!Array.isArray(ruleResources)) { return GUARANTEED_MINIMUM_STATIC_RULES; } const enabledRulesets = await this.getEnabledStaticRulesets(extension); const enabledRulesCount = Array.from(enabledRulesets.values()).reduce( (acc, ruleset) => acc + ruleset.rules.length, 0 ); return GUARANTEED_MINIMUM_STATIC_RULES - enabledRulesCount; } /** * Update the enabled rulesets, queue changes to prevent races between calls * that may be triggered while an update is still in process. Loading Loading @@ -428,7 +447,15 @@ class RulesetsStore { * API method). * @returns {Promise<Map<ruleset_id, object>> | void} */ async #getManifestStaticRulesets(extension, enabledRulesetIds = null) { async #getManifestStaticRulesets( extension, { enabledRulesetIds = null, availableStaticRuleCount = lazy.ExtensionDNR.limits .GUARANTEED_MINIMUM_STATIC_RULES, isUpdateEnabledRulesets = false, } = {} ) { const ruleResources = extension.manifest.declarative_net_request?.rule_resources; if (!Array.isArray(ruleResources)) { Loading Loading @@ -554,11 +581,31 @@ class RulesetsStore { ); } const ruleset = { idx, rules: ruleValidator.getValidatedRules(), }; rulesets.set(id, ruleset); const validatedRules = ruleValidator.getValidatedRules(); // NOTE: this is currently only accounting for valid rules because // only the valid rules will be actually be loaded. Reconsider if // we should instead also account for the rules that have been // ignored as invalid. if (availableStaticRuleCount - validatedRules.length < 0) { if (isUpdateEnabledRulesets) { throw new ExtensionError( "updateEnabledRulesets request is exceeding the available static rule count" ); } // TODO(Bug 1803363): consider collect telemetry. Cu.reportError( `Ignoring static ruleset exceeding the available static rule count: ruleset_id "${id}" (extension: "${extension.id}")` ); // TODO: currently ignoring the current ruleset but would load the one that follows if it // fits in the available rule count when loading the rule on extension startup, // should it stop loading additional rules instead? continue; } availableStaticRuleCount -= validatedRules.length; rulesets.set(id, { idx, rules: validatedRules }); } return rulesets; Loading Loading @@ -645,7 +692,11 @@ class RulesetsStore { // Only load the rules from rulesets that are enabled in the stored DNR data, // if the array (eventually empty) of the enabled static rules isn't in the // stored data, then load all the ones enabled in the manifest. Array.isArray(data.staticRulesets) ? data.staticRulesets : null { enabledRulesetIds: Array.isArray(data.staticRulesets) ? data.staticRulesets : null, } ); return new StoreData(data); } catch (e) { Loading Loading @@ -740,7 +791,10 @@ class RulesetsStore { } } const { MAX_NUMBER_OF_ENABLED_STATIC_RULESETS } = lazy.ExtensionDNR.limits; const { MAX_NUMBER_OF_ENABLED_STATIC_RULESETS, GUARANTEED_MINIMUM_STATIC_RULES, } = lazy.ExtensionDNR.limits; const maxNewRulesetsCount = MAX_NUMBER_OF_ENABLED_STATIC_RULESETS - updatedEnabledRulesets.size; Loading @@ -752,11 +806,19 @@ class RulesetsStore { ); } const newRulesets = await this.#getManifestStaticRulesets( extension, Array.from(enableIds) const availableStaticRuleCount = GUARANTEED_MINIMUM_STATIC_RULES - Array.from(updatedEnabledRulesets.values()).reduce( (acc, ruleset) => acc + ruleset.rules.length, 0 ); const newRulesets = await this.#getManifestStaticRulesets(extension, { enabledRulesetIds: Array.from(enableIds), availableStaticRuleCount, isUpdateEnabledRulesets: true, }); for (const [rulesetId, ruleset] of newRulesets.entries()) { updatedEnabledRulesets.set(rulesetId, ruleset); } Loading toolkit/components/extensions/parent/ext-declarativeNetRequest.js +6 −0 Original line number Diff line number Diff line Loading @@ -52,6 +52,12 @@ this.declarativeNetRequest = class extends ExtensionAPI { return ruleManager.enabledStaticRulesetIds; }, async getAvailableStaticRuleCount() { await ExtensionDNR.ensureInitialized(extension); const ruleManager = ExtensionDNR.getRuleManager(extension); return ruleManager.availableStaticRuleCount; }, updateEnabledRulesets({ disableRulesetIds, enableRulesetIds }) { return ExtensionDNR.updateEnabledStaticRulesets(extension, { disableRulesetIds, Loading toolkit/components/extensions/schemas/declarative_net_request.json +18 −0 Original line number Diff line number Diff line Loading @@ -506,6 +506,24 @@ } ] }, { "name": "getAvailableStaticRuleCount", "type": "function", "description": "Returns the remaining number of static rules an extension can enable", "async": "callback", "parameters": [ { "name": "callback", "type": "function", "parameters": [ { "name": "count", "type": "integer" } ] } ] }, { "name": "getSessionRules", "type": "function", Loading toolkit/components/extensions/test/xpcshell/test_ext_dnr_static_rules.js +283 −11 Original line number Diff line number Diff line Loading @@ -18,6 +18,9 @@ function backgroundWithDNRAPICallHandlers() { case "getEnabledRulesets": result = await browser.declarativeNetRequest.getEnabledRulesets(); break; case "getAvailableStaticRuleCount": result = await browser.declarativeNetRequest.getAvailableStaticRuleCount(); break; case "testMatchOutcome": result = await browser.declarativeNetRequest .testMatchOutcome(...args) Loading Loading @@ -155,6 +158,20 @@ const assertDNRTestMatchOutcome = async ( ); }; 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 Loading @@ -170,7 +187,8 @@ const assertDNRGetEnabledRulesets = async ( const assertDNRStoreData = async ( dnrStore, extensionTestWrapper, expectedRulesets expectedRulesets, { assertIndividualRules = true } = {} ) => { const extUUID = extensionTestWrapper.uuid; const rule_resources = Loading Loading @@ -213,16 +231,65 @@ const assertDNRStoreData = async ( ); for (const rulesetId of expectedRulesetIds) { Assert.deepEqual( dnrExtData.staticRulesets.get(rulesetId), { idx: expectedRulesetIndexesMap.get(rulesetId), rules: getSchemaNormalizedRules( const expectedRulesetIdx = expectedRulesetIndexesMap.get(rulesetId); const expectedRulesetRules = getSchemaNormalizedRules( extensionTestWrapper, expectedRulesets[rulesetId] ), }, `Got the expected rules for the enabled ruleset ${rulesetId}` ); const actualData = dnrExtData.staticRulesets.get(rulesetId); equal( actualData.idx, expectedRulesetIdx, `Got the expected ruleset index for ruleset id ${rulesetId}` ); // Asserting an entire array of rules all at once will produce // a big enough output to don't be immediately useful to investigate // failures, asserting each rule individually would produce more // readable assertion failure logs. const assertRuleAtIdx = ruleIdx => Assert.deepEqual( actualData.rules[ruleIdx], expectedRulesetRules[ruleIdx], `Got the expected rule at index ${ruleIdx} for ruleset id "${rulesetId}"` ); // Some tests may be using a big enough number of rules that // the assertiongs would be producing a huge amount of log spam, // and so for those tests we only explicitly assert the first // and last rule and that the total amount of rules matches the // expected number of rules (there are still other tests explicitly // asserting all loaded rules). if (assertIndividualRules) { info( `Verify the each individual rule loaded for ruleset id "${rulesetId}"` ); for (let ruleIdx = 0; ruleIdx < expectedRulesetRules.length; ruleIdx++) { assertRuleAtIdx(ruleIdx); } } else { // NOTE: Only asserting the first and last rule also helps to speed up // the test is some slower builds when the number of expected rules is // big enough (e.g. the test task verifying the enforced rule count limits // was timing out in tsan build because asserting all indidual rules was // taking long enough and the event page was being suspended on the idle // timeout by the time we did run all these assertion and proceeding with // the rest of the test task assertions), we still confirm that the total // number of expected vs actual rules also matches right after these // assertions. info( `Verify the first and last rules loaded for ruleset id "${rulesetId}"` ); const lastExpectedRuleIdx = expectedRulesetRules.length - 1; for (const ruleIdx of [0, lastExpectedRuleIdx]) { assertRuleAtIdx(ruleIdx); } } equal( actualData.rules.length, expectedRulesetRules.length, `Got the expected number of rules loaded for ruleset id "${rulesetId}"` ); } }; Loading Loading @@ -828,6 +895,211 @@ add_task(async function test_updateEnabledRuleset_id_validation() { await extension.unload(); }); add_task(async function test_getAvailableStaticRulesCountAndLimits() { const dnrStore = ExtensionDNRStore._getStoreForTesting(); const { GUARANTEED_MINIMUM_STATIC_RULES } = ExtensionDNR.limits; 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"); 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. extension.sendMessage("updateEnabledRulesets", { enableRulesetIds: ["ruleset_1"], }); await extension.awaitMessage("updateEnabledRulesets:done"); 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" ); extension.sendMessage("updateEnabledRulesets", { disableRulesetIds: ["ruleset_0"], enableRulesetIds: ["ruleset_1"], }); await extension.awaitMessage("updateEnabledRulesets:done"); 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" ); extension.sendMessage("updateEnabledRulesets", { enableRulesetIds: ["ruleset_disabled"], }); await extension.awaitMessage("updateEnabledRulesets:done"); 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"); extension.sendMessage("updateEnabledRulesets", { enableRulesetIds: ["ruleset_empty"], }); await extension.awaitMessage("updateEnabledRulesets:done"); 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"); extension.sendMessage("updateEnabledRulesets", { disableRulesetIds: ["ruleset_1", "ruleset_empty"], enableRulesetIds: ["ruleset_withInvalid"], }); await extension.awaitMessage("updateEnabledRulesets:done"); 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(); }); add_task(async function test_static_rulesets_limits() { const dnrStore = ExtensionDNRStore._getStoreForTesting(); Loading Loading
toolkit/components/extensions/ExtensionDNR.sys.mjs +11 −0 Original line number Diff line number Diff line Loading @@ -1140,6 +1140,17 @@ class RuleManager { this.hasRulesWithTabIds = false; } get availableStaticRuleCount() { return Math.max( GUARANTEED_MINIMUM_STATIC_RULES - this.enabledStaticRules.reduce( (acc, ruleset) => acc + ruleset.rules.length, 0 ), 0 ); } get enabledStaticRulesetIds() { return this.enabledStaticRules.map(ruleset => ruleset.id); } Loading
toolkit/components/extensions/ExtensionDNRStore.sys.mjs +74 −12 Original line number Diff line number Diff line Loading @@ -246,6 +246,25 @@ class RulesetsStore { return data?.staticRulesets; } async getAvailableStaticRuleCount(extension) { const { GUARANTEED_MINIMUM_STATIC_RULES } = lazy.ExtensionDNR.limits; const ruleResources = extension.manifest.declarative_net_request?.rule_resources; // TODO: return maximum rules count when no static rules is listed in the manifest? if (!Array.isArray(ruleResources)) { return GUARANTEED_MINIMUM_STATIC_RULES; } const enabledRulesets = await this.getEnabledStaticRulesets(extension); const enabledRulesCount = Array.from(enabledRulesets.values()).reduce( (acc, ruleset) => acc + ruleset.rules.length, 0 ); return GUARANTEED_MINIMUM_STATIC_RULES - enabledRulesCount; } /** * Update the enabled rulesets, queue changes to prevent races between calls * that may be triggered while an update is still in process. Loading Loading @@ -428,7 +447,15 @@ class RulesetsStore { * API method). * @returns {Promise<Map<ruleset_id, object>> | void} */ async #getManifestStaticRulesets(extension, enabledRulesetIds = null) { async #getManifestStaticRulesets( extension, { enabledRulesetIds = null, availableStaticRuleCount = lazy.ExtensionDNR.limits .GUARANTEED_MINIMUM_STATIC_RULES, isUpdateEnabledRulesets = false, } = {} ) { const ruleResources = extension.manifest.declarative_net_request?.rule_resources; if (!Array.isArray(ruleResources)) { Loading Loading @@ -554,11 +581,31 @@ class RulesetsStore { ); } const ruleset = { idx, rules: ruleValidator.getValidatedRules(), }; rulesets.set(id, ruleset); const validatedRules = ruleValidator.getValidatedRules(); // NOTE: this is currently only accounting for valid rules because // only the valid rules will be actually be loaded. Reconsider if // we should instead also account for the rules that have been // ignored as invalid. if (availableStaticRuleCount - validatedRules.length < 0) { if (isUpdateEnabledRulesets) { throw new ExtensionError( "updateEnabledRulesets request is exceeding the available static rule count" ); } // TODO(Bug 1803363): consider collect telemetry. Cu.reportError( `Ignoring static ruleset exceeding the available static rule count: ruleset_id "${id}" (extension: "${extension.id}")` ); // TODO: currently ignoring the current ruleset but would load the one that follows if it // fits in the available rule count when loading the rule on extension startup, // should it stop loading additional rules instead? continue; } availableStaticRuleCount -= validatedRules.length; rulesets.set(id, { idx, rules: validatedRules }); } return rulesets; Loading Loading @@ -645,7 +692,11 @@ class RulesetsStore { // Only load the rules from rulesets that are enabled in the stored DNR data, // if the array (eventually empty) of the enabled static rules isn't in the // stored data, then load all the ones enabled in the manifest. Array.isArray(data.staticRulesets) ? data.staticRulesets : null { enabledRulesetIds: Array.isArray(data.staticRulesets) ? data.staticRulesets : null, } ); return new StoreData(data); } catch (e) { Loading Loading @@ -740,7 +791,10 @@ class RulesetsStore { } } const { MAX_NUMBER_OF_ENABLED_STATIC_RULESETS } = lazy.ExtensionDNR.limits; const { MAX_NUMBER_OF_ENABLED_STATIC_RULESETS, GUARANTEED_MINIMUM_STATIC_RULES, } = lazy.ExtensionDNR.limits; const maxNewRulesetsCount = MAX_NUMBER_OF_ENABLED_STATIC_RULESETS - updatedEnabledRulesets.size; Loading @@ -752,11 +806,19 @@ class RulesetsStore { ); } const newRulesets = await this.#getManifestStaticRulesets( extension, Array.from(enableIds) const availableStaticRuleCount = GUARANTEED_MINIMUM_STATIC_RULES - Array.from(updatedEnabledRulesets.values()).reduce( (acc, ruleset) => acc + ruleset.rules.length, 0 ); const newRulesets = await this.#getManifestStaticRulesets(extension, { enabledRulesetIds: Array.from(enableIds), availableStaticRuleCount, isUpdateEnabledRulesets: true, }); for (const [rulesetId, ruleset] of newRulesets.entries()) { updatedEnabledRulesets.set(rulesetId, ruleset); } Loading
toolkit/components/extensions/parent/ext-declarativeNetRequest.js +6 −0 Original line number Diff line number Diff line Loading @@ -52,6 +52,12 @@ this.declarativeNetRequest = class extends ExtensionAPI { return ruleManager.enabledStaticRulesetIds; }, async getAvailableStaticRuleCount() { await ExtensionDNR.ensureInitialized(extension); const ruleManager = ExtensionDNR.getRuleManager(extension); return ruleManager.availableStaticRuleCount; }, updateEnabledRulesets({ disableRulesetIds, enableRulesetIds }) { return ExtensionDNR.updateEnabledStaticRulesets(extension, { disableRulesetIds, Loading
toolkit/components/extensions/schemas/declarative_net_request.json +18 −0 Original line number Diff line number Diff line Loading @@ -506,6 +506,24 @@ } ] }, { "name": "getAvailableStaticRuleCount", "type": "function", "description": "Returns the remaining number of static rules an extension can enable", "async": "callback", "parameters": [ { "name": "callback", "type": "function", "parameters": [ { "name": "count", "type": "integer" } ] } ] }, { "name": "getSessionRules", "type": "function", Loading
toolkit/components/extensions/test/xpcshell/test_ext_dnr_static_rules.js +283 −11 Original line number Diff line number Diff line Loading @@ -18,6 +18,9 @@ function backgroundWithDNRAPICallHandlers() { case "getEnabledRulesets": result = await browser.declarativeNetRequest.getEnabledRulesets(); break; case "getAvailableStaticRuleCount": result = await browser.declarativeNetRequest.getAvailableStaticRuleCount(); break; case "testMatchOutcome": result = await browser.declarativeNetRequest .testMatchOutcome(...args) Loading Loading @@ -155,6 +158,20 @@ const assertDNRTestMatchOutcome = async ( ); }; 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 Loading @@ -170,7 +187,8 @@ const assertDNRGetEnabledRulesets = async ( const assertDNRStoreData = async ( dnrStore, extensionTestWrapper, expectedRulesets expectedRulesets, { assertIndividualRules = true } = {} ) => { const extUUID = extensionTestWrapper.uuid; const rule_resources = Loading Loading @@ -213,16 +231,65 @@ const assertDNRStoreData = async ( ); for (const rulesetId of expectedRulesetIds) { Assert.deepEqual( dnrExtData.staticRulesets.get(rulesetId), { idx: expectedRulesetIndexesMap.get(rulesetId), rules: getSchemaNormalizedRules( const expectedRulesetIdx = expectedRulesetIndexesMap.get(rulesetId); const expectedRulesetRules = getSchemaNormalizedRules( extensionTestWrapper, expectedRulesets[rulesetId] ), }, `Got the expected rules for the enabled ruleset ${rulesetId}` ); const actualData = dnrExtData.staticRulesets.get(rulesetId); equal( actualData.idx, expectedRulesetIdx, `Got the expected ruleset index for ruleset id ${rulesetId}` ); // Asserting an entire array of rules all at once will produce // a big enough output to don't be immediately useful to investigate // failures, asserting each rule individually would produce more // readable assertion failure logs. const assertRuleAtIdx = ruleIdx => Assert.deepEqual( actualData.rules[ruleIdx], expectedRulesetRules[ruleIdx], `Got the expected rule at index ${ruleIdx} for ruleset id "${rulesetId}"` ); // Some tests may be using a big enough number of rules that // the assertiongs would be producing a huge amount of log spam, // and so for those tests we only explicitly assert the first // and last rule and that the total amount of rules matches the // expected number of rules (there are still other tests explicitly // asserting all loaded rules). if (assertIndividualRules) { info( `Verify the each individual rule loaded for ruleset id "${rulesetId}"` ); for (let ruleIdx = 0; ruleIdx < expectedRulesetRules.length; ruleIdx++) { assertRuleAtIdx(ruleIdx); } } else { // NOTE: Only asserting the first and last rule also helps to speed up // the test is some slower builds when the number of expected rules is // big enough (e.g. the test task verifying the enforced rule count limits // was timing out in tsan build because asserting all indidual rules was // taking long enough and the event page was being suspended on the idle // timeout by the time we did run all these assertion and proceeding with // the rest of the test task assertions), we still confirm that the total // number of expected vs actual rules also matches right after these // assertions. info( `Verify the first and last rules loaded for ruleset id "${rulesetId}"` ); const lastExpectedRuleIdx = expectedRulesetRules.length - 1; for (const ruleIdx of [0, lastExpectedRuleIdx]) { assertRuleAtIdx(ruleIdx); } } equal( actualData.rules.length, expectedRulesetRules.length, `Got the expected number of rules loaded for ruleset id "${rulesetId}"` ); } }; Loading Loading @@ -828,6 +895,211 @@ add_task(async function test_updateEnabledRuleset_id_validation() { await extension.unload(); }); add_task(async function test_getAvailableStaticRulesCountAndLimits() { const dnrStore = ExtensionDNRStore._getStoreForTesting(); const { GUARANTEED_MINIMUM_STATIC_RULES } = ExtensionDNR.limits; 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"); 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. extension.sendMessage("updateEnabledRulesets", { enableRulesetIds: ["ruleset_1"], }); await extension.awaitMessage("updateEnabledRulesets:done"); 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" ); extension.sendMessage("updateEnabledRulesets", { disableRulesetIds: ["ruleset_0"], enableRulesetIds: ["ruleset_1"], }); await extension.awaitMessage("updateEnabledRulesets:done"); 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" ); extension.sendMessage("updateEnabledRulesets", { enableRulesetIds: ["ruleset_disabled"], }); await extension.awaitMessage("updateEnabledRulesets:done"); 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"); extension.sendMessage("updateEnabledRulesets", { enableRulesetIds: ["ruleset_empty"], }); await extension.awaitMessage("updateEnabledRulesets:done"); 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"); extension.sendMessage("updateEnabledRulesets", { disableRulesetIds: ["ruleset_1", "ruleset_empty"], enableRulesetIds: ["ruleset_withInvalid"], }); await extension.awaitMessage("updateEnabledRulesets:done"); 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(); }); add_task(async function test_static_rulesets_limits() { const dnrStore = ExtensionDNRStore._getStoreForTesting(); Loading