tbb-commits
Threads by month
- ----- 2025 -----
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- 1 participants
- 18530 discussions

[Git][tpo/applications/tor-browser-build][main] Bug 41488: Disable sys/random.h for Node.js.
by boklm (@boklm) 13 Jun '25
by boklm (@boklm) 13 Jun '25
13 Jun '25
boklm pushed to branch main at The Tor Project / Applications / tor-browser-build
Commits:
a1e364d3 by Pier Angelo Vendrame at 2025-06-12T18:18:59+02:00
Bug 41488: Disable sys/random.h for Node.js.
It is not available in the glibc version we use in our Linux
containers.
- - - - -
1 changed file:
- projects/node/build
Changes:
=====================================
projects/node/build
=====================================
@@ -11,6 +11,11 @@ distdir=/var/tmp/dist/[% project %]
tar -xf [% c('input_files_by_name/node') %]
cd node-v[% c('version') %]
+[% IF c("var/linux") -%]
+ # Same workaround as Mozilla. See taskcluster/scripts/misc/build-nodejs.sh.
+ sed -i '/HAVE_SYS_RANDOM_H/d;/HAVE_GETRANDOM/d' deps/cares/config/linux/ares_config.h
+[% END -%]
+
./configure --prefix=$distdir
make -j[% c("num_procs") %]
make install
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/a…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/a…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-140.0a1-15.0-1] fixup! TB 40933: Add tor-launcher functionality
by henry (@henry) 12 Jun '25
by henry (@henry) 12 Jun '25
12 Jun '25
henry pushed to branch tor-browser-140.0a1-15.0-1 at The Tor Project / Applications / Tor Browser
Commits:
297b2e99 by Henry Wilkes at 2025-06-12T17:52:26+00:00
fixup! TB 40933: Add tor-launcher functionality
TB 43577: Do not try and flush settings to torrc on android.
- - - - -
1 changed file:
- toolkit/components/tor-launcher/TorProvider.sys.mjs
Changes:
=====================================
toolkit/components/tor-launcher/TorProvider.sys.mjs
=====================================
@@ -356,6 +356,10 @@ export class TorProvider {
}
async flushSettings() {
+ if (TorLauncherUtil.isAndroid) {
+ // Android does not have a torrc to flush to. See tor-browser#43577.
+ return;
+ }
await this.#controller.flushSettings();
}
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/297b2e9…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/297b2e9…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-115.24.0esr-13.5-1] 5 commits: fixup! Bug 40925: Implemented the Security Level component
by ma1 (@ma1) 12 Jun '25
by ma1 (@ma1) 12 Jun '25
12 Jun '25
ma1 pushed to branch tor-browser-115.24.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits:
88b35704 by Henry Wilkes at 2025-06-09T17:04:40+01:00
fixup! Bug 40925: Implemented the Security Level component
TB 43783: Prompt user for a restart if their security level preferences
are not aligned at startup or mid-session.
Also handle failures to apply NoScript settings.
- - - - -
6c89d75e by Henry Wilkes at 2025-06-09T17:05:13+01:00
fixup! Bug 40069: Add helpers for message passing with extensions
TB 43783: Allow the browser to wait for the NoScript settings to be
applied.
- - - - -
9c10817a by Henry Wilkes at 2025-06-09T17:05:54+01:00
fixup! Base Browser strings
TB 43783: Add security level prompt strings.
- - - - -
cdd62d0c by Henry Wilkes at 2025-06-09T17:05:59+01:00
fixup! Base Browser strings
TB 43782: Add strings for new security level UX flow.
- - - - -
ebb66713 by Henry Wilkes at 2025-06-09T17:06:02+01:00
fixup! Bug 40925: Implemented the Security Level component
TB 43782: Update security level UI for new UX flow.
In addition, we drop the distinction between the security levels in the
UI when the user has a custom security level.
I.e. we always show shield as unfilled but with a yellow dot in the
toolbar, and we just call it "Custom" rather than "Standard Custom",
etc.
- - - - -
17 changed files:
- browser/components/BrowserGlue.sys.mjs
- + browser/components/securitylevel/SecurityLevelUIUtils.sys.mjs
- browser/components/securitylevel/content/securityLevel.js
- browser/components/securitylevel/content/securityLevelButton.css
- + browser/components/securitylevel/content/securityLevelDialog.js
- + browser/components/securitylevel/content/securityLevelDialog.xhtml
- browser/components/securitylevel/content/securityLevelPanel.css
- browser/components/securitylevel/content/securityLevelPanel.inc.xhtml
- browser/components/securitylevel/content/securityLevelPreferences.css
- browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml
- browser/components/securitylevel/jar.mn
- browser/components/securitylevel/moz.build
- + browser/modules/SecurityLevelRestartNotification.sys.mjs
- browser/modules/moz.build
- toolkit/components/extensions/ExtensionParent.sys.mjs
- toolkit/components/securitylevel/SecurityLevel.sys.mjs
- toolkit/locales/en-US/toolkit/global/base-browser.ftl
Changes:
=====================================
browser/components/BrowserGlue.sys.mjs
=====================================
@@ -59,6 +59,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
Sanitizer: "resource:///modules/Sanitizer.sys.mjs",
ScreenshotsUtils: "resource:///modules/ScreenshotsUtils.sys.mjs",
SearchSERPTelemetry: "resource:///modules/SearchSERPTelemetry.sys.mjs",
+ SecurityLevelRestartNotification:
+ "resource:///modules/SecurityLevelRestartNotification.sys.mjs",
SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
ShellService: "resource:///modules/ShellService.sys.mjs",
@@ -1907,6 +1909,8 @@ BrowserGlue.prototype = {
lazy.DragDropFilter.init();
+ lazy.SecurityLevelRestartNotification.ready();
+
lazy.TorProviderBuilder.firstWindowLoaded();
ClipboardPrivacy.startup();
=====================================
browser/components/securitylevel/SecurityLevelUIUtils.sys.mjs
=====================================
@@ -0,0 +1,73 @@
+/**
+ * Common methods for the desktop security level components.
+ */
+export const SecurityLevelUIUtils = {
+ /**
+ * Create an element that gives a description of the security level. To be
+ * used in the settings.
+ *
+ * @param {string} level - The security level to describe.
+ * @param {Document} doc - The document where the element will be inserted.
+ *
+ * @returns {Element} - The newly created element.
+ */
+ createDescriptionElement(level, doc) {
+ const el = doc.createElement("div");
+ el.classList.add("security-level-description");
+
+ let l10nIdSummary;
+ let bullets;
+ switch (level) {
+ case "standard":
+ l10nIdSummary = "security-level-summary-standard";
+ break;
+ case "safer":
+ l10nIdSummary = "security-level-summary-safer";
+ bullets = [
+ "security-level-preferences-bullet-https-only-javascript",
+ "security-level-preferences-bullet-limit-font-and-symbols",
+ "security-level-preferences-bullet-limit-media",
+ ];
+ break;
+ case "safest":
+ l10nIdSummary = "security-level-summary-safest";
+ bullets = [
+ "security-level-preferences-bullet-disabled-javascript",
+ "security-level-preferences-bullet-limit-font-and-symbols-and-images",
+ "security-level-preferences-bullet-limit-media",
+ ];
+ break;
+ case "custom":
+ l10nIdSummary = "security-level-summary-custom";
+ break;
+ default:
+ throw Error(`Unhandled level: ${level}`);
+ }
+
+ const summaryEl = doc.createElement("div");
+ summaryEl.classList.add("security-level-summary");
+ doc.l10n.setAttributes(summaryEl, l10nIdSummary);
+
+ el.append(summaryEl);
+
+ if (!bullets) {
+ return el;
+ }
+
+ const listEl = doc.createElement("ul");
+ listEl.classList.add("security-level-description-extra");
+ // Add a mozilla styling class as well:
+ listEl.classList.add("privacy-extra-information");
+ for (const l10nId of bullets) {
+ const bulletEl = doc.createElement("li");
+ bulletEl.classList.add("security-level-description-bullet");
+
+ doc.l10n.setAttributes(bulletEl, l10nId);
+
+ listEl.append(bulletEl);
+ }
+
+ el.append(listEl);
+ return el;
+ },
+};
=====================================
browser/components/securitylevel/content/securityLevel.js
=====================================
@@ -1,9 +1,10 @@
"use strict";
-/* global AppConstants, Services, openPreferences, XPCOMUtils */
+/* global AppConstants, Services, openPreferences, XPCOMUtils, gSubDialog */
ChromeUtils.defineESModuleGetters(this, {
SecurityLevelPrefs: "resource://gre/modules/SecurityLevel.sys.mjs",
+ SecurityLevelUIUtils: "resource:///modules/SecurityLevelUIUtils.sys.mjs",
});
/*
@@ -35,12 +36,8 @@ var SecurityLevelButton = {
_anchorButton: null,
_configUIFromPrefs() {
- const level = SecurityLevelPrefs.securityLevel;
- if (!level) {
- return;
- }
- const custom = SecurityLevelPrefs.securityCustom;
- this._button.setAttribute("level", custom ? `${level}_custom` : level);
+ const level = SecurityLevelPrefs.securityLevelSummary;
+ this._button.setAttribute("level", level);
let l10nIdLevel;
switch (level) {
@@ -53,15 +50,12 @@ var SecurityLevelButton = {
case "safest":
l10nIdLevel = "security-level-toolbar-button-safest";
break;
+ case "custom":
+ l10nIdLevel = "security-level-toolbar-button-custom";
+ break;
default:
throw Error(`Unhandled level: ${level}`);
}
- if (custom) {
- // Don't distinguish between the different levels when in the custom
- // state. We just want to emphasise that it is custom rather than any
- // specific level.
- l10nIdLevel = "security-level-toolbar-button-custom";
- }
document.l10n.setAttributes(this._button, l10nIdLevel);
},
@@ -164,12 +158,7 @@ var SecurityLevelPanel = {
panel: document.getElementById("securityLevel-panel"),
background: document.getElementById("securityLevel-background"),
levelName: document.getElementById("securityLevel-level"),
- customName: document.getElementById("securityLevel-custom"),
summary: document.getElementById("securityLevel-summary"),
- restoreDefaultsButton: document.getElementById(
- "securityLevel-restoreDefaults"
- ),
- settingsButton: document.getElementById("securityLevel-settings"),
};
const learnMoreEl = document.getElementById("securityLevel-learnMore");
@@ -177,12 +166,11 @@ var SecurityLevelPanel = {
this.hide();
});
- this._elements.restoreDefaultsButton.addEventListener("command", () => {
- this.restoreDefaults();
- });
- this._elements.settingsButton.addEventListener("command", () => {
- this.openSecuritySettings();
- });
+ document
+ .getElementById("securityLevel-settings")
+ .addEventListener("command", () => {
+ this.openSecuritySettings();
+ });
this._elements.panel.addEventListener("popupshown", () => {
// Bring focus into the panel by focusing the default button.
@@ -200,19 +188,7 @@ var SecurityLevelPanel = {
}
// get security prefs
- const level = SecurityLevelPrefs.securityLevel;
- const custom = SecurityLevelPrefs.securityCustom;
-
- // only visible when user is using custom settings
- this._elements.customName.hidden = !custom;
- this._elements.restoreDefaultsButton.hidden = !custom;
- if (custom) {
- this._elements.settingsButton.removeAttribute("default");
- this._elements.restoreDefaultsButton.setAttribute("default", "true");
- } else {
- this._elements.settingsButton.setAttribute("default", "true");
- this._elements.restoreDefaultsButton.removeAttribute("default");
- }
+ const level = SecurityLevelPrefs.securityLevelSummary;
// Descriptions change based on security level
this._elements.background.setAttribute("level", level);
@@ -231,12 +207,13 @@ var SecurityLevelPanel = {
l10nIdLevel = "security-level-panel-level-safest";
l10nIdSummary = "security-level-summary-safest";
break;
+ case "custom":
+ l10nIdLevel = "security-level-panel-level-custom";
+ l10nIdSummary = "security-level-summary-custom";
+ break;
default:
throw Error(`Unhandled level: ${level}`);
}
- if (custom) {
- l10nIdSummary = "security-level-summary-custom";
- }
document.l10n.setAttributes(this._elements.levelName, l10nIdLevel);
document.l10n.setAttributes(this._elements.summary, l10nIdSummary);
@@ -270,13 +247,6 @@ var SecurityLevelPanel = {
this._elements.panel.hidePopup();
},
- restoreDefaults() {
- SecurityLevelPrefs.securityCustom = false;
- // Move focus to the settings button since restore defaults button will
- // become hidden.
- this._elements.settingsButton.focus();
- },
-
openSecuritySettings() {
openPreferences("privacy-securitylevel");
this.hide();
@@ -302,62 +272,64 @@ var SecurityLevelPanel = {
var SecurityLevelPreferences = {
_securityPrefsBranch: null,
+
/**
- * The notification box shown when the user has a custom security setting.
- *
- * @type {Element}
- */
- _customNotification: null,
- /**
- * The radiogroup for this preference.
- *
- * @type {Element}
- */
- _radiogroup: null,
- /**
- * A list of radio options and their containers.
+ * The element that shows the current security level.
*
- * @type {Array<object>}
+ * @type {?Element}
*/
- _radioOptions: null,
+ _currentEl: null,
_populateXUL() {
- this._customNotification = document.getElementById(
- "securityLevel-customNotification"
+ this._currentEl = document.getElementById("security-level-current");
+ const changeButton = document.getElementById("security-level-change");
+ const badgeEl = this._currentEl.querySelector(
+ ".security-level-current-badge"
);
- document
- .getElementById("securityLevel-restoreDefaults")
- .addEventListener("command", () => {
- SecurityLevelPrefs.securityCustom = false;
- });
- this._radiogroup = document.getElementById("securityLevel-radiogroup");
+ for (const { level, nameId } of [
+ { level: "standard", nameId: "security-level-panel-level-standard" },
+ { level: "safer", nameId: "security-level-panel-level-safer" },
+ { level: "safest", nameId: "security-level-panel-level-safest" },
+ { level: "custom", nameId: "security-level-panel-level-custom" },
+ ]) {
+ // Classes that control visibility:
+ // security-level-current-standard
+ // security-level-current-safer
+ // security-level-current-safest
+ // security-level-current-custom
+ const visibilityClass = `security-level-current-${level}`;
+ const nameEl = document.createElement("div");
+ nameEl.classList.add("security-level-name", visibilityClass);
+ document.l10n.setAttributes(nameEl, nameId);
+
+ const descriptionEl = SecurityLevelUIUtils.createDescriptionElement(
+ level,
+ document
+ );
+ descriptionEl.classList.add(visibilityClass);
- this._radioOptions = Array.from(
- this._radiogroup.querySelectorAll(".securityLevel-radio-option"),
- container => {
- return { container, radio: container.querySelector("radio") };
- }
- );
+ this._currentEl.insertBefore(nameEl, badgeEl);
+ this._currentEl.insertBefore(descriptionEl, changeButton);
+ }
- this._radiogroup.addEventListener("select", () => {
- SecurityLevelPrefs.securityLevel = this._radiogroup.value;
+ changeButton.addEventListener("click", () => {
+ this._openDialog();
});
},
+ _openDialog() {
+ gSubDialog.open(
+ "chrome://browser/content/securitylevel/securityLevelDialog.xhtml",
+ { features: "resizable=yes" }
+ );
+ },
+
_configUIFromPrefs() {
- this._radiogroup.value = SecurityLevelPrefs.securityLevel;
- const isCustom = SecurityLevelPrefs.securityCustom;
- this._radiogroup.disabled = isCustom;
- this._customNotification.hidden = !isCustom;
- // Have the container's selection CSS class match the selection state of the
- // radio elements.
- for (const { container, radio } of this._radioOptions) {
- container.classList.toggle(
- "securityLevel-radio-option-selected",
- radio.selected
- );
- }
+ // Set a data-current-level attribute for showing the current security
+ // level, and hiding the rest.
+ this._currentEl.dataset.currentLevel =
+ SecurityLevelPrefs.securityLevelSummary;
},
init() {
=====================================
browser/components/securitylevel/content/securityLevelButton.css
=====================================
@@ -7,12 +7,6 @@ toolbarbutton#security-level-button[level="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"] {
+toolbarbutton#security-level-button[level="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
=====================================
browser/components/securitylevel/content/securityLevelDialog.js
=====================================
@@ -0,0 +1,188 @@
+"use strict";
+
+const { SecurityLevelPrefs } = ChromeUtils.importESModule(
+ "resource://gre/modules/SecurityLevel.sys.mjs"
+);
+const { SecurityLevelUIUtils } = ChromeUtils.importESModule(
+ "resource:///modules/SecurityLevelUIUtils.sys.mjs"
+);
+
+const gSecurityLevelDialog = {
+ /**
+ * The security level when this dialog was opened.
+ *
+ * @type {string}
+ */
+ _prevLevel: SecurityLevelPrefs.securityLevelSummary,
+ /**
+ * The security level currently selected.
+ *
+ * @type {string}
+ */
+ _selectedLevel: "",
+ /**
+ * The radiogroup for this preference.
+ *
+ * @type {?Element}
+ */
+ _radiogroup: null,
+ /**
+ * A list of radio options and their containers.
+ *
+ * @type {?Array<{ container: Element, radio: Element }>}
+ */
+ _radioOptions: null,
+
+ /**
+ * Initialise the dialog.
+ */
+ async init() {
+ const dialog = document.getElementById("security-level-dialog");
+ dialog.addEventListener("dialogaccept", event => {
+ if (this._acceptButton.disabled) {
+ event.preventDefault();
+ return;
+ }
+ this._commitChange();
+ });
+
+ this._acceptButton = dialog.getButton("accept");
+
+ document.l10n.setAttributes(
+ this._acceptButton,
+ "security-level-dialog-save-restart"
+ );
+
+ this._radiogroup = document.getElementById("security-level-radiogroup");
+
+ this._radioOptions = Array.from(
+ this._radiogroup.querySelectorAll(".security-level-radio-container"),
+ container => {
+ return {
+ container,
+ radio: container.querySelector(".security-level-radio"),
+ };
+ }
+ );
+
+ for (const { container, radio } of this._radioOptions) {
+ const level = radio.value;
+ radio.id = `security-level-radio-${level}`;
+ const currentEl = container.querySelector(
+ ".security-level-current-badge"
+ );
+ currentEl.id = `security-level-current-badge-${level}`;
+ const descriptionEl = SecurityLevelUIUtils.createDescriptionElement(
+ level,
+ document
+ );
+ descriptionEl.classList.add("indent");
+ descriptionEl.id = `security-level-description-${level}`;
+
+ // Wait for the full translation of the element before adding it to the
+ // DOM. In particular, we want to make sure the elements have text before
+ // we measure the maxHeight below.
+ await document.l10n.translateFragment(descriptionEl);
+ document.l10n.pauseObserving();
+ container.append(descriptionEl);
+ document.l10n.resumeObserving();
+
+ if (level === this._prevLevel) {
+ currentEl.hidden = false;
+ // When the currentEl is visible, include it in the accessible name for
+ // the radio option.
+ // NOTE: The currentEl has an accessible name which includes punctuation
+ // to help separate it's content from the security level name.
+ // E.g. "Standard (Current level)".
+ radio.setAttribute("aria-labelledby", `${radio.id} ${currentEl.id}`);
+ } else {
+ currentEl.hidden = true;
+ }
+ // We point the accessible description to the wrapping
+ // .security-level-description element, rather than its children
+ // that define the actual text content. This means that when the
+ // privacy-extra-information is shown or hidden, its text content is
+ // included or excluded from the accessible description, respectively.
+ radio.setAttribute("aria-describedby", descriptionEl.id);
+ }
+
+ // We want to reserve the maximum height of the radiogroup so that the
+ // dialog has enough height when the user switches options. So we cycle
+ // through the options and measure the height when they are selected to set
+ // a minimum height that fits all of them.
+ // NOTE: At the time of implementation, at this point the dialog may not
+ // yet have the "subdialog" attribute, which means it is missing the
+ // common.css stylesheet from its shadow root, which effects the size of the
+ // .radio-check element and the font. Therefore, we have duplicated the
+ // import of common.css in SecurityLevelDialog.xhtml to ensure it is applied
+ // at this earlier stage.
+ let maxHeight = 0;
+ for (const { container } of this._radioOptions) {
+ container.classList.add("selected");
+ maxHeight = Math.max(
+ maxHeight,
+ this._radiogroup.getBoundingClientRect().height
+ );
+ container.classList.remove("selected");
+ }
+ this._radiogroup.style.minHeight = `${maxHeight}px`;
+
+ if (this._prevLevel !== "custom") {
+ this._selectedLevel = this._prevLevel;
+ this._radiogroup.value = this._prevLevel;
+ } else {
+ this._radiogroup.selectedItem = null;
+ }
+
+ this._radiogroup.addEventListener("select", () => {
+ this._selectedLevel = this._radiogroup.value;
+ this._updateSelected();
+ });
+
+ this._updateSelected();
+ },
+
+ /**
+ * Update the UI in response to a change in selection.
+ */
+ _updateSelected() {
+ this._acceptButton.disabled =
+ !this._selectedLevel || this._selectedLevel === this._prevLevel;
+ // Have the container's `selected` CSS class match the selection state of
+ // the radio elements.
+ for (const { container, radio } of this._radioOptions) {
+ container.classList.toggle("selected", radio.selected);
+ }
+ },
+
+ /**
+ * Commit the change in security level and restart the browser.
+ */
+ _commitChange() {
+ SecurityLevelPrefs.setSecurityLevelBeforeRestart(this._selectedLevel);
+ Services.startup.quit(
+ Services.startup.eAttemptQuit | Services.startup.eRestart
+ );
+ },
+};
+
+// Initial focus is not visible, even if opened with a keyboard. We avoid the
+// default handler and manage the focus ourselves, which will paint the focus
+// ring by default.
+// NOTE: A side effect is that the focus ring will show even if the user opened
+// with a mouse event.
+// TODO: Remove this once bugzilla bug 1708261 is resolved.
+document.subDialogSetDefaultFocus = () => {
+ document.getElementById("security-level-radiogroup").focus();
+};
+
+// Delay showing and sizing the subdialog until it is fully initialised.
+document.mozSubdialogReady = new Promise(resolve => {
+ window.addEventListener(
+ "DOMContentLoaded",
+ () => {
+ gSecurityLevelDialog.init().finally(resolve);
+ },
+ { once: true }
+ );
+});
=====================================
browser/components/securitylevel/content/securityLevelDialog.xhtml
=====================================
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+
+<window
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ data-l10n-id="security-level-dialog-window"
+>
+ <dialog id="security-level-dialog" buttons="accept,cancel">
+ <linkset>
+ <html:link rel="stylesheet" href="chrome://global/skin/global.css" />
+ <!-- NOTE: We include common.css explicitly, rather than relying on
+ - the dialog's shadowroot importing it, which is late loaded in
+ - response to the dialog's "subdialog" attribute, which is set
+ - in response to DOMFrameContentLoaded.
+ - In particular, we need the .radio-check rule and font rules from
+ - common-shared.css to be in place when gSecurityLevelDialog.init is
+ - called, which will help ensure that the radio element has the correct
+ - size when we measure its bounding box. -->
+ <html:link
+ rel="stylesheet"
+ href="chrome://global/skin/in-content/common.css"
+ />
+ <html:link
+ rel="stylesheet"
+ href="chrome://browser/skin/preferences/preferences.css"
+ />
+ <html:link
+ rel="stylesheet"
+ href="chrome://browser/skin/preferences/privacy.css"
+ />
+ <html:link
+ rel="stylesheet"
+ href="chrome://browser/content/securitylevel/securityLevelPreferences.css"
+ />
+
+ <html:link rel="localization" href="branding/brand.ftl" />
+ <html:link rel="localization" href="toolkit/global/base-browser.ftl" />
+ </linkset>
+
+ <script src="chrome://browser/content/securitylevel/securityLevelDialog.js" />
+
+ <description data-l10n-id="security-level-dialog-restart-description" />
+
+ <radiogroup id="security-level-radiogroup" class="highlighting-group">
+ <html:div
+ class="security-level-radio-container security-level-grid privacy-detailedoption info-box-container"
+ >
+ <radio
+ class="security-level-radio security-level-name"
+ value="standard"
+ data-l10n-id="security-level-preferences-level-standard"
+ />
+ <html:div
+ class="security-level-current-badge"
+ data-l10n-id="security-level-preferences-current-badge"
+ ></html:div>
+ </html:div>
+ <html:div
+ class="security-level-radio-container security-level-grid privacy-detailedoption info-box-container"
+ >
+ <radio
+ class="security-level-radio security-level-name"
+ value="safer"
+ data-l10n-id="security-level-preferences-level-safer"
+ />
+ <html:div
+ class="security-level-current-badge"
+ data-l10n-id="security-level-preferences-current-badge"
+ ></html:div>
+ </html:div>
+ <html:div
+ class="security-level-radio-container security-level-grid privacy-detailedoption info-box-container"
+ >
+ <radio
+ class="security-level-radio security-level-name"
+ value="safest"
+ data-l10n-id="security-level-preferences-level-safest"
+ />
+ <html:div
+ class="security-level-current-badge"
+ data-l10n-id="security-level-preferences-current-badge"
+ ></html:div>
+ </html:div>
+ </radiogroup>
+ </dialog>
+</window>
=====================================
browser/components/securitylevel/content/securityLevelPanel.css
=====================================
@@ -23,7 +23,7 @@
background-position-x: right var(--background-inline-offset);
}
-#securityLevel-background[level="standard"] {
+#securityLevel-background:is([level="standard"], [level="custom"]) {
background-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard");
}
@@ -49,14 +49,6 @@
font-weight: 600;
}
-#securityLevel-custom {
- border-radius: 4px;
- background-color: var(--warning-color);
- color: black;
- padding: 0.4em 0.5em;
- margin-inline-start: 1em;
-}
-
#securityLevel-summary {
padding-inline-end: 5em;
max-width: 20em;
=====================================
browser/components/securitylevel/content/securityLevelPanel.inc.xhtml
=====================================
@@ -16,10 +16,6 @@
<vbox id="securityLevel-background" class="panel-subview-body">
<html:p id="securityLevel-subheading">
<html:span id="securityLevel-level"></html:span>
- <html:span
- id="securityLevel-custom"
- data-l10n-id="security-level-panel-custom-badge"
- ></html:span>
</html:p>
<html:p id="securityLevel-summary"></html:p>
<html:a
@@ -32,11 +28,8 @@
<hbox class="panel-footer">
<button
id="securityLevel-settings"
+ default="true"
data-l10n-id="security-level-panel-open-settings-button"
/>
- <button
- id="securityLevel-restoreDefaults"
- data-l10n-id="security-level-restore-defaults-button"
- />
</hbox>
</panel>
=====================================
browser/components/securitylevel/content/securityLevelPreferences.css
=====================================
@@ -1,58 +1,185 @@
-#securityLevel-groupbox {
- --section-highlight-background-color: color-mix(in srgb, var(--in-content-accent-color) 20%, transparent);
+/* CSS variables copied from ESR 128 */
+:root {
+ --space-xsmall: 0.267rem;
+ --space-small: calc(2 * var(--space-xsmall));
+ --space-medium: calc(3 * var(--space-xsmall));
+ --space-large: calc(4 * var(--space-xsmall));
+ --space-xlarge: calc(6 * var(--space-xsmall));
+ --font-size-small: 0.867rem;
+ --background-color-information: #e2f7ff; /* acorn oklch --color-blue-0 */
+ --button-text-color-primary: var(--in-content-primary-button-text-color);
+ --border-radius-small: 4px;
}
-#securityLevel-customNotification {
- /* Spacing similar to #fpiIncompatibilityWarning. */
- margin-block: 16px;
+@media (prefers-color-scheme: dark) {
+ :root {
+ --background-color-information: #00317e; /* acorn oklch --color-blue-90 */
+ }
}
-.info-icon.securityLevel-custom-warning-icon {
- list-style-image: url("chrome://global/skin/icons/warning.svg");
+.security-level-grid {
+ display: grid;
+ grid-template:
+ "icon name badge button" min-content
+ "icon summary summary button" auto
+ "icon extra extra ." auto
+ / max-content max-content 1fr max-content;
}
-#securityLevel-customHeading {
- font-weight: bold;
+.security-level-icon {
+ grid-area: icon;
+ align-self: start;
+ width: 24px;
+ height: 24px;
+ -moz-context-properties: fill;
+ fill: var(--in-content-icon-color);
+ margin-block-start: var(--space-xsmall);
+ margin-inline-end: var(--space-large);
+}
+
+.security-level-current-badge {
+ grid-area: badge;
+ align-self: center;
+ justify-self: start;
+ white-space: nowrap;
+ background: var(--background-color-information);
+ color: inherit;
+ font-size: var(--font-size-small);
+ border-radius: var(--border-radius-circle);
+ margin-inline-start: var(--space-small);
+ padding-block: var(--space-xsmall);
+ padding-inline: var(--space-small);
+}
+
+.security-level-current-badge span {
+ /* Still accessible to screen reader, but not visual.
+ * Keep inline, but with no layout width. */
+ display: inline-block;
+ width: 1px;
+ margin-inline-end: -1px;
+ clip-path: inset(50%);
}
-/* Overwrite indent rule from preferences.css */
-#securityLevel-radiogroup description.indent {
- color: var(--in-content-page-color);
+@media (prefers-contrast) and (not (forced-colors)) {
+ .security-level-current-badge {
+ /* Match the checkbox/radio colors. */
+ background: var(--color-accent-primary);
+ color: var(--button-text-color-primary);
+ }
}
-#securityLevel-radiogroup radio {
+@media (forced-colors) {
+ .security-level-current-badge {
+ /* Match the checkbox/radio/selected colors. */
+ background: SelectedItem;
+ color: SelectedItemText;
+ }
+}
+
+.security-level-name {
+ grid-area: name;
font-weight: bold;
+ align-self: center;
+ white-space: nowrap;
+}
+
+.security-level-description {
+ display: grid;
+ grid-column: summary-start / extra-end;
+ grid-row: summary-start / extra-end;
+ grid-template-rows: subgrid;
+ grid-template-columns: subgrid;
+ margin-block-start: var(--space-small);
+}
+
+.security-level-summary {
+ grid-area: summary;
}
-#securityLevel-radiogroup[disabled] {
- opacity: 0.5;
+.security-level-description-extra {
+ grid-area: extra;
+ margin-block: var(--space-medium) 0;
+ margin-inline: var(--space-large) 0;
+ padding: 0;
}
-/* Overwrite the rule in common-shared.css so we don't get 0.25 opacity overall
- * on the radio text. */
-#securityLevel-radiogroup[disabled] radio[disabled] {
- opacity: 1.0;
+.security-level-description-bullet:not(:last-child) {
+ margin-block-end: var(--space-medium);
}
-.securityLevel-radio-option {
+/* Tweak current security level display. */
+
+#security-level-current {
+ margin-block-start: var(--space-large);
+ background: var(--in-content-box-background);
border: 1px solid var(--in-content-box-border-color);
- border-radius: 4px;
- margin: 3px 0;
- padding: 9px;
+ border-radius: var(--border-radius-small);
+ padding: var(--space-medium);
}
-.securityLevel-radio-option.securityLevel-radio-option-selected {
- background-color: var(--section-highlight-background-color);
- border: 1px solid var(--in-content-accent-color);
+#security-level-change {
+ grid-area: button;
+ align-self: center;
+ margin: 0;
+ margin-inline-start: var(--space-large);
+}
+
+/* Adjust which content is visible depending on the current security level. */
+
+#security-level-current:not([data-current-level="standard"]) .security-level-current-standard {
+ display: none;
+}
+#security-level-current:not([data-current-level="safer"]) .security-level-current-safer {
+ display: none;
}
-.securityLevel-radio-option:not(
- .securityLevel-radio-option-selected
-) .securityLevel-descriptionList {
+#security-level-current:not([data-current-level="safest"]) .security-level-current-safest {
display: none;
}
-.securityLevel-descriptionList description {
- display: list-item;
+#security-level-current:not([data-current-level="custom"]) .security-level-current-custom {
+ display: none;
+}
+
+#security-level-current[data-current-level="standard"] .security-level-icon {
+ content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard");
+}
+
+#security-level-current[data-current-level="safer"] .security-level-icon {
+ content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safer");
+}
+
+#security-level-current[data-current-level="safest"] .security-level-icon {
+ content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safest");
+}
+
+#security-level-current[data-current-level="custom"] .security-level-icon {
+ content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard_custom");
+}
+
+/* Tweak security level dialog. */
+
+#security-level-radiogroup {
+ margin-block: var(--space-large) var(--space-xlarge);
+}
+
+.security-level-radio-container {
+ padding-block: var(--space-large);
+}
+
+#security-level-radiogroup .security-level-radio {
+ margin: 0;
+}
+
+#security-level-radiogroup .radio-label-box {
+ /* .security-level-current-badge already has a margin. */
+ margin: 0;
+}
+
+#security-level-radiogroup .privacy-detailedoption.security-level-radio-container:not(.selected) .security-level-description-extra {
+ /* .privacy-detailedoption uses visibility: hidden, which does not work with
+ * our grid display (the margin is still reserved) so we use display: none
+ * instead. */
+ display: none;
}
=====================================
browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml
=====================================
@@ -18,108 +18,19 @@
data-l10n-id="security-level-preferences-learn-more-link"
></html:a>
</description>
- <hbox
- id="securityLevel-customNotification"
- class="info-box-container"
- flex="1"
- >
- <hbox class="info-icon-container">
- <image class="info-icon securityLevel-custom-warning-icon"/>
- </hbox>
- <vbox flex="1">
- <label
- id="securityLevel-customHeading"
- data-l10n-id="security-level-preferences-custom-heading"
- />
- <description
- id="securityLevel-customDescription"
- data-l10n-id="security-level-summary-custom"
- flex="1"
- />
- </vbox>
- <hbox align="center">
- <button
- id="securityLevel-restoreDefaults"
- data-l10n-id="security-level-restore-defaults-button"
- />
- </hbox>
- </hbox>
- <radiogroup id="securityLevel-radiogroup">
- <vbox class="securityLevel-radio-option">
- <radio
- value="standard"
- data-l10n-id="security-level-preferences-level-standard"
- aria-describedby="securityLevelSummary-standard"
- />
- <vbox id="securityLevelSummary-standard">
- <description
- class="summary indent"
- flex="1"
- data-l10n-id="security-level-summary-standard"
- />
- </vbox>
- </vbox>
- <vbox class="securityLevel-radio-option">
- <!-- NOTE: We point the accessible description to the wrapping vbox
- - rather than its first description element. This means that when the
- - securityLevel-descriptionList is shown or hidden, its text content
- - is included or excluded from the accessible description,
- - respectively. -->
- <radio
- value="safer"
- data-l10n-id="security-level-preferences-level-safer"
- aria-describedby="securityLevelSummary-safer"
- />
- <vbox id="securityLevelSummary-safer">
- <description
- class="summary indent"
- flex="1"
- data-l10n-id="security-level-summary-safer"
- />
- <vbox class="securityLevel-descriptionList indent">
- <description
- class="indent"
- data-l10n-id="security-level-preferences-bullet-https-only-javascript"
- />
- <description
- class="indent"
- data-l10n-id="security-level-preferences-bullet-limit-font-and-symbols"
- />
- <description
- class="indent"
- data-l10n-id="security-level-preferences-bullet-limit-media"
- />
- </vbox>
- </vbox>
- </vbox>
- <vbox class="securityLevel-radio-option">
- <radio
- value="safest"
- data-l10n-id="security-level-preferences-level-safest"
- aria-describedby="securityLevelSummary-safest"
- />
- <vbox id="securityLevelSummary-safest">
- <description
- class="summary indent"
- flex="1"
- data-l10n-id="security-level-summary-safest"
- />
- <vbox class="securityLevel-descriptionList indent">
- <description
- class="indent"
- data-l10n-id="security-level-preferences-bullet-disabled-javascript"
- />
- <description
- class="indent"
- data-l10n-id="security-level-preferences-bullet-limit-font-and-symbols-and-images"
- />
- <description
- class="indent"
- data-l10n-id="security-level-preferences-bullet-limit-media"
- />
- </vbox>
- </vbox>
- </vbox>
- </radiogroup>
+ <html:div id="security-level-current" class="security-level-grid">
+ <html:img
+ class="security-level-icon"
+ alt=""
+ />
+ <html:div
+ class="security-level-current-badge"
+ data-l10n-id="security-level-preferences-current-badge"
+ ></html:div>
+ <html:button
+ id="security-level-change"
+ data-l10n-id="security-level-preferences-change-button"
+ ></html:button>
+ </html:div>
</vbox>
</groupbox>
=====================================
browser/components/securitylevel/jar.mn
=====================================
@@ -4,3 +4,5 @@ browser.jar:
content/browser/securitylevel/securityLevelButton.css (content/securityLevelButton.css)
content/browser/securitylevel/securityLevelPreferences.css (content/securityLevelPreferences.css)
content/browser/securitylevel/securityLevelIcon.svg (content/securityLevelIcon.svg)
+ content/browser/securitylevel/securityLevelDialog.xhtml (content/securityLevelDialog.xhtml)
+ content/browser/securitylevel/securityLevelDialog.js (content/securityLevelDialog.js)
=====================================
browser/components/securitylevel/moz.build
=====================================
@@ -1 +1,5 @@
JAR_MANIFESTS += ["jar.mn"]
+
+EXTRA_JS_MODULES += [
+ "SecurityLevelUIUtils.sys.mjs",
+]
=====================================
browser/modules/SecurityLevelRestartNotification.sys.mjs
=====================================
@@ -0,0 +1,77 @@
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+const lazy = {};
+
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
+});
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ SecurityLevelPrefs: "resource://gre/modules/SecurityLevel.sys.mjs",
+});
+
+ChromeUtils.defineLazyGetter(lazy, "NotificationStrings", function () {
+ return new Localization([
+ "branding/brand.ftl",
+ "toolkit/global/base-browser.ftl",
+ ]);
+});
+
+/**
+ * Interface for showing the security level restart notification on desktop.
+ */
+export const SecurityLevelRestartNotification = {
+ /**
+ * Whether we have already been initialised.
+ *
+ * @type {boolean}
+ */
+ _initialized: false,
+
+ /**
+ * Called when the UI is ready to show a notification.
+ */
+ ready() {
+ if (this._initialized) {
+ return;
+ }
+ this._initialized = true;
+ lazy.SecurityLevelPrefs.setRestartNotificationHandler(this);
+ },
+
+ /**
+ * Show the restart notification, and perform the restart if the user agrees.
+ */
+ async tryRestartBrowser() {
+ const [titleText, bodyText, primaryButtonText, secondaryButtonText] =
+ await lazy.NotificationStrings.formatValues([
+ { id: "security-level-restart-prompt-title" },
+ { id: "security-level-restart-prompt-body" },
+ { id: "security-level-restart-prompt-button-restart" },
+ { id: "security-level-restart-prompt-button-ignore" },
+ ]);
+ const buttonFlags =
+ Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
+ Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1;
+
+ const propBag = await Services.prompt.asyncConfirmEx(
+ lazy.BrowserWindowTracker.getTopWindow()?.browsingContext ?? null,
+ Services.prompt.MODAL_TYPE_INTERNAL_WINDOW,
+ titleText,
+ bodyText,
+ buttonFlags,
+ primaryButtonText,
+ secondaryButtonText,
+ null,
+ null,
+ null,
+ {}
+ );
+
+ if (propBag.get("buttonNumClicked") === 0) {
+ Services.startup.quit(
+ Services.startup.eAttemptQuit | Services.startup.eRestart
+ );
+ }
+ },
+};
=====================================
browser/modules/moz.build
=====================================
@@ -142,6 +142,7 @@ EXTRA_JS_MODULES += [
"PingCentre.jsm",
"ProcessHangMonitor.jsm",
"Sanitizer.sys.mjs",
+ "SecurityLevelRestartNotification.sys.mjs",
"SelectionChangedMenulist.jsm",
"SiteDataManager.jsm",
"SitePermissions.sys.mjs",
=====================================
toolkit/components/extensions/ExtensionParent.sys.mjs
=====================================
@@ -2275,6 +2275,7 @@ async function torSendExtensionMessage(extensionId, message) {
const result = await ProxyMessenger.conduit.castRuntimeMessage("messenger", {
extensionId,
holder: new StructuredCloneHolder("torSendExtensionMessage", null, message),
+ query: true,
firstResponse: true,
sender: {
id: extensionId,
=====================================
toolkit/components/securitylevel/SecurityLevel.sys.mjs
=====================================
@@ -18,6 +18,7 @@ const BrowserTopics = Object.freeze({
// The Security Settings prefs in question.
const kSliderPref = "browser.security_level.security_slider";
const kCustomPref = "browser.security_level.security_custom";
+const kNoScriptInitedPref = "browser.security_level.noscript_inited";
// __getPrefValue(prefName)__
// Returns the current value of a preference, regardless of its type.
@@ -34,11 +35,11 @@ var getPrefValue = function (prefName) {
}
};
-// __bindPref(prefName, prefHandler, init)__
+// __bindPref(prefName, prefHandler)__
// 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) {
+// Returns the observer that was added.
+var bindPref = function (prefName, prefHandler) {
let update = () => {
prefHandler(getPrefValue(prefName));
},
@@ -50,21 +51,9 @@ var bindPref = function (prefName, prefHandler, init = false) {
},
};
Services.prefs.addObserver(prefName, observer);
- if (init) {
- update();
- }
- return () => {
- Services.prefs.removeObserver(prefName, observer);
- };
+ return 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 } = lazy.ExtensionParent;
if (torWaitForExtensionMessage) {
@@ -76,7 +65,7 @@ async function waitForExtensionMessage(extensionId, checker = () => {}) {
async function sendExtensionMessage(extensionId, message) {
const { torSendExtensionMessage } = lazy.ExtensionParent;
if (torSendExtensionMessage) {
- return torSendExtensionMessage(extensionId, message);
+ return await torSendExtensionMessage(extensionId, message);
}
return undefined;
}
@@ -189,14 +178,8 @@ var initializeNoScriptControl = () => {
// `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));
+ let sendNoScriptSettings = async settings =>
+ await sendExtensionMessage(noscriptID, settings);
// __securitySliderToSafetyLevel(sliderState)__.
// Converts the "browser.security_level.security_slider" pref value
@@ -206,36 +189,46 @@ var initializeNoScriptControl = () => {
// Wait for the first message from NoScript to arrive, and then
// bind the security_slider pref to the NoScript settings.
- let messageListener = a => {
+ let messageListener = async a => {
try {
logger.debug("Message received from NoScript:", a);
- let noscriptPersist = Services.prefs.getBoolPref(
- "browser.security_level.noscript_persist",
- false
- );
+ const persistPref = "browser.security_level.noscript_persist";
+ let noscriptPersist = Services.prefs.getBoolPref(persistPref, false);
let noscriptInited = Services.prefs.getBoolPref(
- "browser.security_level.noscript_inited",
+ kNoScriptInitedPref,
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
+ // Set the noscript safety level once at startup.
+ // If a user has set noscriptPersist, then we only send this if the
+ // security level was changed in a previous session.
+ // NOTE: We do not re-send this when the security_slider preference
+ // changes mid-session because this should always require a restart.
+ if (noscriptPersist && noscriptInited) {
+ logger.warn(
+ `Not initialising NoScript since the user has set ${persistPref}`
);
+ return;
}
+ // Read the security level, even if the user has the "custom"
+ // preference.
+ const securityIndex = Services.prefs.getIntPref(kSliderPref, 0);
+ const safetyLevel = securitySliderToSafetyLevel(securityIndex);
+ // May throw if NoScript fails to apply the settings:
+ const noscriptResult = await sendNoScriptSettings(
+ noscriptSettings(safetyLevel)
+ );
+ // Mark the NoScript extension as initialised so we do not reset it
+ // at the next startup for noscript_persist users.
+ Services.prefs.setBoolPref(kNoScriptInitedPref, true);
+ logger.info("NoScript successfully initialised.");
+ // In the future NoScript may tell us more about how it applied our
+ // settings, e.g. if user is overriding per-site permissions.
+ // Up to NoScript 12.6 noscriptResult is undefined.
+ logger.debug("NoScript response:", noscriptResult);
} catch (e) {
- logger.exception(e);
+ logger.error("Could not apply NoScript settings", e);
+ // Treat as a custom security level for the rest of the session.
+ Services.prefs.setBoolPref(kCustomPref, true);
}
};
waitForExtensionMessage(noscriptID, a => a.__meta.name === "started").then(
@@ -244,6 +237,8 @@ var initializeNoScriptControl = () => {
logger.info("Listening for messages from NoScript.");
} catch (e) {
logger.exception(e);
+ // Treat as a custom security level for the rest of the session.
+ Services.prefs.setBoolPref(kCustomPref, true);
}
};
@@ -274,16 +269,60 @@ const kSecuritySettings = {
// ### Prefs
+/**
+ * Amend the security level index to a standard value.
+ *
+ * @param {integer} index - The input index value.
+ * @returns {integer} - A standard index value.
+ */
+function fixupIndex(index) {
+ if (!Number.isInteger(index) || index < 1 || index > 4) {
+ // Unexpected value out of range, go to the "safest" level as a fallback.
+ return 1;
+ }
+ if (index === 3) {
+ // Migrate from old medium-low (3) to new medium (2).
+ return 2;
+ }
+ return index;
+}
+
+/**
+ * A list of preference observers that should be disabled whilst we write our
+ * preference values.
+ *
+ * @type {{ prefName: string, observer: object }[]}
+ */
+const prefObservers = [];
+
// __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]
- )
- );
+ settingIndex = fixupIndex(settingIndex);
+ // Don't want to trigger our internal observers when setting ourselves.
+ for (const { prefName, observer } of prefObservers) {
+ Services.prefs.removeObserver(prefName, observer);
+ }
+ try {
+ // Make sure noscript is re-initialised at the next startup when the
+ // security level changes.
+ Services.prefs.setBoolPref(kNoScriptInitedPref, false);
+ Services.prefs.setIntPref(kSliderPref, settingIndex);
+ // NOTE: We do not clear kCustomPref. Instead, we rely on the preference
+ // being cleared on the next startup.
+ Object.keys(kSecuritySettings).forEach(prefName =>
+ Services.prefs.setBoolPref(
+ prefName,
+ kSecuritySettings[prefName][settingIndex]
+ )
+ );
+ } finally {
+ // Re-add the observers.
+ for (const { prefName, observer } of prefObservers) {
+ Services.prefs.addObserver(prefName, observer);
+ }
+ }
};
// __read_setting_from_prefs()__.
@@ -312,24 +351,6 @@ var read_setting_from_prefs = function (prefNames) {
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;
@@ -345,36 +366,82 @@ var initializeSecurityPrefs = function () {
}
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) {
+
+ const wasCustom = Services.prefs.getBoolPref(kCustomPref, false);
+ // For new profiles with no user preference, the security level should be "4"
+ // and it should not be custom.
+ let desiredIndex = Services.prefs.getIntPref(kSliderPref, 4);
+ desiredIndex = fixupIndex(desiredIndex);
+ // Make sure the user has a set preference user value.
+ Services.prefs.setIntPref(kSliderPref, desiredIndex);
+ Services.prefs.setBoolPref(kCustomPref, wasCustom);
+
+ // Make sure that the preference values at application startup match the
+ // expected values for the desired security level. See tor-browser#43783.
+
+ // NOTE: We assume that the controlled preference values that are read prior
+ // to profile-after-change do not change in value before this method is
+ // called. I.e. we expect the current preference values to match the
+ // preference values that were used during the application initialisation.
+ const effectiveIndex = read_setting_from_prefs();
+
+ if (wasCustom && effectiveIndex !== null) {
+ logger.info(`Custom startup values match index ${effectiveIndex}`);
+ // Do not consider custom any more.
+ // NOTE: This level needs to be set before it is read elsewhere. In
+ // particular, for the NoScript addon.
Services.prefs.setBoolPref(kCustomPref, false);
- write_setting_to_prefs(prefIndex);
+ Services.prefs.setIntPref(kSliderPref, effectiveIndex);
+ } else if (!wasCustom && effectiveIndex !== desiredIndex) {
+ // NOTE: We assume all our controlled preferences require a restart.
+ // In practice, only a subset of these preferences may actually require a
+ // restart, so we could switch their values. But we treat them all the same
+ // for simplicity, consistency and stability in case mozilla changes the
+ // restart requirements.
+ logger.info(`Startup values do not match for index ${desiredIndex}`);
+ SecurityLevelPrefs.requireRestart();
+ }
+
+ // Start listening for external changes to the controlled preferences.
+ prefObservers.push({
+ prefName: kCustomPref,
+ observer: bindPref(kCustomPref, custom => {
+ // Custom flag was removed mid-session. Requires a restart to apply the
+ // security level.
+ if (custom === false) {
+ logger.info("Custom flag was cleared externally");
+ SecurityLevelPrefs.requireRestart();
+ }
+ }),
});
- // 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);
- }
+ prefObservers.push({
+ prefName: kSliderPref,
+ observer: bindPref(kSliderPref, () => {
+ // Security level was changed mid-session. Requires a restart to apply.
+ logger.info("Security level was changed externally");
+ SecurityLevelPrefs.requireRestart();
+ }),
});
- // 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);
+
+ for (const prefName of Object.keys(kSecuritySettings)) {
+ prefObservers.push({
+ prefName,
+ observer: bindPref(prefName, () => {
+ logger.warn(
+ `The controlled preference ${prefName} was changed externally.` +
+ " Treating as a custom security level."
+ );
+ // Something outside of this module changed the preference value for a
+ // preference we control.
+ // Always treat as a custom security level for the rest of this session,
+ // even if the new preference values match a pre-set security level. We
+ // do this because some controlled preferences require a restart to be
+ // properly applied. See tor-browser#43783.
+ // In the case where it does match a pre-set security level, the custom
+ // flag will be cleared at the next startup.
+ Services.prefs.setBoolPref(kCustomPref, true);
+ }),
+ });
}
logger.info("security-prefs.js initialization complete");
@@ -426,8 +493,9 @@ export class SecurityLevel {
init() {
migratePreferences();
- initializeNoScriptControl();
+ // Fixup our preferences before we pass on the security level to NoScript.
initializeSecurityPrefs();
+ initializeNoScriptControl();
}
observe(aSubject, aTopic, aData) {
@@ -437,10 +505,19 @@ export class SecurityLevel {
}
}
+/**
+ * @typedef {object} SecurityLevelRestartNotificationHandler
+ *
+ * An object that can serve the user a restart notification.
+ *
+ * @property {Function} tryRestartBrowser - The method that should be called to
+ * ask the user to restart the browser.
+ */
+
/*
Security Level Prefs
- Getters and Setters for relevant torbutton prefs
+ Getters and Setters for relevant security level prefs
*/
export const SecurityLevelPrefs = {
SecurityLevels: Object.freeze({
@@ -451,6 +528,14 @@ export const SecurityLevelPrefs = {
security_slider_pref: "browser.security_level.security_slider",
security_custom_pref: "browser.security_level.security_custom",
+ /**
+ * The current security level preference.
+ *
+ * This ignores any custom settings the user may have changed, and just
+ * gives the underlying security level.
+ *
+ * @type {?string}
+ */
get securityLevel() {
// Set the default return value to 0, which won't match anything in
// SecurityLevels.
@@ -460,18 +545,147 @@ export const SecurityLevelPrefs = {
)?.[0];
},
- set securityLevel(level) {
- const val = this.SecurityLevels[level];
- if (val !== undefined) {
- Services.prefs.setIntPref(this.security_slider_pref, val);
- }
+ /**
+ * Set the desired security level just before a restart.
+ *
+ * The caller must restart the browser after calling this method.
+ *
+ * @param {string} level - The name of the new security level to set.
+ */
+ setSecurityLevelBeforeRestart(level) {
+ write_setting_to_prefs(this.SecurityLevels[level]);
},
+ /**
+ * Whether the user has any custom setting values that do not match a pre-set
+ * security level.
+ *
+ * @type {boolean}
+ */
get securityCustom() {
return Services.prefs.getBoolPref(this.security_custom_pref);
},
- set securityCustom(val) {
- Services.prefs.setBoolPref(this.security_custom_pref, val);
+ /**
+ * A summary of the current security level.
+ *
+ * If the user has some custom settings, this returns "custom". Otherwise
+ * returns the name of the security level.
+ *
+ * @type {string}
+ */
+ get securityLevelSummary() {
+ if (this.securityCustom) {
+ return "custom";
+ }
+ return this.securityLevel ?? "custom";
+ },
+
+ /**
+ * Whether the browser should be restarted to apply the security level.
+ *
+ * @type {boolean}
+ */
+ _needRestart: false,
+
+ /**
+ * The external handler that can show a notification to the user, if any.
+ *
+ * @type {?SecurityLevelRestartNotificationHandler}
+ */
+ _restartNotificationHandler: null,
+
+ /**
+ * Set the external handler for showing notifications to the user.
+ *
+ * This should only be called once per session once the handler is ready to
+ * show a notification, which may occur immediately during this call.
+ *
+ * @param {SecurityLevelRestartNotificationHandler} handler - The new handler
+ * to use.
+ */
+ setRestartNotificationHandler(handler) {
+ logger.info("Restart notification handler is set");
+ this._restartNotificationHandler = handler;
+ if (this._needRestart) {
+ // Show now using the new handler.
+ this._tryShowRestartNotification();
+ }
+ },
+
+ /**
+ * A promise for any ongoing notification prompt task.
+ *
+ * @type {Promise}
+ */
+ _restartNotificationPromise: null,
+
+ /**
+ * Try show a notification to the user.
+ *
+ * If no notification handler has been attached yet, this will do nothing.
+ */
+ async _tryShowRestartNotification() {
+ if (!this._restartNotificationHandler) {
+ logger.info("Missing a restart notification handler");
+ // This may be added later in the session.
+ return;
+ }
+
+ const prevPromise = this._restartNotificationPromise;
+ let resolve;
+ this._restartNotificationPromise = new Promise(r => {
+ resolve = r;
+ });
+ await prevPromise;
+
+ try {
+ await this._restartNotificationHandler?.tryRestartBrowser();
+ } finally {
+ // Allow the notification to be shown again.
+ resolve();
+ }
+ },
+
+ /**
+ * Mark the session as requiring a restart to apply a change in security
+ * level.
+ *
+ * The security level will immediately be switched to "custom", and the user
+ * may be shown a notification to restart the browser.
+ */
+ requireRestart() {
+ logger.warn("The browser needs to be restarted to set the security level");
+ // Treat as a custom security level for the rest of the session.
+ // At the next startup, the custom flag may be cleared if the settings are
+ // as expected.
+ Services.prefs.setBoolPref(kCustomPref, true);
+ this._needRestart = true;
+
+ // NOTE: We need to change the controlled security level preferences in
+ // response to the desired change in security level. We could either:
+ // 1. Only change the controlled preferences after the user confirms a
+ // restart. Or
+ // 2. Change the controlled preferences and then try and ask the user to
+ // restart.
+ //
+ // We choose the latter:
+ // 1. To allow users to manually restart.
+ // 2. If the user ignores or misses the notification, they will at least be
+ // in the correct state when the browser starts again. Although they will
+ // be in a custom/undefined state in the mean time.
+ // 3. Currently Android relies on triggering the change in security level
+ // by setting the browser.security_level.security_slider preference
+ // value. So it currently uses this path. So we need to set the values
+ // now, before it preforms a restart.
+ // TODO: Have android use the `setSecurityLevelBeforeRestart` method
+ // instead of setting the security_slider preference value directly, so that
+ // it knows exactly when it can restart the browser. tor-browser#43820
+ write_setting_to_prefs(Services.prefs.getIntPref(kSliderPref, 0));
+ // NOTE: Even though we have written the preferences, the session should
+ // still be marked as "custom" because:
+ // 1. Some preferences require a browser restart to be applied.
+ // 2. NoScript has not been updated with the new settings.
+ this._tryShowRestartNotification();
},
}; /* Security Level Prefs */
=====================================
toolkit/locales/en-US/toolkit/global/base-browser.ftl
=====================================
@@ -118,10 +118,6 @@ security-level-toolbar-button-custom =
# Uses sentence case in English (US).
security-level-panel-heading = Security level
-
-security-level-panel-level-standard = Standard
-security-level-panel-level-safer = Safer
-security-level-panel-level-safest = Safest
security-level-panel-learn-more-link = Learn more
# Button to open security level settings.
security-level-panel-open-settings-button = Settings…
@@ -131,6 +127,19 @@ security-level-panel-open-settings-button = Settings…
security-level-preferences-heading = Security Level
security-level-preferences-overview = Disable certain web features that can be used to attack your security and anonymity.
security-level-preferences-learn-more-link = Learn more
+# Text for a badge that labels the currently active security level.
+# The text in between '<span>' and '</span>' should contain some kind of bracket, like '(' and ')', or other punctuation used in your language to separate out text from its surrounding context. This will not be visible, but will be use for screen readers to make it clear that the text is not part of the same sentence. For example, in US English this would be read as "(Current level)", and the full line of text would be read as "Safest (Current level)".
+security-level-preferences-current-badge = <span>(</span>Current level<span>)</span>
+security-level-preferences-change-button = Change…
+
+## Security level settings dialog.
+
+security-level-dialog-window =
+ .title = Change security level
+
+# '-brand-short-name' is the localized browser name, like "Tor Browser".
+security-level-dialog-restart-description = You will need to restart { -brand-short-name } to apply any changes. This will close all windows and tabs.
+
security-level-preferences-level-standard =
.label = Standard
security-level-preferences-level-safer =
@@ -138,6 +147,16 @@ security-level-preferences-level-safer =
security-level-preferences-level-safest =
.label = Safest
+security-level-dialog-save-restart =
+ .label = Save and restart
+
+## Security level names shown in the security panel and settings.
+
+security-level-panel-level-standard = Standard
+security-level-panel-level-safer = Safer
+security-level-panel-level-safest = Safest
+security-level-panel-level-custom = Custom
+
## Security level summaries shown in security panel and settings.
security-level-summary-standard = All browser and website features are enabled.
@@ -156,16 +175,16 @@ security-level-preferences-bullet-limit-font-and-symbols-and-images = Some fonts
## Custom security level.
## Some custom preferences configuration has placed the user outside one of the standard three levels.
-# Shown in the security level panel as an orange badge next to the expected level.
-security-level-panel-custom-badge = Custom
-# Shown in the security level settings in a warning box.
-security-level-preferences-custom-heading = Custom security level configured
# Description of custom state and recommended action.
# Shown in the security level panel and settings.
security-level-summary-custom = 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.
-# Button to undo custom changes to the security level and place the user in one of the standard security levels.
-# Shown in the security level panel and settings.
-security-level-restore-defaults-button = Restore defaults
+
+## Security level restart prompt.
+
+security-level-restart-prompt-title = Your security level settings require a restart
+security-level-restart-prompt-body = You must restart { -brand-short-name } for your security level settings to be applied. This will close all your windows and tabs.
+security-level-restart-prompt-button-restart = Restart
+security-level-restart-prompt-button-ignore = Ignore
## Notification for dropped operating system support.
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/d3e4ff…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/d3e4ff…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-140.0a1-15.0-1] 3 commits: fixup! Tweaks to the build system
by brizental (@brizental) 12 Jun '25
by brizental (@brizental) 12 Jun '25
12 Jun '25
brizental pushed to branch tor-browser-140.0a1-15.0-1 at The Tor Project / Applications / Tor Browser
Commits:
fadec010 by Beatriz Rizental at 2025-06-12T17:31:27+02:00
fixup! Tweaks to the build system
Make it possible to provide MOZ_PKG_MAC_DSSTORE, MOZ_PKG_MAC_BACKGROUND,
MOZ_PKG_MAC_ICON as environment variables. These files are not where the
build system expects it.
In our build system these files are in
tor-browser-build/projects/browser/Bundle-Data/BaseBrowser.dmg while the
build system expects it to be in browser/branding/tb-*.
For ./mach package to work properly when building for macos we need
these locations. Passing them as env vars is fine for building artifacts
in tbb.
- - - - -
d1bcbcb1 by Beatriz Rizental at 2025-06-12T17:31:27+02:00
fixup! Tweaks to the build system
Disable inclusion of browser/installer/windows into packaged development
artifacts. That file is not included by our builds -- at least not by
the ones done by tor-browser-build.
Attempting to zip the unexisting folder will crash `./mach package` when
building inside tor-browser-build.
- - - - -
315fd439 by Beatriz Rizental at 2025-06-12T17:31:28+02:00
BB 43843: Do not bootstrap MacOS packaging tools if available
Do not bootstrap MacOS packing tools if they are already available in
the environment. This can be uplifted.
- - - - -
3 changed files:
- python/mozbuild/mozbuild/action/make_dmg.py
- toolkit/mozapps/installer/packager.mk
- toolkit/mozapps/installer/upload-files.mk
Changes:
=====================================
python/mozbuild/mozbuild/action/make_dmg.py
=====================================
@@ -7,6 +7,7 @@ import platform
import sys
from pathlib import Path
+import buildconfig
from mozpack import dmg
from mozbuild.bootstrap import bootstrap_toolchain
@@ -46,9 +47,11 @@ def main(args):
)
# Resolve required tools
- dmg_tool = bootstrap_toolchain("dmg/dmg")
- hfs_tool = bootstrap_toolchain("dmg/hfsplus")
- mkfshfs_tool = bootstrap_toolchain("hfsplus/newfs_hfs")
+ dmg_tool = buildconfig.substs.get("DMG_TOOL") or bootstrap_toolchain("dmg/dmg")
+ hfs_tool = buildconfig.substs.get("HFS_TOOL") or bootstrap_toolchain("dmg/hfsplus")
+ mkfshfs_tool = buildconfig.substs.get("MKFSHFS") or bootstrap_toolchain(
+ "hfsplus/newfs_hfs"
+ )
dmg.create_dmg(
source_directory=Path(options.inpath),
=====================================
toolkit/mozapps/installer/packager.mk
=====================================
@@ -135,7 +135,7 @@ make-package: FORCE
$(MAKE) make-package-internal
ifeq (WINNT,$(OS_ARCH))
ifeq ($(MOZ_PKG_FORMAT),ZIP)
- $(MAKE) -C windows ZIP_IN='$(ABS_DIST)/$(PACKAGE)' installer
+# $(MAKE) -C windows ZIP_IN='$(ABS_DIST)/$(PACKAGE)' installer
endif
endif
ifdef MOZ_AUTOMATION
=====================================
toolkit/mozapps/installer/upload-files.mk
=====================================
@@ -205,9 +205,9 @@ ifeq ($(MOZ_PKG_FORMAT),DMG)
_ABS_MOZSRCDIR = $(shell cd $(MOZILLA_DIR) && pwd)
PKG_DMG_SOURCE = $(MOZ_PKG_DIR)
- MOZ_PKG_MAC_DSSTORE=$(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/dsstore
- MOZ_PKG_MAC_BACKGROUND=$(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/background.png
- MOZ_PKG_MAC_ICON=$(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/disk.icns
+ MOZ_PKG_MAC_DSSTORE?=$(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/dsstore
+ MOZ_PKG_MAC_BACKGROUND?=$(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/background.png
+ MOZ_PKG_MAC_ICON?=$(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/disk.icns
INNER_MAKE_PACKAGE = \
$(call py_action,make_dmg, \
$(if $(MOZ_PKG_MAC_DSSTORE),--dsstore '$(MOZ_PKG_MAC_DSSTORE)') \
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/759ac0…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/759ac0…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][main] Bug 41453: Update android toochain for building application-services
by brizental (@brizental) 12 Jun '25
by brizental (@brizental) 12 Jun '25
12 Jun '25
brizental pushed to branch main at The Tor Project / Applications / tor-browser-build
Commits:
df7c4ade by Beatriz Rizental at 2025-06-12T09:12:15+02:00
Bug 41453: Update android toochain for building application-services
- - - - -
13 changed files:
- projects/android-toolchain/config
- projects/application-services/a-s-noop.diff
- − projects/application-services/bug40485-nimbus-fml-reproducibility.diff
- projects/application-services/build
- projects/application-services/config
- projects/application-services/glean-noop.diff
- projects/application-services/gradle-dependencies-list.txt
- projects/application-services/list_toolchain_updates_checks
- projects/application-services/local-repository.diff
- − projects/application-services/ohttp-no-git.diff
- projects/glean/config
- projects/gradle/config
- projects/uniffi-rs/config
Changes:
=====================================
projects/android-toolchain/config
=====================================
@@ -1,7 +1,7 @@
# vim: filetype=yaml sw=2
# FIXME: Rework this whole mess
filename: '[% project %]-[% c("version") %]-[% c("var/build_id") %].tar.[% c("compress_tar") %]'
-version: '34.0.0'
+version: 36.0.0
var:
setup: |
mkdir -p /var/tmp/dist
@@ -29,39 +29,39 @@ var:
export PATH=$PATH:$GRADLE_HOME/bin
[% END -%]
google_repo: https://dl.google.com/android/repository
- android_api_level: 34
- android_release_dir: android-14
- android_platform_revision: '03'
- android_ndk_version: 26
- android_ndk_revision: c
- android_ndk_version_build: 26.2.11394342
+ android_api_level: 36
+ android_release_dir: android-16
+ android_platform_revision: '01'
+ android_ndk_version: 28
+ android_ndk_revision: b
+ android_ndk_version_build: 28.1.13356709
# We need the following two variables for get_build_tools, used by signing
# scripts.
- build_tools_version: 34
- build_tools_filename: 'build-tools_r[% c("var/build_tools_version") %]-linux.zip'
- build_tools_sha512sum: c28dd52f8eca82996726905617f3cb4b0f0aee1334417b450d296991d7112cab1288f5fd42c48a079ba6788218079f81caa3e3e9108e4a6f27163a1eb7f32bd7
- commandlinetools_version: 11076708
- commandlinetools_version_string: 12.0
+ build_tools_version: 36
+ build_tools_filename: 'build-tools_r[% c("var/build_tools_version") %]_linux.zip'
+ build_tools_sha512sum: 277c08c2dcced3c1cc46ce0349e18adc6d6d4b8739868eab5d469f9fb4e9c6b79aa9d2a6592cfc5db0ccb8690fcc75a44db4b18b03dcf349efd0eb54025ef5ca
+ commandlinetools_version: 13114758
+ commandlinetools_version_string: 19.0
input_files:
# Hashes can be compared with https://gitlab.com/fdroid/android-sdk-transparency-log/-/blob/master/checks…
- URL: '[% c("var/google_repo") %]/commandlinetools-linux-[% c("var/commandlinetools_version") %]_latest.zip'
name: android_commandlinetools
- sha256sum: 2d2d50857e4eb553af5a6dc3ad507a17adf43d115264b1afc116f95c92e5e258
+ sha256sum: 7ec965280a073311c339e571cd5de778b9975026cfcbe79f2b1cdcb1e15317ee
- URL: '[% c("var/google_repo") %]/android-ndk-r[% c("var/android_ndk_version") %][% c("var/android_ndk_revision") %]-linux.zip'
name: android_ndk_compiler
- sha256sum: 6d6e659834d28bb24ba7ae66148ad05115ebbad7dabed1af9b3265674774fcf6
+ sha256sum: e9f2759862cecfd48c20bbb7d8cfedbb020f4d91b5f78d9a2fc106f7db3c27ed
- URL: '[% c("var/google_repo") %]/[% c("var/build_tools_filename") %]'
name: build_tools
sha512sum: '[% c("var/build_tools_sha512sum") %]'
- - URL: '[% c("var/google_repo") %]/platform-[% c("var/android_api_level") %]-ext7_r[% c("var/android_platform_revision") %].zip'
+ - URL: '[% c("var/google_repo") %]/platform-[% c("var/android_api_level") %]_r[% c("var/android_platform_revision") %].zip'
name: platform
- sha256sum: 16fdb74c55e59ae3ef52def135aec713508467bd56d7dabcd8c9be31fa8b20f3
+ sha256sum: a5273f7d68de0a6a58032b26c24965634bc14ed3839e70a3a9759369f3f6c02a
# ./mach bootstrap is fetching the latest version, so it does not seem to
# matter which particular version we are using. Pin to the one fitting best to
# SDK version/build-tools version.
- URL: '[% c("var/google_repo") %]/platform-tools_r[% c("version") %]-linux.zip'
name: platform_tools
- sha256sum: 8137c2834dea05cb64c1a8bc041ea00fcd43e3a8a29429ad4f25b8ee51efebf6
+ sha256sum: 0ead642c943ffe79701fccca8f5f1c69c4ce4f43df2eefee553f6ccb27cbfbe8
steps:
# The get_build_tools step is used by tools/signing/android-signing
get_build_tools:
=====================================
projects/application-services/a-s-noop.diff
=====================================
@@ -1,3 +1,48 @@
+diff --git a/build-scripts/component-common.gradle b/build-scripts/component-common.gradle
+index a126341b0..3b3579290 100644
+--- a/build-scripts/component-common.gradle
++++ b/build-scripts/component-common.gradle
+@@ -111,16 +111,30 @@ ext.configureUniFFIBindgen = { crateName ->
+ commandLine "${mozconfig.topobjdir}/dist/host/bin/embedded-uniffi-bindgen", 'generate', '--library', libraryPath, "--crate", crateName, '--language', 'kotlin', '--out-dir', uniffiOutDir.get(), '--no-format'
+ }
+ } else {
+- def libraryPath = megazordNative.asFileTree.matching {
+- include "${nativeRustTarget}/libmegazord.*"
+- }.singleFile
+-
+- if (libraryPath == null) {
+- throw new GradleException("libmegazord dynamic library path not found")
+- }
+- exec {
+- workingDir project.rootDir
+- commandLine '/usr/bin/env', 'cargo', 'uniffi-bindgen', 'generate', '--library', libraryPath, "--crate", crateName, '--language', 'kotlin', '--out-dir', uniffiOutDir.get(), '--no-format'
++ if (crateName.toString().contains("places")) {
++ def libraryPath = megazordNative.asFileTree.matching {
++ include "${nativeRustTarget}/libmegazord.*"
++ }.singleFile
++
++ if (libraryPath == null) {
++ throw new GradleException("libmegazord dynamic library path not found")
++ }
++ exec {
++ workingDir project.rootDir
++ commandLine '/usr/bin/env', 'cargo', 'uniffi-bindgen', 'generate', '--library', libraryPath, "--crate", crateName, '--language', 'kotlin', '--out-dir', uniffiOutDir.get(), '--no-format'
++ }
++ } else {
++ def libraryPath = "${project.rootDir}/megazords/full/target/debug/libmegazord.so"
++ exec {
++ workingDir project.rootDir
++ def command = ["${rootProject.projectDir}/uniffi-rs/uniffi-bindgen", 'generate', '--library', libraryPath, "--crate", crateName, '--language', 'kotlin', '--out-dir', uniffiOutDir.get(), '--no-format']
++ println "Executing command: ${command.join(' ')}"
++ commandLine command
++
++ // Print both stdout and stderr to Gradle's console
++ standardOutput = System.out
++ errorOutput = System.err
++ }
+ }
+ }
+ }
diff --git a/components/fxa-client/android/src/main/java/mozilla/appservices/fxaclient/Config.kt b/components/fxa-client/android/src/main/java/mozilla/appservices/fxaclient/Config.kt
index 78c16dd0a..d2615fa74 100644
--- a/components/fxa-client/android/src/main/java/mozilla/appservices/fxaclient/Config.kt
@@ -9,10 +54,10 @@ index 78c16dd0a..d2615fa74 100644
+ is FxaServer.__NOOP -> ""
}
diff --git a/components/nimbus/android/src/main/java/org/mozilla/experiments/nimbus/Nimbus.kt b/components/nimbus/android/src/main/java/org/mozilla/experiments/nimbus/Nimbus.kt
-index 8c05be9cf..6bf2ec15d 100644
+index 376ef90d9..d80eea50f 100644
--- a/components/nimbus/android/src/main/java/org/mozilla/experiments/nimbus/Nimbus.kt
+++ b/components/nimbus/android/src/main/java/org/mozilla/experiments/nimbus/Nimbus.kt
-@@ -532,6 +532,8 @@ open class Nimbus(
+@@ -535,6 +535,8 @@ open class Nimbus(
),
)
}
@@ -22,10 +67,10 @@ index 8c05be9cf..6bf2ec15d 100644
}
}
diff --git a/megazords/full/Cargo.toml b/megazords/full/Cargo.toml
-index 75f44a812..a51c0643b 100644
+index c5e9eea19..ce899aac1 100644
--- a/megazords/full/Cargo.toml
+++ b/megazords/full/Cargo.toml
-@@ -9,21 +9,21 @@ license = "MPL-2.0"9345a6ea
+@@ -9,24 +9,24 @@ license = "MPL-2.0"
crate-type = ["cdylib"]
[dependencies]
@@ -49,6 +94,8 @@ index 75f44a812..a51c0643b 100644
-crashtest = { path = "../../components/crashtest" }
-error-support = { path = "../../components/support/error" }
-suggest = { path = "../../components/suggest" }
+-search = { path = "../../components/search" }
+-tracing-support = { path = "../../components/support/tracing" }
+# push = { path = "../../components/push" }
+# remote_settings = { path = "../../components/remote_settings" }
+# rust-log-forwarder = { path = "../../components/support/rust-log-forwarder" }
@@ -58,13 +105,16 @@ index 75f44a812..a51c0643b 100644
+# crashtest = { path = "../../components/crashtest" }
+# error-support = { path = "../../components/support/error" }
+# suggest = { path = "../../components/suggest" }
++# search = { path = "../../components/search" }
++# tracing-support = { path = "../../components/support/tracing" }
lazy_static = "1.4"
+ init_rust_components = { path = "../../components/init_rust_components" }
diff --git a/megazords/full/src/lib.rs b/megazords/full/src/lib.rs
-index 01d1be98b..a68eb647f 100644
+index 4b6ba4499..e4fde58ae 100644
--- a/megazords/full/src/lib.rs
+++ b/megazords/full/src/lib.rs
-@@ -8,20 +8,20 @@
+@@ -8,24 +8,24 @@
use std::ffi::CString;
use std::os::raw::c_char;
@@ -72,46 +122,37 @@ index 01d1be98b..a68eb647f 100644
-pub use crashtest;
-pub use error_support;
-pub use fxa_client;
+-pub use init_rust_components;
-pub use logins;
+-pub use merino;
-pub use nimbus;
+// pub use autofill;
+// pub use crashtest;
+// pub use error_support;
+// pub use fxa_client;
++// pub use init_rust_components;
+// pub use logins;
++// pub use merino;
+// pub use nimbus;
pub use places;
-pub use push;
-pub use remote_settings;
-pub use rust_log_forwarder;
+-pub use search;
-pub use suggest;
-pub use sync_manager;
-pub use tabs;
+-pub use tracing_support;
-pub use viaduct;
+// pub use push;
+// pub use remote_settings;
+// pub use rust_log_forwarder;
++// pub use search;
+// pub use suggest;
+// pub use sync_manager;
+// pub use tabs;
++// pub use tracing_support;
+// pub use viaduct;
// TODO: Uncomment this code when webext-storage component is integrated in android
// pub use webext_storage;
-diff --git a/publish.gradle b/publish.gradle
-index 71f5d55b9..659e26489 100644
---- a/publish.gradle
-+++ b/publish.gradle
-@@ -194,7 +194,11 @@ ext.configureUniFFIBindgen = { udlFilePath ->
- def uniffiGeneratedPath = "generated/source/uniffi/${variant.name}/java"
- def t = tasks.register("generate${variant.name.capitalize()}UniFFIBindings", Exec) {
- workingDir project.rootDir
-- commandLine '/usr/bin/env', 'cargo', 'uniffi-bindgen', 'generate', "${project.projectDir}/${udlFilePath}", '--language', 'kotlin', '--out-dir', "${buildDir}/${uniffiGeneratedPath}"
-+ if (udlFilePath.contains("places")) {
-+ commandLine '/usr/bin/env', 'cargo', 'uniffi-bindgen', 'generate', "${project.projectDir}/${udlFilePath}", '--language', 'kotlin', '--out-dir', "${buildDir}/${uniffiGeneratedPath}"
-+ } else {
-+ commandLine '/usr/bin/env', "${rootProject.projectDir}/uniffi-rs/uniffi-bindgen", 'generate', "${project.projectDir}/${udlFilePath}", '--language', 'kotlin', '--out-dir', "${buildDir}/${uniffiGeneratedPath}"
-+ }
- outputs.dir "${buildDir}/${uniffiGeneratedPath}"
- // Re-generate if the interface definition changes.
- inputs.file "${project.projectDir}/${udlFilePath}"
=====================================
projects/application-services/bug40485-nimbus-fml-reproducibility.diff deleted
=====================================
@@ -1,48 +0,0 @@
-diff --git a/components/support/nimbus-fml/src/intermediate_representation.rs b/components/support/nimbus-fml/src/intermediate_representation.rs
-index 97d545672..249406a0c 100644
---- a/components/support/nimbus-fml/src/intermediate_representation.rs
-+++ b/components/support/nimbus-fml/src/intermediate_representation.rs
-@@ -237,10 +237,10 @@ pub struct FeatureManifest {
- pub(crate) about: AboutBlock,
-
- #[serde(default)]
-- pub(crate) imported_features: HashMap<ModuleId, BTreeSet<String>>,
-+ pub(crate) imported_features: BTreeMap<ModuleId, BTreeSet<String>>,
-
- #[serde(default)]
-- pub(crate) all_imports: HashMap<ModuleId, FeatureManifest>,
-+ pub(crate) all_imports: BTreeMap<ModuleId, FeatureManifest>,
- }
-
- impl TypeFinder for FeatureManifest {
-diff --git a/components/support/nimbus-fml/src/parser.rs b/components/support/nimbus-fml/src/parser.rs
-index 49cace525..cdf692b86 100644
---- a/components/support/nimbus-fml/src/parser.rs
-+++ b/components/support/nimbus-fml/src/parser.rs
-@@ -223,7 +223,7 @@ impl Parser {
- &self,
- current: &FilePath,
- channel: Option<&str>,
-- imports: &mut HashMap<ModuleId, FeatureManifest>,
-+ imports: &mut BTreeMap<ModuleId, FeatureManifest>,
- // includes: &mut HashSet<ModuleId>,
- ) -> Result<ModuleId> {
- let id = current.try_into()?;
-@@ -253,7 +253,7 @@ impl Parser {
- // This loop does the work of merging the default blocks back into the imported manifests.
- // We'll then attach all the manifests to the root (i.e. the one we're generating code for today), in `imports`.
- // We associate only the feature ids with the manifest we're loading in this method.
-- let mut imported_feature_id_map = HashMap::new();
-+ let mut imported_feature_id_map = BTreeMap::new();
-
- for block in &frontend.imports {
- // 1. Load the imported manifests in to the hash map.
-@@ -328,7 +328,7 @@ impl Parser {
- &self,
- channel: Option<&str>,
- ) -> Result<FeatureManifest, FMLError> {
-- let mut manifests = HashMap::new();
-+ let mut manifests = BTreeMap::new();
- let id = self.load_imports(&self.source, channel, &mut manifests)?;
- let mut fm = manifests
- .remove(&id)
=====================================
projects/application-services/build
=====================================
@@ -6,8 +6,8 @@
gradle_tarfile => c("input_files_by_name/gradle"),
}) %]
pushd /var/tmp/dist/android-toolchain/android-sdk-linux/build-tools/
-unzip $rootdir/'[% c("input_files_by_name/build_tools_30") %]'
-mv android-11 30.0.3
+unzip $rootdir/'[% c("input_files_by_name/build_tools_35") %]'
+mv android-15 35.0
popd
distdir=/var/tmp/dist/[% project %]
@@ -73,6 +73,19 @@ tar -xf $rootdir/[% c('input_files_by_name/uniffi-rs') %]
# latest versions of dependencies sometimes.
sed -i '$ s/$/-TORBROWSER/' version.txt
+pushd libs
+ln -s $rootdir/[% c("input_files_by_name/nss") %] ./
+ln -s $rootdir/bug_13028.patch
+patch -p2 < $rootdir/apply-bug-13028.diff
+patch -p2 < $rootdir/no-ndk-lookup.diff
+./build-all.sh desktop
+./build-all.sh android
+popd
+
+pushd megazords/full
+NSS_DIR=$(pwd)/../../libs/desktop/linux-x86-64/nss cargo build --target-dir=$(pwd)/target
+popd
+
cp $rootdir/cargo-no-build.sh megazords/full/android/
chmod +x megazords/full/android/cargo-no-build.sh
patch -p1 < $rootdir/a-s-noop.diff
@@ -83,15 +96,6 @@ cp $builddir/uniffi-rs/uniffi-bindgen glean-core/android-native/
patch -p1 < $rootdir/glean-noop.diff
popd
-pushd libs
-ln -s $rootdir/[% c("input_files_by_name/nss") %] ./
-ln -s $rootdir/bug_13028.patch
-patch -p2 < $rootdir/apply-bug-13028.diff
-patch -p2 < $rootdir/no-ndk-lookup.diff
-./build-all.sh desktop
-./build-all.sh android
-popd
-
[% IF c('var/fetch_gradle_dependencies') %]
# XXX: `assemble` is still not enough to see all fetched dependencies via
# Gradle's --debug. See: tor-browser-build#40056.
@@ -104,8 +108,6 @@ popd
export LC_ALL=C.UTF-8
export LANG=C.UTF-8
patch -p1 < $rootdir/local-repository.diff
- patch -p1 < $rootdir/ohttp-no-git.diff
- patch -p1 < $rootdir/bug40485-nimbus-fml-reproducibility.diff
patch -p1 < $rootdir/offline-nimbus-fml.diff
patch -p1 < $rootdir/41422-disable-viaduct.diff
gradle_flags="--offline --no-daemon"
=====================================
projects/application-services/config
=====================================
@@ -1,6 +1,6 @@
# vim: filetype=yaml sw=2
-version: 128.0.1
-git_hash: 0e4777f4133a98f44bca164fcf0406e8f706dbe4
+version: 140.0
+git_hash: 8ee6cb6a23f96ff8e2161926441aea72d6f09249
git_url: https://github.com/mozilla/application-services
git_submodule: 1
container:
@@ -8,11 +8,11 @@ container:
var:
# This should be updated when the list of gradle dependencies is changed.
- gradle_dependencies_version: 11
- gradle_version: 8.8
- glean_parser: 14.0.1
- nss_version: '3.101'
- nspr_version: '4.35'
+ gradle_dependencies_version: 12
+ gradle_version: 8.13
+ glean_parser: 17.1.0
+ nss_version: '3.110'
+ nspr_version: '4.36'
steps:
build:
@@ -21,7 +21,7 @@ steps:
# Due to some issue in application-service causing non matching
# builds when build time differ a lot, we need to do periodic rebuilds:
# https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/merge_re…
- rebuild_date: '2024-09-12'
+ rebuild_date: '2025-06-12'
arch_deps:
# Needed to build NSS
- gyp
@@ -43,16 +43,16 @@ steps:
- project: uniffi-rs
name: uniffi-rs
pkg_type: build
- # Only Application Services currently requires build tools 30.0.3.
+ # Only Application Services currently requires build tools 35.
# So, download them only here, rather than adding them to the shared
# toolchain.
- - URL: 'https://dl.google.com/android/repository/build-tools_r30.0.3-linux.zip'
- name: build_tools_30
- sha256sum: 24593500aa95d2f99fb4f10658aae7e65cb519be6cd33fa164f15f27f3c4a2d6
+ - URL: 'https://dl.google.com/android/repository/build-tools_r35_linux.zip'
+ name: build_tools_35
+ sha256sum: bd3a4966912eb8b30ed0d00b0cda6b6543b949d5ffe00bea54c04c81e1561d88
# NSS version ans sha256 are in libs/build-all.sh.
- URL: 'https://ftp.mozilla.org/pub/security/nss/releases/NSS_[% c("var/nss_version") | replace("\\.", "_") %]_RTM/src/nss-[% c("var/nss_version") %]-with-nspr-[% c("var/nspr_version") %].tar.gz'
name: nss
- sha256sum: 566faa9283ff3d9a7d6c44272df6e4330e3e06ca4e841a68840d31b27c9161c4
+ sha256sum: 96114bef9e9692dda6e7793da26fedef0dc0449c3644ebe1122464e599a39dc0
- filename: 'gradle-dependencies-[% c("var/gradle_dependencies_version") %]'
name: gradle-dependencies
exec: '[% INCLUDE "fetch-gradle-dependencies" %]'
@@ -65,19 +65,14 @@ steps:
project: application-services
pkg_type: cargo_vendor
norec:
- sha256sum: 92ce1131b35bf78d86362670ba7e4930ecdd1117694996d7cce850e4860f90b6
+ sha256sum: 4cf0235b525b5ce1d8bcc40e9763a13fd0e79c773749b900b4d7420551cf5114
- filename: local-repository.diff
enable: '[% !c("var/fetch_gradle_dependencies") %]'
- filename: gen_gradle_deps_file.sh
enable: '[% c("var/fetch_gradle_dependencies") %]'
- filename: bug_13028.patch
- filename: apply-bug-13028.diff
- # Delete when this patch is included upstream
- - filename: bug40485-nimbus-fml-reproducibility.diff
- filename: no-ndk-lookup.diff
- # as-ohttp-client lists both the version for ohttp and a git repo + rev,
- # but this breaks the vendoring for offline builds.
- - filename: ohttp-no-git.diff
- filename: offline-nimbus-fml.diff
- filename: glean-noop.diff
- filename: a-s-noop.diff
=====================================
projects/application-services/glean-noop.diff
=====================================
@@ -1,8 +1,8 @@
diff --git a/glean-core/android-native/build.gradle b/glean-core/android-native/build.gradle
-index 48769651..ade00ec3 100644
+index 53716d523..d38af8320 100644
--- a/glean-core/android-native/build.gradle
+++ b/glean-core/android-native/build.gradle
-@@ -57,6 +57,8 @@ android {
+@@ -48,6 +48,8 @@ android {
}
cargo {
@@ -12,15 +12,15 @@ index 48769651..ade00ec3 100644
module = '../bundle-android'
diff --git a/glean-core/android/build.gradle b/glean-core/android/build.gradle
-index 3568e40b..05f28296 100644
+index 7748378b4..5af06b1c3 100644
--- a/glean-core/android/build.gradle
+++ b/glean-core/android/build.gradle
-@@ -221,7 +221,7 @@ android.libraryVariants.all { variant ->
- def udlFilePath = "../src/glean.udl"
- def t = tasks.register("generate${variant.name.capitalize()}UniFFIBindings", Exec) {
- workingDir project.rootDir
-- commandLine 'cargo', 'uniffi-bindgen', 'generate', '--no-format', "${project.projectDir}/${udlFilePath}", '--language', 'kotlin', '--out-dir', "${buildDir}/${uniffiGeneratedPath}"
-+ commandLine '${rootDir}/glean-core/android-native/uniffi-bindgen', 'generate', '--no-format', "${project.projectDir}/${udlFilePath}", '--language', 'kotlin', '--out-dir', "${buildDir}/${uniffiGeneratedPath}"
- outputs.dir "${buildDir}/${uniffiGeneratedPath}"
- // Re-generate if the interface definition changes.
- inputs.file "${project.projectDir}/../src/glean.udl"
+@@ -197,7 +197,7 @@ def generateUniffiBindings = tasks.register("generateUniffiBindings") {
+ doFirst {
+ exec {
+ workingDir project.rootDir
+- commandLine 'cargo', 'uniffi-bindgen', 'generate', '--no-format', "${project.projectDir}/${udlFilePath}", '--language', 'kotlin', '--out-dir', UNIFFI_OUT_DIR.get()
++ commandLine '${rootDir}/glean-core/android-native/uniffi-bindgen', 'generate', '--no-format', "${project.projectDir}/${udlFilePath}", '--language', 'kotlin', '--out-dir', UNIFFI_OUT_DIR.get()
+ }
+ }
+ outputs.dir UNIFFI_OUT_DIR
=====================================
projects/application-services/gradle-dependencies-list.txt
=====================================
The diff for this file was not included because it is too large.
=====================================
projects/application-services/list_toolchain_updates_checks
=====================================
@@ -8,7 +8,7 @@ if (m/^\\s*compileSdkVersion:\\s([^"]*),/) {
}
EOF
needed=$(cat build.gradle | perl -ne "$p")
-current=34
+current='[% pc("android-toolchain", "version") %]'
check_update_needed compileSdkVersion "$needed" "$current"
@@ -48,12 +48,12 @@ current='[% c("var/nss_version") %]-with-nspr-[% c("var/nspr_version") %]'
check_update_needed nss-nspr "$needed" "$current"
# uniffi
-read -d '' p << 'EOF' || true
-if (m/^\\s*uniffi\\s*=\\s*"([^"]*)"/) {
- print $1;
- exit;
-}
-EOF
-needed=$(cat Cargo.toml | perl -ne "$p")
+p='while (<>) {
+ if (m/^\s*uniffi\s*=\s*\{[^}]*version\s*=\s*"([^"]*)"/) {
+ print $1;
+ exit;
+ }
+}'
+needed=$(cat components/autofill/Cargo.toml | perl -ne "$p")
current='[% pc("uniffi-rs", "version") %]'
check_update_needed uniffi "$needed" "$current"
=====================================
projects/application-services/local-repository.diff
=====================================
@@ -1,10 +1,10 @@
diff --git a/build.gradle b/build.gradle
-index b22a0737..d335aa5b 100644
+index bd516e58c..b30d552c9 100644
--- a/build.gradle
+++ b/build.gradle
-@@ -39,6 +39,13 @@ buildscript {
- ]
-
+@@ -14,6 +14,14 @@ buildscript {
+ }
+
repositories {
+ maven {
+ url "file:///var/tmp/dist/gradle-dependencies"
@@ -13,11 +13,12 @@ index b22a0737..d335aa5b 100644
+ mavenPom()
+ }
+ }
++
mavenCentral()
google()
- jcenter()
-@@ -88,6 +95,13 @@ apply plugin: 'de.undercouch.download'
-
+ maven {
+@@ -59,6 +67,14 @@ plugins {
+
allprojects {
repositories {
+ maven {
@@ -27,11 +28,12 @@ index b22a0737..d335aa5b 100644
+ mavenPom()
+ }
+ }
++
google()
- jcenter()
+ mavenCentral()
maven {
diff --git a/settings.gradle b/settings.gradle
-index f652bd02..8c30a368 100644
+index 4117bc0d3..f1b028dad 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,6 +1,19 @@
@@ -52,37 +54,39 @@ index f652bd02..8c30a368 100644
+}
+
import org.yaml.snakeyaml.Yaml
-
- includeBuild('tools/nimbus-gradle-plugin') {
-@@ -14,6 +27,13 @@ buildscript {
- classpath 'org.yaml:snakeyaml:2.0'
- }
- repositories {
-+ maven {
-+ url "file:///var/tmp/dist/gradle-dependencies"
-+ metadataSources {
-+ gradleMetadata()
-+ mavenPom()
+
+ // We prefer `appServicesRootDir` over `rootDir` to help us on the path to the monorepo.
+@@ -23,6 +36,14 @@ buildscript {
+ if (!gradle.root.hasProperty("mozconfig")) {
+ // in app-services
+ repositories {
++ maven {
++ url "file:///var/tmp/dist/gradle-dependencies"
++ metadataSources {
++ gradleMetadata()
++ mavenPom()
++ }
+ }
-+ }
- jcenter()
- }
- }
++
+ mavenCentral()
+ }
+ } else {
diff --git a/tools/nimbus-gradle-plugin/settings.gradle b/tools/nimbus-gradle-plugin/settings.gradle
-index 7db19be0..1f36b991 100644
+index 7d907f057..c0d213503 100644
--- a/tools/nimbus-gradle-plugin/settings.gradle
+++ b/tools/nimbus-gradle-plugin/settings.gradle
-@@ -8,6 +8,13 @@ buildscript {
- classpath 'org.yaml:snakeyaml:2.0'
- }
- repositories {
-+ maven {
-+ url "file:///var/tmp/dist/gradle-dependencies"
-+ metadataSources {
-+ gradleMetadata()
-+ mavenPom()
+@@ -14,6 +14,14 @@ buildscript {
+ if (!gradle.root.hasProperty("mozconfig")) {
+ // in app-services
+ repositories {
++ maven {
++ url "file:///var/tmp/dist/gradle-dependencies"
++ metadataSources {
++ gradleMetadata()
++ mavenPom()
++ }
+ }
-+ }
- jcenter()
- }
- }
++
+ mavenCentral()
+ }
+ } else {
=====================================
projects/application-services/ohttp-no-git.diff deleted
=====================================
@@ -1,15 +0,0 @@
-diff --git a/components/as-ohttp-client/Cargo.toml b/components/as-ohttp-client/Cargo.toml
-index fd45a058b..b7070fc15 100644
---- a/components/as-ohttp-client/Cargo.toml
-+++ b/components/as-ohttp-client/Cargo.toml
-@@ -19,8 +19,8 @@ rusqlite = { workspace = true, features = ["bundled"] }
- [dependencies.ohttp]
- version = "0.4"
- default-features = false
--git = "https://github.com/martinthomson/ohttp.git"
--rev = "fc3f4c787d1f6a6a87bf5194f7152cc906b02973"
-+# git = "https://github.com/martinthomson/ohttp.git"
-+# rev = "fc3f4c787d1f6a6a87bf5194f7152cc906b02973"
- features = ["client", "server", "app-svc", "external-sqlite"]
-
- [build-dependencies]
=====================================
projects/glean/config
=====================================
@@ -19,13 +19,15 @@ var:
- torsocks
glean_wheels_sha256sum:
14.0.1: 95f01d0e158ae7968680029b7d9c2908895e1d1ec1ba4e6a044648a05af4ee0c
+ 17.1.0: d96ef1995676fc3b838efb84283294befd5060ca64eea94fb67ae1f41d422664
glean_wheels_url:
14.0.1: https://build-sources.tbb.torproject.org/glean-wheels-14.0.1.tar.xz
+ 17.1.0: https://build-sources.tbb.torproject.org/glean-wheels-17.1.0.tar.xz
steps:
create_glean_deps_tarball:
git_url: ''
- version: 14.0.1
+ version: 17.1.0
filename: 'glean-wheels-[% c("version") %].tar.xz'
build_log: '-'
container:
=====================================
projects/gradle/config
=====================================
@@ -7,6 +7,7 @@ container:
var:
gradle_sha256sum:
8.8: a4b4158601f8636cdeeab09bd76afb640030bb5b144aafe261a5e8af027dc612
+ 8.13: 20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78
input_files:
- project: container-image
=====================================
projects/uniffi-rs/config
=====================================
@@ -1,7 +1,7 @@
# vim: filetype=yaml sw=2
-version: 0.27.1
-git_hash: bfb52effb0292f16d4c030d622887781639cbd1f
-git_url: https://gitlab.torproject.org/tpo/applications/uniffi-rs.git
+version: 0.29.0
+git_hash: c69eba9eb08b16a66aab7b25df6c55d0b60541db
+git_url: https://gitlab.torproject.org/tpo/applications/uniffi-rs
container:
use_container: 1
@@ -17,4 +17,4 @@ steps:
project: uniffi-rs
pkg_type: cargo_vendor
norec:
- sha256sum: 12a61a3dbe65a3946ed58fff3d849d11c9ade5818e2ef5771d2a2a531e25f553
+ sha256sum: 23c79da9169064f42c34b9dadfee9dcf5ed251f9843615ca3e9a0e59a49f581d
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/d…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/d…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-140.0a1-15.0-1] fixup! TB 2176: Rebrand Firefox to TorBrowser
by Pier Angelo Vendrame (@pierov) 11 Jun '25
by Pier Angelo Vendrame (@pierov) 11 Jun '25
11 Jun '25
Pier Angelo Vendrame pushed to branch tor-browser-140.0a1-15.0-1 at The Tor Project / Applications / Tor Browser
Commits:
759ac06a by Pier Angelo Vendrame at 2025-06-11T16:02:37+02:00
fixup! TB 2176: Rebrand Firefox to TorBrowser
TB 43855: Set Android branding for l10n merging.
- - - - -
1 changed file:
- mobile/android/locales/l10n.toml
Changes:
=====================================
mobile/android/locales/l10n.toml
=====================================
@@ -124,6 +124,18 @@ exclude-multi-locale = [
reference = "mobile/android/locales/en-US/**"
l10n = "{l}mobile/android/**"
+[[paths]]
+ reference = "mobile/android/branding/tb-alpha/locales/en-US/**"
+ l10n = "{l}mobile/android/branding/tb-alpha/**"
+
+[[paths]]
+ reference = "mobile/android/branding/tb-nightly/locales/en-US/**"
+ l10n = "{l}mobile/android/branding/tb-nightly/**"
+
+[[paths]]
+ reference = "mobile/android/branding/tb-release/locales/en-US/**"
+ l10n = "{l}mobile/android/branding/tb-release/**"
+
# hand-picked paths from toolkit, keep in sync with jar.mn
[[paths]]
reference = "dom/locales/en-US/chrome/accessibility/AccessFu.properties"
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/759ac06…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/759ac06…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][main] 7 commits: Bug 41457: Set mar IDs in tor-browser-build.
by Pier Angelo Vendrame (@pierov) 11 Jun '25
by Pier Angelo Vendrame (@pierov) 11 Jun '25
11 Jun '25
Pier Angelo Vendrame pushed to branch main at The Tor Project / Applications / tor-browser-build
Commits:
b428d050 by Pier Angelo Vendrame at 2025-06-11T18:05:45+02:00
Bug 41457: Set mar IDs in tor-browser-build.
We used to have a logic to set the mar IDs in confvars.sh but, from
Firefox 137, that file can contains only key/value without any logic.
- - - - -
90cccdd8 by Pier Angelo Vendrame at 2025-06-11T19:49:57+02:00
Bug 41448: Update toolchains for ESR140 (common).
Switch to Clang 19.1.7, Rust 1.86.0, nasm 2.16.03.
- - - - -
3e26939c by Pier Angelo Vendrame at 2025-06-11T19:49:57+02:00
Bug 41448: Update toolchains for ESR140 (macOS).
macOS specific updates for Firefox 140.
- - - - -
fad83635 by Pier Angelo Vendrame at 2025-06-11T19:49:58+02:00
Bug 41448: Update toolchains for ESR140 (Windows).
Windows specific updates for Firefox 140.
- - - - -
78708705 by Pier Angelo Vendrame at 2025-06-11T19:49:58+02:00
Bug 41458: Ship geckodriver only on Linux.
Geckodriver is now a host tool for Firefox.
- - - - -
23f1ab0d by Pier Angelo Vendrame at 2025-06-11T19:49:58+02:00
Bug 41478: Include vim in our containers.
Also, clean a little bit some other dependencies.
- - - - -
ba52be71 by Nicolas Vigier at 2025-06-11T19:49:59+02:00
Bug 41448: Check for more toolchain updates
- - - - -
35 changed files:
- projects/cbindgen/config
- projects/cctools/build
- + projects/cctools/cctools.patch
- projects/cctools/config
- projects/clang/build
- projects/clang/config
- + projects/clang/gnullvm-environment.diff
- projects/common/list_toolchain_updates-common-firefox-geckoview
- projects/firefox/build
- projects/firefox/config
- projects/firefox/list_toolchain_updates_checks
- projects/firefox/mozconfig
- projects/firefox/nsis-uninstall.patch
- projects/geckoview/config
- projects/llvm-project/config
- projects/macosx-toolchain/config
- projects/macosx-toolchain/macpkg.py
- projects/mingw-w64-clang/build
- projects/mingw-w64-clang/config
- − projects/mingw-w64-clang/mingw-composition.patch
- projects/mingw-w64-clang/mingw-dwrite_3.patch
- + projects/mingw-w64-clang/mingw-foundation_redef.patch
- − projects/mingw-w64-clang/mingw-unknown.patch
- projects/mingw-w64-clang/mingw-webrtc.patch
- projects/mmdebstrap-image/config
- projects/nasm/config
- projects/node/config
- projects/rust/build
- − projects/rust/chkstk.patch
- projects/rust/config
- projects/wasi-sysroot/config
- projects/wasi-sysroot/wasi-sdk.patch
- − projects/windows-rs/build
- − projects/windows-rs/config
- rbm.conf
The diff was not included because it is too large.
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/compare/…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/compare/…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][maint-13.5] Bug 41477: Update keyring/boklm.gpg for new subkeys (2025)
by boklm (@boklm) 11 Jun '25
by boklm (@boklm) 11 Jun '25
11 Jun '25
boklm pushed to branch maint-13.5 at The Tor Project / Applications / tor-browser-build
Commits:
6a8e243d by Nicolas Vigier at 2025-06-11T14:08:54+02:00
Bug 41477: Update keyring/boklm.gpg for new subkeys (2025)
- - - - -
1 changed file:
- keyring/boklm.gpg
Changes:
=====================================
keyring/boklm.gpg
=====================================
Binary files a/keyring/boklm.gpg and b/keyring/boklm.gpg differ
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/6…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/6…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][maint-14.5] Bug 41477: Update keyring/boklm.gpg for new subkeys (2025)
by boklm (@boklm) 11 Jun '25
by boklm (@boklm) 11 Jun '25
11 Jun '25
boklm pushed to branch maint-14.5 at The Tor Project / Applications / tor-browser-build
Commits:
5aaf9bba by Nicolas Vigier at 2025-06-11T14:08:04+02:00
Bug 41477: Update keyring/boklm.gpg for new subkeys (2025)
- - - - -
1 changed file:
- keyring/boklm.gpg
Changes:
=====================================
keyring/boklm.gpg
=====================================
Binary files a/keyring/boklm.gpg and b/keyring/boklm.gpg differ
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/5…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/5…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][main] Bug 41477: Update keyring/boklm.gpg for new subkeys (2025)
by boklm (@boklm) 11 Jun '25
by boklm (@boklm) 11 Jun '25
11 Jun '25
boklm pushed to branch main at The Tor Project / Applications / tor-browser-build
Commits:
2010ef74 by Nicolas Vigier at 2025-06-11T12:14:53+02:00
Bug 41477: Update keyring/boklm.gpg for new subkeys (2025)
- - - - -
1 changed file:
- keyring/boklm.gpg
Changes:
=====================================
keyring/boklm.gpg
=====================================
Binary files a/keyring/boklm.gpg and b/keyring/boklm.gpg differ
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/2…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/2…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-140.0a1-15.0-1] fixup! BB 42758: Fix WebRTC build errors.
by Pier Angelo Vendrame (@pierov) 11 Jun '25
by Pier Angelo Vendrame (@pierov) 11 Jun '25
11 Jun '25
Pier Angelo Vendrame pushed to branch tor-browser-140.0a1-15.0-1 at The Tor Project / Applications / Tor Browser
Commits:
81d916f0 by Pier Angelo Vendrame at 2025-06-11T08:57:46+02:00
fixup! BB 42758: Fix WebRTC build errors.
MB 440: Change abseil's thread mode.
Remove pthread again and use win32 threads also when building with
mingw.
- - - - -
5 changed files:
- media/libaom/moz.build
- third_party/abseil-cpp/absl/base/base_gn/moz.build
- third_party/abseil-cpp/absl/base/internal/thread_identity.cc
- third_party/abseil-cpp/absl/base/internal/thread_identity.h
- third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.cc
Changes:
=====================================
media/libaom/moz.build
=====================================
@@ -143,6 +143,3 @@ LOCAL_INCLUDES += [
# TEST_DIRS += [
# 'test/fuzztest'
# ]
-
-if CONFIG["OS_TARGET"] == "WINNT" and CONFIG["CC_TYPE"] == "clang":
- OS_LIBS += ["pthread"]
=====================================
third_party/abseil-cpp/absl/base/base_gn/moz.build
=====================================
@@ -168,7 +168,4 @@ if CONFIG["OS_TARGET"] == "Linux" and CONFIG["TARGET_CPU"] == "x86_64":
DEFINES["_GNU_SOURCE"] = True
-if CONFIG["OS_TARGET"] == "WINNT" and CONFIG["CC_TYPE"] == "clang":
- OS_LIBS += ["pthread"]
-
Library("base_gn")
=====================================
third_party/abseil-cpp/absl/base/internal/thread_identity.cc
=====================================
@@ -14,7 +14,7 @@
#include "absl/base/internal/thread_identity.h"
-#if !defined(_WIN32) || defined(__MINGW32__)
+#if !defined(_WIN32)
#include <pthread.h>
#ifndef __wasi__
// WASI does not provide this header, either way we disable use
=====================================
third_party/abseil-cpp/absl/base/internal/thread_identity.h
=====================================
@@ -217,7 +217,7 @@ void ClearCurrentThreadIdentity();
#error ABSL_THREAD_IDENTITY_MODE cannot be directly set
#elif defined(ABSL_FORCE_THREAD_IDENTITY_MODE)
#define ABSL_THREAD_IDENTITY_MODE ABSL_FORCE_THREAD_IDENTITY_MODE
-#elif defined(_WIN32) && !defined(__MINGW32__)
+#elif defined(_WIN32)
#define ABSL_THREAD_IDENTITY_MODE ABSL_THREAD_IDENTITY_MODE_USE_CPP11
#elif defined(__APPLE__) && defined(ABSL_HAVE_THREAD_LOCAL)
#define ABSL_THREAD_IDENTITY_MODE ABSL_THREAD_IDENTITY_MODE_USE_CPP11
=====================================
third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.cc
=====================================
@@ -370,7 +370,7 @@ HRESULT WgcCaptureSession::ProcessFrame() {
return hr;
}
- ComPtr<ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>
+ ComPtr<Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>
direct3DDxgiInterfaceAccess;
hr = d3d_surface->QueryInterface(IID_PPV_ARGS(&direct3DDxgiInterfaceAccess));
if (FAILED(hr)) {
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/81d916f…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/81d916f…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][base-browser-140.0a1-15.0-1] fixup! BB 42758: Fix WebRTC build errors.
by Pier Angelo Vendrame (@pierov) 11 Jun '25
by Pier Angelo Vendrame (@pierov) 11 Jun '25
11 Jun '25
Pier Angelo Vendrame pushed to branch base-browser-140.0a1-15.0-1 at The Tor Project / Applications / Tor Browser
Commits:
75272211 by Pier Angelo Vendrame at 2025-06-11T08:56:12+02:00
fixup! BB 42758: Fix WebRTC build errors.
MB 440: Change abseil's thread mode.
Remove pthread again and use win32 threads also when building with
mingw.
- - - - -
5 changed files:
- media/libaom/moz.build
- third_party/abseil-cpp/absl/base/base_gn/moz.build
- third_party/abseil-cpp/absl/base/internal/thread_identity.cc
- third_party/abseil-cpp/absl/base/internal/thread_identity.h
- third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.cc
Changes:
=====================================
media/libaom/moz.build
=====================================
@@ -143,6 +143,3 @@ LOCAL_INCLUDES += [
# TEST_DIRS += [
# 'test/fuzztest'
# ]
-
-if CONFIG["OS_TARGET"] == "WINNT" and CONFIG["CC_TYPE"] == "clang":
- OS_LIBS += ["pthread"]
=====================================
third_party/abseil-cpp/absl/base/base_gn/moz.build
=====================================
@@ -168,7 +168,4 @@ if CONFIG["OS_TARGET"] == "Linux" and CONFIG["TARGET_CPU"] == "x86_64":
DEFINES["_GNU_SOURCE"] = True
-if CONFIG["OS_TARGET"] == "WINNT" and CONFIG["CC_TYPE"] == "clang":
- OS_LIBS += ["pthread"]
-
Library("base_gn")
=====================================
third_party/abseil-cpp/absl/base/internal/thread_identity.cc
=====================================
@@ -14,7 +14,7 @@
#include "absl/base/internal/thread_identity.h"
-#if !defined(_WIN32) || defined(__MINGW32__)
+#if !defined(_WIN32)
#include <pthread.h>
#ifndef __wasi__
// WASI does not provide this header, either way we disable use
=====================================
third_party/abseil-cpp/absl/base/internal/thread_identity.h
=====================================
@@ -217,7 +217,7 @@ void ClearCurrentThreadIdentity();
#error ABSL_THREAD_IDENTITY_MODE cannot be directly set
#elif defined(ABSL_FORCE_THREAD_IDENTITY_MODE)
#define ABSL_THREAD_IDENTITY_MODE ABSL_FORCE_THREAD_IDENTITY_MODE
-#elif defined(_WIN32) && !defined(__MINGW32__)
+#elif defined(_WIN32)
#define ABSL_THREAD_IDENTITY_MODE ABSL_THREAD_IDENTITY_MODE_USE_CPP11
#elif defined(__APPLE__) && defined(ABSL_HAVE_THREAD_LOCAL)
#define ABSL_THREAD_IDENTITY_MODE ABSL_THREAD_IDENTITY_MODE_USE_CPP11
=====================================
third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.cc
=====================================
@@ -370,7 +370,7 @@ HRESULT WgcCaptureSession::ProcessFrame() {
return hr;
}
- ComPtr<ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>
+ ComPtr<Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>
direct3DDxgiInterfaceAccess;
hr = d3d_surface->QueryInterface(IID_PPV_ARGS(&direct3DDxgiInterfaceAccess));
if (FAILED(hr)) {
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/7527221…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/7527221…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/mullvad-browser][mullvad-browser-140.0a1-15.0-1] fixup! BB 42758: Fix WebRTC build errors.
by Pier Angelo Vendrame (@pierov) 11 Jun '25
by Pier Angelo Vendrame (@pierov) 11 Jun '25
11 Jun '25
Pier Angelo Vendrame pushed to branch mullvad-browser-140.0a1-15.0-1 at The Tor Project / Applications / Mullvad Browser
Commits:
623f30c0 by Pier Angelo Vendrame at 2025-06-11T08:50:46+02:00
fixup! BB 42758: Fix WebRTC build errors.
MB 440: Change abseil's thread mode.
Remove pthread again and use win32 threads also when building with
mingw.
- - - - -
5 changed files:
- media/libaom/moz.build
- third_party/abseil-cpp/absl/base/base_gn/moz.build
- third_party/abseil-cpp/absl/base/internal/thread_identity.cc
- third_party/abseil-cpp/absl/base/internal/thread_identity.h
- third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.cc
Changes:
=====================================
media/libaom/moz.build
=====================================
@@ -143,6 +143,3 @@ LOCAL_INCLUDES += [
# TEST_DIRS += [
# 'test/fuzztest'
# ]
-
-if CONFIG["OS_TARGET"] == "WINNT" and CONFIG["CC_TYPE"] == "clang":
- OS_LIBS += ["pthread"]
=====================================
third_party/abseil-cpp/absl/base/base_gn/moz.build
=====================================
@@ -168,7 +168,4 @@ if CONFIG["OS_TARGET"] == "Linux" and CONFIG["TARGET_CPU"] == "x86_64":
DEFINES["_GNU_SOURCE"] = True
-if CONFIG["OS_TARGET"] == "WINNT" and CONFIG["CC_TYPE"] == "clang":
- OS_LIBS += ["pthread"]
-
Library("base_gn")
=====================================
third_party/abseil-cpp/absl/base/internal/thread_identity.cc
=====================================
@@ -14,7 +14,7 @@
#include "absl/base/internal/thread_identity.h"
-#if !defined(_WIN32) || defined(__MINGW32__)
+#if !defined(_WIN32)
#include <pthread.h>
#ifndef __wasi__
// WASI does not provide this header, either way we disable use
=====================================
third_party/abseil-cpp/absl/base/internal/thread_identity.h
=====================================
@@ -217,7 +217,7 @@ void ClearCurrentThreadIdentity();
#error ABSL_THREAD_IDENTITY_MODE cannot be directly set
#elif defined(ABSL_FORCE_THREAD_IDENTITY_MODE)
#define ABSL_THREAD_IDENTITY_MODE ABSL_FORCE_THREAD_IDENTITY_MODE
-#elif defined(_WIN32) && !defined(__MINGW32__)
+#elif defined(_WIN32)
#define ABSL_THREAD_IDENTITY_MODE ABSL_THREAD_IDENTITY_MODE_USE_CPP11
#elif defined(__APPLE__) && defined(ABSL_HAVE_THREAD_LOCAL)
#define ABSL_THREAD_IDENTITY_MODE ABSL_THREAD_IDENTITY_MODE_USE_CPP11
=====================================
third_party/libwebrtc/modules/desktop_capture/win/wgc_capture_session.cc
=====================================
@@ -370,7 +370,7 @@ HRESULT WgcCaptureSession::ProcessFrame() {
return hr;
}
- ComPtr<ABI::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>
+ ComPtr<Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>
direct3DDxgiInterfaceAccess;
hr = d3d_surface->QueryInterface(IID_PPV_ARGS(&direct3DDxgiInterfaceAccess));
if (FAILED(hr)) {
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/623…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/623…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/mullvad-browser][mullvad-browser-140.0a1-15.0-1] 5 commits: fixup! BB 40925: Implemented the Security Level component
by ma1 (@ma1) 10 Jun '25
by ma1 (@ma1) 10 Jun '25
10 Jun '25
ma1 pushed to branch mullvad-browser-140.0a1-15.0-1 at The Tor Project / Applications / Mullvad Browser
Commits:
c34e1a81 by Henry Wilkes at 2025-06-10T19:01:47+02:00
fixup! BB 40925: Implemented the Security Level component
TB 43783: Prompt user for a restart if their security level preferences
are not aligned at startup or mid-session.
Also handle failures to apply NoScript settings.
- - - - -
1c46cb4e by Henry Wilkes at 2025-06-10T19:01:49+02:00
fixup! BB 40069: Add helpers for message passing with extensions
TB 43783: Allow the browser to wait for the NoScript settings to be
applied.
- - - - -
418e8ab4 by Henry Wilkes at 2025-06-10T19:01:51+02:00
fixup! Base Browser strings
TB 43783: Add security level prompt strings.
- - - - -
87bc322d by Henry Wilkes at 2025-06-10T19:01:53+02:00
fixup! Base Browser strings
TB 43782: Add strings for new security level UX flow.
- - - - -
4fe56d8e by Henry Wilkes at 2025-06-10T19:01:55+02:00
fixup! BB 40925: Implemented the Security Level component
TB 43782: Update security level UI for new UX flow.
In addition, we drop the distinction between the security levels in the
UI when the user has a custom security level.
I.e. we always show shield as unfilled but with a yellow dot in the
toolbar, and we just call it "Custom" rather than "Standard Custom",
etc.
- - - - -
17 changed files:
- browser/components/BrowserGlue.sys.mjs
- + browser/components/securitylevel/SecurityLevelUIUtils.sys.mjs
- browser/components/securitylevel/content/securityLevel.js
- browser/components/securitylevel/content/securityLevelButton.css
- + browser/components/securitylevel/content/securityLevelDialog.js
- + browser/components/securitylevel/content/securityLevelDialog.xhtml
- browser/components/securitylevel/content/securityLevelPanel.css
- browser/components/securitylevel/content/securityLevelPanel.inc.xhtml
- browser/components/securitylevel/content/securityLevelPreferences.css
- browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml
- browser/components/securitylevel/jar.mn
- browser/components/securitylevel/moz.build
- + browser/modules/SecurityLevelRestartNotification.sys.mjs
- browser/modules/moz.build
- toolkit/components/extensions/ExtensionParent.sys.mjs
- toolkit/components/securitylevel/SecurityLevel.sys.mjs
- toolkit/locales/en-US/toolkit/global/base-browser.ftl
Changes:
=====================================
browser/components/BrowserGlue.sys.mjs
=====================================
@@ -63,6 +63,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
ScreenshotsUtils: "resource:///modules/ScreenshotsUtils.sys.mjs",
SearchSERPTelemetry:
"moz-src:///browser/components/search/SearchSERPTelemetry.sys.mjs",
+ SecurityLevelRestartNotification:
+ "resource:///modules/SecurityLevelRestartNotification.sys.mjs",
SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
@@ -997,6 +999,8 @@ BrowserGlue.prototype = {
lazy.WeaveService.init();
}
+ lazy.SecurityLevelRestartNotification.ready();
+
ClipboardPrivacy.startup();
lazy.BrowserUtils.callModulesFromCategory(
=====================================
browser/components/securitylevel/SecurityLevelUIUtils.sys.mjs
=====================================
@@ -0,0 +1,73 @@
+/**
+ * Common methods for the desktop security level components.
+ */
+export const SecurityLevelUIUtils = {
+ /**
+ * Create an element that gives a description of the security level. To be
+ * used in the settings.
+ *
+ * @param {string} level - The security level to describe.
+ * @param {Document} doc - The document where the element will be inserted.
+ *
+ * @returns {Element} - The newly created element.
+ */
+ createDescriptionElement(level, doc) {
+ const el = doc.createElement("div");
+ el.classList.add("security-level-description");
+
+ let l10nIdSummary;
+ let bullets;
+ switch (level) {
+ case "standard":
+ l10nIdSummary = "security-level-summary-standard";
+ break;
+ case "safer":
+ l10nIdSummary = "security-level-summary-safer";
+ bullets = [
+ "security-level-preferences-bullet-https-only-javascript",
+ "security-level-preferences-bullet-limit-font-and-symbols",
+ "security-level-preferences-bullet-limit-media",
+ ];
+ break;
+ case "safest":
+ l10nIdSummary = "security-level-summary-safest";
+ bullets = [
+ "security-level-preferences-bullet-disabled-javascript",
+ "security-level-preferences-bullet-limit-font-and-symbols-and-images",
+ "security-level-preferences-bullet-limit-media",
+ ];
+ break;
+ case "custom":
+ l10nIdSummary = "security-level-summary-custom";
+ break;
+ default:
+ throw Error(`Unhandled level: ${level}`);
+ }
+
+ const summaryEl = doc.createElement("div");
+ summaryEl.classList.add("security-level-summary");
+ doc.l10n.setAttributes(summaryEl, l10nIdSummary);
+
+ el.append(summaryEl);
+
+ if (!bullets) {
+ return el;
+ }
+
+ const listEl = doc.createElement("ul");
+ listEl.classList.add("security-level-description-extra");
+ // Add a mozilla styling class as well:
+ listEl.classList.add("privacy-extra-information");
+ for (const l10nId of bullets) {
+ const bulletEl = doc.createElement("li");
+ bulletEl.classList.add("security-level-description-bullet");
+
+ doc.l10n.setAttributes(bulletEl, l10nId);
+
+ listEl.append(bulletEl);
+ }
+
+ el.append(listEl);
+ return el;
+ },
+};
=====================================
browser/components/securitylevel/content/securityLevel.js
=====================================
@@ -1,9 +1,10 @@
"use strict";
-/* global AppConstants, Services, openPreferences, XPCOMUtils */
+/* global AppConstants, Services, openPreferences, XPCOMUtils, gSubDialog */
ChromeUtils.defineESModuleGetters(this, {
SecurityLevelPrefs: "resource://gre/modules/SecurityLevel.sys.mjs",
+ SecurityLevelUIUtils: "resource:///modules/SecurityLevelUIUtils.sys.mjs",
});
/*
@@ -35,12 +36,8 @@ var SecurityLevelButton = {
_anchorButton: null,
_configUIFromPrefs() {
- const level = SecurityLevelPrefs.securityLevel;
- if (!level) {
- return;
- }
- const custom = SecurityLevelPrefs.securityCustom;
- this._button.setAttribute("level", custom ? `${level}_custom` : level);
+ const level = SecurityLevelPrefs.securityLevelSummary;
+ this._button.setAttribute("level", level);
let l10nIdLevel;
switch (level) {
@@ -53,15 +50,12 @@ var SecurityLevelButton = {
case "safest":
l10nIdLevel = "security-level-toolbar-button-safest";
break;
+ case "custom":
+ l10nIdLevel = "security-level-toolbar-button-custom";
+ break;
default:
throw Error(`Unhandled level: ${level}`);
}
- if (custom) {
- // Don't distinguish between the different levels when in the custom
- // state. We just want to emphasise that it is custom rather than any
- // specific level.
- l10nIdLevel = "security-level-toolbar-button-custom";
- }
document.l10n.setAttributes(this._button, l10nIdLevel);
},
@@ -164,12 +158,7 @@ var SecurityLevelPanel = {
panel: document.getElementById("securityLevel-panel"),
background: document.getElementById("securityLevel-background"),
levelName: document.getElementById("securityLevel-level"),
- customName: document.getElementById("securityLevel-custom"),
summary: document.getElementById("securityLevel-summary"),
- restoreDefaultsButton: document.getElementById(
- "securityLevel-restoreDefaults"
- ),
- settingsButton: document.getElementById("securityLevel-settings"),
};
const learnMoreEl = document.getElementById("securityLevel-learnMore");
@@ -177,12 +166,11 @@ var SecurityLevelPanel = {
this.hide();
});
- this._elements.restoreDefaultsButton.addEventListener("command", () => {
- this.restoreDefaults();
- });
- this._elements.settingsButton.addEventListener("command", () => {
- this.openSecuritySettings();
- });
+ document
+ .getElementById("securityLevel-settings")
+ .addEventListener("command", () => {
+ this.openSecuritySettings();
+ });
this._elements.panel.addEventListener("popupshown", () => {
// Bring focus into the panel by focusing the default button.
@@ -199,19 +187,7 @@ var SecurityLevelPanel = {
}
// get security prefs
- const level = SecurityLevelPrefs.securityLevel;
- const custom = SecurityLevelPrefs.securityCustom;
-
- // only visible when user is using custom settings
- this._elements.customName.hidden = !custom;
- this._elements.restoreDefaultsButton.hidden = !custom;
- if (custom) {
- this._elements.settingsButton.removeAttribute("default");
- this._elements.restoreDefaultsButton.setAttribute("default", "true");
- } else {
- this._elements.settingsButton.setAttribute("default", "true");
- this._elements.restoreDefaultsButton.removeAttribute("default");
- }
+ const level = SecurityLevelPrefs.securityLevelSummary;
// Descriptions change based on security level
this._elements.background.setAttribute("level", level);
@@ -230,12 +206,13 @@ var SecurityLevelPanel = {
l10nIdLevel = "security-level-panel-level-safest";
l10nIdSummary = "security-level-summary-safest";
break;
+ case "custom":
+ l10nIdLevel = "security-level-panel-level-custom";
+ l10nIdSummary = "security-level-summary-custom";
+ break;
default:
throw Error(`Unhandled level: ${level}`);
}
- if (custom) {
- l10nIdSummary = "security-level-summary-custom";
- }
document.l10n.setAttributes(this._elements.levelName, l10nIdLevel);
document.l10n.setAttributes(this._elements.summary, l10nIdSummary);
@@ -269,13 +246,6 @@ var SecurityLevelPanel = {
this._elements.panel.hidePopup();
},
- restoreDefaults() {
- SecurityLevelPrefs.securityCustom = false;
- // Move focus to the settings button since restore defaults button will
- // become hidden.
- this._elements.settingsButton.focus();
- },
-
openSecuritySettings() {
openPreferences("privacy-securitylevel");
this.hide();
@@ -301,59 +271,64 @@ var SecurityLevelPanel = {
var SecurityLevelPreferences = {
_securityPrefsBranch: null,
+
/**
- * The notification box shown when the user has a custom security setting.
- *
- * @type {Element}
- */
- _customNotification: null,
- /**
- * The radiogroup for this preference.
- *
- * @type {Element}
- */
- _radiogroup: null,
- /**
- * A list of radio options and their containers.
+ * The element that shows the current security level.
*
- * @type {Array<object>}
+ * @type {?Element}
*/
- _radioOptions: null,
+ _currentEl: null,
_populateXUL() {
- this._customNotification = document.getElementById(
- "securityLevel-customNotification"
+ this._currentEl = document.getElementById("security-level-current");
+ const changeButton = document.getElementById("security-level-change");
+ const badgeEl = this._currentEl.querySelector(
+ ".security-level-current-badge"
);
- document
- .getElementById("securityLevel-restoreDefaults")
- .addEventListener("command", () => {
- SecurityLevelPrefs.securityCustom = false;
- });
-
- this._radiogroup = document.getElementById("securityLevel-radiogroup");
- this._radioOptions = Array.from(
- this._radiogroup.querySelectorAll(".securityLevel-radio-option"),
- container => {
- return { container, radio: container.querySelector("radio") };
- }
- );
+ for (const { level, nameId } of [
+ { level: "standard", nameId: "security-level-panel-level-standard" },
+ { level: "safer", nameId: "security-level-panel-level-safer" },
+ { level: "safest", nameId: "security-level-panel-level-safest" },
+ { level: "custom", nameId: "security-level-panel-level-custom" },
+ ]) {
+ // Classes that control visibility:
+ // security-level-current-standard
+ // security-level-current-safer
+ // security-level-current-safest
+ // security-level-current-custom
+ const visibilityClass = `security-level-current-${level}`;
+ const nameEl = document.createElement("div");
+ nameEl.classList.add("security-level-name", visibilityClass);
+ document.l10n.setAttributes(nameEl, nameId);
+
+ const descriptionEl = SecurityLevelUIUtils.createDescriptionElement(
+ level,
+ document
+ );
+ descriptionEl.classList.add(visibilityClass);
+
+ this._currentEl.insertBefore(nameEl, badgeEl);
+ this._currentEl.insertBefore(descriptionEl, changeButton);
+ }
- this._radiogroup.addEventListener("select", () => {
- SecurityLevelPrefs.securityLevel = this._radiogroup.value;
+ changeButton.addEventListener("click", () => {
+ this._openDialog();
});
},
+ _openDialog() {
+ gSubDialog.open(
+ "chrome://browser/content/securitylevel/securityLevelDialog.xhtml",
+ { features: "resizable=yes" }
+ );
+ },
+
_configUIFromPrefs() {
- this._radiogroup.value = SecurityLevelPrefs.securityLevel;
- const isCustom = SecurityLevelPrefs.securityCustom;
- this._radiogroup.disabled = isCustom;
- this._customNotification.hidden = !isCustom;
- // Have the container's selection CSS class match the selection state of the
- // radio elements.
- for (const { container, radio } of this._radioOptions) {
- container.classList.toggle("selected", radio.selected);
- }
+ // Set a data-current-level attribute for showing the current security
+ // level, and hiding the rest.
+ this._currentEl.dataset.currentLevel =
+ SecurityLevelPrefs.securityLevelSummary;
},
init() {
=====================================
browser/components/securitylevel/content/securityLevelButton.css
=====================================
@@ -7,12 +7,6 @@ toolbarbutton#security-level-button[level="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"] {
+toolbarbutton#security-level-button[level="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");
-}
=====================================
browser/components/securitylevel/content/securityLevelDialog.js
=====================================
@@ -0,0 +1,188 @@
+"use strict";
+
+const { SecurityLevelPrefs } = ChromeUtils.importESModule(
+ "resource://gre/modules/SecurityLevel.sys.mjs"
+);
+const { SecurityLevelUIUtils } = ChromeUtils.importESModule(
+ "resource:///modules/SecurityLevelUIUtils.sys.mjs"
+);
+
+const gSecurityLevelDialog = {
+ /**
+ * The security level when this dialog was opened.
+ *
+ * @type {string}
+ */
+ _prevLevel: SecurityLevelPrefs.securityLevelSummary,
+ /**
+ * The security level currently selected.
+ *
+ * @type {string}
+ */
+ _selectedLevel: "",
+ /**
+ * The radiogroup for this preference.
+ *
+ * @type {?Element}
+ */
+ _radiogroup: null,
+ /**
+ * A list of radio options and their containers.
+ *
+ * @type {?Array<{ container: Element, radio: Element }>}
+ */
+ _radioOptions: null,
+
+ /**
+ * Initialise the dialog.
+ */
+ async init() {
+ const dialog = document.getElementById("security-level-dialog");
+ dialog.addEventListener("dialogaccept", event => {
+ if (this._acceptButton.disabled) {
+ event.preventDefault();
+ return;
+ }
+ this._commitChange();
+ });
+
+ this._acceptButton = dialog.getButton("accept");
+
+ document.l10n.setAttributes(
+ this._acceptButton,
+ "security-level-dialog-save-restart"
+ );
+
+ this._radiogroup = document.getElementById("security-level-radiogroup");
+
+ this._radioOptions = Array.from(
+ this._radiogroup.querySelectorAll(".security-level-radio-container"),
+ container => {
+ return {
+ container,
+ radio: container.querySelector(".security-level-radio"),
+ };
+ }
+ );
+
+ for (const { container, radio } of this._radioOptions) {
+ const level = radio.value;
+ radio.id = `security-level-radio-${level}`;
+ const currentEl = container.querySelector(
+ ".security-level-current-badge"
+ );
+ currentEl.id = `security-level-current-badge-${level}`;
+ const descriptionEl = SecurityLevelUIUtils.createDescriptionElement(
+ level,
+ document
+ );
+ descriptionEl.classList.add("indent");
+ descriptionEl.id = `security-level-description-${level}`;
+
+ // Wait for the full translation of the element before adding it to the
+ // DOM. In particular, we want to make sure the elements have text before
+ // we measure the maxHeight below.
+ await document.l10n.translateFragment(descriptionEl);
+ document.l10n.pauseObserving();
+ container.append(descriptionEl);
+ document.l10n.resumeObserving();
+
+ if (level === this._prevLevel) {
+ currentEl.hidden = false;
+ // When the currentEl is visible, include it in the accessible name for
+ // the radio option.
+ // NOTE: The currentEl has an accessible name which includes punctuation
+ // to help separate it's content from the security level name.
+ // E.g. "Standard (Current level)".
+ radio.setAttribute("aria-labelledby", `${radio.id} ${currentEl.id}`);
+ } else {
+ currentEl.hidden = true;
+ }
+ // We point the accessible description to the wrapping
+ // .security-level-description element, rather than its children
+ // that define the actual text content. This means that when the
+ // privacy-extra-information is shown or hidden, its text content is
+ // included or excluded from the accessible description, respectively.
+ radio.setAttribute("aria-describedby", descriptionEl.id);
+ }
+
+ // We want to reserve the maximum height of the radiogroup so that the
+ // dialog has enough height when the user switches options. So we cycle
+ // through the options and measure the height when they are selected to set
+ // a minimum height that fits all of them.
+ // NOTE: At the time of implementation, at this point the dialog may not
+ // yet have the "subdialog" attribute, which means it is missing the
+ // common.css stylesheet from its shadow root, which effects the size of the
+ // .radio-check element and the font. Therefore, we have duplicated the
+ // import of common.css in SecurityLevelDialog.xhtml to ensure it is applied
+ // at this earlier stage.
+ let maxHeight = 0;
+ for (const { container } of this._radioOptions) {
+ container.classList.add("selected");
+ maxHeight = Math.max(
+ maxHeight,
+ this._radiogroup.getBoundingClientRect().height
+ );
+ container.classList.remove("selected");
+ }
+ this._radiogroup.style.minHeight = `${maxHeight}px`;
+
+ if (this._prevLevel !== "custom") {
+ this._selectedLevel = this._prevLevel;
+ this._radiogroup.value = this._prevLevel;
+ } else {
+ this._radiogroup.selectedItem = null;
+ }
+
+ this._radiogroup.addEventListener("select", () => {
+ this._selectedLevel = this._radiogroup.value;
+ this._updateSelected();
+ });
+
+ this._updateSelected();
+ },
+
+ /**
+ * Update the UI in response to a change in selection.
+ */
+ _updateSelected() {
+ this._acceptButton.disabled =
+ !this._selectedLevel || this._selectedLevel === this._prevLevel;
+ // Have the container's `selected` CSS class match the selection state of
+ // the radio elements.
+ for (const { container, radio } of this._radioOptions) {
+ container.classList.toggle("selected", radio.selected);
+ }
+ },
+
+ /**
+ * Commit the change in security level and restart the browser.
+ */
+ _commitChange() {
+ SecurityLevelPrefs.setSecurityLevelBeforeRestart(this._selectedLevel);
+ Services.startup.quit(
+ Services.startup.eAttemptQuit | Services.startup.eRestart
+ );
+ },
+};
+
+// Initial focus is not visible, even if opened with a keyboard. We avoid the
+// default handler and manage the focus ourselves, which will paint the focus
+// ring by default.
+// NOTE: A side effect is that the focus ring will show even if the user opened
+// with a mouse event.
+// TODO: Remove this once bugzilla bug 1708261 is resolved.
+document.subDialogSetDefaultFocus = () => {
+ document.getElementById("security-level-radiogroup").focus();
+};
+
+// Delay showing and sizing the subdialog until it is fully initialised.
+document.mozSubdialogReady = new Promise(resolve => {
+ window.addEventListener(
+ "DOMContentLoaded",
+ () => {
+ gSecurityLevelDialog.init().finally(resolve);
+ },
+ { once: true }
+ );
+});
=====================================
browser/components/securitylevel/content/securityLevelDialog.xhtml
=====================================
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+
+<window
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ data-l10n-id="security-level-dialog-window"
+>
+ <dialog id="security-level-dialog" buttons="accept,cancel">
+ <linkset>
+ <html:link rel="stylesheet" href="chrome://global/skin/global.css" />
+ <!-- NOTE: We include common.css explicitly, rather than relying on
+ - the dialog's shadowroot importing it, which is late loaded in
+ - response to the dialog's "subdialog" attribute, which is set
+ - in response to DOMFrameContentLoaded.
+ - In particular, we need the .radio-check rule and font rules from
+ - common-shared.css to be in place when gSecurityLevelDialog.init is
+ - called, which will help ensure that the radio element has the correct
+ - size when we measure its bounding box. -->
+ <html:link
+ rel="stylesheet"
+ href="chrome://global/skin/in-content/common.css"
+ />
+ <html:link
+ rel="stylesheet"
+ href="chrome://browser/skin/preferences/preferences.css"
+ />
+ <html:link
+ rel="stylesheet"
+ href="chrome://browser/skin/preferences/privacy.css"
+ />
+ <html:link
+ rel="stylesheet"
+ href="chrome://browser/content/securitylevel/securityLevelPreferences.css"
+ />
+
+ <html:link rel="localization" href="branding/brand.ftl" />
+ <html:link rel="localization" href="toolkit/global/base-browser.ftl" />
+ </linkset>
+
+ <script src="chrome://browser/content/securitylevel/securityLevelDialog.js" />
+
+ <description data-l10n-id="security-level-dialog-restart-description" />
+
+ <radiogroup id="security-level-radiogroup" class="highlighting-group">
+ <html:div
+ class="security-level-radio-container security-level-grid privacy-detailedoption info-box-container"
+ >
+ <radio
+ class="security-level-radio security-level-name"
+ value="standard"
+ data-l10n-id="security-level-preferences-level-standard"
+ />
+ <html:div
+ class="security-level-current-badge"
+ data-l10n-id="security-level-preferences-current-badge"
+ ></html:div>
+ </html:div>
+ <html:div
+ class="security-level-radio-container security-level-grid privacy-detailedoption info-box-container"
+ >
+ <radio
+ class="security-level-radio security-level-name"
+ value="safer"
+ data-l10n-id="security-level-preferences-level-safer"
+ />
+ <html:div
+ class="security-level-current-badge"
+ data-l10n-id="security-level-preferences-current-badge"
+ ></html:div>
+ </html:div>
+ <html:div
+ class="security-level-radio-container security-level-grid privacy-detailedoption info-box-container"
+ >
+ <radio
+ class="security-level-radio security-level-name"
+ value="safest"
+ data-l10n-id="security-level-preferences-level-safest"
+ />
+ <html:div
+ class="security-level-current-badge"
+ data-l10n-id="security-level-preferences-current-badge"
+ ></html:div>
+ </html:div>
+ </radiogroup>
+ </dialog>
+</window>
=====================================
browser/components/securitylevel/content/securityLevelPanel.css
=====================================
@@ -23,7 +23,7 @@
background-position-x: right var(--background-inline-offset);
}
-#securityLevel-background[level="standard"] {
+#securityLevel-background:is([level="standard"], [level="custom"]) {
background-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard");
}
@@ -49,14 +49,6 @@
font-weight: 600;
}
-#securityLevel-custom {
- border-radius: 4px;
- background-color: var(--warning-icon-bgcolor);
- color: black;
- padding: 0.4em 0.5em;
- margin-inline-start: 1em;
-}
-
#securityLevel-summary {
padding-inline-end: 5em;
max-width: 20em;
=====================================
browser/components/securitylevel/content/securityLevelPanel.inc.xhtml
=====================================
@@ -15,10 +15,6 @@
<vbox id="securityLevel-background" class="panel-subview-body">
<html:p id="securityLevel-subheading">
<html:span id="securityLevel-level"></html:span>
- <html:span
- id="securityLevel-custom"
- data-l10n-id="security-level-panel-custom-badge"
- ></html:span>
</html:p>
<html:p id="securityLevel-summary"></html:p>
<html:a
@@ -32,12 +28,8 @@
<button
id="securityLevel-settings"
class="footer-button"
+ default="true"
data-l10n-id="security-level-panel-open-settings-button"
/>
- <button
- id="securityLevel-restoreDefaults"
- class="footer-button"
- data-l10n-id="security-level-restore-defaults-button"
- />
</html:moz-button-group>
</panel>
=====================================
browser/components/securitylevel/content/securityLevelPreferences.css
=====================================
@@ -1,26 +1,166 @@
-#securityLevel-groupbox {
- --section-highlight-background-color: color-mix(in srgb, var(--in-content-accent-color) 20%, transparent);
+.security-level-grid {
+ display: grid;
+ grid-template:
+ "icon name badge button" min-content
+ "icon summary summary button" auto
+ "icon extra extra ." auto
+ / max-content max-content 1fr max-content;
}
-#securityLevel-customNotification {
- /* Spacing similar to #fpiIncompatibilityWarning. */
- margin-block: 16px;
+.security-level-icon {
+ grid-area: icon;
+ align-self: start;
+ width: 24px;
+ height: 24px;
+ -moz-context-properties: fill;
+ fill: var(--in-content-icon-color);
+ margin-block-start: var(--space-xsmall);
+ margin-inline-end: var(--space-large);
}
-.info-icon.securityLevel-custom-warning-icon {
- list-style-image: url("chrome://global/skin/icons/warning.svg");
+.security-level-current-badge {
+ grid-area: badge;
+ align-self: center;
+ justify-self: start;
+ white-space: nowrap;
+ background: var(--background-color-information);
+ color: inherit;
+ font-size: var(--font-size-small);
+ border-radius: var(--border-radius-circle);
+ margin-inline-start: var(--space-small);
+ padding-block: var(--space-xsmall);
+ padding-inline: var(--space-small);
}
-#securityLevel-customHeading {
+.security-level-current-badge span {
+ /* Still accessible to screen reader, but not visual.
+ * Keep inline, but with no layout width. */
+ display: inline-block;
+ width: 1px;
+ margin-inline-end: -1px;
+ clip-path: inset(50%);
+}
+
+@media (prefers-contrast) and (not (forced-colors)) {
+ .security-level-current-badge {
+ /* Match the checkbox/radio colors. */
+ background: var(--color-accent-primary);
+ color: var(--button-text-color-primary);
+ }
+}
+
+@media (forced-colors) {
+ .security-level-current-badge {
+ /* Match the checkbox/radio/selected colors. */
+ background: SelectedItem;
+ color: SelectedItemText;
+ }
+}
+
+.security-level-name {
+ grid-area: name;
font-weight: bold;
+ align-self: center;
+ white-space: nowrap;
+}
+
+.security-level-description {
+ display: grid;
+ grid-column: summary-start / extra-end;
+ grid-row: summary-start / extra-end;
+ grid-template-rows: subgrid;
+ grid-template-columns: subgrid;
+ margin-block-start: var(--space-small);
+}
+
+.security-level-summary {
+ grid-area: summary;
+}
+
+.security-level-description-extra {
+ grid-area: extra;
+ margin-block: var(--space-medium) 0;
+ margin-inline: var(--space-large) 0;
+ padding: 0;
+}
+
+.security-level-description-bullet:not(:last-child) {
+ margin-block-end: var(--space-medium);
+}
+
+/* Tweak current security level display. */
+
+#security-level-current {
+ margin-block-start: var(--space-large);
+ background: var(--in-content-box-background);
+ border: 1px solid var(--in-content-box-border-color);
+ border-radius: var(--border-radius-small);
+ padding: var(--space-medium);
+}
+
+#security-level-change {
+ grid-area: button;
+ align-self: center;
+ margin: 0;
+ margin-inline-start: var(--space-large);
+}
+
+/* Adjust which content is visible depending on the current security level. */
+
+#security-level-current:not([data-current-level="standard"]) .security-level-current-standard {
+ display: none;
+}
+
+#security-level-current:not([data-current-level="safer"]) .security-level-current-safer {
+ display: none;
+}
+
+#security-level-current:not([data-current-level="safest"]) .security-level-current-safest {
+ display: none;
+}
+
+#security-level-current:not([data-current-level="custom"]) .security-level-current-custom {
+ display: none;
+}
+
+#security-level-current[data-current-level="standard"] .security-level-icon {
+ content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard");
+}
+
+#security-level-current[data-current-level="safer"] .security-level-icon {
+ content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safer");
+}
+
+#security-level-current[data-current-level="safest"] .security-level-icon {
+ content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safest");
+}
+
+#security-level-current[data-current-level="custom"] .security-level-icon {
+ content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard_custom");
+}
+
+/* Tweak security level dialog. */
+
+#security-level-radiogroup {
+ margin-block: var(--space-large) var(--space-xlarge);
+}
+
+.security-level-radio-container {
+ padding-block: var(--space-large);
+}
+
+#security-level-radiogroup .security-level-radio {
+ margin: 0;
}
-#securityLevel-radiogroup[disabled] {
- opacity: 0.5;
+#security-level-radiogroup .radio-label-box {
+ /* .security-level-current-badge already has a margin. */
+ margin: 0;
}
-/* Overwrite the rule in common-shared.css so we don't get 0.25 opacity overall
- * on the radio text. */
-#securityLevel-radiogroup[disabled] radio[disabled] .radio-label-box {
- opacity: 1.0;
+#security-level-radiogroup .privacy-detailedoption.security-level-radio-container:not(.selected) .security-level-description-extra {
+ /* .privacy-detailedoption uses visibility: hidden, which does not work with
+ * our grid display (the margin is still reserved) so we use display: none
+ * instead. */
+ display: none;
}
=====================================
browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml
=====================================
@@ -17,112 +17,19 @@
data-l10n-id="security-level-preferences-learn-more-link"
></html:a>
</description>
- <hbox
- id="securityLevel-customNotification"
- class="info-box-container"
- flex="1"
- >
- <hbox class="info-icon-container">
- <image class="info-icon securityLevel-custom-warning-icon"/>
- </hbox>
- <vbox flex="1">
- <label
- id="securityLevel-customHeading"
- data-l10n-id="security-level-preferences-custom-heading"
- />
- <description
- id="securityLevel-customDescription"
- data-l10n-id="security-level-summary-custom"
- flex="1"
- />
- </vbox>
- <hbox align="center">
- <button
- id="securityLevel-restoreDefaults"
- data-l10n-id="security-level-restore-defaults-button"
- />
- </hbox>
- </hbox>
- <radiogroup id="securityLevel-radiogroup">
- <vbox class="securityLevel-radio-option privacy-detailedoption info-box-container">
- <radio
- value="standard"
- data-l10n-id="security-level-preferences-level-standard"
- aria-describedby="securityLevelSummary-standard"
- />
- <vbox id="securityLevelSummary-standard" class="indent">
- <label data-l10n-id="security-level-summary-standard" />
- </vbox>
- </vbox>
- <vbox class="securityLevel-radio-option privacy-detailedoption info-box-container">
- <!-- NOTE: We point the accessible description to the wrapping vbox
- - rather than its first description element. This means that when the
- - privacy-extra-information is shown or hidden, its text content is
- - included or excluded from the accessible description, respectively.
- -->
- <radio
- value="safer"
- data-l10n-id="security-level-preferences-level-safer"
- aria-describedby="securityLevelSummary-safer"
- />
- <vbox id="securityLevelSummary-safer" class="indent">
- <label data-l10n-id="security-level-summary-safer" />
- <vbox class="privacy-extra-information">
- <vbox class="indent">
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-https-only-javascript"
- />
- </hbox>
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-limit-font-and-symbols"
- />
- </hbox>
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-limit-media"
- />
- </hbox>
- </vbox>
- </vbox>
- </vbox>
- </vbox>
- <vbox class="securityLevel-radio-option privacy-detailedoption info-box-container">
- <radio
- value="safest"
- data-l10n-id="security-level-preferences-level-safest"
- aria-describedby="securityLevelSummary-safest"
- />
- <vbox id="securityLevelSummary-safest" class="indent">
- <label data-l10n-id="security-level-summary-safest" />
- <vbox class="privacy-extra-information">
- <vbox class="indent">
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-disabled-javascript"
- />
- </hbox>
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-limit-font-and-symbols-and-images"
- />
- </hbox>
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-limit-media"
- />
- </hbox>
- </vbox>
- </vbox>
- </vbox>
- </vbox>
- </radiogroup>
+ <html:div id="security-level-current" class="security-level-grid">
+ <html:img
+ class="security-level-icon"
+ alt=""
+ />
+ <html:div
+ class="security-level-current-badge"
+ data-l10n-id="security-level-preferences-current-badge"
+ ></html:div>
+ <html:button
+ id="security-level-change"
+ data-l10n-id="security-level-preferences-change-button"
+ ></html:button>
+ </html:div>
</vbox>
</groupbox>
=====================================
browser/components/securitylevel/jar.mn
=====================================
@@ -4,3 +4,5 @@ browser.jar:
content/browser/securitylevel/securityLevelButton.css (content/securityLevelButton.css)
content/browser/securitylevel/securityLevelPreferences.css (content/securityLevelPreferences.css)
content/browser/securitylevel/securityLevelIcon.svg (content/securityLevelIcon.svg)
+ content/browser/securitylevel/securityLevelDialog.xhtml (content/securityLevelDialog.xhtml)
+ content/browser/securitylevel/securityLevelDialog.js (content/securityLevelDialog.js)
=====================================
browser/components/securitylevel/moz.build
=====================================
@@ -1 +1,5 @@
JAR_MANIFESTS += ["jar.mn"]
+
+EXTRA_JS_MODULES += [
+ "SecurityLevelUIUtils.sys.mjs",
+]
=====================================
browser/modules/SecurityLevelRestartNotification.sys.mjs
=====================================
@@ -0,0 +1,72 @@
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
+ SecurityLevelPrefs: "resource://gre/modules/SecurityLevel.sys.mjs",
+});
+
+ChromeUtils.defineLazyGetter(lazy, "NotificationStrings", function () {
+ return new Localization([
+ "branding/brand.ftl",
+ "toolkit/global/base-browser.ftl",
+ ]);
+});
+
+/**
+ * Interface for showing the security level restart notification on desktop.
+ */
+export const SecurityLevelRestartNotification = {
+ /**
+ * Whether we have already been initialised.
+ *
+ * @type {boolean}
+ */
+ _initialized: false,
+
+ /**
+ * Called when the UI is ready to show a notification.
+ */
+ ready() {
+ if (this._initialized) {
+ return;
+ }
+ this._initialized = true;
+ lazy.SecurityLevelPrefs.setRestartNotificationHandler(this);
+ },
+
+ /**
+ * Show the restart notification, and perform the restart if the user agrees.
+ */
+ async tryRestartBrowser() {
+ const [titleText, bodyText, primaryButtonText, secondaryButtonText] =
+ await lazy.NotificationStrings.formatValues([
+ { id: "security-level-restart-prompt-title" },
+ { id: "security-level-restart-prompt-body" },
+ { id: "security-level-restart-prompt-button-restart" },
+ { id: "security-level-restart-prompt-button-ignore" },
+ ]);
+ const buttonFlags =
+ Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
+ Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1;
+
+ const propBag = await Services.prompt.asyncConfirmEx(
+ lazy.BrowserWindowTracker.getTopWindow()?.browsingContext ?? null,
+ Services.prompt.MODAL_TYPE_INTERNAL_WINDOW,
+ titleText,
+ bodyText,
+ buttonFlags,
+ primaryButtonText,
+ secondaryButtonText,
+ null,
+ null,
+ null,
+ {}
+ );
+
+ if (propBag.get("buttonNumClicked") === 0) {
+ Services.startup.quit(
+ Services.startup.eAttemptQuit | Services.startup.eRestart
+ );
+ }
+ },
+};
=====================================
browser/modules/moz.build
=====================================
@@ -136,6 +136,7 @@ EXTRA_JS_MODULES += [
"PopupBlockerObserver.sys.mjs",
"ProcessHangMonitor.sys.mjs",
"Sanitizer.sys.mjs",
+ "SecurityLevelRestartNotification.sys.mjs",
"SelectionChangedMenulist.sys.mjs",
"SharingUtils.sys.mjs",
"SiteDataManager.sys.mjs",
=====================================
toolkit/components/extensions/ExtensionParent.sys.mjs
=====================================
@@ -2348,6 +2348,7 @@ async function torSendExtensionMessage(extensionId, message) {
const result = await ProxyMessenger.conduit.castRuntimeMessage("messenger", {
extensionId,
holder: new StructuredCloneHolder("torSendExtensionMessage", null, message),
+ query: true,
firstResponse: true,
sender: {
id: extensionId,
=====================================
toolkit/components/securitylevel/SecurityLevel.sys.mjs
=====================================
@@ -16,6 +16,7 @@ const BrowserTopics = Object.freeze({
// The Security Settings prefs in question.
const kSliderPref = "browser.security_level.security_slider";
const kCustomPref = "browser.security_level.security_custom";
+const kNoScriptInitedPref = "browser.security_level.noscript_inited";
// __getPrefValue(prefName)__
// Returns the current value of a preference, regardless of its type.
@@ -32,11 +33,11 @@ var getPrefValue = function (prefName) {
}
};
-// __bindPref(prefName, prefHandler, init)__
+// __bindPref(prefName, prefHandler)__
// 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) {
+// Returns the observer that was added.
+var bindPref = function (prefName, prefHandler) {
let update = () => {
prefHandler(getPrefValue(prefName));
},
@@ -48,21 +49,9 @@ var bindPref = function (prefName, prefHandler, init = false) {
},
};
Services.prefs.addObserver(prefName, observer);
- if (init) {
- update();
- }
- return () => {
- Services.prefs.removeObserver(prefName, observer);
- };
+ return 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 } = lazy.ExtensionParent;
if (torWaitForExtensionMessage) {
@@ -74,7 +63,7 @@ async function waitForExtensionMessage(extensionId, checker = () => {}) {
async function sendExtensionMessage(extensionId, message) {
const { torSendExtensionMessage } = lazy.ExtensionParent;
if (torSendExtensionMessage) {
- return torSendExtensionMessage(extensionId, message);
+ return await torSendExtensionMessage(extensionId, message);
}
return undefined;
}
@@ -199,14 +188,8 @@ var initializeNoScriptControl = () => {
// `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));
+ let sendNoScriptSettings = async settings =>
+ await sendExtensionMessage(noscriptID, settings);
// __securitySliderToSafetyLevel(sliderState)__.
// Converts the "browser.security_level.security_slider" pref value
@@ -216,36 +199,46 @@ var initializeNoScriptControl = () => {
// Wait for the first message from NoScript to arrive, and then
// bind the security_slider pref to the NoScript settings.
- let messageListener = a => {
+ let messageListener = async a => {
try {
logger.debug("Message received from NoScript:", a);
- let noscriptPersist = Services.prefs.getBoolPref(
- "browser.security_level.noscript_persist",
- false
- );
+ const persistPref = "browser.security_level.noscript_persist";
+ let noscriptPersist = Services.prefs.getBoolPref(persistPref, false);
let noscriptInited = Services.prefs.getBoolPref(
- "browser.security_level.noscript_inited",
+ kNoScriptInitedPref,
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
+ // Set the noscript safety level once at startup.
+ // If a user has set noscriptPersist, then we only send this if the
+ // security level was changed in a previous session.
+ // NOTE: We do not re-send this when the security_slider preference
+ // changes mid-session because this should always require a restart.
+ if (noscriptPersist && noscriptInited) {
+ logger.warn(
+ `Not initialising NoScript since the user has set ${persistPref}`
);
+ return;
}
+ // Read the security level, even if the user has the "custom"
+ // preference.
+ const securityIndex = Services.prefs.getIntPref(kSliderPref, 0);
+ const safetyLevel = securitySliderToSafetyLevel(securityIndex);
+ // May throw if NoScript fails to apply the settings:
+ const noscriptResult = await sendNoScriptSettings(
+ noscriptSettings(safetyLevel)
+ );
+ // Mark the NoScript extension as initialised so we do not reset it
+ // at the next startup for noscript_persist users.
+ Services.prefs.setBoolPref(kNoScriptInitedPref, true);
+ logger.info("NoScript successfully initialised.");
+ // In the future NoScript may tell us more about how it applied our
+ // settings, e.g. if user is overriding per-site permissions.
+ // Up to NoScript 12.6 noscriptResult is undefined.
+ logger.debug("NoScript response:", noscriptResult);
} catch (e) {
- logger.exception(e);
+ logger.error("Could not apply NoScript settings", e);
+ // Treat as a custom security level for the rest of the session.
+ Services.prefs.setBoolPref(kCustomPref, true);
}
};
waitForExtensionMessage(noscriptID, a => a.__meta.name === "started").then(
@@ -254,6 +247,8 @@ var initializeNoScriptControl = () => {
logger.info("Listening for messages from NoScript.");
} catch (e) {
logger.exception(e);
+ // Treat as a custom security level for the rest of the session.
+ Services.prefs.setBoolPref(kCustomPref, true);
}
};
@@ -283,16 +278,60 @@ const kSecuritySettings = {
// ### Prefs
+/**
+ * Amend the security level index to a standard value.
+ *
+ * @param {integer} index - The input index value.
+ * @returns {integer} - A standard index value.
+ */
+function fixupIndex(index) {
+ if (!Number.isInteger(index) || index < 1 || index > 4) {
+ // Unexpected value out of range, go to the "safest" level as a fallback.
+ return 1;
+ }
+ if (index === 3) {
+ // Migrate from old medium-low (3) to new medium (2).
+ return 2;
+ }
+ return index;
+}
+
+/**
+ * A list of preference observers that should be disabled whilst we write our
+ * preference values.
+ *
+ * @type {{ prefName: string, observer: object }[]}
+ */
+const prefObservers = [];
+
// __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]
- )
- );
+ settingIndex = fixupIndex(settingIndex);
+ // Don't want to trigger our internal observers when setting ourselves.
+ for (const { prefName, observer } of prefObservers) {
+ Services.prefs.removeObserver(prefName, observer);
+ }
+ try {
+ // Make sure noscript is re-initialised at the next startup when the
+ // security level changes.
+ Services.prefs.setBoolPref(kNoScriptInitedPref, false);
+ Services.prefs.setIntPref(kSliderPref, settingIndex);
+ // NOTE: We do not clear kCustomPref. Instead, we rely on the preference
+ // being cleared on the next startup.
+ Object.keys(kSecuritySettings).forEach(prefName =>
+ Services.prefs.setBoolPref(
+ prefName,
+ kSecuritySettings[prefName][settingIndex]
+ )
+ );
+ } finally {
+ // Re-add the observers.
+ for (const { prefName, observer } of prefObservers) {
+ Services.prefs.addObserver(prefName, observer);
+ }
+ }
};
// __read_setting_from_prefs()__.
@@ -321,24 +360,6 @@ var read_setting_from_prefs = function (prefNames) {
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;
@@ -354,36 +375,82 @@ var initializeSecurityPrefs = function () {
}
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) {
+
+ const wasCustom = Services.prefs.getBoolPref(kCustomPref, false);
+ // For new profiles with no user preference, the security level should be "4"
+ // and it should not be custom.
+ let desiredIndex = Services.prefs.getIntPref(kSliderPref, 4);
+ desiredIndex = fixupIndex(desiredIndex);
+ // Make sure the user has a set preference user value.
+ Services.prefs.setIntPref(kSliderPref, desiredIndex);
+ Services.prefs.setBoolPref(kCustomPref, wasCustom);
+
+ // Make sure that the preference values at application startup match the
+ // expected values for the desired security level. See tor-browser#43783.
+
+ // NOTE: We assume that the controlled preference values that are read prior
+ // to profile-after-change do not change in value before this method is
+ // called. I.e. we expect the current preference values to match the
+ // preference values that were used during the application initialisation.
+ const effectiveIndex = read_setting_from_prefs();
+
+ if (wasCustom && effectiveIndex !== null) {
+ logger.info(`Custom startup values match index ${effectiveIndex}`);
+ // Do not consider custom any more.
+ // NOTE: This level needs to be set before it is read elsewhere. In
+ // particular, for the NoScript addon.
Services.prefs.setBoolPref(kCustomPref, false);
- write_setting_to_prefs(prefIndex);
+ Services.prefs.setIntPref(kSliderPref, effectiveIndex);
+ } else if (!wasCustom && effectiveIndex !== desiredIndex) {
+ // NOTE: We assume all our controlled preferences require a restart.
+ // In practice, only a subset of these preferences may actually require a
+ // restart, so we could switch their values. But we treat them all the same
+ // for simplicity, consistency and stability in case mozilla changes the
+ // restart requirements.
+ logger.info(`Startup values do not match for index ${desiredIndex}`);
+ SecurityLevelPrefs.requireRestart();
+ }
+
+ // Start listening for external changes to the controlled preferences.
+ prefObservers.push({
+ prefName: kCustomPref,
+ observer: bindPref(kCustomPref, custom => {
+ // Custom flag was removed mid-session. Requires a restart to apply the
+ // security level.
+ if (custom === false) {
+ logger.info("Custom flag was cleared externally");
+ SecurityLevelPrefs.requireRestart();
+ }
+ }),
});
- // 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);
- }
+ prefObservers.push({
+ prefName: kSliderPref,
+ observer: bindPref(kSliderPref, () => {
+ // Security level was changed mid-session. Requires a restart to apply.
+ logger.info("Security level was changed externally");
+ SecurityLevelPrefs.requireRestart();
+ }),
});
- // 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);
+
+ for (const prefName of Object.keys(kSecuritySettings)) {
+ prefObservers.push({
+ prefName,
+ observer: bindPref(prefName, () => {
+ logger.warn(
+ `The controlled preference ${prefName} was changed externally.` +
+ " Treating as a custom security level."
+ );
+ // Something outside of this module changed the preference value for a
+ // preference we control.
+ // Always treat as a custom security level for the rest of this session,
+ // even if the new preference values match a pre-set security level. We
+ // do this because some controlled preferences require a restart to be
+ // properly applied. See tor-browser#43783.
+ // In the case where it does match a pre-set security level, the custom
+ // flag will be cleared at the next startup.
+ Services.prefs.setBoolPref(kCustomPref, true);
+ }),
+ });
}
logger.info("security-prefs.js initialization complete");
@@ -437,8 +504,9 @@ export class SecurityLevel {
init() {
migratePreferences();
- initializeNoScriptControl();
+ // Fixup our preferences before we pass on the security level to NoScript.
initializeSecurityPrefs();
+ initializeNoScriptControl();
}
observe(aSubject, aTopic) {
@@ -448,10 +516,19 @@ export class SecurityLevel {
}
}
+/**
+ * @typedef {object} SecurityLevelRestartNotificationHandler
+ *
+ * An object that can serve the user a restart notification.
+ *
+ * @property {Function} tryRestartBrowser - The method that should be called to
+ * ask the user to restart the browser.
+ */
+
/*
Security Level Prefs
- Getters and Setters for relevant torbutton prefs
+ Getters and Setters for relevant security level prefs
*/
export const SecurityLevelPrefs = {
SecurityLevels: Object.freeze({
@@ -462,6 +539,14 @@ export const SecurityLevelPrefs = {
security_slider_pref: "browser.security_level.security_slider",
security_custom_pref: "browser.security_level.security_custom",
+ /**
+ * The current security level preference.
+ *
+ * This ignores any custom settings the user may have changed, and just
+ * gives the underlying security level.
+ *
+ * @type {?string}
+ */
get securityLevel() {
// Set the default return value to 0, which won't match anything in
// SecurityLevels.
@@ -471,18 +556,146 @@ export const SecurityLevelPrefs = {
)?.[0];
},
- set securityLevel(level) {
- const val = this.SecurityLevels[level];
- if (val !== undefined) {
- Services.prefs.setIntPref(this.security_slider_pref, val);
- }
+ /**
+ * Set the desired security level just before a restart.
+ *
+ * The caller must restart the browser after calling this method.
+ *
+ * @param {string} level - The name of the new security level to set.
+ */
+ setSecurityLevelBeforeRestart(level) {
+ write_setting_to_prefs(this.SecurityLevels[level]);
},
+ /**
+ * Whether the user has any custom setting values that do not match a pre-set
+ * security level.
+ *
+ * @type {boolean}
+ */
get securityCustom() {
return Services.prefs.getBoolPref(this.security_custom_pref);
},
- set securityCustom(val) {
- Services.prefs.setBoolPref(this.security_custom_pref, val);
+ /**
+ * A summary of the current security level.
+ *
+ * If the user has some custom settings, this returns "custom". Otherwise
+ * returns the name of the security level.
+ *
+ * @type {string}
+ */
+ get securityLevelSummary() {
+ if (this.securityCustom) {
+ return "custom";
+ }
+ return this.securityLevel ?? "custom";
+ },
+
+ /**
+ * Whether the browser should be restarted to apply the security level.
+ *
+ * @type {boolean}
+ */
+ _needRestart: false,
+
+ /**
+ * The external handler that can show a notification to the user, if any.
+ *
+ * @type {?SecurityLevelRestartNotificationHandler}
+ */
+ _restartNotificationHandler: null,
+
+ /**
+ * Set the external handler for showing notifications to the user.
+ *
+ * This should only be called once per session once the handler is ready to
+ * show a notification, which may occur immediately during this call.
+ *
+ * @param {SecurityLevelRestartNotificationHandler} handler - The new handler
+ * to use.
+ */
+ setRestartNotificationHandler(handler) {
+ logger.info("Restart notification handler is set");
+ this._restartNotificationHandler = handler;
+ if (this._needRestart) {
+ // Show now using the new handler.
+ this._tryShowRestartNotification();
+ }
+ },
+
+ /**
+ * A promise for any ongoing notification prompt task.
+ *
+ * @type {Promise}
+ */
+ _restartNotificationPromise: null,
+
+ /**
+ * Try show a notification to the user.
+ *
+ * If no notification handler has been attached yet, this will do nothing.
+ */
+ async _tryShowRestartNotification() {
+ if (!this._restartNotificationHandler) {
+ logger.info("Missing a restart notification handler");
+ // This may be added later in the session.
+ return;
+ }
+
+ const prevPromise = this._restartNotificationPromise;
+ let resolve;
+ ({ promise: this._restartNotificationPromise, resolve } =
+ Promise.withResolvers());
+ await prevPromise;
+
+ try {
+ await this._restartNotificationHandler?.tryRestartBrowser();
+ } finally {
+ // Allow the notification to be shown again.
+ resolve();
+ }
+ },
+
+ /**
+ * Mark the session as requiring a restart to apply a change in security
+ * level.
+ *
+ * The security level will immediately be switched to "custom", and the user
+ * may be shown a notification to restart the browser.
+ */
+ requireRestart() {
+ logger.warn("The browser needs to be restarted to set the security level");
+ // Treat as a custom security level for the rest of the session.
+ // At the next startup, the custom flag may be cleared if the settings are
+ // as expected.
+ Services.prefs.setBoolPref(kCustomPref, true);
+ this._needRestart = true;
+
+ // NOTE: We need to change the controlled security level preferences in
+ // response to the desired change in security level. We could either:
+ // 1. Only change the controlled preferences after the user confirms a
+ // restart. Or
+ // 2. Change the controlled preferences and then try and ask the user to
+ // restart.
+ //
+ // We choose the latter:
+ // 1. To allow users to manually restart.
+ // 2. If the user ignores or misses the notification, they will at least be
+ // in the correct state when the browser starts again. Although they will
+ // be in a custom/undefined state in the mean time.
+ // 3. Currently Android relies on triggering the change in security level
+ // by setting the browser.security_level.security_slider preference
+ // value. So it currently uses this path. So we need to set the values
+ // now, before it preforms a restart.
+ // TODO: Have android use the `setSecurityLevelBeforeRestart` method
+ // instead of setting the security_slider preference value directly, so that
+ // it knows exactly when it can restart the browser. tor-browser#43820
+ write_setting_to_prefs(Services.prefs.getIntPref(kSliderPref, 0));
+ // NOTE: Even though we have written the preferences, the session should
+ // still be marked as "custom" because:
+ // 1. Some preferences require a browser restart to be applied.
+ // 2. NoScript has not been updated with the new settings.
+ this._tryShowRestartNotification();
},
}; /* Security Level Prefs */
=====================================
toolkit/locales/en-US/toolkit/global/base-browser.ftl
=====================================
@@ -123,10 +123,6 @@ security-level-toolbar-button-custom =
# Uses sentence case in English (US).
security-level-panel-heading = Security level
-
-security-level-panel-level-standard = Standard
-security-level-panel-level-safer = Safer
-security-level-panel-level-safest = Safest
security-level-panel-learn-more-link = Learn more
# Button to open security level settings.
security-level-panel-open-settings-button = Settings…
@@ -136,6 +132,19 @@ security-level-panel-open-settings-button = Settings…
security-level-preferences-heading = Security Level
security-level-preferences-overview = Disable certain web features that can be used to attack your security and anonymity.
security-level-preferences-learn-more-link = Learn more
+# Text for a badge that labels the currently active security level.
+# The text in between '<span>' and '</span>' should contain some kind of bracket, like '(' and ')', or other punctuation used in your language to separate out text from its surrounding context. This will not be visible, but will be use for screen readers to make it clear that the text is not part of the same sentence. For example, in US English this would be read as "(Current level)", and the full line of text would be read as "Safest (Current level)".
+security-level-preferences-current-badge = <span>(</span>Current level<span>)</span>
+security-level-preferences-change-button = Change…
+
+## Security level settings dialog.
+
+security-level-dialog-window =
+ .title = Change security level
+
+# '-brand-short-name' is the localized browser name, like "Tor Browser".
+security-level-dialog-restart-description = You will need to restart { -brand-short-name } to apply any changes. This will close all windows and tabs.
+
security-level-preferences-level-standard =
.label = Standard
security-level-preferences-level-safer =
@@ -143,6 +152,16 @@ security-level-preferences-level-safer =
security-level-preferences-level-safest =
.label = Safest
+security-level-dialog-save-restart =
+ .label = Save and restart
+
+## Security level names shown in the security panel and settings.
+
+security-level-panel-level-standard = Standard
+security-level-panel-level-safer = Safer
+security-level-panel-level-safest = Safest
+security-level-panel-level-custom = Custom
+
## Security level summaries shown in security panel and settings.
security-level-summary-standard = All browser and website features are enabled.
@@ -161,13 +180,13 @@ security-level-preferences-bullet-limit-font-and-symbols-and-images = Some fonts
## Custom security level.
## Some custom preferences configuration has placed the user outside one of the standard three levels.
-# Shown in the security level panel as an orange badge next to the expected level.
-security-level-panel-custom-badge = Custom
-# Shown in the security level settings in a warning box.
-security-level-preferences-custom-heading = Custom security level configured
# Description of custom state and recommended action.
# Shown in the security level panel and settings.
security-level-summary-custom = 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.
-# Button to undo custom changes to the security level and place the user in one of the standard security levels.
-# Shown in the security level panel and settings.
-security-level-restore-defaults-button = Restore defaults
+
+## Security level restart prompt.
+
+security-level-restart-prompt-title = Your security level settings require a restart
+security-level-restart-prompt-body = You must restart { -brand-short-name } for your security level settings to be applied. This will close all your windows and tabs.
+security-level-restart-prompt-button-restart = Restart
+security-level-restart-prompt-button-ignore = Ignore
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/50…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/50…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][base-browser-140.0a1-15.0-1] 5 commits: fixup! BB 40925: Implemented the Security Level component
by ma1 (@ma1) 10 Jun '25
by ma1 (@ma1) 10 Jun '25
10 Jun '25
ma1 pushed to branch base-browser-140.0a1-15.0-1 at The Tor Project / Applications / Tor Browser
Commits:
a00fd3bb by Henry Wilkes at 2025-06-10T18:57:40+02:00
fixup! BB 40925: Implemented the Security Level component
TB 43783: Prompt user for a restart if their security level preferences
are not aligned at startup or mid-session.
Also handle failures to apply NoScript settings.
- - - - -
d994c066 by Henry Wilkes at 2025-06-10T18:57:52+02:00
fixup! BB 40069: Add helpers for message passing with extensions
TB 43783: Allow the browser to wait for the NoScript settings to be
applied.
- - - - -
44db81d7 by Henry Wilkes at 2025-06-10T18:57:54+02:00
fixup! Base Browser strings
TB 43783: Add security level prompt strings.
- - - - -
0ad2b8cd by Henry Wilkes at 2025-06-10T18:58:05+02:00
fixup! Base Browser strings
TB 43782: Add strings for new security level UX flow.
- - - - -
133f9afe by Henry Wilkes at 2025-06-10T18:58:07+02:00
fixup! BB 40925: Implemented the Security Level component
TB 43782: Update security level UI for new UX flow.
In addition, we drop the distinction between the security levels in the
UI when the user has a custom security level.
I.e. we always show shield as unfilled but with a yellow dot in the
toolbar, and we just call it "Custom" rather than "Standard Custom",
etc.
- - - - -
17 changed files:
- browser/components/BrowserGlue.sys.mjs
- + browser/components/securitylevel/SecurityLevelUIUtils.sys.mjs
- browser/components/securitylevel/content/securityLevel.js
- browser/components/securitylevel/content/securityLevelButton.css
- + browser/components/securitylevel/content/securityLevelDialog.js
- + browser/components/securitylevel/content/securityLevelDialog.xhtml
- browser/components/securitylevel/content/securityLevelPanel.css
- browser/components/securitylevel/content/securityLevelPanel.inc.xhtml
- browser/components/securitylevel/content/securityLevelPreferences.css
- browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml
- browser/components/securitylevel/jar.mn
- browser/components/securitylevel/moz.build
- + browser/modules/SecurityLevelRestartNotification.sys.mjs
- browser/modules/moz.build
- toolkit/components/extensions/ExtensionParent.sys.mjs
- toolkit/components/securitylevel/SecurityLevel.sys.mjs
- toolkit/locales/en-US/toolkit/global/base-browser.ftl
Changes:
=====================================
browser/components/BrowserGlue.sys.mjs
=====================================
@@ -63,6 +63,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
ScreenshotsUtils: "resource:///modules/ScreenshotsUtils.sys.mjs",
SearchSERPTelemetry:
"moz-src:///browser/components/search/SearchSERPTelemetry.sys.mjs",
+ SecurityLevelRestartNotification:
+ "resource:///modules/SecurityLevelRestartNotification.sys.mjs",
SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
@@ -994,6 +996,8 @@ BrowserGlue.prototype = {
lazy.WeaveService.init();
}
+ lazy.SecurityLevelRestartNotification.ready();
+
ClipboardPrivacy.startup();
lazy.BrowserUtils.callModulesFromCategory(
=====================================
browser/components/securitylevel/SecurityLevelUIUtils.sys.mjs
=====================================
@@ -0,0 +1,73 @@
+/**
+ * Common methods for the desktop security level components.
+ */
+export const SecurityLevelUIUtils = {
+ /**
+ * Create an element that gives a description of the security level. To be
+ * used in the settings.
+ *
+ * @param {string} level - The security level to describe.
+ * @param {Document} doc - The document where the element will be inserted.
+ *
+ * @returns {Element} - The newly created element.
+ */
+ createDescriptionElement(level, doc) {
+ const el = doc.createElement("div");
+ el.classList.add("security-level-description");
+
+ let l10nIdSummary;
+ let bullets;
+ switch (level) {
+ case "standard":
+ l10nIdSummary = "security-level-summary-standard";
+ break;
+ case "safer":
+ l10nIdSummary = "security-level-summary-safer";
+ bullets = [
+ "security-level-preferences-bullet-https-only-javascript",
+ "security-level-preferences-bullet-limit-font-and-symbols",
+ "security-level-preferences-bullet-limit-media",
+ ];
+ break;
+ case "safest":
+ l10nIdSummary = "security-level-summary-safest";
+ bullets = [
+ "security-level-preferences-bullet-disabled-javascript",
+ "security-level-preferences-bullet-limit-font-and-symbols-and-images",
+ "security-level-preferences-bullet-limit-media",
+ ];
+ break;
+ case "custom":
+ l10nIdSummary = "security-level-summary-custom";
+ break;
+ default:
+ throw Error(`Unhandled level: ${level}`);
+ }
+
+ const summaryEl = doc.createElement("div");
+ summaryEl.classList.add("security-level-summary");
+ doc.l10n.setAttributes(summaryEl, l10nIdSummary);
+
+ el.append(summaryEl);
+
+ if (!bullets) {
+ return el;
+ }
+
+ const listEl = doc.createElement("ul");
+ listEl.classList.add("security-level-description-extra");
+ // Add a mozilla styling class as well:
+ listEl.classList.add("privacy-extra-information");
+ for (const l10nId of bullets) {
+ const bulletEl = doc.createElement("li");
+ bulletEl.classList.add("security-level-description-bullet");
+
+ doc.l10n.setAttributes(bulletEl, l10nId);
+
+ listEl.append(bulletEl);
+ }
+
+ el.append(listEl);
+ return el;
+ },
+};
=====================================
browser/components/securitylevel/content/securityLevel.js
=====================================
@@ -1,9 +1,10 @@
"use strict";
-/* global AppConstants, Services, openPreferences, XPCOMUtils */
+/* global AppConstants, Services, openPreferences, XPCOMUtils, gSubDialog */
ChromeUtils.defineESModuleGetters(this, {
SecurityLevelPrefs: "resource://gre/modules/SecurityLevel.sys.mjs",
+ SecurityLevelUIUtils: "resource:///modules/SecurityLevelUIUtils.sys.mjs",
});
/*
@@ -35,12 +36,8 @@ var SecurityLevelButton = {
_anchorButton: null,
_configUIFromPrefs() {
- const level = SecurityLevelPrefs.securityLevel;
- if (!level) {
- return;
- }
- const custom = SecurityLevelPrefs.securityCustom;
- this._button.setAttribute("level", custom ? `${level}_custom` : level);
+ const level = SecurityLevelPrefs.securityLevelSummary;
+ this._button.setAttribute("level", level);
let l10nIdLevel;
switch (level) {
@@ -53,15 +50,12 @@ var SecurityLevelButton = {
case "safest":
l10nIdLevel = "security-level-toolbar-button-safest";
break;
+ case "custom":
+ l10nIdLevel = "security-level-toolbar-button-custom";
+ break;
default:
throw Error(`Unhandled level: ${level}`);
}
- if (custom) {
- // Don't distinguish between the different levels when in the custom
- // state. We just want to emphasise that it is custom rather than any
- // specific level.
- l10nIdLevel = "security-level-toolbar-button-custom";
- }
document.l10n.setAttributes(this._button, l10nIdLevel);
},
@@ -164,12 +158,7 @@ var SecurityLevelPanel = {
panel: document.getElementById("securityLevel-panel"),
background: document.getElementById("securityLevel-background"),
levelName: document.getElementById("securityLevel-level"),
- customName: document.getElementById("securityLevel-custom"),
summary: document.getElementById("securityLevel-summary"),
- restoreDefaultsButton: document.getElementById(
- "securityLevel-restoreDefaults"
- ),
- settingsButton: document.getElementById("securityLevel-settings"),
};
const learnMoreEl = document.getElementById("securityLevel-learnMore");
@@ -177,12 +166,11 @@ var SecurityLevelPanel = {
this.hide();
});
- this._elements.restoreDefaultsButton.addEventListener("command", () => {
- this.restoreDefaults();
- });
- this._elements.settingsButton.addEventListener("command", () => {
- this.openSecuritySettings();
- });
+ document
+ .getElementById("securityLevel-settings")
+ .addEventListener("command", () => {
+ this.openSecuritySettings();
+ });
this._elements.panel.addEventListener("popupshown", () => {
// Bring focus into the panel by focusing the default button.
@@ -199,19 +187,7 @@ var SecurityLevelPanel = {
}
// get security prefs
- const level = SecurityLevelPrefs.securityLevel;
- const custom = SecurityLevelPrefs.securityCustom;
-
- // only visible when user is using custom settings
- this._elements.customName.hidden = !custom;
- this._elements.restoreDefaultsButton.hidden = !custom;
- if (custom) {
- this._elements.settingsButton.removeAttribute("default");
- this._elements.restoreDefaultsButton.setAttribute("default", "true");
- } else {
- this._elements.settingsButton.setAttribute("default", "true");
- this._elements.restoreDefaultsButton.removeAttribute("default");
- }
+ const level = SecurityLevelPrefs.securityLevelSummary;
// Descriptions change based on security level
this._elements.background.setAttribute("level", level);
@@ -230,12 +206,13 @@ var SecurityLevelPanel = {
l10nIdLevel = "security-level-panel-level-safest";
l10nIdSummary = "security-level-summary-safest";
break;
+ case "custom":
+ l10nIdLevel = "security-level-panel-level-custom";
+ l10nIdSummary = "security-level-summary-custom";
+ break;
default:
throw Error(`Unhandled level: ${level}`);
}
- if (custom) {
- l10nIdSummary = "security-level-summary-custom";
- }
document.l10n.setAttributes(this._elements.levelName, l10nIdLevel);
document.l10n.setAttributes(this._elements.summary, l10nIdSummary);
@@ -269,13 +246,6 @@ var SecurityLevelPanel = {
this._elements.panel.hidePopup();
},
- restoreDefaults() {
- SecurityLevelPrefs.securityCustom = false;
- // Move focus to the settings button since restore defaults button will
- // become hidden.
- this._elements.settingsButton.focus();
- },
-
openSecuritySettings() {
openPreferences("privacy-securitylevel");
this.hide();
@@ -301,59 +271,64 @@ var SecurityLevelPanel = {
var SecurityLevelPreferences = {
_securityPrefsBranch: null,
+
/**
- * The notification box shown when the user has a custom security setting.
- *
- * @type {Element}
- */
- _customNotification: null,
- /**
- * The radiogroup for this preference.
- *
- * @type {Element}
- */
- _radiogroup: null,
- /**
- * A list of radio options and their containers.
+ * The element that shows the current security level.
*
- * @type {Array<object>}
+ * @type {?Element}
*/
- _radioOptions: null,
+ _currentEl: null,
_populateXUL() {
- this._customNotification = document.getElementById(
- "securityLevel-customNotification"
+ this._currentEl = document.getElementById("security-level-current");
+ const changeButton = document.getElementById("security-level-change");
+ const badgeEl = this._currentEl.querySelector(
+ ".security-level-current-badge"
);
- document
- .getElementById("securityLevel-restoreDefaults")
- .addEventListener("command", () => {
- SecurityLevelPrefs.securityCustom = false;
- });
-
- this._radiogroup = document.getElementById("securityLevel-radiogroup");
- this._radioOptions = Array.from(
- this._radiogroup.querySelectorAll(".securityLevel-radio-option"),
- container => {
- return { container, radio: container.querySelector("radio") };
- }
- );
+ for (const { level, nameId } of [
+ { level: "standard", nameId: "security-level-panel-level-standard" },
+ { level: "safer", nameId: "security-level-panel-level-safer" },
+ { level: "safest", nameId: "security-level-panel-level-safest" },
+ { level: "custom", nameId: "security-level-panel-level-custom" },
+ ]) {
+ // Classes that control visibility:
+ // security-level-current-standard
+ // security-level-current-safer
+ // security-level-current-safest
+ // security-level-current-custom
+ const visibilityClass = `security-level-current-${level}`;
+ const nameEl = document.createElement("div");
+ nameEl.classList.add("security-level-name", visibilityClass);
+ document.l10n.setAttributes(nameEl, nameId);
+
+ const descriptionEl = SecurityLevelUIUtils.createDescriptionElement(
+ level,
+ document
+ );
+ descriptionEl.classList.add(visibilityClass);
+
+ this._currentEl.insertBefore(nameEl, badgeEl);
+ this._currentEl.insertBefore(descriptionEl, changeButton);
+ }
- this._radiogroup.addEventListener("select", () => {
- SecurityLevelPrefs.securityLevel = this._radiogroup.value;
+ changeButton.addEventListener("click", () => {
+ this._openDialog();
});
},
+ _openDialog() {
+ gSubDialog.open(
+ "chrome://browser/content/securitylevel/securityLevelDialog.xhtml",
+ { features: "resizable=yes" }
+ );
+ },
+
_configUIFromPrefs() {
- this._radiogroup.value = SecurityLevelPrefs.securityLevel;
- const isCustom = SecurityLevelPrefs.securityCustom;
- this._radiogroup.disabled = isCustom;
- this._customNotification.hidden = !isCustom;
- // Have the container's selection CSS class match the selection state of the
- // radio elements.
- for (const { container, radio } of this._radioOptions) {
- container.classList.toggle("selected", radio.selected);
- }
+ // Set a data-current-level attribute for showing the current security
+ // level, and hiding the rest.
+ this._currentEl.dataset.currentLevel =
+ SecurityLevelPrefs.securityLevelSummary;
},
init() {
=====================================
browser/components/securitylevel/content/securityLevelButton.css
=====================================
@@ -7,12 +7,6 @@ toolbarbutton#security-level-button[level="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"] {
+toolbarbutton#security-level-button[level="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");
-}
=====================================
browser/components/securitylevel/content/securityLevelDialog.js
=====================================
@@ -0,0 +1,188 @@
+"use strict";
+
+const { SecurityLevelPrefs } = ChromeUtils.importESModule(
+ "resource://gre/modules/SecurityLevel.sys.mjs"
+);
+const { SecurityLevelUIUtils } = ChromeUtils.importESModule(
+ "resource:///modules/SecurityLevelUIUtils.sys.mjs"
+);
+
+const gSecurityLevelDialog = {
+ /**
+ * The security level when this dialog was opened.
+ *
+ * @type {string}
+ */
+ _prevLevel: SecurityLevelPrefs.securityLevelSummary,
+ /**
+ * The security level currently selected.
+ *
+ * @type {string}
+ */
+ _selectedLevel: "",
+ /**
+ * The radiogroup for this preference.
+ *
+ * @type {?Element}
+ */
+ _radiogroup: null,
+ /**
+ * A list of radio options and their containers.
+ *
+ * @type {?Array<{ container: Element, radio: Element }>}
+ */
+ _radioOptions: null,
+
+ /**
+ * Initialise the dialog.
+ */
+ async init() {
+ const dialog = document.getElementById("security-level-dialog");
+ dialog.addEventListener("dialogaccept", event => {
+ if (this._acceptButton.disabled) {
+ event.preventDefault();
+ return;
+ }
+ this._commitChange();
+ });
+
+ this._acceptButton = dialog.getButton("accept");
+
+ document.l10n.setAttributes(
+ this._acceptButton,
+ "security-level-dialog-save-restart"
+ );
+
+ this._radiogroup = document.getElementById("security-level-radiogroup");
+
+ this._radioOptions = Array.from(
+ this._radiogroup.querySelectorAll(".security-level-radio-container"),
+ container => {
+ return {
+ container,
+ radio: container.querySelector(".security-level-radio"),
+ };
+ }
+ );
+
+ for (const { container, radio } of this._radioOptions) {
+ const level = radio.value;
+ radio.id = `security-level-radio-${level}`;
+ const currentEl = container.querySelector(
+ ".security-level-current-badge"
+ );
+ currentEl.id = `security-level-current-badge-${level}`;
+ const descriptionEl = SecurityLevelUIUtils.createDescriptionElement(
+ level,
+ document
+ );
+ descriptionEl.classList.add("indent");
+ descriptionEl.id = `security-level-description-${level}`;
+
+ // Wait for the full translation of the element before adding it to the
+ // DOM. In particular, we want to make sure the elements have text before
+ // we measure the maxHeight below.
+ await document.l10n.translateFragment(descriptionEl);
+ document.l10n.pauseObserving();
+ container.append(descriptionEl);
+ document.l10n.resumeObserving();
+
+ if (level === this._prevLevel) {
+ currentEl.hidden = false;
+ // When the currentEl is visible, include it in the accessible name for
+ // the radio option.
+ // NOTE: The currentEl has an accessible name which includes punctuation
+ // to help separate it's content from the security level name.
+ // E.g. "Standard (Current level)".
+ radio.setAttribute("aria-labelledby", `${radio.id} ${currentEl.id}`);
+ } else {
+ currentEl.hidden = true;
+ }
+ // We point the accessible description to the wrapping
+ // .security-level-description element, rather than its children
+ // that define the actual text content. This means that when the
+ // privacy-extra-information is shown or hidden, its text content is
+ // included or excluded from the accessible description, respectively.
+ radio.setAttribute("aria-describedby", descriptionEl.id);
+ }
+
+ // We want to reserve the maximum height of the radiogroup so that the
+ // dialog has enough height when the user switches options. So we cycle
+ // through the options and measure the height when they are selected to set
+ // a minimum height that fits all of them.
+ // NOTE: At the time of implementation, at this point the dialog may not
+ // yet have the "subdialog" attribute, which means it is missing the
+ // common.css stylesheet from its shadow root, which effects the size of the
+ // .radio-check element and the font. Therefore, we have duplicated the
+ // import of common.css in SecurityLevelDialog.xhtml to ensure it is applied
+ // at this earlier stage.
+ let maxHeight = 0;
+ for (const { container } of this._radioOptions) {
+ container.classList.add("selected");
+ maxHeight = Math.max(
+ maxHeight,
+ this._radiogroup.getBoundingClientRect().height
+ );
+ container.classList.remove("selected");
+ }
+ this._radiogroup.style.minHeight = `${maxHeight}px`;
+
+ if (this._prevLevel !== "custom") {
+ this._selectedLevel = this._prevLevel;
+ this._radiogroup.value = this._prevLevel;
+ } else {
+ this._radiogroup.selectedItem = null;
+ }
+
+ this._radiogroup.addEventListener("select", () => {
+ this._selectedLevel = this._radiogroup.value;
+ this._updateSelected();
+ });
+
+ this._updateSelected();
+ },
+
+ /**
+ * Update the UI in response to a change in selection.
+ */
+ _updateSelected() {
+ this._acceptButton.disabled =
+ !this._selectedLevel || this._selectedLevel === this._prevLevel;
+ // Have the container's `selected` CSS class match the selection state of
+ // the radio elements.
+ for (const { container, radio } of this._radioOptions) {
+ container.classList.toggle("selected", radio.selected);
+ }
+ },
+
+ /**
+ * Commit the change in security level and restart the browser.
+ */
+ _commitChange() {
+ SecurityLevelPrefs.setSecurityLevelBeforeRestart(this._selectedLevel);
+ Services.startup.quit(
+ Services.startup.eAttemptQuit | Services.startup.eRestart
+ );
+ },
+};
+
+// Initial focus is not visible, even if opened with a keyboard. We avoid the
+// default handler and manage the focus ourselves, which will paint the focus
+// ring by default.
+// NOTE: A side effect is that the focus ring will show even if the user opened
+// with a mouse event.
+// TODO: Remove this once bugzilla bug 1708261 is resolved.
+document.subDialogSetDefaultFocus = () => {
+ document.getElementById("security-level-radiogroup").focus();
+};
+
+// Delay showing and sizing the subdialog until it is fully initialised.
+document.mozSubdialogReady = new Promise(resolve => {
+ window.addEventListener(
+ "DOMContentLoaded",
+ () => {
+ gSecurityLevelDialog.init().finally(resolve);
+ },
+ { once: true }
+ );
+});
=====================================
browser/components/securitylevel/content/securityLevelDialog.xhtml
=====================================
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+
+<window
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ data-l10n-id="security-level-dialog-window"
+>
+ <dialog id="security-level-dialog" buttons="accept,cancel">
+ <linkset>
+ <html:link rel="stylesheet" href="chrome://global/skin/global.css" />
+ <!-- NOTE: We include common.css explicitly, rather than relying on
+ - the dialog's shadowroot importing it, which is late loaded in
+ - response to the dialog's "subdialog" attribute, which is set
+ - in response to DOMFrameContentLoaded.
+ - In particular, we need the .radio-check rule and font rules from
+ - common-shared.css to be in place when gSecurityLevelDialog.init is
+ - called, which will help ensure that the radio element has the correct
+ - size when we measure its bounding box. -->
+ <html:link
+ rel="stylesheet"
+ href="chrome://global/skin/in-content/common.css"
+ />
+ <html:link
+ rel="stylesheet"
+ href="chrome://browser/skin/preferences/preferences.css"
+ />
+ <html:link
+ rel="stylesheet"
+ href="chrome://browser/skin/preferences/privacy.css"
+ />
+ <html:link
+ rel="stylesheet"
+ href="chrome://browser/content/securitylevel/securityLevelPreferences.css"
+ />
+
+ <html:link rel="localization" href="branding/brand.ftl" />
+ <html:link rel="localization" href="toolkit/global/base-browser.ftl" />
+ </linkset>
+
+ <script src="chrome://browser/content/securitylevel/securityLevelDialog.js" />
+
+ <description data-l10n-id="security-level-dialog-restart-description" />
+
+ <radiogroup id="security-level-radiogroup" class="highlighting-group">
+ <html:div
+ class="security-level-radio-container security-level-grid privacy-detailedoption info-box-container"
+ >
+ <radio
+ class="security-level-radio security-level-name"
+ value="standard"
+ data-l10n-id="security-level-preferences-level-standard"
+ />
+ <html:div
+ class="security-level-current-badge"
+ data-l10n-id="security-level-preferences-current-badge"
+ ></html:div>
+ </html:div>
+ <html:div
+ class="security-level-radio-container security-level-grid privacy-detailedoption info-box-container"
+ >
+ <radio
+ class="security-level-radio security-level-name"
+ value="safer"
+ data-l10n-id="security-level-preferences-level-safer"
+ />
+ <html:div
+ class="security-level-current-badge"
+ data-l10n-id="security-level-preferences-current-badge"
+ ></html:div>
+ </html:div>
+ <html:div
+ class="security-level-radio-container security-level-grid privacy-detailedoption info-box-container"
+ >
+ <radio
+ class="security-level-radio security-level-name"
+ value="safest"
+ data-l10n-id="security-level-preferences-level-safest"
+ />
+ <html:div
+ class="security-level-current-badge"
+ data-l10n-id="security-level-preferences-current-badge"
+ ></html:div>
+ </html:div>
+ </radiogroup>
+ </dialog>
+</window>
=====================================
browser/components/securitylevel/content/securityLevelPanel.css
=====================================
@@ -23,7 +23,7 @@
background-position-x: right var(--background-inline-offset);
}
-#securityLevel-background[level="standard"] {
+#securityLevel-background:is([level="standard"], [level="custom"]) {
background-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard");
}
@@ -49,14 +49,6 @@
font-weight: 600;
}
-#securityLevel-custom {
- border-radius: 4px;
- background-color: var(--warning-icon-bgcolor);
- color: black;
- padding: 0.4em 0.5em;
- margin-inline-start: 1em;
-}
-
#securityLevel-summary {
padding-inline-end: 5em;
max-width: 20em;
=====================================
browser/components/securitylevel/content/securityLevelPanel.inc.xhtml
=====================================
@@ -15,10 +15,6 @@
<vbox id="securityLevel-background" class="panel-subview-body">
<html:p id="securityLevel-subheading">
<html:span id="securityLevel-level"></html:span>
- <html:span
- id="securityLevel-custom"
- data-l10n-id="security-level-panel-custom-badge"
- ></html:span>
</html:p>
<html:p id="securityLevel-summary"></html:p>
<html:a
@@ -32,12 +28,8 @@
<button
id="securityLevel-settings"
class="footer-button"
+ default="true"
data-l10n-id="security-level-panel-open-settings-button"
/>
- <button
- id="securityLevel-restoreDefaults"
- class="footer-button"
- data-l10n-id="security-level-restore-defaults-button"
- />
</html:moz-button-group>
</panel>
=====================================
browser/components/securitylevel/content/securityLevelPreferences.css
=====================================
@@ -1,26 +1,166 @@
-#securityLevel-groupbox {
- --section-highlight-background-color: color-mix(in srgb, var(--in-content-accent-color) 20%, transparent);
+.security-level-grid {
+ display: grid;
+ grid-template:
+ "icon name badge button" min-content
+ "icon summary summary button" auto
+ "icon extra extra ." auto
+ / max-content max-content 1fr max-content;
}
-#securityLevel-customNotification {
- /* Spacing similar to #fpiIncompatibilityWarning. */
- margin-block: 16px;
+.security-level-icon {
+ grid-area: icon;
+ align-self: start;
+ width: 24px;
+ height: 24px;
+ -moz-context-properties: fill;
+ fill: var(--in-content-icon-color);
+ margin-block-start: var(--space-xsmall);
+ margin-inline-end: var(--space-large);
}
-.info-icon.securityLevel-custom-warning-icon {
- list-style-image: url("chrome://global/skin/icons/warning.svg");
+.security-level-current-badge {
+ grid-area: badge;
+ align-self: center;
+ justify-self: start;
+ white-space: nowrap;
+ background: var(--background-color-information);
+ color: inherit;
+ font-size: var(--font-size-small);
+ border-radius: var(--border-radius-circle);
+ margin-inline-start: var(--space-small);
+ padding-block: var(--space-xsmall);
+ padding-inline: var(--space-small);
}
-#securityLevel-customHeading {
+.security-level-current-badge span {
+ /* Still accessible to screen reader, but not visual.
+ * Keep inline, but with no layout width. */
+ display: inline-block;
+ width: 1px;
+ margin-inline-end: -1px;
+ clip-path: inset(50%);
+}
+
+@media (prefers-contrast) and (not (forced-colors)) {
+ .security-level-current-badge {
+ /* Match the checkbox/radio colors. */
+ background: var(--color-accent-primary);
+ color: var(--button-text-color-primary);
+ }
+}
+
+@media (forced-colors) {
+ .security-level-current-badge {
+ /* Match the checkbox/radio/selected colors. */
+ background: SelectedItem;
+ color: SelectedItemText;
+ }
+}
+
+.security-level-name {
+ grid-area: name;
font-weight: bold;
+ align-self: center;
+ white-space: nowrap;
+}
+
+.security-level-description {
+ display: grid;
+ grid-column: summary-start / extra-end;
+ grid-row: summary-start / extra-end;
+ grid-template-rows: subgrid;
+ grid-template-columns: subgrid;
+ margin-block-start: var(--space-small);
+}
+
+.security-level-summary {
+ grid-area: summary;
+}
+
+.security-level-description-extra {
+ grid-area: extra;
+ margin-block: var(--space-medium) 0;
+ margin-inline: var(--space-large) 0;
+ padding: 0;
+}
+
+.security-level-description-bullet:not(:last-child) {
+ margin-block-end: var(--space-medium);
+}
+
+/* Tweak current security level display. */
+
+#security-level-current {
+ margin-block-start: var(--space-large);
+ background: var(--in-content-box-background);
+ border: 1px solid var(--in-content-box-border-color);
+ border-radius: var(--border-radius-small);
+ padding: var(--space-medium);
+}
+
+#security-level-change {
+ grid-area: button;
+ align-self: center;
+ margin: 0;
+ margin-inline-start: var(--space-large);
+}
+
+/* Adjust which content is visible depending on the current security level. */
+
+#security-level-current:not([data-current-level="standard"]) .security-level-current-standard {
+ display: none;
+}
+
+#security-level-current:not([data-current-level="safer"]) .security-level-current-safer {
+ display: none;
+}
+
+#security-level-current:not([data-current-level="safest"]) .security-level-current-safest {
+ display: none;
+}
+
+#security-level-current:not([data-current-level="custom"]) .security-level-current-custom {
+ display: none;
+}
+
+#security-level-current[data-current-level="standard"] .security-level-icon {
+ content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard");
+}
+
+#security-level-current[data-current-level="safer"] .security-level-icon {
+ content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safer");
+}
+
+#security-level-current[data-current-level="safest"] .security-level-icon {
+ content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safest");
+}
+
+#security-level-current[data-current-level="custom"] .security-level-icon {
+ content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard_custom");
+}
+
+/* Tweak security level dialog. */
+
+#security-level-radiogroup {
+ margin-block: var(--space-large) var(--space-xlarge);
+}
+
+.security-level-radio-container {
+ padding-block: var(--space-large);
+}
+
+#security-level-radiogroup .security-level-radio {
+ margin: 0;
}
-#securityLevel-radiogroup[disabled] {
- opacity: 0.5;
+#security-level-radiogroup .radio-label-box {
+ /* .security-level-current-badge already has a margin. */
+ margin: 0;
}
-/* Overwrite the rule in common-shared.css so we don't get 0.25 opacity overall
- * on the radio text. */
-#securityLevel-radiogroup[disabled] radio[disabled] .radio-label-box {
- opacity: 1.0;
+#security-level-radiogroup .privacy-detailedoption.security-level-radio-container:not(.selected) .security-level-description-extra {
+ /* .privacy-detailedoption uses visibility: hidden, which does not work with
+ * our grid display (the margin is still reserved) so we use display: none
+ * instead. */
+ display: none;
}
=====================================
browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml
=====================================
@@ -17,112 +17,19 @@
data-l10n-id="security-level-preferences-learn-more-link"
></html:a>
</description>
- <hbox
- id="securityLevel-customNotification"
- class="info-box-container"
- flex="1"
- >
- <hbox class="info-icon-container">
- <image class="info-icon securityLevel-custom-warning-icon"/>
- </hbox>
- <vbox flex="1">
- <label
- id="securityLevel-customHeading"
- data-l10n-id="security-level-preferences-custom-heading"
- />
- <description
- id="securityLevel-customDescription"
- data-l10n-id="security-level-summary-custom"
- flex="1"
- />
- </vbox>
- <hbox align="center">
- <button
- id="securityLevel-restoreDefaults"
- data-l10n-id="security-level-restore-defaults-button"
- />
- </hbox>
- </hbox>
- <radiogroup id="securityLevel-radiogroup">
- <vbox class="securityLevel-radio-option privacy-detailedoption info-box-container">
- <radio
- value="standard"
- data-l10n-id="security-level-preferences-level-standard"
- aria-describedby="securityLevelSummary-standard"
- />
- <vbox id="securityLevelSummary-standard" class="indent">
- <label data-l10n-id="security-level-summary-standard" />
- </vbox>
- </vbox>
- <vbox class="securityLevel-radio-option privacy-detailedoption info-box-container">
- <!-- NOTE: We point the accessible description to the wrapping vbox
- - rather than its first description element. This means that when the
- - privacy-extra-information is shown or hidden, its text content is
- - included or excluded from the accessible description, respectively.
- -->
- <radio
- value="safer"
- data-l10n-id="security-level-preferences-level-safer"
- aria-describedby="securityLevelSummary-safer"
- />
- <vbox id="securityLevelSummary-safer" class="indent">
- <label data-l10n-id="security-level-summary-safer" />
- <vbox class="privacy-extra-information">
- <vbox class="indent">
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-https-only-javascript"
- />
- </hbox>
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-limit-font-and-symbols"
- />
- </hbox>
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-limit-media"
- />
- </hbox>
- </vbox>
- </vbox>
- </vbox>
- </vbox>
- <vbox class="securityLevel-radio-option privacy-detailedoption info-box-container">
- <radio
- value="safest"
- data-l10n-id="security-level-preferences-level-safest"
- aria-describedby="securityLevelSummary-safest"
- />
- <vbox id="securityLevelSummary-safest" class="indent">
- <label data-l10n-id="security-level-summary-safest" />
- <vbox class="privacy-extra-information">
- <vbox class="indent">
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-disabled-javascript"
- />
- </hbox>
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-limit-font-and-symbols-and-images"
- />
- </hbox>
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-limit-media"
- />
- </hbox>
- </vbox>
- </vbox>
- </vbox>
- </vbox>
- </radiogroup>
+ <html:div id="security-level-current" class="security-level-grid">
+ <html:img
+ class="security-level-icon"
+ alt=""
+ />
+ <html:div
+ class="security-level-current-badge"
+ data-l10n-id="security-level-preferences-current-badge"
+ ></html:div>
+ <html:button
+ id="security-level-change"
+ data-l10n-id="security-level-preferences-change-button"
+ ></html:button>
+ </html:div>
</vbox>
</groupbox>
=====================================
browser/components/securitylevel/jar.mn
=====================================
@@ -4,3 +4,5 @@ browser.jar:
content/browser/securitylevel/securityLevelButton.css (content/securityLevelButton.css)
content/browser/securitylevel/securityLevelPreferences.css (content/securityLevelPreferences.css)
content/browser/securitylevel/securityLevelIcon.svg (content/securityLevelIcon.svg)
+ content/browser/securitylevel/securityLevelDialog.xhtml (content/securityLevelDialog.xhtml)
+ content/browser/securitylevel/securityLevelDialog.js (content/securityLevelDialog.js)
=====================================
browser/components/securitylevel/moz.build
=====================================
@@ -1 +1,5 @@
JAR_MANIFESTS += ["jar.mn"]
+
+EXTRA_JS_MODULES += [
+ "SecurityLevelUIUtils.sys.mjs",
+]
=====================================
browser/modules/SecurityLevelRestartNotification.sys.mjs
=====================================
@@ -0,0 +1,72 @@
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
+ SecurityLevelPrefs: "resource://gre/modules/SecurityLevel.sys.mjs",
+});
+
+ChromeUtils.defineLazyGetter(lazy, "NotificationStrings", function () {
+ return new Localization([
+ "branding/brand.ftl",
+ "toolkit/global/base-browser.ftl",
+ ]);
+});
+
+/**
+ * Interface for showing the security level restart notification on desktop.
+ */
+export const SecurityLevelRestartNotification = {
+ /**
+ * Whether we have already been initialised.
+ *
+ * @type {boolean}
+ */
+ _initialized: false,
+
+ /**
+ * Called when the UI is ready to show a notification.
+ */
+ ready() {
+ if (this._initialized) {
+ return;
+ }
+ this._initialized = true;
+ lazy.SecurityLevelPrefs.setRestartNotificationHandler(this);
+ },
+
+ /**
+ * Show the restart notification, and perform the restart if the user agrees.
+ */
+ async tryRestartBrowser() {
+ const [titleText, bodyText, primaryButtonText, secondaryButtonText] =
+ await lazy.NotificationStrings.formatValues([
+ { id: "security-level-restart-prompt-title" },
+ { id: "security-level-restart-prompt-body" },
+ { id: "security-level-restart-prompt-button-restart" },
+ { id: "security-level-restart-prompt-button-ignore" },
+ ]);
+ const buttonFlags =
+ Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
+ Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1;
+
+ const propBag = await Services.prompt.asyncConfirmEx(
+ lazy.BrowserWindowTracker.getTopWindow()?.browsingContext ?? null,
+ Services.prompt.MODAL_TYPE_INTERNAL_WINDOW,
+ titleText,
+ bodyText,
+ buttonFlags,
+ primaryButtonText,
+ secondaryButtonText,
+ null,
+ null,
+ null,
+ {}
+ );
+
+ if (propBag.get("buttonNumClicked") === 0) {
+ Services.startup.quit(
+ Services.startup.eAttemptQuit | Services.startup.eRestart
+ );
+ }
+ },
+};
=====================================
browser/modules/moz.build
=====================================
@@ -136,6 +136,7 @@ EXTRA_JS_MODULES += [
"PopupBlockerObserver.sys.mjs",
"ProcessHangMonitor.sys.mjs",
"Sanitizer.sys.mjs",
+ "SecurityLevelRestartNotification.sys.mjs",
"SelectionChangedMenulist.sys.mjs",
"SharingUtils.sys.mjs",
"SiteDataManager.sys.mjs",
=====================================
toolkit/components/extensions/ExtensionParent.sys.mjs
=====================================
@@ -2348,6 +2348,7 @@ async function torSendExtensionMessage(extensionId, message) {
const result = await ProxyMessenger.conduit.castRuntimeMessage("messenger", {
extensionId,
holder: new StructuredCloneHolder("torSendExtensionMessage", null, message),
+ query: true,
firstResponse: true,
sender: {
id: extensionId,
=====================================
toolkit/components/securitylevel/SecurityLevel.sys.mjs
=====================================
@@ -16,6 +16,7 @@ const BrowserTopics = Object.freeze({
// The Security Settings prefs in question.
const kSliderPref = "browser.security_level.security_slider";
const kCustomPref = "browser.security_level.security_custom";
+const kNoScriptInitedPref = "browser.security_level.noscript_inited";
// __getPrefValue(prefName)__
// Returns the current value of a preference, regardless of its type.
@@ -32,11 +33,11 @@ var getPrefValue = function (prefName) {
}
};
-// __bindPref(prefName, prefHandler, init)__
+// __bindPref(prefName, prefHandler)__
// 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) {
+// Returns the observer that was added.
+var bindPref = function (prefName, prefHandler) {
let update = () => {
prefHandler(getPrefValue(prefName));
},
@@ -48,21 +49,9 @@ var bindPref = function (prefName, prefHandler, init = false) {
},
};
Services.prefs.addObserver(prefName, observer);
- if (init) {
- update();
- }
- return () => {
- Services.prefs.removeObserver(prefName, observer);
- };
+ return 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 } = lazy.ExtensionParent;
if (torWaitForExtensionMessage) {
@@ -74,7 +63,7 @@ async function waitForExtensionMessage(extensionId, checker = () => {}) {
async function sendExtensionMessage(extensionId, message) {
const { torSendExtensionMessage } = lazy.ExtensionParent;
if (torSendExtensionMessage) {
- return torSendExtensionMessage(extensionId, message);
+ return await torSendExtensionMessage(extensionId, message);
}
return undefined;
}
@@ -187,14 +176,8 @@ var initializeNoScriptControl = () => {
// `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));
+ let sendNoScriptSettings = async settings =>
+ await sendExtensionMessage(noscriptID, settings);
// __securitySliderToSafetyLevel(sliderState)__.
// Converts the "browser.security_level.security_slider" pref value
@@ -204,36 +187,46 @@ var initializeNoScriptControl = () => {
// Wait for the first message from NoScript to arrive, and then
// bind the security_slider pref to the NoScript settings.
- let messageListener = a => {
+ let messageListener = async a => {
try {
logger.debug("Message received from NoScript:", a);
- let noscriptPersist = Services.prefs.getBoolPref(
- "browser.security_level.noscript_persist",
- false
- );
+ const persistPref = "browser.security_level.noscript_persist";
+ let noscriptPersist = Services.prefs.getBoolPref(persistPref, false);
let noscriptInited = Services.prefs.getBoolPref(
- "browser.security_level.noscript_inited",
+ kNoScriptInitedPref,
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
+ // Set the noscript safety level once at startup.
+ // If a user has set noscriptPersist, then we only send this if the
+ // security level was changed in a previous session.
+ // NOTE: We do not re-send this when the security_slider preference
+ // changes mid-session because this should always require a restart.
+ if (noscriptPersist && noscriptInited) {
+ logger.warn(
+ `Not initialising NoScript since the user has set ${persistPref}`
);
+ return;
}
+ // Read the security level, even if the user has the "custom"
+ // preference.
+ const securityIndex = Services.prefs.getIntPref(kSliderPref, 0);
+ const safetyLevel = securitySliderToSafetyLevel(securityIndex);
+ // May throw if NoScript fails to apply the settings:
+ const noscriptResult = await sendNoScriptSettings(
+ noscriptSettings(safetyLevel)
+ );
+ // Mark the NoScript extension as initialised so we do not reset it
+ // at the next startup for noscript_persist users.
+ Services.prefs.setBoolPref(kNoScriptInitedPref, true);
+ logger.info("NoScript successfully initialised.");
+ // In the future NoScript may tell us more about how it applied our
+ // settings, e.g. if user is overriding per-site permissions.
+ // Up to NoScript 12.6 noscriptResult is undefined.
+ logger.debug("NoScript response:", noscriptResult);
} catch (e) {
- logger.exception(e);
+ logger.error("Could not apply NoScript settings", e);
+ // Treat as a custom security level for the rest of the session.
+ Services.prefs.setBoolPref(kCustomPref, true);
}
};
waitForExtensionMessage(noscriptID, a => a.__meta.name === "started").then(
@@ -242,6 +235,8 @@ var initializeNoScriptControl = () => {
logger.info("Listening for messages from NoScript.");
} catch (e) {
logger.exception(e);
+ // Treat as a custom security level for the rest of the session.
+ Services.prefs.setBoolPref(kCustomPref, true);
}
};
@@ -271,16 +266,60 @@ const kSecuritySettings = {
// ### Prefs
+/**
+ * Amend the security level index to a standard value.
+ *
+ * @param {integer} index - The input index value.
+ * @returns {integer} - A standard index value.
+ */
+function fixupIndex(index) {
+ if (!Number.isInteger(index) || index < 1 || index > 4) {
+ // Unexpected value out of range, go to the "safest" level as a fallback.
+ return 1;
+ }
+ if (index === 3) {
+ // Migrate from old medium-low (3) to new medium (2).
+ return 2;
+ }
+ return index;
+}
+
+/**
+ * A list of preference observers that should be disabled whilst we write our
+ * preference values.
+ *
+ * @type {{ prefName: string, observer: object }[]}
+ */
+const prefObservers = [];
+
// __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]
- )
- );
+ settingIndex = fixupIndex(settingIndex);
+ // Don't want to trigger our internal observers when setting ourselves.
+ for (const { prefName, observer } of prefObservers) {
+ Services.prefs.removeObserver(prefName, observer);
+ }
+ try {
+ // Make sure noscript is re-initialised at the next startup when the
+ // security level changes.
+ Services.prefs.setBoolPref(kNoScriptInitedPref, false);
+ Services.prefs.setIntPref(kSliderPref, settingIndex);
+ // NOTE: We do not clear kCustomPref. Instead, we rely on the preference
+ // being cleared on the next startup.
+ Object.keys(kSecuritySettings).forEach(prefName =>
+ Services.prefs.setBoolPref(
+ prefName,
+ kSecuritySettings[prefName][settingIndex]
+ )
+ );
+ } finally {
+ // Re-add the observers.
+ for (const { prefName, observer } of prefObservers) {
+ Services.prefs.addObserver(prefName, observer);
+ }
+ }
};
// __read_setting_from_prefs()__.
@@ -309,24 +348,6 @@ var read_setting_from_prefs = function (prefNames) {
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;
@@ -342,36 +363,82 @@ var initializeSecurityPrefs = function () {
}
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) {
+
+ const wasCustom = Services.prefs.getBoolPref(kCustomPref, false);
+ // For new profiles with no user preference, the security level should be "4"
+ // and it should not be custom.
+ let desiredIndex = Services.prefs.getIntPref(kSliderPref, 4);
+ desiredIndex = fixupIndex(desiredIndex);
+ // Make sure the user has a set preference user value.
+ Services.prefs.setIntPref(kSliderPref, desiredIndex);
+ Services.prefs.setBoolPref(kCustomPref, wasCustom);
+
+ // Make sure that the preference values at application startup match the
+ // expected values for the desired security level. See tor-browser#43783.
+
+ // NOTE: We assume that the controlled preference values that are read prior
+ // to profile-after-change do not change in value before this method is
+ // called. I.e. we expect the current preference values to match the
+ // preference values that were used during the application initialisation.
+ const effectiveIndex = read_setting_from_prefs();
+
+ if (wasCustom && effectiveIndex !== null) {
+ logger.info(`Custom startup values match index ${effectiveIndex}`);
+ // Do not consider custom any more.
+ // NOTE: This level needs to be set before it is read elsewhere. In
+ // particular, for the NoScript addon.
Services.prefs.setBoolPref(kCustomPref, false);
- write_setting_to_prefs(prefIndex);
+ Services.prefs.setIntPref(kSliderPref, effectiveIndex);
+ } else if (!wasCustom && effectiveIndex !== desiredIndex) {
+ // NOTE: We assume all our controlled preferences require a restart.
+ // In practice, only a subset of these preferences may actually require a
+ // restart, so we could switch their values. But we treat them all the same
+ // for simplicity, consistency and stability in case mozilla changes the
+ // restart requirements.
+ logger.info(`Startup values do not match for index ${desiredIndex}`);
+ SecurityLevelPrefs.requireRestart();
+ }
+
+ // Start listening for external changes to the controlled preferences.
+ prefObservers.push({
+ prefName: kCustomPref,
+ observer: bindPref(kCustomPref, custom => {
+ // Custom flag was removed mid-session. Requires a restart to apply the
+ // security level.
+ if (custom === false) {
+ logger.info("Custom flag was cleared externally");
+ SecurityLevelPrefs.requireRestart();
+ }
+ }),
});
- // 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);
- }
+ prefObservers.push({
+ prefName: kSliderPref,
+ observer: bindPref(kSliderPref, () => {
+ // Security level was changed mid-session. Requires a restart to apply.
+ logger.info("Security level was changed externally");
+ SecurityLevelPrefs.requireRestart();
+ }),
});
- // 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);
+
+ for (const prefName of Object.keys(kSecuritySettings)) {
+ prefObservers.push({
+ prefName,
+ observer: bindPref(prefName, () => {
+ logger.warn(
+ `The controlled preference ${prefName} was changed externally.` +
+ " Treating as a custom security level."
+ );
+ // Something outside of this module changed the preference value for a
+ // preference we control.
+ // Always treat as a custom security level for the rest of this session,
+ // even if the new preference values match a pre-set security level. We
+ // do this because some controlled preferences require a restart to be
+ // properly applied. See tor-browser#43783.
+ // In the case where it does match a pre-set security level, the custom
+ // flag will be cleared at the next startup.
+ Services.prefs.setBoolPref(kCustomPref, true);
+ }),
+ });
}
logger.info("security-prefs.js initialization complete");
@@ -425,8 +492,9 @@ export class SecurityLevel {
init() {
migratePreferences();
- initializeNoScriptControl();
+ // Fixup our preferences before we pass on the security level to NoScript.
initializeSecurityPrefs();
+ initializeNoScriptControl();
}
observe(aSubject, aTopic) {
@@ -436,10 +504,19 @@ export class SecurityLevel {
}
}
+/**
+ * @typedef {object} SecurityLevelRestartNotificationHandler
+ *
+ * An object that can serve the user a restart notification.
+ *
+ * @property {Function} tryRestartBrowser - The method that should be called to
+ * ask the user to restart the browser.
+ */
+
/*
Security Level Prefs
- Getters and Setters for relevant torbutton prefs
+ Getters and Setters for relevant security level prefs
*/
export const SecurityLevelPrefs = {
SecurityLevels: Object.freeze({
@@ -450,6 +527,14 @@ export const SecurityLevelPrefs = {
security_slider_pref: "browser.security_level.security_slider",
security_custom_pref: "browser.security_level.security_custom",
+ /**
+ * The current security level preference.
+ *
+ * This ignores any custom settings the user may have changed, and just
+ * gives the underlying security level.
+ *
+ * @type {?string}
+ */
get securityLevel() {
// Set the default return value to 0, which won't match anything in
// SecurityLevels.
@@ -459,18 +544,146 @@ export const SecurityLevelPrefs = {
)?.[0];
},
- set securityLevel(level) {
- const val = this.SecurityLevels[level];
- if (val !== undefined) {
- Services.prefs.setIntPref(this.security_slider_pref, val);
- }
+ /**
+ * Set the desired security level just before a restart.
+ *
+ * The caller must restart the browser after calling this method.
+ *
+ * @param {string} level - The name of the new security level to set.
+ */
+ setSecurityLevelBeforeRestart(level) {
+ write_setting_to_prefs(this.SecurityLevels[level]);
},
+ /**
+ * Whether the user has any custom setting values that do not match a pre-set
+ * security level.
+ *
+ * @type {boolean}
+ */
get securityCustom() {
return Services.prefs.getBoolPref(this.security_custom_pref);
},
- set securityCustom(val) {
- Services.prefs.setBoolPref(this.security_custom_pref, val);
+ /**
+ * A summary of the current security level.
+ *
+ * If the user has some custom settings, this returns "custom". Otherwise
+ * returns the name of the security level.
+ *
+ * @type {string}
+ */
+ get securityLevelSummary() {
+ if (this.securityCustom) {
+ return "custom";
+ }
+ return this.securityLevel ?? "custom";
+ },
+
+ /**
+ * Whether the browser should be restarted to apply the security level.
+ *
+ * @type {boolean}
+ */
+ _needRestart: false,
+
+ /**
+ * The external handler that can show a notification to the user, if any.
+ *
+ * @type {?SecurityLevelRestartNotificationHandler}
+ */
+ _restartNotificationHandler: null,
+
+ /**
+ * Set the external handler for showing notifications to the user.
+ *
+ * This should only be called once per session once the handler is ready to
+ * show a notification, which may occur immediately during this call.
+ *
+ * @param {SecurityLevelRestartNotificationHandler} handler - The new handler
+ * to use.
+ */
+ setRestartNotificationHandler(handler) {
+ logger.info("Restart notification handler is set");
+ this._restartNotificationHandler = handler;
+ if (this._needRestart) {
+ // Show now using the new handler.
+ this._tryShowRestartNotification();
+ }
+ },
+
+ /**
+ * A promise for any ongoing notification prompt task.
+ *
+ * @type {Promise}
+ */
+ _restartNotificationPromise: null,
+
+ /**
+ * Try show a notification to the user.
+ *
+ * If no notification handler has been attached yet, this will do nothing.
+ */
+ async _tryShowRestartNotification() {
+ if (!this._restartNotificationHandler) {
+ logger.info("Missing a restart notification handler");
+ // This may be added later in the session.
+ return;
+ }
+
+ const prevPromise = this._restartNotificationPromise;
+ let resolve;
+ ({ promise: this._restartNotificationPromise, resolve } =
+ Promise.withResolvers());
+ await prevPromise;
+
+ try {
+ await this._restartNotificationHandler?.tryRestartBrowser();
+ } finally {
+ // Allow the notification to be shown again.
+ resolve();
+ }
+ },
+
+ /**
+ * Mark the session as requiring a restart to apply a change in security
+ * level.
+ *
+ * The security level will immediately be switched to "custom", and the user
+ * may be shown a notification to restart the browser.
+ */
+ requireRestart() {
+ logger.warn("The browser needs to be restarted to set the security level");
+ // Treat as a custom security level for the rest of the session.
+ // At the next startup, the custom flag may be cleared if the settings are
+ // as expected.
+ Services.prefs.setBoolPref(kCustomPref, true);
+ this._needRestart = true;
+
+ // NOTE: We need to change the controlled security level preferences in
+ // response to the desired change in security level. We could either:
+ // 1. Only change the controlled preferences after the user confirms a
+ // restart. Or
+ // 2. Change the controlled preferences and then try and ask the user to
+ // restart.
+ //
+ // We choose the latter:
+ // 1. To allow users to manually restart.
+ // 2. If the user ignores or misses the notification, they will at least be
+ // in the correct state when the browser starts again. Although they will
+ // be in a custom/undefined state in the mean time.
+ // 3. Currently Android relies on triggering the change in security level
+ // by setting the browser.security_level.security_slider preference
+ // value. So it currently uses this path. So we need to set the values
+ // now, before it preforms a restart.
+ // TODO: Have android use the `setSecurityLevelBeforeRestart` method
+ // instead of setting the security_slider preference value directly, so that
+ // it knows exactly when it can restart the browser. tor-browser#43820
+ write_setting_to_prefs(Services.prefs.getIntPref(kSliderPref, 0));
+ // NOTE: Even though we have written the preferences, the session should
+ // still be marked as "custom" because:
+ // 1. Some preferences require a browser restart to be applied.
+ // 2. NoScript has not been updated with the new settings.
+ this._tryShowRestartNotification();
},
}; /* Security Level Prefs */
=====================================
toolkit/locales/en-US/toolkit/global/base-browser.ftl
=====================================
@@ -123,10 +123,6 @@ security-level-toolbar-button-custom =
# Uses sentence case in English (US).
security-level-panel-heading = Security level
-
-security-level-panel-level-standard = Standard
-security-level-panel-level-safer = Safer
-security-level-panel-level-safest = Safest
security-level-panel-learn-more-link = Learn more
# Button to open security level settings.
security-level-panel-open-settings-button = Settings…
@@ -136,6 +132,19 @@ security-level-panel-open-settings-button = Settings…
security-level-preferences-heading = Security Level
security-level-preferences-overview = Disable certain web features that can be used to attack your security and anonymity.
security-level-preferences-learn-more-link = Learn more
+# Text for a badge that labels the currently active security level.
+# The text in between '<span>' and '</span>' should contain some kind of bracket, like '(' and ')', or other punctuation used in your language to separate out text from its surrounding context. This will not be visible, but will be use for screen readers to make it clear that the text is not part of the same sentence. For example, in US English this would be read as "(Current level)", and the full line of text would be read as "Safest (Current level)".
+security-level-preferences-current-badge = <span>(</span>Current level<span>)</span>
+security-level-preferences-change-button = Change…
+
+## Security level settings dialog.
+
+security-level-dialog-window =
+ .title = Change security level
+
+# '-brand-short-name' is the localized browser name, like "Tor Browser".
+security-level-dialog-restart-description = You will need to restart { -brand-short-name } to apply any changes. This will close all windows and tabs.
+
security-level-preferences-level-standard =
.label = Standard
security-level-preferences-level-safer =
@@ -143,6 +152,16 @@ security-level-preferences-level-safer =
security-level-preferences-level-safest =
.label = Safest
+security-level-dialog-save-restart =
+ .label = Save and restart
+
+## Security level names shown in the security panel and settings.
+
+security-level-panel-level-standard = Standard
+security-level-panel-level-safer = Safer
+security-level-panel-level-safest = Safest
+security-level-panel-level-custom = Custom
+
## Security level summaries shown in security panel and settings.
security-level-summary-standard = All browser and website features are enabled.
@@ -161,13 +180,13 @@ security-level-preferences-bullet-limit-font-and-symbols-and-images = Some fonts
## Custom security level.
## Some custom preferences configuration has placed the user outside one of the standard three levels.
-# Shown in the security level panel as an orange badge next to the expected level.
-security-level-panel-custom-badge = Custom
-# Shown in the security level settings in a warning box.
-security-level-preferences-custom-heading = Custom security level configured
# Description of custom state and recommended action.
# Shown in the security level panel and settings.
security-level-summary-custom = 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.
-# Button to undo custom changes to the security level and place the user in one of the standard security levels.
-# Shown in the security level panel and settings.
-security-level-restore-defaults-button = Restore defaults
+
+## Security level restart prompt.
+
+security-level-restart-prompt-title = Your security level settings require a restart
+security-level-restart-prompt-body = You must restart { -brand-short-name } for your security level settings to be applied. This will close all your windows and tabs.
+security-level-restart-prompt-button-restart = Restart
+security-level-restart-prompt-button-ignore = Ignore
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/aec824…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/aec824…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-140.0a1-15.0-1] 9 commits: fixup! BB 40925: Implemented the Security Level component
by ma1 (@ma1) 10 Jun '25
by ma1 (@ma1) 10 Jun '25
10 Jun '25
ma1 pushed to branch tor-browser-140.0a1-15.0-1 at The Tor Project / Applications / Tor Browser
Commits:
7ba7899b by Henry Wilkes at 2025-06-10T15:27:16+01:00
fixup! BB 40925: Implemented the Security Level component
TB 43783: Prompt user for a restart if their security level preferences
are not aligned at startup or mid-session.
Also handle failures to apply NoScript settings.
- - - - -
8a3f4ef0 by Henry Wilkes at 2025-06-10T15:27:17+01:00
fixup! BB 40069: Add helpers for message passing with extensions
TB 43783: Allow the browser to wait for the NoScript settings to be
applied.
- - - - -
f0744c43 by Henry Wilkes at 2025-06-10T15:27:18+01:00
fixup! Base Browser strings
TB 43783: Add security level prompt strings.
- - - - -
48604a71 by Henry Wilkes at 2025-06-10T15:27:19+01:00
fixup! TB 40026 [android]: Implement Security Level settings on Android.
TB 43783: Expose SecurityLevelPrefs.setSecurityLevelAndRestart to
android integration.
- - - - -
00b151ee by Henry Wilkes at 2025-06-10T15:27:20+01:00
fixup! Base Browser strings
TB 43782: Add strings for new security level UX flow.
- - - - -
b521b013 by Henry Wilkes at 2025-06-10T15:27:21+01:00
fixup! BB 40925: Implemented the Security Level component
TB 43782: Update security level UI for new UX flow.
In addition, we drop the distinction between the security levels in the
UI when the user has a custom security level.
I.e. we always show shield as unfilled but with a yellow dot in the
toolbar, and we just call it "Custom" rather than "Standard Custom",
etc.
- - - - -
116c3b51 by clairehurst at 2025-06-10T15:27:22+01:00
fixup! TB 40026 [android]: Implement Security Level settings on Android.
TB 43786: Add new UX flow for changing security level (Android)
- - - - -
fc2602a2 by clairehurst at 2025-06-10T15:27:23+01:00
fixup! [android] Implement Android-native Connection Assist UI
TB 43786: Add new UX flow for changing security level (Android)
- - - - -
e1670e7a by clairehurst at 2025-06-10T15:27:24+01:00
fixup! [android] TBA strings
TB 43786: Add new UX flow for changing security level (Android)
- - - - -
37 changed files:
- browser/components/BrowserGlue.sys.mjs
- + browser/components/securitylevel/SecurityLevelUIUtils.sys.mjs
- browser/components/securitylevel/content/securityLevel.js
- browser/components/securitylevel/content/securityLevelButton.css
- + browser/components/securitylevel/content/securityLevelDialog.js
- + browser/components/securitylevel/content/securityLevelDialog.xhtml
- browser/components/securitylevel/content/securityLevelPanel.css
- browser/components/securitylevel/content/securityLevelPanel.inc.xhtml
- browser/components/securitylevel/content/securityLevelPreferences.css
- browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml
- browser/components/securitylevel/jar.mn
- browser/components/securitylevel/moz.build
- + browser/modules/SecurityLevelRestartNotification.sys.mjs
- browser/modules/moz.build
- mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngine.kt
- mobile/android/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/Settings.kt
- mobile/android/android-components/components/feature/search/src/main/java/mozilla/components/feature/search/SearchUseCases.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/Core.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt
- − mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/TorSecurityLevelFragment.kt
- − mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/SecurityLevel.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistFragment.kt
- + mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorSecurityLevel.kt
- + mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorSecurityLevelFragment.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
- + mobile/android/fenix/app/src/main/res/layout/fragment_tor_security_level_preferences.xml
- mobile/android/fenix/app/src/main/res/navigation/nav_graph.xml
- mobile/android/fenix/app/src/main/res/values/preference_keys.xml
- mobile/android/fenix/app/src/main/res/values/torbrowser_strings.xml
- mobile/android/fenix/app/src/main/res/xml/preferences.xml
- − mobile/android/fenix/app/src/main/res/xml/tor_security_level_preferences.xml
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorAndroidIntegration.java
- toolkit/components/extensions/ExtensionParent.sys.mjs
- toolkit/components/securitylevel/SecurityLevel.sys.mjs
- toolkit/locales/en-US/toolkit/global/base-browser.ftl
- toolkit/modules/TorAndroidIntegration.sys.mjs
The diff was not included because it is too large.
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/f8d973…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/f8d973…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-140.0a1-15.0-1] 2 commits: fixup! TB 40933: Add tor-launcher functionality
by henry (@henry) 10 Jun '25
by henry (@henry) 10 Jun '25
10 Jun '25
henry pushed to branch tor-browser-140.0a1-15.0-1 at The Tor Project / Applications / Tor Browser
Commits:
1036d63b by Henry Wilkes at 2025-06-10T13:54:43+01:00
fixup! TB 40933: Add tor-launcher functionality
TB 43636: Distinguish between bootstrap errors and TorProvider
initialisation errors.
- - - - -
f8d973ab by Henry Wilkes at 2025-06-10T13:54:45+01:00
fixup! TB 40597: Implement TorSettings module
TB 43636: Move to the Start stage when a bootstrap fails due to a
TorProvider initialisation error.
- - - - -
4 changed files:
- toolkit/components/tor-launcher/TorBootstrapRequest.sys.mjs
- toolkit/components/tor-launcher/TorProvider.sys.mjs
- toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs
- toolkit/modules/TorConnect.sys.mjs
Changes:
=====================================
toolkit/components/tor-launcher/TorBootstrapRequest.sys.mjs
=====================================
@@ -54,9 +54,7 @@ export class TorBootstrapRequest {
}
case lazy.TorProviderTopics.BootstrapError: {
log.info("TorBootstrapRequest: observerd TorBootstrapError", obj);
- const error = new Error(obj.summary);
- Object.assign(error, obj);
- this.#stop(error);
+ this.#stop(obj);
break;
}
}
=====================================
toolkit/components/tor-launcher/TorProvider.sys.mjs
=====================================
@@ -6,7 +6,10 @@ import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs";
import { TorLauncherUtil } from "resource://gre/modules/TorLauncherUtil.sys.mjs";
import { TorParsers } from "resource://gre/modules/TorParsers.sys.mjs";
-import { TorProviderTopics } from "resource://gre/modules/TorProviderBuilder.sys.mjs";
+import {
+ TorBootstrapError,
+ TorProviderTopics,
+} from "resource://gre/modules/TorProviderBuilder.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
@@ -996,12 +999,11 @@ export class TorProvider {
// anymore, since the first error eligible for notification will as a
// matter of fact cancel the bootstrap.
Services.obs.notifyObservers(
- {
+ new TorBootstrapError({
phase: statusObj.TAG,
reason: statusObj.REASON,
summary: statusObj.SUMMARY,
- warning: statusObj.WARNING,
- },
+ }),
TorProviderTopics.BootstrapError
);
}
=====================================
toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs
=====================================
@@ -18,6 +18,41 @@ export const TorProviderTopics = Object.freeze({
CircuitCredentialsMatched: "TorCircuitCredentialsMatched",
});
+/**
+ * Wrapper error class for errors raised during TorProvider.init.
+ */
+export class TorProviderInitError extends Error {
+ /**
+ * Create a new instance.
+ *
+ * @param {any} error - The raised error that we want to wrap.
+ */
+ constructor(error) {
+ super(error?.message, { cause: error });
+ this.name = "TorProviderInitError";
+ }
+}
+
+/**
+ * Bootstrap errors raised by the TorProvider.
+ */
+export class TorBootstrapError extends Error {
+ /**
+ * Create a new instance.
+ *
+ * @param {object} details - Details about the error.
+ * @param {string} details.summary - A summary of the error.
+ * @param {string} details.phase - The bootstrap phase when the error occured.
+ * @param {string} details.reason - The reason for the bootsrap failure.
+ */
+ constructor(details) {
+ super(details.summary);
+ this.name = "TorBootstrapError";
+ this.phase = details.phase;
+ this.reason = details.reason;
+ }
+}
+
export const TorProviders = Object.freeze({
none: 0,
tor: 1,
@@ -178,7 +213,13 @@ export class TorProviderBuilder {
(await oldProvider)?.uninit();
} catch {}
const provider = new lazy.TorProvider();
- await provider.init();
+ try {
+ await provider.init();
+ } catch (error) {
+ // Wrap in an error type for callers to know whether the error comes from
+ // initialisation or something else.
+ throw new TorProviderInitError(error);
+ }
return provider;
}
=====================================
toolkit/modules/TorConnect.sys.mjs
=====================================
@@ -10,6 +10,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
MoatRPC: "resource://gre/modules/Moat.sys.mjs",
TorBootstrapRequest: "resource://gre/modules/TorBootstrapRequest.sys.mjs",
TorProviderTopics: "resource://gre/modules/TorProviderBuilder.sys.mjs",
+ TorBootstrapError: "resource://gre/modules/TorProviderBuilder.sys.mjs",
+ TorProviderInitError: "resource://gre/modules/TorProviderBuilder.sys.mjs",
TorLauncherUtil: "resource://gre/modules/TorLauncherUtil.sys.mjs",
TorSettings: "resource://gre/modules/TorSettings.sys.mjs",
TorSettingsTopics: "resource://gre/modules/TorSettings.sys.mjs",
@@ -223,9 +225,11 @@ class BootstrapAttempt {
_timeout: 0,
bootstrap() {
this._timeout = setTimeout(() => {
- const err = new Error("Censorship simulation");
- err.phase = "conn";
- err.reason = "noroute";
+ const err = new lazy.TorBootstrapError({
+ summary: "Censorship simulation",
+ phase: "conn",
+ reason: "noroute",
+ });
this.onbootstraperror(err);
}, options.simulateDelay || 0);
},
@@ -258,9 +262,7 @@ class BootstrapAttempt {
return;
}
- this.#resolveRun({
- error: new TorConnectError(TorConnectError.BootstrapError, error),
- });
+ this.#resolveRun({ error });
};
this.#bootstrap.bootstrap();
@@ -595,12 +597,9 @@ class AutoBootstrapAttempt {
// bootstrapAttempt.
result = await this.#bootstrapAttempt.run(progressCallback, options);
} catch (error) {
- // Only re-try with the next settings *if* we have a BootstrapError.
+ // Only re-try with the next settings *if* we have a TorBootstrapError.
// Other errors will end this auto-bootstrap attempt entirely.
- if (
- error instanceof TorConnectError &&
- error.code === TorConnectError.BootstrapError
- ) {
+ if (error instanceof lazy.TorBootstrapError) {
lazy.logger.info("TorConnect setting failed", bridges, error);
// Try with the next settings.
// NOTE: We do not restore the user settings in between these runs.
@@ -1200,7 +1199,9 @@ export const TorConnect = {
// Currently it simulates the old behaviour for about:torconnect.
lazy.logger.debug("Signalling error", error);
- if (!(error instanceof TorConnectError)) {
+ if (error instanceof lazy.TorBootstrapError) {
+ error = new TorConnectError(TorConnectError.BootstrapError, error);
+ } else if (!(error instanceof TorConnectError)) {
error = new TorConnectError(TorConnectError.ExternalError, error);
}
this._errorDetails = error;
@@ -1434,6 +1435,19 @@ export const TorConnect = {
lazy.logger.info("Bootstrap attempt error", error);
this._tryAgain = true;
+ if (error instanceof lazy.TorProviderInitError) {
+ // Treat like TorProviderTopics.ProcessExited. We expect a user
+ // notification when this happens.
+ // Treat a failure as a possibly broken configuration.
+ // So, prevent quickstart at the next start.
+ Services.prefs.setBoolPref(TorConnectPrefs.prompt_at_startup, true);
+ lazy.logger.info(
+ "Starting again since the tor provider failed to initialise"
+ );
+ this._setStage(TorConnectStage.Start);
+ return;
+ }
+
if (
(beginStage === TorConnectStage.Start ||
beginStage === TorConnectStage.Offline) &&
@@ -1462,10 +1476,7 @@ export const TorConnect = {
switch (beginStage) {
case TorConnectStage.Start:
case TorConnectStage.Offline:
- if (
- error instanceof TorConnectError &&
- error.code === TorConnectError.BootstrapError
- ) {
+ if (error instanceof lazy.TorBootstrapError) {
errorStage = TorConnectStage.ChooseRegion;
}
// Else, some other unexpected error type. Skip straight to the
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/469928…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/469928…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/mullvad-browser][mullvad-browser-128.11.0esr-14.5-1] 5 commits: fixup! BB 40925: Implemented the Security Level component
by ma1 (@ma1) 10 Jun '25
by ma1 (@ma1) 10 Jun '25
10 Jun '25
ma1 pushed to branch mullvad-browser-128.11.0esr-14.5-1 at The Tor Project / Applications / Mullvad Browser
Commits:
84c77940 by Henry Wilkes at 2025-06-10T12:38:13+02:00
fixup! BB 40925: Implemented the Security Level component
TB 43783: Prompt user for a restart if their security level preferences
are not aligned at startup or mid-session.
Also handle failures to apply NoScript settings.
- - - - -
2fc667c6 by Henry Wilkes at 2025-06-10T12:38:16+02:00
fixup! BB 40069: Add helpers for message passing with extensions
TB 43783: Allow the browser to wait for the NoScript settings to be
applied.
- - - - -
c9771be6 by Henry Wilkes at 2025-06-10T12:38:19+02:00
fixup! Base Browser strings
TB 43783: Add security level prompt strings.
- - - - -
9adaa2c2 by Henry Wilkes at 2025-06-10T12:38:22+02:00
fixup! Base Browser strings
TB 43782: Add strings for new security level UX flow.
- - - - -
c9bf0837 by Henry Wilkes at 2025-06-10T12:38:24+02:00
fixup! BB 40925: Implemented the Security Level component
TB 43782: Update security level UI for new UX flow.
In addition, we drop the distinction between the security levels in the
UI when the user has a custom security level.
I.e. we always show shield as unfilled but with a yellow dot in the
toolbar, and we just call it "Custom" rather than "Standard Custom",
etc.
- - - - -
17 changed files:
- browser/components/BrowserGlue.sys.mjs
- + browser/components/securitylevel/SecurityLevelUIUtils.sys.mjs
- browser/components/securitylevel/content/securityLevel.js
- browser/components/securitylevel/content/securityLevelButton.css
- + browser/components/securitylevel/content/securityLevelDialog.js
- + browser/components/securitylevel/content/securityLevelDialog.xhtml
- browser/components/securitylevel/content/securityLevelPanel.css
- browser/components/securitylevel/content/securityLevelPanel.inc.xhtml
- browser/components/securitylevel/content/securityLevelPreferences.css
- browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml
- browser/components/securitylevel/jar.mn
- browser/components/securitylevel/moz.build
- + browser/modules/SecurityLevelRestartNotification.sys.mjs
- browser/modules/moz.build
- toolkit/components/extensions/ExtensionParent.sys.mjs
- toolkit/components/securitylevel/SecurityLevel.sys.mjs
- toolkit/locales/en-US/toolkit/global/base-browser.ftl
Changes:
=====================================
browser/components/BrowserGlue.sys.mjs
=====================================
@@ -82,6 +82,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
ScreenshotsUtils: "resource:///modules/ScreenshotsUtils.sys.mjs",
SearchSERPCategorization: "resource:///modules/SearchSERPTelemetry.sys.mjs",
SearchSERPTelemetry: "resource:///modules/SearchSERPTelemetry.sys.mjs",
+ SecurityLevelRestartNotification:
+ "resource:///modules/SecurityLevelRestartNotification.sys.mjs",
SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
ShellService: "resource:///modules/ShellService.sys.mjs",
@@ -1938,6 +1940,8 @@ BrowserGlue.prototype = {
lazy.DoHController.init();
+ lazy.SecurityLevelRestartNotification.ready();
+
ClipboardPrivacy.startup();
this._firstWindowTelemetry(aWindow);
=====================================
browser/components/securitylevel/SecurityLevelUIUtils.sys.mjs
=====================================
@@ -0,0 +1,73 @@
+/**
+ * Common methods for the desktop security level components.
+ */
+export const SecurityLevelUIUtils = {
+ /**
+ * Create an element that gives a description of the security level. To be
+ * used in the settings.
+ *
+ * @param {string} level - The security level to describe.
+ * @param {Document} doc - The document where the element will be inserted.
+ *
+ * @returns {Element} - The newly created element.
+ */
+ createDescriptionElement(level, doc) {
+ const el = doc.createElement("div");
+ el.classList.add("security-level-description");
+
+ let l10nIdSummary;
+ let bullets;
+ switch (level) {
+ case "standard":
+ l10nIdSummary = "security-level-summary-standard";
+ break;
+ case "safer":
+ l10nIdSummary = "security-level-summary-safer";
+ bullets = [
+ "security-level-preferences-bullet-https-only-javascript",
+ "security-level-preferences-bullet-limit-font-and-symbols",
+ "security-level-preferences-bullet-limit-media",
+ ];
+ break;
+ case "safest":
+ l10nIdSummary = "security-level-summary-safest";
+ bullets = [
+ "security-level-preferences-bullet-disabled-javascript",
+ "security-level-preferences-bullet-limit-font-and-symbols-and-images",
+ "security-level-preferences-bullet-limit-media",
+ ];
+ break;
+ case "custom":
+ l10nIdSummary = "security-level-summary-custom";
+ break;
+ default:
+ throw Error(`Unhandled level: ${level}`);
+ }
+
+ const summaryEl = doc.createElement("div");
+ summaryEl.classList.add("security-level-summary");
+ doc.l10n.setAttributes(summaryEl, l10nIdSummary);
+
+ el.append(summaryEl);
+
+ if (!bullets) {
+ return el;
+ }
+
+ const listEl = doc.createElement("ul");
+ listEl.classList.add("security-level-description-extra");
+ // Add a mozilla styling class as well:
+ listEl.classList.add("privacy-extra-information");
+ for (const l10nId of bullets) {
+ const bulletEl = doc.createElement("li");
+ bulletEl.classList.add("security-level-description-bullet");
+
+ doc.l10n.setAttributes(bulletEl, l10nId);
+
+ listEl.append(bulletEl);
+ }
+
+ el.append(listEl);
+ return el;
+ },
+};
=====================================
browser/components/securitylevel/content/securityLevel.js
=====================================
@@ -1,9 +1,10 @@
"use strict";
-/* global AppConstants, Services, openPreferences, XPCOMUtils */
+/* global AppConstants, Services, openPreferences, XPCOMUtils, gSubDialog */
ChromeUtils.defineESModuleGetters(this, {
SecurityLevelPrefs: "resource://gre/modules/SecurityLevel.sys.mjs",
+ SecurityLevelUIUtils: "resource:///modules/SecurityLevelUIUtils.sys.mjs",
});
/*
@@ -35,12 +36,8 @@ var SecurityLevelButton = {
_anchorButton: null,
_configUIFromPrefs() {
- const level = SecurityLevelPrefs.securityLevel;
- if (!level) {
- return;
- }
- const custom = SecurityLevelPrefs.securityCustom;
- this._button.setAttribute("level", custom ? `${level}_custom` : level);
+ const level = SecurityLevelPrefs.securityLevelSummary;
+ this._button.setAttribute("level", level);
let l10nIdLevel;
switch (level) {
@@ -53,15 +50,12 @@ var SecurityLevelButton = {
case "safest":
l10nIdLevel = "security-level-toolbar-button-safest";
break;
+ case "custom":
+ l10nIdLevel = "security-level-toolbar-button-custom";
+ break;
default:
throw Error(`Unhandled level: ${level}`);
}
- if (custom) {
- // Don't distinguish between the different levels when in the custom
- // state. We just want to emphasise that it is custom rather than any
- // specific level.
- l10nIdLevel = "security-level-toolbar-button-custom";
- }
document.l10n.setAttributes(this._button, l10nIdLevel);
},
@@ -164,12 +158,7 @@ var SecurityLevelPanel = {
panel: document.getElementById("securityLevel-panel"),
background: document.getElementById("securityLevel-background"),
levelName: document.getElementById("securityLevel-level"),
- customName: document.getElementById("securityLevel-custom"),
summary: document.getElementById("securityLevel-summary"),
- restoreDefaultsButton: document.getElementById(
- "securityLevel-restoreDefaults"
- ),
- settingsButton: document.getElementById("securityLevel-settings"),
};
const learnMoreEl = document.getElementById("securityLevel-learnMore");
@@ -177,12 +166,11 @@ var SecurityLevelPanel = {
this.hide();
});
- this._elements.restoreDefaultsButton.addEventListener("command", () => {
- this.restoreDefaults();
- });
- this._elements.settingsButton.addEventListener("command", () => {
- this.openSecuritySettings();
- });
+ document
+ .getElementById("securityLevel-settings")
+ .addEventListener("command", () => {
+ this.openSecuritySettings();
+ });
this._elements.panel.addEventListener("popupshown", () => {
// Bring focus into the panel by focusing the default button.
@@ -199,19 +187,7 @@ var SecurityLevelPanel = {
}
// get security prefs
- const level = SecurityLevelPrefs.securityLevel;
- const custom = SecurityLevelPrefs.securityCustom;
-
- // only visible when user is using custom settings
- this._elements.customName.hidden = !custom;
- this._elements.restoreDefaultsButton.hidden = !custom;
- if (custom) {
- this._elements.settingsButton.removeAttribute("default");
- this._elements.restoreDefaultsButton.setAttribute("default", "true");
- } else {
- this._elements.settingsButton.setAttribute("default", "true");
- this._elements.restoreDefaultsButton.removeAttribute("default");
- }
+ const level = SecurityLevelPrefs.securityLevelSummary;
// Descriptions change based on security level
this._elements.background.setAttribute("level", level);
@@ -230,12 +206,13 @@ var SecurityLevelPanel = {
l10nIdLevel = "security-level-panel-level-safest";
l10nIdSummary = "security-level-summary-safest";
break;
+ case "custom":
+ l10nIdLevel = "security-level-panel-level-custom";
+ l10nIdSummary = "security-level-summary-custom";
+ break;
default:
throw Error(`Unhandled level: ${level}`);
}
- if (custom) {
- l10nIdSummary = "security-level-summary-custom";
- }
document.l10n.setAttributes(this._elements.levelName, l10nIdLevel);
document.l10n.setAttributes(this._elements.summary, l10nIdSummary);
@@ -269,13 +246,6 @@ var SecurityLevelPanel = {
this._elements.panel.hidePopup();
},
- restoreDefaults() {
- SecurityLevelPrefs.securityCustom = false;
- // Move focus to the settings button since restore defaults button will
- // become hidden.
- this._elements.settingsButton.focus();
- },
-
openSecuritySettings() {
openPreferences("privacy-securitylevel");
this.hide();
@@ -301,59 +271,64 @@ var SecurityLevelPanel = {
var SecurityLevelPreferences = {
_securityPrefsBranch: null,
+
/**
- * The notification box shown when the user has a custom security setting.
- *
- * @type {Element}
- */
- _customNotification: null,
- /**
- * The radiogroup for this preference.
- *
- * @type {Element}
- */
- _radiogroup: null,
- /**
- * A list of radio options and their containers.
+ * The element that shows the current security level.
*
- * @type {Array<object>}
+ * @type {?Element}
*/
- _radioOptions: null,
+ _currentEl: null,
_populateXUL() {
- this._customNotification = document.getElementById(
- "securityLevel-customNotification"
+ this._currentEl = document.getElementById("security-level-current");
+ const changeButton = document.getElementById("security-level-change");
+ const badgeEl = this._currentEl.querySelector(
+ ".security-level-current-badge"
);
- document
- .getElementById("securityLevel-restoreDefaults")
- .addEventListener("command", () => {
- SecurityLevelPrefs.securityCustom = false;
- });
-
- this._radiogroup = document.getElementById("securityLevel-radiogroup");
- this._radioOptions = Array.from(
- this._radiogroup.querySelectorAll(".securityLevel-radio-option"),
- container => {
- return { container, radio: container.querySelector("radio") };
- }
- );
+ for (const { level, nameId } of [
+ { level: "standard", nameId: "security-level-panel-level-standard" },
+ { level: "safer", nameId: "security-level-panel-level-safer" },
+ { level: "safest", nameId: "security-level-panel-level-safest" },
+ { level: "custom", nameId: "security-level-panel-level-custom" },
+ ]) {
+ // Classes that control visibility:
+ // security-level-current-standard
+ // security-level-current-safer
+ // security-level-current-safest
+ // security-level-current-custom
+ const visibilityClass = `security-level-current-${level}`;
+ const nameEl = document.createElement("div");
+ nameEl.classList.add("security-level-name", visibilityClass);
+ document.l10n.setAttributes(nameEl, nameId);
+
+ const descriptionEl = SecurityLevelUIUtils.createDescriptionElement(
+ level,
+ document
+ );
+ descriptionEl.classList.add(visibilityClass);
+
+ this._currentEl.insertBefore(nameEl, badgeEl);
+ this._currentEl.insertBefore(descriptionEl, changeButton);
+ }
- this._radiogroup.addEventListener("select", () => {
- SecurityLevelPrefs.securityLevel = this._radiogroup.value;
+ changeButton.addEventListener("click", () => {
+ this._openDialog();
});
},
+ _openDialog() {
+ gSubDialog.open(
+ "chrome://browser/content/securitylevel/securityLevelDialog.xhtml",
+ { features: "resizable=yes" }
+ );
+ },
+
_configUIFromPrefs() {
- this._radiogroup.value = SecurityLevelPrefs.securityLevel;
- const isCustom = SecurityLevelPrefs.securityCustom;
- this._radiogroup.disabled = isCustom;
- this._customNotification.hidden = !isCustom;
- // Have the container's selection CSS class match the selection state of the
- // radio elements.
- for (const { container, radio } of this._radioOptions) {
- container.classList.toggle("selected", radio.selected);
- }
+ // Set a data-current-level attribute for showing the current security
+ // level, and hiding the rest.
+ this._currentEl.dataset.currentLevel =
+ SecurityLevelPrefs.securityLevelSummary;
},
init() {
=====================================
browser/components/securitylevel/content/securityLevelButton.css
=====================================
@@ -7,12 +7,6 @@ toolbarbutton#security-level-button[level="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"] {
+toolbarbutton#security-level-button[level="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");
-}
=====================================
browser/components/securitylevel/content/securityLevelDialog.js
=====================================
@@ -0,0 +1,188 @@
+"use strict";
+
+const { SecurityLevelPrefs } = ChromeUtils.importESModule(
+ "resource://gre/modules/SecurityLevel.sys.mjs"
+);
+const { SecurityLevelUIUtils } = ChromeUtils.importESModule(
+ "resource:///modules/SecurityLevelUIUtils.sys.mjs"
+);
+
+const gSecurityLevelDialog = {
+ /**
+ * The security level when this dialog was opened.
+ *
+ * @type {string}
+ */
+ _prevLevel: SecurityLevelPrefs.securityLevelSummary,
+ /**
+ * The security level currently selected.
+ *
+ * @type {string}
+ */
+ _selectedLevel: "",
+ /**
+ * The radiogroup for this preference.
+ *
+ * @type {?Element}
+ */
+ _radiogroup: null,
+ /**
+ * A list of radio options and their containers.
+ *
+ * @type {?Array<{ container: Element, radio: Element }>}
+ */
+ _radioOptions: null,
+
+ /**
+ * Initialise the dialog.
+ */
+ async init() {
+ const dialog = document.getElementById("security-level-dialog");
+ dialog.addEventListener("dialogaccept", event => {
+ if (this._acceptButton.disabled) {
+ event.preventDefault();
+ return;
+ }
+ this._commitChange();
+ });
+
+ this._acceptButton = dialog.getButton("accept");
+
+ document.l10n.setAttributes(
+ this._acceptButton,
+ "security-level-dialog-save-restart"
+ );
+
+ this._radiogroup = document.getElementById("security-level-radiogroup");
+
+ this._radioOptions = Array.from(
+ this._radiogroup.querySelectorAll(".security-level-radio-container"),
+ container => {
+ return {
+ container,
+ radio: container.querySelector(".security-level-radio"),
+ };
+ }
+ );
+
+ for (const { container, radio } of this._radioOptions) {
+ const level = radio.value;
+ radio.id = `security-level-radio-${level}`;
+ const currentEl = container.querySelector(
+ ".security-level-current-badge"
+ );
+ currentEl.id = `security-level-current-badge-${level}`;
+ const descriptionEl = SecurityLevelUIUtils.createDescriptionElement(
+ level,
+ document
+ );
+ descriptionEl.classList.add("indent");
+ descriptionEl.id = `security-level-description-${level}`;
+
+ // Wait for the full translation of the element before adding it to the
+ // DOM. In particular, we want to make sure the elements have text before
+ // we measure the maxHeight below.
+ await document.l10n.translateFragment(descriptionEl);
+ document.l10n.pauseObserving();
+ container.append(descriptionEl);
+ document.l10n.resumeObserving();
+
+ if (level === this._prevLevel) {
+ currentEl.hidden = false;
+ // When the currentEl is visible, include it in the accessible name for
+ // the radio option.
+ // NOTE: The currentEl has an accessible name which includes punctuation
+ // to help separate it's content from the security level name.
+ // E.g. "Standard (Current level)".
+ radio.setAttribute("aria-labelledby", `${radio.id} ${currentEl.id}`);
+ } else {
+ currentEl.hidden = true;
+ }
+ // We point the accessible description to the wrapping
+ // .security-level-description element, rather than its children
+ // that define the actual text content. This means that when the
+ // privacy-extra-information is shown or hidden, its text content is
+ // included or excluded from the accessible description, respectively.
+ radio.setAttribute("aria-describedby", descriptionEl.id);
+ }
+
+ // We want to reserve the maximum height of the radiogroup so that the
+ // dialog has enough height when the user switches options. So we cycle
+ // through the options and measure the height when they are selected to set
+ // a minimum height that fits all of them.
+ // NOTE: At the time of implementation, at this point the dialog may not
+ // yet have the "subdialog" attribute, which means it is missing the
+ // common.css stylesheet from its shadow root, which effects the size of the
+ // .radio-check element and the font. Therefore, we have duplicated the
+ // import of common.css in SecurityLevelDialog.xhtml to ensure it is applied
+ // at this earlier stage.
+ let maxHeight = 0;
+ for (const { container } of this._radioOptions) {
+ container.classList.add("selected");
+ maxHeight = Math.max(
+ maxHeight,
+ this._radiogroup.getBoundingClientRect().height
+ );
+ container.classList.remove("selected");
+ }
+ this._radiogroup.style.minHeight = `${maxHeight}px`;
+
+ if (this._prevLevel !== "custom") {
+ this._selectedLevel = this._prevLevel;
+ this._radiogroup.value = this._prevLevel;
+ } else {
+ this._radiogroup.selectedItem = null;
+ }
+
+ this._radiogroup.addEventListener("select", () => {
+ this._selectedLevel = this._radiogroup.value;
+ this._updateSelected();
+ });
+
+ this._updateSelected();
+ },
+
+ /**
+ * Update the UI in response to a change in selection.
+ */
+ _updateSelected() {
+ this._acceptButton.disabled =
+ !this._selectedLevel || this._selectedLevel === this._prevLevel;
+ // Have the container's `selected` CSS class match the selection state of
+ // the radio elements.
+ for (const { container, radio } of this._radioOptions) {
+ container.classList.toggle("selected", radio.selected);
+ }
+ },
+
+ /**
+ * Commit the change in security level and restart the browser.
+ */
+ _commitChange() {
+ SecurityLevelPrefs.setSecurityLevelBeforeRestart(this._selectedLevel);
+ Services.startup.quit(
+ Services.startup.eAttemptQuit | Services.startup.eRestart
+ );
+ },
+};
+
+// Initial focus is not visible, even if opened with a keyboard. We avoid the
+// default handler and manage the focus ourselves, which will paint the focus
+// ring by default.
+// NOTE: A side effect is that the focus ring will show even if the user opened
+// with a mouse event.
+// TODO: Remove this once bugzilla bug 1708261 is resolved.
+document.subDialogSetDefaultFocus = () => {
+ document.getElementById("security-level-radiogroup").focus();
+};
+
+// Delay showing and sizing the subdialog until it is fully initialised.
+document.mozSubdialogReady = new Promise(resolve => {
+ window.addEventListener(
+ "DOMContentLoaded",
+ () => {
+ gSecurityLevelDialog.init().finally(resolve);
+ },
+ { once: true }
+ );
+});
=====================================
browser/components/securitylevel/content/securityLevelDialog.xhtml
=====================================
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+
+<window
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ data-l10n-id="security-level-dialog-window"
+>
+ <dialog id="security-level-dialog" buttons="accept,cancel">
+ <linkset>
+ <html:link rel="stylesheet" href="chrome://global/skin/global.css" />
+ <!-- NOTE: We include common.css explicitly, rather than relying on
+ - the dialog's shadowroot importing it, which is late loaded in
+ - response to the dialog's "subdialog" attribute, which is set
+ - in response to DOMFrameContentLoaded.
+ - In particular, we need the .radio-check rule and font rules from
+ - common-shared.css to be in place when gSecurityLevelDialog.init is
+ - called, which will help ensure that the radio element has the correct
+ - size when we measure its bounding box. -->
+ <html:link
+ rel="stylesheet"
+ href="chrome://global/skin/in-content/common.css"
+ />
+ <html:link
+ rel="stylesheet"
+ href="chrome://browser/skin/preferences/preferences.css"
+ />
+ <html:link
+ rel="stylesheet"
+ href="chrome://browser/skin/preferences/privacy.css"
+ />
+ <html:link
+ rel="stylesheet"
+ href="chrome://browser/content/securitylevel/securityLevelPreferences.css"
+ />
+
+ <html:link rel="localization" href="branding/brand.ftl" />
+ <html:link rel="localization" href="toolkit/global/base-browser.ftl" />
+ </linkset>
+
+ <script src="chrome://browser/content/securitylevel/securityLevelDialog.js" />
+
+ <description data-l10n-id="security-level-dialog-restart-description" />
+
+ <radiogroup id="security-level-radiogroup" class="highlighting-group">
+ <html:div
+ class="security-level-radio-container security-level-grid privacy-detailedoption info-box-container"
+ >
+ <radio
+ class="security-level-radio security-level-name"
+ value="standard"
+ data-l10n-id="security-level-preferences-level-standard"
+ />
+ <html:div
+ class="security-level-current-badge"
+ data-l10n-id="security-level-preferences-current-badge"
+ ></html:div>
+ </html:div>
+ <html:div
+ class="security-level-radio-container security-level-grid privacy-detailedoption info-box-container"
+ >
+ <radio
+ class="security-level-radio security-level-name"
+ value="safer"
+ data-l10n-id="security-level-preferences-level-safer"
+ />
+ <html:div
+ class="security-level-current-badge"
+ data-l10n-id="security-level-preferences-current-badge"
+ ></html:div>
+ </html:div>
+ <html:div
+ class="security-level-radio-container security-level-grid privacy-detailedoption info-box-container"
+ >
+ <radio
+ class="security-level-radio security-level-name"
+ value="safest"
+ data-l10n-id="security-level-preferences-level-safest"
+ />
+ <html:div
+ class="security-level-current-badge"
+ data-l10n-id="security-level-preferences-current-badge"
+ ></html:div>
+ </html:div>
+ </radiogroup>
+ </dialog>
+</window>
=====================================
browser/components/securitylevel/content/securityLevelPanel.css
=====================================
@@ -23,7 +23,7 @@
background-position-x: right var(--background-inline-offset);
}
-#securityLevel-background[level="standard"] {
+#securityLevel-background:is([level="standard"], [level="custom"]) {
background-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard");
}
@@ -49,14 +49,6 @@
font-weight: 600;
}
-#securityLevel-custom {
- border-radius: 4px;
- background-color: var(--warning-icon-bgcolor);
- color: black;
- padding: 0.4em 0.5em;
- margin-inline-start: 1em;
-}
-
#securityLevel-summary {
padding-inline-end: 5em;
max-width: 20em;
=====================================
browser/components/securitylevel/content/securityLevelPanel.inc.xhtml
=====================================
@@ -15,10 +15,6 @@
<vbox id="securityLevel-background" class="panel-subview-body">
<html:p id="securityLevel-subheading">
<html:span id="securityLevel-level"></html:span>
- <html:span
- id="securityLevel-custom"
- data-l10n-id="security-level-panel-custom-badge"
- ></html:span>
</html:p>
<html:p id="securityLevel-summary"></html:p>
<html:a
@@ -32,12 +28,8 @@
<button
id="securityLevel-settings"
class="footer-button"
+ default="true"
data-l10n-id="security-level-panel-open-settings-button"
/>
- <button
- id="securityLevel-restoreDefaults"
- class="footer-button"
- data-l10n-id="security-level-restore-defaults-button"
- />
</html:moz-button-group>
</panel>
=====================================
browser/components/securitylevel/content/securityLevelPreferences.css
=====================================
@@ -1,26 +1,166 @@
-#securityLevel-groupbox {
- --section-highlight-background-color: color-mix(in srgb, var(--in-content-accent-color) 20%, transparent);
+.security-level-grid {
+ display: grid;
+ grid-template:
+ "icon name badge button" min-content
+ "icon summary summary button" auto
+ "icon extra extra ." auto
+ / max-content max-content 1fr max-content;
}
-#securityLevel-customNotification {
- /* Spacing similar to #fpiIncompatibilityWarning. */
- margin-block: 16px;
+.security-level-icon {
+ grid-area: icon;
+ align-self: start;
+ width: 24px;
+ height: 24px;
+ -moz-context-properties: fill;
+ fill: var(--in-content-icon-color);
+ margin-block-start: var(--space-xsmall);
+ margin-inline-end: var(--space-large);
}
-.info-icon.securityLevel-custom-warning-icon {
- list-style-image: url("chrome://global/skin/icons/warning.svg");
+.security-level-current-badge {
+ grid-area: badge;
+ align-self: center;
+ justify-self: start;
+ white-space: nowrap;
+ background: var(--background-color-information);
+ color: inherit;
+ font-size: var(--font-size-small);
+ border-radius: var(--border-radius-circle);
+ margin-inline-start: var(--space-small);
+ padding-block: var(--space-xsmall);
+ padding-inline: var(--space-small);
}
-#securityLevel-customHeading {
+.security-level-current-badge span {
+ /* Still accessible to screen reader, but not visual.
+ * Keep inline, but with no layout width. */
+ display: inline-block;
+ width: 1px;
+ margin-inline-end: -1px;
+ clip-path: inset(50%);
+}
+
+@media (prefers-contrast) and (not (forced-colors)) {
+ .security-level-current-badge {
+ /* Match the checkbox/radio colors. */
+ background: var(--color-accent-primary);
+ color: var(--button-text-color-primary);
+ }
+}
+
+@media (forced-colors) {
+ .security-level-current-badge {
+ /* Match the checkbox/radio/selected colors. */
+ background: SelectedItem;
+ color: SelectedItemText;
+ }
+}
+
+.security-level-name {
+ grid-area: name;
font-weight: bold;
+ align-self: center;
+ white-space: nowrap;
+}
+
+.security-level-description {
+ display: grid;
+ grid-column: summary-start / extra-end;
+ grid-row: summary-start / extra-end;
+ grid-template-rows: subgrid;
+ grid-template-columns: subgrid;
+ margin-block-start: var(--space-small);
+}
+
+.security-level-summary {
+ grid-area: summary;
+}
+
+.security-level-description-extra {
+ grid-area: extra;
+ margin-block: var(--space-medium) 0;
+ margin-inline: var(--space-large) 0;
+ padding: 0;
+}
+
+.security-level-description-bullet:not(:last-child) {
+ margin-block-end: var(--space-medium);
+}
+
+/* Tweak current security level display. */
+
+#security-level-current {
+ margin-block-start: var(--space-large);
+ background: var(--in-content-box-background);
+ border: 1px solid var(--in-content-box-border-color);
+ border-radius: var(--border-radius-small);
+ padding: var(--space-medium);
+}
+
+#security-level-change {
+ grid-area: button;
+ align-self: center;
+ margin: 0;
+ margin-inline-start: var(--space-large);
+}
+
+/* Adjust which content is visible depending on the current security level. */
+
+#security-level-current:not([data-current-level="standard"]) .security-level-current-standard {
+ display: none;
+}
+
+#security-level-current:not([data-current-level="safer"]) .security-level-current-safer {
+ display: none;
+}
+
+#security-level-current:not([data-current-level="safest"]) .security-level-current-safest {
+ display: none;
+}
+
+#security-level-current:not([data-current-level="custom"]) .security-level-current-custom {
+ display: none;
+}
+
+#security-level-current[data-current-level="standard"] .security-level-icon {
+ content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard");
+}
+
+#security-level-current[data-current-level="safer"] .security-level-icon {
+ content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safer");
+}
+
+#security-level-current[data-current-level="safest"] .security-level-icon {
+ content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safest");
+}
+
+#security-level-current[data-current-level="custom"] .security-level-icon {
+ content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard_custom");
+}
+
+/* Tweak security level dialog. */
+
+#security-level-radiogroup {
+ margin-block: var(--space-large) var(--space-xlarge);
+}
+
+.security-level-radio-container {
+ padding-block: var(--space-large);
+}
+
+#security-level-radiogroup .security-level-radio {
+ margin: 0;
}
-#securityLevel-radiogroup[disabled] {
- opacity: 0.5;
+#security-level-radiogroup .radio-label-box {
+ /* .security-level-current-badge already has a margin. */
+ margin: 0;
}
-/* Overwrite the rule in common-shared.css so we don't get 0.25 opacity overall
- * on the radio text. */
-#securityLevel-radiogroup[disabled] radio[disabled] .radio-label-box {
- opacity: 1.0;
+#security-level-radiogroup .privacy-detailedoption.security-level-radio-container:not(.selected) .security-level-description-extra {
+ /* .privacy-detailedoption uses visibility: hidden, which does not work with
+ * our grid display (the margin is still reserved) so we use display: none
+ * instead. */
+ display: none;
}
=====================================
browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml
=====================================
@@ -17,112 +17,19 @@
data-l10n-id="security-level-preferences-learn-more-link"
></html:a>
</description>
- <hbox
- id="securityLevel-customNotification"
- class="info-box-container"
- flex="1"
- >
- <hbox class="info-icon-container">
- <image class="info-icon securityLevel-custom-warning-icon"/>
- </hbox>
- <vbox flex="1">
- <label
- id="securityLevel-customHeading"
- data-l10n-id="security-level-preferences-custom-heading"
- />
- <description
- id="securityLevel-customDescription"
- data-l10n-id="security-level-summary-custom"
- flex="1"
- />
- </vbox>
- <hbox align="center">
- <button
- id="securityLevel-restoreDefaults"
- data-l10n-id="security-level-restore-defaults-button"
- />
- </hbox>
- </hbox>
- <radiogroup id="securityLevel-radiogroup">
- <vbox class="securityLevel-radio-option privacy-detailedoption info-box-container">
- <radio
- value="standard"
- data-l10n-id="security-level-preferences-level-standard"
- aria-describedby="securityLevelSummary-standard"
- />
- <vbox id="securityLevelSummary-standard" class="indent">
- <label data-l10n-id="security-level-summary-standard" />
- </vbox>
- </vbox>
- <vbox class="securityLevel-radio-option privacy-detailedoption info-box-container">
- <!-- NOTE: We point the accessible description to the wrapping vbox
- - rather than its first description element. This means that when the
- - privacy-extra-information is shown or hidden, its text content is
- - included or excluded from the accessible description, respectively.
- -->
- <radio
- value="safer"
- data-l10n-id="security-level-preferences-level-safer"
- aria-describedby="securityLevelSummary-safer"
- />
- <vbox id="securityLevelSummary-safer" class="indent">
- <label data-l10n-id="security-level-summary-safer" />
- <vbox class="privacy-extra-information">
- <vbox class="indent">
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-https-only-javascript"
- />
- </hbox>
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-limit-font-and-symbols"
- />
- </hbox>
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-limit-media"
- />
- </hbox>
- </vbox>
- </vbox>
- </vbox>
- </vbox>
- <vbox class="securityLevel-radio-option privacy-detailedoption info-box-container">
- <radio
- value="safest"
- data-l10n-id="security-level-preferences-level-safest"
- aria-describedby="securityLevelSummary-safest"
- />
- <vbox id="securityLevelSummary-safest" class="indent">
- <label data-l10n-id="security-level-summary-safest" />
- <vbox class="privacy-extra-information">
- <vbox class="indent">
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-disabled-javascript"
- />
- </hbox>
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-limit-font-and-symbols-and-images"
- />
- </hbox>
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-limit-media"
- />
- </hbox>
- </vbox>
- </vbox>
- </vbox>
- </vbox>
- </radiogroup>
+ <html:div id="security-level-current" class="security-level-grid">
+ <html:img
+ class="security-level-icon"
+ alt=""
+ />
+ <html:div
+ class="security-level-current-badge"
+ data-l10n-id="security-level-preferences-current-badge"
+ ></html:div>
+ <html:button
+ id="security-level-change"
+ data-l10n-id="security-level-preferences-change-button"
+ ></html:button>
+ </html:div>
</vbox>
</groupbox>
=====================================
browser/components/securitylevel/jar.mn
=====================================
@@ -4,3 +4,5 @@ browser.jar:
content/browser/securitylevel/securityLevelButton.css (content/securityLevelButton.css)
content/browser/securitylevel/securityLevelPreferences.css (content/securityLevelPreferences.css)
content/browser/securitylevel/securityLevelIcon.svg (content/securityLevelIcon.svg)
+ content/browser/securitylevel/securityLevelDialog.xhtml (content/securityLevelDialog.xhtml)
+ content/browser/securitylevel/securityLevelDialog.js (content/securityLevelDialog.js)
=====================================
browser/components/securitylevel/moz.build
=====================================
@@ -1 +1,5 @@
JAR_MANIFESTS += ["jar.mn"]
+
+EXTRA_JS_MODULES += [
+ "SecurityLevelUIUtils.sys.mjs",
+]
=====================================
browser/modules/SecurityLevelRestartNotification.sys.mjs
=====================================
@@ -0,0 +1,72 @@
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
+ SecurityLevelPrefs: "resource://gre/modules/SecurityLevel.sys.mjs",
+});
+
+ChromeUtils.defineLazyGetter(lazy, "NotificationStrings", function () {
+ return new Localization([
+ "branding/brand.ftl",
+ "toolkit/global/base-browser.ftl",
+ ]);
+});
+
+/**
+ * Interface for showing the security level restart notification on desktop.
+ */
+export const SecurityLevelRestartNotification = {
+ /**
+ * Whether we have already been initialised.
+ *
+ * @type {boolean}
+ */
+ _initialized: false,
+
+ /**
+ * Called when the UI is ready to show a notification.
+ */
+ ready() {
+ if (this._initialized) {
+ return;
+ }
+ this._initialized = true;
+ lazy.SecurityLevelPrefs.setRestartNotificationHandler(this);
+ },
+
+ /**
+ * Show the restart notification, and perform the restart if the user agrees.
+ */
+ async tryRestartBrowser() {
+ const [titleText, bodyText, primaryButtonText, secondaryButtonText] =
+ await lazy.NotificationStrings.formatValues([
+ { id: "security-level-restart-prompt-title" },
+ { id: "security-level-restart-prompt-body" },
+ { id: "security-level-restart-prompt-button-restart" },
+ { id: "security-level-restart-prompt-button-ignore" },
+ ]);
+ const buttonFlags =
+ Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
+ Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1;
+
+ const propBag = await Services.prompt.asyncConfirmEx(
+ lazy.BrowserWindowTracker.getTopWindow()?.browsingContext ?? null,
+ Services.prompt.MODAL_TYPE_INTERNAL_WINDOW,
+ titleText,
+ bodyText,
+ buttonFlags,
+ primaryButtonText,
+ secondaryButtonText,
+ null,
+ null,
+ null,
+ {}
+ );
+
+ if (propBag.get("buttonNumClicked") === 0) {
+ Services.startup.quit(
+ Services.startup.eAttemptQuit | Services.startup.eRestart
+ );
+ }
+ },
+};
=====================================
browser/modules/moz.build
=====================================
@@ -126,6 +126,7 @@ EXTRA_JS_MODULES += [
"PermissionUI.sys.mjs",
"ProcessHangMonitor.sys.mjs",
"Sanitizer.sys.mjs",
+ "SecurityLevelRestartNotification.sys.mjs",
"SelectionChangedMenulist.sys.mjs",
"SiteDataManager.sys.mjs",
"SitePermissions.sys.mjs",
=====================================
toolkit/components/extensions/ExtensionParent.sys.mjs
=====================================
@@ -2345,6 +2345,7 @@ async function torSendExtensionMessage(extensionId, message) {
const result = await ProxyMessenger.conduit.castRuntimeMessage("messenger", {
extensionId,
holder: new StructuredCloneHolder("torSendExtensionMessage", null, message),
+ query: true,
firstResponse: true,
sender: {
id: extensionId,
=====================================
toolkit/components/securitylevel/SecurityLevel.sys.mjs
=====================================
@@ -16,6 +16,7 @@ const BrowserTopics = Object.freeze({
// The Security Settings prefs in question.
const kSliderPref = "browser.security_level.security_slider";
const kCustomPref = "browser.security_level.security_custom";
+const kNoScriptInitedPref = "browser.security_level.noscript_inited";
// __getPrefValue(prefName)__
// Returns the current value of a preference, regardless of its type.
@@ -32,11 +33,11 @@ var getPrefValue = function (prefName) {
}
};
-// __bindPref(prefName, prefHandler, init)__
+// __bindPref(prefName, prefHandler)__
// 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) {
+// Returns the observer that was added.
+var bindPref = function (prefName, prefHandler) {
let update = () => {
prefHandler(getPrefValue(prefName));
},
@@ -48,21 +49,9 @@ var bindPref = function (prefName, prefHandler, init = false) {
},
};
Services.prefs.addObserver(prefName, observer);
- if (init) {
- update();
- }
- return () => {
- Services.prefs.removeObserver(prefName, observer);
- };
+ return 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 } = lazy.ExtensionParent;
if (torWaitForExtensionMessage) {
@@ -74,7 +63,7 @@ async function waitForExtensionMessage(extensionId, checker = () => {}) {
async function sendExtensionMessage(extensionId, message) {
const { torSendExtensionMessage } = lazy.ExtensionParent;
if (torSendExtensionMessage) {
- return torSendExtensionMessage(extensionId, message);
+ return await torSendExtensionMessage(extensionId, message);
}
return undefined;
}
@@ -199,14 +188,8 @@ var initializeNoScriptControl = () => {
// `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));
+ let sendNoScriptSettings = async settings =>
+ await sendExtensionMessage(noscriptID, settings);
// __securitySliderToSafetyLevel(sliderState)__.
// Converts the "browser.security_level.security_slider" pref value
@@ -216,36 +199,46 @@ var initializeNoScriptControl = () => {
// Wait for the first message from NoScript to arrive, and then
// bind the security_slider pref to the NoScript settings.
- let messageListener = a => {
+ let messageListener = async a => {
try {
logger.debug("Message received from NoScript:", a);
- let noscriptPersist = Services.prefs.getBoolPref(
- "browser.security_level.noscript_persist",
- false
- );
+ const persistPref = "browser.security_level.noscript_persist";
+ let noscriptPersist = Services.prefs.getBoolPref(persistPref, false);
let noscriptInited = Services.prefs.getBoolPref(
- "browser.security_level.noscript_inited",
+ kNoScriptInitedPref,
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
+ // Set the noscript safety level once at startup.
+ // If a user has set noscriptPersist, then we only send this if the
+ // security level was changed in a previous session.
+ // NOTE: We do not re-send this when the security_slider preference
+ // changes mid-session because this should always require a restart.
+ if (noscriptPersist && noscriptInited) {
+ logger.warn(
+ `Not initialising NoScript since the user has set ${persistPref}`
);
+ return;
}
+ // Read the security level, even if the user has the "custom"
+ // preference.
+ const securityIndex = Services.prefs.getIntPref(kSliderPref, 0);
+ const safetyLevel = securitySliderToSafetyLevel(securityIndex);
+ // May throw if NoScript fails to apply the settings:
+ const noscriptResult = await sendNoScriptSettings(
+ noscriptSettings(safetyLevel)
+ );
+ // Mark the NoScript extension as initialised so we do not reset it
+ // at the next startup for noscript_persist users.
+ Services.prefs.setBoolPref(kNoScriptInitedPref, true);
+ logger.info("NoScript successfully initialised.");
+ // In the future NoScript may tell us more about how it applied our
+ // settings, e.g. if user is overriding per-site permissions.
+ // Up to NoScript 12.6 noscriptResult is undefined.
+ logger.debug("NoScript response:", noscriptResult);
} catch (e) {
- logger.exception(e);
+ logger.error("Could not apply NoScript settings", e);
+ // Treat as a custom security level for the rest of the session.
+ Services.prefs.setBoolPref(kCustomPref, true);
}
};
waitForExtensionMessage(noscriptID, a => a.__meta.name === "started").then(
@@ -254,6 +247,8 @@ var initializeNoScriptControl = () => {
logger.info("Listening for messages from NoScript.");
} catch (e) {
logger.exception(e);
+ // Treat as a custom security level for the rest of the session.
+ Services.prefs.setBoolPref(kCustomPref, true);
}
};
@@ -283,16 +278,60 @@ const kSecuritySettings = {
// ### Prefs
+/**
+ * Amend the security level index to a standard value.
+ *
+ * @param {integer} index - The input index value.
+ * @returns {integer} - A standard index value.
+ */
+function fixupIndex(index) {
+ if (!Number.isInteger(index) || index < 1 || index > 4) {
+ // Unexpected value out of range, go to the "safest" level as a fallback.
+ return 1;
+ }
+ if (index === 3) {
+ // Migrate from old medium-low (3) to new medium (2).
+ return 2;
+ }
+ return index;
+}
+
+/**
+ * A list of preference observers that should be disabled whilst we write our
+ * preference values.
+ *
+ * @type {{ prefName: string, observer: object }[]}
+ */
+const prefObservers = [];
+
// __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]
- )
- );
+ settingIndex = fixupIndex(settingIndex);
+ // Don't want to trigger our internal observers when setting ourselves.
+ for (const { prefName, observer } of prefObservers) {
+ Services.prefs.removeObserver(prefName, observer);
+ }
+ try {
+ // Make sure noscript is re-initialised at the next startup when the
+ // security level changes.
+ Services.prefs.setBoolPref(kNoScriptInitedPref, false);
+ Services.prefs.setIntPref(kSliderPref, settingIndex);
+ // NOTE: We do not clear kCustomPref. Instead, we rely on the preference
+ // being cleared on the next startup.
+ Object.keys(kSecuritySettings).forEach(prefName =>
+ Services.prefs.setBoolPref(
+ prefName,
+ kSecuritySettings[prefName][settingIndex]
+ )
+ );
+ } finally {
+ // Re-add the observers.
+ for (const { prefName, observer } of prefObservers) {
+ Services.prefs.addObserver(prefName, observer);
+ }
+ }
};
// __read_setting_from_prefs()__.
@@ -321,24 +360,6 @@ var read_setting_from_prefs = function (prefNames) {
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;
@@ -354,36 +375,82 @@ var initializeSecurityPrefs = function () {
}
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) {
+
+ const wasCustom = Services.prefs.getBoolPref(kCustomPref, false);
+ // For new profiles with no user preference, the security level should be "4"
+ // and it should not be custom.
+ let desiredIndex = Services.prefs.getIntPref(kSliderPref, 4);
+ desiredIndex = fixupIndex(desiredIndex);
+ // Make sure the user has a set preference user value.
+ Services.prefs.setIntPref(kSliderPref, desiredIndex);
+ Services.prefs.setBoolPref(kCustomPref, wasCustom);
+
+ // Make sure that the preference values at application startup match the
+ // expected values for the desired security level. See tor-browser#43783.
+
+ // NOTE: We assume that the controlled preference values that are read prior
+ // to profile-after-change do not change in value before this method is
+ // called. I.e. we expect the current preference values to match the
+ // preference values that were used during the application initialisation.
+ const effectiveIndex = read_setting_from_prefs();
+
+ if (wasCustom && effectiveIndex !== null) {
+ logger.info(`Custom startup values match index ${effectiveIndex}`);
+ // Do not consider custom any more.
+ // NOTE: This level needs to be set before it is read elsewhere. In
+ // particular, for the NoScript addon.
Services.prefs.setBoolPref(kCustomPref, false);
- write_setting_to_prefs(prefIndex);
+ Services.prefs.setIntPref(kSliderPref, effectiveIndex);
+ } else if (!wasCustom && effectiveIndex !== desiredIndex) {
+ // NOTE: We assume all our controlled preferences require a restart.
+ // In practice, only a subset of these preferences may actually require a
+ // restart, so we could switch their values. But we treat them all the same
+ // for simplicity, consistency and stability in case mozilla changes the
+ // restart requirements.
+ logger.info(`Startup values do not match for index ${desiredIndex}`);
+ SecurityLevelPrefs.requireRestart();
+ }
+
+ // Start listening for external changes to the controlled preferences.
+ prefObservers.push({
+ prefName: kCustomPref,
+ observer: bindPref(kCustomPref, custom => {
+ // Custom flag was removed mid-session. Requires a restart to apply the
+ // security level.
+ if (custom === false) {
+ logger.info("Custom flag was cleared externally");
+ SecurityLevelPrefs.requireRestart();
+ }
+ }),
});
- // 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);
- }
+ prefObservers.push({
+ prefName: kSliderPref,
+ observer: bindPref(kSliderPref, () => {
+ // Security level was changed mid-session. Requires a restart to apply.
+ logger.info("Security level was changed externally");
+ SecurityLevelPrefs.requireRestart();
+ }),
});
- // 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);
+
+ for (const prefName of Object.keys(kSecuritySettings)) {
+ prefObservers.push({
+ prefName,
+ observer: bindPref(prefName, () => {
+ logger.warn(
+ `The controlled preference ${prefName} was changed externally.` +
+ " Treating as a custom security level."
+ );
+ // Something outside of this module changed the preference value for a
+ // preference we control.
+ // Always treat as a custom security level for the rest of this session,
+ // even if the new preference values match a pre-set security level. We
+ // do this because some controlled preferences require a restart to be
+ // properly applied. See tor-browser#43783.
+ // In the case where it does match a pre-set security level, the custom
+ // flag will be cleared at the next startup.
+ Services.prefs.setBoolPref(kCustomPref, true);
+ }),
+ });
}
logger.info("security-prefs.js initialization complete");
@@ -437,8 +504,9 @@ export class SecurityLevel {
init() {
migratePreferences();
- initializeNoScriptControl();
+ // Fixup our preferences before we pass on the security level to NoScript.
initializeSecurityPrefs();
+ initializeNoScriptControl();
}
observe(aSubject, aTopic) {
@@ -448,10 +516,19 @@ export class SecurityLevel {
}
}
+/**
+ * @typedef {object} SecurityLevelRestartNotificationHandler
+ *
+ * An object that can serve the user a restart notification.
+ *
+ * @property {Function} tryRestartBrowser - The method that should be called to
+ * ask the user to restart the browser.
+ */
+
/*
Security Level Prefs
- Getters and Setters for relevant torbutton prefs
+ Getters and Setters for relevant security level prefs
*/
export const SecurityLevelPrefs = {
SecurityLevels: Object.freeze({
@@ -462,6 +539,14 @@ export const SecurityLevelPrefs = {
security_slider_pref: "browser.security_level.security_slider",
security_custom_pref: "browser.security_level.security_custom",
+ /**
+ * The current security level preference.
+ *
+ * This ignores any custom settings the user may have changed, and just
+ * gives the underlying security level.
+ *
+ * @type {?string}
+ */
get securityLevel() {
// Set the default return value to 0, which won't match anything in
// SecurityLevels.
@@ -471,18 +556,146 @@ export const SecurityLevelPrefs = {
)?.[0];
},
- set securityLevel(level) {
- const val = this.SecurityLevels[level];
- if (val !== undefined) {
- Services.prefs.setIntPref(this.security_slider_pref, val);
- }
+ /**
+ * Set the desired security level just before a restart.
+ *
+ * The caller must restart the browser after calling this method.
+ *
+ * @param {string} level - The name of the new security level to set.
+ */
+ setSecurityLevelBeforeRestart(level) {
+ write_setting_to_prefs(this.SecurityLevels[level]);
},
+ /**
+ * Whether the user has any custom setting values that do not match a pre-set
+ * security level.
+ *
+ * @type {boolean}
+ */
get securityCustom() {
return Services.prefs.getBoolPref(this.security_custom_pref);
},
- set securityCustom(val) {
- Services.prefs.setBoolPref(this.security_custom_pref, val);
+ /**
+ * A summary of the current security level.
+ *
+ * If the user has some custom settings, this returns "custom". Otherwise
+ * returns the name of the security level.
+ *
+ * @type {string}
+ */
+ get securityLevelSummary() {
+ if (this.securityCustom) {
+ return "custom";
+ }
+ return this.securityLevel ?? "custom";
+ },
+
+ /**
+ * Whether the browser should be restarted to apply the security level.
+ *
+ * @type {boolean}
+ */
+ _needRestart: false,
+
+ /**
+ * The external handler that can show a notification to the user, if any.
+ *
+ * @type {?SecurityLevelRestartNotificationHandler}
+ */
+ _restartNotificationHandler: null,
+
+ /**
+ * Set the external handler for showing notifications to the user.
+ *
+ * This should only be called once per session once the handler is ready to
+ * show a notification, which may occur immediately during this call.
+ *
+ * @param {SecurityLevelRestartNotificationHandler} handler - The new handler
+ * to use.
+ */
+ setRestartNotificationHandler(handler) {
+ logger.info("Restart notification handler is set");
+ this._restartNotificationHandler = handler;
+ if (this._needRestart) {
+ // Show now using the new handler.
+ this._tryShowRestartNotification();
+ }
+ },
+
+ /**
+ * A promise for any ongoing notification prompt task.
+ *
+ * @type {Promise}
+ */
+ _restartNotificationPromise: null,
+
+ /**
+ * Try show a notification to the user.
+ *
+ * If no notification handler has been attached yet, this will do nothing.
+ */
+ async _tryShowRestartNotification() {
+ if (!this._restartNotificationHandler) {
+ logger.info("Missing a restart notification handler");
+ // This may be added later in the session.
+ return;
+ }
+
+ const prevPromise = this._restartNotificationPromise;
+ let resolve;
+ ({ promise: this._restartNotificationPromise, resolve } =
+ Promise.withResolvers());
+ await prevPromise;
+
+ try {
+ await this._restartNotificationHandler?.tryRestartBrowser();
+ } finally {
+ // Allow the notification to be shown again.
+ resolve();
+ }
+ },
+
+ /**
+ * Mark the session as requiring a restart to apply a change in security
+ * level.
+ *
+ * The security level will immediately be switched to "custom", and the user
+ * may be shown a notification to restart the browser.
+ */
+ requireRestart() {
+ logger.warn("The browser needs to be restarted to set the security level");
+ // Treat as a custom security level for the rest of the session.
+ // At the next startup, the custom flag may be cleared if the settings are
+ // as expected.
+ Services.prefs.setBoolPref(kCustomPref, true);
+ this._needRestart = true;
+
+ // NOTE: We need to change the controlled security level preferences in
+ // response to the desired change in security level. We could either:
+ // 1. Only change the controlled preferences after the user confirms a
+ // restart. Or
+ // 2. Change the controlled preferences and then try and ask the user to
+ // restart.
+ //
+ // We choose the latter:
+ // 1. To allow users to manually restart.
+ // 2. If the user ignores or misses the notification, they will at least be
+ // in the correct state when the browser starts again. Although they will
+ // be in a custom/undefined state in the mean time.
+ // 3. Currently Android relies on triggering the change in security level
+ // by setting the browser.security_level.security_slider preference
+ // value. So it currently uses this path. So we need to set the values
+ // now, before it preforms a restart.
+ // TODO: Have android use the `setSecurityLevelBeforeRestart` method
+ // instead of setting the security_slider preference value directly, so that
+ // it knows exactly when it can restart the browser. tor-browser#43820
+ write_setting_to_prefs(Services.prefs.getIntPref(kSliderPref, 0));
+ // NOTE: Even though we have written the preferences, the session should
+ // still be marked as "custom" because:
+ // 1. Some preferences require a browser restart to be applied.
+ // 2. NoScript has not been updated with the new settings.
+ this._tryShowRestartNotification();
},
}; /* Security Level Prefs */
=====================================
toolkit/locales/en-US/toolkit/global/base-browser.ftl
=====================================
@@ -128,10 +128,6 @@ security-level-toolbar-button-custom =
# Uses sentence case in English (US).
security-level-panel-heading = Security level
-
-security-level-panel-level-standard = Standard
-security-level-panel-level-safer = Safer
-security-level-panel-level-safest = Safest
security-level-panel-learn-more-link = Learn more
# Button to open security level settings.
security-level-panel-open-settings-button = Settings…
@@ -141,6 +137,19 @@ security-level-panel-open-settings-button = Settings…
security-level-preferences-heading = Security Level
security-level-preferences-overview = Disable certain web features that can be used to attack your security and anonymity.
security-level-preferences-learn-more-link = Learn more
+# Text for a badge that labels the currently active security level.
+# The text in between '<span>' and '</span>' should contain some kind of bracket, like '(' and ')', or other punctuation used in your language to separate out text from its surrounding context. This will not be visible, but will be use for screen readers to make it clear that the text is not part of the same sentence. For example, in US English this would be read as "(Current level)", and the full line of text would be read as "Safest (Current level)".
+security-level-preferences-current-badge = <span>(</span>Current level<span>)</span>
+security-level-preferences-change-button = Change…
+
+## Security level settings dialog.
+
+security-level-dialog-window =
+ .title = Change security level
+
+# '-brand-short-name' is the localized browser name, like "Tor Browser".
+security-level-dialog-restart-description = You will need to restart { -brand-short-name } to apply any changes. This will close all windows and tabs.
+
security-level-preferences-level-standard =
.label = Standard
security-level-preferences-level-safer =
@@ -148,6 +157,16 @@ security-level-preferences-level-safer =
security-level-preferences-level-safest =
.label = Safest
+security-level-dialog-save-restart =
+ .label = Save and restart
+
+## Security level names shown in the security panel and settings.
+
+security-level-panel-level-standard = Standard
+security-level-panel-level-safer = Safer
+security-level-panel-level-safest = Safest
+security-level-panel-level-custom = Custom
+
## Security level summaries shown in security panel and settings.
security-level-summary-standard = All browser and website features are enabled.
@@ -166,13 +185,13 @@ security-level-preferences-bullet-limit-font-and-symbols-and-images = Some fonts
## Custom security level.
## Some custom preferences configuration has placed the user outside one of the standard three levels.
-# Shown in the security level panel as an orange badge next to the expected level.
-security-level-panel-custom-badge = Custom
-# Shown in the security level settings in a warning box.
-security-level-preferences-custom-heading = Custom security level configured
# Description of custom state and recommended action.
# Shown in the security level panel and settings.
security-level-summary-custom = 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.
-# Button to undo custom changes to the security level and place the user in one of the standard security levels.
-# Shown in the security level panel and settings.
-security-level-restore-defaults-button = Restore defaults
+
+## Security level restart prompt.
+
+security-level-restart-prompt-title = Your security level settings require a restart
+security-level-restart-prompt-body = You must restart { -brand-short-name } for your security level settings to be applied. This will close all your windows and tabs.
+security-level-restart-prompt-button-restart = Restart
+security-level-restart-prompt-button-ignore = Ignore
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/c2…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/c2…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][base-browser-128.11.0esr-14.5-1] 5 commits: fixup! BB 40925: Implemented the Security Level component
by ma1 (@ma1) 10 Jun '25
by ma1 (@ma1) 10 Jun '25
10 Jun '25
ma1 pushed to branch base-browser-128.11.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits:
f0e5e82c by Henry Wilkes at 2025-06-10T12:36:21+02:00
fixup! BB 40925: Implemented the Security Level component
TB 43783: Prompt user for a restart if their security level preferences
are not aligned at startup or mid-session.
Also handle failures to apply NoScript settings.
- - - - -
c60e8848 by Henry Wilkes at 2025-06-10T12:36:29+02:00
fixup! BB 40069: Add helpers for message passing with extensions
TB 43783: Allow the browser to wait for the NoScript settings to be
applied.
- - - - -
0ac7500f by Henry Wilkes at 2025-06-10T12:36:32+02:00
fixup! Base Browser strings
TB 43783: Add security level prompt strings.
- - - - -
7aa2426e by Henry Wilkes at 2025-06-10T12:36:49+02:00
fixup! Base Browser strings
TB 43782: Add strings for new security level UX flow.
- - - - -
8366be53 by Henry Wilkes at 2025-06-10T12:36:51+02:00
fixup! BB 40925: Implemented the Security Level component
TB 43782: Update security level UI for new UX flow.
In addition, we drop the distinction between the security levels in the
UI when the user has a custom security level.
I.e. we always show shield as unfilled but with a yellow dot in the
toolbar, and we just call it "Custom" rather than "Standard Custom",
etc.
- - - - -
17 changed files:
- browser/components/BrowserGlue.sys.mjs
- + browser/components/securitylevel/SecurityLevelUIUtils.sys.mjs
- browser/components/securitylevel/content/securityLevel.js
- browser/components/securitylevel/content/securityLevelButton.css
- + browser/components/securitylevel/content/securityLevelDialog.js
- + browser/components/securitylevel/content/securityLevelDialog.xhtml
- browser/components/securitylevel/content/securityLevelPanel.css
- browser/components/securitylevel/content/securityLevelPanel.inc.xhtml
- browser/components/securitylevel/content/securityLevelPreferences.css
- browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml
- browser/components/securitylevel/jar.mn
- browser/components/securitylevel/moz.build
- + browser/modules/SecurityLevelRestartNotification.sys.mjs
- browser/modules/moz.build
- toolkit/components/extensions/ExtensionParent.sys.mjs
- toolkit/components/securitylevel/SecurityLevel.sys.mjs
- toolkit/locales/en-US/toolkit/global/base-browser.ftl
Changes:
=====================================
browser/components/BrowserGlue.sys.mjs
=====================================
@@ -82,6 +82,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
ScreenshotsUtils: "resource:///modules/ScreenshotsUtils.sys.mjs",
SearchSERPCategorization: "resource:///modules/SearchSERPTelemetry.sys.mjs",
SearchSERPTelemetry: "resource:///modules/SearchSERPTelemetry.sys.mjs",
+ SecurityLevelRestartNotification:
+ "resource:///modules/SecurityLevelRestartNotification.sys.mjs",
SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
ShellService: "resource:///modules/ShellService.sys.mjs",
@@ -1954,6 +1956,8 @@ BrowserGlue.prototype = {
lazy.DoHController.init();
+ lazy.SecurityLevelRestartNotification.ready();
+
ClipboardPrivacy.startup();
this._firstWindowTelemetry(aWindow);
=====================================
browser/components/securitylevel/SecurityLevelUIUtils.sys.mjs
=====================================
@@ -0,0 +1,73 @@
+/**
+ * Common methods for the desktop security level components.
+ */
+export const SecurityLevelUIUtils = {
+ /**
+ * Create an element that gives a description of the security level. To be
+ * used in the settings.
+ *
+ * @param {string} level - The security level to describe.
+ * @param {Document} doc - The document where the element will be inserted.
+ *
+ * @returns {Element} - The newly created element.
+ */
+ createDescriptionElement(level, doc) {
+ const el = doc.createElement("div");
+ el.classList.add("security-level-description");
+
+ let l10nIdSummary;
+ let bullets;
+ switch (level) {
+ case "standard":
+ l10nIdSummary = "security-level-summary-standard";
+ break;
+ case "safer":
+ l10nIdSummary = "security-level-summary-safer";
+ bullets = [
+ "security-level-preferences-bullet-https-only-javascript",
+ "security-level-preferences-bullet-limit-font-and-symbols",
+ "security-level-preferences-bullet-limit-media",
+ ];
+ break;
+ case "safest":
+ l10nIdSummary = "security-level-summary-safest";
+ bullets = [
+ "security-level-preferences-bullet-disabled-javascript",
+ "security-level-preferences-bullet-limit-font-and-symbols-and-images",
+ "security-level-preferences-bullet-limit-media",
+ ];
+ break;
+ case "custom":
+ l10nIdSummary = "security-level-summary-custom";
+ break;
+ default:
+ throw Error(`Unhandled level: ${level}`);
+ }
+
+ const summaryEl = doc.createElement("div");
+ summaryEl.classList.add("security-level-summary");
+ doc.l10n.setAttributes(summaryEl, l10nIdSummary);
+
+ el.append(summaryEl);
+
+ if (!bullets) {
+ return el;
+ }
+
+ const listEl = doc.createElement("ul");
+ listEl.classList.add("security-level-description-extra");
+ // Add a mozilla styling class as well:
+ listEl.classList.add("privacy-extra-information");
+ for (const l10nId of bullets) {
+ const bulletEl = doc.createElement("li");
+ bulletEl.classList.add("security-level-description-bullet");
+
+ doc.l10n.setAttributes(bulletEl, l10nId);
+
+ listEl.append(bulletEl);
+ }
+
+ el.append(listEl);
+ return el;
+ },
+};
=====================================
browser/components/securitylevel/content/securityLevel.js
=====================================
@@ -1,9 +1,10 @@
"use strict";
-/* global AppConstants, Services, openPreferences, XPCOMUtils */
+/* global AppConstants, Services, openPreferences, XPCOMUtils, gSubDialog */
ChromeUtils.defineESModuleGetters(this, {
SecurityLevelPrefs: "resource://gre/modules/SecurityLevel.sys.mjs",
+ SecurityLevelUIUtils: "resource:///modules/SecurityLevelUIUtils.sys.mjs",
});
/*
@@ -35,12 +36,8 @@ var SecurityLevelButton = {
_anchorButton: null,
_configUIFromPrefs() {
- const level = SecurityLevelPrefs.securityLevel;
- if (!level) {
- return;
- }
- const custom = SecurityLevelPrefs.securityCustom;
- this._button.setAttribute("level", custom ? `${level}_custom` : level);
+ const level = SecurityLevelPrefs.securityLevelSummary;
+ this._button.setAttribute("level", level);
let l10nIdLevel;
switch (level) {
@@ -53,15 +50,12 @@ var SecurityLevelButton = {
case "safest":
l10nIdLevel = "security-level-toolbar-button-safest";
break;
+ case "custom":
+ l10nIdLevel = "security-level-toolbar-button-custom";
+ break;
default:
throw Error(`Unhandled level: ${level}`);
}
- if (custom) {
- // Don't distinguish between the different levels when in the custom
- // state. We just want to emphasise that it is custom rather than any
- // specific level.
- l10nIdLevel = "security-level-toolbar-button-custom";
- }
document.l10n.setAttributes(this._button, l10nIdLevel);
},
@@ -164,12 +158,7 @@ var SecurityLevelPanel = {
panel: document.getElementById("securityLevel-panel"),
background: document.getElementById("securityLevel-background"),
levelName: document.getElementById("securityLevel-level"),
- customName: document.getElementById("securityLevel-custom"),
summary: document.getElementById("securityLevel-summary"),
- restoreDefaultsButton: document.getElementById(
- "securityLevel-restoreDefaults"
- ),
- settingsButton: document.getElementById("securityLevel-settings"),
};
const learnMoreEl = document.getElementById("securityLevel-learnMore");
@@ -177,12 +166,11 @@ var SecurityLevelPanel = {
this.hide();
});
- this._elements.restoreDefaultsButton.addEventListener("command", () => {
- this.restoreDefaults();
- });
- this._elements.settingsButton.addEventListener("command", () => {
- this.openSecuritySettings();
- });
+ document
+ .getElementById("securityLevel-settings")
+ .addEventListener("command", () => {
+ this.openSecuritySettings();
+ });
this._elements.panel.addEventListener("popupshown", () => {
// Bring focus into the panel by focusing the default button.
@@ -199,19 +187,7 @@ var SecurityLevelPanel = {
}
// get security prefs
- const level = SecurityLevelPrefs.securityLevel;
- const custom = SecurityLevelPrefs.securityCustom;
-
- // only visible when user is using custom settings
- this._elements.customName.hidden = !custom;
- this._elements.restoreDefaultsButton.hidden = !custom;
- if (custom) {
- this._elements.settingsButton.removeAttribute("default");
- this._elements.restoreDefaultsButton.setAttribute("default", "true");
- } else {
- this._elements.settingsButton.setAttribute("default", "true");
- this._elements.restoreDefaultsButton.removeAttribute("default");
- }
+ const level = SecurityLevelPrefs.securityLevelSummary;
// Descriptions change based on security level
this._elements.background.setAttribute("level", level);
@@ -230,12 +206,13 @@ var SecurityLevelPanel = {
l10nIdLevel = "security-level-panel-level-safest";
l10nIdSummary = "security-level-summary-safest";
break;
+ case "custom":
+ l10nIdLevel = "security-level-panel-level-custom";
+ l10nIdSummary = "security-level-summary-custom";
+ break;
default:
throw Error(`Unhandled level: ${level}`);
}
- if (custom) {
- l10nIdSummary = "security-level-summary-custom";
- }
document.l10n.setAttributes(this._elements.levelName, l10nIdLevel);
document.l10n.setAttributes(this._elements.summary, l10nIdSummary);
@@ -269,13 +246,6 @@ var SecurityLevelPanel = {
this._elements.panel.hidePopup();
},
- restoreDefaults() {
- SecurityLevelPrefs.securityCustom = false;
- // Move focus to the settings button since restore defaults button will
- // become hidden.
- this._elements.settingsButton.focus();
- },
-
openSecuritySettings() {
openPreferences("privacy-securitylevel");
this.hide();
@@ -301,59 +271,64 @@ var SecurityLevelPanel = {
var SecurityLevelPreferences = {
_securityPrefsBranch: null,
+
/**
- * The notification box shown when the user has a custom security setting.
- *
- * @type {Element}
- */
- _customNotification: null,
- /**
- * The radiogroup for this preference.
- *
- * @type {Element}
- */
- _radiogroup: null,
- /**
- * A list of radio options and their containers.
+ * The element that shows the current security level.
*
- * @type {Array<object>}
+ * @type {?Element}
*/
- _radioOptions: null,
+ _currentEl: null,
_populateXUL() {
- this._customNotification = document.getElementById(
- "securityLevel-customNotification"
+ this._currentEl = document.getElementById("security-level-current");
+ const changeButton = document.getElementById("security-level-change");
+ const badgeEl = this._currentEl.querySelector(
+ ".security-level-current-badge"
);
- document
- .getElementById("securityLevel-restoreDefaults")
- .addEventListener("command", () => {
- SecurityLevelPrefs.securityCustom = false;
- });
-
- this._radiogroup = document.getElementById("securityLevel-radiogroup");
- this._radioOptions = Array.from(
- this._radiogroup.querySelectorAll(".securityLevel-radio-option"),
- container => {
- return { container, radio: container.querySelector("radio") };
- }
- );
+ for (const { level, nameId } of [
+ { level: "standard", nameId: "security-level-panel-level-standard" },
+ { level: "safer", nameId: "security-level-panel-level-safer" },
+ { level: "safest", nameId: "security-level-panel-level-safest" },
+ { level: "custom", nameId: "security-level-panel-level-custom" },
+ ]) {
+ // Classes that control visibility:
+ // security-level-current-standard
+ // security-level-current-safer
+ // security-level-current-safest
+ // security-level-current-custom
+ const visibilityClass = `security-level-current-${level}`;
+ const nameEl = document.createElement("div");
+ nameEl.classList.add("security-level-name", visibilityClass);
+ document.l10n.setAttributes(nameEl, nameId);
+
+ const descriptionEl = SecurityLevelUIUtils.createDescriptionElement(
+ level,
+ document
+ );
+ descriptionEl.classList.add(visibilityClass);
+
+ this._currentEl.insertBefore(nameEl, badgeEl);
+ this._currentEl.insertBefore(descriptionEl, changeButton);
+ }
- this._radiogroup.addEventListener("select", () => {
- SecurityLevelPrefs.securityLevel = this._radiogroup.value;
+ changeButton.addEventListener("click", () => {
+ this._openDialog();
});
},
+ _openDialog() {
+ gSubDialog.open(
+ "chrome://browser/content/securitylevel/securityLevelDialog.xhtml",
+ { features: "resizable=yes" }
+ );
+ },
+
_configUIFromPrefs() {
- this._radiogroup.value = SecurityLevelPrefs.securityLevel;
- const isCustom = SecurityLevelPrefs.securityCustom;
- this._radiogroup.disabled = isCustom;
- this._customNotification.hidden = !isCustom;
- // Have the container's selection CSS class match the selection state of the
- // radio elements.
- for (const { container, radio } of this._radioOptions) {
- container.classList.toggle("selected", radio.selected);
- }
+ // Set a data-current-level attribute for showing the current security
+ // level, and hiding the rest.
+ this._currentEl.dataset.currentLevel =
+ SecurityLevelPrefs.securityLevelSummary;
},
init() {
=====================================
browser/components/securitylevel/content/securityLevelButton.css
=====================================
@@ -7,12 +7,6 @@ toolbarbutton#security-level-button[level="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"] {
+toolbarbutton#security-level-button[level="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");
-}
=====================================
browser/components/securitylevel/content/securityLevelDialog.js
=====================================
@@ -0,0 +1,188 @@
+"use strict";
+
+const { SecurityLevelPrefs } = ChromeUtils.importESModule(
+ "resource://gre/modules/SecurityLevel.sys.mjs"
+);
+const { SecurityLevelUIUtils } = ChromeUtils.importESModule(
+ "resource:///modules/SecurityLevelUIUtils.sys.mjs"
+);
+
+const gSecurityLevelDialog = {
+ /**
+ * The security level when this dialog was opened.
+ *
+ * @type {string}
+ */
+ _prevLevel: SecurityLevelPrefs.securityLevelSummary,
+ /**
+ * The security level currently selected.
+ *
+ * @type {string}
+ */
+ _selectedLevel: "",
+ /**
+ * The radiogroup for this preference.
+ *
+ * @type {?Element}
+ */
+ _radiogroup: null,
+ /**
+ * A list of radio options and their containers.
+ *
+ * @type {?Array<{ container: Element, radio: Element }>}
+ */
+ _radioOptions: null,
+
+ /**
+ * Initialise the dialog.
+ */
+ async init() {
+ const dialog = document.getElementById("security-level-dialog");
+ dialog.addEventListener("dialogaccept", event => {
+ if (this._acceptButton.disabled) {
+ event.preventDefault();
+ return;
+ }
+ this._commitChange();
+ });
+
+ this._acceptButton = dialog.getButton("accept");
+
+ document.l10n.setAttributes(
+ this._acceptButton,
+ "security-level-dialog-save-restart"
+ );
+
+ this._radiogroup = document.getElementById("security-level-radiogroup");
+
+ this._radioOptions = Array.from(
+ this._radiogroup.querySelectorAll(".security-level-radio-container"),
+ container => {
+ return {
+ container,
+ radio: container.querySelector(".security-level-radio"),
+ };
+ }
+ );
+
+ for (const { container, radio } of this._radioOptions) {
+ const level = radio.value;
+ radio.id = `security-level-radio-${level}`;
+ const currentEl = container.querySelector(
+ ".security-level-current-badge"
+ );
+ currentEl.id = `security-level-current-badge-${level}`;
+ const descriptionEl = SecurityLevelUIUtils.createDescriptionElement(
+ level,
+ document
+ );
+ descriptionEl.classList.add("indent");
+ descriptionEl.id = `security-level-description-${level}`;
+
+ // Wait for the full translation of the element before adding it to the
+ // DOM. In particular, we want to make sure the elements have text before
+ // we measure the maxHeight below.
+ await document.l10n.translateFragment(descriptionEl);
+ document.l10n.pauseObserving();
+ container.append(descriptionEl);
+ document.l10n.resumeObserving();
+
+ if (level === this._prevLevel) {
+ currentEl.hidden = false;
+ // When the currentEl is visible, include it in the accessible name for
+ // the radio option.
+ // NOTE: The currentEl has an accessible name which includes punctuation
+ // to help separate it's content from the security level name.
+ // E.g. "Standard (Current level)".
+ radio.setAttribute("aria-labelledby", `${radio.id} ${currentEl.id}`);
+ } else {
+ currentEl.hidden = true;
+ }
+ // We point the accessible description to the wrapping
+ // .security-level-description element, rather than its children
+ // that define the actual text content. This means that when the
+ // privacy-extra-information is shown or hidden, its text content is
+ // included or excluded from the accessible description, respectively.
+ radio.setAttribute("aria-describedby", descriptionEl.id);
+ }
+
+ // We want to reserve the maximum height of the radiogroup so that the
+ // dialog has enough height when the user switches options. So we cycle
+ // through the options and measure the height when they are selected to set
+ // a minimum height that fits all of them.
+ // NOTE: At the time of implementation, at this point the dialog may not
+ // yet have the "subdialog" attribute, which means it is missing the
+ // common.css stylesheet from its shadow root, which effects the size of the
+ // .radio-check element and the font. Therefore, we have duplicated the
+ // import of common.css in SecurityLevelDialog.xhtml to ensure it is applied
+ // at this earlier stage.
+ let maxHeight = 0;
+ for (const { container } of this._radioOptions) {
+ container.classList.add("selected");
+ maxHeight = Math.max(
+ maxHeight,
+ this._radiogroup.getBoundingClientRect().height
+ );
+ container.classList.remove("selected");
+ }
+ this._radiogroup.style.minHeight = `${maxHeight}px`;
+
+ if (this._prevLevel !== "custom") {
+ this._selectedLevel = this._prevLevel;
+ this._radiogroup.value = this._prevLevel;
+ } else {
+ this._radiogroup.selectedItem = null;
+ }
+
+ this._radiogroup.addEventListener("select", () => {
+ this._selectedLevel = this._radiogroup.value;
+ this._updateSelected();
+ });
+
+ this._updateSelected();
+ },
+
+ /**
+ * Update the UI in response to a change in selection.
+ */
+ _updateSelected() {
+ this._acceptButton.disabled =
+ !this._selectedLevel || this._selectedLevel === this._prevLevel;
+ // Have the container's `selected` CSS class match the selection state of
+ // the radio elements.
+ for (const { container, radio } of this._radioOptions) {
+ container.classList.toggle("selected", radio.selected);
+ }
+ },
+
+ /**
+ * Commit the change in security level and restart the browser.
+ */
+ _commitChange() {
+ SecurityLevelPrefs.setSecurityLevelBeforeRestart(this._selectedLevel);
+ Services.startup.quit(
+ Services.startup.eAttemptQuit | Services.startup.eRestart
+ );
+ },
+};
+
+// Initial focus is not visible, even if opened with a keyboard. We avoid the
+// default handler and manage the focus ourselves, which will paint the focus
+// ring by default.
+// NOTE: A side effect is that the focus ring will show even if the user opened
+// with a mouse event.
+// TODO: Remove this once bugzilla bug 1708261 is resolved.
+document.subDialogSetDefaultFocus = () => {
+ document.getElementById("security-level-radiogroup").focus();
+};
+
+// Delay showing and sizing the subdialog until it is fully initialised.
+document.mozSubdialogReady = new Promise(resolve => {
+ window.addEventListener(
+ "DOMContentLoaded",
+ () => {
+ gSecurityLevelDialog.init().finally(resolve);
+ },
+ { once: true }
+ );
+});
=====================================
browser/components/securitylevel/content/securityLevelDialog.xhtml
=====================================
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+
+<window
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ data-l10n-id="security-level-dialog-window"
+>
+ <dialog id="security-level-dialog" buttons="accept,cancel">
+ <linkset>
+ <html:link rel="stylesheet" href="chrome://global/skin/global.css" />
+ <!-- NOTE: We include common.css explicitly, rather than relying on
+ - the dialog's shadowroot importing it, which is late loaded in
+ - response to the dialog's "subdialog" attribute, which is set
+ - in response to DOMFrameContentLoaded.
+ - In particular, we need the .radio-check rule and font rules from
+ - common-shared.css to be in place when gSecurityLevelDialog.init is
+ - called, which will help ensure that the radio element has the correct
+ - size when we measure its bounding box. -->
+ <html:link
+ rel="stylesheet"
+ href="chrome://global/skin/in-content/common.css"
+ />
+ <html:link
+ rel="stylesheet"
+ href="chrome://browser/skin/preferences/preferences.css"
+ />
+ <html:link
+ rel="stylesheet"
+ href="chrome://browser/skin/preferences/privacy.css"
+ />
+ <html:link
+ rel="stylesheet"
+ href="chrome://browser/content/securitylevel/securityLevelPreferences.css"
+ />
+
+ <html:link rel="localization" href="branding/brand.ftl" />
+ <html:link rel="localization" href="toolkit/global/base-browser.ftl" />
+ </linkset>
+
+ <script src="chrome://browser/content/securitylevel/securityLevelDialog.js" />
+
+ <description data-l10n-id="security-level-dialog-restart-description" />
+
+ <radiogroup id="security-level-radiogroup" class="highlighting-group">
+ <html:div
+ class="security-level-radio-container security-level-grid privacy-detailedoption info-box-container"
+ >
+ <radio
+ class="security-level-radio security-level-name"
+ value="standard"
+ data-l10n-id="security-level-preferences-level-standard"
+ />
+ <html:div
+ class="security-level-current-badge"
+ data-l10n-id="security-level-preferences-current-badge"
+ ></html:div>
+ </html:div>
+ <html:div
+ class="security-level-radio-container security-level-grid privacy-detailedoption info-box-container"
+ >
+ <radio
+ class="security-level-radio security-level-name"
+ value="safer"
+ data-l10n-id="security-level-preferences-level-safer"
+ />
+ <html:div
+ class="security-level-current-badge"
+ data-l10n-id="security-level-preferences-current-badge"
+ ></html:div>
+ </html:div>
+ <html:div
+ class="security-level-radio-container security-level-grid privacy-detailedoption info-box-container"
+ >
+ <radio
+ class="security-level-radio security-level-name"
+ value="safest"
+ data-l10n-id="security-level-preferences-level-safest"
+ />
+ <html:div
+ class="security-level-current-badge"
+ data-l10n-id="security-level-preferences-current-badge"
+ ></html:div>
+ </html:div>
+ </radiogroup>
+ </dialog>
+</window>
=====================================
browser/components/securitylevel/content/securityLevelPanel.css
=====================================
@@ -23,7 +23,7 @@
background-position-x: right var(--background-inline-offset);
}
-#securityLevel-background[level="standard"] {
+#securityLevel-background:is([level="standard"], [level="custom"]) {
background-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard");
}
@@ -49,14 +49,6 @@
font-weight: 600;
}
-#securityLevel-custom {
- border-radius: 4px;
- background-color: var(--warning-icon-bgcolor);
- color: black;
- padding: 0.4em 0.5em;
- margin-inline-start: 1em;
-}
-
#securityLevel-summary {
padding-inline-end: 5em;
max-width: 20em;
=====================================
browser/components/securitylevel/content/securityLevelPanel.inc.xhtml
=====================================
@@ -15,10 +15,6 @@
<vbox id="securityLevel-background" class="panel-subview-body">
<html:p id="securityLevel-subheading">
<html:span id="securityLevel-level"></html:span>
- <html:span
- id="securityLevel-custom"
- data-l10n-id="security-level-panel-custom-badge"
- ></html:span>
</html:p>
<html:p id="securityLevel-summary"></html:p>
<html:a
@@ -32,12 +28,8 @@
<button
id="securityLevel-settings"
class="footer-button"
+ default="true"
data-l10n-id="security-level-panel-open-settings-button"
/>
- <button
- id="securityLevel-restoreDefaults"
- class="footer-button"
- data-l10n-id="security-level-restore-defaults-button"
- />
</html:moz-button-group>
</panel>
=====================================
browser/components/securitylevel/content/securityLevelPreferences.css
=====================================
@@ -1,26 +1,166 @@
-#securityLevel-groupbox {
- --section-highlight-background-color: color-mix(in srgb, var(--in-content-accent-color) 20%, transparent);
+.security-level-grid {
+ display: grid;
+ grid-template:
+ "icon name badge button" min-content
+ "icon summary summary button" auto
+ "icon extra extra ." auto
+ / max-content max-content 1fr max-content;
}
-#securityLevel-customNotification {
- /* Spacing similar to #fpiIncompatibilityWarning. */
- margin-block: 16px;
+.security-level-icon {
+ grid-area: icon;
+ align-self: start;
+ width: 24px;
+ height: 24px;
+ -moz-context-properties: fill;
+ fill: var(--in-content-icon-color);
+ margin-block-start: var(--space-xsmall);
+ margin-inline-end: var(--space-large);
}
-.info-icon.securityLevel-custom-warning-icon {
- list-style-image: url("chrome://global/skin/icons/warning.svg");
+.security-level-current-badge {
+ grid-area: badge;
+ align-self: center;
+ justify-self: start;
+ white-space: nowrap;
+ background: var(--background-color-information);
+ color: inherit;
+ font-size: var(--font-size-small);
+ border-radius: var(--border-radius-circle);
+ margin-inline-start: var(--space-small);
+ padding-block: var(--space-xsmall);
+ padding-inline: var(--space-small);
}
-#securityLevel-customHeading {
+.security-level-current-badge span {
+ /* Still accessible to screen reader, but not visual.
+ * Keep inline, but with no layout width. */
+ display: inline-block;
+ width: 1px;
+ margin-inline-end: -1px;
+ clip-path: inset(50%);
+}
+
+@media (prefers-contrast) and (not (forced-colors)) {
+ .security-level-current-badge {
+ /* Match the checkbox/radio colors. */
+ background: var(--color-accent-primary);
+ color: var(--button-text-color-primary);
+ }
+}
+
+@media (forced-colors) {
+ .security-level-current-badge {
+ /* Match the checkbox/radio/selected colors. */
+ background: SelectedItem;
+ color: SelectedItemText;
+ }
+}
+
+.security-level-name {
+ grid-area: name;
font-weight: bold;
+ align-self: center;
+ white-space: nowrap;
+}
+
+.security-level-description {
+ display: grid;
+ grid-column: summary-start / extra-end;
+ grid-row: summary-start / extra-end;
+ grid-template-rows: subgrid;
+ grid-template-columns: subgrid;
+ margin-block-start: var(--space-small);
+}
+
+.security-level-summary {
+ grid-area: summary;
+}
+
+.security-level-description-extra {
+ grid-area: extra;
+ margin-block: var(--space-medium) 0;
+ margin-inline: var(--space-large) 0;
+ padding: 0;
+}
+
+.security-level-description-bullet:not(:last-child) {
+ margin-block-end: var(--space-medium);
+}
+
+/* Tweak current security level display. */
+
+#security-level-current {
+ margin-block-start: var(--space-large);
+ background: var(--in-content-box-background);
+ border: 1px solid var(--in-content-box-border-color);
+ border-radius: var(--border-radius-small);
+ padding: var(--space-medium);
+}
+
+#security-level-change {
+ grid-area: button;
+ align-self: center;
+ margin: 0;
+ margin-inline-start: var(--space-large);
+}
+
+/* Adjust which content is visible depending on the current security level. */
+
+#security-level-current:not([data-current-level="standard"]) .security-level-current-standard {
+ display: none;
+}
+
+#security-level-current:not([data-current-level="safer"]) .security-level-current-safer {
+ display: none;
+}
+
+#security-level-current:not([data-current-level="safest"]) .security-level-current-safest {
+ display: none;
+}
+
+#security-level-current:not([data-current-level="custom"]) .security-level-current-custom {
+ display: none;
+}
+
+#security-level-current[data-current-level="standard"] .security-level-icon {
+ content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard");
+}
+
+#security-level-current[data-current-level="safer"] .security-level-icon {
+ content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safer");
+}
+
+#security-level-current[data-current-level="safest"] .security-level-icon {
+ content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safest");
+}
+
+#security-level-current[data-current-level="custom"] .security-level-icon {
+ content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard_custom");
+}
+
+/* Tweak security level dialog. */
+
+#security-level-radiogroup {
+ margin-block: var(--space-large) var(--space-xlarge);
+}
+
+.security-level-radio-container {
+ padding-block: var(--space-large);
+}
+
+#security-level-radiogroup .security-level-radio {
+ margin: 0;
}
-#securityLevel-radiogroup[disabled] {
- opacity: 0.5;
+#security-level-radiogroup .radio-label-box {
+ /* .security-level-current-badge already has a margin. */
+ margin: 0;
}
-/* Overwrite the rule in common-shared.css so we don't get 0.25 opacity overall
- * on the radio text. */
-#securityLevel-radiogroup[disabled] radio[disabled] .radio-label-box {
- opacity: 1.0;
+#security-level-radiogroup .privacy-detailedoption.security-level-radio-container:not(.selected) .security-level-description-extra {
+ /* .privacy-detailedoption uses visibility: hidden, which does not work with
+ * our grid display (the margin is still reserved) so we use display: none
+ * instead. */
+ display: none;
}
=====================================
browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml
=====================================
@@ -17,112 +17,19 @@
data-l10n-id="security-level-preferences-learn-more-link"
></html:a>
</description>
- <hbox
- id="securityLevel-customNotification"
- class="info-box-container"
- flex="1"
- >
- <hbox class="info-icon-container">
- <image class="info-icon securityLevel-custom-warning-icon"/>
- </hbox>
- <vbox flex="1">
- <label
- id="securityLevel-customHeading"
- data-l10n-id="security-level-preferences-custom-heading"
- />
- <description
- id="securityLevel-customDescription"
- data-l10n-id="security-level-summary-custom"
- flex="1"
- />
- </vbox>
- <hbox align="center">
- <button
- id="securityLevel-restoreDefaults"
- data-l10n-id="security-level-restore-defaults-button"
- />
- </hbox>
- </hbox>
- <radiogroup id="securityLevel-radiogroup">
- <vbox class="securityLevel-radio-option privacy-detailedoption info-box-container">
- <radio
- value="standard"
- data-l10n-id="security-level-preferences-level-standard"
- aria-describedby="securityLevelSummary-standard"
- />
- <vbox id="securityLevelSummary-standard" class="indent">
- <label data-l10n-id="security-level-summary-standard" />
- </vbox>
- </vbox>
- <vbox class="securityLevel-radio-option privacy-detailedoption info-box-container">
- <!-- NOTE: We point the accessible description to the wrapping vbox
- - rather than its first description element. This means that when the
- - privacy-extra-information is shown or hidden, its text content is
- - included or excluded from the accessible description, respectively.
- -->
- <radio
- value="safer"
- data-l10n-id="security-level-preferences-level-safer"
- aria-describedby="securityLevelSummary-safer"
- />
- <vbox id="securityLevelSummary-safer" class="indent">
- <label data-l10n-id="security-level-summary-safer" />
- <vbox class="privacy-extra-information">
- <vbox class="indent">
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-https-only-javascript"
- />
- </hbox>
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-limit-font-and-symbols"
- />
- </hbox>
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-limit-media"
- />
- </hbox>
- </vbox>
- </vbox>
- </vbox>
- </vbox>
- <vbox class="securityLevel-radio-option privacy-detailedoption info-box-container">
- <radio
- value="safest"
- data-l10n-id="security-level-preferences-level-safest"
- aria-describedby="securityLevelSummary-safest"
- />
- <vbox id="securityLevelSummary-safest" class="indent">
- <label data-l10n-id="security-level-summary-safest" />
- <vbox class="privacy-extra-information">
- <vbox class="indent">
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-disabled-javascript"
- />
- </hbox>
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-limit-font-and-symbols-and-images"
- />
- </hbox>
- <hbox class="extra-information-label">
- <label
- class="content-blocking-label"
- data-l10n-id="security-level-preferences-bullet-limit-media"
- />
- </hbox>
- </vbox>
- </vbox>
- </vbox>
- </vbox>
- </radiogroup>
+ <html:div id="security-level-current" class="security-level-grid">
+ <html:img
+ class="security-level-icon"
+ alt=""
+ />
+ <html:div
+ class="security-level-current-badge"
+ data-l10n-id="security-level-preferences-current-badge"
+ ></html:div>
+ <html:button
+ id="security-level-change"
+ data-l10n-id="security-level-preferences-change-button"
+ ></html:button>
+ </html:div>
</vbox>
</groupbox>
=====================================
browser/components/securitylevel/jar.mn
=====================================
@@ -4,3 +4,5 @@ browser.jar:
content/browser/securitylevel/securityLevelButton.css (content/securityLevelButton.css)
content/browser/securitylevel/securityLevelPreferences.css (content/securityLevelPreferences.css)
content/browser/securitylevel/securityLevelIcon.svg (content/securityLevelIcon.svg)
+ content/browser/securitylevel/securityLevelDialog.xhtml (content/securityLevelDialog.xhtml)
+ content/browser/securitylevel/securityLevelDialog.js (content/securityLevelDialog.js)
=====================================
browser/components/securitylevel/moz.build
=====================================
@@ -1 +1,5 @@
JAR_MANIFESTS += ["jar.mn"]
+
+EXTRA_JS_MODULES += [
+ "SecurityLevelUIUtils.sys.mjs",
+]
=====================================
browser/modules/SecurityLevelRestartNotification.sys.mjs
=====================================
@@ -0,0 +1,72 @@
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
+ SecurityLevelPrefs: "resource://gre/modules/SecurityLevel.sys.mjs",
+});
+
+ChromeUtils.defineLazyGetter(lazy, "NotificationStrings", function () {
+ return new Localization([
+ "branding/brand.ftl",
+ "toolkit/global/base-browser.ftl",
+ ]);
+});
+
+/**
+ * Interface for showing the security level restart notification on desktop.
+ */
+export const SecurityLevelRestartNotification = {
+ /**
+ * Whether we have already been initialised.
+ *
+ * @type {boolean}
+ */
+ _initialized: false,
+
+ /**
+ * Called when the UI is ready to show a notification.
+ */
+ ready() {
+ if (this._initialized) {
+ return;
+ }
+ this._initialized = true;
+ lazy.SecurityLevelPrefs.setRestartNotificationHandler(this);
+ },
+
+ /**
+ * Show the restart notification, and perform the restart if the user agrees.
+ */
+ async tryRestartBrowser() {
+ const [titleText, bodyText, primaryButtonText, secondaryButtonText] =
+ await lazy.NotificationStrings.formatValues([
+ { id: "security-level-restart-prompt-title" },
+ { id: "security-level-restart-prompt-body" },
+ { id: "security-level-restart-prompt-button-restart" },
+ { id: "security-level-restart-prompt-button-ignore" },
+ ]);
+ const buttonFlags =
+ Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
+ Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1;
+
+ const propBag = await Services.prompt.asyncConfirmEx(
+ lazy.BrowserWindowTracker.getTopWindow()?.browsingContext ?? null,
+ Services.prompt.MODAL_TYPE_INTERNAL_WINDOW,
+ titleText,
+ bodyText,
+ buttonFlags,
+ primaryButtonText,
+ secondaryButtonText,
+ null,
+ null,
+ null,
+ {}
+ );
+
+ if (propBag.get("buttonNumClicked") === 0) {
+ Services.startup.quit(
+ Services.startup.eAttemptQuit | Services.startup.eRestart
+ );
+ }
+ },
+};
=====================================
browser/modules/moz.build
=====================================
@@ -126,6 +126,7 @@ EXTRA_JS_MODULES += [
"PermissionUI.sys.mjs",
"ProcessHangMonitor.sys.mjs",
"Sanitizer.sys.mjs",
+ "SecurityLevelRestartNotification.sys.mjs",
"SelectionChangedMenulist.sys.mjs",
"SiteDataManager.sys.mjs",
"SitePermissions.sys.mjs",
=====================================
toolkit/components/extensions/ExtensionParent.sys.mjs
=====================================
@@ -2345,6 +2345,7 @@ async function torSendExtensionMessage(extensionId, message) {
const result = await ProxyMessenger.conduit.castRuntimeMessage("messenger", {
extensionId,
holder: new StructuredCloneHolder("torSendExtensionMessage", null, message),
+ query: true,
firstResponse: true,
sender: {
id: extensionId,
=====================================
toolkit/components/securitylevel/SecurityLevel.sys.mjs
=====================================
@@ -16,6 +16,7 @@ const BrowserTopics = Object.freeze({
// The Security Settings prefs in question.
const kSliderPref = "browser.security_level.security_slider";
const kCustomPref = "browser.security_level.security_custom";
+const kNoScriptInitedPref = "browser.security_level.noscript_inited";
// __getPrefValue(prefName)__
// Returns the current value of a preference, regardless of its type.
@@ -32,11 +33,11 @@ var getPrefValue = function (prefName) {
}
};
-// __bindPref(prefName, prefHandler, init)__
+// __bindPref(prefName, prefHandler)__
// 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) {
+// Returns the observer that was added.
+var bindPref = function (prefName, prefHandler) {
let update = () => {
prefHandler(getPrefValue(prefName));
},
@@ -48,21 +49,9 @@ var bindPref = function (prefName, prefHandler, init = false) {
},
};
Services.prefs.addObserver(prefName, observer);
- if (init) {
- update();
- }
- return () => {
- Services.prefs.removeObserver(prefName, observer);
- };
+ return 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 } = lazy.ExtensionParent;
if (torWaitForExtensionMessage) {
@@ -74,7 +63,7 @@ async function waitForExtensionMessage(extensionId, checker = () => {}) {
async function sendExtensionMessage(extensionId, message) {
const { torSendExtensionMessage } = lazy.ExtensionParent;
if (torSendExtensionMessage) {
- return torSendExtensionMessage(extensionId, message);
+ return await torSendExtensionMessage(extensionId, message);
}
return undefined;
}
@@ -187,14 +176,8 @@ var initializeNoScriptControl = () => {
// `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));
+ let sendNoScriptSettings = async settings =>
+ await sendExtensionMessage(noscriptID, settings);
// __securitySliderToSafetyLevel(sliderState)__.
// Converts the "browser.security_level.security_slider" pref value
@@ -204,36 +187,46 @@ var initializeNoScriptControl = () => {
// Wait for the first message from NoScript to arrive, and then
// bind the security_slider pref to the NoScript settings.
- let messageListener = a => {
+ let messageListener = async a => {
try {
logger.debug("Message received from NoScript:", a);
- let noscriptPersist = Services.prefs.getBoolPref(
- "browser.security_level.noscript_persist",
- false
- );
+ const persistPref = "browser.security_level.noscript_persist";
+ let noscriptPersist = Services.prefs.getBoolPref(persistPref, false);
let noscriptInited = Services.prefs.getBoolPref(
- "browser.security_level.noscript_inited",
+ kNoScriptInitedPref,
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
+ // Set the noscript safety level once at startup.
+ // If a user has set noscriptPersist, then we only send this if the
+ // security level was changed in a previous session.
+ // NOTE: We do not re-send this when the security_slider preference
+ // changes mid-session because this should always require a restart.
+ if (noscriptPersist && noscriptInited) {
+ logger.warn(
+ `Not initialising NoScript since the user has set ${persistPref}`
);
+ return;
}
+ // Read the security level, even if the user has the "custom"
+ // preference.
+ const securityIndex = Services.prefs.getIntPref(kSliderPref, 0);
+ const safetyLevel = securitySliderToSafetyLevel(securityIndex);
+ // May throw if NoScript fails to apply the settings:
+ const noscriptResult = await sendNoScriptSettings(
+ noscriptSettings(safetyLevel)
+ );
+ // Mark the NoScript extension as initialised so we do not reset it
+ // at the next startup for noscript_persist users.
+ Services.prefs.setBoolPref(kNoScriptInitedPref, true);
+ logger.info("NoScript successfully initialised.");
+ // In the future NoScript may tell us more about how it applied our
+ // settings, e.g. if user is overriding per-site permissions.
+ // Up to NoScript 12.6 noscriptResult is undefined.
+ logger.debug("NoScript response:", noscriptResult);
} catch (e) {
- logger.exception(e);
+ logger.error("Could not apply NoScript settings", e);
+ // Treat as a custom security level for the rest of the session.
+ Services.prefs.setBoolPref(kCustomPref, true);
}
};
waitForExtensionMessage(noscriptID, a => a.__meta.name === "started").then(
@@ -242,6 +235,8 @@ var initializeNoScriptControl = () => {
logger.info("Listening for messages from NoScript.");
} catch (e) {
logger.exception(e);
+ // Treat as a custom security level for the rest of the session.
+ Services.prefs.setBoolPref(kCustomPref, true);
}
};
@@ -271,16 +266,60 @@ const kSecuritySettings = {
// ### Prefs
+/**
+ * Amend the security level index to a standard value.
+ *
+ * @param {integer} index - The input index value.
+ * @returns {integer} - A standard index value.
+ */
+function fixupIndex(index) {
+ if (!Number.isInteger(index) || index < 1 || index > 4) {
+ // Unexpected value out of range, go to the "safest" level as a fallback.
+ return 1;
+ }
+ if (index === 3) {
+ // Migrate from old medium-low (3) to new medium (2).
+ return 2;
+ }
+ return index;
+}
+
+/**
+ * A list of preference observers that should be disabled whilst we write our
+ * preference values.
+ *
+ * @type {{ prefName: string, observer: object }[]}
+ */
+const prefObservers = [];
+
// __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]
- )
- );
+ settingIndex = fixupIndex(settingIndex);
+ // Don't want to trigger our internal observers when setting ourselves.
+ for (const { prefName, observer } of prefObservers) {
+ Services.prefs.removeObserver(prefName, observer);
+ }
+ try {
+ // Make sure noscript is re-initialised at the next startup when the
+ // security level changes.
+ Services.prefs.setBoolPref(kNoScriptInitedPref, false);
+ Services.prefs.setIntPref(kSliderPref, settingIndex);
+ // NOTE: We do not clear kCustomPref. Instead, we rely on the preference
+ // being cleared on the next startup.
+ Object.keys(kSecuritySettings).forEach(prefName =>
+ Services.prefs.setBoolPref(
+ prefName,
+ kSecuritySettings[prefName][settingIndex]
+ )
+ );
+ } finally {
+ // Re-add the observers.
+ for (const { prefName, observer } of prefObservers) {
+ Services.prefs.addObserver(prefName, observer);
+ }
+ }
};
// __read_setting_from_prefs()__.
@@ -309,24 +348,6 @@ var read_setting_from_prefs = function (prefNames) {
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;
@@ -342,36 +363,82 @@ var initializeSecurityPrefs = function () {
}
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) {
+
+ const wasCustom = Services.prefs.getBoolPref(kCustomPref, false);
+ // For new profiles with no user preference, the security level should be "4"
+ // and it should not be custom.
+ let desiredIndex = Services.prefs.getIntPref(kSliderPref, 4);
+ desiredIndex = fixupIndex(desiredIndex);
+ // Make sure the user has a set preference user value.
+ Services.prefs.setIntPref(kSliderPref, desiredIndex);
+ Services.prefs.setBoolPref(kCustomPref, wasCustom);
+
+ // Make sure that the preference values at application startup match the
+ // expected values for the desired security level. See tor-browser#43783.
+
+ // NOTE: We assume that the controlled preference values that are read prior
+ // to profile-after-change do not change in value before this method is
+ // called. I.e. we expect the current preference values to match the
+ // preference values that were used during the application initialisation.
+ const effectiveIndex = read_setting_from_prefs();
+
+ if (wasCustom && effectiveIndex !== null) {
+ logger.info(`Custom startup values match index ${effectiveIndex}`);
+ // Do not consider custom any more.
+ // NOTE: This level needs to be set before it is read elsewhere. In
+ // particular, for the NoScript addon.
Services.prefs.setBoolPref(kCustomPref, false);
- write_setting_to_prefs(prefIndex);
+ Services.prefs.setIntPref(kSliderPref, effectiveIndex);
+ } else if (!wasCustom && effectiveIndex !== desiredIndex) {
+ // NOTE: We assume all our controlled preferences require a restart.
+ // In practice, only a subset of these preferences may actually require a
+ // restart, so we could switch their values. But we treat them all the same
+ // for simplicity, consistency and stability in case mozilla changes the
+ // restart requirements.
+ logger.info(`Startup values do not match for index ${desiredIndex}`);
+ SecurityLevelPrefs.requireRestart();
+ }
+
+ // Start listening for external changes to the controlled preferences.
+ prefObservers.push({
+ prefName: kCustomPref,
+ observer: bindPref(kCustomPref, custom => {
+ // Custom flag was removed mid-session. Requires a restart to apply the
+ // security level.
+ if (custom === false) {
+ logger.info("Custom flag was cleared externally");
+ SecurityLevelPrefs.requireRestart();
+ }
+ }),
});
- // 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);
- }
+ prefObservers.push({
+ prefName: kSliderPref,
+ observer: bindPref(kSliderPref, () => {
+ // Security level was changed mid-session. Requires a restart to apply.
+ logger.info("Security level was changed externally");
+ SecurityLevelPrefs.requireRestart();
+ }),
});
- // 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);
+
+ for (const prefName of Object.keys(kSecuritySettings)) {
+ prefObservers.push({
+ prefName,
+ observer: bindPref(prefName, () => {
+ logger.warn(
+ `The controlled preference ${prefName} was changed externally.` +
+ " Treating as a custom security level."
+ );
+ // Something outside of this module changed the preference value for a
+ // preference we control.
+ // Always treat as a custom security level for the rest of this session,
+ // even if the new preference values match a pre-set security level. We
+ // do this because some controlled preferences require a restart to be
+ // properly applied. See tor-browser#43783.
+ // In the case where it does match a pre-set security level, the custom
+ // flag will be cleared at the next startup.
+ Services.prefs.setBoolPref(kCustomPref, true);
+ }),
+ });
}
logger.info("security-prefs.js initialization complete");
@@ -425,8 +492,9 @@ export class SecurityLevel {
init() {
migratePreferences();
- initializeNoScriptControl();
+ // Fixup our preferences before we pass on the security level to NoScript.
initializeSecurityPrefs();
+ initializeNoScriptControl();
}
observe(aSubject, aTopic) {
@@ -436,10 +504,19 @@ export class SecurityLevel {
}
}
+/**
+ * @typedef {object} SecurityLevelRestartNotificationHandler
+ *
+ * An object that can serve the user a restart notification.
+ *
+ * @property {Function} tryRestartBrowser - The method that should be called to
+ * ask the user to restart the browser.
+ */
+
/*
Security Level Prefs
- Getters and Setters for relevant torbutton prefs
+ Getters and Setters for relevant security level prefs
*/
export const SecurityLevelPrefs = {
SecurityLevels: Object.freeze({
@@ -450,6 +527,14 @@ export const SecurityLevelPrefs = {
security_slider_pref: "browser.security_level.security_slider",
security_custom_pref: "browser.security_level.security_custom",
+ /**
+ * The current security level preference.
+ *
+ * This ignores any custom settings the user may have changed, and just
+ * gives the underlying security level.
+ *
+ * @type {?string}
+ */
get securityLevel() {
// Set the default return value to 0, which won't match anything in
// SecurityLevels.
@@ -459,18 +544,146 @@ export const SecurityLevelPrefs = {
)?.[0];
},
- set securityLevel(level) {
- const val = this.SecurityLevels[level];
- if (val !== undefined) {
- Services.prefs.setIntPref(this.security_slider_pref, val);
- }
+ /**
+ * Set the desired security level just before a restart.
+ *
+ * The caller must restart the browser after calling this method.
+ *
+ * @param {string} level - The name of the new security level to set.
+ */
+ setSecurityLevelBeforeRestart(level) {
+ write_setting_to_prefs(this.SecurityLevels[level]);
},
+ /**
+ * Whether the user has any custom setting values that do not match a pre-set
+ * security level.
+ *
+ * @type {boolean}
+ */
get securityCustom() {
return Services.prefs.getBoolPref(this.security_custom_pref);
},
- set securityCustom(val) {
- Services.prefs.setBoolPref(this.security_custom_pref, val);
+ /**
+ * A summary of the current security level.
+ *
+ * If the user has some custom settings, this returns "custom". Otherwise
+ * returns the name of the security level.
+ *
+ * @type {string}
+ */
+ get securityLevelSummary() {
+ if (this.securityCustom) {
+ return "custom";
+ }
+ return this.securityLevel ?? "custom";
+ },
+
+ /**
+ * Whether the browser should be restarted to apply the security level.
+ *
+ * @type {boolean}
+ */
+ _needRestart: false,
+
+ /**
+ * The external handler that can show a notification to the user, if any.
+ *
+ * @type {?SecurityLevelRestartNotificationHandler}
+ */
+ _restartNotificationHandler: null,
+
+ /**
+ * Set the external handler for showing notifications to the user.
+ *
+ * This should only be called once per session once the handler is ready to
+ * show a notification, which may occur immediately during this call.
+ *
+ * @param {SecurityLevelRestartNotificationHandler} handler - The new handler
+ * to use.
+ */
+ setRestartNotificationHandler(handler) {
+ logger.info("Restart notification handler is set");
+ this._restartNotificationHandler = handler;
+ if (this._needRestart) {
+ // Show now using the new handler.
+ this._tryShowRestartNotification();
+ }
+ },
+
+ /**
+ * A promise for any ongoing notification prompt task.
+ *
+ * @type {Promise}
+ */
+ _restartNotificationPromise: null,
+
+ /**
+ * Try show a notification to the user.
+ *
+ * If no notification handler has been attached yet, this will do nothing.
+ */
+ async _tryShowRestartNotification() {
+ if (!this._restartNotificationHandler) {
+ logger.info("Missing a restart notification handler");
+ // This may be added later in the session.
+ return;
+ }
+
+ const prevPromise = this._restartNotificationPromise;
+ let resolve;
+ ({ promise: this._restartNotificationPromise, resolve } =
+ Promise.withResolvers());
+ await prevPromise;
+
+ try {
+ await this._restartNotificationHandler?.tryRestartBrowser();
+ } finally {
+ // Allow the notification to be shown again.
+ resolve();
+ }
+ },
+
+ /**
+ * Mark the session as requiring a restart to apply a change in security
+ * level.
+ *
+ * The security level will immediately be switched to "custom", and the user
+ * may be shown a notification to restart the browser.
+ */
+ requireRestart() {
+ logger.warn("The browser needs to be restarted to set the security level");
+ // Treat as a custom security level for the rest of the session.
+ // At the next startup, the custom flag may be cleared if the settings are
+ // as expected.
+ Services.prefs.setBoolPref(kCustomPref, true);
+ this._needRestart = true;
+
+ // NOTE: We need to change the controlled security level preferences in
+ // response to the desired change in security level. We could either:
+ // 1. Only change the controlled preferences after the user confirms a
+ // restart. Or
+ // 2. Change the controlled preferences and then try and ask the user to
+ // restart.
+ //
+ // We choose the latter:
+ // 1. To allow users to manually restart.
+ // 2. If the user ignores or misses the notification, they will at least be
+ // in the correct state when the browser starts again. Although they will
+ // be in a custom/undefined state in the mean time.
+ // 3. Currently Android relies on triggering the change in security level
+ // by setting the browser.security_level.security_slider preference
+ // value. So it currently uses this path. So we need to set the values
+ // now, before it preforms a restart.
+ // TODO: Have android use the `setSecurityLevelBeforeRestart` method
+ // instead of setting the security_slider preference value directly, so that
+ // it knows exactly when it can restart the browser. tor-browser#43820
+ write_setting_to_prefs(Services.prefs.getIntPref(kSliderPref, 0));
+ // NOTE: Even though we have written the preferences, the session should
+ // still be marked as "custom" because:
+ // 1. Some preferences require a browser restart to be applied.
+ // 2. NoScript has not been updated with the new settings.
+ this._tryShowRestartNotification();
},
}; /* Security Level Prefs */
=====================================
toolkit/locales/en-US/toolkit/global/base-browser.ftl
=====================================
@@ -128,10 +128,6 @@ security-level-toolbar-button-custom =
# Uses sentence case in English (US).
security-level-panel-heading = Security level
-
-security-level-panel-level-standard = Standard
-security-level-panel-level-safer = Safer
-security-level-panel-level-safest = Safest
security-level-panel-learn-more-link = Learn more
# Button to open security level settings.
security-level-panel-open-settings-button = Settings…
@@ -141,6 +137,19 @@ security-level-panel-open-settings-button = Settings…
security-level-preferences-heading = Security Level
security-level-preferences-overview = Disable certain web features that can be used to attack your security and anonymity.
security-level-preferences-learn-more-link = Learn more
+# Text for a badge that labels the currently active security level.
+# The text in between '<span>' and '</span>' should contain some kind of bracket, like '(' and ')', or other punctuation used in your language to separate out text from its surrounding context. This will not be visible, but will be use for screen readers to make it clear that the text is not part of the same sentence. For example, in US English this would be read as "(Current level)", and the full line of text would be read as "Safest (Current level)".
+security-level-preferences-current-badge = <span>(</span>Current level<span>)</span>
+security-level-preferences-change-button = Change…
+
+## Security level settings dialog.
+
+security-level-dialog-window =
+ .title = Change security level
+
+# '-brand-short-name' is the localized browser name, like "Tor Browser".
+security-level-dialog-restart-description = You will need to restart { -brand-short-name } to apply any changes. This will close all windows and tabs.
+
security-level-preferences-level-standard =
.label = Standard
security-level-preferences-level-safer =
@@ -148,6 +157,16 @@ security-level-preferences-level-safer =
security-level-preferences-level-safest =
.label = Safest
+security-level-dialog-save-restart =
+ .label = Save and restart
+
+## Security level names shown in the security panel and settings.
+
+security-level-panel-level-standard = Standard
+security-level-panel-level-safer = Safer
+security-level-panel-level-safest = Safest
+security-level-panel-level-custom = Custom
+
## Security level summaries shown in security panel and settings.
security-level-summary-standard = All browser and website features are enabled.
@@ -166,13 +185,13 @@ security-level-preferences-bullet-limit-font-and-symbols-and-images = Some fonts
## Custom security level.
## Some custom preferences configuration has placed the user outside one of the standard three levels.
-# Shown in the security level panel as an orange badge next to the expected level.
-security-level-panel-custom-badge = Custom
-# Shown in the security level settings in a warning box.
-security-level-preferences-custom-heading = Custom security level configured
# Description of custom state and recommended action.
# Shown in the security level panel and settings.
security-level-summary-custom = 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.
-# Button to undo custom changes to the security level and place the user in one of the standard security levels.
-# Shown in the security level panel and settings.
-security-level-restore-defaults-button = Restore defaults
+
+## Security level restart prompt.
+
+security-level-restart-prompt-title = Your security level settings require a restart
+security-level-restart-prompt-body = You must restart { -brand-short-name } for your security level settings to be applied. This will close all your windows and tabs.
+security-level-restart-prompt-button-restart = Restart
+security-level-restart-prompt-button-ignore = Ignore
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/4d4584…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/4d4584…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/mullvad-browser][mullvad-browser-140.0a1-15.0-1] fixup! Firefox preference overrides.
by Pier Angelo Vendrame (@pierov) 10 Jun '25
by Pier Angelo Vendrame (@pierov) 10 Jun '25
10 Jun '25
Pier Angelo Vendrame pushed to branch mullvad-browser-140.0a1-15.0-1 at The Tor Project / Applications / Mullvad Browser
Commits:
500300ab by hackademix at 2025-06-10T11:12:21+02:00
fixup! Firefox preference overrides.
BB 43811: Block 0.0.0.0
- - - - -
1 changed file:
- browser/app/profile/001-base-profile.js
Changes:
=====================================
browser/app/profile/001-base-profile.js
=====================================
@@ -536,6 +536,11 @@ pref("network.proxy.failover_direct", false, locked);
// alters content load order in a page. See tor-browser#24686
pref("network.http.tailing.enabled", true, locked);
+// Block 0.0.0.0
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1889130
+// tor-browser#43811
+pref("network.socket.ip_addr_any.disabled", true);
+
// tor-browser#23044: Make sure we don't have any GIO supported protocols
// (defense in depth measure).
// As of Firefox 118 (Bug 1843763), upstream does not add any protocol by
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/500…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/500…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][base-browser-140.0a1-15.0-1] fixup! Firefox preference overrides.
by Pier Angelo Vendrame (@pierov) 10 Jun '25
by Pier Angelo Vendrame (@pierov) 10 Jun '25
10 Jun '25
Pier Angelo Vendrame pushed to branch base-browser-140.0a1-15.0-1 at The Tor Project / Applications / Tor Browser
Commits:
aec824b3 by hackademix at 2025-06-10T11:12:00+02:00
fixup! Firefox preference overrides.
BB 43811: Block 0.0.0.0
- - - - -
1 changed file:
- browser/app/profile/001-base-profile.js
Changes:
=====================================
browser/app/profile/001-base-profile.js
=====================================
@@ -536,6 +536,11 @@ pref("network.proxy.failover_direct", false, locked);
// alters content load order in a page. See tor-browser#24686
pref("network.http.tailing.enabled", true, locked);
+// Block 0.0.0.0
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1889130
+// tor-browser#43811
+pref("network.socket.ip_addr_any.disabled", true);
+
// tor-browser#23044: Make sure we don't have any GIO supported protocols
// (defense in depth measure).
// As of Firefox 118 (Bug 1843763), upstream does not add any protocol by
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/aec824b…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/aec824b…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-140.0a1-15.0-1] 3 commits: fixup! [android] Modify build system
by Pier Angelo Vendrame (@pierov) 10 Jun '25
by Pier Angelo Vendrame (@pierov) 10 Jun '25
10 Jun '25
Pier Angelo Vendrame pushed to branch tor-browser-140.0a1-15.0-1 at The Tor Project / Applications / Tor Browser
Commits:
cdecf99e by Dan Ballard at 2025-06-10T11:10:58+02:00
fixup! [android] Modify build system
Bug 43809: allow tba-fetch-deps to download from nightlies
- - - - -
4eec3120 by Dan Ballard at 2025-06-10T11:11:07+02:00
fixup! TB 42669: [android] Use custom no-op app-services
Bug 43809: allow tba-fetch-deps to download nightlies
- - - - -
469928c7 by hackademix at 2025-06-10T11:11:13+02:00
fixup! Firefox preference overrides.
BB 43811: Block 0.0.0.0
- - - - -
2 changed files:
- browser/app/profile/001-base-profile.js
- mobile/android/fenix/tools/tba-fetch-deps.sh
Changes:
=====================================
browser/app/profile/001-base-profile.js
=====================================
@@ -536,6 +536,11 @@ pref("network.proxy.failover_direct", false, locked);
// alters content load order in a page. See tor-browser#24686
pref("network.http.tailing.enabled", true, locked);
+// Block 0.0.0.0
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1889130
+// tor-browser#43811
+pref("network.socket.ip_addr_any.disabled", true);
+
// tor-browser#23044: Make sure we don't have any GIO supported protocols
// (defense in depth measure).
// As of Firefox 118 (Bug 1843763), upstream does not add any protocol by
=====================================
mobile/android/fenix/tools/tba-fetch-deps.sh
=====================================
@@ -1,24 +1,55 @@
#!/bin/bash
+if [ $# -eq 0 ]; then
+ echo "Usage: ./tba-fetch-deps.sh --\$MODE"
+ echo " modes:"
+ echo " --nightly Downloads the needed assets from the nightlies build server. Use when local version matches nightly build server version."
+ echo " --tbb PATH Harvest most recently built assets from PATH assuming it points to a tor-browser-build dir. Use when local version does NOT match nightly build server version."
+ exit -1
+fi
+
+TBB_BUILD_06="https://tb-build-06.torproject.org/~tb-builder/tor-browser-build/out"
+
+if [[ $1 == "--tbb" && -z $2 ]]; then
+ echo "--tbb needs path to tor-browser-build dir"
+ exit -1
+fi
+TBB_PATH=$2
+
cd "$(dirname $(realpath "$0"))/.."
if [ -z "$TOR_BROWSER_BUILD" ]; then
TOR_BROWSER_BUILD=../../../../tor-browser-build
fi
-tor_expert_bundle_aar="$(ls -1td "$TOR_BROWSER_BUILD/out/tor-expert-bundle-aar/"tor-expert-bundle-aar-* | head -1)"
-if [ -z "tor_expert_bundle_aar" ]; then
+echo "Fetching tor-expert-bundle.aar..."
+
+if [[ $1 == "--tbb" ]]; then
+ tor_expert_bundle_aar="$(ls -1td "$TOR_BROWSER_BUILD/out/tor-expert-bundle-aar/"tor-expert-bundle-aar-* | head -1)"
+ cp "$tor_expert_bundle_aar"/* app/
+else
+ tor_expert_bundle_aar_dirname="$(curl -s $TBB_BUILD_06/tor-expert-bundle-aar/ | sed -nE 's/.*href=\"(tor-expert-bundle-aar-[0-9a-z\.\-]*).*/\1/p')"
+ curl -o app/tor-expert-bundle.aar $TBB_BUILD_06/tor-expert-bundle-aar/$tor_expert_bundle_aar_dirname/tor-expert-bundle.aar
+fi
+
+if [ -z app/tor_expert_bundle.aar ]; then
echo "Cannot find Tor Expert Bundle arr artifacts!"
exit 2
fi
+echo ""
-cp "$tor_expert_bundle_aar"/* app/
+echo "Fetching noscript..."
-noscript="$(find "$TOR_BROWSER_BUILD/out/browser" -name 'noscript*.xpi' -print | sort | tail -1)"
mkdir -p "app/src/main/assets/extensions"
-if [ -f "$noscript" ]; then
- cp "$noscript" "app/src/main/assets/extensions/{73a6fe31-595d-460b-a920-fcc0f8843232}.xpi"
+
+if [[ $1 == "--tbb" ]]; then
+ noscript="$(find "$TOR_BROWSER_BUILD/out/browser" -name 'noscript*.xpi' -print | sort | tail -1)"
+ cp "$noscript" "app/src/main/assets/extensions/{73a6fe31-595d-460b-a920-fcc0f8843232}.xpi"
+else
+ noscript_fname="$(curl -s $TBB_BUILD_06/browser/ | sed -nE 's/.*href=\"(noscript-[0-9a-z\.\-]*).*/\1/p')"
+ curl -o "app/src/main/assets/extensions/{73a6fe31-595d-460b-a920-fcc0f8843232}.xpi" $TBB_BUILD_06/browser/$noscript_fname
fi
+echo ""
if [ -z "$GRADLE_MAVEN_REPOSITORIES" ]; then
GRADLE_MAVEN_REPOSITORIES="$HOME/.m2/repository"
@@ -47,7 +78,18 @@ if [ "$os" = "unsupported" ] || [ "$arch" = "unsupported" ]; then
exit 2
fi
-app_services="$(ls -1t "$TOR_BROWSER_BUILD/out/application-services/"application-services*.tar.zst | head -1)"
+echo "Fetching application-services..."
+
+if [[ $1 == "--tbb" ]]; then
+ app_services="$(ls -1t "$TOR_BROWSER_BUILD/out/application-services/"application-services*.tar.zst | head -1)"
+ tar -C /tmp -xf "$app_services"
+else
+ app_services_fname="$(curl -s $TBB_BUILD_06/application-services/ | sed -nE 's/.*href=\"(application-services-[0-9a-z\.\-]*).*/\1/p')"
+ app_services=/tmp/$app_services_fname
+ curl -o $app_services $TBB_BUILD_06/application-services/$app_services_fname
+ tar -C /tmp -xf "$app_services"
+ rm "$app_services"
+fi
mkdir -p "$GRADLE_MAVEN_REPOSITORIES/org/mozilla"
if [ -f "$app_services" ]; then
tar -C /tmp -xf "$app_services"
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/7ff9d2…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/7ff9d2…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][main] Update rbm for rbm#40084
by boklm (@boklm) 09 Jun '25
by boklm (@boklm) 09 Jun '25
09 Jun '25
boklm pushed to branch main at The Tor Project / Applications / tor-browser-build
Commits:
269e782c by Nicolas Vigier at 2025-06-09T19:02:07+02:00
Update rbm for rbm#40084
- - - - -
1 changed file:
- rbm
Changes:
=====================================
rbm
=====================================
@@ -1 +1 @@
-Subproject commit f04c4f217ad8dbd0caa87d45713a6de576cf0534
+Subproject commit 263693c984a467d971bb7b2c1cdeb6f1800eab2f
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/2…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/2…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/rbm][main] Bug 40084: Always use bash for the debug terminal
by boklm (@boklm) 09 Jun '25
by boklm (@boklm) 09 Jun '25
09 Jun '25
boklm pushed to branch main at The Tor Project / Applications / RBM
Commits:
263693c9 by Beatriz Rizental at 2025-06-09T15:16:11+02:00
Bug 40084: Always use bash for the debug terminal
- - - - -
1 changed file:
- lib/RBM.pm
Changes:
=====================================
lib/RBM.pm
=====================================
@@ -1291,9 +1291,10 @@ sub build_run {
if (project_config($project, 'debug', $options)) {
print STDERR $error, "\nOpening debug shell\n";
print STDERR "Warning: build files will be removed when you exit this shell.\n";
+ my $original_term = shell_quote($ENV{TERM} || 'dumb');
my $cmd = project_config($project, "remote_exec", {
%$options,
- exec_cmd => "cd $remote_tmp_src; PS1='debug-$project\$ ' \${SHELL-/bin/bash}",
+ exec_cmd => "cd $remote_tmp_src; TERM=$original_term PS1='debug-$project\$ ' /bin/bash",
exec_name => "debug-$s",
exec_as_root => $scripts_root{$s},
interactive => 1,
View it on GitLab: https://gitlab.torproject.org/tpo/applications/rbm/-/commit/263693c984a467d…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/rbm/-/commit/263693c984a467d…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/mullvad-browser] Pushed new tag mullvad-browser-140.0a1-15.0-1-build1
by Pier Angelo Vendrame (@pierov) 09 Jun '25
by Pier Angelo Vendrame (@pierov) 09 Jun '25
09 Jun '25
Pier Angelo Vendrame pushed new tag mullvad-browser-140.0a1-15.0-1-build1 at The Tor Project / Applications / Mullvad Browser
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/tree/mullv…
You're receiving this email because of your account on gitlab.torproject.org.
1
0