[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [tor-browser] 18/48: Bug 40925: Implemented the Security Level component
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2
in repository tor-browser.
commit 8e758c6e952c41c7238078f4482935cbd30a8fd8
Author: Pier Angelo Vendrame <pierov@xxxxxxxxxxxxxx>
AuthorDate: Fri Jul 8 16:19:41 2022 +0200
Bug 40925: Implemented the Security Level component
This component adds a new Security Level toolbar button which visually
indicates the current global security level via icon (as defined by the
extensions.torbutton.security_slider pref), a drop-down hanger with a
short description of the current security level, and a new section in
the about:preferences#privacy page where users can change their current
security level. In addition, the hanger and the preferences page will
show a visual warning when the user has modified prefs associated with
the security level and provide a one-click 'Restore Defaults' button to
get the user back on recommended settings.
Bug 40125: Expose Security Level pref in GeckoView
---
browser/app/profile/001-base-profile.js | 2 +-
browser/base/content/browser.js | 10 +
browser/base/content/browser.xhtml | 2 +
browser/base/content/main-popupset.inc.xhtml | 1 +
browser/base/content/navigator-toolbox.inc.xhtml | 2 +
browser/components/moz.build | 1 +
browser/components/preferences/preferences.xhtml | 1 +
browser/components/preferences/privacy.inc.xhtml | 2 +
browser/components/preferences/privacy.js | 20 +
browser/components/securitylevel/SecurityLevel.jsm | 421 ++++++++++++++
.../securitylevel/SecurityLevel.manifest | 1 +
browser/components/securitylevel/components.conf | 10 +
.../securitylevel/content/securityLevel.js | 611 +++++++++++++++++++++
.../securitylevel/content/securityLevelButton.css | 18 +
.../content/securityLevelButton.inc.xhtml | 7 +
.../securitylevel/content/securityLevelIcon.svg | 40 ++
.../securitylevel/content/securityLevelPanel.css | 71 +++
.../content/securityLevelPanel.inc.xhtml | 44 ++
.../content/securityLevelPreferences.css | 51 ++
.../content/securityLevelPreferences.inc.xhtml | 62 +++
browser/components/securitylevel/jar.mn | 11 +
.../locale/en-US/securityLevel.properties | 30 +
browser/components/securitylevel/moz.build | 13 +
browser/installer/package-manifest.in | 5 +
.../shared/customizableui/panelUI-shared.css | 3 +-
mobile/android/geckoview/api.txt | 3 +
.../mozilla/geckoview/GeckoRuntimeSettings.java | 11 +
27 files changed, 1451 insertions(+), 2 deletions(-)
diff --git a/browser/app/profile/001-base-profile.js b/browser/app/profile/001-base-profile.js
index 7ba669c37aa7..1858d29c98c1 100644
--- a/browser/app/profile/001-base-profile.js
+++ b/browser/app/profile/001-base-profile.js
@@ -324,7 +324,7 @@ pref("extensions.webextensions.restrictedDomains", "");
pref("extensions.postDownloadThirdPartyPrompt", false);
// Toolbar layout
-pref("browser.uiCustomization.state", "{\"placements\":{\"widget-overflow-fixed-list\":[],\"PersonalToolbar\":[\"personal-bookmarks\"],\"nav-bar\":[\"back-button\",\"forward-button\",\"stop-reload-button\",\"urlbar-container\",\"downloads-button\"],\"TabsToolbar\":[\"tabbrowser-tabs\",\"new-tab-button\",\"alltabs-button\"],\"toolbar-menubar\":[\"menubar-items\"],\"PanelUI-contents\":[\"home-button\",\"edit-controls\",\"zoom-controls\",\"new-window-button\",\"save-page-button\",\"print-bu [...]
+pref("browser.uiCustomization.state", "{\"placements\":{\"widget-overflow-fixed-list\":[],\"PersonalToolbar\":[\"personal-bookmarks\"],\"nav-bar\":[\"back-button\",\"forward-button\",\"stop-reload-button\",\"urlbar-container\",\"security-level-button\",\"downloads-button\"],\"TabsToolbar\":[\"tabbrowser-tabs\",\"new-tab-button\",\"alltabs-button\"],\"toolbar-menubar\":[\"menubar-items\"],\"PanelUI-contents\":[\"home-button\",\"edit-controls\",\"zoom-controls\",\"new-window-button\",\"sav [...]
// Enforce certificate pinning, see: https://bugs.torproject.org/16206
pref("security.cert_pinning.enforcement_level", 2);
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index 628c58cc2208..ac8b1d100ad8 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -223,6 +223,11 @@ XPCOMUtils.defineLazyScriptGetter(
["DownloadsButton", "DownloadsIndicatorView"],
"chrome://browser/content/downloads/indicator.js"
);
+XPCOMUtils.defineLazyScriptGetter(
+ this,
+ ["SecurityLevelButton"],
+ "chrome://browser/content/securitylevel/securityLevel.js"
+);
XPCOMUtils.defineLazyScriptGetter(
this,
"gEditItemOverlay",
@@ -1772,6 +1777,9 @@ var gBrowserInit = {
// doesn't flicker as the window is being shown.
DownloadsButton.init();
+ // Init the SecuritySettingsButton
+ SecurityLevelButton.init();
+
// Certain kinds of automigration rely on this notification to complete
// their tasks BEFORE the browser window is shown. SessionStore uses it to
// restore tabs into windows AFTER important parts like gMultiProcessBrowser
@@ -2492,6 +2500,8 @@ var gBrowserInit = {
DownloadsButton.uninit();
+ SecurityLevelButton.uninit();
+
gAccessibilityServiceIndicator.uninit();
if (gToolbarKeyNavEnabled) {
diff --git a/browser/base/content/browser.xhtml b/browser/base/content/browser.xhtml
index 32fca7fdc89c..5f53fa119435 100644
--- a/browser/base/content/browser.xhtml
+++ b/browser/base/content/browser.xhtml
@@ -20,6 +20,8 @@
<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/tabbrowser.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/downloads/downloads.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/securitylevel/securityLevelPanel.css"?>
+<?xml-stylesheet href="chrome://browser/content/securitylevel/securityLevelButton.css"?>
<?xml-stylesheet href="chrome://browser/content/places/places.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/usercontext/usercontext.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
diff --git a/browser/base/content/main-popupset.inc.xhtml b/browser/base/content/main-popupset.inc.xhtml
index 391bf512c7e7..f44d1635165e 100644
--- a/browser/base/content/main-popupset.inc.xhtml
+++ b/browser/base/content/main-popupset.inc.xhtml
@@ -534,6 +534,7 @@
#include ../../components/controlcenter/content/protectionsPanel.inc.xhtml
#include ../../components/downloads/content/downloadsPanel.inc.xhtml
#include ../../../devtools/startup/enableDevToolsPopup.inc.xhtml
+#include ../../components/securitylevel/content/securityLevelPanel.inc.xhtml
#include browser-allTabsMenu.inc.xhtml
<tooltip id="dynamic-shortcut-tooltip"
diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml
index 316d3ef98371..4e216ac82508 100644
--- a/browser/base/content/navigator-toolbox.inc.xhtml
+++ b/browser/base/content/navigator-toolbox.inc.xhtml
@@ -405,6 +405,8 @@
</box>
</toolbarbutton>
+#include ../../components/securitylevel/content/securityLevelButton.inc.xhtml
+
<toolbarbutton id="fxa-toolbar-menu-button" class="toolbarbutton-1 chromeclass-toolbar-additional subviewbutton-nav"
badged="true"
onmousedown="gSync.toggleAccountPanel(this, event)"
diff --git a/browser/components/moz.build b/browser/components/moz.build
index 0f9378a2f147..09c7d2a3767e 100644
--- a/browser/components/moz.build
+++ b/browser/components/moz.build
@@ -49,6 +49,7 @@ DIRS += [
"resistfingerprinting",
"screenshots",
"search",
+ "securitylevel",
"sessionstore",
"shell",
"syncedtabs",
diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml
index 6ee14eec9b2e..8706870466fa 100644
--- a/browser/components/preferences/preferences.xhtml
+++ b/browser/components/preferences/preferences.xhtml
@@ -13,6 +13,7 @@
<?xml-stylesheet href="chrome://browser/skin/preferences/search.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/containers.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/privacy.css"?>
+<?xml-stylesheet href="chrome://browser/content/securitylevel/securityLevelPreferences.css"?>
<!DOCTYPE html>
diff --git a/browser/components/preferences/privacy.inc.xhtml b/browser/components/preferences/privacy.inc.xhtml
index a6733ca978bc..8b2a1c99390d 100644
--- a/browser/components/preferences/privacy.inc.xhtml
+++ b/browser/components/preferences/privacy.inc.xhtml
@@ -1038,6 +1038,8 @@
<html:h1 data-l10n-id="security-header"/>
</hbox>
+#include ../securitylevel/content/securityLevelPreferences.inc.xhtml
+
<!-- addons, forgery (phishing) UI Security -->
<groupbox id="browsingProtectionGroup" data-category="panePrivacy" hidden="true">
<label><html:h2 data-l10n-id="security-browsing-protection"/></label>
diff --git a/browser/components/preferences/privacy.js b/browser/components/preferences/privacy.js
index e86645f30551..b1413b208521 100644
--- a/browser/components/preferences/privacy.js
+++ b/browser/components/preferences/privacy.js
@@ -48,6 +48,13 @@ XPCOMUtils.defineLazyGetter(this, "AlertsServiceDND", function() {
}
});
+// TODO: module import via ChromeUtils.defineModuleGetter
+XPCOMUtils.defineLazyScriptGetter(
+ this,
+ ["SecurityLevelPreferences"],
+ "chrome://browser/content/securitylevel/securityLevel.js"
+);
+
XPCOMUtils.defineLazyPreferenceGetter(
this,
"OS_AUTH_ENABLED",
@@ -314,6 +321,18 @@ function initTCPRolloutSection() {
var gPrivacyPane = {
_pane: null,
+ /**
+ * Show the Security Level UI
+ */
+ _initSecurityLevel() {
+ SecurityLevelPreferences.init();
+ let unload = () => {
+ window.removeEventListener("unload", unload);
+ SecurityLevelPreferences.uninit();
+ };
+ window.addEventListener("unload", unload);
+ },
+
/**
* Whether the prompt to restart Firefox should appear when changing the autostart pref.
*/
@@ -509,6 +528,7 @@ var gPrivacyPane = {
this.trackingProtectionReadPrefs();
this.networkCookieBehaviorReadPrefs();
this._initTrackingProtectionExtensionControl();
+ this._initSecurityLevel();
Services.telemetry.setEventRecordingEnabled("pwmgr", true);
diff --git a/browser/components/securitylevel/SecurityLevel.jsm b/browser/components/securitylevel/SecurityLevel.jsm
new file mode 100644
index 000000000000..60e95d777192
--- /dev/null
+++ b/browser/components/securitylevel/SecurityLevel.jsm
@@ -0,0 +1,421 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["SecurityLevel"];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const BrowserTopics = Object.freeze({
+ ProfileAfterChange: "profile-after-change",
+});
+
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ ExtensionParent: "resource://gre/modules/ExtensionParent.jsm",
+});
+
+// Logger adapted from CustomizableUI.jsm
+XPCOMUtils.defineLazyGetter(this, "logger", () => {
+ const { ConsoleAPI } = ChromeUtils.import(
+ "resource://gre/modules/Console.jsm"
+ );
+ let consoleOptions = {
+ maxLogLevel: "info",
+ prefix: "SecurityLevel",
+ };
+ return new ConsoleAPI(consoleOptions);
+});
+
+// The Security Settings prefs in question.
+const kSliderPref = "extensions.torbutton.security_slider";
+const kCustomPref = "extensions.torbutton.security_custom";
+const kSliderMigration = "extensions.torbutton.security_slider_migration";
+
+// __getPrefValue(prefName)__
+// Returns the current value of a preference, regardless of its type.
+var getPrefValue = function(prefName) {
+ switch (Services.prefs.getPrefType(prefName)) {
+ case Services.prefs.PREF_BOOL:
+ return Services.prefs.getBoolPref(prefName);
+ case Services.prefs.PREF_INT:
+ return Services.prefs.getIntPref(prefName);
+ case Services.prefs.PREF_STRING:
+ return Services.prefs.getCharPref(prefName);
+ default:
+ return null;
+ }
+};
+
+// __bindPref(prefName, prefHandler, init)__
+// Applies prefHandler whenever the value of the pref changes.
+// If init is true, applies prefHandler to the current value.
+// Returns a zero-arg function that unbinds the pref.
+var bindPref = function(prefName, prefHandler, init = false) {
+ let update = () => {
+ prefHandler(getPrefValue(prefName));
+ },
+ observer = {
+ observe(subject, topic, data) {
+ if (data === prefName) {
+ update();
+ }
+ },
+ };
+ Services.prefs.addObserver(prefName, observer);
+ if (init) {
+ update();
+ }
+ return () => {
+ Services.prefs.removeObserver(prefName, observer);
+ };
+};
+
+// __bindPrefAndInit(prefName, prefHandler)__
+// Applies prefHandler to the current value of pref specified by prefName.
+// Re-applies prefHandler whenever the value of the pref changes.
+// Returns a zero-arg function that unbinds the pref.
+var bindPrefAndInit = (prefName, prefHandler) =>
+ bindPref(prefName, prefHandler, true);
+
+async function waitForExtensionMessage(extensionId, checker = () => {}) {
+ const { torWaitForExtensionMessage } = ExtensionParent;
+ if (torWaitForExtensionMessage) {
+ return torWaitForExtensionMessage(extensionId, checker);
+ }
+ return undefined;
+}
+
+async function sendExtensionMessage(extensionId, message) {
+ const { torSendExtensionMessage } = ExtensionParent;
+ if (torSendExtensionMessage) {
+ return torSendExtensionMessage(extensionId, message);
+ }
+ return undefined;
+}
+
+// ## NoScript settings
+
+// Minimum and maximum capability states as controlled by NoScript.
+const max_caps = [
+ "fetch",
+ "font",
+ "frame",
+ "media",
+ "object",
+ "other",
+ "script",
+ "webgl",
+ "noscript",
+];
+const min_caps = ["frame", "other", "noscript"];
+
+// Untrusted capabilities for [Standard, Safer, Safest] safety levels.
+const untrusted_caps = [
+ max_caps, // standard safety: neither http nor https
+ ["frame", "font", "object", "other", "noscript"], // safer: http
+ min_caps, // safest: neither http nor https
+];
+
+// Default capabilities for [Standard, Safer, Safest] safety levels.
+const default_caps = [
+ max_caps, // standard: both http and https
+ ["fetch", "font", "frame", "object", "other", "script", "noscript"], // safer: https only
+ min_caps, // safest: both http and https
+];
+
+// __noscriptSettings(safetyLevel)__.
+// Produces NoScript settings with policy according to
+// the safetyLevel which can be:
+// 0 = Standard, 1 = Safer, 2 = Safest
+//
+// At the "Standard" safety level, we leave all sites at
+// default with maximal capabilities. Essentially no content
+// is blocked.
+//
+// At "Safer", we set all http sites to untrusted,
+// and all https sites to default. Scripts are only permitted
+// on https sites. Neither type of site is supposed to allow
+// media, but both allow fonts (as we used in legacy NoScript).
+//
+// At "Safest", all sites are at default with minimal
+// capabilities. Most things are blocked.
+let noscriptSettings = safetyLevel => ({
+ __meta: {
+ name: "updateSettings",
+ recipientInfo: null,
+ },
+ policy: {
+ DEFAULT: {
+ capabilities: default_caps[safetyLevel],
+ temp: false,
+ },
+ TRUSTED: {
+ capabilities: max_caps,
+ temp: false,
+ },
+ UNTRUSTED: {
+ capabilities: untrusted_caps[safetyLevel],
+ temp: false,
+ },
+ sites: {
+ trusted: [],
+ untrusted: [[], ["http:"], []][safetyLevel],
+ custom: {},
+ temp: [],
+ },
+ enforced: true,
+ autoAllowTop: false,
+ },
+ isTorBrowser: true,
+ tabId: -1,
+});
+
+// ## Communications
+
+// The extension ID for NoScript (WebExtension)
+const noscriptID = "{73a6fe31-595d-460b-a920-fcc0f8843232}";
+
+// Ensure binding only occurs once.
+let initialized = false;
+
+// __initialize()__.
+// The main function that binds the NoScript settings to the security
+// slider pref state.
+var initializeNoScriptControl = () => {
+ if (initialized) {
+ return;
+ }
+ initialized = true;
+
+ try {
+ // LegacyExtensionContext is not there anymore. Using raw
+ // Services.cpmm.sendAsyncMessage mechanism to communicate with
+ // NoScript.
+
+ // The component that handles WebExtensions' sendMessage.
+
+ // __setNoScriptSettings(settings)__.
+ // NoScript listens for internal settings with onMessage. We can send
+ // a new settings JSON object according to NoScript's
+ // protocol and these are accepted! See the use of
+ // `browser.runtime.onMessage.addListener(...)` in NoScript's bg/main.js.
+
+ // TODO: Is there a better way?
+ let sendNoScriptSettings = settings =>
+ sendExtensionMessage(noscriptID, settings);
+
+ // __setNoScriptSafetyLevel(safetyLevel)__.
+ // Set NoScript settings according to a particular safety level
+ // (security slider level): 0 = Standard, 1 = Safer, 2 = Safest
+ let setNoScriptSafetyLevel = safetyLevel =>
+ sendNoScriptSettings(noscriptSettings(safetyLevel));
+
+ // __securitySliderToSafetyLevel(sliderState)__.
+ // Converts the "extensions.torbutton.security_slider" pref value
+ // to a "safety level" value: 0 = Standard, 1 = Safer, 2 = Safest
+ let securitySliderToSafetyLevel = sliderState =>
+ [undefined, 2, 1, 1, 0][sliderState];
+
+ // Wait for the first message from NoScript to arrive, and then
+ // bind the security_slider pref to the NoScript settings.
+ let messageListener = a => {
+ try {
+ logger.debug("Message received from NoScript:", a);
+ let noscriptPersist = Services.prefs.getBoolPref(
+ "extensions.torbutton.noscript_persist",
+ false
+ );
+ let noscriptInited = Services.prefs.getBoolPref(
+ "extensions.torbutton.noscript_inited",
+ false
+ );
+ // Set the noscript safety level once if we have never run noscript
+ // before, or if we are not allowing noscript per-site settings to be
+ // persisted between browser sessions. Otherwise make sure that the
+ // security slider position, if changed, will rewrite the noscript
+ // settings.
+ bindPref(
+ kSliderPref,
+ sliderState =>
+ setNoScriptSafetyLevel(securitySliderToSafetyLevel(sliderState)),
+ !noscriptPersist || !noscriptInited
+ );
+ if (!noscriptInited) {
+ Services.prefs.setBoolPref(
+ "extensions.torbutton.noscript_inited",
+ true
+ );
+ }
+ } catch (e) {
+ logger.exception(e);
+ }
+ };
+ waitForExtensionMessage(noscriptID, a => a.__meta.name === "started").then(
+ messageListener
+ );
+ logger.info("Listening for messages from NoScript.");
+ } catch (e) {
+ logger.exception(e);
+ }
+};
+
+// ### Constants
+
+// __kSecuritySettings__.
+// A table of all prefs bound to the security slider, and the value
+// for each security setting. Note that 2-m and 3-m are identical,
+// corresponding to the old 2-medium-high setting. We also separately
+// bind NoScript settings to the extensions.torbutton.security_slider
+// (see noscript-control.js).
+/* eslint-disable */
+const kSecuritySettings = {
+ // Preference name : [0, 1-high 2-m 3-m 4-low]
+ "javascript.options.ion" : [, false, false, false, true ],
+ "javascript.options.baselinejit" : [, false, false, false, true ],
+ "javascript.options.native_regexp" : [, false, false, false, true ],
+ "mathml.disabled" : [, true, true, true, false],
+ "gfx.font_rendering.graphite.enabled" : [, false, false, false, true ],
+ "gfx.font_rendering.opentype_svg.enabled" : [, false, false, false, true ],
+ "svg.disabled" : [, true, false, false, false],
+ "javascript.options.asmjs" : [, false, false, false, true ],
+ "javascript.options.wasm" : [, false, false, false, true ],
+ "dom.security.https_only_mode_send_http_background_request" : [, false, false, false, true ],
+};
+/* eslint-enable */
+
+// ### Prefs
+
+// __write_setting_to_prefs(settingIndex)__.
+// Take a given setting index and write the appropriate pref values
+// to the pref database.
+var write_setting_to_prefs = function(settingIndex) {
+ Object.keys(kSecuritySettings).forEach(prefName =>
+ Services.prefs.setBoolPref(
+ prefName,
+ kSecuritySettings[prefName][settingIndex]
+ )
+ );
+};
+
+// __read_setting_from_prefs()__.
+// Read the current pref values, and decide if any of our
+// security settings matches. Otherwise return null.
+var read_setting_from_prefs = function(prefNames) {
+ prefNames = prefNames || Object.keys(kSecuritySettings);
+ for (let settingIndex of [1, 2, 3, 4]) {
+ let possibleSetting = true;
+ // For the given settingIndex, check if all current pref values
+ // match the setting.
+ for (let prefName of prefNames) {
+ if (
+ kSecuritySettings[prefName][settingIndex] !==
+ Services.prefs.getBoolPref(prefName)
+ ) {
+ possibleSetting = false;
+ }
+ }
+ if (possibleSetting) {
+ // We have a match!
+ return settingIndex;
+ }
+ }
+ // No matching setting; return null.
+ return null;
+};
+
+// __watch_security_prefs(onSettingChanged)__.
+// Whenever a pref bound to the security slider changes, onSettingChanged
+// is called with the new security setting value (1,2,3,4 or null).
+// Returns a zero-arg function that ends this binding.
+var watch_security_prefs = function(onSettingChanged) {
+ let prefNames = Object.keys(kSecuritySettings);
+ let unbindFuncs = [];
+ for (let prefName of prefNames) {
+ unbindFuncs.push(
+ bindPrefAndInit(prefName, () =>
+ onSettingChanged(read_setting_from_prefs())
+ )
+ );
+ }
+ // Call all the unbind functions.
+ return () => unbindFuncs.forEach(unbind => unbind());
+};
+
+// __initialized__.
+// Have we called initialize() yet?
+var initializedSecPrefs = false;
+
+// __initialize()__.
+// Defines the behavior of "extensions.torbutton.security_custom",
+// "extensions.torbutton.security_slider", and the security-sensitive
+// prefs declared in kSecuritySettings.
+var initializeSecurityPrefs = function() {
+ // Only run once.
+ if (initializedSecPrefs) {
+ return;
+ }
+ logger.info("Initializing security-prefs.js");
+ initializedSecPrefs = true;
+ // When security_custom is set to false, apply security_slider setting
+ // to the security-sensitive prefs.
+ bindPrefAndInit(kCustomPref, function(custom) {
+ if (custom === false) {
+ write_setting_to_prefs(Services.prefs.getIntPref(kSliderPref));
+ }
+ });
+ // If security_slider is given a new value, then security_custom should
+ // be set to false.
+ bindPref(kSliderPref, function(prefIndex) {
+ Services.prefs.setBoolPref(kCustomPref, false);
+ write_setting_to_prefs(prefIndex);
+ });
+ // If a security-sensitive pref changes, then decide if the set of pref values
+ // constitutes a security_slider setting or a custom value.
+ watch_security_prefs(settingIndex => {
+ if (settingIndex === null) {
+ Services.prefs.setBoolPref(kCustomPref, true);
+ } else {
+ Services.prefs.setIntPref(kSliderPref, settingIndex);
+ Services.prefs.setBoolPref(kCustomPref, false);
+ }
+ });
+ // Migrate from old medium-low (3) to new medium (2).
+ if (
+ Services.prefs.getBoolPref(kCustomPref) === false &&
+ Services.prefs.getIntPref(kSliderPref) === 3
+ ) {
+ Services.prefs.setIntPref(kSliderPref, 2);
+ write_setting_to_prefs(2);
+ }
+
+ // Revert #33613 fix
+ if (Services.prefs.getIntPref(kSliderMigration, 0) < 2) {
+ // We can't differentiate between users having flipped `javascript.enabled`
+ // to `false` before it got governed by the security settings vs. those who
+ // had it flipped due to #33613. Reset the preference for everyone.
+ if (Services.prefs.getIntPref(kSliderPref) === 1) {
+ Services.prefs.setBoolPref("javascript.enabled", true);
+ }
+ Services.prefs.clearUserPref("media.webaudio.enabled");
+ Services.prefs.setIntPref(kSliderMigration, 2);
+ }
+ logger.info("security-prefs.js initialization complete");
+};
+
+// This class is used to initialize the security level stuff at the startup
+class SecurityLevel {
+ QueryInterface = ChromeUtils.generateQI(["nsIObserver"]);
+
+ init() {
+ initializeNoScriptControl();
+ initializeSecurityPrefs();
+ }
+
+ observe(aSubject, aTopic, aData) {
+ if (aTopic === BrowserTopics.ProfileAfterChange) {
+ this.init();
+ }
+ }
+}
diff --git a/browser/components/securitylevel/SecurityLevel.manifest b/browser/components/securitylevel/SecurityLevel.manifest
new file mode 100644
index 000000000000..0bfbd2ba1ac7
--- /dev/null
+++ b/browser/components/securitylevel/SecurityLevel.manifest
@@ -0,0 +1 @@
+category profile-after-change SecurityLevel @torproject.org/security-level;1
diff --git a/browser/components/securitylevel/components.conf b/browser/components/securitylevel/components.conf
new file mode 100644
index 000000000000..4b9263ce7f0f
--- /dev/null
+++ b/browser/components/securitylevel/components.conf
@@ -0,0 +1,10 @@
+Classes = [
+ {
+ "cid": "{c602ffe5-abf4-40d0-a944-26738b81efdb}",
+ "contract_ids": [
+ "@torproject.org/security-level;1",
+ ],
+ "jsm": "resource:///modules/SecurityLevel.jsm",
+ "constructor": "SecurityLevel",
+ }
+]
diff --git a/browser/components/securitylevel/content/securityLevel.js b/browser/components/securitylevel/content/securityLevel.js
new file mode 100644
index 000000000000..4b27ddbde592
--- /dev/null
+++ b/browser/components/securitylevel/content/securityLevel.js
@@ -0,0 +1,611 @@
+"use strict";
+
+/* global AppConstants, Services, openPreferences, XPCOMUtils */
+
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ CustomizableUI: "resource:///modules/CustomizableUI.jsm",
+ PanelMultiView: "resource:///modules/PanelMultiView.jsm",
+});
+
+const SecurityLevels = Object.freeze(["", "safest", "safer", "", "standard"]);
+
+XPCOMUtils.defineLazyGetter(this, "SecurityLevelStrings", () => {
+ let strings = {
+ // Generic terms
+ security_level: "Security Level",
+ security_level_standard: "Standard",
+ security_level_safer: "Safer",
+ security_level_safest: "Safest",
+ security_level_tooltip_standard: "Security Level: Standard",
+ security_level_tooltip_safer: "Security Level: Safer",
+ security_level_tooltip_safest: "Security Level: Safest",
+ // Shown only for custom level
+ security_level_custom: "Custom",
+ security_level_restore: "Restore Defaults",
+ security_level_learn_more: "Learn more",
+ // Panel
+ security_level_change: "Change…",
+ security_level_standard_summary:
+ "All browser and website features are enabled.",
+ security_level_safer_summary:
+ "Disables website features that are often dangerous, causing some sites to lose functionality.",
+ security_level_safest_summary:
+ "Only allows website features required for static sites and basic services. These changes affect images, media, and scripts.",
+ security_level_custom_summary:
+ "Your custom browser preferences have resulted in unusual security settings. For security and privacy reasons, we recommend you choose one of the default security levels.",
+ // Security level section in about:preferences#privacy
+ security_level_overview:
+ "Disable certain web features that can be used to attack your security and anonymity.",
+ security_level_list_safer: "At the safer setting:",
+ security_level_list_safest: "At the safest setting:",
+ // Strings for descriptions
+ security_level_js_https_only: "JavaScript is disabled on non-HTTPS sites.",
+ security_level_js_disabled:
+ "JavaScript is disabled by default on all sites.",
+ security_level_limit_typography:
+ "Some fonts and math symbols are disabled.",
+ security_level_limit_typography_svg:
+ "Some fonts, icons, math symbols, and images are disabled.",
+ security_level_limit_media:
+ "Audio and video (HTML5 media), and WebGL are click-to-play.",
+ };
+ let bundle = null;
+ try {
+ bundle = Services.strings.createBundle(
+ "chrome://securitylevel/locale/securityLevel.properties"
+ );
+ } catch (e) {
+ console.warn("Could not load the Security Level strings");
+ }
+ if (bundle) {
+ for (const key of Object.keys(strings)) {
+ try {
+ strings[key] = bundle.GetStringFromName(key);
+ } catch (e) {}
+ }
+ }
+ return strings;
+});
+
+/*
+ Security Level Prefs
+
+ Getters and Setters for relevant torbutton prefs
+*/
+const SecurityLevelPrefs = {
+ security_slider_pref: "extensions.torbutton.security_slider",
+ security_custom_pref: "extensions.torbutton.security_custom",
+
+ get securitySlider() {
+ try {
+ return Services.prefs.getIntPref(this.security_slider_pref);
+ } catch (e) {
+ // init pref to 4 (standard)
+ const val = 4;
+ Services.prefs.setIntPref(this.security_slider_pref, val);
+ return val;
+ }
+ },
+
+ set securitySlider(val) {
+ Services.prefs.setIntPref(this.security_slider_pref, val);
+ },
+
+ get securitySliderLevel() {
+ const slider = this.securitySlider;
+ if (slider >= 1 && slider <= 4 && SecurityLevels[slider]) {
+ return SecurityLevels[slider];
+ }
+ return null;
+ },
+
+ get securityCustom() {
+ try {
+ return Services.prefs.getBoolPref(this.security_custom_pref);
+ } catch (e) {
+ // init custom to false
+ const val = false;
+ Services.prefs.setBoolPref(this.security_custom_pref, val);
+ return val;
+ }
+ },
+
+ set securityCustom(val) {
+ Services.prefs.setBoolPref(this.security_custom_pref, val);
+ },
+}; /* Security Level Prefs */
+
+/*
+ Security Level Button Code
+
+ Controls init and update of the security level toolbar button
+*/
+
+const SecurityLevelButton = {
+ _securityPrefsBranch: null,
+
+ _configUIFromPrefs() {
+ const securityLevelButton = this.button;
+ if (securityLevelButton != null) {
+ const level = SecurityLevelPrefs.securitySliderLevel;
+ if (!level) {
+ return;
+ }
+ const customStr = SecurityLevelPrefs.securityCustom ? "_custom" : "";
+ securityLevelButton.setAttribute("level", `${level}${customStr}`);
+ securityLevelButton.setAttribute(
+ "tooltiptext",
+ SecurityLevelStrings[`security_level_tooltip_${level}`]
+ );
+ }
+ },
+
+ /**
+ * The node for this button.
+ *
+ * Note, the returned element may be part of the DOM or may live in the
+ * toolbox palette, where it may be added later to the DOM through
+ * customization.
+ *
+ * @type {MozToolbarbutton}
+ */
+ get button() {
+ // We first search in the DOM for the security level button. If it does not
+ // exist it may be in the toolbox palette. We still want to return the
+ // button in the latter case to allow it to be initialized or adjusted in
+ // case it is added back through customization.
+ return (
+ document.getElementById("security-level-button") ||
+ window.gNavToolbox.palette.querySelector("#security-level-button")
+ );
+ },
+
+ get anchor() {
+ let button = this.button;
+ let anchor = button?.icon;
+ if (!anchor) {
+ return null;
+ }
+
+ anchor.setAttribute("consumeanchor", button.id);
+ return anchor;
+ },
+
+ init() {
+ // Set a label to be be used as the accessible name, and to be shown in the
+ // overflow menu and during customization.
+ this.button?.setAttribute("label", SecurityLevelStrings.security_level);
+ // set the initial class based off of the current pref
+ this._configUIFromPrefs();
+
+ this._securityPrefsBranch = Services.prefs.getBranch(
+ "extensions.torbutton."
+ );
+ this._securityPrefsBranch.addObserver("", this);
+
+ SecurityLevelPanel.init();
+ },
+
+ uninit() {
+ this._securityPrefsBranch.removeObserver("", this);
+ this._securityPrefsBranch = null;
+
+ SecurityLevelPanel.uninit();
+ },
+
+ observe(subject, topic, data) {
+ switch (topic) {
+ case "nsPref:changed":
+ if (data === "security_slider" || data === "security_custom") {
+ this._configUIFromPrefs();
+ }
+ break;
+ }
+ },
+
+ // for when the toolbar button needs to be activated and displays the Security Level panel
+ //
+ // In the toolbarbutton xul you'll notice we register this callback for both onkeypress and
+ // onmousedown. We do this to match the behavior of other panel spawning buttons such as Downloads,
+ // Library, and the Hamburger menus. Using oncommand alone would result in only getting fired
+ // after onclick, which is mousedown followed by mouseup.
+ onCommand(aEvent) {
+ // snippet borrowed from /browser/components/downloads/content/indicator.js DownloadsIndicatorView.onCommand(evt)
+ if (
+ // On Mac, ctrl-click will send a context menu event from the widget, so
+ // we don't want to bring up the panel when ctrl key is pressed.
+ (aEvent.type == "mousedown" &&
+ (aEvent.button != 0 ||
+ (AppConstants.platform == "macosx" && aEvent.ctrlKey))) ||
+ (aEvent.type == "keypress" && aEvent.key != " " && aEvent.key != "Enter")
+ ) {
+ return;
+ }
+
+ // we need to set this attribute for the button to be shaded correctly to look like it is pressed
+ // while the security level panel is open
+ this.button.setAttribute("open", "true");
+ SecurityLevelPanel.show();
+ aEvent.stopPropagation();
+ },
+}; /* Security Level Button */
+
+/*
+ Security Level Panel Code
+
+ Controls init and update of the panel in the security level hanger
+*/
+
+const SecurityLevelPanel = {
+ _securityPrefsBranch: null,
+ _panel: null,
+ _anchor: null,
+ _populated: false,
+
+ _selectors: Object.freeze({
+ panel: "panel#securityLevel-panel",
+ icon: "vbox#securityLevel-vbox>vbox",
+ labelLevel: "label#securityLevel-level",
+ labelCustom: "label#securityLevel-custom",
+ summary: "description#securityLevel-summary",
+ restoreDefaults: "button#securityLevel-restoreDefaults",
+ advancedSecuritySettings: "button#securityLevel-advancedSecuritySettings",
+ // Selectors used only for l10n - remove them when switching to Fluent
+ header: "#securityLevel-header",
+ learnMore: "#securityLevel-panel .learnMore",
+ }),
+
+ _populateXUL() {
+ let selectors = this._selectors;
+
+ this._elements = {
+ panel: document.querySelector(selectors.panel),
+ icon: document.querySelector(selectors.icon),
+ labelLevel: document.querySelector(selectors.labelLevel),
+ labelCustom: document.querySelector(selectors.labelCustom),
+ summaryDescription: document.querySelector(selectors.summary),
+ restoreDefaultsButton: document.querySelector(selectors.restoreDefaults),
+ advancedSecuritySettings: document.querySelector(
+ selectors.advancedSecuritySettings
+ ),
+ header: document.querySelector(selectors.header),
+ learnMore: document.querySelector(selectors.learnMore),
+ };
+
+ this._elements.header.textContent = SecurityLevelStrings.security_level;
+ this._elements.labelCustom.setAttribute(
+ "value",
+ SecurityLevelStrings.security_level_custom
+ );
+ this._elements.learnMore.setAttribute(
+ "value",
+ SecurityLevelStrings.security_level_learn_more
+ );
+ this._elements.restoreDefaultsButton.textContent =
+ SecurityLevelStrings.security_level_restore;
+ this._elements.advancedSecuritySettings.textContent =
+ SecurityLevelStrings.security_level_change;
+
+ this._elements.panel.addEventListener("onpopupshown", e => {
+ this.onPopupShown(e);
+ });
+ this._elements.panel.addEventListener("onpopuphidden", e => {
+ this.onPopupHidden(e);
+ });
+ this._elements.restoreDefaultsButton.addEventListener("command", () => {
+ this.restoreDefaults();
+ });
+ this._elements.advancedSecuritySettings.addEventListener("command", () => {
+ this.openAdvancedSecuritySettings();
+ });
+ this._populated = true;
+ this._configUIFromPrefs();
+ },
+
+ _configUIFromPrefs() {
+ if (!this._populated) {
+ console.warn("_configUIFromPrefs before XUL was populated.");
+ return;
+ }
+
+ // get security prefs
+ const level = SecurityLevelPrefs.securitySliderLevel;
+ const custom = SecurityLevelPrefs.securityCustom;
+
+ // only visible when user is using custom settings
+ let labelCustomWarning = this._elements.labelCustom;
+ labelCustomWarning.hidden = !custom;
+ let buttonRestoreDefaults = this._elements.restoreDefaultsButton;
+ buttonRestoreDefaults.hidden = !custom;
+
+ const summary = this._elements.summaryDescription;
+ // Descriptions change based on security level
+ if (level) {
+ this._elements.icon.setAttribute("level", level);
+ this._elements.labelLevel.setAttribute(
+ "value",
+ SecurityLevelStrings[`security_level_${level}`]
+ );
+ summary.textContent =
+ SecurityLevelStrings[`security_level_${level}_summary`];
+ }
+ // override the summary text with custom warning
+ if (custom) {
+ summary.textContent = SecurityLevelStrings.security_level_custom_summary;
+ }
+ },
+
+ init() {
+ this._securityPrefsBranch = Services.prefs.getBranch(
+ "extensions.torbutton."
+ );
+ this._securityPrefsBranch.addObserver("", this);
+ },
+
+ uninit() {
+ this._securityPrefsBranch.removeObserver("", this);
+ this._securityPrefsBranch = null;
+ },
+
+ show() {
+ // we have to defer this until after the browser has finished init'ing
+ // before we can populate the panel
+ if (!this._populated) {
+ this._populateXUL();
+ }
+
+ this._elements.panel.hidden = false;
+ PanelMultiView.openPopup(
+ this._elements.panel,
+ SecurityLevelButton.anchor,
+ "bottomcenter topright",
+ 0,
+ 0,
+ false,
+ null
+ ).catch(Cu.reportError);
+ },
+
+ hide() {
+ PanelMultiView.hidePopup(this._elements.panel);
+ },
+
+ restoreDefaults() {
+ SecurityLevelPrefs.securityCustom = false;
+ // hide and reshow so that layout re-renders properly
+ this.hide();
+ this.show(this._anchor);
+ },
+
+ openAdvancedSecuritySettings() {
+ openPreferences("privacy-securitylevel");
+ this.hide();
+ },
+
+ // callback when prefs change
+ observe(subject, topic, data) {
+ switch (topic) {
+ case "nsPref:changed":
+ if (data == "security_slider" || data == "security_custom") {
+ this._configUIFromPrefs();
+ }
+ break;
+ }
+ },
+
+ // callback when the panel is displayed
+ onPopupShown(event) {
+ SecurityLevelButton.button.setAttribute("open", "true");
+ },
+
+ // callback when the panel is hidden
+ onPopupHidden(event) {
+ SecurityLevelButton.button.removeAttribute("open");
+ },
+}; /* Security Level Panel */
+
+/*
+ Security Level Preferences Code
+
+ Code to handle init and update of security level section in about:preferences#privacy
+*/
+
+const SecurityLevelPreferences = {
+ _securityPrefsBranch: null,
+
+ _populateXUL() {
+ const groupbox = document.querySelector("#securityLevel-groupbox");
+ const radiogroup = groupbox.querySelector("#securityLevel-radiogroup");
+ radiogroup.addEventListener(
+ "command",
+ SecurityLevelPreferences.selectSecurityLevel
+ );
+
+ groupbox.querySelector("h2").textContent =
+ SecurityLevelStrings.security_level;
+ groupbox.querySelector("#securityLevel-overview").textContent =
+ SecurityLevelStrings.security_level_overview;
+ groupbox
+ .querySelector("#securityLevel-learnMore")
+ .setAttribute("value", SecurityLevelStrings.security_level_learn_more);
+
+ const populateRadioElements = (level, descr) => {
+ const vbox = groupbox.querySelector(`#securityLevel-vbox-${level}`);
+ vbox
+ .querySelector("radio")
+ .setAttribute("label", SecurityLevelStrings[`security_level_${level}`]);
+ vbox
+ .querySelector(".securityLevel-customWarning")
+ .setAttribute("value", SecurityLevelStrings.security_level_custom);
+ vbox.querySelector(".summary").textContent =
+ SecurityLevelStrings[`security_level_${level}_summary`];
+ const labelRestoreDefaults = vbox.querySelector(
+ ".securityLevel-restoreDefaults"
+ );
+ labelRestoreDefaults.setAttribute(
+ "value",
+ SecurityLevelStrings.security_level_restore
+ );
+ labelRestoreDefaults.addEventListener(
+ "click",
+ SecurityLevelStrings.restoreDefaults
+ );
+ if (descr) {
+ const descrList = vbox.querySelector(".securityLevel-descriptionList");
+ // TODO: Add the elements in securityLevelPreferences.inc.xhtml again
+ // when we switch to Fluent
+ for (const text of descr) {
+ let elem = document.createXULElement("description");
+ elem.textContent = text;
+ elem.className = "indent";
+ descrList.append(elem);
+ }
+ }
+ };
+ populateRadioElements("standard");
+ populateRadioElements("safer", [
+ SecurityLevelStrings.security_level_js_https_only,
+ SecurityLevelStrings.security_level_limit_typography,
+ SecurityLevelStrings.security_level_limit_media,
+ ]);
+ populateRadioElements("safest", [
+ SecurityLevelStrings.security_level_js_disabled,
+ SecurityLevelStrings.security_level_limit_typography_svg,
+ SecurityLevelStrings.security_level_limit_media,
+ ]);
+ },
+
+ _configUIFromPrefs() {
+ // read our prefs
+ const securitySlider = SecurityLevelPrefs.securitySlider;
+ const securityCustom = SecurityLevelPrefs.securityCustom;
+
+ // get our elements
+ const groupbox = document.querySelector("#securityLevel-groupbox");
+ let radiogroup = groupbox.querySelector("#securityLevel-radiogroup");
+ let labelStandardCustom = groupbox.querySelector(
+ "#securityLevel-vbox-standard label.securityLevel-customWarning"
+ );
+ let labelSaferCustom = groupbox.querySelector(
+ "#securityLevel-vbox-safer label.securityLevel-customWarning"
+ );
+ let labelSafestCustom = groupbox.querySelector(
+ "#securityLevel-vbox-safest label.securityLevel-customWarning"
+ );
+ let labelStandardRestoreDefaults = groupbox.querySelector(
+ "#securityLevel-vbox-standard label.securityLevel-restoreDefaults"
+ );
+ let labelSaferRestoreDefaults = groupbox.querySelector(
+ "#securityLevel-vbox-safer label.securityLevel-restoreDefaults"
+ );
+ let labelSafestRestoreDefaults = groupbox.querySelector(
+ "#securityLevel-vbox-safest label.securityLevel-restoreDefaults"
+ );
+
+ // hide custom label by default until we know which level we're at
+ labelStandardCustom.hidden = true;
+ labelSaferCustom.hidden = true;
+ labelSafestCustom.hidden = true;
+
+ labelStandardRestoreDefaults.hidden = true;
+ labelSaferRestoreDefaults.hidden = true;
+ labelSafestRestoreDefaults.hidden = true;
+
+ switch (securitySlider) {
+ // standard
+ case 4:
+ radiogroup.value = "standard";
+ labelStandardCustom.hidden = !securityCustom;
+ labelStandardRestoreDefaults.hidden = !securityCustom;
+ break;
+ // safer
+ case 2:
+ radiogroup.value = "safer";
+ labelSaferCustom.hidden = !securityCustom;
+ labelSaferRestoreDefaults.hidden = !securityCustom;
+ break;
+ // safest
+ case 1:
+ radiogroup.value = "safest";
+ labelSafestCustom.hidden = !securityCustom;
+ labelSafestRestoreDefaults.hidden = !securityCustom;
+ break;
+ }
+ },
+
+ init() {
+ // populate XUL with localized strings
+ this._populateXUL();
+
+ // read prefs and populate UI
+ this._configUIFromPrefs();
+
+ // register for pref chagnes
+ this._securityPrefsBranch = Services.prefs.getBranch(
+ "extensions.torbutton."
+ );
+ this._securityPrefsBranch.addObserver("", this);
+ },
+
+ uninit() {
+ // unregister for pref change events
+ this._securityPrefsBranch.removeObserver("", this);
+ this._securityPrefsBranch = null;
+ },
+
+ // callback for when prefs change
+ observe(subject, topic, data) {
+ switch (topic) {
+ case "nsPref:changed":
+ if (data == "security_slider" || data == "security_custom") {
+ this._configUIFromPrefs();
+ }
+ break;
+ }
+ },
+
+ selectSecurityLevel() {
+ // radio group elements
+ let radiogroup = document.getElementById("securityLevel-radiogroup");
+
+ // update pref based on selected radio option
+ switch (radiogroup.value) {
+ case "standard":
+ SecurityLevelPrefs.securitySlider = 4;
+ break;
+ case "safer":
+ SecurityLevelPrefs.securitySlider = 2;
+ break;
+ case "safest":
+ SecurityLevelPrefs.securitySlider = 1;
+ break;
+ }
+
+ SecurityLevelPreferences.restoreDefaults();
+ },
+
+ restoreDefaults() {
+ SecurityLevelPrefs.securityCustom = false;
+ },
+}; /* Security Level Prefereces */
+
+Object.defineProperty(this, "SecurityLevelButton", {
+ value: SecurityLevelButton,
+ enumerable: true,
+ writable: false,
+});
+
+Object.defineProperty(this, "SecurityLevelPanel", {
+ value: SecurityLevelPanel,
+ enumerable: true,
+ writable: false,
+});
+
+Object.defineProperty(this, "SecurityLevelPreferences", {
+ value: SecurityLevelPreferences,
+ enumerable: true,
+ writable: false,
+});
diff --git a/browser/components/securitylevel/content/securityLevelButton.css b/browser/components/securitylevel/content/securityLevelButton.css
new file mode 100644
index 000000000000..38701250e9c9
--- /dev/null
+++ b/browser/components/securitylevel/content/securityLevelButton.css
@@ -0,0 +1,18 @@
+toolbarbutton#security-level-button[level="standard"] {
+ list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard");
+}
+toolbarbutton#security-level-button[level="safer"] {
+ list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safer");
+}
+toolbarbutton#security-level-button[level="safest"] {
+ list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safest");
+}
+toolbarbutton#security-level-button[level="standard_custom"] {
+ list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard_custom");
+}
+toolbarbutton#security-level-button[level="safer_custom"] {
+ list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safer_custom");
+}
+toolbarbutton#security-level-button[level="safest_custom"] {
+ list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safest_custom");
+}
\ No newline at end of file
diff --git a/browser/components/securitylevel/content/securityLevelButton.inc.xhtml b/browser/components/securitylevel/content/securityLevelButton.inc.xhtml
new file mode 100644
index 000000000000..96ee1ec0ca49
--- /dev/null
+++ b/browser/components/securitylevel/content/securityLevelButton.inc.xhtml
@@ -0,0 +1,7 @@
+<toolbarbutton id="security-level-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ badged="true"
+ removable="true"
+ onmousedown="SecurityLevelButton.onCommand(event);"
+ onkeypress="SecurityLevelButton.onCommand(event);"
+ closemenu="none"
+ cui-areatype="toolbar"/>
diff --git a/browser/components/securitylevel/content/securityLevelIcon.svg b/browser/components/securitylevel/content/securityLevelIcon.svg
new file mode 100644
index 000000000000..38cdbcb68afc
--- /dev/null
+++ b/browser/components/securitylevel/content/securityLevelIcon.svg
@@ -0,0 +1,40 @@
+<svg width="16" height="16" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <style>
+ use:not(:target) {
+ display: none;
+ }
+ </style>
+ <defs>
+ <g id="standard_icon" stroke="none" stroke-width="1">
+ <path clip-rule="evenodd" d="m8.49614.283505c-.30743-.175675-.68485-.175675-.99228.000001l-6 3.428574c-.31157.17804-.50386.50938-.50386.86824v1.41968c0 4 2.98667 9.0836 7 10 4.0133-.9164 7-6 7-10v-1.41968c0-.35886-.1923-.6902-.5039-.86824zm-.49614 1.216495-5.75 3.28571v1.2746c0 1.71749.65238 3.7522 1.78726 5.46629 1.07287 1.6204 2.47498 2.8062 3.96274 3.2425 1.48776-.4363 2.8899-1.6221 3.9627-3.2425 1.1349-1.71409 1.7873-3.7488 1.7873-5.46629v-1.2746z" fill-rule="evenodd" />
+ </g>
+ <g id="safer_icon" stroke="none" stroke-width="1">
+ <path clip-rule="evenodd" d="m8.49614.283505c-.30743-.175675-.68485-.175675-.99228.000001l-6 3.428574c-.31157.17804-.50386.50938-.50386.86824v1.41968c0 4 2.98667 9.0836 7 10 4.0133-.9164 7-6 7-10v-1.41968c0-.35886-.1923-.6902-.5039-.86824zm-.49614 1.216495-5.75 3.28571v1.2746c0 1.71749.65238 3.7522 1.78726 5.46629 1.07287 1.6204 2.47498 2.8062 3.96274 3.2425 1.48776-.4363 2.8899-1.6221 3.9627-3.2425 1.1349-1.71409 1.7873-3.7488 1.7873-5.46629v-1.2746z" fill-rule="evenodd"/>
+ <path d="m3.5 6.12062v-.40411c0-.08972.04807-.17255.12597-.21706l4-2.28572c.16666-.09523.37403.02511.37403.21707v10.0766c-1.01204-.408-2.054-1.3018-2.92048-2.6105-1.02134-1.54265-1.57952-3.34117-1.57952-4.77628z"/>
+ </g>
+ <g id="safest_icon" stroke="none" stroke-width="1">
+ <path clip-rule="evenodd" d="m8.49614.283505c-.30743-.175675-.68485-.175675-.99228.000001l-6 3.428574c-.31157.17804-.50386.50938-.50386.86824v1.41968c0 4 2.98667 9.0836 7 10 4.0133-.9164 7-6 7-10v-1.41968c0-.35886-.1923-.6902-.5039-.86824zm-.49614 1.216495-5.75 3.28571v1.2746c0 1.71749.65238 3.7522 1.78726 5.46629 1.07287 1.6204 2.47498 2.8062 3.96274 3.2425 1.48776-.4363 2.8899-1.6221 3.9627-3.2425 1.1349-1.71409 1.7873-3.7488 1.7873-5.46629v-1.2746z" fill-rule="evenodd"/>
+ <path d="m3.5 6.12062v-.40411c0-.08972.04807-.17255.12597-.21706l4.25-2.42857c.07685-.04392.17121-.04392.24806 0l4.24997 2.42857c.0779.04451.126.12734.126.21706v.40411c0 1.43511-.5582 3.23363-1.5795 4.77628-.8665 1.3087-1.90846 2.2025-2.9205 2.6105-1.01204-.408-2.054-1.3018-2.92048-2.6105-1.02134-1.54265-1.57952-3.34117-1.57952-4.77628z"/>
+ </g>
+ <g id="standard_custom_icon" stroke="none" stroke-width="1">
+ <path d="m9.37255.784312-.87641-.500806c-.30743-.175676-.68485-.175676-.99228 0l-6 3.428574c-.31157.17804-.50386.50938-.50386.86824v1.41968c0 4 2.98667 9.0836 7 10 3.7599-.8585 6.6186-5.3745 6.9647-9.23043-.4008.20936-.8392.35666-1.3024.42914-.2132 1.43414-.8072 2.98009-1.6996 4.32789-1.0728 1.6204-2.47494 2.8062-3.9627 3.2425-1.48776-.4363-2.88987-1.6221-3.96274-3.2425-1.13488-1.71409-1.78726-3.7488-1.78726-5.46629v-1.2746l5.75-3.28571.86913.49664c.10502-.43392.27664-.84184.50342- [...]
+ <circle cx="13" cy="3" fill="#ffbd2e" r="3"/>
+ </g>
+ <g id="safer_custom_icon" stroke="none" stroke-width="1">
+ <path d="m9.37255.784312-.87641-.500806c-.30743-.175676-.68485-.175676-.99228 0l-6 3.428574c-.31157.17804-.50386.50938-.50386.86824v1.41968c0 4 2.98667 9.0836 7 10 3.7599-.8585 6.6186-5.3745 6.9647-9.23043-.4008.20936-.8392.35666-1.3024.42914-.2132 1.43414-.8072 2.98009-1.6996 4.32789-1.0728 1.6204-2.47494 2.8062-3.9627 3.2425-1.48776-.4363-2.88987-1.6221-3.96274-3.2425-1.13488-1.71409-1.78726-3.7488-1.78726-5.46629v-1.2746l5.75-3.28571.86913.49664c.10502-.43392.27664-.84184.50342- [...]
+ <path d="m3.5 6.12062v-.40411c0-.08972.04807-.17255.12597-.21706l4-2.28572c.16666-.09523.37403.02511.37403.21707v10.0766c-1.01204-.408-2.054-1.3018-2.92048-2.6105-1.02134-1.54265-1.57952-3.34117-1.57952-4.77628z"/>
+ <circle cx="13" cy="3" fill="#ffbd2e" r="3"/>
+ </g>
+ <g id="safest_custom_icon" stroke="none" stroke-width="1">
+ <path d="m9.37255.784312-.87641-.500806c-.30743-.175676-.68485-.175676-.99228 0l-6 3.428574c-.31157.17804-.50386.50938-.50386.86824v1.41968c0 4 2.98667 9.0836 7 10 3.7599-.8585 6.6186-5.3745 6.9647-9.23043-.4008.20936-.8392.35666-1.3024.42914-.2132 1.43414-.8072 2.98009-1.6996 4.32789-1.0728 1.6204-2.47494 2.8062-3.9627 3.2425-1.48776-.4363-2.88987-1.6221-3.96274-3.2425-1.13488-1.71409-1.78726-3.7488-1.78726-5.46629v-1.2746l5.75-3.28571.86913.49664c.10502-.43392.27664-.84184.50342- [...]
+ <path d="m8.77266 3.44151-.64863-.37064c-.07685-.04392-.17121-.04392-.24806 0l-4.25 2.42857c-.0779.04451-.12597.12735-.12597.21706v.40412c0 1.4351.55818 3.23362 1.57952 4.77618.86648 1.3087 1.90844 2.2026 2.92048 2.6106 1.01204-.408 2.054-1.3018 2.9205-2.6106.7761-1.17217 1.2847-2.49215 1.4843-3.68816-1.9219-.26934-3.43158-1.82403-3.63214-3.76713z"/>
+ <circle cx="13" cy="3" fill="#ffbd2e" r="3"/>
+ </g>
+ </defs>
+ <use id="standard" fill="context-fill" fill-opacity="context-fill-opacity" href="#standard_icon" />
+ <use id="safer" fill="context-fill" fill-opacity="context-fill-opacity" href="#safer_icon" />
+ <use id="safest" fill="context-fill" fill-opacity="context-fill-opacity" href="#safest_icon" />
+ <use id="standard_custom" fill="context-fill" fill-opacity="context-fill-opacity" href="#standard_custom_icon" />
+ <use id="safer_custom" fill="context-fill" fill-opacity="context-fill-opacity" href="#safer_custom_icon" />
+ <use id="safest_custom" fill="context-fill" fill-opacity="context-fill-opacity" href="#safest_custom_icon" />
+</svg>
diff --git a/browser/components/securitylevel/content/securityLevelPanel.css b/browser/components/securitylevel/content/securityLevelPanel.css
new file mode 100644
index 000000000000..c50acf0ae76c
--- /dev/null
+++ b/browser/components/securitylevel/content/securityLevelPanel.css
@@ -0,0 +1,71 @@
+/* Security Level CSS */
+
+panelview#securityLevel-panelview {
+ width: 25em;
+}
+
+vbox#securityLevel-vbox > vbox {
+ background-repeat: no-repeat;
+ /* icon center-line should be in-line with right margin */
+ /* -margin + panelWidth - imageWidth/2 */
+ background-position: calc(-16px + 25em - 4.5em) 0.4em;
+ background-size: 9em 9em;
+ -moz-context-properties: fill, fill-opacity;
+ fill-opacity: 1;
+ fill: var(--button-bgcolor);
+ min-height: 10em;
+}
+
+vbox#securityLevel-vbox > vbox[level="standard"] {
+ background-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard");
+}
+vbox#securityLevel-vbox > vbox[level="safer"] {
+ background-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safer");
+}
+vbox#securityLevel-vbox > vbox[level="safest"] {
+ background-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safest");
+}
+
+vbox#securityLevel-vbox > toolbarseparator {
+ margin-inline: 16px;
+}
+
+vbox#securityLevel-vbox > vbox {
+ margin-inline: 0;
+ padding-inline: 16px;
+}
+
+vbox#securityLevel-vbox > vbox * {
+ margin-inline: 0;
+}
+
+label#securityLevel-level {
+ font-size: 1.25em;
+ font-weight: 600;
+ padding-top: 0.15em;
+}
+
+label#securityLevel-custom {
+ border-radius: 4px;
+ background-color: var(--yellow-50);
+ color: black;
+ font-size: 1em;
+ height: 1.6em;
+ line-height: 1.0em;
+ padding: 0.4em 0.5em;
+ margin-inline-start: 1em !important;
+}
+
+description#securityLevel-summary {
+ margin-top: 1em;
+ padding-inline-end: 5em;
+}
+
+vbox#securityLevel-vbox > hbox.panel-footer {
+ display: flex;
+}
+
+
+button#securityLevel-advancedSecuritySettings {
+ margin-block: 0;
+}
diff --git a/browser/components/securitylevel/content/securityLevelPanel.inc.xhtml b/browser/components/securitylevel/content/securityLevelPanel.inc.xhtml
new file mode 100644
index 000000000000..c485f819aba9
--- /dev/null
+++ b/browser/components/securitylevel/content/securityLevelPanel.inc.xhtml
@@ -0,0 +1,44 @@
+<panel id="securityLevel-panel"
+ role="group"
+ type="arrow"
+ orient="vertical"
+ level="top"
+ hidden="true"
+ class="panel-no-padding">
+ <panelmultiview mainViewId="securityLevel-panelview">
+ <panelview id="securityLevel-panelview" descriptionheightworkaround="true">
+ <vbox id="securityLevel-vbox">
+ <box class="panel-header">
+ <html:h1 id="securityLevel-header"/>
+ </box>
+ <toolbarseparator></toolbarseparator>
+ <vbox>
+ <hbox>
+ <label id="securityLevel-level"/>
+ <vbox>
+ <spacer flex="1"/>
+ <label id="securityLevel-custom"/>
+ <spacer flex="1"/>
+ </vbox>
+ <spacer flex="1"/>
+ </hbox>
+ <description id="securityLevel-summary"/>
+ <hbox>
+ <label
+ class="learnMore text-link"
+ href="about:manual#security-settings"
+ useoriginprincipal="true"
+ onclick="SecurityLevelPanel.hide();"
+ is="text-link"/>
+ <spacer/>
+ </hbox>
+ </vbox>
+ <hbox class="panel-footer">
+ <button id="securityLevel-restoreDefaults"/>
+ <button id="securityLevel-advancedSecuritySettings"
+ default="true"/>
+ </hbox>
+ </vbox>
+ </panelview>
+ </panelmultiview>
+</panel>
diff --git a/browser/components/securitylevel/content/securityLevelPreferences.css b/browser/components/securitylevel/content/securityLevelPreferences.css
new file mode 100644
index 000000000000..152c6489f365
--- /dev/null
+++ b/browser/components/securitylevel/content/securityLevelPreferences.css
@@ -0,0 +1,51 @@
+label.securityLevel-customWarning {
+ border-radius: 4px;
+ background-color: var(--yellow-50);
+ color: black;
+ font-size: 1em;
+ height: 1.6em;
+ padding: 0.4em 0.5em;
+}
+
+radiogroup#securityLevel-radiogroup description {
+ color: var(--in-content-page-color)!important;
+}
+
+radiogroup#securityLevel-radiogroup radio {
+ font-weight: bold;
+}
+
+radiogroup#securityLevel-radiogroup > vbox {
+ border: 1px solid var(--in-content-box-border-color);
+ border-radius: 4px;
+ margin: 3px 0;
+ padding: 9px;
+}
+
+radiogroup#securityLevel-radiogroup[value=standard] > vbox#securityLevel-vbox-standard,
+radiogroup#securityLevel-radiogroup[value=safer] > vbox#securityLevel-vbox-safer,
+radiogroup#securityLevel-radiogroup[value=safest] > vbox#securityLevel-vbox-safest {
+ --section-highlight-background-color: color-mix(in srgb, var(--in-content-accent-color) 20%, transparent);
+ background-color: var(--section-highlight-background-color);
+ border: 1px solid var(--in-content-accent-color);
+
+}
+
+vbox.securityLevel-descriptionList {
+ display: none;
+}
+
+radiogroup#securityLevel-radiogroup[value=safer] vbox#securityLevel-vbox-safer vbox.securityLevel-descriptionList,
+radiogroup#securityLevel-radiogroup[value=safest] vbox#securityLevel-vbox-safest vbox.securityLevel-descriptionList {
+ display: inherit;
+}
+
+vbox.securityLevel-descriptionList description {
+ display: list-item;
+}
+
+vbox#securityLevel-vbox-standard,
+vbox#securityLevel-vbox-safer,
+vbox#securityLevel-vbox-safest {
+ margin-top: 0.4em;
+}
diff --git a/browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml b/browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml
new file mode 100644
index 000000000000..07d9a1d3b32d
--- /dev/null
+++ b/browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml
@@ -0,0 +1,62 @@
+<groupbox id="securityLevel-groupbox" data-category="panePrivacy" hidden="true">
+ <label><html:h2/></label>
+ <vbox data-subcategory="securitylevel" flex="1">
+ <description flex="1">
+ <html:span id="securityLevel-overview" class="tail-with-learn-more"/>
+ <label id="securityLevel-learnMore"
+ class="learnMore text-link"
+ is="text-link"
+ href="about:manual#security-settings"
+ useoriginprincipal="true"/>
+ </description>
+ <radiogroup id="securityLevel-radiogroup">
+ <vbox id="securityLevel-vbox-standard">
+ <hbox>
+ <radio value="standard"/>
+ <vbox>
+ <spacer flex="1"/>
+ <label class="securityLevel-customWarning"/>
+ <spacer flex="1"/>
+ </vbox>
+ <spacer flex="1"/>
+ </hbox>
+ <description flex="1" class="indent">
+ <html:span class="summary tail-with-learn-more"/>
+ <label class="securityLevel-restoreDefaults learnMore text-link"/>
+ </description>
+ </vbox>
+ <vbox id="securityLevel-vbox-safer">
+ <hbox>
+ <radio value="safer"/>
+ <vbox>
+ <spacer flex="1"/>
+ <label class="securityLevel-customWarning"/>
+ <spacer flex="1"/>
+ </vbox>
+ </hbox>
+ <description flex="1" class="indent">
+ <html:span class="summary tail-with-learn-more"/>
+ <label class="securityLevel-restoreDefaults learnMore text-link"/>
+ </description>
+ <vbox class="securityLevel-descriptionList indent">
+ </vbox>
+ </vbox>
+ <vbox id="securityLevel-vbox-safest">
+ <hbox>
+ <radio value="safest"/>
+ <vbox>
+ <spacer flex="1"/>
+ <label class="securityLevel-customWarning"/>
+ <spacer flex="1"/>
+ </vbox>
+ </hbox>
+ <description flex="1" class="indent">
+ <html:span class="summary tail-with-learn-more"/>
+ <label class="securityLevel-restoreDefaults learnMore text-link"/>
+ </description>
+ <vbox class="securityLevel-descriptionList indent">
+ </vbox>
+ </vbox>
+ </radiogroup>
+ </vbox>
+</groupbox>
diff --git a/browser/components/securitylevel/jar.mn b/browser/components/securitylevel/jar.mn
new file mode 100644
index 000000000000..ac8df00b1574
--- /dev/null
+++ b/browser/components/securitylevel/jar.mn
@@ -0,0 +1,11 @@
+browser.jar:
+ content/browser/securitylevel/securityLevel.js (content/securityLevel.js)
+ content/browser/securitylevel/securityLevelPanel.css (content/securityLevelPanel.css)
+ content/browser/securitylevel/securityLevelButton.css (content/securityLevelButton.css)
+ content/browser/securitylevel/securityLevelPreferences.css (content/securityLevelPreferences.css)
+ content/browser/securitylevel/securityLevelIcon.svg (content/securityLevelIcon.svg)
+
+securitylevel.jar:
+# See New Identity for further information on how this works
+% locale securitylevel en-US %locale/en-US/
+ locale/en-US/securityLevel.properties (locale/en-US/securityLevel.properties)
diff --git a/browser/components/securitylevel/locale/en-US/securityLevel.properties b/browser/components/securitylevel/locale/en-US/securityLevel.properties
new file mode 100644
index 000000000000..ba047579d8a7
--- /dev/null
+++ b/browser/components/securitylevel/locale/en-US/securityLevel.properties
@@ -0,0 +1,30 @@
+# Generic terms
+security_level = Security Level
+security_level_standard = Standard
+security_level_safer = Safer
+security_level_safest = Safest
+security_level_tooltip_standard = Security Level: Standard
+security_level_tooltip_safer = Security Level: Safer
+security_level_tooltip_safest = Security Level: Safest
+# Shown only for custom level
+security_level_custom = Custom
+security_level_restore = Restore Defaults
+security_level_learn_more = Learn more
+
+# Panel
+security_level_change = Change…
+security_level_standard_summary = All browser and website features are enabled.
+security_level_safer_summary = Disables website features that are often dangerous, causing some sites to lose functionality.
+security_level_safest_summary = Only allows website features required for static sites and basic services. These changes affect images, media, and scripts.
+security_level_custom_summary = Your custom browser preferences have resulted in unusual security settings. For security and privacy reasons, we recommend you choose one of the default security levels.
+
+## Security level section in about:preferences#privacy
+security_level_overview = Disable certain web features that can be used to attack your security and anonymity.
+security_level_list_safer = At the safer setting:
+security_level_list_safest = At the safest setting:
+# Strings for descriptions
+security_level_js_https_only = JavaScript is disabled on non-HTTPS sites.
+security_level_js_disabled = JavaScript is disabled by default on all sites.
+security_level_limit_typography = Some fonts and math symbols are disabled.
+security_level_limit_typography_svg = Some fonts, icons, math symbols, and images are disabled.
+security_level_limit_media = Audio and video (HTML5 media), and WebGL are click-to-play.
diff --git a/browser/components/securitylevel/moz.build b/browser/components/securitylevel/moz.build
new file mode 100644
index 000000000000..33573a3706f9
--- /dev/null
+++ b/browser/components/securitylevel/moz.build
@@ -0,0 +1,13 @@
+JAR_MANIFESTS += ["jar.mn"]
+
+EXTRA_JS_MODULES += [
+ "SecurityLevel.jsm",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+EXTRA_COMPONENTS += [
+ "SecurityLevel.manifest",
+]
diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in
index bdc712aaa8e7..61dc9789a2eb 100644
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -244,6 +244,11 @@
@RESPATH@/browser/chrome/icons/default/default128.png
#endif
+; Base Browser
+@RESPATH@/browser/chrome/securitylevel.manifest
+@RESPATH@/browser/chrome/securitylevel/
+@RESPATH@/browser/components/SecurityLevel.manifest
+
; [DevTools Startup Files]
@RESPATH@/browser/chrome/devtools-startup@JAREXT@
@RESPATH@/browser/chrome/devtools-startup.manifest
diff --git a/browser/themes/shared/customizableui/panelUI-shared.css b/browser/themes/shared/customizableui/panelUI-shared.css
index fcb62bcff1d9..76839ce2df07 100644
--- a/browser/themes/shared/customizableui/panelUI-shared.css
+++ b/browser/themes/shared/customizableui/panelUI-shared.css
@@ -1323,7 +1323,8 @@ panelview .toolbarbutton-1 {
#editBookmarkPanel toolbarseparator,
#downloadsFooterButtons > toolbarseparator,
.cui-widget-panelview menuseparator,
-.cui-widget-panel toolbarseparator {
+.cui-widget-panel toolbarseparator,
+#securityLevel-panel toolbarseparator {
appearance: none;
min-height: 0;
border-top: 1px solid var(--panel-separator-color);
diff --git a/mobile/android/geckoview/api.txt b/mobile/android/geckoview/api.txt
index 4ec5b10c4da8..af1b7796e6d2 100644
--- a/mobile/android/geckoview/api.txt
+++ b/mobile/android/geckoview/api.txt
@@ -812,6 +812,7 @@ package org.mozilla.geckoview {
method @Nullable public Rect getScreenSizeOverride();
method public boolean getSpoofEnglish();
method @Nullable public RuntimeTelemetry.Delegate getTelemetryDelegate();
+ method public int getTorSecurityLevel();
method public boolean getUseMaxScreenDepth();
method public boolean getWebFontsEnabled();
method public boolean getWebManifestEnabled();
@@ -833,6 +834,7 @@ package org.mozilla.geckoview {
method @NonNull public GeckoRuntimeSettings setPreferredColorScheme(int);
method @NonNull public GeckoRuntimeSettings setRemoteDebuggingEnabled(boolean);
method @NonNull public GeckoRuntimeSettings setSpoofEnglish(boolean);
+ method @NonNull public GeckoRuntimeSettings setTorSecurityLevel(int);
method @NonNull public GeckoRuntimeSettings setWebFontsEnabled(boolean);
method @NonNull public GeckoRuntimeSettings setWebManifestEnabled(boolean);
field public static final int ALLOW_ALL = 0;
@@ -874,6 +876,7 @@ package org.mozilla.geckoview {
method @NonNull public GeckoRuntimeSettings.Builder screenSizeOverride(int, int);
method @NonNull public GeckoRuntimeSettings.Builder spoofEnglish(boolean);
method @NonNull public GeckoRuntimeSettings.Builder telemetryDelegate(@NonNull RuntimeTelemetry.Delegate);
+ method @NonNull public GeckoRuntimeSettings.Builder torSecurityLevel(int);
method @NonNull public GeckoRuntimeSettings.Builder useMaxScreenDepth(boolean);
method @NonNull public GeckoRuntimeSettings.Builder webFontsEnabled(boolean);
method @NonNull public GeckoRuntimeSettings.Builder webManifest(boolean);
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
index b96fbd15cf5d..48d13963d0b6 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
@@ -464,6 +464,17 @@ public final class GeckoRuntimeSettings extends RuntimeSettings {
getSettings().mSpoofEnglish.set(flag ? 2 : 1);
return this;
}
+
+ /**
+ * Set security level.
+ *
+ * @param level A value determining the security level. Default is 0.
+ * @return This Builder instance.
+ */
+ public @NonNull Builder torSecurityLevel(final int level) {
+ getSettings().mTorSecurityLevel.set(level);
+ return this;
+ }
}
private GeckoRuntime mRuntime;
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits