tbb-commits
Threads by month
- ----- 2026 -----
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- 1 participants
- 20837 discussions
[Git][tpo/applications/mullvad-browser][mullvad-browser-153.0esr-16.0-1] fixup! Base Browser strings
by morgan (@morgan) 11 Aug '26
by morgan (@morgan) 11 Aug '26
11 Aug '26
morgan pushed to branch mullvad-browser-153.0esr-16.0-1 at The Tor Project / Applications / Mullvad Browser
Commits:
71212888 by Henry Wilkes at 2026-08-11T11:55:01+00:00
fixup! Base Browser strings
BB 45079: Drop old letterboxing strings.
- - - - -
1 changed file:
- toolkit/locales/en-US/toolkit/global/base-browser.ftl
Changes:
=====================================
toolkit/locales/en-US/toolkit/global/base-browser.ftl
=====================================
@@ -74,13 +74,6 @@ new-identity-blocked-home-ignore-button = Load it anyway
## Preferences - Letterboxing.
-# The word "Letterboxing" is the proper noun for the Tor Browser feature, and is therefore capitalised.
-# "Letterboxing" should be treated as a feature/product name, and likely not changed in other languages.
-letterboxing-header = Letterboxing
-# The word "Letterboxing" is the proper noun for the Tor Browser feature, and is therefore capitalised.
-# "Letterboxing" should be treated as a feature/product name, and likely not changed in other languages.
-letterboxing-overview = { -brand-short-name }'s Letterboxing feature restricts websites to display at specific sizes, making it harder to single out users on the basis of their window or screen size.
-letterboxing-learn-more = Learn more
# The word "Letterboxing" is the proper noun for the Tor Browser feature, and is therefore capitalised.
# "Letterboxing" should be treated as a feature/product name, and likely not changed in other languages.
# "{ -brand-short-name }" will be replaced with the localized name of the browser, e.g. "Tor Browser".
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/712…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/712…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/tor-browser][tor-browser-153.0esr-16.0-1] fixup! Base Browser strings
by morgan (@morgan) 11 Aug '26
by morgan (@morgan) 11 Aug '26
11 Aug '26
morgan pushed to branch tor-browser-153.0esr-16.0-1 at The Tor Project / Applications / Tor Browser
Commits:
43fea19f by Henry Wilkes at 2026-08-11T11:49:18+00:00
fixup! Base Browser strings
BB 45079: Drop old letterboxing strings.
- - - - -
1 changed file:
- toolkit/locales/en-US/toolkit/global/base-browser.ftl
Changes:
=====================================
toolkit/locales/en-US/toolkit/global/base-browser.ftl
=====================================
@@ -74,13 +74,6 @@ new-identity-blocked-home-ignore-button = Load it anyway
## Preferences - Letterboxing.
-# The word "Letterboxing" is the proper noun for the Tor Browser feature, and is therefore capitalised.
-# "Letterboxing" should be treated as a feature/product name, and likely not changed in other languages.
-letterboxing-header = Letterboxing
-# The word "Letterboxing" is the proper noun for the Tor Browser feature, and is therefore capitalised.
-# "Letterboxing" should be treated as a feature/product name, and likely not changed in other languages.
-letterboxing-overview = { -brand-short-name }'s Letterboxing feature restricts websites to display at specific sizes, making it harder to single out users on the basis of their window or screen size.
-letterboxing-learn-more = Learn more
# The word "Letterboxing" is the proper noun for the Tor Browser feature, and is therefore capitalised.
# "Letterboxing" should be treated as a feature/product name, and likely not changed in other languages.
# "{ -brand-short-name }" will be replaced with the localized name of the browser, e.g. "Tor Browser".
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/43fea19…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/43fea19…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/tor-browser][tor-browser-153.0esr-16.0-1] 2 commits: fixup! TB 30237: Add v3 onion services client authentication prompt
by morgan (@morgan) 11 Aug '26
by morgan (@morgan) 11 Aug '26
11 Aug '26
morgan pushed to branch tor-browser-153.0esr-16.0-1 at The Tor Project / Applications / Tor Browser
Commits:
3122fae2 by Henry Wilkes at 2026-08-11T11:29:38+00:00
fixup! TB 30237: Add v3 onion services client authentication prompt
TB 45200: Include onion authentication in the new password settings.
- - - - -
3066ef32 by Henry Wilkes at 2026-08-11T11:29:38+00:00
fixup! Tor Browser strings
TB 45200: Modify the saved keys button text.
- - - - -
3 changed files:
- browser/components/preferences/config/passwords-autofill.mjs
- browser/components/preferences/preferences.js
- toolkit/locales/en-US/toolkit/global/tor-browser.ftl
Changes:
=====================================
browser/components/preferences/config/passwords-autofill.mjs
=====================================
@@ -797,7 +797,7 @@ SettingGroupManager.registerGroups({
{
id: "onionSiteSavedKeys",
control: "moz-box-button",
- l10nId: "onion-site-authentication-saved-keys-button",
+ l10nId: "onion-site-authentication-saved-keys-button2",
},
],
},
=====================================
browser/components/preferences/preferences.js
=====================================
@@ -393,7 +393,7 @@ const CONFIG_PANES = Object.freeze({
passwordsAutofill: {
l10nId: "preferences-passwords-autofill-header",
iconSrc: "chrome://browser/skin/login.svg",
- groupIds: ["passwords", "payments", "addresses"],
+ groupIds: ["passwords", "onionSiteAuthentication", "payments", "addresses"],
module:
"chrome://browser/content/preferences/config/passwords-autofill.mjs",
visible: () => srdSectionEnabled("passwordsAutofill"),
=====================================
toolkit/locales/en-US/toolkit/global/tor-browser.ftl
=====================================
@@ -746,8 +746,8 @@ onion-site-authentication-group =
.label = Onion site authentication
.description = Some onion sites require that you identify yourself with a key (a kind of password) before you can access them.
# Here "key" is a noun, and refers to a special code that gives the user access to a website.
-onion-site-authentication-saved-keys-button =
- .label = Saved keys…
+onion-site-authentication-saved-keys-button2 =
+ .label = Manage saved keys
## Onion site saved keys dialog.
## "Onion site" is an abbreviation of "onion website": a website whose domain URL ends in ".onion", which is reachable through the Tor network.
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/5fac28…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/5fac28…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/mullvad-browser][mullvad-browser-153.0esr-16.0-1] MB 446: Selective Saving Mode Backend
by Pier Angelo Vendrame (@pierov) 11 Aug '26
by Pier Angelo Vendrame (@pierov) 11 Aug '26
11 Aug '26
Pier Angelo Vendrame pushed to branch mullvad-browser-153.0esr-16.0-1 at The Tor Project / Applications / Mullvad Browser
Commits:
59bcec0c by june wilde at 2026-08-10T10:28:18-07:00
MB 446: Selective Saving Mode Backend
- - - - -
11 changed files:
- browser/app/profile/000-mullvad-browser.js
- toolkit/components/moz.build
- + toolkit/components/selectivesavingmode/SelectiveSavingMode.manifest
- + toolkit/components/selectivesavingmode/SelectiveSavingMode.sys.mjs
- + toolkit/components/selectivesavingmode/components.conf
- + toolkit/components/selectivesavingmode/content/broom-clean.svg
- + toolkit/components/selectivesavingmode/content/broom-saved.svg
- + toolkit/components/selectivesavingmode/jar.mn
- + toolkit/components/selectivesavingmode/moz.build
- + toolkit/components/selectivesavingmode/tests/xpcshell/test_selective_saving_mode.js
- + toolkit/components/selectivesavingmode/tests/xpcshell/xpcshell.toml
Changes:
=====================================
browser/app/profile/000-mullvad-browser.js
=====================================
@@ -39,3 +39,20 @@ pref("app.update.url.details", "https://mullvad.net/download/browser");
pref("app.update.badgeWaitTime", 0);
// point to our feedback url rather than Mozilla's
pref("app.feedback.baseURL", "https://mullvad.net/help/tag/browser/");
+
+// mullvad-browser#446: Default to unconfigured for selective saving mode for alpha
+// Controls what selective saving mode the browser operates in.
+// On initial startup the selective saving toolkit component sets this mode pref
+// when the user hasn't modified any of the prefs this pref controls. Otherwise,
+// the mode is set to selective saving
+//
+// 0 = mode has yet to be configured
+// 1 = always on private browsing mode (old default)
+// 2 = selective saving mode (new default)
+// 3 = normal browsing mode
+pref("browser.selectiveSavingMode", 0);
+#if MOZ_UPDATE_CHANNEL == release
+pref("browser.selectiveSavingMode.enabled", false);
+#else
+pref("browser.selectiveSavingMode.enabled", true);
+#endif
=====================================
toolkit/components/moz.build
=====================================
@@ -78,6 +78,7 @@ DIRS += [
"resistfingerprinting",
"search",
"securitylevel",
+ "selectivesavingmode",
"sessionstore",
"shell",
"startup",
=====================================
toolkit/components/selectivesavingmode/SelectiveSavingMode.manifest
=====================================
@@ -0,0 +1 @@
+category profile-after-change SelectiveSavingMode @torproject.org/selective-saving-mode;1
=====================================
toolkit/components/selectivesavingmode/SelectiveSavingMode.sys.mjs
=====================================
@@ -0,0 +1,229 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Pref to select browsing mode via this component
+export const kSelectiveSavingModePref = "browser.selectiveSavingMode";
+
+// Selective Saving Mode underlaying functionality prefs
+// Sanitize site data upon shutdown
+const kSanitizeOnShutdown = "privacy.sanitize.sanitizeOnShutdown";
+// Start browser in private window
+const kPrivateBrowsingAutostart = "browser.privatebrowsing.autostart";
+// Keep site permissions stored in memory rather than on disk
+const kKeepPermissionInMemory = "permissions.memory_only";
+
+// Valid options for the browsing mode pref to be set to
+export const SelectiveSavingModes = Object.freeze({
+ unconfigured: 0,
+ total_clearing: 1,
+ selective_saving: 2,
+ normal_browsing: 3,
+ custom: 4,
+});
+
+// A table of all prefs used to move between default browsing modes.
+const kSelectiveSavingModeSettings = {
+ total_clearing: {
+ [kSanitizeOnShutdown]: true,
+ [kPrivateBrowsingAutostart]: true,
+ [kKeepPermissionInMemory]: true,
+ },
+ selective_saving: {
+ [kSanitizeOnShutdown]: true,
+ [kPrivateBrowsingAutostart]: false,
+ [kKeepPermissionInMemory]: false,
+ },
+ normal_browsing: {
+ [kSanitizeOnShutdown]: false,
+ [kPrivateBrowsingAutostart]: false,
+ [kKeepPermissionInMemory]: false,
+ },
+};
+
+/**
+ * Selective Saving Mode Pref Controller
+ *
+ * Listens for and handles changes in prefs relevant to Selective Saving Mode in
+ * order to keep the browsing mode pref and the corresponding functionality
+ * prefs in sync with each other each time any of the aforementioned prefs are
+ * changed
+ */
+export class SelectiveSavingPrefController {
+ QueryInterface = ChromeUtils.generateQI(["nsIObserver"]);
+
+ /**
+ * Initialize pref controller
+ */
+ init() {
+ if (!Services.prefs.getBoolPref("browser.selectiveSavingMode.enabled")) {
+ return;
+ }
+
+ if (
+ Services.prefs.getIntPref(kSelectiveSavingModePref) ===
+ SelectiveSavingModes.unconfigured
+ ) {
+ this.#setBrowsingModeFromPrefs();
+ } else {
+ this.#setPrefsFromBrowsingMode();
+ }
+
+ this.#createPreferenceObservers();
+ }
+
+ /**
+ * Shutdown pref controller
+ */
+ shutdown() {
+ this.#removePreferenceObservers();
+ }
+
+ /**
+ * Component hook for observing relevant event messages
+ *
+ * @param {string} aSubject
+ * @param {string} aTopic
+ */
+ observe(aSubject, aTopic) {
+ if (aTopic === "profile-after-change") {
+ this.init();
+ }
+ }
+
+ /**
+ * Sets current Selective Saving browsing mode to closely match how
+ * functionality prefs are configured in kSelectiveSavingModeSettings
+ */
+ #setBrowsingModeFromPrefs() {
+ const currentSanitizePref = Services.prefs.getBoolPref(kSanitizeOnShutdown);
+ const currentAutostartPref = Services.prefs.getBoolPref(
+ kPrivateBrowsingAutostart
+ );
+ const currentPermissionPref = Services.prefs.getBoolPref(
+ kKeepPermissionInMemory
+ );
+
+ for (let [mode, settings] of Object.entries(kSelectiveSavingModeSettings)) {
+ if (
+ currentSanitizePref === settings[kSanitizeOnShutdown] &&
+ currentAutostartPref === settings[kPrivateBrowsingAutostart] &&
+ currentPermissionPref === settings[kKeepPermissionInMemory]
+ ) {
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes[mode]
+ );
+ return;
+ }
+ }
+
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes.custom
+ );
+ }
+
+ /**
+ * A wrapper for setBrowsingModeFromPrefs to reset pref observers
+ */
+ #setModeAndResetObservers = () => {
+ this.#removePreferenceObservers();
+ this.#setBrowsingModeFromPrefs();
+ this.#createPreferenceObservers();
+ };
+
+ /**
+ * Sets current Selective Saving functionality prefs to closely match the
+ * currently assigned browsing mode. Corrects for anomalous settings if present
+ * as well
+ */
+ #setPrefsFromBrowsingMode() {
+ let currentBrowsingMode = Services.prefs.getIntPref(
+ kSelectiveSavingModePref
+ );
+
+ const mode = Object.entries(SelectiveSavingModes).find(
+ ([, value]) => value === currentBrowsingMode
+ )?.[0];
+
+ if (
+ mode === undefined ||
+ currentBrowsingMode === SelectiveSavingModes.unconfigured
+ ) {
+ this.#setBrowsingModeFromPrefs();
+ return;
+ }
+
+ if (kSelectiveSavingModeSettings[mode]) {
+ Services.prefs.setBoolPref(
+ kSanitizeOnShutdown,
+ kSelectiveSavingModeSettings[mode][kSanitizeOnShutdown]
+ );
+ Services.prefs.setBoolPref(
+ kPrivateBrowsingAutostart,
+ kSelectiveSavingModeSettings[mode][kPrivateBrowsingAutostart]
+ );
+ Services.prefs.setBoolPref(
+ kKeepPermissionInMemory,
+ kSelectiveSavingModeSettings[mode][kKeepPermissionInMemory]
+ );
+ }
+ }
+
+ /**
+ * A wrapper for setPrefsFromBrowsingMode to reset pref observers
+ */
+ #setPrefsAndResetObservers = () => {
+ this.#removePreferenceObservers();
+ this.#setPrefsFromBrowsingMode();
+ this.#createPreferenceObservers();
+ };
+
+ /**
+ * A helper function for standing up pref observers with the correct callback
+ */
+ #createPreferenceObservers() {
+ Services.prefs.addObserver(
+ kSelectiveSavingModePref,
+ this.#setPrefsAndResetObservers
+ );
+
+ Services.prefs.addObserver(
+ kSanitizeOnShutdown,
+ this.#setModeAndResetObservers
+ );
+ Services.prefs.addObserver(
+ kPrivateBrowsingAutostart,
+ this.#setModeAndResetObservers
+ );
+ Services.prefs.addObserver(
+ kKeepPermissionInMemory,
+ this.#setModeAndResetObservers
+ );
+ }
+
+ /**
+ * A helper function for disabling pref observers keyed to their respective
+ * callbacks
+ */
+ #removePreferenceObservers() {
+ Services.prefs.removeObserver(
+ kSelectiveSavingModePref,
+ this.#setPrefsAndResetObservers
+ );
+
+ Services.prefs.removeObserver(
+ kSanitizeOnShutdown,
+ this.#setModeAndResetObservers
+ );
+ Services.prefs.removeObserver(
+ kPrivateBrowsingAutostart,
+ this.#setModeAndResetObservers
+ );
+ Services.prefs.removeObserver(
+ kKeepPermissionInMemory,
+ this.#setModeAndResetObservers
+ );
+ }
+} /* Selective Saving Mode Pref Controller */
=====================================
toolkit/components/selectivesavingmode/components.conf
=====================================
@@ -0,0 +1,14 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Classes = [
+ {
+ "cid": "{b4a1073c-9e3c-4ce7-b13b-c100f5c64ccb}",
+ "contract_ids": [
+ "@torproject.org/selective-saving-mode;1",
+ ],
+ "esModule": "moz-src:///toolkit/components/selectivesavingmode/SelectiveSavingMode.sys.mjs",
+ "constructor": "SelectiveSavingPrefController",
+ }
+]
=====================================
toolkit/components/selectivesavingmode/content/broom-clean.svg
=====================================
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <g fill="context-fill" fill-opacity="context-fill-opacity">
+ <path d="m13.5383 14.5627c-.1712-.0053-.3194-.1334-.3505-.3028-.0419-.294-.1441-.5789-.3001-.8369-.2583-.1558-.5436-.2579-.838-.2998-.1694-.0313-.2974-.1793-.3026-.3501-.0053-.1708.1136-.3146.2813-.3402.2944-.0329.5762-.1254.8284-.272.1426-.2476.2313-.5243.2608-.8129.0237-.1679.1662-.2884.3372-.2851.1699.0042.3181.1295.3517.2973.0471.2931.1533.5763.312.8323.2565.1573.5396.263.8326.3109.1682.0345.2929.1836.2958.3536.0028.17-.1171.3116-.2843.3357-.2894.0285-.5669.1172-.8147.2604-.1462.2521-.2386.5335-.2717.8274-.025.167-.1675.2861-.3379.2822z"/>
+ <path d="m6.49858 2.99992c-.14675-.00459-.27377-.11436-.3004-.25961-.03593-.25196-.12354-.49621-.25729-.71731-.22137-.13358-.46594-.22109-.71822-.25699-.14526-.02682-.25492-.15363-.25945-.30004-.00454-.14641.09737-.26967.24112-.29164.25236-.02817.49393-.10747.71013-.233093.12217-.2123.19825-.449454.22353-.696834.0203-.143878.14242-.24714456.28897-.24434753.14565.00358504.27273.11100153.30149.25484453.0404.251183.13139.493923.2674.713349.21988.134841.46256.225461.71364.266481.14417.02957.25114.15744.25358.30313.00244.1457-.10035.26707-.24368.28774-.2481.02441-.48592.10041-.69835.22319-.1253.2161-.20449.45729-.23284.7092-.0214.14312-.14361.24521-.28963.24193z"/>
+ <path d="m1.82093 5.3609c-.15279-.00473-.28512-.11875-.31315-.26981-.02739-.18014-.08781-.35525-.1782-.51643-.16152-.09021-.336989-.15052-.517512-.17788-.151437-.02794-.265749-.16003-.270474-.31254-.004724-.15251.101518-.2809.251381-.30378.181146-.02145.355265-.07593.513815-.16075.08209-.15545.13363-.32622.15197-.50355.02095-.15059.14903-.25861.3025-.25512.15164.00368.28404.11525.31428.26484.03021.18029.09338.35503.18632.51538.16048.09192.33508.15452.51517.18469.1503.0308.26181.164.26435.31577.00254.15176-.10462.27819-.25404.29971-.17764.01914-.34855.07141-.50396.15412-.08502.1582-.13963.33194-.16114.5127-.022.14911-.14912.25571-.30131.25265z"/>
+ <path clip-rule="evenodd" d="m15.3213 1.06694c.2441-.244076.2441-.639804 0-.883882-.2441-.2440775-.6398-.2440774-.8839 0l-5.96506 5.965062h-.50519c-1.996-1.09517-4.49023.42233-6.49079 1.63948-.41545.25277-.80961.49258-1.173597.69335-.16756.10002-.289261.26641-.30145394.48048-.01219156.21407.06079654.41038.21802994.56743l1.243691 1.24224 2.37084-1.02603c.15392-.06661.30331.14022.18601.25753l-1.66213 1.6621 1.46329 1.4616 1.66126-1.6613c.1173-.1173.32413.0321.25752.186l-1.02482 2.3682 1.25462 1.2531c.15724.157.35379.23.56815.2178.19095-.0561.35851-.1561.45869-.3234.20012-.3592.43577-.7455.68321-1.1511 1.22241-2.0039 2.73233-4.47901 1.66484-6.47533v-.49654zm-7.46715 6.55077c1.12692 1.12692.64113 2.69369-.05278 3.70149h-.50137l-3.13-3.1492v-.5c1.00858-.68566 2.56556-1.17088 3.68415-.05229z" fill-rule="evenodd"/>
+ </g>
+</svg>
=====================================
toolkit/components/selectivesavingmode/content/broom-saved.svg
=====================================
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <g fill="context-fill" fill-opacity="context-fill-opacity">
+ <path d="M1.31604 2.56132C1.07376 2.31905 0.680954 2.31905 0.438678 2.56132C0.196403 2.8036 0.196403 3.1964 0.438678 3.43868L12.3649 15.3649C12.6072 15.6072 13 15.6072 13.2423 15.3649C13.4845 15.1226 13.4845 14.7298 13.2423 14.4875L1.31604 2.56132Z" />
+ <path d="M5.75301 6.99811C5.17481 7.10048 4.6182 7.36528 4.17 7.66998V8.16998L7.29989 11.3194H7.80184C8.11144 10.8697 8.37762 10.3082 8.47957 9.72467L9.40926 10.6544C9.01989 11.8129 8.31279 12.978 7.67977 14.0157C7.43234 14.4213 7.19629 14.8079 6.99617 15.1671C6.89602 15.3341 6.72885 15.4342 6.53817 15.4903C6.32389 15.5024 6.12701 15.4295 5.96981 15.2725L4.71492 14.0196L5.74031 11.6514C5.8069 11.4976 5.59983 11.3479 5.4825 11.4649L3.82137 13.126L2.3575 11.6651L4.01961 10.003C4.13689 9.88571 3.98795 9.67869 3.83406 9.74518L1.46297 10.7715L0.219804 9.52936C0.0625708 9.37231 -0.0111371 9.17507 0.00105446 8.961C0.0133896 8.74714 0.135367 8.58049 0.302812 8.48053C0.666747 8.27978 1.06126 8.03989 1.47664 7.78717C2.50628 7.16073 3.6674 6.45677 4.82332 6.06842L5.75301 6.99811ZM14.4375 0.182679C14.6816 -0.0610732 15.0773 -0.0611198 15.3213 0.182679C15.5654 0.426757 15.5654 0.823367 15.3213 1.06744L9.34481 7.04401V7.5401C9.6249 8.06391 9.72625 8.62084 9.70321 9.19342L6.28426 5.77545C6.86809 5.75137 7.43503 5.85671 7.96688 6.1485H8.47274L14.4375 0.182679Z" />
+ </g>
+</svg>
=====================================
toolkit/components/selectivesavingmode/jar.mn
=====================================
@@ -0,0 +1,7 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+toolkit.jar:
+ content/global/selectivesaving/broom-clean.svg (content/broom-clean.svg)
+ content/global/selectivesaving/broom-saved.svg (content/broom-saved.svg)
=====================================
toolkit/components/selectivesavingmode/moz.build
=====================================
@@ -0,0 +1,23 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += [
+ "tests/xpcshell/xpcshell.toml",
+]
+
+MOZ_SRC_FILES += [
+ "SelectiveSavingMode.sys.mjs",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+EXTRA_COMPONENTS += [
+ "SelectiveSavingMode.manifest",
+]
+
+JAR_MANIFESTS += [
+ "jar.mn",
+]
=====================================
toolkit/components/selectivesavingmode/tests/xpcshell/test_selective_saving_mode.js
=====================================
@@ -0,0 +1,755 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+ SelectiveSavingPrefController,
+ SelectiveSavingModes,
+ kSelectiveSavingModePref,
+} = ChromeUtils.importESModule(
+ "moz-src:///toolkit/components/selectivesavingmode/SelectiveSavingMode.sys.mjs"
+);
+
+// secondary prefs
+const kSanitizeOnShutdown = "privacy.sanitize.sanitizeOnShutdown";
+const kPrivateBrowsingAutostart = "browser.privatebrowsing.autostart";
+const kKeepPermissionInMemory = "permissions.memory_only";
+
+const controller = new SelectiveSavingPrefController();
+
+function resetToTotalSavingPrefs() {
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, true);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, true);
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, true);
+}
+
+registerCleanupFunction(async () => {
+ controller.shutdown();
+});
+
+add_task(async function test_init_from_secondary_prefs() {
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes.unconfigured
+ );
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, true);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, true);
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, true);
+
+ controller.init();
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.total_clearing,
+ "After pref controller init from unconfigured state, primary pref should be set to total clearing due to the way secondary prefs were set earlier"
+ );
+
+ controller.shutdown();
+
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes.unconfigured
+ );
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, true);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, false);
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, false);
+
+ controller.init();
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.selective_saving,
+ "After pref controller init from unconfigured state, primary pref should be set to selective saving due to the way secondary prefs were set earlier"
+ );
+
+ controller.shutdown();
+
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes.unconfigured
+ );
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, false);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, false);
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, false);
+
+ controller.init();
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.normal_browsing,
+ "After pref controller init from unconfigured state, primary pref should be set to normal browsing due to the way secondary prefs were set earlier"
+ );
+
+ controller.shutdown();
+
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes.unconfigured
+ );
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, true);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, true);
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, false);
+
+ controller.init();
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.custom,
+ "After pref controller init from unconfigured state, primary pref should be set to custom browsing due to the way secondary prefs were set earlier"
+ );
+
+ controller.shutdown();
+});
+
+add_task(async function test_init_from_secondary_prefs() {
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes.total_clearing
+ );
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, true);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, true);
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, true);
+
+ controller.init();
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.total_clearing,
+ "After pref controller init from total clearing mode configured state (primary and secondary), primary pref shouldn't be changed from total clearing"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kSanitizeOnShutdown),
+ true,
+ "After pref controller init from total clearing mode configured state (primary and secondary), secondary pref for sanitize on shutdown should be set to true"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kPrivateBrowsingAutostart),
+ true,
+ "After pref controller init from total clearing mode configured state (primary and secondary), secondary pref for pbm autostart should be set to true"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kKeepPermissionInMemory),
+ true,
+ "After pref controller init from total clearing mode configured state (primary and secondary), secondary pref for memory only permissions should be set to true"
+ );
+
+ controller.shutdown();
+
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes.selective_saving
+ );
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, true);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, false);
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, false);
+
+ controller.init();
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.selective_saving,
+ "After pref controller init from selective saving configured state (primary and secondary), primary pref shouldn't be changed from selective saving"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kSanitizeOnShutdown),
+ true,
+ "After pref controller init from selective saving mode configured state (primary and secondary), secondary pref for sanitize on shutdown should be set to true"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kPrivateBrowsingAutostart),
+ false,
+ "After pref controller init from selective saving mode configured state (primary and secondary), secondary pref for pbm autostart should be set to false"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kKeepPermissionInMemory),
+ false,
+ "After pref controller init from selective saving mode configured state (primary and secondary), secondary pref for memory only permissions should be set to false"
+ );
+
+ controller.shutdown();
+
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes.normal_browsing
+ );
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, false);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, false);
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, false);
+
+ controller.init();
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.normal_browsing,
+ "After pref controller init from normal browsing mode configured state (primary and secondary), primary pref shouldn't be changed from normal browsing"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kSanitizeOnShutdown),
+ false,
+ "After pref controller init from normal browsing mode configured state (primary and secondary), secondary pref for sanitize on shutdown should be set to false"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kPrivateBrowsingAutostart),
+ false,
+ "After pref controller init from normal browsing mode configured state (primary and secondary), secondary pref for pbm autostart should be set to false"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kKeepPermissionInMemory),
+ false,
+ "After pref controller init from normal browsing mode configured state (primary and secondary), secondary pref for memory only permissions should be set to false"
+ );
+
+ controller.shutdown();
+
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes.custom
+ );
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, true);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, true);
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, false);
+
+ controller.init();
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.custom,
+ "After pref controller init from custom mode configured state (primary and secondary), primary pref shouldn't be changed from custom"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kSanitizeOnShutdown),
+ true,
+ "After pref contoller init from total clearing mode configured state (primary and secondary), secondary pref for sanitize on shutdown should be set to true"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kPrivateBrowsingAutostart),
+ true,
+ "After pref contoller init from total clearing mode configured state (primary and secondary), secondary pref for pbm autostart should be set to true"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kKeepPermissionInMemory),
+ false,
+ "After pref contoller init from total clearing mode configured state (primary and secondary), secondary pref for memory only permissions should be set to false"
+ );
+
+ controller.shutdown();
+
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes.total_clearing
+ );
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, false);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, false);
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, false);
+
+ controller.init();
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.total_clearing,
+ "After pref controller init from configured state, primary pref shouldn't be changed from total clearing"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kSanitizeOnShutdown),
+ true,
+ "After pref contoller init from total clearing mode configured state, secondary pref for sanitize on shutdown should be set to true"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kPrivateBrowsingAutostart),
+ true,
+ "After pref contoller init from total clearing mode configured state, secondary pref for pbm autostart should be set to true"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kKeepPermissionInMemory),
+ true,
+ "After pref contoller init from total clearing mode configured state, secondary pref for memory only permissions should be set to true"
+ );
+
+ controller.shutdown();
+
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes.selective_saving
+ );
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, false);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, false);
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, false);
+
+ controller.init();
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.selective_saving,
+ "After pref controller init from configured state, primary pref shouldn't be changed from normal browsing"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kSanitizeOnShutdown),
+ true,
+ "After pref contoller init from selective saving mode configured state, secondary pref for sanitize on shutdown should be set to true"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kPrivateBrowsingAutostart),
+ false,
+ "After pref contoller init from selective saving mode configured state, secondary pref for pbm autostart should be set to false"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kKeepPermissionInMemory),
+ false,
+ "After pref contoller init from selective saving mode configured state, secondary pref for memory only permissions should be set to false"
+ );
+
+ controller.shutdown();
+
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes.normal_browsing
+ );
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, true);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, true);
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, true);
+
+ controller.init();
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.normal_browsing,
+ "After pref controller init from configured state, primary pref shouldn't be changed from normal browsing"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kSanitizeOnShutdown),
+ false,
+ "After pref contoller init from normal browsing mode configured state, secondary pref for sanitize on shutdown should be set to false"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kPrivateBrowsingAutostart),
+ false,
+ "After pref contoller init from normal browsing mode configured state, secondary pref for pbm autostart should be set to false"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kKeepPermissionInMemory),
+ false,
+ "After pref contoller init from normal browsing mode configured state, secondary pref for memory only permissions should be set to false"
+ );
+
+ controller.shutdown();
+
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes.custom
+ );
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, false);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, false);
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, false);
+
+ controller.init();
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.custom,
+ "After pref controller init from configured state, primary pref shouldn't be changed from custom"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kSanitizeOnShutdown),
+ false,
+ "After pref contoller init from custom mode configured state, secondary pref for sanitize on shutdown should remain unchanged"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kPrivateBrowsingAutostart),
+ false,
+ "After pref contoller init from custom mode configured state, secondary pref for pbm autostart should remain unchanged"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kKeepPermissionInMemory),
+ false,
+ "After pref contoller init from custom mode configured state, secondary pref for memory only permissions should remain unchanged"
+ );
+});
+
+add_task(async function test_secondary_prefs_update_mode() {
+ controller.init();
+
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, true);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, true);
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, true);
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.total_clearing,
+ "After pref update mode should be set to total clearing"
+ );
+
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, false);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, false);
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, true);
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.selective_saving,
+ "After pref update mode should be set to selective saving"
+ );
+
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, false);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, false);
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, false);
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.normal_browsing,
+ "After pref update mode should be set to normal browsing"
+ );
+
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, false);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, false);
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, false);
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.normal_browsing,
+ "Duplicate for idempotency. after pref update mode should be set to normal browsing"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kSanitizeOnShutdown),
+ false,
+ "Duplicate for idempotency. Changing browsing mode to normal browsing should set sanitize on shutdown to false"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kPrivateBrowsingAutostart),
+ false,
+ "Duplicate for idempotency. Changing browsing mode to normal browsing should set pbm autostart to false"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kKeepPermissionInMemory),
+ false,
+ "Duplicate for idempotency. Changing browsing mode to normal browsing should set memory only permissions to false"
+ );
+
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, false);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, true);
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, true);
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.custom,
+ "After pref update mode should be set to custom"
+ );
+
+ resetToTotalSavingPrefs();
+
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes.custom
+ );
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, false);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, false);
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.selective_saving,
+ "Manually changing to custom browsing via primary pref then changing secondary prefs to match another browsing mode should leave custom browsing"
+ );
+
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, true);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, true);
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, false);
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.custom,
+ "Secondary Prefs set to (true, true, false) should result in Custom Browsing Mode"
+ );
+
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, true);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, false);
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, true);
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.custom,
+ "Secondary Prefs set to (true, false, true) should result in Custom Browsing Mode"
+ );
+
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, false);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, false);
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, true);
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.custom,
+ "Secondary Prefs set to (false, false, true) should result in Custom Browsing Mode"
+ );
+
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, false);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, true);
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, false);
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.custom,
+ "Secondary Prefs set to (false, true, false) should result in Custom Browsing Mode"
+ );
+
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, false);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, true);
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, true);
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.custom,
+ "Secondary Prefs set to (false, true, true) should result in Custom Browsing Mode"
+ );
+});
+
+add_task(async function test_mode_updates_from_secondary_prefs() {
+ controller.init();
+
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, false);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, false);
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, false);
+
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes.total_clearing
+ );
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.total_clearing,
+ "Changing browsing mode to total clearing should set browsing mode to match"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kSanitizeOnShutdown),
+ true,
+ "Changing browsing mode to total clearing should set sanitize on shutdown to true"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kPrivateBrowsingAutostart),
+ true,
+ "Changing browsing mode to total clearing should set pbm autostart to true"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kKeepPermissionInMemory),
+ true,
+ "Changing browsing mode to total clearing should set memory only permissions to true"
+ );
+
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes.total_clearing
+ );
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.total_clearing,
+ "Duplicate for idempotency. Changing browsing mode to total clearing should set browsing mode to match"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kSanitizeOnShutdown),
+ true,
+ "Duplicate for idempotency. Changing browsing mode to total clearing should set sanitize on shutdown to true"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kPrivateBrowsingAutostart),
+ true,
+ "Duplicate for idempotency. Changing browsing mode to total clearing should set pbm autostart to true"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kKeepPermissionInMemory),
+ true,
+ "Duplicate for idempotency. Changing browsing mode to total clearing should set memory only permissions to true"
+ );
+
+ resetToTotalSavingPrefs();
+
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes.selective_saving
+ );
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.selective_saving,
+ "Changing browsing mode to selective saving should set browsing mode to match"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kSanitizeOnShutdown),
+ true,
+ "Changing browsing mode to selective saving should set sanitize on shutdown to true"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kPrivateBrowsingAutostart),
+ false,
+ "Changing browsing mode to selective saving should set pbm autostart to false"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kKeepPermissionInMemory),
+ false,
+ "Changing browsing mode to selective saving should set memory only permissions to false"
+ );
+
+ resetToTotalSavingPrefs();
+
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes.normal_browsing
+ );
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.normal_browsing,
+ "Changing browsing mode to normal browsing should set browsing mode to match"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kSanitizeOnShutdown),
+ false,
+ "Changing browsing mode to normal browsing should set sanitize on shutdown to true"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kPrivateBrowsingAutostart),
+ false,
+ "Changing browsing mode to normal browsing should set pbm autostart to false"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kKeepPermissionInMemory),
+ false,
+ "Changing browsing mode to normal browsing should set memory only permissions to false"
+ );
+
+ resetToTotalSavingPrefs();
+
+ for (const sanitize of [false, true]) {
+ for (const pbmAutostart of [false, true]) {
+ for (const permissions of [false, true]) {
+ Services.prefs.setBoolPref(kSanitizeOnShutdown, sanitize);
+ Services.prefs.setBoolPref(kPrivateBrowsingAutostart, pbmAutostart);
+ Services.prefs.setBoolPref(kKeepPermissionInMemory, permissions);
+
+ if (sanitize && pbmAutostart && permissions) {
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.total_clearing,
+ "pref settings for true true true should result in total clearing"
+ );
+ } else if (sanitize && !pbmAutostart && !permissions) {
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.selective_saving,
+ "pref settings for true false false should result in selective saving"
+ );
+ } else if (!sanitize && !pbmAutostart && !permissions) {
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.normal_browsing,
+ "pref settings for false false false should result in normal browsing"
+ );
+ } else {
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.custom,
+ `pref settings of ${sanitize} ${pbmAutostart} ${permissions} should result in custom browsing`
+ );
+ }
+ }
+ }
+ }
+
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes.custom
+ );
+
+ Assert.equal(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.custom,
+ "Manually changing to custom browsing via primary pref should happen regardless of secondary prefs"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kSanitizeOnShutdown),
+ true,
+ "Changing browsing mode to custom should leave sanitize on shutdown as previously set"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kPrivateBrowsingAutostart),
+ true,
+ "Changing browsing mode to custom should leave pbm autostart as previously set"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kKeepPermissionInMemory),
+ true,
+ "Changing browsing mode to custom should leave memory only permissions as previously set"
+ );
+
+ resetToTotalSavingPrefs();
+
+ Services.prefs.setIntPref(
+ kSelectiveSavingModePref,
+ SelectiveSavingModes.unconfigured
+ );
+
+ Assert.notEqual(
+ Services.prefs.getIntPref(kSelectiveSavingModePref),
+ SelectiveSavingModes.unconfigured,
+ "Manually changing to unconfigured mode via primary pref should set primary pref based on secondary prefs"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kSanitizeOnShutdown),
+ true,
+ "Changing browsing mode to unconfigured should leave sanitize on shutdown as previously set"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kPrivateBrowsingAutostart),
+ true,
+ "Changing browsing mode to unconfigured should leave pbm autostart to true as previously set"
+ );
+
+ Assert.equal(
+ Services.prefs.getBoolPref(kKeepPermissionInMemory),
+ true,
+ "Changing browsing mode to unconfigured should leave memory only permissions to true as previously set"
+ );
+});
=====================================
toolkit/components/selectivesavingmode/tests/xpcshell/xpcshell.toml
=====================================
@@ -0,0 +1,7 @@
+[DEFAULT]
+tags = "mullvad"
+prefs = [
+ "browser.selectiveSavingMode.enabled=true"
+]
+
+["test_selective_saving_mode.js"]
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/59b…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/59b…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/tor-browser][tor-browser-153.0esr-16.0-1] fixup! [android] Disable features and functionality
by Dan Ballard (@dan) 11 Aug '26
by Dan Ballard (@dan) 11 Aug '26
11 Aug '26
Dan Ballard pushed to branch tor-browser-153.0esr-16.0-1 at The Tor Project / Applications / Tor Browser
Commits:
5fac2896 by clairehurst at 2026-08-10T14:56:50-07:00
fixup! [android] Disable features and functionality
Bug 45098: Remove share link button
- - - - -
3 changed files:
- mobile/android/android-components/components/compose/browser-toolbar/src/main/java/mozilla/components/compose/browser/toolbar/ActionContainer.kt
- mobile/android/android-components/components/compose/browser-toolbar/src/main/java/mozilla/components/compose/browser/toolbar/concept/Action.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMiddleware.kt
Changes:
=====================================
mobile/android/android-components/components/compose/browser-toolbar/src/main/java/mozilla/components/compose/browser/toolbar/ActionContainer.kt
=====================================
@@ -37,6 +37,8 @@ import mozilla.components.compose.browser.toolbar.ui.TabCounter
import mozilla.components.compose.browser.toolbar.ui.ActionButton as ActionButtonComposable
import mozilla.components.ui.icons.R as iconsR
+import mozilla.components.compose.browser.toolbar.concept.NoopAction
+
/**
* A container for displaying [Action]s.
*
@@ -122,6 +124,8 @@ fun ActionContainer(
)
}
}
+
+ else -> NoopAction()
}
}
}
=====================================
mobile/android/android-components/components/compose/browser-toolbar/src/main/java/mozilla/components/compose/browser/toolbar/concept/Action.kt
=====================================
@@ -189,3 +189,5 @@ sealed class Action {
val onClick: BrowserToolbarInteraction,
) : Action()
}
+
+class NoopAction : Action()
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMiddleware.kt
=====================================
@@ -143,6 +143,8 @@ import mozilla.components.lib.state.Action as MVIAction
import mozilla.components.ui.icons.R as iconsR
import mozilla.components.ui.tabcounter.R as tabcounterR
+import mozilla.components.compose.browser.toolbar.concept.NoopAction
+
@VisibleForTesting
internal sealed class DisplayActions(override val source: Source) : BrowserToolbarEvent {
data class MenuClicked(override val source: Source) : DisplayActions(source)
@@ -1318,17 +1320,13 @@ class BrowserToolbarMiddleware(
)
}
- ToolbarAction.Share -> ActionButtonRes(
- drawableResId = iconsR.drawable.mozac_ic_share_android_24,
- contentDescription = R.string.browser_menu_share,
- onClick = ShareClicked(source),
- )
-
ToolbarAction.Homepage -> ActionButtonRes(
drawableResId = iconsR.drawable.mozac_ic_home_24,
contentDescription = R.string.browser_menu_homepage,
onClick = HomepageClicked(source),
)
+
+ else -> NoopAction()
}
private fun buildSiteInfoAction(
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/5fac289…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/5fac289…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/mullvad-browser][mullvad-browser-153.0esr-16.0-1] 2 commits: fixup! BB 41916: Letterboxing preferences UI
by morgan (@morgan) 10 Aug '26
by morgan (@morgan) 10 Aug '26
10 Aug '26
morgan pushed to branch mullvad-browser-153.0esr-16.0-1 at The Tor Project / Applications / Mullvad Browser
Commits:
37b63f8b by Henry Wilkes at 2026-08-10T16:39:30+00:00
fixup! BB 41916: Letterboxing preferences UI
BB 45079: Move letterboxing controls into appearance settings.
- - - - -
74547cf2 by Henry Wilkes at 2026-08-10T16:39:35+00:00
fixup! Base Browser strings
BB 45079: Combine letterboxing setting strings together.
- - - - -
8 changed files:
- browser/components/preferences/config/appearance.mjs
- browser/components/preferences/letterboxing.js → browser/components/preferences/config/letterboxing.mjs
- browser/components/preferences/jar.mn
- − browser/components/preferences/letterboxing.inc.xhtml
- browser/components/preferences/main.inc.xhtml
- browser/components/preferences/main.js
- browser/components/preferences/preferences.js
- toolkit/locales/en-US/toolkit/global/base-browser.ftl
Changes:
=====================================
browser/components/preferences/config/appearance.mjs
=====================================
@@ -5,6 +5,11 @@
import { Preferences } from "chrome://global/content/preferences/Preferences.mjs";
import { SettingGroupManager } from "chrome://browser/content/preferences/config/SettingGroupManager.mjs";
+ChromeUtils.importESModule(
+ "chrome://browser/content/preferences/config/letterboxing.mjs",
+ { global: "current" }
+);
+
const FORCED_COLORS_QUERY = matchMedia("(forced-colors)");
Preferences.addAll([
=====================================
browser/components/preferences/letterboxing.js → browser/components/preferences/config/letterboxing.mjs
=====================================
@@ -1,5 +1,5 @@
-/* import-globals-from preferences.js */
-/* import-globals-from findInPage.js */
+import { Preferences } from "chrome://global/content/preferences/Preferences.mjs";
+import { SettingGroupManager } from "chrome://browser/content/preferences/config/SettingGroupManager.mjs";
Preferences.addAll([
{
@@ -22,21 +22,29 @@ Preferences.addSetting({
});
Preferences.addSetting({
- id: "letterboxingRememberSize",
- pref: "privacy.resistFingerprinting.letterboxing.rememberSize",
+ id: "letterboxingWindowSize",
deps: ["letterboxingEnabled", "resistFingerprinting"],
visible: ({ letterboxingEnabled, resistFingerprinting }) => {
return letterboxingEnabled.value && resistFingerprinting.value;
},
});
+Preferences.addSetting({
+ id: "letterboxingRememberSize",
+ pref: "privacy.resistFingerprinting.letterboxing.rememberSize",
+});
+
Preferences.addSetting({
id: "letterboxingContentAlignment",
- pref: "privacy.resistFingerprinting.letterboxing.vcenter",
deps: ["letterboxingEnabled", "resistFingerprinting"],
visible: ({ letterboxingEnabled, resistFingerprinting }) => {
return letterboxingEnabled.value && resistFingerprinting.value;
},
+});
+
+Preferences.addSetting({
+ id: "letterboxingContentAlignmentOptions",
+ pref: "privacy.resistFingerprinting.letterboxing.vcenter",
get: val => {
return val ? "middle" : "top";
},
@@ -57,28 +65,21 @@ Preferences.addSetting({
return;
}
letterboxingEnabled.value = true;
- setTimeout(() => {
- // Need to re-search to remove the "hidden" attribute on the groupbox
- // elements (after the data-hidden-from-search attributes are changed by
- // the "visible" callback).
- // TODO: Is this an upstream issue that "hidden" is not removed?
- if (!gSearchResultsPane.query) {
- search(gLastCategory.category, "data-category");
- }
- // Button should have focus when activated but will be hidden now,
- // so re-assign focus to the newly revealed options.
- Services.focus.moveFocus(
- window,
- buttonEl,
- Services.focus.MOVEFOCUS_FORWARD,
- 0
- );
- });
+ // Button should have focus when activated but will be hidden now,
+ // so re-assign focus after the new section is revealed.
+ document
+ .getElementById("letterboxingWindowSize")
+ .updateComplete.then(() => {
+ document.getElementById("letterboxingRememberSize").focus();
+ });
},
});
SettingGroupManager.registerGroups({
- letterboxingDisabled: {
+ letterboxing: {
+ l10nId: "letterboxing-settings-group",
+ supportPage: "tor-manual:features__fingerprinting-protections___letterboxing",
+ headingLevel: 2,
items: [
{
id: "letterboxingShouldEnable",
@@ -93,63 +94,55 @@ SettingGroupManager.registerGroups({
},
],
},
- ],
- },
- letterboxingSize: {
- l10nId: "letterboxing-window-size-group",
- headingLevel: 2,
- items: [
{
- id: "letterboxingRememberSize",
- l10nId: "letterboxing-remember-size",
- control: "moz-checkbox",
+ id: "letterboxingWindowSize",
+ l10nId: "letterboxing-window-size-group",
+ control: "moz-fieldset",
+ controlAttrs: {
+ headinglevel: 3,
+ },
+ items: [
+ {
+ id: "letterboxingRememberSize",
+ l10nId: "letterboxing-remember-size",
+ control: "moz-checkbox",
+ },
+ ],
},
- ],
- },
- letterboxingAlignment: {
- l10nId: "letterboxing-alignment-group",
- headingLevel: 2,
- items: [
{
id: "letterboxingContentAlignment",
- control: "moz-visual-picker",
- options: [
+ l10nId: "letterboxing-alignment-group",
+ control: "moz-fieldset",
+ controlAttrs: {
+ headinglevel: 3,
+ },
+ items: [
{
- value: "top",
- l10nId: "letterboxing-alignment-top-option",
- controlAttrs: {
- class: "setting-chooser-item letterboxing-chooser-item",
- imagesrc:
- "chrome://browser/content/preferences/letterboxing-top.svg",
- },
- },
- {
- value: "middle",
- l10nId: "letterboxing-alignment-middle-option",
- controlAttrs: {
- class: "setting-chooser-item letterboxing-chooser-item",
- imagesrc:
- "chrome://browser/content/preferences/letterboxing-middle.svg",
- },
+ id: "letterboxingContentAlignmentOptions",
+ control: "moz-visual-picker",
+ options: [
+ {
+ value: "top",
+ l10nId: "letterboxing-alignment-top-option",
+ controlAttrs: {
+ class: "setting-chooser-item letterboxing-chooser-item",
+ imagesrc:
+ "chrome://browser/content/preferences/letterboxing-top.svg",
+ },
+ },
+ {
+ value: "middle",
+ l10nId: "letterboxing-alignment-middle-option",
+ controlAttrs: {
+ class: "setting-chooser-item letterboxing-chooser-item",
+ imagesrc:
+ "chrome://browser/content/preferences/letterboxing-middle.svg",
+ },
+ },
+ ],
},
],
},
],
},
});
-
-var gLetterboxingPrefs = {
- init() {
- const rfpSetting = Preferences.getSetting("resistFingerprinting");
- const updateCategoryVisibility = () => {
- document
- .getElementById("letterboxingCategory")
- .classList.toggle("hide-all-letterboxing", !rfpSetting.value);
- };
- rfpSetting.on("change", updateCategoryVisibility);
- updateCategoryVisibility();
- initSettingGroup("letterboxingDisabled");
- initSettingGroup("letterboxingSize");
- initSettingGroup("letterboxingAlignment");
- },
-};
=====================================
browser/components/preferences/jar.mn
=====================================
@@ -71,7 +71,7 @@ browser.jar:
content/browser/preferences/widgets/update-state.mjs (widgets/update-state/update-state.mjs)
content/browser/preferences/widgets/update-state.css (widgets/update-state/update-state.css)
- content/browser/preferences/letterboxing.js
- content/browser/preferences/letterboxing.css
- content/browser/preferences/letterboxing-middle.svg
- content/browser/preferences/letterboxing-top.svg
+ content/browser/preferences/config/letterboxing.mjs (config/letterboxing.mjs)
+ content/browser/preferences/letterboxing.css (letterboxing.css)
+ content/browser/preferences/letterboxing-middle.svg (letterboxing-middle.svg)
+ content/browser/preferences/letterboxing-top.svg (letterboxing-top.svg)
=====================================
browser/components/preferences/letterboxing.inc.xhtml deleted
=====================================
@@ -1,26 +0,0 @@
-<script src="chrome://browser/content/preferences/letterboxing.js" />
-<vbox
- id="letterboxingCategory"
- class="subcategory"
- hidden="true"
- data-category="paneGeneral"
->
- <html:h1 data-l10n-id="letterboxing-header" />
- <description class="letterboxing-overview description-deemphasized">
- <html:span data-l10n-id="letterboxing-overview"></html:span>
- <html:a
- is="moz-support-link"
- support-page="tor-manual:features__fingerprinting-protections___letterboxing"
- data-l10n-id="letterboxing-learn-more"
- ></html:a>
- </description>
-</vbox>
-<groupbox data-category="paneGeneral" hidden="true">
- <html:setting-group groupid="letterboxingDisabled"></html:setting-group>
-</groupbox>
-<groupbox data-category="paneGeneral" hidden="true">
- <html:setting-group groupid="letterboxingSize"></html:setting-group>
-</groupbox>
-<groupbox data-category="paneGeneral" hidden="true">
- <html:setting-group groupid="letterboxingAlignment"></html:setting-group>
-</groupbox>
=====================================
browser/components/preferences/main.inc.xhtml
=====================================
@@ -40,7 +40,7 @@
<html:setting-group groupid="browserLayout" data-category="paneGeneral" data-subcategory="layout" data-srd-migrated="" hidden="true"></html:setting-group>
<!-- Letterboxing -->
-#include ./letterboxing.inc.xhtml
+<html:setting-group data-category="paneGeneral" groupid="letterboxing" data-srd-migrated="" hidden="true"></html:setting-group>
<hbox id="languageAndAppearanceCategory"
class="subcategory"
=====================================
browser/components/preferences/main.js
=====================================
@@ -10,7 +10,6 @@
/** @import { HandlerInfoWrapper, ApplicationListItem } from './config/downloads.mjs';*/
/* import-globals-from extensionControlled.js */
-/* import-globals-from letterboxing.js */
/* import-globals-from preferences.js */
/* import-globals-from /toolkit/mozapps/preferences/fontbuilder.js */
/* import-globals-from /browser/base/content/aboutDialog-appUpdater.js */
@@ -834,6 +833,7 @@ var gMainPane = {
initSettingGroup("tabs");
initSettingGroup("profiles");
initSettingGroup("profilePane");
+ initSettingGroup("letterboxing");
setEventListener("manageBrowserLanguagesButton", "command", function () {
gMainPane.showBrowserLanguagesSubDialog({ search: false });
@@ -850,8 +850,6 @@ var gMainPane = {
// Listen for window unload so we can remove our preference observers.
window.addEventListener("unload", this);
- gLetterboxingPrefs.init();
-
// Notify observers that the UI is now ready
Services.obs.notifyObservers(window, "main-pane-loaded");
this.setInitialized();
=====================================
browser/components/preferences/preferences.js
=====================================
@@ -244,7 +244,7 @@ const CONFIG_PANES = Object.freeze({
},
appearance: {
l10nId: "preferences-appearance-header",
- groupIds: ["appearance", "browserTheme", "relatedSettings"],
+ groupIds: ["appearance", "browserTheme", "letterboxing", "relatedSettings"],
module: "chrome://browser/content/preferences/config/appearance.mjs",
iconSrc: "chrome://global/skin/icons/eye.svg",
visible: () => srdSectionPrefs.all,
=====================================
toolkit/locales/en-US/toolkit/global/base-browser.ftl
=====================================
@@ -81,6 +81,12 @@ letterboxing-header = Letterboxing
# "Letterboxing" should be treated as a feature/product name, and likely not changed in other languages.
letterboxing-overview = { -brand-short-name }'s Letterboxing feature restricts websites to display at specific sizes, making it harder to single out users on the basis of their window or screen size.
letterboxing-learn-more = Learn more
+# The word "Letterboxing" is the proper noun for the Tor Browser feature, and is therefore capitalised.
+# "Letterboxing" should be treated as a feature/product name, and likely not changed in other languages.
+# "{ -brand-short-name }" will be replaced with the localized name of the browser, e.g. "Tor Browser".
+letterboxing-settings-group =
+ .label = Letterboxing
+ .description = { -brand-short-name }'s Letterboxing feature restricts websites to display at specific sizes, making it harder to single out users on the basis of their window or screen size.
letterboxing-window-size-group =
.label = Window size
letterboxing-remember-size =
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/b0…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/b0…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/tor-browser][tor-browser-153.0esr-16.0-1] 3 commits: fixup! BB 41916: Letterboxing preferences UI
by morgan (@morgan) 10 Aug '26
by morgan (@morgan) 10 Aug '26
10 Aug '26
morgan pushed to branch tor-browser-153.0esr-16.0-1 at The Tor Project / Applications / Tor Browser
Commits:
b45d554c by Henry Wilkes at 2026-08-10T15:31:38+00:00
fixup! BB 41916: Letterboxing preferences UI
BB 45079: Move letterboxing controls into appearance settings.
- - - - -
8c8eb548 by Henry Wilkes at 2026-08-10T15:31:38+00:00
fixup! Base Browser strings
BB 45079: Combine letterboxing setting strings together.
- - - - -
fcb13195 by Henry Wilkes at 2026-08-10T15:31:38+00:00
fixup! Tor Browser localization migration scripts.
TB 45079: Add migration to combine letterboxing setting strings
together.
- - - - -
9 changed files:
- browser/components/preferences/config/appearance.mjs
- browser/components/preferences/letterboxing.js → browser/components/preferences/config/letterboxing.mjs
- browser/components/preferences/jar.mn
- − browser/components/preferences/letterboxing.inc.xhtml
- browser/components/preferences/main.inc.xhtml
- browser/components/preferences/main.js
- browser/components/preferences/preferences.js
- toolkit/locales/en-US/toolkit/global/base-browser.ftl
- + tools/torbrowser/l10n/migrations/bug-45079-letterboxing-settings-single.py
Changes:
=====================================
browser/components/preferences/config/appearance.mjs
=====================================
@@ -5,6 +5,11 @@
import { Preferences } from "chrome://global/content/preferences/Preferences.mjs";
import { SettingGroupManager } from "chrome://browser/content/preferences/config/SettingGroupManager.mjs";
+ChromeUtils.importESModule(
+ "chrome://browser/content/preferences/config/letterboxing.mjs",
+ { global: "current" }
+);
+
const FORCED_COLORS_QUERY = matchMedia("(forced-colors)");
Preferences.addAll([
=====================================
browser/components/preferences/letterboxing.js → browser/components/preferences/config/letterboxing.mjs
=====================================
@@ -1,5 +1,5 @@
-/* import-globals-from preferences.js */
-/* import-globals-from findInPage.js */
+import { Preferences } from "chrome://global/content/preferences/Preferences.mjs";
+import { SettingGroupManager } from "chrome://browser/content/preferences/config/SettingGroupManager.mjs";
Preferences.addAll([
{
@@ -22,21 +22,29 @@ Preferences.addSetting({
});
Preferences.addSetting({
- id: "letterboxingRememberSize",
- pref: "privacy.resistFingerprinting.letterboxing.rememberSize",
+ id: "letterboxingWindowSize",
deps: ["letterboxingEnabled", "resistFingerprinting"],
visible: ({ letterboxingEnabled, resistFingerprinting }) => {
return letterboxingEnabled.value && resistFingerprinting.value;
},
});
+Preferences.addSetting({
+ id: "letterboxingRememberSize",
+ pref: "privacy.resistFingerprinting.letterboxing.rememberSize",
+});
+
Preferences.addSetting({
id: "letterboxingContentAlignment",
- pref: "privacy.resistFingerprinting.letterboxing.vcenter",
deps: ["letterboxingEnabled", "resistFingerprinting"],
visible: ({ letterboxingEnabled, resistFingerprinting }) => {
return letterboxingEnabled.value && resistFingerprinting.value;
},
+});
+
+Preferences.addSetting({
+ id: "letterboxingContentAlignmentOptions",
+ pref: "privacy.resistFingerprinting.letterboxing.vcenter",
get: val => {
return val ? "middle" : "top";
},
@@ -57,28 +65,21 @@ Preferences.addSetting({
return;
}
letterboxingEnabled.value = true;
- setTimeout(() => {
- // Need to re-search to remove the "hidden" attribute on the groupbox
- // elements (after the data-hidden-from-search attributes are changed by
- // the "visible" callback).
- // TODO: Is this an upstream issue that "hidden" is not removed?
- if (!gSearchResultsPane.query) {
- search(gLastCategory.category, "data-category");
- }
- // Button should have focus when activated but will be hidden now,
- // so re-assign focus to the newly revealed options.
- Services.focus.moveFocus(
- window,
- buttonEl,
- Services.focus.MOVEFOCUS_FORWARD,
- 0
- );
- });
+ // Button should have focus when activated but will be hidden now,
+ // so re-assign focus after the new section is revealed.
+ document
+ .getElementById("letterboxingWindowSize")
+ .updateComplete.then(() => {
+ document.getElementById("letterboxingRememberSize").focus();
+ });
},
});
SettingGroupManager.registerGroups({
- letterboxingDisabled: {
+ letterboxing: {
+ l10nId: "letterboxing-settings-group",
+ supportPage: "tor-manual:features__fingerprinting-protections___letterboxing",
+ headingLevel: 2,
items: [
{
id: "letterboxingShouldEnable",
@@ -93,63 +94,55 @@ SettingGroupManager.registerGroups({
},
],
},
- ],
- },
- letterboxingSize: {
- l10nId: "letterboxing-window-size-group",
- headingLevel: 2,
- items: [
{
- id: "letterboxingRememberSize",
- l10nId: "letterboxing-remember-size",
- control: "moz-checkbox",
+ id: "letterboxingWindowSize",
+ l10nId: "letterboxing-window-size-group",
+ control: "moz-fieldset",
+ controlAttrs: {
+ headinglevel: 3,
+ },
+ items: [
+ {
+ id: "letterboxingRememberSize",
+ l10nId: "letterboxing-remember-size",
+ control: "moz-checkbox",
+ },
+ ],
},
- ],
- },
- letterboxingAlignment: {
- l10nId: "letterboxing-alignment-group",
- headingLevel: 2,
- items: [
{
id: "letterboxingContentAlignment",
- control: "moz-visual-picker",
- options: [
+ l10nId: "letterboxing-alignment-group",
+ control: "moz-fieldset",
+ controlAttrs: {
+ headinglevel: 3,
+ },
+ items: [
{
- value: "top",
- l10nId: "letterboxing-alignment-top-option",
- controlAttrs: {
- class: "setting-chooser-item letterboxing-chooser-item",
- imagesrc:
- "chrome://browser/content/preferences/letterboxing-top.svg",
- },
- },
- {
- value: "middle",
- l10nId: "letterboxing-alignment-middle-option",
- controlAttrs: {
- class: "setting-chooser-item letterboxing-chooser-item",
- imagesrc:
- "chrome://browser/content/preferences/letterboxing-middle.svg",
- },
+ id: "letterboxingContentAlignmentOptions",
+ control: "moz-visual-picker",
+ options: [
+ {
+ value: "top",
+ l10nId: "letterboxing-alignment-top-option",
+ controlAttrs: {
+ class: "setting-chooser-item letterboxing-chooser-item",
+ imagesrc:
+ "chrome://browser/content/preferences/letterboxing-top.svg",
+ },
+ },
+ {
+ value: "middle",
+ l10nId: "letterboxing-alignment-middle-option",
+ controlAttrs: {
+ class: "setting-chooser-item letterboxing-chooser-item",
+ imagesrc:
+ "chrome://browser/content/preferences/letterboxing-middle.svg",
+ },
+ },
+ ],
},
],
},
],
},
});
-
-var gLetterboxingPrefs = {
- init() {
- const rfpSetting = Preferences.getSetting("resistFingerprinting");
- const updateCategoryVisibility = () => {
- document
- .getElementById("letterboxingCategory")
- .classList.toggle("hide-all-letterboxing", !rfpSetting.value);
- };
- rfpSetting.on("change", updateCategoryVisibility);
- updateCategoryVisibility();
- initSettingGroup("letterboxingDisabled");
- initSettingGroup("letterboxingSize");
- initSettingGroup("letterboxingAlignment");
- },
-};
=====================================
browser/components/preferences/jar.mn
=====================================
@@ -71,7 +71,7 @@ browser.jar:
content/browser/preferences/widgets/update-state.mjs (widgets/update-state/update-state.mjs)
content/browser/preferences/widgets/update-state.css (widgets/update-state/update-state.css)
- content/browser/preferences/letterboxing.js
- content/browser/preferences/letterboxing.css
- content/browser/preferences/letterboxing-middle.svg
- content/browser/preferences/letterboxing-top.svg
+ content/browser/preferences/config/letterboxing.mjs (config/letterboxing.mjs)
+ content/browser/preferences/letterboxing.css (letterboxing.css)
+ content/browser/preferences/letterboxing-middle.svg (letterboxing-middle.svg)
+ content/browser/preferences/letterboxing-top.svg (letterboxing-top.svg)
=====================================
browser/components/preferences/letterboxing.inc.xhtml deleted
=====================================
@@ -1,26 +0,0 @@
-<script src="chrome://browser/content/preferences/letterboxing.js" />
-<vbox
- id="letterboxingCategory"
- class="subcategory"
- hidden="true"
- data-category="paneGeneral"
->
- <html:h1 data-l10n-id="letterboxing-header" />
- <description class="letterboxing-overview description-deemphasized">
- <html:span data-l10n-id="letterboxing-overview"></html:span>
- <html:a
- is="moz-support-link"
- support-page="tor-manual:features__fingerprinting-protections___letterboxing"
- data-l10n-id="letterboxing-learn-more"
- ></html:a>
- </description>
-</vbox>
-<groupbox data-category="paneGeneral" hidden="true">
- <html:setting-group groupid="letterboxingDisabled"></html:setting-group>
-</groupbox>
-<groupbox data-category="paneGeneral" hidden="true">
- <html:setting-group groupid="letterboxingSize"></html:setting-group>
-</groupbox>
-<groupbox data-category="paneGeneral" hidden="true">
- <html:setting-group groupid="letterboxingAlignment"></html:setting-group>
-</groupbox>
=====================================
browser/components/preferences/main.inc.xhtml
=====================================
@@ -40,7 +40,7 @@
<html:setting-group groupid="browserLayout" data-category="paneGeneral" data-subcategory="layout" data-srd-migrated="" hidden="true"></html:setting-group>
<!-- Letterboxing -->
-#include ./letterboxing.inc.xhtml
+<html:setting-group data-category="paneGeneral" groupid="letterboxing" data-srd-migrated="" hidden="true"></html:setting-group>
<hbox id="languageAndAppearanceCategory"
class="subcategory"
=====================================
browser/components/preferences/main.js
=====================================
@@ -10,7 +10,6 @@
/** @import { HandlerInfoWrapper, ApplicationListItem } from './config/downloads.mjs';*/
/* import-globals-from extensionControlled.js */
-/* import-globals-from letterboxing.js */
/* import-globals-from preferences.js */
/* import-globals-from /toolkit/mozapps/preferences/fontbuilder.js */
/* import-globals-from /browser/base/content/aboutDialog-appUpdater.js */
@@ -841,6 +840,7 @@ var gMainPane = {
initSettingGroup("tabs");
initSettingGroup("profiles");
initSettingGroup("profilePane");
+ initSettingGroup("letterboxing");
setEventListener("manageBrowserLanguagesButton", "command", function () {
gMainPane.showBrowserLanguagesSubDialog({ search: false });
@@ -857,8 +857,6 @@ var gMainPane = {
// Listen for window unload so we can remove our preference observers.
window.addEventListener("unload", this);
- gLetterboxingPrefs.init();
-
// Notify observers that the UI is now ready
Services.obs.notifyObservers(window, "main-pane-loaded");
this.setInitialized();
=====================================
browser/components/preferences/preferences.js
=====================================
@@ -245,7 +245,7 @@ const CONFIG_PANES = Object.freeze({
},
appearance: {
l10nId: "preferences-appearance-header",
- groupIds: ["appearance", "browserTheme", "relatedSettings"],
+ groupIds: ["appearance", "browserTheme", "letterboxing", "relatedSettings"],
module: "chrome://browser/content/preferences/config/appearance.mjs",
iconSrc: "chrome://global/skin/icons/eye.svg",
visible: () => srdSectionPrefs.all,
=====================================
toolkit/locales/en-US/toolkit/global/base-browser.ftl
=====================================
@@ -81,6 +81,12 @@ letterboxing-header = Letterboxing
# "Letterboxing" should be treated as a feature/product name, and likely not changed in other languages.
letterboxing-overview = { -brand-short-name }'s Letterboxing feature restricts websites to display at specific sizes, making it harder to single out users on the basis of their window or screen size.
letterboxing-learn-more = Learn more
+# The word "Letterboxing" is the proper noun for the Tor Browser feature, and is therefore capitalised.
+# "Letterboxing" should be treated as a feature/product name, and likely not changed in other languages.
+# "{ -brand-short-name }" will be replaced with the localized name of the browser, e.g. "Tor Browser".
+letterboxing-settings-group =
+ .label = Letterboxing
+ .description = { -brand-short-name }'s Letterboxing feature restricts websites to display at specific sizes, making it harder to single out users on the basis of their window or screen size.
letterboxing-window-size-group =
.label = Window size
letterboxing-remember-size =
=====================================
tools/torbrowser/l10n/migrations/bug-45079-letterboxing-settings-single.py
=====================================
@@ -0,0 +1,16 @@
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ ctx.add_transforms(
+ "base-browser.ftl",
+ "base-browser.ftl",
+ transforms_from(
+ """
+letterboxing-settings-group =
+ .label = { COPY_PATTERN(path, "letterboxing-header") }
+ .description = { COPY_PATTERN(path, "letterboxing-overview") }
+""",
+ path="base-browser.ftl",
+ ),
+ )
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/492c55…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/492c55…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/tor-browser][tor-browser-153.0esr-16.0-1] 3 commits: fixup! TB 31286: Implementation of bridge, proxy, and firewall settings in...
by morgan (@morgan) 07 Aug '26
by morgan (@morgan) 07 Aug '26
07 Aug '26
morgan pushed to branch tor-browser-153.0esr-16.0-1 at The Tor Project / Applications / Tor Browser
Commits:
6aa05c11 by Henry Wilkes at 2026-08-06T19:42:53+01:00
fixup! TB 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
TB 45196: Replace old connection settings with setting-group elements.
These can eventually be dropped entirely after we switch to the settings
redesign, but this allows us to do a lot of tidying in advance.
- - - - -
6054bf5a by Henry Wilkes at 2026-08-06T19:42:55+01:00
fixup! Tor Browser strings
TB 45196: Drop old strings.
- - - - -
492c55b8 by Henry Wilkes at 2026-08-06T19:42:55+01:00
fixup! Add TorStrings module for localization
TB 45196: Drop some old strings.
- - - - -
9 changed files:
- browser/components/preferences/preferences.js
- browser/components/torpreferences/config/connection.mjs
- browser/components/torpreferences/content/connectionPane.inc.xhtml
- − browser/components/torpreferences/content/connectionPane.js
- browser/components/torpreferences/content/torPreferences.css
- browser/components/torpreferences/jar.mn
- toolkit/locales/en-US/toolkit/global/tor-browser.ftl
- toolkit/modules/TorStrings.sys.mjs
- toolkit/torbutton/chrome/locale/en-US/settings.properties
Changes:
=====================================
browser/components/preferences/preferences.js
=====================================
@@ -12,7 +12,6 @@
/* import-globals-from findInPage.js */
/* import-globals-from /browser/base/content/utilityOverlay.js */
/* import-globals-from /toolkit/content/preferencesBindings.js */
-/* import-globals-from ../torpreferences/content/connectionPane.js */
/** @import MozButton from "chrome://global/content/elements/moz-button.mjs" */
/** @import {SettingConfig, SettingEmitChange} from "chrome://global/content/preferences/Setting.mjs" */
@@ -116,7 +115,7 @@ ChromeUtils.defineESModuleGetters(this, {
"resource:///modules/SelectionChangedMenulist.sys.mjs",
ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
SiteDataManager: "resource:///modules/SiteDataManager.sys.mjs",
- TorConnect: "resource://gre/modules/TorConnect.sys.mjs",
+ TorConnect: "moz-src:///toolkit/modules/TorConnect.sys.mjs",
TransientPrefs: "resource:///modules/TransientPrefs.sys.mjs",
UIState: "resource://services-sync/UIState.sys.mjs",
UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
@@ -574,12 +573,22 @@ function init_all() {
register_module("paneSync", gSyncPane);
}
register_module("paneSearchResults", gSearchResultsPane);
- if (gConnectionPane.enabled) {
- document.getElementById("category-connection").hidden = false;
- register_module("paneConnection", gConnectionPane);
- } else {
- // Remove the pane from the DOM so it doesn't get incorrectly included in search results.
- document.getElementById("template-paneConnection").remove();
+ if (!redesignEnabled) {
+ if (TorConnect.enabled) {
+ register_module("paneConnection", {
+ init() {
+ ChromeUtils.importESModule(
+ "chrome://browser/content/torpreferences/config/connection.mjs",
+ { global: "current" }
+ );
+ initSettingGroup("connectionStatus");
+ initSettingGroup("torBridges");
+ initSettingGroup("torAdvanced");
+ },
+ });
+ } else {
+ document.getElementById("category-connection").remove();
+ }
}
for (let [id, config] of Object.entries(CONFIG_PANES)) {
// Skip over configs we do not want, including all its children.
=====================================
browser/components/torpreferences/config/connection.mjs
=====================================
@@ -35,7 +35,6 @@ const TOR_BRIDGES_EMAIL = "bridges(a)torproject.org";
SettingGroupManager.registerGroups({
connectionStatus: {
- inProgress: true,
l10nId: "tor-connection-internet-status-group",
supportPage: "tor-manual:getting-started__about-tor-browser",
headingLevel: 2,
@@ -64,7 +63,6 @@ SettingGroupManager.registerGroups({
],
},
torBridges: {
- inProgress: true,
l10nId: "tor-bridges-group",
supportPage: "tor-manual:bridges",
headingLevel: 2,
@@ -193,7 +191,6 @@ SettingGroupManager.registerGroups({
],
},
torAdvanced: {
- inProgress: true,
l10nId: "tor-advanced-group",
headingLevel: 2,
items: [
=====================================
browser/components/torpreferences/content/connectionPane.inc.xhtml
=====================================
@@ -246,642 +246,30 @@
</html:fieldset>
</html:template>
-<!-- Tor panel -->
-
-<script
- type="application/javascript"
- src="chrome://browser/content/torpreferences/connectionPane.js"
+<hbox
+ id="torPreferencesCategory"
+ class="subcategory"
+ data-category="paneConnection"
+ hidden="true"
+ data-srd-groupid="connectionStatus"
+>
+ <html:h1 data-l10n-id="tor-connection-settings-heading"></html:h1>
+</hbox>
+<html:setting-group
+ groupid="connectionStatus"
+ data-category="paneConnection"
+ hidden="hidden"
+ data-srd-migrated=""
+/>
+<html:setting-group
+ groupid="torBridges"
+ data-category="paneConnection"
+ hidden="hidden"
+ data-srd-migrated=""
+/>
+<html:setting-group
+ groupid="torAdvanced"
+ data-category="paneConnection"
+ hidden="hidden"
+ data-srd-migrated=""
/>
-<html:template id="template-paneConnection">
- <vbox
- id="torPreferencesCategory"
- class="subcategory"
- data-category="paneConnection"
- hidden="true"
- data-srd-groupid="connectionStatus"
- >
- <html:h1 data-l10n-id="tor-connection-settings-heading"></html:h1>
- <description class="description-deemphasized" flex="1">
- <html:span data-l10n-id="tor-connection-overview"></html:span>
- <label
- class="learnMore text-link"
- is="text-link"
- href="about:manual#getting-started__about-tor-browser"
- useoriginprincipal="true"
- data-l10n-id="tor-connection-browser-learn-more-link"
- />
- </description>
- <!-- Keep within #torPreferencesCategory so this won't appear in search
- - results. -->
- <html:div
- id="network-status-internet-area"
- class="network-status-area"
- role="group"
- aria-labelledby="network-status-internet-area-label"
- >
- <html:img alt="" class="network-status-icon" />
- <!-- NOTE: We do not wrap the label and status ("Internet: Offline", etc)
- - in an aria-live area because it may be too noisey and may not be
- - important to the user. -->
- <html:span
- id="network-status-internet-area-label"
- class="network-status-label"
- data-l10n-id="tor-connection-internet-status-label"
- ></html:span>
- <html:span class="network-status-result"></html:span>
- </html:div>
- <html:div
- id="network-status-tor-area"
- class="network-status-area"
- role="group"
- aria-labelledby="network-status-tor-area-label"
- >
- <html:img alt="" class="network-status-icon" />
- <!-- NOTE: We do not wrap the label and status
- - ("Tor network: Not connected", etc) in an aria-live area.
- - This is not likely to change whilst this page has focus.
- - Moreover, the status is already present in the torconnect status bar
- - in the window tab bar. -->
- <html:span
- id="network-status-tor-area-label"
- class="network-status-label"
- data-l10n-id="tor-connection-network-status-label"
- ></html:span>
- <html:span class="network-status-result"></html:span>
- <html:button
- id="network-status-tor-connect-button"
- data-l10n-id="tor-connection-network-status-connect-button"
- ></html:button>
- </html:div>
- </vbox>
-
- <!-- Quickstart -->
- <groupbox
- data-category="paneConnection"
- hidden="true"
- data-srd-groupid="connectionStatus"
- >
- <label>
- <html:h2 data-l10n-id="tor-connection-automatic-heading"></html:h2>
- </label>
- <description
- class="description-deemphasized"
- flex="1"
- data-l10n-id="tor-connection-automatic-description"
- />
- <html:moz-toggle
- id="tor-connection-quickstart-toggle"
- class="tor-toggle"
- data-l10n-id="tor-connection-quickstart-checkbox"
- data-l10n-attrs="label"
- />
- </groupbox>
-
- <!-- Bridges -->
- <hbox
- class="subcategory"
- data-category="paneConnection"
- hidden="true"
- data-srd-groupid="connectionStatus"
- >
- <html:h1
- id="tor-bridges-subcategory-heading-non-search"
- class="tor-bridges-subcategory-heading tor-focusable-heading"
- tabindex="-1"
- data-l10n-id="tor-bridges-heading"
- ></html:h1>
- </hbox>
- <groupbox
- id="torPreferences-bridges-group"
- data-category="paneConnection"
- hidden="true"
- aria-labelledby="tor-bridges-subcategory-heading-non-search"
- data-srd-groupid="connectionStatus"
- >
- <!-- Add a search-header that only appears in search results as a substitute
- - for the hidden h1 element. See tor-browser#43320.
- - NOTE: Usually the first xul:label will act as the accessible name for
- - a xul:groubbox element *if* it is not hidden. Since the search-header
- - is sometimes hidden we need an explicit aria-labelledby anyway.
- - However, we keep the wrapper xul:label for styling consistency with the
- - other settings. -->
- <label class="search-header" hidden="true">
- <html:h2
- class="tor-bridges-subcategory-heading tor-focusable-heading"
- tabindex="-1"
- data-l10n-id="tor-bridges-heading"
- ></html:h2>
- </label>
- <description class="description-deemphasized" flex="1">
- <html:span data-l10n-id="tor-bridges-overview"></html:span>
- <label
- class="learnMore text-link"
- is="text-link"
- href="about:manual#circumvention__unblocking-tor"
- useoriginprincipal="true"
- data-l10n-id="tor-bridges-learn-more-link"
- />
- </description>
- <hbox
- align="center"
- id="torPreferences-bridges-locationGroup"
- hidden="true"
- >
- <label
- id="torPreferences-bridges-locationLabel"
- control="torPreferences-bridges-location"
- />
- <spacer flex="1" />
- <menulist id="torPreferences-bridges-location">
- <menupopup id="torPreferences-bridges-locationEntries" />
- </menulist>
- <button
- id="torPreferences-bridges-buttonChooseBridgeForMe"
- class="primary tor-button"
- />
- </hbox>
- <html:moz-toggle
- id="tor-bridges-enabled-toggle"
- class="tor-toggle"
- data-l10n-id="tor-bridges-use-bridges"
- data-l10n-attrs="label"
- />
- <!-- Add an aria-live area where we can post notifications to screen
- - reader users about changes to their list of bridges. This is to give
- - these users some feedback for when the remove a bridge or change
- - their bridges in other ways. I.e. whenever tor-bridges-grid-display
- - changes its rows.
- -
- - If we change the text in #tor-bridges-update-area-text, a screen
- - reader should speak out the text to the user, even when this area
- - does not have focus.
- -
- - In fact, we don't really want the user to navigate to this element
- - directly. But currently using an aria-live region in the DOM is the
- - only way to effectively pass a notification to a screen reader user.
- - Since it must be somewhere in the DOM, we logically place it just
- - before the grid, where it is hopefully least confusing to stumble
- - across.
- -
- - TODO: Instead of aria-live in the DOM, use the proposed ariaNotify
- - API if it gets accepted into firefox and works with screen readers.
- - See https://github.com/WICG/proposals/issues/112
- -->
- <!-- NOTE: This area is hidden by default, and is only shown temporarily
- - when a notification is added. It should never match with search
- - queries. -->
- <html:div
- id="tor-bridges-update-area"
- hidden="hidden"
- data-hidden-from-search="true"
- >
- <!-- NOTE: This first span's text content will *not* be read out as part
- - of the notification because it does not have an aria-live
- - attribute. Instead it is just here to give context to the following
- - text in #tor-bridges-update-area-text if the user navigates to
- - #tor-bridges-update-area manually whilst it is not hidden.
- - I.e. it is just here to make it less confusing if a screen reader
- - user stumbles across this.
- -->
- <html:span data-l10n-id="tor-bridges-update-area-intro"></html:span>
- <!-- Whitespace between spans. -->
- <!-- This second span is the area to place notification text in. -->
- <html:span
- id="tor-bridges-update-area-text"
- aria-live="polite"
- ></html:span>
- </html:div>
- <html:div id="tor-bridges-none" hidden="hidden">
- <html:img id="tor-bridges-none-icon" alt="" />
- <html:div data-l10n-id="tor-bridges-none-added"></html:div>
- </html:div>
- <html:div id="tor-bridges-current" class="tor-bridges-box" hidden="hidden">
- <html:div id="tor-bridges-current-header-bar">
- <html:h2
- id="tor-bridges-current-heading-non-search"
- class="tor-bridges-current-heading tor-focusable-heading tor-small-heading tor-non-search-heading"
- tabindex="-1"
- data-l10n-id="tor-bridges-your-bridges"
- ></html:h2>
- <!-- Add a duplicate search heading.
- - In a search result the heading h1.tor-bridges-subcategory-heading
- - will be hidden, and the h2.tor-bridges-subcategory-heading
- - will be visible.
- - As such, all headings below h2.tor-bridges-subcategory-heading also
- - need to shift one lower in heading level to preseve the correct
- - hierarchy of - heading levels.
- - In this case we hide the <h2> heading and show the duplicate <h3>
- - heading instead.
- - See tor-browser#43320. -->
- <html:h3
- class="tor-bridges-current-heading tor-focusable-heading tor-small-heading tor-search-heading"
- tabindex="-1"
- data-l10n-id="tor-bridges-your-bridges"
- ></html:h3>
- <html:span
- id="tor-bridges-user-label"
- class="tor-bridges-source-label"
- data-l10n-id="tor-bridges-source-user"
- ></html:span>
- <html:span
- id="tor-bridges-built-in-label"
- class="tor-bridges-source-label"
- data-l10n-id="tor-bridges-source-built-in"
- ></html:span>
- <html:span
- id="tor-bridges-requested-label"
- class="tor-bridges-source-label"
- data-l10n-id="tor-bridges-source-requested"
- ></html:span>
- <html:span id="tor-bridges-lox-label" class="tor-bridges-source-label">
- <html:img id="tor-bridges-lox-label-icon" alt="" />
- <html:span data-l10n-id="tor-bridges-source-lox"></html:span>
- </html:span>
- <html:button
- id="tor-bridges-all-options-button"
- class="tor-bridges-options-button"
- aria-haspopup="menu"
- aria-expanded="false"
- aria-controls="tor-bridges-all-options-menu"
- data-l10n-id="tor-bridges-options-button"
- ></html:button>
- <html:panel-list
- id="tor-bridges-all-options-menu"
- data-hidden-from-search="true"
- >
- <html:panel-item
- id="tor-bridges-options-qr-all-menu-item"
- data-l10n-attrs="accesskey"
- data-l10n-id="tor-bridges-menu-item-qr-all-bridge-addresses"
- ></html:panel-item>
- <html:panel-item
- id="tor-bridges-options-copy-all-menu-item"
- data-l10n-attrs="accesskey"
- data-l10n-id="tor-bridges-menu-item-copy-all-bridge-addresses"
- ></html:panel-item>
- <html:panel-item
- id="tor-bridges-options-edit-all-menu-item"
- data-l10n-attrs="accesskey"
- data-l10n-id="tor-bridges-menu-item-edit-all-bridges"
- ></html:panel-item>
- <html:panel-item
- id="tor-bridges-options-remove-all-menu-item"
- data-l10n-attrs="accesskey"
- data-l10n-id="tor-bridges-menu-item-remove-all-bridges"
- ></html:panel-item>
- </html:panel-list>
- </html:div>
- <html:div id="tor-bridges-built-in-display" hidden="hidden">
- <html:div id="tor-bridges-built-in-type-name"></html:div>
- <html:div
- id="tor-bridges-built-in-connected"
- class="bridge-status-badge"
- >
- <html:div class="bridge-status-icon"></html:div>
- <html:span
- data-l10n-id="tor-bridges-built-in-status-connected"
- ></html:span>
- </html:div>
- <html:div id="tor-bridges-built-in-description"></html:div>
- </html:div>
- <html:div
- id="tor-bridges-grid-display"
- class="tor-bridges-grid"
- role="grid"
- aria-labelledby="tor-bridges-current-heading-non-search"
- hidden="hidden"
- ></html:div>
- <html:template id="tor-bridges-grid-row-template">
- <html:div class="tor-bridges-grid-row" role="row">
- <!-- TODO: lox status cell for new bridges? -->
- <html:span
- class="tor-bridges-type-cell tor-bridges-grid-cell"
- role="gridcell"
- ></html:span>
- <html:span class="tor-bridges-emojis-block" role="none"></html:span>
- <html:span class="tor-bridges-grid-end-block" role="none">
- <html:span
- class="tor-bridges-address-cell tor-bridges-grid-cell"
- role="gridcell"
- >
- <html:span class="tor-bridges-address-cell-text"></html:span>
- </html:span>
- <html:span
- class="tor-bridges-status-cell tor-bridges-grid-cell"
- role="gridcell"
- >
- <html:div class="bridge-status-badge">
- <html:div class="bridge-status-icon"></html:div>
- <html:span class="tor-bridges-status-cell-text"></html:span>
- </html:div>
- </html:span>
- <html:span
- class="tor-bridges-options-cell tor-bridges-grid-cell"
- role="gridcell"
- >
- <html:button
- class="tor-bridges-options-cell-button tor-bridges-options-button tor-bridges-grid-focus"
- aria-haspopup="menu"
- aria-expanded="false"
- data-l10n-id="tor-bridges-individual-bridge-options-button"
- ></html:button>
- <html:panel-list
- class="tor-bridges-individual-options-menu"
- data-hidden-from-search="true"
- >
- <html:panel-item
- class="tor-bridges-options-qr-one-menu-item"
- data-l10n-attrs="accesskey"
- data-l10n-id="tor-bridges-menu-item-qr-address"
- ></html:panel-item>
- <html:panel-item
- class="tor-bridges-options-copy-one-menu-item"
- data-l10n-attrs="accesskey"
- data-l10n-id="tor-bridges-menu-item-copy-address"
- ></html:panel-item>
- <html:panel-item
- class="tor-bridges-options-remove-one-menu-item"
- data-l10n-attrs="accesskey"
- data-l10n-id="tor-bridges-menu-item-remove-bridge"
- ></html:panel-item>
- </html:panel-list>
- </html:span>
- </html:span>
- </html:div>
- </html:template>
- <html:div
- id="tor-bridges-share"
- class="tor-bridges-details-box"
- hidden="hidden"
- >
- <html:h3
- class="tor-bridges-share-heading tor-small-heading tor-non-search-heading"
- data-l10n-id="tor-bridges-share-heading"
- ></html:h3>
- <!-- Add a duplicate search heading. See tor-browser#43320. -->
- <html:h4
- class="tor-bridges-share-heading tor-small-heading tor-search-heading"
- data-l10n-id="tor-bridges-share-heading"
- ></html:h4>
- <html:span
- id="tor-bridges-share-description"
- data-l10n-id="tor-bridges-share-description"
- ></html:span>
- <html:button
- id="tor-bridges-copy-addresses-button"
- data-l10n-id="tor-bridges-copy-addresses-button"
- ></html:button>
- <html:button
- id="tor-bridges-qr-addresses-button"
- data-l10n-id="tor-bridges-qr-addresses-button"
- ></html:button>
- </html:div>
- <html:div id="tor-bridges-lox-status" hidden="hidden">
- <html:div data-l10n-id="tor-bridges-lox-description"></html:div>
- <html:div
- id="tor-bridges-lox-details"
- class="tor-bridges-details-box tor-bridges-lox-box"
- hidden="hidden"
- >
- <html:img alt="" class="tor-bridges-lox-image-inner" />
- <html:img alt="" class="tor-bridges-lox-image-outer" />
- <html:h3
- class="tor-bridges-lox-next-unlock-counter tor-small-heading tor-bridges-lox-intro tor-focusable-heading tor-non-search-heading"
- tabindex="-1"
- ></html:h3>
- <!-- Add a duplicate search heading. See tor-browser#43320. -->
- <html:h4
- class="tor-bridges-lox-next-unlock-counter tor-small-heading tor-bridges-lox-intro tor-focusable-heading tor-search-heading"
- tabindex="-1"
- ></html:h4>
- <html:ul class="tor-bridges-lox-list">
- <html:li
- id="tor-bridges-lox-next-unlock-gain-bridges"
- class="tor-bridges-lox-list-item tor-bridges-lox-list-item-bridge"
- data-l10n-id="tor-bridges-lox-unlock-two-bridges"
- hidden="hidden"
- ></html:li>
- <html:li
- id="tor-bridges-lox-next-unlock-first-invites"
- class="tor-bridges-lox-list-item tor-bridges-lox-list-item-invite"
- data-l10n-id="tor-bridges-lox-unlock-first-invites"
- hidden="hidden"
- ></html:li>
- <html:li
- id="tor-bridges-lox-next-unlock-more-invites"
- class="tor-bridges-lox-list-item tor-bridges-lox-list-item-invite"
- data-l10n-id="tor-bridges-lox-unlock-more-invites"
- hidden="hidden"
- ></html:li>
- </html:ul>
- <html:div
- id="tor-bridges-lox-remaining-invites"
- hidden="hidden"
- ></html:div>
- <html:button
- id="tor-bridges-lox-show-invites-button"
- class="tor-bridges-lox-button"
- data-l10n-id="tor-bridges-lox-show-invites-button"
- hidden="hidden"
- ></html:button>
- </html:div>
- <html:div
- id="tor-bridges-lox-unlock-alert"
- role="alert"
- class="tor-bridges-details-box tor-bridges-lox-box"
- hidden="hidden"
- >
- <html:img alt="" class="tor-bridges-lox-image-inner" />
- <html:img alt="" class="tor-bridges-lox-image-outer" />
- <html:div
- id="tor-bridge-unlock-alert-title"
- class="tor-small-heading tor-bridges-lox-intro"
- ></html:div>
- <html:ul class="tor-bridges-lox-list">
- <html:li
- id="tor-bridges-lox-unlock-alert-gain-bridges"
- class="tor-bridges-lox-list-item tor-bridges-lox-list-item-bridge"
- data-l10n-id="tor-bridges-lox-gained-two-bridges"
- hidden="hidden"
- ></html:li>
- <html:li
- id="tor-bridges-lox-unlock-alert-new-bridges"
- class="tor-bridges-lox-list-item tor-bridges-lox-list-item-bridge"
- data-l10n-id="tor-bridges-lox-new-bridges"
- hidden="hidden"
- ></html:li>
- <html:li
- id="tor-bridges-lox-unlock-alert-invites"
- class="tor-bridges-lox-list-item tor-bridges-lox-list-item-invite"
- hidden="hidden"
- ></html:li>
- </html:ul>
- <html:button
- id="tor-bridges-lox-unlock-alert-button"
- class="tor-bridges-lox-button"
- data-l10n-id="tor-bridges-lox-got-it-button"
- ></html:button>
- </html:div>
- </html:div>
- </html:div>
- <html:h2
- class="tor-bridges-change-heading tor-medium-heading tor-non-search-heading"
- ></html:h2>
- <!-- Add a duplicate search heading. See tor-browser#43320.
- - This has the same content, but a smaller font. -->
- <html:h3
- class="tor-bridges-change-heading tor-small-heading tor-search-heading"
- ></html:h3>
- <hbox align="center">
- <description
- flex="1"
- data-l10n-id="tor-bridges-select-built-in-description"
- />
- <html:button
- id="tor-bridges-open-built-in-dialog-button"
- class="accessory-button"
- data-l10n-id="tor-bridges-select-built-in-button"
- ></html:button>
- </hbox>
- <hbox align="center">
- <description id="tor-bridges-user-provide-description" flex="1" />
- <html:button
- id="tor-bridges-open-user-provide-dialog-button"
- class="accessory-button"
- ></html:button>
- </hbox>
- <html:h3
- class="tor-bridges-provider-heading tor-medium-heading tor-non-search-heading"
- data-l10n-id="tor-bridges-find-more-heading"
- ></html:h3>
- <!-- Add a duplicate search heading. See tor-browser#43320.
- - This has the same content, but a smaller font. -->
- <html:h4
- class="tor-bridges-provider-heading tor-small-heading tor-search-heading"
- data-l10n-id="tor-bridges-find-more-heading"
- ></html:h4>
- <description
- data-l10n-id="tor-bridges-find-more-description"
- class="description-deemphasized"
- />
- <html:div id="tor-bridges-provider-area">
- <html:ul id="tor-bridges-provider-list">
- <html:li class="tor-bridges-provider-item">
- <html:img
- id="tor-bridges-provider-icon-telegram"
- class="tor-bridges-provider-icon"
- alt=""
- />
- <html:div
- class="tor-bridges-provider-name"
- data-l10n-id="tor-bridges-provider-telegram-name"
- ></html:div>
- <html:div
- id="tor-bridges-provider-instruction-telegram"
- class="tor-bridges-provider-instruction"
- >
- <html:a data-l10n-name="user"></html:a>
- </html:div>
- </html:li>
- <html:li class="tor-bridges-provider-item">
- <html:img
- id="tor-bridges-provider-icon-web"
- class="tor-bridges-provider-icon"
- alt=""
- />
- <html:div
- class="tor-bridges-provider-name"
- data-l10n-id="tor-bridges-provider-web-name"
- ></html:div>
- <html:div
- class="tor-bridges-provider-instruction"
- data-l10n-id="tor-bridges-provider-web-instruction"
- data-l10n-args='{ "url": "bridges.torproject.org" }'
- >
- <html:a
- href="https://bridges.torproject.org"
- data-l10n-name="url"
- ></html:a>
- </html:div>
- </html:li>
- <html:li class="tor-bridges-provider-item">
- <html:img
- id="tor-bridges-provider-icon-email"
- class="tor-bridges-provider-icon"
- alt=""
- />
- <html:div
- class="tor-bridges-provider-name"
- data-l10n-id="tor-bridges-provider-email-name"
- ></html:div>
- <html:div
- class="tor-bridges-provider-instruction"
- data-l10n-id="tor-bridges-provider-email-instruction"
- data-l10n-args='{ "address": "bridges(a)torproject.org" }'
- ></html:div>
- </html:li>
- </html:ul>
- <html:div id="tor-bridges-request-box" class="tor-bridges-box">
- <html:img
- alt=""
- src="chrome://browser/content/torpreferences/bridge-bot.svg"
- ></html:img>
- <html:div
- id="tor-bridges-request-description"
- data-l10n-id="tor-bridges-request-from-browser"
- ></html:div>
- <html:button
- id="tor-bridges-open-request-dialog-button"
- data-l10n-id="tor-bridges-request-button"
- ></html:button>
- </html:div>
- </html:div>
- </groupbox>
-
- <!-- Advanced -->
- <hbox
- class="subcategory"
- data-category="paneConnection"
- hidden="true"
- data-srd-groupid="connectionStatus"
- >
- <html:h1
- id="tor-advanced-subcategory-heading-non-search"
- data-l10n-id="tor-advanced-settings-heading"
- ></html:h1>
- </hbox>
- <groupbox
- id="torPreferences-advanced-group"
- data-category="paneConnection"
- hidden="true"
- aria-labelledby="tor-advanced-subcategory-heading-non-search"
- data-srd-groupid="connectionStatus"
- >
- <!-- Add a search-header that only appears in search results as a substitute
- - for the hidden h1 element. See tor-browser#43320.
- - NOTE: Usually the first xul:label will act as the accessible name for
- - a xul:groubbox element *if* it is not hidden. Since the search-header
- - is sometimes hidden we need an explicit aria-labelledby anyway.
- - However, we keep the wrapper xul:label for styling consistency with the
- - other settings. -->
- <label class="search-header" hidden="true">
- <html:h2 data-l10n-id="tor-advanced-settings-heading"></html:h2>
- </label>
- <hbox align="center">
- <description data-l10n-id="tor-advanced-settings-description" flex="1" />
- <html:button
- id="torPreferences-advanced-button"
- class="accessory-button"
- data-l10n-id="tor-advanced-settings-button"
- ></html:button>
- </hbox>
- <hbox align="center" data-subcategory="viewlogs">
- <description data-l10n-id="tor-view-log-description" flex="1" />
- <html:button
- id="torPreferences-buttonTorLogs"
- class="accessory-button"
- data-l10n-id="tor-view-log-button"
- ></html:button>
- </hbox>
- </groupbox>
-</html:template>
=====================================
browser/components/torpreferences/content/connectionPane.js deleted
=====================================
@@ -1,2663 +0,0 @@
-// Copyright (c) 2022, The Tor Project, Inc.
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-"use strict";
-
-/* import-globals-from /browser/components/preferences/preferences.js */
-/* import-globals-from /browser/components/preferences/search.js */
-
-const { setTimeout, clearTimeout } = ChromeUtils.importESModule(
- "resource://gre/modules/Timer.sys.mjs"
-);
-
-const { TorSettings, TorSettingsTopics, TorBridgeSource } =
- ChromeUtils.importESModule("moz-src:///toolkit/modules/TorSettings.sys.mjs");
-
-const { TorParsers } = ChromeUtils.importESModule(
- "moz-src:///toolkit/components/tor-launcher/TorParsers.sys.mjs"
-);
-const { TorProviderBuilder, TorProviderTopics } = ChromeUtils.importESModule(
- "moz-src:///toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs"
-);
-
-const { InternetStatus, TorConnect, TorConnectTopics, TorConnectStage } =
- ChromeUtils.importESModule("moz-src:///toolkit/modules/TorConnect.sys.mjs");
-
-const { TorConnectParent } = ChromeUtils.importESModule(
- "moz-src:///browser/components/torconnect/TorConnectParent.sys.mjs"
-);
-
-const { QRCode } = ChromeUtils.importESModule(
- "moz-src:///toolkit/modules/QRCode.sys.mjs"
-);
-
-const { TorStrings } = ChromeUtils.importESModule(
- "moz-src:///toolkit/modules/TorStrings.sys.mjs"
-);
-
-const { Lox, LoxTopics } = ChromeUtils.importESModule(
- "moz-src:///toolkit/components/lox/Lox.sys.mjs"
-);
-
-const log = console.createInstance({
- maxLogLevel: "Warn",
- prefix: "connectionPane",
-});
-
-/*
- * Fake Lox module:
-
-const Lox = {
- levelHistory: [0, 1],
- // levelHistory: [1, 2],
- // levelHistory: [2, 3],
- // levelHistory: [3, 4],
- // levelHistory: [0, 1, 2],
- // levelHistory: [1, 2, 3],
- // levelHistory: [4, 3],
- // levelHistory: [4, 1],
- // levelHistory: [2, 1],
- //levelHistory: [2, 3, 4, 1, 2],
- // Gain some invites and then loose them all. Shouldn't show any change.
- // levelHistory: [0, 1, 2, 1],
- // levelHistory: [1, 2, 3, 1],
- getEventData() {
- let prevLevel = this.levelHistory[0];
- const events = [];
- for (let i = 1; i < this.levelHistory.length; i++) {
- const level = this.levelHistory[i];
- events.push({ type: level > prevLevel ? "levelup" : "blockage", newLevel: level });
- prevLevel = level;
- }
- return events;
- },
- clearEventData() {
- this.levelHistory = [];
- },
- nextUnlock: { date: "2024-01-31T00:00:00Z", nextLevel: 1 },
- //nextUnlock: { date: "2024-01-31T00:00:00Z", nextLevel: 2 },
- //nextUnlock: { date: "2024-01-31T00:00:00Z", nextLevel: 3 },
- //nextUnlock: { date: "2024-01-31T00:00:00Z", nextLevel: 4 },
- getNextUnlock() {
- return this.nextUnlock;
- },
- remainingInvites: 3,
- // remainingInvites: 0,
- getRemainingInviteCount() {
- return this.remainingInvites;
- },
- invites: [],
- // invites: ["a", "b"],
- getInvites() {
- return this.invites;
- },
-};
-*/
-
-/**
- * Get the ID/fingerprint of the bridge used in the most recent Tor circuit.
- *
- * @returns {string?} - The bridge ID or null if a bridge with an id was not
- * used in the last circuit.
- */
-async function getConnectedBridgeId() {
- // TODO: PieroV: We could make sure TorSettings is in sync by monitoring also
- // changes of settings. At that point, we could query it, instead of doing a
- // query over the control port.
- let bridge = null;
- try {
- const provider = await TorProviderBuilder.build();
- bridge = provider.currentBridge;
- } catch (e) {
- console.warn("Could not get current bridge", e);
- }
- return bridge?.fingerprint ?? null;
-}
-
-/**
- * Show the bridge QR to the user.
- *
- * @param {string} bridgeString - The string to use in the QR.
- */
-function showBridgeQr(bridgeString) {
- gSubDialog.open(
- "chrome://browser/content/torpreferences/bridgeQrDialog.xhtml",
- { features: "resizable=yes" },
- bridgeString
- );
-}
-
-// TODO: Instead of aria-live in the DOM, use the proposed ariaNotify
-// API if it gets accepted into firefox and works with screen readers.
-// See https://github.com/WICG/proposals/issues/112
-/**
- * Notification for screen reader users.
- */
-const gBridgesNotification = {
- /**
- * The screen reader area that shows updates.
- *
- * @type {Element?}
- */
- _updateArea: null,
- /**
- * The text for the screen reader update.
- *
- * @type {Element?}
- */
- _textEl: null,
- /**
- * A timeout for hiding the update.
- *
- * @type {integer?}
- */
- _hideUpdateTimeout: null,
-
- /**
- * Initialize the area for notifications.
- */
- init() {
- this._updateArea = document.getElementById("tor-bridges-update-area");
- this._textEl = document.getElementById("tor-bridges-update-area-text");
- },
-
- /**
- * Post a new notification, replacing any existing one.
- *
- * @param {string} type - The notification type.
- */
- post(type) {
- this._updateArea.hidden = false;
- // First we clear the update area to reset the text to be empty.
- this._textEl.removeAttribute("data-l10n-id");
- this._textEl.textContent = "";
- if (this._hideUpdateTimeout !== null) {
- clearTimeout(this._hideUpdateTimeout);
- this._hideUpdateTimeout = null;
- }
-
- let updateId;
- switch (type) {
- case "removed-one":
- updateId = "tor-bridges-update-removed-one-bridge";
- break;
- case "removed-all":
- updateId = "tor-bridges-update-removed-all-bridges";
- break;
- case "changed":
- default:
- // Generic message for when bridges change.
- updateId = "tor-bridges-update-changed-bridges";
- break;
- }
-
- // Hide the area after 5 minutes, when the update is not "recent" any
- // more.
- this._hideUpdateTimeout = setTimeout(() => {
- this._updateArea.hidden = true;
- }, 300000);
-
- // Wait a small amount of time to actually set the textContent. Otherwise
- // the screen reader (tested with Orca) may not pick up on the change in
- // text.
- setTimeout(() => {
- document.l10n.setAttributes(this._textEl, updateId);
- }, 500);
- },
-};
-
-/**
- * Controls the bridge grid.
- */
-const gBridgeGrid = {
- /**
- * The grid element.
- *
- * @type {Element?}
- */
- _grid: null,
- /**
- * The template for creating new rows.
- *
- * @type {HTMLTemplateElement?}
- */
- _rowTemplate: null,
-
- /**
- * @typedef {object} BridgeGridRow
- *
- * @property {Element} element - The row element.
- * @property {Element} optionsButton - The options button.
- * @property {Element} menu - The options menupopup.
- * @property {Element} statusEl - The bridge status element.
- * @property {Element} statusText - The status text.
- * @property {string} bridgeLine - The identifying bridge string for this row.
- * @property {string?} bridgeId - The ID/fingerprint for the bridge, or null
- * if it doesn't have one.
- * @property {integer} index - The index of the row in the grid.
- * @property {boolean} connected - Whether we are connected to the bridge
- * (recently in use for a Tor circuit).
- * @property {BridgeGridCell[]} cells - The cells that belong to the row,
- * ordered by their column.
- */
- /**
- * @typedef {object} BridgeGridCell
- *
- * @property {Element} element - The cell element.
- * @property {Element} focusEl - The element belonging to the cell that should
- * receive focus. Should be the cell element itself, or an interactive
- * focusable child.
- * @property {integer} columnIndex - The index of the column this cell belongs
- * to.
- * @property {BridgeGridRow} row - The row this cell belongs to.
- */
- /**
- * The current rows in the grid.
- *
- * @type {BridgeGridRow[]}
- */
- _rows: [],
- /**
- * The cell that should be the focus target when the user moves focus into the
- * grid, or null if the grid itself should be the target.
- *
- * @type {BridgeGridCell?}
- */
- _focusCell: null,
-
- /**
- * Initialize the bridge grid.
- */
- init() {
- this._grid = document.getElementById("tor-bridges-grid-display");
- // Initially, make only the grid itself part of the keyboard tab cycle.
- // matches _focusCell = null.
- this._grid.tabIndex = 0;
-
- this._rowTemplate = document.getElementById(
- "tor-bridges-grid-row-template"
- );
-
- this._grid.addEventListener("keydown", this);
- this._grid.addEventListener("mousedown", this);
- this._grid.addEventListener("focusin", this);
-
- Services.obs.addObserver(this, TorSettingsTopics.SettingsChanged);
-
- // NOTE: Before initializedPromise completes, this area is hidden.
- TorSettings.initializedPromise.then(() => {
- this._updateRows(true);
- });
- },
-
- /**
- * Uninitialize the bridge grid.
- */
- uninit() {
- Services.obs.removeObserver(this, TorSettingsTopics.SettingsChanged);
- this.deactivate();
- },
-
- /**
- * Whether the grid is visible and responsive.
- *
- * @type {boolean}
- */
- _active: false,
-
- /**
- * Activate and show the bridge grid.
- */
- activate() {
- if (this._active) {
- return;
- }
-
- this._active = true;
-
- Services.obs.addObserver(this, TorProviderTopics.BridgeChanged);
-
- this._grid.hidden = false;
-
- this._updateConnectedBridge();
- },
-
- /**
- * Deactivate and hide the bridge grid.
- */
- deactivate() {
- if (!this._active) {
- return;
- }
-
- this._active = false;
-
- this._forceCloseRowMenus();
-
- this._grid.hidden = true;
-
- Services.obs.removeObserver(this, TorProviderTopics.BridgeChanged);
- },
-
- observe(subject, topic) {
- switch (topic) {
- case TorSettingsTopics.SettingsChanged: {
- const { changes } = subject.wrappedJSObject;
- if (
- changes.includes("bridges.source") ||
- changes.includes("bridges.bridge_strings")
- ) {
- this._updateRows();
- }
- break;
- }
- case TorProviderTopics.BridgeChanged:
- this._updateConnectedBridge();
- break;
- }
- },
-
- handleEvent(event) {
- if (event.type === "keydown") {
- if (event.altKey || event.shiftKey || event.metaKey || event.ctrlKey) {
- // Don't interfere with these events.
- return;
- }
-
- if (this._rows.some(row => row.menu.open)) {
- // Have an open menu, let the menu handle the event instead.
- return;
- }
-
- let numRows = this._rows.length;
- if (!numRows) {
- // Nowhere for focus to go.
- return;
- }
-
- let moveRow = 0;
- let moveColumn = 0;
- const isLTR = this._grid.matches(":dir(ltr)");
- switch (event.key) {
- case "ArrowDown":
- moveRow = 1;
- break;
- case "ArrowUp":
- moveRow = -1;
- break;
- case "ArrowRight":
- moveColumn = isLTR ? 1 : -1;
- break;
- case "ArrowLeft":
- moveColumn = isLTR ? -1 : 1;
- break;
- default:
- return;
- }
-
- // Prevent scrolling the nearest scroll container.
- event.preventDefault();
-
- const curCell = this._focusCell;
- let row = curCell ? curCell.row.index + moveRow : 0;
- let column = curCell ? curCell.columnIndex + moveColumn : 0;
-
- // Clamp in bounds.
- if (row < 0) {
- row = 0;
- } else if (row >= numRows) {
- row = numRows - 1;
- }
-
- const numCells = this._rows[row].cells.length;
- if (column < 0) {
- column = 0;
- } else if (column >= numCells) {
- column = numCells - 1;
- }
-
- const newCell = this._rows[row].cells[column];
-
- if (newCell !== curCell) {
- this._setFocus(newCell);
- }
- } else if (event.type === "mousedown") {
- if (event.button !== 0) {
- return;
- }
- // Move focus index to the clicked target.
- // NOTE: Since the cells and the grid have "tabindex=-1", they are still
- // click-focusable. Therefore, the default mousedown handler will try to
- // move focus to it.
- // Rather than block this default handler, we instead re-direct the focus
- // to the correct cell in the "focusin" listener.
- const newCell = this._getCellFromTarget(event.target);
- // NOTE: If newCell is null, then we do nothing here, but instead wait for
- // the focusin handler to trigger.
- if (newCell && newCell !== this._focusCell) {
- this._setFocus(newCell);
- }
- } else if (event.type === "focusin") {
- const focusCell = this._getCellFromTarget(event.target);
- if (focusCell !== this._focusCell) {
- // Focus is not where it is expected.
- // E.g. the user has clicked the edge of the grid.
- // Restore focus immediately back to the cell we expect.
- this._setFocus(this._focusCell);
- }
- }
- },
-
- /**
- * Return the cell that was the target of an event.
- *
- * @param {Element} element - The target of an event.
- *
- * @returns {BridgeGridCell?} - The cell that the element belongs to, or null
- * if it doesn't belong to any cell.
- */
- _getCellFromTarget(element) {
- for (const row of this._rows) {
- for (const cell of row.cells) {
- if (cell.element.contains(element)) {
- return cell;
- }
- }
- }
- return null;
- },
-
- /**
- * Determine whether the document's active element (focus) is within the grid
- * or not.
- *
- * @returns {boolean} - Whether focus is within this grid or not.
- */
- _focusWithin() {
- return this._grid.contains(document.activeElement);
- },
-
- /**
- * Set the cell that should be the focus target of the grid, possibly moving
- * the document's focus as well.
- *
- * @param {BridgeGridCell?} cell - The cell to make the focus target, or null
- * if the grid itself should be the target.
- * @param {boolean} [focusWithin] - Whether focus should be moved within the
- * grid. If undefined, this will move focus if the grid currently contains
- * the document's focus.
- */
- _setFocus(cell, focusWithin) {
- if (focusWithin === undefined) {
- focusWithin = this._focusWithin();
- }
- const prevFocusElement = this._focusCell
- ? this._focusCell.focusEl
- : this._grid;
- const newFocusElement = cell ? cell.focusEl : this._grid;
-
- if (prevFocusElement !== newFocusElement) {
- prevFocusElement.tabIndex = -1;
- newFocusElement.tabIndex = 0;
- }
- // Set _focusCell now, before we potentially call "focus", which can trigger
- // the "focusin" handler.
- this._focusCell = cell;
-
- if (focusWithin) {
- // Focus was within the grid, so we need to actively move it to the new
- // element.
- newFocusElement.focus({ preventScroll: true });
- // Scroll to the whole cell into view, rather than just the focus element.
- (cell?.element ?? newFocusElement).scrollIntoView({
- block: "nearest",
- inline: "nearest",
- });
- }
- },
-
- /**
- * Reset the grids focus to be the first row's first cell, if any.
- *
- * @param {boolean} [focusWithin] - Whether focus should be moved within the
- * grid. If undefined, this will move focus if the grid currently contains
- * the document's focus.
- */
- _resetFocus(focusWithin) {
- this._setFocus(
- this._rows.length ? this._rows[0].cells[0] : null,
- focusWithin
- );
- },
-
- /**
- * The bridge ID/fingerprint of the most recently used bridge (appearing in
- * the latest Tor circuit). Roughly corresponds to the bridge we are currently
- * connected to.
- *
- * null if there are no such bridges.
- *
- * @type {string?}
- */
- _connectedBridgeId: null,
- /**
- * Update _connectedBridgeId.
- */
- async _updateConnectedBridge() {
- const bridgeId = await getConnectedBridgeId();
- if (bridgeId === this._connectedBridgeId) {
- return;
- }
- this._connectedBridgeId = bridgeId;
- for (const row of this._rows) {
- this._updateRowStatus(row);
- }
- },
-
- /**
- * Update the status of a row.
- *
- * @param {BridgeGridRow} row - The row to update.
- */
- _updateRowStatus(row) {
- const connected = row.bridgeId && this._connectedBridgeId === row.bridgeId;
- // NOTE: row.connected is initially undefined, so won't match `connected`.
- if (connected === row.connected) {
- return;
- }
-
- row.connected = connected;
-
- const noStatus = !connected;
-
- row.element.classList.toggle("hide-status", noStatus);
- row.statusEl.classList.toggle("bridge-status-none", noStatus);
- row.statusEl.classList.toggle("bridge-status-connected", connected);
-
- if (connected) {
- document.l10n.setAttributes(
- row.statusText,
- "tor-bridges-status-connected"
- );
- } else {
- document.l10n.setAttributes(row.statusText, "tor-bridges-status-none");
- }
- },
-
- /**
- * Create a new row for the grid.
- *
- * @param {string} bridgeLine - The bridge line for this row, which also acts
- * as its ID.
- *
- * @returns {BridgeGridRow} - A new row, with then "index" unset and the
- * "element" without a parent.
- */
- _createRow(bridgeLine) {
- let details;
- try {
- details = TorParsers.parseBridgeLine(bridgeLine);
- } catch (e) {
- console.error(`Detected invalid bridge line: ${bridgeLine}`, e);
- }
- const row = {
- element: this._rowTemplate.content.children[0].cloneNode(true),
- bridgeLine,
- bridgeId: details?.id ?? null,
- cells: [],
- };
-
- const emojiBlock = row.element.querySelector(".tor-bridges-emojis-block");
- const BridgeEmoji = customElements.get("tor-bridge-emoji");
- for (const cell of BridgeEmoji.createForAddress(bridgeLine)) {
- // Each emoji is its own cell, we rely on the fact that createForAddress
- // always returns four elements.
- cell.setAttribute("role", "gridcell");
- cell.classList.add("tor-bridges-grid-cell", "tor-bridges-emoji-cell");
- emojiBlock.append(cell);
- }
-
- for (const [columnIndex, element] of row.element
- .querySelectorAll(".tor-bridges-grid-cell")
- .entries()) {
- const focusEl =
- element.querySelector(".tor-bridges-grid-focus") ?? element;
- // Set a negative tabIndex, this makes the element click-focusable but not
- // part of the tab navigation sequence.
- focusEl.tabIndex = -1;
- row.cells.push({ element, focusEl, columnIndex, row });
- }
-
- const transport = details?.transport ?? "vanilla";
- const typeCell = row.element.querySelector(".tor-bridges-type-cell");
- if (transport === "vanilla") {
- document.l10n.setAttributes(typeCell, "tor-bridges-type-prefix-generic");
- } else {
- document.l10n.setAttributes(typeCell, "tor-bridges-type-prefix", {
- type: transport,
- });
- }
-
- row.element.querySelector(".tor-bridges-address-cell-text").textContent =
- bridgeLine;
-
- row.statusEl = row.element.querySelector(
- ".tor-bridges-status-cell .bridge-status-badge"
- );
- row.statusText = row.element.querySelector(".tor-bridges-status-cell-text");
-
- this._initRowMenu(row);
-
- this._updateRowStatus(row);
- return row;
- },
-
- /**
- * The row menu index used for generating new ids.
- *
- * @type {integer}
- */
- _rowMenuIndex: 0,
- /**
- * Generate a new id for the options menu.
- *
- * @returns {string} - The new id.
- */
- _generateRowMenuId() {
- const id = `tor-bridges-individual-options-menu-${this._rowMenuIndex}`;
- // Assume we won't run out of ids.
- this._rowMenuIndex++;
- return id;
- },
-
- /**
- * Initialize the shared menu for a row.
- *
- * @param {BridgeGridRow} row - The row to initialize the menu of.
- */
- _initRowMenu(row) {
- row.menu = row.element.querySelector(
- ".tor-bridges-individual-options-menu"
- );
- row.optionsButton = row.element.querySelector(
- ".tor-bridges-options-cell-button"
- );
-
- row.menu.id = this._generateRowMenuId();
- row.optionsButton.setAttribute("aria-controls", row.menu.id);
-
- row.optionsButton.addEventListener("click", event => {
- row.menu.toggle(event);
- });
-
- row.menu.addEventListener("hidden", () => {
- // Make sure the button receives focus again when the menu is hidden.
- // Currently, panel-list.js only does this when the menu is opened with a
- // keyboard, but this causes focus to be lost from the page if the user
- // uses a mixture of keyboard and mouse.
- row.optionsButton.focus();
- });
-
- const qrItem = row.menu.querySelector(
- ".tor-bridges-options-qr-one-menu-item"
- );
- const removeItem = row.menu.querySelector(
- ".tor-bridges-options-remove-one-menu-item"
- );
- row.menu.addEventListener("showing", () => {
- const show =
- this._bridgeSource === TorBridgeSource.UserProvided ||
- this._bridgeSource === TorBridgeSource.BridgeDB;
- qrItem.hidden = !show;
- removeItem.hidden = !show;
- });
-
- qrItem.addEventListener("click", () => {
- const bridgeLine = row.bridgeLine;
- if (!bridgeLine) {
- return;
- }
- showBridgeQr(bridgeLine);
- });
- row.menu
- .querySelector(".tor-bridges-options-copy-one-menu-item")
- .addEventListener("click", () => {
- const clipboard = Cc[
- "@mozilla.org/widget/clipboardhelper;1"
- ].getService(Ci.nsIClipboardHelper);
- clipboard.copyString(row.bridgeLine);
- });
- removeItem.addEventListener("click", () => {
- const bridgeLine = row.bridgeLine;
- const source = TorSettings.bridges.source;
- const strings = TorSettings.bridges.bridge_strings;
- const index = strings.indexOf(bridgeLine);
- if (index === -1) {
- return;
- }
- strings.splice(index, 1);
-
- if (strings.length) {
- TorSettings.changeSettings({
- bridges: { source, bridge_strings: strings },
- });
- } else {
- // Remove all bridges and disable.
- TorSettings.changeSettings({
- bridges: { source: TorBridgeSource.Invalid },
- });
- }
- });
- },
-
- /**
- * Force the row menu to close.
- */
- _forceCloseRowMenus() {
- for (const row of this._rows) {
- row.menu.hide(null, { force: true });
- }
- },
-
- /**
- * The known bridge source.
- *
- * Initially null to indicate that it is unset.
- *
- * @type {integer?}
- */
- _bridgeSource: null,
- /**
- * The bridge sources this is shown for.
- *
- * @type {string[]}
- */
- _supportedSources: [
- TorBridgeSource.BridgeDB,
- TorBridgeSource.UserProvided,
- TorBridgeSource.Lox,
- ],
-
- /**
- * Update the grid to show the latest bridge strings.
- *
- * @param {boolean} [initializing=false] - Whether this is being called as
- * part of initialization.
- */
- _updateRows(initializing = false) {
- // Store whether we have focus within the grid, before removing or hiding
- // DOM elements.
- const focusWithin = this._focusWithin();
-
- let lostAllBridges = false;
- let newSource = false;
- const bridgeSource = TorSettings.bridges.source;
- if (bridgeSource !== this._bridgeSource) {
- newSource = true;
-
- this._bridgeSource = bridgeSource;
-
- if (this._supportedSources.includes(bridgeSource)) {
- this.activate();
- } else {
- if (this._active && bridgeSource === TorBridgeSource.Invalid) {
- lostAllBridges = true;
- }
- this.deactivate();
- }
- }
-
- const ordered = this._active
- ? TorSettings.bridges.bridge_strings.map(bridgeLine => {
- const row = this._rows.find(r => r.bridgeLine === bridgeLine);
- if (row) {
- return row;
- }
- return this._createRow(bridgeLine);
- })
- : [];
-
- // Whether we should reset the grid's focus.
- // We always reset when we have a new bridge source.
- // We reset the focus if no current Cell has focus. I.e. when adding a row
- // to an empty grid, we want the focus to move to the first item.
- // We also reset the focus if the current Cell is in a row that will be
- // removed (including if all rows are removed).
- // NOTE: In principle, if a row is removed, we could move the focus to the
- // next or previous row (in the same cell column). However, most likely if
- // the grid has the user focus, they are removing a single row using its
- // options button. In this case, returning the user to some other row's
- // options button might be more disorienting since it would not be simple
- // for them to know *which* bridge they have landed on.
- // NOTE: We do not reset the focus in other cases because we do not want the
- // user to loose their place in the grid unnecessarily.
- let resetFocus =
- newSource || !this._focusCell || !ordered.includes(this._focusCell.row);
-
- // Remove rows no longer needed from the DOM.
- let numRowsRemoved = 0;
- let rowAddedOrMoved = false;
-
- for (const row of this._rows) {
- if (!ordered.includes(row)) {
- numRowsRemoved++;
- // If the row menu was open, it will also be deleted.
- // NOTE: Since the row menu is part of the row, focusWithin will be true
- // if the menu had focus, so focus should be re-assigned.
- row.element.remove();
- }
- }
-
- // Go through all the rows to set their ".index" property and to ensure they
- // are in the correct position in the DOM.
- // NOTE: We could use replaceChildren to get the correct DOM structure, but
- // we want to avoid rebuilding the entire tree when a single row is added or
- // removed.
- for (const [index, row] of ordered.entries()) {
- row.index = index;
- const element = row.element;
- // Get the expected previous element, that should already be in the DOM
- // from the previous loop.
- const prevEl = index ? ordered[index - 1].element : null;
-
- if (
- element.parentElement === this._grid &&
- prevEl === element.previousElementSibling
- ) {
- // Already in the correct position in the DOM.
- continue;
- }
-
- rowAddedOrMoved = true;
- // NOTE: Any elements already in the DOM, but not in the correct position
- // will be removed and re-added by the below command.
- // NOTE: if the row has document focus, then it should remain there.
- if (prevEl) {
- prevEl.after(element);
- } else {
- this._grid.prepend(element);
- }
- }
- this._rows = ordered;
-
- // Restore any lost focus.
- if (resetFocus) {
- // If we are not active (and therefore hidden), we will not try and move
- // focus (activeElement), but may still change the *focusable* element for
- // when we are shown again.
- this._resetFocus(this._active && focusWithin);
- }
- if (!this._active && focusWithin) {
- // Move focus out of this element, which has been hidden.
- gBridgeSettings.takeFocus();
- }
-
- // Notify the user if there was some change to the DOM.
- // If we are initializing, we generate no notification since there has been
- // no change in the setting.
- if (!initializing) {
- let notificationType;
- if (lostAllBridges) {
- // Just lost all bridges, and became de-active.
- notificationType = "removed-all";
- } else if (this._rows.length) {
- // Otherwise, only generate a notification if we are still active, with
- // at least one bridge.
- // I.e. do not generate a message if the new source is "builtin".
- if (newSource) {
- // A change in source.
- notificationType = "changed";
- } else if (numRowsRemoved === 1 && !rowAddedOrMoved) {
- // Only one bridge was removed. This is most likely in response to them
- // manually removing a single bridge or using the bridge row's options
- // menu.
- notificationType = "removed-one";
- } else if (numRowsRemoved || rowAddedOrMoved) {
- // Some other change. This is most likely in response to a manual edit
- // of the existing bridges.
- notificationType = "changed";
- }
- // Else, there was no change.
- }
-
- if (notificationType) {
- gBridgesNotification.post(notificationType);
- }
- }
- },
-};
-
-/**
- * Controls the built-in bridges area.
- */
-const gBuiltinBridgesArea = {
- /**
- * The display area.
- *
- * @type {Element?}
- */
- _area: null,
- /**
- * The type name element.
- *
- * @type {Element?}
- */
- _nameEl: null,
- /**
- * The bridge type description element.
- *
- * @type {Element?}
- */
- _descriptionEl: null,
- /**
- * The connection status.
- *
- * @type {Element?}
- */
- _connectionStatusEl: null,
-
- /**
- * Initialize the built-in bridges area.
- */
- init() {
- this._area = document.getElementById("tor-bridges-built-in-display");
- this._nameEl = document.getElementById("tor-bridges-built-in-type-name");
- this._descriptionEl = document.getElementById(
- "tor-bridges-built-in-description"
- );
- this._connectionStatusEl = document.getElementById(
- "tor-bridges-built-in-connected"
- );
-
- Services.obs.addObserver(this, TorSettingsTopics.SettingsChanged);
-
- // NOTE: Before initializedPromise completes, this area is hidden.
- TorSettings.initializedPromise.then(() => {
- this._updateBridgeType(true);
- });
- },
-
- /**
- * Uninitialize the built-in bridges area.
- */
- uninit() {
- Services.obs.removeObserver(this, TorSettingsTopics.SettingsChanged);
- this.deactivate();
- },
-
- /**
- * Whether the built-in area is visible and responsive.
- *
- * @type {boolean}
- */
- _active: false,
-
- /**
- * Activate and show the built-in bridge area.
- */
- activate() {
- if (this._active) {
- return;
- }
- this._active = true;
-
- Services.obs.addObserver(this, TorProviderTopics.BridgeChanged);
-
- this._area.hidden = false;
-
- this._updateBridgeIds();
- this._updateConnectedBridge();
- },
-
- /**
- * Deactivate and hide built-in bridge area.
- */
- deactivate() {
- if (!this._active) {
- return;
- }
- this._active = false;
-
- this._area.hidden = true;
-
- Services.obs.removeObserver(this, TorProviderTopics.BridgeChanged);
- },
-
- observe(subject, topic) {
- switch (topic) {
- case TorSettingsTopics.SettingsChanged: {
- const { changes } = subject.wrappedJSObject;
- if (
- changes.includes("bridges.source") ||
- changes.includes("bridges.builtin_type")
- ) {
- this._updateBridgeType();
- }
- if (changes.includes("bridges.bridge_strings")) {
- this._updateBridgeIds();
- }
- break;
- }
- case TorProviderTopics.BridgeChanged:
- this._updateConnectedBridge();
- break;
- }
- },
-
- /**
- * Updates the shown connected state.
- */
- _updateConnectedState() {
- this._connectionStatusEl.classList.toggle(
- "bridge-status-connected",
- this._bridgeType &&
- this._connectedBridgeId &&
- this._bridgeIds.includes(this._connectedBridgeId)
- );
- },
-
- /**
- * The currently shown bridge type. Empty if deactivated, and null if
- * uninitialized.
- *
- * @type {string?}
- */
- _bridgeType: null,
- /**
- * The strings for each known bridge type.
- *
- * @type {{[key: string]: {[key: string]: string}}}
- */
- _bridgeTypeStrings: {
- obfs4: {
- name: "tor-bridges-built-in-obfs4-name",
- description: "tor-bridges-built-in-obfs4-description",
- },
- snowflake: {
- name: "tor-bridges-built-in-snowflake-name",
- description: "tor-bridges-built-in-snowflake-description",
- },
- meek: {
- name: "tor-bridges-built-in-meek-name",
- description: "tor-bridges-built-in-meek-description",
- },
- },
-
- /**
- * The known bridge source.
- *
- * Initially null to indicate that it is unset.
- *
- * @type {integer?}
- */
- _bridgeSource: null,
-
- /**
- * Update the shown bridge type.
- *
- * @param {boolean} [initializing=false] - Whether this is being called as
- * part of initialization.
- */
- async _updateBridgeType(initializing = false) {
- let lostAllBridges = false;
- let newSource = false;
- const bridgeSource = TorSettings.bridges.source;
- if (bridgeSource !== this._bridgeSource) {
- newSource = true;
-
- this._bridgeSource = bridgeSource;
-
- if (bridgeSource === TorBridgeSource.BuiltIn) {
- this.activate();
- } else {
- if (this._active && bridgeSource === TorBridgeSource.Invalid) {
- lostAllBridges = true;
- }
- const hadFocus = this._area.contains(document.activeElement);
- this.deactivate();
- if (hadFocus) {
- gBridgeSettings.takeFocus();
- }
- }
- }
-
- const bridgeType = this._active ? TorSettings.bridges.builtin_type : "";
-
- let newType = false;
- if (bridgeType !== this._bridgeType) {
- newType = true;
-
- this._bridgeType = bridgeType;
-
- const bridgeStrings = this._bridgeTypeStrings[bridgeType];
- if (bridgeStrings) {
- document.l10n.setAttributes(this._nameEl, bridgeStrings.name);
- document.l10n.setAttributes(
- this._descriptionEl,
- bridgeStrings.description
- );
- } else {
- // Unknown type, or no type.
- this._nameEl.removeAttribute("data-l10n-id");
- this._nameEl.textContent = bridgeType;
- this._descriptionEl.removeAttribute("data-l10n-id");
- this._descriptionEl.textContent = "";
- }
-
- this._updateConnectedState();
- }
-
- // Notify the user if there was some change to the type.
- // If we are initializing, we generate no notification since there has been
- // no change in the setting.
- if (!initializing) {
- let notificationType;
- if (lostAllBridges) {
- // Just lost all bridges, and became de-active.
- notificationType = "removed-all";
- } else if (this._active && (newSource || newType)) {
- // Otherwise, only generate a notification if we are still active, with
- // a bridge type.
- // I.e. do not generate a message if the new source is not "builtin".
- notificationType = "changed";
- }
-
- if (notificationType) {
- gBridgesNotification.post(notificationType);
- }
- }
- },
-
- /**
- * The bridge IDs/fingerprints for the built-in bridges.
- *
- * @type {Array<string>}
- */
- _bridgeIds: [],
- /**
- * Update _bridgeIds
- */
- _updateBridgeIds() {
- this._bridgeIds = [];
- for (const bridgeLine of TorSettings.bridges.bridge_strings) {
- try {
- this._bridgeIds.push(TorParsers.parseBridgeLine(bridgeLine).id);
- } catch (e) {
- console.error(`Detected invalid bridge line: ${bridgeLine}`, e);
- }
- }
-
- this._updateConnectedState();
- },
-
- /**
- * The bridge ID/fingerprint of the most recently used bridge (appearing in
- * the latest Tor circuit). Roughly corresponds to the bridge we are currently
- * connected to.
- *
- * @type {string?}
- */
- _connectedBridgeId: null,
- /**
- * Update _connectedBridgeId.
- */
- async _updateConnectedBridge() {
- this._connectedBridgeId = await getConnectedBridgeId();
- this._updateConnectedState();
- },
-};
-
-/**
- * Controls the bridge pass area.
- */
-const gLoxStatus = {
- /**
- * The status area.
- *
- * @type {Element?}
- */
- _area: null,
- /**
- * The area for showing the next unlock and invites.
- *
- * @type {Element?}
- */
- _detailsArea: null,
- /**
- * The list items showing the next unlocks.
- *
- * @type {?{[key: string]: Element}}
- */
- _nextUnlockItems: null,
- /**
- * The day counter headings for the next unlock.
- *
- * One heading is shown during a search, the other is shown otherwise.
- *
- * @type {?Element[]}
- */
- _nextUnlockCounterEls: null,
- /**
- * Shows the number of remaining invites.
- *
- * @type {Element?}
- */
- _remainingInvitesEl: null,
- /**
- * The button to show the invites.
- *
- * @type {Element?}
- */
- _invitesButton: null,
- /**
- * The alert for new unlocks.
- *
- * @type {Element?}
- */
- _unlockAlert: null,
- /**
- * The list items showing the unlocks.
- *
- * @type {?{[key: string]: Element}}
- */
- _unlockItems: null,
- /**
- * The alert title.
- *
- * @type {Element?}
- */
- _unlockAlertTitle: null,
- /**
- * The alert invites item.
- *
- * @type {Element?}
- */
- _unlockAlertInvitesItem: null,
- /**
- * Button for the user to dismiss the alert.
- *
- * @type {Element?}
- */
- _unlockAlertButton: null,
-
- /**
- * Initialize the bridge pass area.
- */
- init() {
- if (!Lox.enabled) {
- // Area should remain inactive and hidden.
- return;
- }
-
- this._area = document.getElementById("tor-bridges-lox-status");
- this._detailsArea = document.getElementById("tor-bridges-lox-details");
- this._nextUnlockItems = {
- gainBridges: document.getElementById(
- "tor-bridges-lox-next-unlock-gain-bridges"
- ),
- firstInvites: document.getElementById(
- "tor-bridges-lox-next-unlock-first-invites"
- ),
- moreInvites: document.getElementById(
- "tor-bridges-lox-next-unlock-more-invites"
- ),
- };
- this._nextUnlockCounterEls = Array.from(
- document.querySelectorAll(".tor-bridges-lox-next-unlock-counter")
- );
- this._remainingInvitesEl = document.getElementById(
- "tor-bridges-lox-remaining-invites"
- );
- this._invitesButton = document.getElementById(
- "tor-bridges-lox-show-invites-button"
- );
- this._unlockAlert = document.getElementById("tor-bridges-lox-unlock-alert");
- this._unlockItems = {
- gainBridges: document.getElementById(
- "tor-bridges-lox-unlock-alert-gain-bridges"
- ),
- newBridges: document.getElementById(
- "tor-bridges-lox-unlock-alert-new-bridges"
- ),
- invites: document.getElementById("tor-bridges-lox-unlock-alert-invites"),
- };
- this._unlockAlertTitle = document.getElementById(
- "tor-bridge-unlock-alert-title"
- );
- this._unlockAlertInviteItem = document.getElementById(
- "tor-bridges-lox-unlock-alert-invites"
- );
- this._unlockAlertButton = document.getElementById(
- "tor-bridges-lox-unlock-alert-button"
- );
-
- this._invitesButton.addEventListener("click", () => {
- gSubDialog.open(
- "chrome://browser/content/torpreferences/loxInviteDialog.xhtml",
- { features: "resizable=yes" }
- );
- });
- this._unlockAlertButton.addEventListener("click", () => {
- Lox.clearEventData(this._loxId);
- });
-
- Services.obs.addObserver(this, TorSettingsTopics.SettingsChanged);
- Services.obs.addObserver(this, LoxTopics.UpdateActiveLoxId);
- Services.obs.addObserver(this, LoxTopics.UpdateEvents);
- Services.obs.addObserver(this, LoxTopics.UpdateNextUnlock);
- Services.obs.addObserver(this, LoxTopics.UpdateRemainingInvites);
- Services.obs.addObserver(this, LoxTopics.NewInvite);
-
- // NOTE: Before initializedPromise completes, this area is hidden.
- TorSettings.initializedPromise.then(() => {
- this._updateLoxId();
- });
- },
-
- /**
- * Uninitialize the built-in bridges area.
- */
- uninit() {
- if (!Lox.enabled) {
- return;
- }
-
- Services.obs.removeObserver(this, TorSettingsTopics.SettingsChanged);
- Services.obs.removeObserver(this, LoxTopics.UpdateActiveLoxId);
- Services.obs.removeObserver(this, LoxTopics.UpdateEvents);
- Services.obs.removeObserver(this, LoxTopics.UpdateNextUnlock);
- Services.obs.removeObserver(this, LoxTopics.UpdateRemainingInvites);
- Services.obs.removeObserver(this, LoxTopics.NewInvite);
- },
-
- observe(subject, topic) {
- switch (topic) {
- case TorSettingsTopics.SettingsChanged: {
- const { changes } = subject.wrappedJSObject;
- if (changes.includes("bridges.source")) {
- this._updateLoxId();
- }
- // NOTE: We do not call _updateLoxId when "bridges.lox_id" is in the
- // changes. Instead we wait until LoxTopics.UpdateActiveLoxId to ensure
- // that the Lox module has responded to the change in ID strictly
- // *before* we do. In particular, we want to make sure the invites and
- // event data has been cleared.
- break;
- }
- case LoxTopics.UpdateActiveLoxId:
- this._updateLoxId();
- break;
- case LoxTopics.UpdateNextUnlock:
- this._updateNextUnlock();
- break;
- case LoxTopics.UpdateEvents:
- this._updatePendingEvents();
- break;
- case LoxTopics.UpdateRemainingInvites:
- this._updateRemainingInvites();
- break;
- case LoxTopics.NewInvite:
- this._updateHaveExistingInvites();
- break;
- }
- },
-
- /**
- * The Lox id currently shown. Empty if deactivated, and null if
- * uninitialized.
- *
- * @type {string?}
- */
- _loxId: null,
-
- /**
- * Update the shown bridge pass.
- */
- async _updateLoxId() {
- let loxId =
- TorSettings.bridges.source === TorBridgeSource.Lox ? Lox.activeLoxId : "";
- if (loxId === this._loxId) {
- return;
- }
- this._loxId = loxId;
- this._area.hidden = !loxId;
- // We unset _nextUnlock to ensure the areas no longer use the old value for
- // the new loxId.
- this._updateNextUnlock(true);
- this._updateRemainingInvites();
- this._updateHaveExistingInvites();
- this._updatePendingEvents();
- },
-
- /**
- * The remaining invites shown, or null if uninitialized or no loxId.
- *
- * @type {integer?}
- */
- _remainingInvites: null,
- /**
- * Update the shown value.
- */
- _updateRemainingInvites() {
- const numInvites = this._loxId
- ? Lox.getRemainingInviteCount(this._loxId)
- : null;
- if (numInvites === this._remainingInvites) {
- return;
- }
- this._remainingInvites = numInvites;
- this._updateUnlockArea();
- this._updateInvitesArea();
- },
- /**
- * Whether we have existing invites, or null if uninitialized or no loxId.
- *
- * @type {boolean?}
- */
- _haveExistingInvites: null,
- /**
- * Update the shown value.
- */
- _updateHaveExistingInvites() {
- const haveInvites = this._loxId ? !!Lox.getInvites().length : null;
- if (haveInvites === this._haveExistingInvites) {
- return;
- }
- this._haveExistingInvites = haveInvites;
- this._updateInvitesArea();
- },
- /**
- * Details about the next unlock, or null if uninitialized or no loxId.
- *
- * @type {UnlockData?}
- */
- _nextUnlock: null,
- /**
- * Tracker id to ensure that the results from later calls to _updateNextUnlock
- * take priority over earlier calls.
- *
- * @type {integer}
- */
- _nextUnlockCallId: 0,
- /**
- * Update the shown value asynchronously.
- *
- * @param {boolean} [unset=false] - Whether to set the _nextUnlock value to
- * null before waiting for the new value. I.e. ensure that the current value
- * will not be used.
- */
- async _updateNextUnlock(unset = false) {
- // NOTE: We do not expect the integer to exceed the maximum integer.
- this._nextUnlockCallId++;
- const callId = this._nextUnlockCallId;
- if (unset) {
- this._nextUnlock = null;
- }
- const nextUnlock = this._loxId
- ? await Lox.getNextUnlock(this._loxId)
- : null;
- if (callId !== this._nextUnlockCallId) {
- // Replaced by another update.
- // E.g. if the _loxId changed. Or if getNextUnlock triggered
- // LoxTopics.UpdateNextUnlock.
- return;
- }
- // Should be safe to trigger the update, even when the value hasn't changed.
- this._nextUnlock = nextUnlock;
- this._updateUnlockArea();
- },
- /**
- * The list of events the user has not yet cleared, or null if uninitialized
- * or no loxId.
- *
- * @type {EventData[]?}
- */
- _pendingEvents: null,
- /**
- * Update the shown value.
- */
- _updatePendingEvents() {
- // Should be safe to trigger the update, even when the value hasn't changed.
- this._pendingEvents = this._loxId ? Lox.getEventData(this._loxId) : null;
- this._updateUnlockArea();
- },
-
- /**
- * Update the display of the current or next unlock.
- */
- _updateUnlockArea() {
- if (
- !this._loxId ||
- this._pendingEvents === null ||
- this._remainingInvites === null ||
- this._nextUnlock === null
- ) {
- // Uninitialized or no Lox source.
- // NOTE: This area may already be hidden by the change in Lox source,
- // but we clean up for the next non-empty id.
- this._unlockAlert.hidden = true;
- this._detailsArea.hidden = true;
- return;
- }
-
- // Grab focus state before changing visibility.
- const alertHadFocus = this._unlockAlert.contains(document.activeElement);
- const detailsHadFocus = this._detailsArea.contains(document.activeElement);
-
- const pendingEvents = this._pendingEvents;
- const showAlert = !!pendingEvents.length;
- this._unlockAlert.hidden = !showAlert;
- this._detailsArea.hidden = showAlert;
-
- if (showAlert) {
- // At level 0 and level 1, we do not have any invites.
- // If the user starts and ends on level 0 or 1, then overall they would
- // have had no change in their invites. So we do not want to show their
- // latest updates.
- // NOTE: If the user starts at level > 1 and ends with level 1 (levelling
- // down to level 0 should not be possible), then we *do* want to show the
- // user that they now have "0" invites.
- // NOTE: pendingEvents are time-ordered, with the most recent event
- // *last*.
- const firstEvent = pendingEvents[0];
- // NOTE: We cannot get a blockage event when the user starts at level 1 or
- // 0.
- const startingAtLowLevel =
- firstEvent.type === "levelup" && firstEvent.newLevel <= 2;
- const lastEvent = pendingEvents[pendingEvents.length - 1];
- const endingAtLowLevel = lastEvent.newLevel <= 1;
-
- const showInvites = !(startingAtLowLevel && endingAtLowLevel);
-
- let blockage = false;
- let levelUp = false;
- let bridgeGain = false;
- // Go through events, in the order that they occurred.
- for (const loxEvent of pendingEvents) {
- if (loxEvent.type === "levelup") {
- levelUp = true;
- if (loxEvent.newLevel === 1) {
- // Gain 2 bridges from level 0 to 1.
- bridgeGain = true;
- }
- } else {
- blockage = true;
- }
- }
-
- let alertTitleId;
- if (levelUp && !blockage) {
- alertTitleId = "tor-bridges-lox-upgrade";
- } else {
- // Show as blocked bridges replaced.
- // Even if we have a mixture of level ups as well.
- alertTitleId = "tor-bridges-lox-blocked";
- }
- document.l10n.setAttributes(this._unlockAlertTitle, alertTitleId);
- document.l10n.setAttributes(
- this._unlockAlertInviteItem,
- "tor-bridges-lox-new-invites",
- { numInvites: this._remainingInvites }
- );
- this._unlockAlert.classList.toggle(
- "lox-unlock-upgrade",
- levelUp && !blockage
- );
- this._unlockItems.gainBridges.hidden = !bridgeGain;
- this._unlockItems.newBridges.hidden = !blockage;
- this._unlockItems.invites.hidden = !showInvites;
- } else {
- // Show next unlock.
- // Number of days until the next unlock, rounded up.
- const numDays = Math.max(
- 1,
- Math.ceil(
- (new Date(this._nextUnlock.date).getTime() - Date.now()) /
- (24 * 60 * 60 * 1000)
- )
- );
- for (const counterEl of this._nextUnlockCounterEls) {
- document.l10n.setAttributes(
- counterEl,
- "tor-bridges-lox-days-until-unlock",
- { numDays }
- );
- }
-
- // Gain 2 bridges from level 0 to 1. After that gain invites.
- this._nextUnlockItems.gainBridges.hidden =
- this._nextUnlock.nextLevel !== 1;
- this._nextUnlockItems.firstInvites.hidden =
- this._nextUnlock.nextLevel !== 2;
- this._nextUnlockItems.moreInvites.hidden =
- this._nextUnlock.nextLevel <= 2;
- }
-
- if (alertHadFocus && !showAlert) {
- // Alert has become hidden, move focus back up to the now revealed details
- // area.
- // NOTE: We have two headings: one shown during a search and one shown
- // otherwise. We focus the heading that is currently visible.
- // See tor-browser#43320.
- // TODO: It might be better if we could use the # named anchor to
- // re-orient the screen reader position instead of using tabIndex=-1, but
- // about:preferences currently uses the anchor for showing categories
- // only. See bugzilla bug 1799153.
- if (
- this._nextUnlockCounterEls[0].checkVisibility({
- visibilityProperty: true,
- })
- ) {
- this._nextUnlockCounterEls[0].focus();
- } else {
- this._nextUnlockCounterEls[1].focus();
- }
- } else if (detailsHadFocus && showAlert) {
- this._unlockAlertButton.focus();
- }
- },
-
- /**
- * Update the invites area.
- */
- _updateInvitesArea() {
- let hasInvites;
- if (
- !this._loxId ||
- this._remainingInvites === null ||
- this._haveExistingInvites === null
- ) {
- // Not initialized yet.
- hasInvites = false;
- } else {
- hasInvites = this._haveExistingInvites || !!this._remainingInvites;
- }
-
- if (
- !hasInvites &&
- (this._remainingInvitesEl.contains(document.activeElement) ||
- this._invitesButton.contains(document.activeElement))
- ) {
- // About to loose focus.
- // Unexpected for the lox level to loose all invites.
- // Move to the top of the details area, which should be visible if we
- // just had focus.
- this._nextUnlockCounterEl.focus();
- }
- // Hide the invite elements if we have no historic invites or a way of
- // creating new ones.
- this._remainingInvitesEl.hidden = !hasInvites;
- this._invitesButton.hidden = !hasInvites;
-
- if (hasInvites) {
- document.l10n.setAttributes(
- this._remainingInvitesEl,
- "tor-bridges-lox-remaining-invites",
- { numInvites: this._remainingInvites }
- );
- }
- },
-};
-
-/**
- * Controls the bridge settings.
- */
-const gBridgeSettings = {
- /**
- * The preferences <groupbox> for bridges
- *
- * @type {Element?}
- */
- _groupEl: null,
- /**
- * The button for controlling whether bridges are enabled.
- *
- * @type {Element?}
- */
- _toggleButton: null,
- /**
- * The area for showing current bridges.
- *
- * @type {Element?}
- */
- _bridgesEl: null,
- /**
- * The area for sharing bridge addresses.
- *
- * @type {Element?}
- */
- _shareEl: null,
- /**
- * The two headings for the bridge settings.
- *
- * One heading is shown during a search, the other is shown otherwise.
- *
- * @type {?Element[]}
- */
- _bridgesSettingsHeadings: null,
- /**
- * The two headings for the current bridges, at the start of the area.
- *
- * One heading is shown during a search, the other is shown otherwise.
- *
- * @type {Element?}
- */
- _currentBridgesHeadings: null,
- /**
- * The area for showing no bridges.
- *
- * @type {Element?}
- */
- _noBridgesEl: null,
- /**
- * The heading elements for changing bridges.
- *
- * One heading is shown during a search, the other is shown otherwise.
- *
- * @type {?Element[]}
- */
- _changeHeadingEls: null,
- /**
- * The button for user to provide a bridge address or share code.
- *
- * @type {Element?}
- */
- _userProvideButton: null,
- /**
- * A map from the bridge source to its corresponding label.
- *
- * @type {?Map<number, Element>}
- */
- _sourceLabels: null,
-
- /**
- * Initialize the bridge settings.
- */
- init() {
- gBridgesNotification.init();
-
- this._bridgesSettingsHeadings = Array.from(
- document.querySelectorAll(".tor-bridges-subcategory-heading")
- );
- this._currentBridgesHeadings = Array.from(
- document.querySelectorAll(".tor-bridges-current-heading")
- );
- this._bridgesEl = document.getElementById("tor-bridges-current");
- this._noBridgesEl = document.getElementById("tor-bridges-none");
- this._groupEl = document.getElementById("torPreferences-bridges-group");
-
- this._sourceLabels = new Map([
- [
- TorBridgeSource.BuiltIn,
- document.getElementById("tor-bridges-built-in-label"),
- ],
- [
- TorBridgeSource.UserProvided,
- document.getElementById("tor-bridges-user-label"),
- ],
- [
- TorBridgeSource.BridgeDB,
- document.getElementById("tor-bridges-requested-label"),
- ],
- [TorBridgeSource.Lox, document.getElementById("tor-bridges-lox-label")],
- ]);
- this._shareEl = document.getElementById("tor-bridges-share");
-
- this._toggleButton = document.getElementById("tor-bridges-enabled-toggle");
- // Initially disabled whilst TorSettings may not be initialized.
- this._toggleButton.disabled = true;
-
- this._toggleButton.addEventListener("toggle", () => {
- if (!this._haveBridges) {
- return;
- }
- TorSettings.changeSettings({
- bridges: { enabled: this._toggleButton.pressed },
- });
- });
-
- this._changeHeadingEls = Array.from(
- document.querySelectorAll(".tor-bridges-change-heading")
- );
- this._userProvideButton = document.getElementById(
- "tor-bridges-open-user-provide-dialog-button"
- );
-
- document.l10n.setAttributes(
- document.getElementById("tor-bridges-user-provide-description"),
- // TODO: Set a different string if we have Lox enabled.
- "tor-bridges-add-addresses-description"
- );
-
- // TODO: Change to GetLoxBridges if Lox enabled, and the account is set up.
- const telegramUserName = "GetBridgesBot";
- const telegramInstruction = document.getElementById(
- "tor-bridges-provider-instruction-telegram"
- );
- telegramInstruction.querySelector("a").href =
- `https://t.me/${telegramUserName}`;
- document.l10n.setAttributes(
- telegramInstruction,
- "tor-bridges-provider-telegram-instruction",
- { telegramUserName }
- );
-
- document
- .getElementById("tor-bridges-open-built-in-dialog-button")
- .addEventListener("click", () => {
- this._openBuiltinDialog();
- });
- this._userProvideButton.addEventListener("click", () => {
- this._openUserProvideDialog(this._haveBridges ? "replace" : "add");
- });
- document
- .getElementById("tor-bridges-open-request-dialog-button")
- .addEventListener("click", () => {
- this._openRequestDialog();
- });
-
- Services.obs.addObserver(this, TorSettingsTopics.SettingsChanged);
-
- gBridgeGrid.init();
- gBuiltinBridgesArea.init();
- gLoxStatus.init();
-
- this._initBridgesMenu();
- this._initShareArea();
-
- // NOTE: Before initializedPromise completes, the current bridges sections
- // should be hidden.
- // And gBridgeGrid and gBuiltinBridgesArea are not active.
- TorSettings.initializedPromise.then(() => {
- this._updateEnabled();
- this._updateBridgeStrings();
- this._updateSource();
- });
- },
-
- /**
- * Un-initialize the bridge settings.
- */
- uninit() {
- gBridgeGrid.uninit();
- gBuiltinBridgesArea.uninit();
- gLoxStatus.uninit();
-
- Services.obs.removeObserver(this, TorSettingsTopics.SettingsChanged);
- },
-
- observe(subject, topic) {
- switch (topic) {
- case TorSettingsTopics.SettingsChanged: {
- const { changes } = subject.wrappedJSObject;
- if (changes.includes("bridges.enabled")) {
- this._updateEnabled();
- }
- if (changes.includes("bridges.source")) {
- this._updateSource();
- }
- if (changes.includes("bridges.bridge_strings")) {
- this._updateBridgeStrings();
- }
- break;
- }
- }
- },
-
- /**
- * Update whether the bridges should be shown as enabled.
- */
- _updateEnabled() {
- // Changing the pressed property on moz-toggle should not trigger its
- // "toggle" event.
- this._toggleButton.pressed = TorSettings.bridges.enabled;
- },
-
- /**
- * The shown bridge source.
- *
- * Initially null to indicate that it is unset for the first call to
- * _updateSource.
- *
- * @type {integer?}
- */
- _bridgeSource: null,
- /**
- * Whether the user is encouraged to share their bridge addresses.
- *
- * @type {boolean}
- */
- _canShare: false,
-
- /**
- * Update _bridgeSource.
- */
- _updateSource() {
- // NOTE: This should only ever be called after TorSettings is already
- // initialized.
- const bridgeSource = TorSettings.bridges.source;
- if (bridgeSource === this._bridgeSource) {
- // Avoid re-activating an area if the source has not changed.
- return;
- }
-
- this._bridgeSource = bridgeSource;
-
- // Before hiding elements, we determine whether our region contained the
- // user focus.
- const hadFocus =
- this._bridgesEl.contains(document.activeElement) ||
- this._noBridgesEl.contains(document.activeElement);
-
- for (const [source, labelEl] of this._sourceLabels.entries()) {
- labelEl.hidden = source !== bridgeSource;
- }
-
- this._canShare =
- bridgeSource === TorBridgeSource.UserProvided ||
- bridgeSource === TorBridgeSource.BridgeDB;
-
- this._shareEl.hidden = !this._canShare;
-
- // Force the menu to close whenever the source changes.
- // NOTE: If the menu had focus then hadFocus will be true, and focus will be
- // re-assigned.
- this._forceCloseBridgesMenu();
-
- // Update whether we have bridges.
- this._updateHaveBridges();
-
- if (hadFocus) {
- // Always reset the focus to the start of the area whenever the source
- // changes.
- // NOTE: gBuiltinBridges._updateBridgeType and gBridgeGrid._updateRows
- // may have already called takeFocus in response to them being
- // de-activated. The re-call should be safe.
- this.takeFocus();
- }
- },
-
- /**
- * Whether we have bridges or not, or null if it is unknown.
- *
- * @type {boolean?}
- */
- _haveBridges: null,
-
- /**
- * Update the _haveBridges value.
- */
- _updateHaveBridges() {
- // NOTE: We use the TorSettings.bridges.source value, rather than
- // this._bridgeSource because _updateHaveBridges can be called just before
- // _updateSource (via takeFocus).
- const haveBridges = TorSettings.bridges.source !== TorBridgeSource.Invalid;
-
- if (haveBridges === this._haveBridges) {
- return;
- }
-
- this._haveBridges = haveBridges;
-
- this._toggleButton.disabled = !haveBridges;
- // Add classes to show or hide the "no bridges" and "Your bridges" sections.
- // NOTE: Before haveBridges is set, neither class is added, so both sections
- // and hidden.
- this._groupEl.classList.add("bridges-initialized");
- this._bridgesEl.hidden = !haveBridges;
- this._noBridgesEl.hidden = haveBridges;
-
- for (const headingEl of this._changeHeadingEls) {
- document.l10n.setAttributes(
- headingEl,
- haveBridges
- ? "tor-bridges-replace-bridges-heading"
- : "tor-bridges-add-bridges-heading"
- );
- }
- document.l10n.setAttributes(
- this._userProvideButton,
- haveBridges ? "tor-bridges-replace-button" : "tor-bridges-add-new-button"
- );
- },
-
- /**
- * Force the focus to move to the bridge area.
- */
- takeFocus() {
- if (this._haveBridges === null) {
- // The bridges area has not been initialized yet, which means that
- // TorSettings may not be initialized.
- // Unexpected to receive a call before then, so just return early.
- return;
- }
-
- // Make sure we have the latest value for _haveBridges.
- // We also ensure that the _currentBridgesHeadings element is visible before
- // we focus it.
- this._updateHaveBridges();
-
- // Move focus to the start of the relevant section, which is a heading.
- // They have tabindex="-1" so should be focusable, even though they are not
- // part of the usual tab navigation.
- // NOTE: We have two headings: one shown during a search and one shown
- // otherwise. We focus the heading that is currently visible.
- // See tor-browser#43320.
- // TODO: It might be better if we could use the # named anchor to
- // re-orient the screen reader position instead of using tabIndex=-1, but
- // about:preferences currently uses the anchor for showing categories
- // only. See bugzilla bug 1799153.
- const focusHeadings = this._haveBridges
- ? this._currentBridgesHeadings // The heading above the new bridges.
- : this._bridgesSettingsHeadings; // The top of the bridge settings.
- if (focusHeadings[0].checkVisibility({ visibilityProperty: true })) {
- focusHeadings[0].focus();
- } else {
- focusHeadings[1].focus();
- }
- },
-
- /**
- * The bridge strings in a copy-able form.
- *
- * @type {string}
- */
- _bridgeStrings: "",
- /**
- * Whether the bridge strings should be shown as a QR code.
- *
- * @type {boolean}
- */
- _canQRBridges: false,
-
- /**
- * Update the stored bridge strings.
- */
- _updateBridgeStrings() {
- const bridges = TorSettings.bridges.bridge_strings;
-
- this._bridgeStrings = bridges.join("\n");
- // TODO: Determine what logic we want.
- this._canQRBridges = bridges.length <= 3;
-
- this._qrButton.disabled = !this._canQRBridges;
- },
-
- /**
- * Copy all the bridge addresses to the clipboard.
- */
- _copyBridges() {
- const clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
- Ci.nsIClipboardHelper
- );
- clipboard.copyString(this._bridgeStrings);
- },
-
- /**
- * Open the QR code dialog encoding all the bridge addresses.
- */
- _openQR() {
- if (!this._canQRBridges) {
- return;
- }
- showBridgeQr(this._bridgeStrings);
- },
-
- /**
- * The QR button for copying all QR codes.
- *
- * @type {Element?}
- */
- _qrButton: null,
-
- _initShareArea() {
- document
- .getElementById("tor-bridges-copy-addresses-button")
- .addEventListener("click", () => {
- this._copyBridges();
- });
-
- this._qrButton = document.getElementById("tor-bridges-qr-addresses-button");
- this._qrButton.addEventListener("click", () => {
- this._openQR();
- });
- },
-
- /**
- * The menu for all bridges.
- *
- * @type {Element?}
- */
- _bridgesMenu: null,
-
- /**
- * Initialize the menu for all bridges.
- */
- _initBridgesMenu() {
- this._bridgesMenu = document.getElementById("tor-bridges-all-options-menu");
-
- // NOTE: We generally assume that once the bridge menu is opened the
- // this._bridgeStrings value will not change.
- const qrItem = document.getElementById(
- "tor-bridges-options-qr-all-menu-item"
- );
- qrItem.addEventListener("click", () => {
- this._openQR();
- });
-
- const copyItem = document.getElementById(
- "tor-bridges-options-copy-all-menu-item"
- );
- copyItem.addEventListener("click", () => {
- this._copyBridges();
- });
-
- const editItem = document.getElementById(
- "tor-bridges-options-edit-all-menu-item"
- );
- editItem.addEventListener("click", () => {
- this._openUserProvideDialog("edit");
- });
-
- // TODO: Do we want a different item for built-in bridges, rather than
- // "Remove all bridges"?
- document
- .getElementById("tor-bridges-options-remove-all-menu-item")
- .addEventListener("click", async () => {
- // TODO: Should we only have a warning when not built-in?
- const parentWindow =
- Services.wm.getMostRecentWindow("navigator:browser");
- const flags =
- Services.prompt.BUTTON_POS_0 *
- Services.prompt.BUTTON_TITLE_IS_STRING +
- Services.prompt.BUTTON_POS_0_DEFAULT +
- Services.prompt.BUTTON_DEFAULT_IS_DESTRUCTIVE +
- Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL;
-
- const [titleString, bodyString, removeString] =
- await document.l10n.formatValues([
- { id: "remove-all-bridges-warning-title" },
- { id: "remove-all-bridges-warning-description" },
- { id: "remove-all-bridges-warning-remove-button" },
- ]);
-
- // TODO: Update the text, and remove old strings.
- const buttonIndex = Services.prompt.confirmEx(
- parentWindow,
- titleString,
- bodyString,
- flags,
- removeString,
- null,
- null,
- null,
- {}
- );
-
- if (buttonIndex !== 0) {
- return;
- }
-
- TorSettings.changeSettings({
- // This should always have the side effect of disabling bridges as
- // well.
- bridges: { source: TorBridgeSource.Invalid },
- });
- });
-
- this._bridgesMenu.addEventListener("showing", () => {
- qrItem.hidden = !this._canShare || !this._canQRBridges;
- editItem.hidden = this._bridgeSource !== TorBridgeSource.UserProvided;
- });
-
- const bridgesMenuButton = document.getElementById(
- "tor-bridges-all-options-button"
- );
- bridgesMenuButton.addEventListener("click", event => {
- this._bridgesMenu.toggle(event, bridgesMenuButton);
- });
-
- this._bridgesMenu.addEventListener("hidden", () => {
- // Make sure the button receives focus again when the menu is hidden.
- // Currently, panel-list.js only does this when the menu is opened with a
- // keyboard, but this causes focus to be lost from the page if the user
- // uses a mixture of keyboard and mouse.
- bridgesMenuButton.focus();
- });
- },
-
- /**
- * Force the bridges menu to close.
- */
- _forceCloseBridgesMenu() {
- this._bridgesMenu.hide(null, { force: true });
- },
-
- /**
- * Open a bridge dialog that will change the users bridges.
- *
- * @param {string} url - The url of the dialog to open.
- * @param {object?} inputData - The input data to send to the dialog window.
- * @param {Function} onAccept - The method to call if the bridge dialog was
- * accepted by the user. This will be passed a "result" object containing
- * data set by the dialog. This should return a promise that resolves once
- * the bridge settings have been set, or null if the settings have not
- * been applied.
- */
- _openDialog(url, inputData, onAccept) {
- const result = { accepted: false, connect: false };
- let savedSettings = null;
- gSubDialog.open(
- url,
- {
- features: "resizable=yes",
- closingCallback: () => {
- if (!result.accepted) {
- return;
- }
- savedSettings = onAccept(result);
- if (!savedSettings) {
- // No change in settings.
- return;
- }
- if (!result.connect) {
- // Do not open about:torconnect.
- return;
- }
-
- // Wait until the settings are applied before bootstrapping.
- // NOTE: Saving the settings should also cancel any existing bootstrap
- // attempt first. See tor-browser#41921.
- savedSettings.then(() => {
- // The bridge dialog button is "connect" when Tor is not
- // bootstrapped, so do the connect.
-
- // Start Bootstrapping, which should use the configured bridges.
- // NOTE: We do this regardless of any previous TorConnect Error.
- TorConnectParent.open({ beginBootstrapping: "hard" });
- });
- },
- // closedCallback should be called after gSubDialog has already
- // re-assigned focus back to the document.
- closedCallback: () => {
- if (!savedSettings) {
- return;
- }
- // Wait until the settings have changed, so that the UI could
- // respond, then move focus.
- savedSettings.then(() => gBridgeSettings.takeFocus());
- },
- },
- result,
- inputData
- );
- },
-
- /**
- * Open the built-in bridge dialog.
- */
- _openBuiltinDialog() {
- this._openDialog(
- "chrome://browser/content/torpreferences/builtinBridgeDialog.xhtml",
- null,
- result => {
- if (!result.type) {
- return null;
- }
- return TorSettings.changeSettings({
- bridges: {
- enabled: true,
- source: TorBridgeSource.BuiltIn,
- builtin_type: result.type,
- },
- });
- }
- );
- },
-
- /*
- * Open the request bridge dialog.
- */
- _openRequestDialog() {
- this._openDialog(
- "chrome://browser/content/torpreferences/requestBridgeDialog.xhtml",
- null,
- result => {
- if (!result.bridges?.length) {
- return null;
- }
- return TorSettings.changeSettings({
- bridges: {
- enabled: true,
- source: TorBridgeSource.BridgeDB,
- bridge_strings: result.bridges,
- },
- });
- }
- );
- },
-
- /**
- * Open the user provide dialog.
- *
- * @param {string} mode - The mode to open the dialog in: "add", "replace" or
- * "edit".
- */
- _openUserProvideDialog(mode) {
- this._openDialog(
- "chrome://browser/content/torpreferences/provideBridgeDialog.xhtml",
- { mode },
- result => {
- const loxId = result.loxId;
- if (!loxId && !result.addresses?.length) {
- return null;
- }
- const bridges = { enabled: true };
- if (loxId) {
- bridges.source = TorBridgeSource.Lox;
- bridges.lox_id = loxId;
- } else {
- bridges.source = TorBridgeSource.UserProvided;
- bridges.bridge_strings = result.addresses;
- }
- return TorSettings.changeSettings({ bridges });
- }
- );
- },
-};
-
-/**
- * Area to show the internet and tor network connection status.
- */
-const gNetworkStatus = {
- /**
- * Initialize the area.
- */
- init() {
- this._internetAreaEl = document.getElementById(
- "network-status-internet-area"
- );
- this._internetResultEl = this._internetAreaEl.querySelector(
- ".network-status-result"
- );
-
- this._torAreaEl = document.getElementById("network-status-tor-area");
- this._torResultEl = this._torAreaEl.querySelector(".network-status-result");
- this._torConnectButton = document.getElementById(
- "network-status-tor-connect-button"
- );
- this._torConnectButton.addEventListener("click", () => {
- TorConnectParent.open({ beginBootstrapping: "soft" });
- });
-
- this._updateInternetStatus();
- this._updateTorConnectionStatus();
-
- Services.obs.addObserver(this, TorConnectTopics.StageChange);
- Services.obs.addObserver(this, TorConnectTopics.InternetStatusChange);
- },
-
- /**
- * Un-initialize the area.
- */
- uninit() {
- Services.obs.removeObserver(this, TorConnectTopics.StageChange);
- Services.obs.removeObserver(this, TorConnectTopics.InternetStatusChange);
- },
-
- observe(subject, topic) {
- switch (topic) {
- // triggered when tor connect state changes and we may
- // need to update the messagebox
- case TorConnectTopics.StageChange:
- this._updateTorConnectionStatus();
- break;
- case TorConnectTopics.InternetStatusChange:
- this._updateInternetStatus();
- break;
- }
- },
-
- /**
- * Update the shown internet status.
- */
- _updateInternetStatus() {
- let l10nId;
- let isOffline = false;
- switch (TorConnect.internetStatus) {
- case InternetStatus.Offline:
- l10nId = "tor-connection-internet-status-offline";
- isOffline = true;
- break;
- case InternetStatus.Online:
- l10nId = "tor-connection-internet-status-online";
- break;
- default:
- l10nId = "tor-connection-internet-status-unknown";
- break;
- }
- this._internetResultEl.setAttribute("data-l10n-id", l10nId);
- this._internetAreaEl.classList.toggle("status-offline", isOffline);
- },
-
- /**
- * Update the shown Tor connection status.
- */
- _updateTorConnectionStatus() {
- const buttonHadFocus = this._torConnectButton.contains(
- document.activeElement
- );
- const isBootstrapped =
- TorConnect.stageName === TorConnectStage.Bootstrapped;
- const isBlocked = !isBootstrapped && TorConnect.potentiallyBlocked;
- let l10nId;
- if (isBootstrapped) {
- l10nId = "tor-connection-network-status-connected";
- } else if (isBlocked) {
- l10nId = "tor-connection-network-status-blocked";
- } else {
- l10nId = "tor-connection-network-status-not-connected";
- }
-
- document.l10n.setAttributes(this._torResultEl, l10nId);
- this._torAreaEl.classList.toggle("status-connected", isBootstrapped);
- this._torAreaEl.classList.toggle("status-blocked", isBlocked);
- if (isBootstrapped && buttonHadFocus) {
- // Button has become hidden and will loose focus. Most likely this has
- // happened because the user clicked the button to open about:torconnect.
- // Since this is near the top of the page, we move focus to the search
- // input (for when the user returns).
- gSearchResultsPane.searchInput.focus();
- }
- },
-};
-
-/*
- Connection Pane
-
- Code for populating the XUL in about:preferences#connection, handling input events, interfacing with tor-launcher
-*/
-const gConnectionPane = (function () {
- /* CSS selectors for all of the Tor Network DOM elements we need to access */
- const selectors = {
- bridges: {
- locationGroup: "#torPreferences-bridges-locationGroup",
- locationLabel: "#torPreferences-bridges-locationLabel",
- location: "#torPreferences-bridges-location",
- locationEntries: "#torPreferences-bridges-locationEntries",
- chooseForMe: "#torPreferences-bridges-buttonChooseBridgeForMe",
- },
- }; /* selectors */
-
- const retval = {
- // cached frequently accessed DOM elements
- _enableQuickstartToggle: null,
-
- // populate xul with strings and cache the relevant elements
- _populateXUL() {
- // Quickstart
- this._enableQuickstartToggle = document.getElementById(
- "tor-connection-quickstart-toggle"
- );
- this._enableQuickstartToggle.addEventListener("toggle", () => {
- TorConnect.quickstart = this._enableQuickstartToggle.pressed;
- });
- this._enableQuickstartToggle.pressed = TorConnect.quickstart;
- Services.obs.addObserver(this, TorConnectTopics.QuickstartChange);
-
- // Location
- {
- const prefpane = document.getElementById("mainPrefPane");
-
- const locationGroup = prefpane.querySelector(
- selectors.bridges.locationGroup
- );
- prefpane.querySelector(selectors.bridges.locationLabel).textContent =
- TorStrings.settings.bridgeLocation;
- const location = prefpane.querySelector(selectors.bridges.location);
- const locationEntries = prefpane.querySelector(
- selectors.bridges.locationEntries
- );
- const chooseForMe = prefpane.querySelector(
- selectors.bridges.chooseForMe
- );
- chooseForMe.setAttribute(
- "label",
- TorStrings.settings.bridgeChooseForMe
- );
- chooseForMe.addEventListener("command", () => {
- if (!location.value) {
- return;
- }
- TorConnectParent.open({
- beginBootstrapping: "hard",
- regionCode: location.value,
- });
- });
- const createItem = (value, label, disabled) => {
- const item = document.createXULElement("menuitem");
- item.setAttribute("value", value);
- item.setAttribute("label", label);
- if (disabled) {
- item.setAttribute("disabled", "true");
- }
- return item;
- };
-
- // TODO: Re-fetch when intl:app-locales-changed is fired, if we keep
- // this after tor-browser#42477.
- const regionNames = TorConnect.getRegionNames();
- const addLocations = codes => {
- const items = [];
- for (const code of codes) {
- items.push(createItem(code, regionNames[code] || code));
- }
- items.sort((left, right) => left.label.localeCompare(right.label));
- locationEntries.append(...items);
- };
- // Add automatic before waiting for getFrequentRegions.
- locationEntries.append(
- createItem("automatic", TorStrings.settings.bridgeLocationAutomatic)
- );
- location.value = "automatic";
- TorConnect.getFrequentRegions().then(frequentCodes => {
- locationEntries.append(
- createItem("", TorStrings.settings.bridgeLocationFrequent, true)
- );
- addLocations(frequentCodes);
- locationEntries.append(
- createItem("", TorStrings.settings.bridgeLocationOther, true)
- );
- addLocations(Object.keys(regionNames));
- });
- this._showAutoconfiguration = () => {
- locationGroup.hidden =
- !TorConnect.canBeginAutoBootstrap || !TorConnect.potentiallyBlocked;
- };
- this._showAutoconfiguration();
- }
-
- // Advanced setup
- document
- .getElementById("torPreferences-advanced-button")
- .addEventListener("click", () => {
- this.onAdvancedSettings();
- });
-
- // Tor logs
- document
- .getElementById("torPreferences-buttonTorLogs")
- .addEventListener("click", () => {
- this.onViewTorLogs();
- });
-
- Services.obs.addObserver(this, TorConnectTopics.StageChange);
- },
-
- init() {
- gBridgeSettings.init();
- gNetworkStatus.init();
-
- this._populateXUL();
-
- const onUnload = () => {
- window.removeEventListener("unload", onUnload);
- gConnectionPane.uninit();
- };
- window.addEventListener("unload", onUnload);
- },
-
- uninit() {
- gBridgeSettings.uninit();
- gNetworkStatus.uninit();
-
- // unregister our observer topics
- Services.obs.removeObserver(this, TorConnectTopics.QuickstartChange);
- Services.obs.removeObserver(this, TorConnectTopics.StageChange);
- },
-
- // whether the page should be present in about:preferences
- get enabled() {
- return TorConnect.enabled;
- },
-
- //
- // Callbacks
- //
-
- observe(subject, topic) {
- switch (topic) {
- case TorConnectTopics.QuickstartChange: {
- this._enableQuickstartToggle.pressed = TorConnect.quickstart;
- break;
- }
- // triggered when tor connect state changes and we may
- // need to update the messagebox
- case TorConnectTopics.StageChange: {
- this._showAutoconfiguration();
- break;
- }
- }
- },
-
- async onAdvancedSettings() {
- // Ensure TorSettings is complete before loading the dialog, which reads
- // from TorSettings.
- await TorSettings.initializedPromise;
- gSubDialog.open(
- "chrome://browser/content/torpreferences/connectionSettingsDialog.xhtml",
- { features: "resizable=yes" }
- );
- },
-
- onViewTorLogs() {
- gSubDialog.open(
- "chrome://browser/content/torpreferences/torLogDialog.xhtml",
- { features: "resizable=yes" }
- );
- },
- };
- return retval;
-})(); /* gConnectionPane */
=====================================
browser/components/torpreferences/content/torPreferences.css
=====================================
@@ -28,108 +28,14 @@ button.spoof-button-disabled {
}
}
-.tor-toggle {
- margin-block: var(--space-large);
- width: max-content;
-}
-
-/* Status */
-
-#network-status-internet-area {
- margin-block: var(--space-large);
-}
-
-#network-status-tor-area {
- margin-block: 0 var(--space-xxlarge);
-}
-
-.network-status-area {
- display: flex;
- align-items: center;
- white-space: nowrap;
-}
-
-.network-status-area > * {
- flex: 0 0 auto;
-}
-
-.network-status-icon {
- width: var(--icon-size);
- height: var(--icon-size);
- margin-inline-end: var(--space-small);
- -moz-context-properties: fill, stroke;
- fill: var(--icon-color);
- stroke: var(--icon-color);
-}
-
-#network-status-internet-area .network-status-icon {
- content: url("chrome://browser/content/torconnect/network.svg");
-}
-
-#network-status-internet-area.status-offline .network-status-icon {
- content: url("chrome://browser/content/torconnect/network-broken.svg");
-}
-
-#network-status-tor-area .network-status-icon {
- content: url("chrome://browser/content/torconnect/tor-connect.svg");
-}
-
-#network-status-tor-area:not(.status-connected) .network-status-icon {
- content: url("chrome://browser/content/torconnect/tor-connect-broken.svg");
-}
-
-#network-status-tor-area.status-blocked .network-status-icon {
- /* Same as .tor-connect-status-potentially-blocked. */
- stroke: var(--icon-color-critical);
-}
-
-.network-status-label {
- font-weight: var(--font-weight-bold);
- margin-inline-end: var(--space-medium);
-}
-
-.network-status-result {
- margin-inline-end: var(--space-medium);
-}
-
-#network-status-tor-area.status-connected #network-status-tor-connect-button {
- /* Hide button when already connected. */
- display: none;
-}
-
/* Bridge settings */
-.tor-medium-heading {
- /* Same font size as mozilla preferences h2. */
- font-size: var(--font-size-large);
- font-weight: var(--font-weight-bold);
- margin: 0;
-}
-
.tor-small-heading {
font-size: inherit;
font-weight: var(--font-weight-bold);
margin: 0;
}
-/* Hide the tor-search-heading elements when the group's search header is
- * hidden. These only appear in search results.
- * See tor-browser#43320.
- * NOTE: `.search-header[hidden] ~ :is(* .tor-search-heading)` will not match
- * (possibly because the `~` selector is unsure how to integrate with the
- * non-compound `* .tor-search-heading` selector). So we need to duplicate the
- * `.search-header[hidden]` rule. */
-#torPreferences-bridges-group :is(.search-header[hidden] ~ * .tor-search-heading, .search-header[hidden] ~ .tor-search-heading) {
- display: none;
-}
-
-/* Hide the tor-non-search-heading elements when the group's search header is
- * not hidden. These only appear outside of search results.
- * See tor-browser#43320. */
-#torPreferences-bridges-group :is(.search-header:not([hidden]) ~ * .tor-non-search-heading, .search-header:not([hidden]) ~ .tor-non-search-heading) {
- display: none;
-}
-
.tor-focusable-heading {
/* Do not occupy more horizontal space than necessary. */
width: fit-content;
@@ -188,25 +94,6 @@ button.spoof-button-disabled {
display: none;
}
-#tor-bridges-update-area {
- /* Still accessible to screen reader, but not visual. */
- position: absolute;
- clip-path: inset(50%);
-}
-
-#torPreferences-bridges-group:not(.bridges-initialized) {
- /* Hide bridge settings whilst not initialized. */
- display: none;
-}
-
-#tor-bridges-none,
-#tor-bridges-current {
- @media not -moz-pref("browser.settings-redesign.enabled") {
- margin-inline: 0;
- margin-block: var(--space-xxlarge);
- }
-}
-
#tor-bridges-none:not([hidden]) {
display: grid;
justify-items: center;
@@ -217,11 +104,6 @@ button.spoof-button-disabled {
border-radius: var(--border-radius-small);
color: var(--text-color-deemphasized);
border: 2px dashed var(--border-color-deemphasized);
-
- @media not -moz-pref("browser.settings-redesign.enabled") {
- padding-block: 64px;
- gap: var(--space-large);
- }
}
#tor-bridges-none-icon {
@@ -232,33 +114,10 @@ button.spoof-button-disabled {
fill: currentColor;
}
-.tor-bridges-box,
.tor-bridges-details-box {
padding: var(--space-large);
border-radius: var(--border-radius-medium);
border: var(--border-width) solid var(--border-color);
-
- @media not -moz-pref("browser.settings-redesign.enabled") {
- border-radius: var(--border-radius-small);
- background: var(--background-color-box-info);
- }
-}
-
-@media not forced-colors {
- .tor-bridges-box {
- border-color: transparent;
- }
-}
-
-#tor-bridges-current-header-bar {
- display: grid;
- min-width: max-content;
- grid-template: "heading source button" min-content / max-content 1fr max-content;
- align-items: center;
- border-block-end: var(--border-width) solid var(--border-color);
- padding-block-end: var(--space-large);
- margin-block-end: var(--space-large);
- white-space: nowrap;
}
tor-bridges-display {
@@ -685,63 +544,6 @@ tor-bridges-display {
align-self: center;
}
-.tor-bridges-provider-heading {
- margin-block: var(--space-xxlarge) var(--space-small);
-}
-
-#tor-bridges-provider-area {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: var(--space-large);
- align-items: start;
- margin-block-start: var(--space-xlarge);
-}
-
-#tor-bridges-provider-list {
- display: grid;
- grid-template-columns: max-content max-content;
- gap: var(--space-xlarge) var(--space-medium);
- margin-block: var(--space-large);
- padding: 0;
-}
-
-.tor-bridges-provider-item {
- grid-column: 1 / -1;
- display: grid;
- grid-template-columns: subgrid;
- align-items: center;
- justify-items: start;
- gap: var(--space-small) var(--space-medium);
-}
-
-.tor-bridges-provider-icon {
- width: var(--icon-size);
- height: var(--icon-size);
- -moz-context-properties: fill;
- fill: var(--icon-color);
-}
-
-#tor-bridges-provider-icon-telegram {
- content: url("chrome://browser/content/torpreferences/telegram-logo.svg");
-}
-
-#tor-bridges-provider-icon-web {
- content: url("chrome://browser/content/torconnect/network.svg");
-}
-
-#tor-bridges-provider-icon-email {
- content: url("chrome://browser/content/torpreferences/mail.svg");
-}
-
-.tor-bridges-provider-name {
- font-weight: var(--font-weight-bold);
- font-size: var(--font-size-small);
-}
-
-.tor-bridges-provider-instruction {
- grid-column: 2 / 3;
-}
-
#torBridgesRequestBanner {
display: flex;
flex-direction: column;
@@ -760,37 +562,6 @@ tor-bridges-display {
}
}
-#tor-bridges-request-box {
- /* Take up the full height in the container. */
- align-self: stretch;
- display: flex;
- flex-direction: column;
- align-items: center;
- text-align: center;
- justify-content: center;
-}
-
-#tor-bridges-request-box > * {
- flex: 0 0 auto;
-}
-
-#tor-bridges-request-description {
- margin-block: var(--space-medium) var(--space-large);
-}
-
-#tor-bridges-open-request-dialog-button {
- margin: 0;
-}
-
-#torPreferences-bridges-location {
- width: 280px;
-}
-
-#torPreferences-bridges-location menuitem[disabled="true"] {
- color: var(--button-text-color, inherit);
- font-weight: var(--font-weight-bold);
-}
-
/* Request bridges */
image#torPreferences-requestBridge-captchaImage {
@@ -973,11 +744,6 @@ image#torPreferences-requestBridge-errorIcon {
fill: var(--icon-color-warning);
}
-groupbox#torPreferences-bridges-group textarea {
- white-space: pre;
- overflow: auto;
-}
-
/* Provide bridge dialog */
#user-provide-bridge-dialog:not(.show-entry-page) #user-provide-bridge-entry-page {
=====================================
browser/components/torpreferences/jar.mn
=====================================
@@ -30,7 +30,6 @@ browser.jar:
content/browser/torpreferences/widgets/tor-connection-assist-banner.mjs (widgets/tor-connection-assist-banner.mjs)
content/browser/torpreferences/widgets/tor-connection-status.mjs (widgets/tor-connection-status.mjs)
content/browser/torpreferences/widgets/tor-connection-status.css (widgets/tor-connection-status.css)
- content/browser/torpreferences/connectionPane.js (content/connectionPane.js)
content/browser/torpreferences/torPreferences.css (content/torPreferences.css)
content/browser/torpreferences/bridgemoji/BridgeEmoji.js (content/bridgemoji/BridgeEmoji.js)
content/browser/torpreferences/bridgemoji/bridge-emojis.json (content/bridgemoji/bridge-emojis.json)
=====================================
toolkit/locales/en-US/toolkit/global/tor-browser.ftl
=====================================
@@ -101,11 +101,6 @@ tor-connection-settings-pane =
# "Connection" refers to the Tor Browser's connection to the Tor network.
tor-connection-settings-nav-button = Connection
.title = Connection
-# -brand-short-name refers to 'Tor Browser', localized.
-tor-connection-overview = { -brand-short-name } routes your traffic over the Tor network, run by thousands of volunteers around the world.
-tor-connection-browser-learn-more-link = Learn more
-tor-connection-automatic-heading = Connect automatically
-tor-connection-automatic-description = Automatically connect to the Tor network at launch using your current connection settings.
tor-connection-quickstart-checkbox =
.label = Always connect automatically
@@ -138,10 +133,6 @@ tor-connection-network-status-not-connected = Not connected
# Shown when the user's Tor connection may be blocked.
# Uses sentence case in English (US).
tor-connection-network-status-blocked = Potentially blocked
-# Button shown when we are not yet connected to the Tor network.
-# It will open a page to start connecting to the Tor network.
-# Uses sentence case in English (US).
-tor-connection-network-status-connect-button = Connect
# Here "Connect" refers to connecting to the Tor network.
# Uses sentence case in English (US).
tor-connection-status-connect-button =
@@ -149,9 +140,6 @@ tor-connection-status-connect-button =
## Tor Bridges Settings.
-tor-bridges-heading = Bridges
-tor-bridges-overview = Bridges help you securely access the Tor network in places where Tor is blocked. Depending on where you are, one bridge may work better than another.
-tor-bridges-learn-more-link = Learn more
tor-bridges-group =
.label = Bridges
.description = Bridges help you securely access the Tor network in places where Tor is blocked. Depending on where you are, one bridge may work better than another.
@@ -232,10 +220,6 @@ tor-bridges-menu-item-copy-address = Copy bridge address
tor-bridges-menu-item-remove-bridge = Remove bridge
.accesskey = R
-# Text shown just before a description of the most recent change to the list of user's bridges. Some white space will separate this text from the change description.
-# This text is not visible, but is instead used for screen reader users.
-# E.g. in English this could be "Recent update: One of your Tor bridges has been removed."
-tor-bridges-update-area-intro = Recent update:
# Update text for screen reader users when only one of their bridges has been removed.
tor-bridges-update-removed-one-bridge = One of your Tor bridges has been removed.
# Update text for screen reader users when all of their bridges have been removed.
@@ -313,74 +297,33 @@ tor-bridges-lox-new-invites =
# Button for the user to acknowledge a change in their "bridge pass".
tor-bridges-lox-got-it-button = Got it
-
-# Shown as a heading when the user has no current bridges.
-tor-bridges-add-bridges-heading = Add bridges
-# Shown as a heading when the user has existing bridges that can be replaced.
-tor-bridges-replace-bridges-heading = Replace your bridges
# Shown as a heading when the user has no current bridges.
tor-bridges-add-bridges-group =
.label = Add bridges
# Shown as a heading when the user has existing bridges that can be replaced.
tor-bridges-replace-bridges-group =
.label = Replace your bridges
-
-# -brand-short-name refers to 'Tor Browser', localized.
-tor-bridges-select-built-in-description = Choose from one of { -brand-short-name }’s built-in bridges
# -brand-short-name refers to 'Tor Browser', localized.
tor-bridges-choose-built-in-button =
.label = Choose from one of { -brand-short-name }’s built-in bridges
-tor-bridges-select-built-in-button = Select a built-in bridge…
-
-tor-bridges-add-addresses-description = Enter bridge addresses you already know
tor-bridges-enter-bridges-button =
.label = Enter bridge addresses you already know
-# Shown when the user has no current bridges.
-# Opens a dialog where the user can provide a new bridge address or share code.
-tor-bridges-add-new-button = Add new bridges…
-# Shown when the user has existing bridges.
-# Opens a dialog where the user can provide a new bridge address or share code to replace their current bridges.
-tor-bridges-replace-button = Replace bridges…
-
-tor-bridges-find-more-heading = Find more bridges
-# "Tor Project" is the organisation name.
-tor-bridges-find-more-description = Since many bridge addresses aren’t public, you may need to request some from the Tor Project.
+
# "Tor Project" is the organisation name.
tor-bridges-find-more-group =
.label = Find more bridges
.description = Since many bridge addresses aren’t public, you may need to request some from the Tor Project.
-
-# "Telegram" is the common brand name of the Telegram Messenger application
-tor-bridges-provider-telegram-name = Telegram
-# Here "Message" is a verb, short for "Send a message to". This is an instruction to send a message to the given Telegram Messenger user to receive a new bridge.
-# $telegramUserName (String) - The Telegram Messenger user name that should receive messages. Should be wrapped in '<a data-l10n-name="user">' and '</a>'.
-# E.g. in English, "Message GetBridgesBot".
-tor-bridges-provider-telegram-instruction = Message <a data-l10n-name="user">{ $telegramUserName }</a>
# "Telegram" is the common brand name of the Telegram Messenger application.
# Here "Message" is a verb, short for "Send a message to". This is an instruction to send a message to the given Telegram Messenger user to receive a new bridge.
# $telegramUserName (String) - The Telegram Messenger user name that should receive messages.
tor-bridges-source-telegram-link =
.label = Telegram
.description = Message { $telegramUserName }
-
-# "Web" is the proper noun for the "World Wide Web".
-tor-bridges-provider-web-name = Web
-# Instructions to visit the given website.
-# $url (String) - The URL for Tor Project bridges. Should be wrapped in '<a data-l10n-name"url">' and '</a>'.
-tor-bridges-provider-web-instruction = Visit <a data-l10n-name="url">{ $url }</a>
-
# "Web" is the proper noun for the "World Wide Web".
# $url (String) - The URL for Tor Project bridges.
tor-bridges-source-web-link =
.label = Web
.description = Visit { $url }
-
-# "Gmail" is the Google brand name. "Riseup" refers to the Riseup organisation at riseup.net.
-tor-bridges-provider-email-name = Gmail or Riseup
-# Here "Email" is a verb, short for "Send an email to". This is an instruction to send an email to the given address to receive a new bridge.
-# $address (String) - The email address that should receive the email.
-# E.g. in English, "Email bridges(a)torproject.org".
-tor-bridges-provider-email-instruction = Email { $address }
# "Gmail" is the Google brand name. "Riseup" refers to the Riseup organisation at riseup.net.
# Here "Email" is a verb, short for "Send an email to". This is an instruction to send an email to the given address to receive a new bridge.
# $address (String) - The email address that should receive the email.
@@ -514,16 +457,6 @@ tor-advanced-configure-button =
.label = Configure how { -brand-short-name } connects to the internet
tor-view-log-button2 =
.label = View the Tor log
-tor-advanced-settings-heading = Advanced
-tor-advanced-settings-description = Configure how { -brand-short-name } connects to the internet.
-# Button that opens the advanced connection settings dialog.
-# Uses sentence case in English (US).
-tor-advanced-settings-button = Settings…
-# "log" is a noun, referring to the recorded text output of the Tor process.
-tor-view-log-description = View the Tor log.
-# "log" is a noun, referring to the recorded text output of the Tor process.
-# Uses sentence case in English (US).
-tor-view-log-button = View log…
## Tor log dialog.
=====================================
toolkit/modules/TorStrings.sys.mjs
=====================================
@@ -70,13 +70,7 @@ const Loader = {
// Message box
torPreferencesDescription:
"Tor Browser routes your traffic over the Tor network, run by thousands of volunteers around the world.",
- // Quickstart
quickstartCheckbox: "Always connect automatically",
- bridgeLocation: "Your location",
- bridgeLocationAutomatic: "Automatic",
- bridgeLocationFrequent: "Frequently selected locations",
- bridgeLocationOther: "Other locations",
- bridgeChooseForMe: "Choose a Bridge For Me…",
};
const tsb = new TorPropertyStringBundle(
=====================================
toolkit/torbutton/chrome/locale/en-US/settings.properties
=====================================
@@ -9,11 +9,3 @@
settings.torPreferencesDescription=Tor Browser routes your traffic over the Tor network, run by thousands of volunteers around the world.
settings.quickstartCheckbox=Always connect automatically
-
-# Might be removed in tor-browser#42477
-
-settings.bridgeLocation=Your location
-settings.bridgeLocationAutomatic=Automatic
-settings.bridgeLocationFrequent=Frequently selected locations
-settings.bridgeLocationOther=Other locations
-settings.bridgeChooseForMe=Choose a Bridge For Me…
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/8f8c34…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/8f8c34…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/mullvad-browser][mullvad-browser-153.0esr-16.0-1] fixup! BB 42027: Base Browser migration procedures.
by morgan (@morgan) 06 Aug '26
by morgan (@morgan) 06 Aug '26
06 Aug '26
morgan pushed to branch mullvad-browser-153.0esr-16.0-1 at The Tor Project / Applications / Mullvad Browser
Commits:
b0d20211 by Pier Angelo Vendrame at 2026-08-06T16:10:41+00:00
fixup! BB 42027: Base Browser migration procedures.
BB 45039: Make it easier to test ProfileDataUpgrader customization.
- - - - -
1 changed file:
- browser/components/ProfileDataUpgrader.sys.mjs
Changes:
=====================================
browser/components/ProfileDataUpgrader.sys.mjs
=====================================
@@ -1026,7 +1026,15 @@ export let ProfileDataUpgrader = {
Services.prefs.setIntPref("browser.migration.version", newVersion);
},
- upgradeBB(isNewProfile) {
+ /**
+ * Run the profile data migration for Base Browser if needed.
+ *
+ * @param {boolean} isNewProfile When true, just set the migration version
+ * without actually changing anything.
+ * @param {number} [currentVersion] The version to migrating from. To be used
+ * only by tests.
+ */
+ upgradeBB(isNewProfile, currentVersion) {
// Version 1: 13.0a3. Reset layout.css.prefers-color-scheme.content-override
// for tor-browser#41739.
// Version 2: 14.0a5: Reset the privacy tracking headers preferences since
@@ -1056,7 +1064,10 @@ export let ProfileDataUpgrader = {
console.error("upgradeBB: isNewProfile is undefined.");
}
- const currentVersion = Services.prefs.getIntPref(MIGRATION_PREF, 0);
+ if (currentVersion === undefined) {
+ currentVersion = Services.prefs.getIntPref(MIGRATION_PREF, 0);
+ }
+
if (currentVersion < 1) {
Services.prefs.clearUserPref(
"layout.css.prefers-color-scheme.content-override"
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/b0d…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/b0d…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/tor-browser][tor-browser-153.0esr-16.0-1] 2 commits: fixup! BB 42027: Base Browser migration procedures.
by morgan (@morgan) 06 Aug '26
by morgan (@morgan) 06 Aug '26
06 Aug '26
morgan pushed to branch tor-browser-153.0esr-16.0-1 at The Tor Project / Applications / Tor Browser
Commits:
fa075e61 by Pier Angelo Vendrame at 2026-08-06T16:09:00+00:00
fixup! BB 42027: Base Browser migration procedures.
BB 45039: Make it easier to test ProfileDataUpgrader customization.
- - - - -
8f8c3495 by Pier Angelo Vendrame at 2026-08-06T16:09:00+00:00
amend! TB 41435: Add a Tor Browser migration function
TB 41435: Add a Tor Browser migration function
- - - - -
1 changed file:
- browser/components/ProfileDataUpgrader.sys.mjs
Changes:
=====================================
browser/components/ProfileDataUpgrader.sys.mjs
=====================================
@@ -1026,7 +1026,15 @@ export let ProfileDataUpgrader = {
Services.prefs.setIntPref("browser.migration.version", newVersion);
},
- upgradeBB(isNewProfile) {
+ /**
+ * Run the profile data migration for Base Browser if needed.
+ *
+ * @param {boolean} isNewProfile When true, just set the migration version
+ * without actually changing anything.
+ * @param {number} [currentVersion] The version to migrating from. To be used
+ * only by tests.
+ */
+ upgradeBB(isNewProfile, currentVersion) {
// Version 1: 13.0a3. Reset layout.css.prefers-color-scheme.content-override
// for tor-browser#41739.
// Version 2: 14.0a5: Reset the privacy tracking headers preferences since
@@ -1056,7 +1064,10 @@ export let ProfileDataUpgrader = {
console.error("upgradeBB: isNewProfile is undefined.");
}
- const currentVersion = Services.prefs.getIntPref(MIGRATION_PREF, 0);
+ if (currentVersion === undefined) {
+ currentVersion = Services.prefs.getIntPref(MIGRATION_PREF, 0);
+ }
+
if (currentVersion < 1) {
Services.prefs.clearUserPref(
"layout.css.prefers-color-scheme.content-override"
@@ -1163,7 +1174,15 @@ export let ProfileDataUpgrader = {
Services.prefs.setIntPref(MIGRATION_PREF, MIGRATION_VERSION);
},
- async upgradeTB(isNewProfile) {
+ /**
+ * Run the profile data migration for Tor Browser if needed.
+ *
+ * @param {boolean} isNewProfile When true, just set the migration version
+ * without actually changing anything.
+ * @param {number} [currentVersion] The version to migrating from. To be used
+ * only by tests.
+ */
+ async upgradeTB(isNewProfile, currentVersion) {
// Version 1: Tor Browser 12.0. We use it to remove langpacks, after the
// migration to packaged locales.
// Version 2: Tor Browser 13.0/13.0a1: tor-browser#41845. Also, removed some
@@ -1199,7 +1218,10 @@ export let ProfileDataUpgrader = {
console.error("upgradeTB: isNewProfile is undefined.");
}
- const currentVersion = Services.prefs.getIntPref(MIGRATION_PREF, 0);
+ if (currentVersion === undefined) {
+ currentVersion = Services.prefs.getIntPref(MIGRATION_PREF, 0);
+ }
+
const removeLangpacks = async () => {
for (const addon of await AddonManager.getAddonsByTypes(["locale"])) {
await addon.uninstall();
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/53206c…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/53206c…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0