Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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";
var { ExtensionPreferencesManager } = ChromeUtils.importESModule(
"resource://gre/modules/ExtensionPreferencesManager.sys.mjs"
);
var { ExtensionError } = ExtensionUtils;
var { getSettingsAPI, getPrimedSettingsListener } = ExtensionPreferencesManager;
const cookieSvc = Ci.nsICookieService;
const getIntPref = p => Services.prefs.getIntPref(p, undefined);
const getBoolPref = p => Services.prefs.getBoolPref(p, undefined);
const TLS_MIN_PREF = "security.tls.version.min";
const TLS_MAX_PREF = "security.tls.version.max";
const cookieBehaviorValues = new Map([
["allow_all", cookieSvc.BEHAVIOR_ACCEPT],
["reject_third_party", cookieSvc.BEHAVIOR_REJECT_FOREIGN],
["reject_all", cookieSvc.BEHAVIOR_REJECT],
["allow_visited", cookieSvc.BEHAVIOR_LIMIT_FOREIGN],
["reject_trackers", cookieSvc.BEHAVIOR_REJECT_TRACKER],
[
"reject_trackers_and_partition_foreign",
cookieSvc.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
],
]);
function isTLSMinVersionLowerOrEQThan(version) {
return (
Services.prefs.getDefaultBranch("").getIntPref(TLS_MIN_PREF) <= version
);
}
const TLS_VERSIONS = [
{ version: 1, name: "TLSv1", settable: isTLSMinVersionLowerOrEQThan(1) },
{ version: 2, name: "TLSv1.1", settable: isTLSMinVersionLowerOrEQThan(2) },
{ version: 3, name: "TLSv1.2", settable: true },
{ version: 4, name: "TLSv1.3", settable: true },
];
// Add settings objects for supported APIs to the preferences manager.
ExtensionPreferencesManager.addSetting("network.networkPredictionEnabled", {
permission: "privacy",
prefNames: [
"network.predictor.enabled",
"network.prefetch-next",
"network.http.speculative-parallel-limit",
"network.dns.disablePrefetch",
],
setCallback(value) {
return {
"network.http.speculative-parallel-limit": value ? undefined : 0,
"network.dns.disablePrefetch": !value,
"network.predictor.enabled": value,
"network.prefetch-next": value,
};
},
getCallback() {
return (
getBoolPref("network.predictor.enabled") &&
getBoolPref("network.prefetch-next") &&
getIntPref("network.http.speculative-parallel-limit") > 0 &&
!getBoolPref("network.dns.disablePrefetch")
);
},
});
ExtensionPreferencesManager.addSetting("network.globalPrivacyControl", {
permission: "privacy",
prefNames: ["privacy.globalprivacycontrol.enabled"],
readOnly: true,
setCallback(value) {
return {
"privacy.globalprivacycontrol.enabled": value,
};
},
getCallback() {
return getBoolPref("privacy.globalprivacycontrol.enabled");
},
});
ExtensionPreferencesManager.addSetting("network.httpsOnlyMode", {
permission: "privacy",
prefNames: [
"dom.security.https_only_mode",
"dom.security.https_only_mode_pbm",
],
readOnly: true,
setCallback(value) {
let prefs = {
"dom.security.https_only_mode": false,
"dom.security.https_only_mode_pbm": false,
};
switch (value) {
case "always":
prefs["dom.security.https_only_mode"] = true;
break;
case "private_browsing":
prefs["dom.security.https_only_mode_pbm"] = true;
break;
case "never":
break;
}
return prefs;
},
getCallback() {
if (getBoolPref("dom.security.https_only_mode")) {
return "always";
}
if (getBoolPref("dom.security.https_only_mode_pbm")) {
return "private_browsing";
}
return "never";
},
});
ExtensionPreferencesManager.addSetting("network.peerConnectionEnabled", {
permission: "privacy",
prefNames: ["media.peerconnection.enabled"],
setCallback(value) {
return { [this.prefNames[0]]: value };
},
getCallback() {
return getBoolPref("media.peerconnection.enabled");
},
});
ExtensionPreferencesManager.addSetting("network.webRTCIPHandlingPolicy", {
permission: "privacy",
prefNames: [
"media.peerconnection.ice.default_address_only",
"media.peerconnection.ice.no_host",
"media.peerconnection.ice.proxy_only_if_behind_proxy",
"media.peerconnection.ice.proxy_only",
],
setCallback(value) {
let prefs = {};
switch (value) {
case "default":
// All prefs are already set to be reset.
break;
case "default_public_and_private_interfaces":
prefs["media.peerconnection.ice.default_address_only"] = true;
break;
case "default_public_interface_only":
prefs["media.peerconnection.ice.default_address_only"] = true;
prefs["media.peerconnection.ice.no_host"] = true;
break;
case "disable_non_proxied_udp":
prefs["media.peerconnection.ice.default_address_only"] = true;
prefs["media.peerconnection.ice.no_host"] = true;
prefs["media.peerconnection.ice.proxy_only_if_behind_proxy"] = true;
break;
case "proxy_only":
prefs["media.peerconnection.ice.proxy_only"] = true;
break;
}
return prefs;
},
getCallback() {
if (getBoolPref("media.peerconnection.ice.proxy_only")) {
return "proxy_only";
}
let default_address_only = getBoolPref(
"media.peerconnection.ice.default_address_only"
);
if (default_address_only) {
let no_host = getBoolPref("media.peerconnection.ice.no_host");
if (no_host) {
if (
getBoolPref("media.peerconnection.ice.proxy_only_if_behind_proxy")
) {
return "disable_non_proxied_udp";
}
return "default_public_interface_only";
}
return "default_public_and_private_interfaces";
}
return "default";
},
});
ExtensionPreferencesManager.addSetting("services.passwordSavingEnabled", {
permission: "privacy",
prefNames: ["signon.rememberSignons"],
setCallback(value) {
return { [this.prefNames[0]]: value };
},
getCallback() {
return getBoolPref("signon.rememberSignons");
},
});
ExtensionPreferencesManager.addSetting("websites.cookieConfig", {
permission: "privacy",
prefNames: ["network.cookie.cookieBehavior"],
setCallback(value) {
const cookieBehavior = cookieBehaviorValues.get(value.behavior);
// Intentionally use Preferences.get("network.cookie.cookieBehavior") here
// to read the "real" preference value.
const needUpdate =
cookieBehavior !== getIntPref("network.cookie.cookieBehavior");
if (
needUpdate &&
getBoolPref("privacy.firstparty.isolate") &&
cookieBehavior === cookieSvc.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN
) {
throw new ExtensionError(
`Invalid cookieConfig '${value.behavior}' when firstPartyIsolate is enabled`
);
}
if (typeof value.nonPersistentCookies === "boolean") {
Cu.reportError(
"'nonPersistentCookies' has been deprecated and it has no effect anymore."
);
}
return {
"network.cookie.cookieBehavior": cookieBehavior,
};
},
getCallback() {
let prefValue = getIntPref("network.cookie.cookieBehavior");
return {
behavior: Array.from(cookieBehaviorValues.entries()).find(
entry => entry[1] === prefValue
)[0],
// Bug 1754924 - this property is now deprecated.
nonPersistentCookies: false,
};
},
});
ExtensionPreferencesManager.addSetting("websites.firstPartyIsolate", {
permission: "privacy",
prefNames: ["privacy.firstparty.isolate"],
setCallback(value) {
// Intentionally use Preferences.get("network.cookie.cookieBehavior") here
// to read the "real" preference value.
const cookieBehavior = getIntPref("network.cookie.cookieBehavior");
const needUpdate = value !== getBoolPref("privacy.firstparty.isolate");
if (
needUpdate &&
value &&
cookieBehavior === cookieSvc.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN
) {
const behavior = Array.from(cookieBehaviorValues.entries()).find(
entry => entry[1] === cookieBehavior
)[0];
throw new ExtensionError(
`Can't enable firstPartyIsolate when cookieBehavior is '${behavior}'`
);
}
return { [this.prefNames[0]]: value };
},
getCallback() {
return getBoolPref("privacy.firstparty.isolate");
},
});
ExtensionPreferencesManager.addSetting("websites.hyperlinkAuditingEnabled", {
permission: "privacy",
prefNames: ["browser.send_pings"],
setCallback(value) {
return { [this.prefNames[0]]: value };
},
getCallback() {
return getBoolPref("browser.send_pings");
},
});
ExtensionPreferencesManager.addSetting("websites.referrersEnabled", {
permission: "privacy",
prefNames: ["network.http.sendRefererHeader"],
// Values for network.http.sendRefererHeader:
// 0=don't send any, 1=send only on clicks, 2=send on image requests as well
setCallback(value) {
return { [this.prefNames[0]]: value ? 2 : 0 };
},
getCallback() {
return getIntPref("network.http.sendRefererHeader") !== 0;
},
});
ExtensionPreferencesManager.addSetting("websites.resistFingerprinting", {
permission: "privacy",
prefNames: ["privacy.resistFingerprinting"],
setCallback(value) {
return { [this.prefNames[0]]: value };
},
getCallback() {
return getBoolPref("privacy.resistFingerprinting");
},
});
ExtensionPreferencesManager.addSetting("websites.trackingProtectionMode", {
permission: "privacy",
prefNames: [
"privacy.trackingprotection.enabled",
"privacy.trackingprotection.pbmode.enabled",
],
setCallback(value) {
// Default to private browsing.
let prefs = {
"privacy.trackingprotection.enabled": false,
"privacy.trackingprotection.pbmode.enabled": true,
};
switch (value) {
case "private_browsing":
break;
case "always":
prefs["privacy.trackingprotection.enabled"] = true;
break;
case "never":
prefs["privacy.trackingprotection.pbmode.enabled"] = false;
break;
}
return prefs;
},
getCallback() {
if (getBoolPref("privacy.trackingprotection.enabled")) {
return "always";
} else if (getBoolPref("privacy.trackingprotection.pbmode.enabled")) {
return "private_browsing";
}
return "never";
},
});
ExtensionPreferencesManager.addSetting("network.tlsVersionRestriction", {
permission: "privacy",
prefNames: [TLS_MIN_PREF, TLS_MAX_PREF],
setCallback(value) {
function tlsStringToVersion(string) {
const version = TLS_VERSIONS.find(a => a.name === string);
if (version && version.settable) {
return version.version;
}
throw new ExtensionError(
`Setting TLS version ${string} is not allowed for security reasons.`
);
}
const prefs = {};
if (value.minimum) {
prefs[TLS_MIN_PREF] = tlsStringToVersion(value.minimum);
}
if (value.maximum) {
prefs[TLS_MAX_PREF] = tlsStringToVersion(value.maximum);
}
// If minimum has passed and it's greater than the max value.
if (prefs[TLS_MIN_PREF]) {
const max = prefs[TLS_MAX_PREF] || getIntPref(TLS_MAX_PREF);
if (max < prefs[TLS_MIN_PREF]) {
throw new ExtensionError(
`Setting TLS min version grater than the max version is not allowed.`
);
}
}
// If maximum has passed and it's lower than the min value.
else if (prefs[TLS_MAX_PREF]) {
const min = getIntPref(TLS_MIN_PREF);
if (min > prefs[TLS_MAX_PREF]) {
throw new ExtensionError(
`Setting TLS max version lower than the min version is not allowed.`
);
}
}
return prefs;
},
getCallback() {
function tlsVersionToString(pref) {
const value = getIntPref(pref);
const version = TLS_VERSIONS.find(a => a.version === value);
if (version) {
return version.name;
}
return "unknown";
}
return {
minimum: tlsVersionToString(TLS_MIN_PREF),
maximum: tlsVersionToString(TLS_MAX_PREF),
};
},
validate(extension) {
if (!extension.isPrivileged) {
throw new ExtensionError(
"tlsVersionRestriction can be set by privileged extensions only."
);
}
},
});
this.privacy = class extends ExtensionAPI {
primeListener(event, fire) {
let { extension } = this;
let listener = getPrimedSettingsListener({
extension,
name: event,
});
return listener(fire);
}
getAPI(context) {
function makeSettingsAPI(name) {
return getSettingsAPI({
context,
module: "privacy",
name,
});
}
return {
privacy: {
network: {
networkPredictionEnabled: makeSettingsAPI(
"network.networkPredictionEnabled"
),
globalPrivacyControl: makeSettingsAPI("network.globalPrivacyControl"),
httpsOnlyMode: makeSettingsAPI("network.httpsOnlyMode"),
peerConnectionEnabled: makeSettingsAPI(
"network.peerConnectionEnabled"
),
webRTCIPHandlingPolicy: makeSettingsAPI(
"network.webRTCIPHandlingPolicy"
),
tlsVersionRestriction: makeSettingsAPI(
"network.tlsVersionRestriction"
),
},
services: {
passwordSavingEnabled: makeSettingsAPI(
"services.passwordSavingEnabled"
),
},
websites: {
cookieConfig: makeSettingsAPI("websites.cookieConfig"),
firstPartyIsolate: makeSettingsAPI("websites.firstPartyIsolate"),
hyperlinkAuditingEnabled: makeSettingsAPI(
"websites.hyperlinkAuditingEnabled"
),
referrersEnabled: makeSettingsAPI("websites.referrersEnabled"),
resistFingerprinting: makeSettingsAPI(
"websites.resistFingerprinting"
),
trackingProtectionMode: makeSettingsAPI(
"websites.trackingProtectionMode"
),
},
},
};
}
};