[tor-commits] [tor-browser] 39/69: Bug 40925: Implemented the Security Level component

gitolite role git at cupani.torproject.org
Tue Sep 6 18:05:24 UTC 2022


This is an automated email from the git hooks/post-receive script.

richard pushed a commit to branch tor-browser-102.2.0esr-12.0-3
in repository tor-browser.

commit e854af2cb6f3f51429f0e4f91fbea04dfdcd2782
Author: Pier Angelo Vendrame <pierov at torproject.org>
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         | 615 +++++++++++++++++++++
 .../securitylevel/content/securityLevelButton.css  |  18 +
 .../content/securityLevelButton.inc.xhtml          |   9 +
 .../securitylevel/content/securityLevelIcon.svg    |  40 ++
 .../securitylevel/content/securityLevelPanel.css   |  71 +++
 .../content/securityLevelPanel.inc.xhtml           |  46 ++
 .../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, 1459 insertions(+), 2 deletions(-)

diff --git a/browser/app/profile/001-base-profile.js b/browser/app/profile/001-base-profile.js
index 39f9d935b062d..db4896fd2d9f6 100644
--- a/browser/app/profile/001-base-profile.js
+++ b/browser/app/profile/001-base-profile.js
@@ -321,7 +321,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 801bab733ba63..b5ec03f6f3aa4 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 32fca7fdc89c0..5f53fa119435f 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 391bf512c7e70..f44d1635165e1 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 5f1113e1111ef..6ff9d13d0de97 100644
--- a/browser/base/content/navigator-toolbox.inc.xhtml
+++ b/browser/base/content/navigator-toolbox.inc.xhtml
@@ -404,6 +404,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 0f9378a2f1471..09c7d2a3767e9 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 6ee14eec9b2e1..8706870466fa2 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 a6733ca978bc5..8b2a1c99390d5 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 e86645f305510..b1413b2085215 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 0000000000000..60e95d7771928
--- /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 0000000000000..0bfbd2ba1ac7b
--- /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 0000000000000..4b9263ce7f0f2
--- /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 0000000000000..11e63aa37945c
--- /dev/null
+++ b/browser/components/securitylevel/content/securityLevel.js
@@ -0,0 +1,615 @@
+"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(securityLevelButton) {
+    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}`]
+      );
+    }
+  },
+
+  get button() {
+    let button = document.getElementById("security-level-button");
+    if (!button) {
+      return null;
+    }
+    return button;
+  },
+
+  get anchor() {
+    let anchor = this.button.icon;
+    if (!anchor) {
+      return null;
+    }
+
+    anchor.setAttribute("consumeanchor", SecurityLevelButton.button.id);
+    return anchor;
+  },
+
+  init() {
+    // set the initial class based off of the current pref
+    let button = this.button;
+    this._configUIFromPrefs(button);
+
+    this._securityPrefsBranch = Services.prefs.getBranch(
+      "extensions.torbutton."
+    );
+    this._securityPrefsBranch.addObserver("", this);
+
+    CustomizableUI.addListener(this);
+
+    SecurityLevelPanel.init();
+  },
+
+  uninit() {
+    CustomizableUI.removeListener(this);
+
+    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(this.button);
+        }
+        break;
+    }
+  },
+
+  // callback for entering the 'Customize Firefox' screen to set icon
+  onCustomizeStart(window) {
+    let navigatorToolbox = document.getElementById("navigator-toolbox");
+    let button = navigatorToolbox.palette.querySelector(
+      "#security-level-button"
+    );
+    this._configUIFromPrefs(button);
+  },
+
+  // callback when CustomizableUI modifies DOM
+  onWidgetAfterDOMChange(aNode, aNextNode, aContainer, aWasRemoval) {
+    if (aNode.id == "security-level-button" && !aWasRemoval) {
+      this._configUIFromPrefs(aNode);
+    }
+  },
+
+  // 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 0000000000000..38701250e9c96
--- /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 0000000000000..2ca8fcde945c1
--- /dev/null
+++ b/browser/components/securitylevel/content/securityLevelButton.inc.xhtml
@@ -0,0 +1,9 @@
+<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"
+               data-l10n-id="security-level-button"
+               />
diff --git a/browser/components/securitylevel/content/securityLevelIcon.svg b/browser/components/securitylevel/content/securityLevelIcon.svg
new file mode 100644
index 0000000000000..38cdbcb68afc3
--- /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 0000000000000..c50acf0ae76c8
--- /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 0000000000000..0347ab5e70854
--- /dev/null
+++ b/browser/components/securitylevel/content/securityLevelPanel.inc.xhtml
@@ -0,0 +1,46 @@
+<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"
+                    data-l10n-id="security-level-restore-defaults"/>
+            <button id="securityLevel-advancedSecuritySettings"
+                    default="true"
+                    data-l10n-id="security-level-change"/>
+        </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 0000000000000..152c6489f3658
--- /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 0000000000000..07d9a1d3b32d2
--- /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 0000000000000..ac8df00b15747
--- /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 0000000000000..ba047579d8a75
--- /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 0000000000000..33573a3706f97
--- /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 bdc712aaa8e71..61dc9789a2ebe 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
+ at RESPATH@/browser/chrome/securitylevel.manifest
+ at RESPATH@/browser/chrome/securitylevel/
+ at RESPATH@/browser/components/SecurityLevel.manifest
+
 ; [DevTools Startup Files]
 @RESPATH@/browser/chrome/devtools-startup at 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 fcb62bcff1d93..76839ce2df076 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 4ec5b10c4da84..af1b7796e6d27 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 b96fbd15cf5dc..48d13963d0b67 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.


More information about the tor-commits mailing list