Source code
Revision control
Copy as Markdown
Other Tools
/* 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
import {
IPPInfrastructureRuleProvider,
IPPPermissionRuleProvider,
IPPPrincipalRules,
IPPProxyableRuleProvider,
MatchPatternPrefRule,
} from "moz-src:///toolkit/components/ipprotection/IPPSiteRuleProviders.sys.mjs";
export { IPPPrincipalRules };
const lazy = {};
ChromeUtils.defineLazyGetter(lazy, "logConsole", () =>
console.createInstance({
prefix: "IPP_SiteRuleManager",
maxLogLevel: Services.prefs.getBoolPref("browser.ipProtection.log", false)
? "Debug"
: "Warn",
})
);
/**
* Resolves a principal to a rule by asking an ordered list of providers.
*
* The array order is the precedence: the first provider with an opinion wins,
* and the ones after it are never consulted.
*/
export class SiteRuleManager extends EventTarget {
#providers;
#inited = false;
#onProviderChange;
/**
* @param {SiteRuleProvider[]} providers
* Ordered most-important first.
*/
constructor(providers) {
super();
this.#providers = providers;
// The event carries no payload. Which provider changed does not map to
// which principals changed rule: precedence means one provider's update
// can start or stop masking another's.
this.#onProviderChange = () => {
this.dispatchEvent(new CustomEvent("SiteRuleManager:RuleChanged"));
};
}
init() {
if (this.#inited) {
return;
}
// Set before initing the providers: a provider may notify from within
// init(), and re-entering here would double-register the listeners.
this.#inited = true;
for (const provider of this.#providers) {
provider.addEventListener("change", this.#onProviderChange);
try {
provider.init();
} catch (error) {
provider.uninit();
lazy.logConsole.error(
`${provider.constructor.name} failed to init:`,
error
);
Glean.ipprotection.error.record({
source: `SiteRuleManager:${provider.constructor.name}`,
});
}
}
}
uninit() {
if (!this.#inited) {
return;
}
for (const provider of this.#providers) {
provider.removeEventListener("change", this.#onProviderChange);
provider.uninit();
}
this.#inited = false;
}
/**
* The rule that applies to a principal.
*
* @param {?nsIPrincipal} principal
* @returns {?string}
* The first non-null opinion, or null if no provider has one.
*/
getRule(principal) {
if (!this.#inited) {
this.init();
}
try {
for (const provider of this.#providers) {
const rule = provider.getRule(principal);
if (rule != null) {
return rule;
}
}
return null;
} catch (_) {
return IPPPrincipalRules.EXCLUDED;
}
}
/**
* Whether the user can change the rule for this principal. False when a
* higher-precedence provider already claims it.
*
* @param {?nsIPrincipal} principal
* @returns {boolean}
*/
canManage(principal) {
if (!this.#inited) {
this.init();
}
try {
for (const provider of this.#providers) {
// canSet first, so a site the user already set stays manageable.
if (provider.canSet(principal)) {
return true;
}
if (provider.getRule(principal) != null) {
return false;
}
}
return false;
} catch (_) {
return false;
}
}
}
/**
* The one writable rule store. Callers write here, after asking the manager
* whether the principal is manageable at all.
*/
export const IPPPermissionRules = new IPPPermissionRuleProvider();
/**
* The single source of truth for classifying a principal as
* included/excluded/default for the proxy, shared by the channel filter and
* the UI. The array order is the precedence.
*/
export const IPPSiteRuleManager = new SiteRuleManager([
new IPPProxyableRuleProvider(),
new IPPInfrastructureRuleProvider(),
new MatchPatternPrefRule(
"browser.ipProtection.inclusion.match_patterns",
IPPPrincipalRules.INCLUDED
),
IPPPermissionRules,
]);