This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-102.5.0esr-12.0-2 in repository tor-browser.
commit 16b3cf5ec73f2b151f3d93c61d1097ad800fcf3c Author: Pier Angelo Vendrame pierov@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 + .../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 | 65 +++ browser/components/securitylevel/jar.mn | 11 + .../locale/en-US/securityLevel.properties | 30 + browser/components/securitylevel/moz.build | 1 + browser/installer/package-manifest.in | 5 + .../shared/customizableui/panelUI-shared.css | 3 +- mobile/android/geckoview/api.txt | 3 + .../mozilla/geckoview/GeckoRuntimeSettings.java | 33 ++ mobile/android/installer/package-manifest.in | 3 + toolkit/components/moz.build | 1 + toolkit/components/securitylevel/SecurityLevel.jsm | 450 +++++++++++++++ .../securitylevel/SecurityLevel.manifest | 1 + toolkit/components/securitylevel/components.conf | 10 + toolkit/components/securitylevel/moz.build | 11 + 30 files changed, 1508 insertions(+), 2 deletions(-)
diff --git a/browser/app/profile/001-base-profile.js b/browser/app/profile/001-base-profile.js index 4e7b8b03b590..8422cc370c96 100644 --- a/browser/app/profile/001-base-profile.js +++ b/browser/app/profile/001-base-profile.js @@ -414,7 +414,7 @@ pref("extensions.postDownloadThirdPartyPrompt", false); pref("intl.multilingual.downloadEnabled", 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 085df747026d..4fb318e0ab00 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 0750982c3483..df33143a7365 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/content/securityLevel.js b/browser/components/securitylevel/content/securityLevel.js new file mode 100644 index 000000000000..2ef7fc1648df --- /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: "browser.security_level.security_slider", + security_custom_pref: "browser.security_level.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( + "browser.security_level." + ); + 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( + "browser.security_level." + ); + 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( + "browser.security_level." + ); + 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..7ee3d955683b --- /dev/null +++ b/browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml @@ -0,0 +1,65 @@ +<groupbox id="securityLevel-groupbox" + data-category="panePrivacy" + data-subcategory="securitylevel" + hidden="true"> + <label>html:h2/</label> + <vbox 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..2661ad7cb9f3 --- /dev/null +++ b/browser/components/securitylevel/moz.build @@ -0,0 +1 @@ +JAR_MANIFESTS += ["jar.mn"] diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index bdc712aaa8e7..698939576e03 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@/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 1a3da74d143b..5cc92aa81834 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().mSecurityLevel.set(level); + return this; + } }
private GeckoRuntime mRuntime; @@ -513,6 +524,8 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { new Pref<Boolean>("dom.security.https_only_mode_pbm", false); /* package */ final Pref<Integer> mProcessCount = new Pref<>("dom.ipc.processCount", 2); /* package */ final Pref<Integer> mSpoofEnglish = new Pref<>("privacy.spoof_english", 0); + /* package */ final Pref<Integer> mSecurityLevel = + new Pref<>("browser.security_level.security_slider", 4);
/* package */ int mPreferredColorScheme = COLOR_SCHEME_SYSTEM;
@@ -1277,6 +1290,26 @@ public final class GeckoRuntimeSettings extends RuntimeSettings { return this; }
+ /** + * Gets the current security level. + * + * @return current security protection level + */ + public int getTorSecurityLevel() { + return mSecurityLevel.get(); + } + + /** + * Sets the Security Level. + * + * @param level security protection level + * @return This GeckoRuntimeSettings instance. + */ + public @NonNull GeckoRuntimeSettings setTorSecurityLevel(final int level) { + mSecurityLevel.commit(level); + return this; + } + @Override // Parcelable public void writeToParcel(final Parcel out, final int flags) { super.writeToParcel(out, flags); diff --git a/mobile/android/installer/package-manifest.in b/mobile/android/installer/package-manifest.in index 3149f3ac9c6b..d9e8407ade7a 100644 --- a/mobile/android/installer/package-manifest.in +++ b/mobile/android/installer/package-manifest.in @@ -134,6 +134,9 @@ @BINPATH@/chrome/devtools@JAREXT@ @BINPATH@/chrome/devtools.manifest
+; Base Browser +@BINPATH@/components/SecurityLevel.manifest + ; [Default Preferences] ; All the pref files must be part of base to prevent migration bugs #ifndef MOZ_ANDROID_FAT_AAR_ARCHITECTURES diff --git a/toolkit/components/moz.build b/toolkit/components/moz.build index 4e88cf68d22c..86a289d2c71d 100644 --- a/toolkit/components/moz.build +++ b/toolkit/components/moz.build @@ -66,6 +66,7 @@ DIRS += [ "reputationservice", "resistfingerprinting", "search", + "securitylevel", "sessionstore", "shell", "startup", diff --git a/toolkit/components/securitylevel/SecurityLevel.jsm b/toolkit/components/securitylevel/SecurityLevel.jsm new file mode 100644 index 000000000000..f9b6d5971360 --- /dev/null +++ b/toolkit/components/securitylevel/SecurityLevel.jsm @@ -0,0 +1,450 @@ +"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 = "browser.security_level.security_slider"; +const kCustomPref = "browser.security_level.security_custom"; + +// __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 "browser.security_level.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( + "browser.security_level.noscript_persist", + false + ); + let noscriptInited = Services.prefs.getBoolPref( + "browser.security_level.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( + "browser.security_level.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 browser.security_level.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 "browser.security_level.security_custom", +// "browser.security_level.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); + } + + logger.info("security-prefs.js initialization complete"); +}; + +// tor-browser#41460: we changed preference names in 12.0. +// 11.5.8 is an obligated step for desktop users, so this code is helpful only +// to alpha users, and we could remove it quite soon. +function migratePreferences() { + const kPrefCheck = "extensions.torbutton.noscript_inited"; + // For 12.0, check for extensions.torbutton.noscript_inited, which was set + // as a user preference for sure, if someone used security level in previous + // versions. + if(!Services.prefs.prefHasUserValue(kPrefCheck)) { + return; + } + const migrate = (oldName, newName, getter, setter) => { + oldName = `extensions.torbutton.${oldName}`; + newName = `browser.${newName}`; + if (Services.prefs.prefHasUserValue(oldName)) { + setter(newName, getter(oldName)); + Services.prefs.clearUserPref(oldName); + } + }; + const prefs = { + security_custom: "security_level.security_custom", + noscript_persist: "security_level.noscript_persist", + noscript_inited: "security_level.noscript_inited", + } + for (const [oldName, newName] of Object.entries(prefs)) { + migrate( + oldName, + newName, + Services.prefs.getBoolPref.bind(Services.prefs), + Services.prefs.setBoolPref.bind(Services.prefs) + ); + } + migrate( + "security_slider", + "security_level.security_slider", + Services.prefs.getIntPref.bind(Services.prefs), + Services.prefs.setIntPref.bind(Services.prefs) + ); +} + +// This class is used to initialize the security level stuff at the startup +class SecurityLevel { + QueryInterface = ChromeUtils.generateQI(["nsIObserver"]); + + init() { + migratePreferences(); + initializeNoScriptControl(); + initializeSecurityPrefs(); + } + + observe(aSubject, aTopic, aData) { + if (aTopic === BrowserTopics.ProfileAfterChange) { + this.init(); + } + } +} diff --git a/toolkit/components/securitylevel/SecurityLevel.manifest b/toolkit/components/securitylevel/SecurityLevel.manifest new file mode 100644 index 000000000000..0bfbd2ba1ac7 --- /dev/null +++ b/toolkit/components/securitylevel/SecurityLevel.manifest @@ -0,0 +1 @@ +category profile-after-change SecurityLevel @torproject.org/security-level;1 diff --git a/toolkit/components/securitylevel/components.conf b/toolkit/components/securitylevel/components.conf new file mode 100644 index 000000000000..d25c9f994071 --- /dev/null +++ b/toolkit/components/securitylevel/components.conf @@ -0,0 +1,10 @@ +Classes = [ + { + "cid": "{c602ffe5-abf4-40d0-a944-26738b81efdb}", + "contract_ids": [ + "@torproject.org/security-level;1", + ], + "jsm": "resource://gre/modules/SecurityLevel.jsm", + "constructor": "SecurityLevel", + } +] diff --git a/toolkit/components/securitylevel/moz.build b/toolkit/components/securitylevel/moz.build new file mode 100644 index 000000000000..576ee1e1f536 --- /dev/null +++ b/toolkit/components/securitylevel/moz.build @@ -0,0 +1,11 @@ +EXTRA_JS_MODULES += [ + "SecurityLevel.jsm", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +EXTRA_COMPONENTS += [ + "SecurityLevel.manifest", +]