diff options
Diffstat (limited to 'toolkit/components/extensions/parent/ext-declarativeNetRequest.js')
-rw-r--r-- | toolkit/components/extensions/parent/ext-declarativeNetRequest.js | 169 |
1 files changed, 169 insertions, 0 deletions
diff --git a/toolkit/components/extensions/parent/ext-declarativeNetRequest.js b/toolkit/components/extensions/parent/ext-declarativeNetRequest.js new file mode 100644 index 0000000000..766a43d98a --- /dev/null +++ b/toolkit/components/extensions/parent/ext-declarativeNetRequest.js @@ -0,0 +1,169 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +ChromeUtils.defineESModuleGetters(this, { + ExtensionDNR: "resource://gre/modules/ExtensionDNR.sys.mjs", +}); + +var { ExtensionError } = ExtensionUtils; + +const PREF_DNR_FEEDBACK = "extensions.dnr.feedback"; +XPCOMUtils.defineLazyPreferenceGetter( + this, + "dnrFeedbackEnabled", + PREF_DNR_FEEDBACK, + false +); + +function ensureDNRFeedbackEnabled(apiName) { + if (!dnrFeedbackEnabled) { + throw new ExtensionError( + `${apiName} is only available when the "${PREF_DNR_FEEDBACK}" preference is set to true.` + ); + } +} + +this.declarativeNetRequest = class extends ExtensionAPI { + onManifestEntry(entryName) { + if (entryName === "declarative_net_request") { + ExtensionDNR.validateManifestEntry(this.extension); + } + } + + onShutdown() { + ExtensionDNR.clearRuleManager(this.extension); + } + + getAPI(context) { + const { extension } = this; + + return { + declarativeNetRequest: { + updateDynamicRules({ removeRuleIds, addRules }) { + return ExtensionDNR.updateDynamicRules(extension, { + removeRuleIds, + addRules, + }); + }, + + updateSessionRules({ removeRuleIds, addRules }) { + const ruleManager = ExtensionDNR.getRuleManager(extension); + let ruleValidator = new ExtensionDNR.RuleValidator( + ruleManager.getSessionRules(), + { isSessionRuleset: true } + ); + if (removeRuleIds) { + ruleValidator.removeRuleIds(removeRuleIds); + } + if (addRules) { + ruleValidator.addRules(addRules); + } + let failures = ruleValidator.getFailures(); + if (failures.length) { + throw new ExtensionError(failures[0].message); + } + let validatedRules = ruleValidator.getValidatedRules(); + let ruleQuotaCounter = new ExtensionDNR.RuleQuotaCounter(); + ruleQuotaCounter.tryAddRules("_session", validatedRules); + ruleManager.setSessionRules(validatedRules); + }, + + async getEnabledRulesets() { + await ExtensionDNR.ensureInitialized(extension); + const ruleManager = ExtensionDNR.getRuleManager(extension); + 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, + enableRulesetIds, + }); + }, + + async getDynamicRules() { + await ExtensionDNR.ensureInitialized(extension); + return ExtensionDNR.getRuleManager(extension).getDynamicRules(); + }, + + getSessionRules() { + // ruleManager.getSessionRules() returns an array of Rule instances. + // When these are structurally cloned (to send them to the child), + // the enumerable public fields of the class instances are copied to + // plain objects, as desired. + return ExtensionDNR.getRuleManager(extension).getSessionRules(); + }, + + isRegexSupported(regexOptions) { + const { + regex: regexFilter, + isCaseSensitive: isUrlFilterCaseSensitive, + // requireCapturing: is ignored, as it does not affect validation. + } = regexOptions; + + let ruleValidator = new ExtensionDNR.RuleValidator([]); + ruleValidator.addRules([ + { + id: 1, + condition: { regexFilter, isUrlFilterCaseSensitive }, + action: { type: "allow" }, + }, + ]); + let failures = ruleValidator.getFailures(); + if (failures.length) { + // While the UnsupportedRegexReason enum has more entries than just + // "syntaxError" (e.g. also "memoryLimitExceeded"), our validation + // is currently very permissive, and therefore the only + // distinguishable error is "syntaxError". + return { isSupported: false, reason: "syntaxError" }; + } + return { isSupported: true }; + }, + + async testMatchOutcome(request, options) { + ensureDNRFeedbackEnabled("declarativeNetRequest.testMatchOutcome"); + let { url, initiator, ...req } = request; + req.requestURI = Services.io.newURI(url); + if (initiator) { + req.initiatorURI = Services.io.newURI(initiator); + if (req.initiatorURI.schemeIs("data")) { + // data:-URIs are always opaque, i.e. a null principal. We should + // therefore ignore them here. + // ExtensionDNR's NetworkIntegration.startDNREvaluation does not + // encounter data:-URIs because opaque principals are mapped to a + // null initiatorURI. For consistency, we do the same here. + req.initiatorURI = null; + } + } + const matchedRules = ExtensionDNR.getMatchedRulesForRequest( + req, + options?.includeOtherExtensions ? null : extension + ).map(matchedRule => { + // Converts an internal MatchedRule instance to an object described + // by the "MatchedRule" type in declarative_net_request.json. + const result = { + ruleId: matchedRule.rule.id, + rulesetId: matchedRule.ruleset.id, + }; + if (matchedRule.ruleManager.extension !== extension) { + result.extensionId = matchedRule.ruleManager.extension.id; + } + return result; + }); + return { matchedRules }; + }, + }, + }; + } +}; |