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
June 2025
- 1 participants
- 90 discussions

[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

[Git][tpo/applications/mullvad-browser][mullvad-browser-140.0a1-15.0-1] 32 commits: MB 38: Mullvad Browser configuration
by Pier Angelo Vendrame (@pierov) 09 Jun '25
by Pier Angelo Vendrame (@pierov) 09 Jun '25
09 Jun '25
Pier Angelo Vendrame pushed to branch mullvad-browser-140.0a1-15.0-1 at The Tor Project / Applications / Mullvad Browser
Commits:
f2f8c7b0 by Pier Angelo Vendrame at 2025-06-09T14:34:33+02:00
MB 38: Mullvad Browser configuration
- - - - -
fe36d578 by Pier Angelo Vendrame at 2025-06-09T14:34:36+02:00
fixup! MB 38: Mullvad Browser configuration
TB 43653: Remove the unused spoofOsInUserAgentHeader pref.
- - - - -
81d8c579 by Pier Angelo Vendrame at 2025-06-09T14:34:36+02:00
MB 1: Mullvad Browser branding
See also:
mullvad-browser#5: Product name and directory customization
mullvad-browser#12: Create new branding directories and integrate Mullvad icons+branding
mullvad-browser#14: Remove Default Built-in bookmarks
mullvad-browser#35: Add custom PDF icons for Windows builds
mullvad-browser#48: Replace Mozilla copyright and legal trademarks in mullvadbrowser.exe metadata
mullvad-browser#51: Update trademark string
mullvad-browser#104: Update shipped dll metadata copyright/licensing info
mullvad-browser#107: Add alpha and nightly icons
- - - - -
dd91f446 by Henry Wilkes at 2025-06-09T14:34:37+02:00
fixup! MB 1: Mullvad Browser branding
MB 411: Set startup.homepage_override_url on mb-nightly to be used
instead of app.releaseNotesURL.aboutDialog.
- - - - -
00c95c6c by Henry Wilkes at 2025-06-09T14:34:37+02:00
Mullvad Browser strings
This commit adds strings needed by the following Mullvad Browser
patches.
- - - - -
64d807af by Pier Angelo Vendrame at 2025-06-09T14:34:37+02:00
MB 20: Allow packaged-addons in PBM.
We install a few addons from the distribution directory, but they are
not automatically enabled for PBM mode.
This commit modifies the code that installs them to also add the PBM
permission to the known ones.
- - - - -
4092c498 by Pier Angelo Vendrame at 2025-06-09T14:34:38+02:00
MB 63: Customize some about pages for Mullvad Browser
Also:
mullvad-browser#57: Purge unneeded about: pages
- - - - -
7a37de83 by Pier Angelo Vendrame at 2025-06-09T14:46:01+02:00
MB 37: Customization for the about dialog
- - - - -
27402bca by Henry Wilkes at 2025-06-09T14:46:04+02:00
MB 39: Add home page about:mullvad-browser
- - - - -
93cb2b3f by Henry Wilkes at 2025-06-09T14:46:04+02:00
fixup! MB 39: Add home page about:mullvad-browser
MB 411: Use the same update URL in about:mullvad-browser as the about
dialog.
- - - - -
67ea76c6 by hackademix at 2025-06-09T14:46:04+02:00
MB 97: Remove UI cues to install new extensions.
- - - - -
02795f60 by hackademix at 2025-06-09T14:46:05+02:00
MB 47: uBlock Origin customization
- - - - -
ffe7343a by Pier Angelo Vendrame at 2025-06-09T14:46:05+02:00
MB 21: Disable the password manager
This commit disables the about:login page and removes the "Login and
Password" section of about:preferences.
We do not do anything to the real password manager of Firefox, that is
in toolkit: it contains C++ parts that make it difficult to actually
prevent it from being built..
Finally, we modify the the function that opens about:login to report an
error in the console so that we can quickly get a backtrace to the code
that tries to use it.
- - - - -
4296e61b by Pier Angelo Vendrame at 2025-06-09T14:46:06+02:00
MB 112: Updater customization for Mullvad Browser
MB 71: Set the updater base URL to Mullvad domain
- - - - -
12d78c5e by Henry Wilkes at 2025-06-09T14:46:06+02:00
fixup! MB 112: Updater customization for Mullvad Browser
MB 411: Drop mullvadbrowser.post_update.url.
Also, no longer require an override page to be set to open
about:mullvad-browser after an update.
- - - - -
6044fcd4 by Nicolas Vigier at 2025-06-09T14:46:07+02:00
MB 79: Add Mullvad Browser MAR signing keys
MB 256: Add mullvad-browser nightly mar signing key
- - - - -
4d8a26d3 by Pier Angelo Vendrame at 2025-06-09T14:46:07+02:00
MB 34: Hide unsafe and unwanted preferences UI
about:preferences allow to override some of our defaults, that could
be fingeprintable or have some other unwanted consequences.
- - - - -
28633dd0 by Pier Angelo Vendrame at 2025-06-09T14:46:08+02:00
MB 160: Disable the cookie exceptions button
Besides disabling the "Delete on close checkbox", disable also the
"Manage Exceptions" button when always using PBM.
- - - - -
edce653e by hackademix at 2025-06-09T14:46:08+02:00
MB 163: prevent uBlock Origin from being uninstalled/disabled
- - - - -
abc0b3cc by Richard Pospesel at 2025-06-09T14:46:08+02:00
MB 188: Customize Gitlab Issue and Merge templates
- - - - -
1411471a by Morgan at 2025-06-09T14:46:09+02:00
fixup! MB 188: Customize Gitlab Issue and Merge templates
- - - - -
5d5ab465 by rui hildt at 2025-06-09T14:47:34+02:00
MB 213: Customize the search engines list
MB 328: Refactor the search engine patch.
Upstream switched to a completely different search engine configuration
between ESR 115 and ESR 128.
We moved our configuration to a couple of JSON files that do not follow
upstream's schemas, as they are overcomplicated for our needs.
Also, we keep the old search engine extensions for now, as upstream
also kept them, and planned of removing them with Bug 1885953.
- - - - -
7c27497e by hackademix at 2025-06-09T14:47:37+02:00
MB 214: Enable cross-tab identity leak protection in "quiet" mode
- - - - -
22abb47b by Pier Angelo Vendrame at 2025-06-09T14:47:38+02:00
MB 80: Enable Mullvad Browser as a default browser
- - - - -
e60746ec by Pier Angelo Vendrame at 2025-06-09T14:47:38+02:00
MB 320: Temporarily disable WebRTC and WDBA on Windows.
WebRTC should be re-enabled when tor-browser#42758 is resolved, and and
the default browser agent when in general we make this feature work
again.
- - - - -
db7dd12a by Henry Wilkes at 2025-06-09T14:47:38+02:00
MB 329: Customize toolbar for mullvad-browser.
- - - - -
182438d7 by Henry Wilkes at 2025-06-09T14:47:39+02:00
Add CI for Mullvad Browser
- - - - -
44371309 by Henry Wilkes at 2025-06-09T14:47:39+02:00
MB 419: Mullvad Browser migration procedures.
This commit implements the the Mullvad Browser's version of _migrateUI.
- - - - -
71158a0f by Pier Angelo Vendrame at 2025-06-09T14:47:40+02:00
fixup! MB 1: Mullvad Browser branding
TB 43776: Mark branding files for l10n merge.
- - - - -
c3f5e9a6 by Pier Angelo Vendrame at 2025-06-09T14:47:40+02:00
fixup! MB 80: Enable Mullvad Browser as a default browser
Comment a definition since mingw now defines it as well.
- - - - -
878c58b5 by Pier Angelo Vendrame at 2025-06-09T14:47:40+02:00
fixup! MB 39: Add home page about:mullvad-browser
Drop the unnecessary font CSP directive.
Debug builds complain about this, and they crash with a failed
assertion.
- - - - -
3ebd9dbf by Pier Angelo Vendrame at 2025-06-09T15:31:41+02:00
fixup! Base Browser strings
BB 43849: Remove a useless lint exclusion.
Probably it was a rebase error.
- - - - -
271 changed files:
- .gitlab/ci/jobs/update-translations.yml
- .gitlab/issue_templates/000 Bug Report.md
- .gitlab/issue_templates/010 Proposal.md
- .gitlab/issue_templates/020 Web Compatibility.md
- .gitlab/issue_templates/030 Test.md
- .gitlab/issue_templates/040 Feature.md
- .gitlab/issue_templates/060 Rebase - Alpha.md
- .gitlab/issue_templates/061 Rebase - Stable.md
- .gitlab/issue_templates/063 Rebase - Rapid.md
- .gitlab/issue_templates/090 Emergency Security Issue.md
- + .gitlab/issue_templates/Rebase Browser - Alpha.md
- + .gitlab/issue_templates/Rebase Browser - Rapid.md
- + .gitlab/issue_templates/Rebase Browser - Stable.md
- .gitlab/merge_request_templates/Default.md
- + .gitlab/merge_request_templates/Rebase.md
- browser/app/Makefile.in
- browser/app/macbuild/Contents/Info.plist.in
- browser/app/module.ver
- browser/app/firefox.exe.manifest → browser/app/mullvadbrowser.exe.manifest
- + browser/app/profile/000-mullvad-browser.js
- browser/app/profile/001-base-profile.js
- browser/base/content/aboutDialog.xhtml
- browser/base/content/appmenu-viewcache.inc.xhtml
- browser/base/content/browser-menubar.inc
- browser/base/content/browser-places.js
- browser/base/content/browser.js
- browser/base/content/default-bookmarks.html
- browser/base/content/nsContextMenu.sys.mjs
- browser/base/content/overrides/app-license.html
- browser/base/content/pageinfo/pageInfo.xhtml
- browser/base/content/utilityOverlay.js
- browser/branding/branding-common.mozbuild
- + browser/branding/mb-alpha/VisualElements_150.png
- + browser/branding/mb-alpha/VisualElements_70.png
- + browser/branding/mb-alpha/configure.sh
- + browser/branding/mb-alpha/content/about-logo.png
- + browser/branding/mb-alpha/content/about-logo.svg
- + browser/branding/mb-alpha/content/about-logo(a)2x.png
- + browser/branding/mb-alpha/content/about-wordmark.svg
- + browser/branding/mb-alpha/content/about.png
- + browser/branding/mb-alpha/content/aboutDialog.css
- + browser/branding/mb-alpha/content/firefox-wordmark.svg
- + browser/branding/mb-alpha/content/icon128.png
- + browser/branding/mb-alpha/content/icon16.png
- + browser/branding/mb-alpha/content/icon256.png
- + browser/branding/mb-alpha/content/icon32.png
- + browser/branding/mb-alpha/content/icon48.png
- + browser/branding/mb-alpha/content/icon64.png
- + browser/branding/mb-alpha/content/jar.mn
- + browser/branding/mb-alpha/content/moz.build
- + browser/branding/mb-alpha/content/mullvad-branding.css
- + browser/branding/mb-alpha/default128.png
- + browser/branding/mb-alpha/default16.png
- + browser/branding/mb-alpha/default22.png
- + browser/branding/mb-alpha/default24.png
- + browser/branding/mb-alpha/default256.png
- + browser/branding/mb-alpha/default32.png
- + browser/branding/mb-alpha/default48.png
- + browser/branding/mb-alpha/default64.png
- + browser/branding/mb-alpha/document.icns
- + browser/branding/mb-alpha/document.ico
- + browser/branding/mb-alpha/document_pdf.ico
- + browser/branding/mb-alpha/firefox.icns
- + browser/branding/mb-alpha/firefox.ico
- + browser/branding/mb-alpha/firefox.svg
- + browser/branding/mb-alpha/locales/en-US/brand.ftl
- + browser/branding/mb-alpha/locales/en-US/brand.properties
- + browser/branding/mb-alpha/locales/jar.mn
- + browser/branding/mb-alpha/locales/moz.build
- + browser/branding/mb-alpha/locales/mullvad-about-wordmark-en.ftl
- + browser/branding/mb-alpha/moz.build
- + browser/branding/mb-alpha/mullvadbrowser.VisualElementsManifest.xml
- + browser/branding/mb-alpha/newtab.ico
- + browser/branding/mb-alpha/newwindow.ico
- + browser/branding/mb-alpha/pbmode.ico
- + browser/branding/mb-alpha/pref/firefox-branding.js
- + browser/branding/mb-nightly/VisualElements_150.png
- + browser/branding/mb-nightly/VisualElements_70.png
- + browser/branding/mb-nightly/configure.sh
- + browser/branding/mb-nightly/content/about-logo.png
- + browser/branding/mb-nightly/content/about-logo.svg
- + browser/branding/mb-nightly/content/about-logo(a)2x.png
- + browser/branding/mb-nightly/content/about-wordmark.svg
- + browser/branding/mb-nightly/content/about.png
- + browser/branding/mb-nightly/content/aboutDialog.css
- + browser/branding/mb-nightly/content/firefox-wordmark.svg
- + browser/branding/mb-nightly/content/icon128.png
- + browser/branding/mb-nightly/content/icon16.png
- + browser/branding/mb-nightly/content/icon256.png
- + browser/branding/mb-nightly/content/icon32.png
- + browser/branding/mb-nightly/content/icon48.png
- + browser/branding/mb-nightly/content/icon64.png
- + browser/branding/mb-nightly/content/jar.mn
- + browser/branding/mb-nightly/content/moz.build
- + browser/branding/mb-nightly/content/mullvad-branding.css
- + browser/branding/mb-nightly/default128.png
- + browser/branding/mb-nightly/default16.png
- + browser/branding/mb-nightly/default22.png
- + browser/branding/mb-nightly/default24.png
- + browser/branding/mb-nightly/default256.png
- + browser/branding/mb-nightly/default32.png
- + browser/branding/mb-nightly/default48.png
- + browser/branding/mb-nightly/default64.png
- + browser/branding/mb-nightly/document.icns
- + browser/branding/mb-nightly/document.ico
- + browser/branding/mb-nightly/document_pdf.ico
- + browser/branding/mb-nightly/firefox.icns
- + browser/branding/mb-nightly/firefox.ico
- + browser/branding/mb-nightly/firefox.svg
- + browser/branding/mb-nightly/locales/en-US/brand.ftl
- + browser/branding/mb-nightly/locales/en-US/brand.properties
- + browser/branding/mb-nightly/locales/jar.mn
- + browser/branding/mb-nightly/locales/moz.build
- + browser/branding/mb-nightly/locales/mullvad-about-wordmark-en.ftl
- + browser/branding/mb-nightly/moz.build
- + browser/branding/mb-nightly/mullvadbrowser.VisualElementsManifest.xml
- + browser/branding/mb-nightly/newtab.ico
- + browser/branding/mb-nightly/newwindow.ico
- + browser/branding/mb-nightly/pbmode.ico
- + browser/branding/mb-nightly/pref/firefox-branding.js
- + browser/branding/mb-release/VisualElements_150.png
- + browser/branding/mb-release/VisualElements_70.png
- + browser/branding/mb-release/configure.sh
- + browser/branding/mb-release/content/about-logo.png
- + browser/branding/mb-release/content/about-logo.svg
- + browser/branding/mb-release/content/about-logo(a)2x.png
- + browser/branding/mb-release/content/about-wordmark.svg
- + browser/branding/mb-release/content/about.png
- + browser/branding/mb-release/content/aboutDialog.css
- + browser/branding/mb-release/content/firefox-wordmark.svg
- + browser/branding/mb-release/content/icon128.png
- + browser/branding/mb-release/content/icon16.png
- + browser/branding/mb-release/content/icon256.png
- + browser/branding/mb-release/content/icon32.png
- + browser/branding/mb-release/content/icon48.png
- + browser/branding/mb-release/content/icon64.png
- + browser/branding/mb-release/content/jar.mn
- + browser/branding/mb-release/content/moz.build
- + browser/branding/mb-release/content/mullvad-branding.css
- + browser/branding/mb-release/default128.png
- + browser/branding/mb-release/default16.png
- + browser/branding/mb-release/default22.png
- + browser/branding/mb-release/default24.png
- + browser/branding/mb-release/default256.png
- + browser/branding/mb-release/default32.png
- + browser/branding/mb-release/default48.png
- + browser/branding/mb-release/default64.png
- + browser/branding/mb-release/document.icns
- + browser/branding/mb-release/document.ico
- + browser/branding/mb-release/document_pdf.ico
- + browser/branding/mb-release/firefox.icns
- + browser/branding/mb-release/firefox.ico
- + browser/branding/mb-release/firefox.svg
- + browser/branding/mb-release/locales/en-US/brand.ftl
- + browser/branding/mb-release/locales/en-US/brand.properties
- + browser/branding/mb-release/locales/jar.mn
- + browser/branding/mb-release/locales/moz.build
- + browser/branding/mb-release/locales/mullvad-about-wordmark-en.ftl
- + browser/branding/mb-release/moz.build
- + browser/branding/mb-release/mullvadbrowser.VisualElementsManifest.xml
- + browser/branding/mb-release/newtab.ico
- + browser/branding/mb-release/newwindow.ico
- + browser/branding/mb-release/pbmode.ico
- + browser/branding/mb-release/pref/firefox-branding.js
- browser/components/BrowserContentHandler.sys.mjs
- browser/components/BrowserGlue.sys.mjs
- browser/components/DesktopActorRegistry.sys.mjs
- browser/components/about/AboutRedirector.cpp
- browser/components/about/components.conf
- browser/components/customizableui/CustomizableUI.sys.mjs
- browser/components/moz.build
- + browser/components/mullvad-browser/AboutMullvadBrowserChild.sys.mjs
- + browser/components/mullvad-browser/AboutMullvadBrowserParent.sys.mjs
- + browser/components/mullvad-browser/content/2728-sparkles.svg
- + browser/components/mullvad-browser/content/aboutMullvadBrowser.css
- + browser/components/mullvad-browser/content/aboutMullvadBrowser.html
- + browser/components/mullvad-browser/content/aboutMullvadBrowser.js
- + browser/components/mullvad-browser/jar.mn
- + browser/components/mullvad-browser/moz.build
- browser/components/newtab/AboutNewTabRedirector.sys.mjs
- browser/components/preferences/home.inc.xhtml
- browser/components/preferences/main.js
- browser/components/preferences/preferences.xhtml
- browser/components/preferences/privacy.inc.xhtml
- browser/components/preferences/privacy.js
- browser/components/preferences/search.inc.xhtml
- + browser/components/search/extensions/brave/favicon.svg
- + browser/components/search/extensions/brave/manifest.json
- + browser/components/search/extensions/ddg-html/favicon.ico
- + browser/components/search/extensions/ddg-html/manifest.json
- browser/components/search/extensions/ddg/manifest.json
- + browser/components/search/extensions/metager/favicon.ico
- + browser/components/search/extensions/metager/manifest.json
- + browser/components/search/extensions/mojeek/favicon.ico
- + browser/components/search/extensions/mojeek/manifest.json
- + browser/components/search/extensions/mullvad-leta/favicon.svg
- + browser/components/search/extensions/mullvad-leta/manifest.json
- + browser/components/search/extensions/startpage/favicon.png
- + browser/components/search/extensions/startpage/manifest.json
- browser/components/shell/ShellService.sys.mjs
- browser/components/shell/WindowsDefaultBrowser.cpp
- browser/components/shell/nsWindowsShellService.cpp
- browser/config/mozconfigs/base-browser
- + browser/config/mozconfigs/mullvad-browser
- browser/installer/package-manifest.in
- browser/installer/windows/nsis/updater_append.ini
- browser/locales/l10n.toml
- browser/modules/HomePage.sys.mjs
- browser/moz.build
- browser/moz.configure
- config/create_rc.py
- devtools/client/aboutdebugging/src/actions/runtimes.js
- devtools/client/aboutdebugging/src/components/sidebar/Sidebar.js
- devtools/client/jar.mn
- devtools/client/themes/images/aboutdebugging-firefox-aurora.svg
- devtools/client/themes/images/aboutdebugging-firefox-beta.svg
- devtools/client/themes/images/aboutdebugging-firefox-logo.svg
- devtools/client/themes/images/aboutdebugging-firefox-nightly.svg
- devtools/client/themes/images/aboutdebugging-firefox-release.svg
- + devtools/client/themes/images/aboutdebugging-mullvadbrowser-logo.svg
- docshell/base/nsAboutRedirector.cpp
- docshell/build/components.conf
- moz.configure
- mozconfig-linux-aarch64
- mozconfig-linux-aarch64-dev
- mozconfig-linux-x86_64
- mozconfig-linux-x86_64-asan
- mozconfig-linux-x86_64-dev
- mozconfig-macos
- mozconfig-macos-dev
- mozconfig-windows-x86_64
- + other-licenses/nsis/Contrib/ApplicationID/Makefile
- other-licenses/nsis/Contrib/ApplicationID/Set.cpp
- + other-licenses/nsis/Contrib/CityHash/Makefile
- toolkit/components/extensions/child/ext-storage.js
- toolkit/components/extensions/parent/ext-storage.js
- toolkit/components/passwordmgr/LoginHelper.sys.mjs
- toolkit/components/search/AppProvidedSearchEngine.sys.mjs
- toolkit/components/search/SearchService.sys.mjs
- + toolkit/components/search/content/brave.svg
- + toolkit/components/search/content/duckduckgo.ico
- + toolkit/components/search/content/metager.ico
- + toolkit/components/search/content/mojeek.ico
- + toolkit/components/search/content/mullvad-leta.svg
- + toolkit/components/search/content/mullvadBrowserSearchEngineIcons.json
- + toolkit/components/search/content/mullvadBrowserSearchEngines.json
- + toolkit/components/search/content/startpage.png
- + toolkit/components/search/jar.mn
- toolkit/components/search/moz.build
- toolkit/components/securitylevel/SecurityLevel.sys.mjs
- + toolkit/content/aboutRightsMullvad.xhtml
- + toolkit/content/aboutTelemetryMullvad.xhtml
- toolkit/content/jar.mn
- toolkit/content/widgets/moz-support-link/moz-support-link.mjs
- + toolkit/locales/en-US/toolkit/global/mullvad-browser.ftl
- toolkit/mozapps/defaultagent/EventLog.h
- toolkit/mozapps/defaultagent/SetDefaultBrowser.cpp
- toolkit/mozapps/extensions/AddonManager.sys.mjs
- toolkit/mozapps/extensions/content/aboutaddons.css
- toolkit/mozapps/extensions/internal/XPIDatabase.sys.mjs
- toolkit/mozapps/extensions/internal/XPIProvider.sys.mjs
- toolkit/mozapps/update/updater/nightly_aurora_level3_primary.der
- toolkit/mozapps/update/updater/nightly_aurora_level3_secondary.der
- toolkit/mozapps/update/updater/release_primary.der
- toolkit/mozapps/update/updater/release_secondary.der
- + toolkit/themes/shared/icons/mullvadbrowser.png
- toolkit/themes/shared/minimal-toolkit.jar.inc.mn
- toolkit/xre/nsAppRunner.cpp
- tools/lint/fluent-lint/exclusions.yml
- widget/windows/WinTaskbar.cpp
- widget/windows/moz.build
The diff was not included because it is too large.
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/fe…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/fe…
You're receiving this email because of your account on gitlab.torproject.org.
1
0