tbb-commits
Threads by month
- ----- 2026 -----
- September
- 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
- 20967 discussions
[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
[Git][tpo/applications/tor-browser][tor-browser-153.0esr-16.0-1] fixup! TB 40701: Add security warning when downloading a file
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:
53206cf1 by Henry Wilkes at 2026-08-06T16:07:24+00:00
fixup! TB 40701: Add security warning when downloading a file
TB 45180: Add the download warning to the settings.
- - - - -
1 changed file:
- browser/components/preferences/config/downloads.mjs
Changes:
=====================================
browser/components/preferences/config/downloads.mjs
=====================================
@@ -372,6 +372,10 @@ Preferences.addSetting({
id: "applicationsGroup",
});
+Preferences.addSetting({
+ id: "downloadsTorWarningBanner",
+});
+
Preferences.addSetting({
id: "applicationsFilter",
get(val) {
@@ -1846,6 +1850,26 @@ SettingGroupManager.registerGroups({
headingLevel: 2,
inProgress: true,
items: [
+ {
+ id: "downloadsTorWarningBanner",
+ l10nId: "downloads-tor-warning-message-bar",
+ control: "moz-message-bar",
+ controlAttrs: {
+ role: "complementary",
+ type: "warning",
+ },
+ options: [
+ {
+ l10nId: "downloads-tor-warning-tails-link",
+ slot: "support-link",
+ control: "a",
+ controlAttrs: {
+ href: "https://tails.net/",
+ target: "_blank",
+ },
+ },
+ ],
+ },
{
id: "applicationsFilter",
control: "moz-input-search",
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/53206cf…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/53206cf…
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) 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:
62bbac0a by Henry Wilkes at 2026-08-06T16:04:02+00:00
fixup! TB 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
TB 45059: Add "Advanced" connection settings to the config.
- - - - -
8a72a606 by Henry Wilkes at 2026-08-06T16:04:02+00:00
fixup! Tor Browser strings
TB 45059: Advanced connection setting strings.
- - - - -
e3be1a74 by Henry Wilkes at 2026-08-06T16:04:02+00:00
fixup! Tor Browser localization migration scripts.
TB 45059: Add a string migration for the "Advanced" heading.
- - - - -
4 changed files:
- browser/components/preferences/preferences.js
- browser/components/torpreferences/config/connection.mjs
- toolkit/locales/en-US/toolkit/global/tor-browser.ftl
- + tools/torbrowser/l10n/migrations/bug-45059-advanced-connection-settings.py
Changes:
=====================================
browser/components/preferences/preferences.js
=====================================
@@ -271,7 +271,7 @@ const CONFIG_PANES = Object.freeze({
connection: {
l10nId: "tor-connection-settings-pane",
iconSrc: "chrome://browser/content/torconnect/tor-connect.svg",
- groupIds: ["connectionStatus", "torBridges"],
+ groupIds: ["connectionStatus", "torBridges", "torAdvanced"],
module: "chrome://browser/content/torpreferences/config/connection.mjs",
visible: () => {
return TorConnect.enabled;
=====================================
browser/components/torpreferences/config/connection.mjs
=====================================
@@ -192,6 +192,29 @@ SettingGroupManager.registerGroups({
},
],
},
+ torAdvanced: {
+ inProgress: true,
+ l10nId: "tor-advanced-group",
+ headingLevel: 2,
+ items: [
+ {
+ id: "torAdvancedGroup",
+ control: "moz-box-group",
+ items: [
+ {
+ id: "torAdvancedConfigure",
+ l10nId: "tor-advanced-configure-button",
+ control: "moz-box-button",
+ },
+ {
+ id: "torViewLog",
+ l10nId: "tor-view-log-button2",
+ control: "moz-box-button",
+ },
+ ],
+ },
+ ],
+ },
});
Preferences.addSetting({
@@ -561,3 +584,33 @@ Preferences.addSetting({
);
},
});
+
+Preferences.addSetting({
+ id: "torAdvancedGroup",
+});
+
+Preferences.addSetting({
+ id: "torAdvancedConfigure",
+ deps: ["torSettingsReady"],
+ visible({ torSettingsReady }) {
+ // The dialog needs to be able to read TorSettings, so we keep this hidden
+ // until it is ready.
+ return torSettingsReady.value;
+ },
+ onUserClick() {
+ window.gSubDialog.open(
+ "chrome://browser/content/torpreferences/connectionSettingsDialog.xhtml",
+ { features: "resizable=yes" }
+ );
+ },
+});
+
+Preferences.addSetting({
+ id: "torViewLog",
+ onUserClick() {
+ window.gSubDialog.open(
+ "chrome://browser/content/torpreferences/torLogDialog.xhtml",
+ { features: "resizable=yes" }
+ );
+ },
+});
=====================================
toolkit/locales/en-US/toolkit/global/tor-browser.ftl
=====================================
@@ -507,6 +507,13 @@ request-bridge-dialog-captcha-failed = The solution is not correct. Please try a
## Tor advanced settings.
+tor-advanced-group =
+ .label = Advanced
+# "{ -brand-short-name }" will be replaced with the localized name of the browser, e.g. "Tor Browser".
+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.
=====================================
tools/torbrowser/l10n/migrations/bug-45059-advanced-connection-settings.py
=====================================
@@ -0,0 +1,15 @@
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ ctx.add_transforms(
+ "tor-browser.ftl",
+ "tor-browser.ftl",
+ transforms_from(
+ """
+tor-advanced-group =
+ .label = { COPY_PATTERN(path, "tor-advanced-settings-heading") }
+""",
+ path="tor-browser.ftl",
+ ),
+ )
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/092695…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/092695…
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) 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:
4983f698 by Henry Wilkes at 2026-08-06T15:48:25+00:00
fixup! TB 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
TB 43939: Add a connection assist banner.
- - - - -
b825b06d by Henry Wilkes at 2026-08-06T15:48:25+00:00
fixup! Tor Browser strings
TB 43939: Add connection assist banner string.
- - - - -
09269559 by Henry Wilkes at 2026-08-06T15:48:25+00:00
fixup! TB 40597: Implement TorSettings module
TB 43939: Add a method to determine whether the user is in a "Connection
Assist" stage.
- - - - -
6 changed files:
- browser/components/preferences/preferences.xhtml
- browser/components/torpreferences/config/connection.mjs
- browser/components/torpreferences/jar.mn
- + browser/components/torpreferences/widgets/tor-connection-assist-banner.mjs
- toolkit/locales/en-US/toolkit/global/tor-browser.ftl
- toolkit/modules/TorConnect.sys.mjs
Changes:
=====================================
browser/components/preferences/preferences.xhtml
=====================================
@@ -109,6 +109,7 @@
<script type="module" src="chrome://browser/content/preferences/widgets/update-state.mjs"></script>
<script type="module" src="chrome://browser/content/ipprotection/bandwidth-usage.mjs"></script>
<script type="module" src="chrome://browser/content/torpreferences/widgets/tor-bridges-display.mjs"></script>
+ <script type="module" src="chrome://browser/content/torpreferences/widgets/tor-connection-assist-banner.mjs"></script>
<script type="module" src="chrome://browser/content/torpreferences/widgets/tor-connection-status.mjs"></script>
<script src="chrome://browser/content/torpreferences/bridgemoji/BridgeEmoji.js"/>
</head>
=====================================
browser/components/torpreferences/config/connection.mjs
=====================================
@@ -4,6 +4,8 @@ import { Preferences } from "chrome://global/content/preferences/Preferences.mjs
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
InternetStatus: "moz-src:///toolkit/modules/TorConnect.sys.mjs",
+ moveFocusToBridgeHeading:
+ "chrome://browser/content/torpreferences/config/helpers.mjs",
openBridgeDialog:
"chrome://browser/content/torpreferences/config/helpers.mjs",
openUserProvideBridgeDialog:
@@ -68,6 +70,18 @@ SettingGroupManager.registerGroups({
headingLevel: 2,
controlAttrs: { "focusable-heading": true },
items: [
+ {
+ id: "connectionAssistBanner",
+ // NOTE: Instead of using a custom widget for this one banner, we could
+ // use moz-message-bar and populate it's children. However, we want to
+ // intercept the "click" event for the "Connection Assist" link within
+ // the banner text. As of ESR 153, onUserClick would not allow us to
+ // intercept the event because the `<a>` would need to be wrapped in a
+ // `<setting-control>`. But Fluent would not allow wrapping the
+ // `<setting-control>` element as part of a wider string (unlike
+ // `<a data-l10n-name="link">`, which is allowed). tor-browser#43939.
+ control: "tor-connection-assist-banner",
+ },
{
id: "bridgesEnabled",
l10nId: "tor-bridges-use-bridges",
@@ -253,6 +267,27 @@ Preferences.addSetting({
},
});
+Preferences.addSetting({
+ id: "connectionAssistBanner",
+ deps: ["torStatus"],
+ _wasVisible: false,
+ visible({ torStatus }) {
+ const visible = torStatus.value === "potentially-blocked";
+ if (
+ !visible &&
+ this._wasVisible &&
+ document
+ .getElementById("connectionAssistBanner")
+ ?.contains(document.activeElement)
+ ) {
+ // About to loose focus, move focus to the bridge heading.
+ lazy.moveFocusToBridgeHeading(window, true);
+ }
+ this._wasVisible = visible;
+ return visible;
+ },
+});
+
Preferences.addSetting({
id: "torSettingsReady",
_ready: false,
=====================================
browser/components/torpreferences/jar.mn
=====================================
@@ -27,6 +27,7 @@ browser.jar:
content/browser/torpreferences/config/connection.mjs (config/connection.mjs)
content/browser/torpreferences/config/helpers.mjs (config/helpers.mjs)
content/browser/torpreferences/widgets/tor-bridges-display.mjs (widgets/tor-bridges-display.mjs)
+ 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)
=====================================
browser/components/torpreferences/widgets/tor-connection-assist-banner.mjs
=====================================
@@ -0,0 +1,56 @@
+import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
+import { html } from "chrome://global/content/vendor/lit.all.mjs";
+
+const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+ TorConnect: "moz-src:///toolkit/modules/TorConnect.sys.mjs",
+ TorConnectParent:
+ "moz-src:///browser/components/torconnect/TorConnectParent.sys.mjs",
+});
+
+const TOR_CONNECT_HREF = "about:torconnect";
+
+/**
+ * Widget for displaying a Connection Assist banner.
+ *
+ * @tagname tor-connection-status
+ */
+class TorConnectionAssistBanner extends MozLitElement {
+ render() {
+ return html`
+ <moz-message-bar
+ role="complementary"
+ type="warning"
+ @click=${this.#handleClick}
+ >
+ <span
+ slot="message"
+ data-l10n-id="tor-bridges-connection-assist-message"
+ >
+ <a
+ id="link"
+ data-l10n-name="link"
+ href=${TOR_CONNECT_HREF}
+ target="_blank"
+ ></a>
+ </span>
+ </moz-message-bar>
+ `;
+ }
+
+ #handleClick(event) {
+ if (!this.shadowRoot.getElementById("link")?.contains(event.target)) {
+ return;
+ }
+ event.preventDefault();
+ if (!lazy.TorConnect.inConnectionAssistStage) {
+ // Switch to the "ChooseRegion" stage to reflect "Connection Assist".
+ lazy.TorConnect.chooseRegion();
+ }
+ lazy.TorConnectParent.open();
+ }
+}
+customElements.define(
+ "tor-connection-assist-banner",
+ TorConnectionAssistBanner
+);
=====================================
toolkit/locales/en-US/toolkit/global/tor-browser.ftl
=====================================
@@ -156,6 +156,11 @@ 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.
+# "{ -brand-product-name }" will be replaced with the localized name of the browser, e.g. "Tor Browser".
+# "Connection Assist" is the name of a Tor Browser feature, and is therefore capitalised in English. For translations, it should similarly be treated as a feature/product name. It should also be wrapped by the tags '<a data-l10n-name="link">' and '</a>'.
+tor-bridges-connection-assist-message = { -brand-short-name } could not connect to the Tor network. You may want to change your bridge settings below, or use <a data-l10n-name="link">Connection Assist</a> to find bridges for you.
+
+
# Toggle button for enabling and disabling the use of bridges.
tor-bridges-use-bridges =
.label = Use bridges
=====================================
toolkit/modules/TorConnect.sys.mjs
=====================================
@@ -305,6 +305,8 @@ class BootstrapAttempt {
/**
* Each instance can be used to attempt one auto-bootstrapping sequence.
+ *
+ * AKA Connection Assist bootstrap.
*/
class AutoBootstrapAttempt {
/**
@@ -811,6 +813,12 @@ export const TorConnect = {
*/
_bootstrapAttempt: null,
+ /**
+ * Whether the current or last bootstrap attempt was a "normal" bootstrap (not
+ * Connection Assist).
+ */
+ _wasNormalBootstrap: false,
+
/**
* The bootstrap error that was last generated.
*
@@ -1048,7 +1056,7 @@ export const TorConnect = {
// No change.
return;
}
- if (this._stageName === "Bootstrapping") {
+ if (this._stageName === TorConnectStage.Bootstrapping) {
this._bootstrappingStatus.hasWarning = true;
this._notifyBootstrapProgress();
}
@@ -1197,6 +1205,19 @@ export const TorConnect = {
);
},
+ /**
+ * Whether we are in a stage that is considered part of "Connection Assist".
+ *
+ * @type {boolean}
+ */
+ get inConnectionAssistStage() {
+ return (
+ this.canBeginAutoBootstrap ||
+ (this._stageName === TorConnectStage.Bootstrapping &&
+ !this._wasNormalBootstrap)
+ );
+ },
+
/**
* Get a map of all region codes and their localized names.
*
@@ -1377,9 +1398,10 @@ export const TorConnect = {
const beginStage = this._stageName;
const bootstrapOptions = { regionCode };
- const bootstrapAttempt = regionCode
- ? new AutoBootstrapAttempt()
- : new BootstrapAttempt();
+ const normalBootstrap = !regionCode;
+ const bootstrapAttempt = normalBootstrap
+ ? new BootstrapAttempt()
+ : new AutoBootstrapAttempt();
this._addSimulateOptions(bootstrapOptions, regionCode);
@@ -1398,6 +1420,7 @@ export const TorConnect = {
this._requestedStage = null;
this._bootstrapTrigger = beginStage;
this._isQuickstart = isQuickstart;
+ this._wasNormalBootstrap = normalBootstrap;
this._setStage(TorConnectStage.Bootstrapping);
this._bootstrapAttempt = bootstrapAttempt;
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/e97309…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/e97309…
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 40933: Add tor-launcher functionality
by henry (@henry) 06 Aug '26
by henry (@henry) 06 Aug '26
06 Aug '26
henry pushed to branch tor-browser-153.0esr-16.0-1 at The Tor Project / Applications / Tor Browser
Commits:
f767af19 by Henry Wilkes at 2026-08-06T13:32:41+00:00
fixup! TB 40933: Add tor-launcher functionality
TB 45161: Drop the restart prompt.
- - - - -
e97309cd by Henry Wilkes at 2026-08-06T13:32:41+00:00
fixup! Add TorStrings module for localization
TB 45161: Drop TorLauncherUtils prompt strings.
- - - - -
3 changed files:
- toolkit/components/tor-launcher/TorLauncherUtil.sys.mjs
- toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs
- toolkit/torbutton/chrome/locale/en-US/torlauncher.properties
Changes:
=====================================
toolkit/components/tor-launcher/TorLauncherUtil.sys.mjs
=====================================
@@ -15,8 +15,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
"moz-src:///toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs",
});
-const kPropBundleURI = "chrome://torbutton/locale/torlauncher.properties";
-const kPropNamePrefix = "torlauncher.";
const kIPCDirPrefName = "extensions.torlauncher.tmp_ipc_dir";
/**
@@ -374,86 +372,6 @@ export const TorLauncherUtil = {
return !re.test(path);
},
- // Returns true if user confirms; false if not.
- showConfirm(aParentWindow, aMsg, aDefaultButtonLabel, aCancelButtonLabel) {
- if (!aParentWindow) {
- aParentWindow = Services.wm.getMostRecentWindow("navigator:browser");
- }
-
- const ps = Services.prompt;
- const title = this.getLocalizedString("error_title");
- const btnFlags =
- ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING +
- ps.BUTTON_POS_0_DEFAULT +
- ps.BUTTON_POS_1 * ps.BUTTON_TITLE_IS_STRING;
-
- const notUsed = { value: false };
- const btnIndex = ps.confirmEx(
- aParentWindow,
- title,
- aMsg,
- btnFlags,
- aDefaultButtonLabel,
- aCancelButtonLabel,
- null,
- null,
- notUsed
- );
- return btnIndex === 0;
- },
-
- /**
- * Ask the user whether they desire to restart tor.
- *
- * @param {boolean} initError If we could connect to the control port at
- * least once and we are showing this prompt because the tor process exited
- * suddenly, we will display a different message
- * @returns {boolean} true if the user asked to restart tor
- */
- showRestartPrompt(initError) {
- let s;
- if (initError) {
- const key = "tor_exited_during_startup";
- s = this.getLocalizedString(key);
- } else {
- // tor exited suddenly, so configuration should be okay
- s =
- this.getLocalizedString("tor_exited") +
- "\n\n" +
- this.getLocalizedString("tor_exited2");
- }
- const defaultBtnLabel = this.getLocalizedString("restart_tor");
- let cancelBtnLabel = "OK";
- try {
- const kSysBundleURI = "chrome://global/locale/commonDialogs.properties";
- const sysBundle = Services.strings.createBundle(kSysBundleURI);
- cancelBtnLabel = sysBundle.GetStringFromName(cancelBtnLabel);
- } catch (e) {
- console.warn("Could not localize the cancel button", e);
- }
- return this.showConfirm(null, s, defaultBtnLabel, cancelBtnLabel);
- },
-
- _stringBundle: null,
-
- // Localized Strings
- // TODO: Switch to fluent also these ones.
-
- // "torlauncher." is prepended to aStringName.
- getLocalizedString(aStringName) {
- if (!aStringName) {
- return aStringName;
- }
- if (!this._stringBundle) {
- this._stringBundle = Services.strings.createBundle(kPropBundleURI);
- }
- try {
- const key = kPropNamePrefix + aStringName;
- return this._stringBundle.GetStringFromName(key);
- } catch (e) {}
- return aStringName;
- },
-
/**
* Determine what kind of SOCKS port has been requested for this session or
* the browser has been configured for.
=====================================
toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs
=====================================
@@ -4,8 +4,6 @@
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
- TorLauncherUtil:
- "moz-src:///toolkit/components/tor-launcher/TorLauncherUtil.sys.mjs",
TorProvider: "moz-src:///toolkit/components/tor-launcher/TorProvider.sys.mjs",
TorProviderMock:
"moz-src:///toolkit/components/tor-launcher/TorProviderMock.sys.mjs",
@@ -279,8 +277,6 @@ export class TorProviderBuilder {
TorProviderTopics.ProviderStateChanged,
provider.state
);
-
- this.#promptProviderState(false);
}
/**
@@ -400,85 +396,6 @@ export class TorProviderBuilder {
this.#replaceProvider();
}
- // TODO: Remove firstWindowLoaded, #uiReady, #prompting, #promptProviderState
- // and use TorConnect instead. tor-browser#43570.
- /**
- * Check if the provider has been succesfully initialized when the first
- * browser window is shown.
- * This is a workaround we need because ideally we would like the tor process
- * to start as soon as possible, to avoid delays in the about:torconnect page,
- * but we should modify TorConnect and about:torconnect to handle this case
- * there with a better UX.
- */
- static firstWindowLoaded() {
- this.#promptProviderState(true);
- }
-
- /**
- * Tell whether the browser UI is ready.
- * We ignore any errors until it is because we cannot show them.
- *
- * @type {boolean}
- */
- static #uiReady = false;
-
- /**
- * Whether we are prompting the user for a restart of the provider.
- *
- * @type {boolean}
- */
- static #prompting = false;
-
- /**
- * Prompt the user to restart the provider, if this is necessary.
- *
- * @param {boolean} uiReady - Whether this is being called for the first time
- * when the UI is ready.
- */
- static async #promptProviderState(uiReady) {
- if (uiReady) {
- this.#uiReady = true;
- }
- if (this.#providerData.provider.state === TorProviderState.Running) {
- // Nothing to wait for.
- return;
- }
- if (!this.#uiReady) {
- lazy.logger.warn(
- "Seen exit, but not doing anything because the UI is not ready yet."
- );
- return;
- }
- if (this.#prompting) {
- // Already prompting, so don't duplicate.
- return;
- }
-
- this.#prompting = true;
- let waitForInit = uiReady;
- let retry = true;
- try {
- while (retry) {
- if (waitForInit) {
- try {
- await this.#providerData.initPromise;
- } catch {}
- }
- if (
- this.#providerData.provider.state === TorProviderState.Stopped &&
- lazy.TorLauncherUtil.showRestartPrompt(uiReady)
- ) {
- waitForInit = true;
- this.replace();
- } else {
- retry = false;
- }
- }
- } finally {
- this.#prompting = false;
- }
- }
-
/**
* Return the provider chosen by the user.
* This function checks the TOR_PROVIDER environment variable and if it is a
=====================================
toolkit/torbutton/chrome/locale/en-US/torlauncher.properties
=====================================
@@ -3,13 +3,6 @@
# 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/.
-torlauncher.error_title=Tor Launcher
-torlauncher.tor_exited_during_startup=Tor exited during startup. This might be due to an error in your torrc file, a bug in Tor or another program on your system, or faulty hardware. Until you fix the underlying problem and restart Tor, Tor Browser will not start.
-torlauncher.tor_exited=Tor unexpectedly exited. This might be due to a bug in Tor itself, another program on your system, or faulty hardware. Until you restart Tor, Tor Browser will not be able to reach any websites. If the problem persists, please send a copy of your Tor Log to the support team.
-torlauncher.tor_exited2=Restarting Tor will not close your browser tabs.
-torlauncher.restart_tor=Restart Tor
-
-
# Translation note: %1$S is a bootstrap phase from torlauncher.bootstrapStatus,
# %2$S is the error from torlauncher.bootstrapWarning
torlauncher.tor_bootstrap_failed_details=%1$S failed (%2$S).
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/5b194f…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/5b194f…
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] 7 commits: fixup! TB 40597: Implement TorSettings module
by Dan Ballard (@dan) 06 Aug '26
by Dan Ballard (@dan) 06 Aug '26
06 Aug '26
Dan Ballard pushed to branch tor-browser-153.0esr-16.0-1 at The Tor Project / Applications / Tor Browser
Commits:
fe45b5e3 by Henry Wilkes at 2026-08-05T12:12:42-06:00
fixup! TB 40597: Implement TorSettings module
TB 43571: Allow android to enter ProviderStopped.
- - - - -
80e2704f by Henry Wilkes at 2026-08-05T12:12:43-06:00
fixup! TB 42247: Android helpers for the TorProvider
TB 43571: Drop `TorProviderBuilder.firstWindowLoaded`, which is covered
by `TorConnect.init` now.
- - - - -
48c8f5a9 by clairehurst at 2026-08-05T12:12:43-06:00
fixup! [android] Implement Android-native Connection Assist UI
Bug 43570: Add tor exit prompt
- - - - -
ec776e7f by clairehurst at 2026-08-05T12:12:43-06:00
fixup! TB 42247: Android helpers for the TorProvider
Bug 43570: Add tor exit prompt
- - - - -
45aace81 by clairehurst at 2026-08-05T12:12:44-06:00
fixup! [android] TBA strings
Bug 43570: Add tor exit prompt
- - - - -
1efdc3dc by clairehurst at 2026-08-05T12:12:44-06:00
fixup! [android] Implement Android-native Connection Assist UI
Bug 43570: Add tor exit prompt
# Conflicts:
# mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/ProviderStoppedViewModel.kt
- - - - -
5b194faf by clairehurst at 2026-08-05T12:13:48-06:00
fixup! TB 40933: Add tor-launcher functionality
Bug 43571: [Android] Add tor exit prompt
Specifically, this commit is a patch provided from
https://gitlab.torproject.org/tpo/applications/tor-browser/-/merge_requests…
- - - - -
21 changed files:
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SecretSettingsFragment.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/ConnectAssistUiState.kt
- + mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/ProviderStoppedViewModel.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistFragment.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistViewModel.kt
- + mobile/android/fenix/app/src/main/res/drawable/bullet_point.xml
- + mobile/android/fenix/app/src/main/res/drawable/loading_wheel.xml
- + mobile/android/fenix/app/src/main/res/drawable/onion_broken.xml
- mobile/android/fenix/app/src/main/res/layout/fragment_tor_connection_assist.xml
- mobile/android/fenix/app/src/main/res/navigation/nav_graph.xml
- mobile/android/fenix/app/src/main/res/values/preference_keys.xml
- mobile/android/fenix/app/src/main/res/values/torbrowser_strings.xml
- mobile/android/fenix/app/src/main/res/xml/secret_settings_preferences.xml
- + mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ProviderStatus.java
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorAndroidIntegration.java
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorConnectStage.java
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorConnectStageName.java
- toolkit/components/tor-launcher/TorProcessAndroid.sys.mjs
- toolkit/modules/TorAndroidIntegration.sys.mjs
- toolkit/modules/TorConnect.sys.mjs
Changes:
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
=====================================
@@ -7,6 +7,7 @@ package org.mozilla.fenix
import android.annotation.SuppressLint
import android.app.assist.AssistContent
import android.app.PendingIntent
+import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@@ -19,6 +20,7 @@ import android.os.Bundle
import android.os.StrictMode
import android.text.format.DateUtils
import android.util.AttributeSet
+import android.util.Log
import android.view.ActionMode
import android.view.KeyEvent
import android.view.LayoutInflater
@@ -40,7 +42,9 @@ import androidx.core.net.toUri
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.text.layoutDirection
import androidx.core.view.doOnLayout
+import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.navigation.fragment.NavHostFragment
@@ -203,6 +207,7 @@ import org.mozilla.fenix.compose.snackbar.SnackbarState
import org.mozilla.fenix.compose.snackbar.Snackbar
import org.mozilla.fenix.tor.CustomSecurityLevelViewModel
import org.mozilla.fenix.tor.TorController
+import org.mozilla.fenix.tor.ProviderStoppedViewModel
import org.mozilla.fenix.tor.UrlQuickLoadViewModel
import org.mozilla.geckoview.TorAndroidIntegration.BootstrapStateChangeListener
import org.mozilla.geckoview.TorConnectStage
@@ -436,6 +441,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, Crash
private var dialog: RedirectDialogFragment? = null
+ private val providerStoppedViewModel: ProviderStoppedViewModel by viewModels()
+
private val urlQuickLoadViewModel: UrlQuickLoadViewModel by viewModels()
private val customSecurityLevelViewModel: CustomSecurityLevelViewModel by viewModels()
@@ -445,6 +452,18 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, Crash
// DO NOT MOVE ANYTHING ABOVE THIS getProfilerTime CALL.
val startTimeProfiler = components.core.engine.profiler?.getProfilerTime()
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ providerStoppedViewModel.providerStoppedStateFlow.collect { isStopped ->
+ Log.d("providerStoppedViewModel", "isStopped = $isStopped")
+ if (isStopped) {
+ navHost.navController.navigate(NavGraphDirections.actionNavigateToConnectionAssistFromAnywhere())
+ providerStoppedViewModel.providerStoppedStateFlow.value = false
+ }
+ }
+ }
+ }
+
// Setup nimbus-cli tooling. This is a NOOP when launching normally.
components.nimbus.sdk.initializeTooling(applicationContext, intent)
components.strictMode.attachListenerToDisablePenaltyDeath(supportFragmentManager)
@@ -1788,4 +1807,14 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, Crash
components.torController.shutdown()
exitProcess(0)
}
+
+ fun openBatterySaverSettings() {
+ try {
+ startActivity(
+ Intent(android.provider.Settings.ACTION_BATTERY_SAVER_SETTINGS)
+ )
+ } catch (e: ActivityNotFoundException) {
+ e.printStackTrace()
+ }
+ }
}
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SecretSettingsFragment.kt
=====================================
@@ -265,6 +265,13 @@ class SecretSettingsFragment : PreferenceFragmentCompat(), SystemInsetsPaddedFra
}
}
+ requirePreference<Preference>(R.string.pref_key_test_kill_tor).apply {
+ setOnPreferenceClickListener {
+ requireContext().components.core.geckoRuntime.torIntegrationController.shutdown()
+ true
+ }
+ }
+
requirePreference<SwitchPreferenceCompat>(R.string.pref_key_enable_fxsuggest).apply {
isVisible = FeatureFlags.FX_SUGGEST
isChecked = settings.enableFxSuggest
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/ConnectAssistUiState.kt
=====================================
@@ -3,8 +3,8 @@ package org.mozilla.fenix.tor
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
-import mozilla.components.lib.crash.R as crashR
import org.mozilla.fenix.R
+import mozilla.components.lib.crash.R as crashR
enum class ConnectAssistUiState(
val progressBarVisible: Boolean,
@@ -33,6 +33,7 @@ enum class ConnectAssistUiState(
val torBootstrapButton2ShouldOpenSettings: Boolean = true,
val wordmarkLogoVisible: Boolean = false,
val torBootstrapButton2ShouldRestartApp: Boolean = false,
+ val torBootstrapButton1ShouldRestartTor: Boolean = false,
) {
Loading(
progressBarVisible = false,
@@ -298,5 +299,30 @@ enum class ConnectAssistUiState(
torBootstrapButton2TextStringResource = crashR.string.mozac_lib_crash_dialog_button_restart,
torBootstrapButton2ShouldOpenSettings = false,
torBootstrapButton2ShouldRestartApp = true,
- )
+ ),
+ ProviderStopped(
+ progressBarVisible = false,
+ backButtonVisible = false,
+ settingsButtonVisible = true,
+ torConnectImageVisible = true,
+ torConnectImageResource = R.drawable.onion_broken,
+ titleLargeTextViewVisible = true,
+ titleLargeTextViewTextStringResource = R.string.connection_assist_provider_stopped_title,
+ titleDescriptionVisible = true,
+ learnMoreStringResource = R.string.connection_assist_final_error_learn_more_link,
+ internetErrorDescription = R.string.connection_assist_provider_stopped_description1,
+ internetErrorDescription1 = R.string.connection_assist_provider_stopped_description2,
+ internetErrorDescription2 = R.string.connection_assist_provider_stopped_description3,
+ titleDescriptionTextStringResource = null,
+ quickstartSwitchVisible = false,
+ regionDropDownVisible = false,
+ torBootstrapButton1Visible = true,
+ torBootstrapButton1TextStringResource = R.string.connection_assist_restart_connection_button,
+ torBootstrapButton1ShouldOpenSettings = false,
+ torBootstrapButton1ShouldRestartTor = true,
+ torBootstrapButton2Visible = false,
+ torBootstrapButton2TextStringResource = null,
+ torBootstrapButton2ShouldOpenSettings = false,
+ torBootstrapButton2ShouldRestartApp = false,
+ ),
}
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/ProviderStoppedViewModel.kt
=====================================
@@ -0,0 +1,49 @@
+package org.mozilla.fenix.tor
+
+import android.app.Application
+import android.util.Log
+import androidx.lifecycle.AndroidViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import org.mozilla.fenix.ext.components
+import org.mozilla.geckoview.TorAndroidIntegration
+import org.mozilla.geckoview.TorConnectStage
+import org.mozilla.geckoview.TorConnectStageName
+
+class ProviderStoppedViewModel(
+ application: Application,
+) : AndroidViewModel(application), TorAndroidIntegration.BootstrapStateChangeListener {
+
+ private val TAG = "ProviderStoppedViewModel"
+
+ internal val providerStoppedStateFlow: MutableStateFlow<Boolean> by lazy { MutableStateFlow(false) }
+
+ private val _maybeConfigIssue = MutableStateFlow(false)
+ internal val maybeConfigIssue: StateFlow<Boolean> = _maybeConfigIssue
+
+ override fun onBootstrapStageChange(stage: TorConnectStage) {
+ when (stage.name) {
+ TorConnectStageName.ProviderStopped -> {
+ Log.d(TAG, "ProviderStopped detected")
+ providerStoppedStateFlow.value = true
+ }
+
+ else -> {
+ providerStoppedStateFlow.value = false
+ }
+ }
+
+ _maybeConfigIssue.value = stage.providerStatus.maybeConfigIssue
+ }
+
+ init {
+ getApplication<Application>().components.core.geckoRuntime.torIntegrationController.registerBootstrapStateChangeListener(this)
+ }
+
+ override fun onCleared() {
+ getApplication<Application>().components.core.geckoRuntime.torIntegrationController.unregisterBootstrapStateChangeListener(this)
+ }
+
+ override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) {}
+
+}
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistFragment.kt
=====================================
@@ -8,6 +8,7 @@ import android.graphics.Color
import android.graphics.Typeface
import android.os.Build
import android.os.Bundle
+import android.os.PowerManager
import android.text.SpannableString
import android.text.Spanned
import android.text.TextPaint
@@ -17,9 +18,11 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.annotation.StringRes
import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -43,14 +46,22 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.LinkAnnotation
+import androidx.compose.ui.text.TextLinkStyles
import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import androidx.core.content.getSystemService
import androidx.core.view.isEmpty
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
@@ -68,6 +79,7 @@ import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.FragmentTorConnectionAssistBinding
import org.mozilla.fenix.e2e.SystemInsetsPaddedFragment
+import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.hideToolbar
class TorConnectionAssistFragment : Fragment(), UserInteractionHandler, SystemInsetsPaddedFragment {
@@ -76,6 +88,7 @@ class TorConnectionAssistFragment : Fragment(), UserInteractionHandler, SystemIn
private val progressViewModel: TorBootstrapProgressViewModel by viewModels()
private val quickstartViewModel: QuickstartViewModel by activityViewModels()
private val torConnectionAssistViewModel : TorConnectionAssistViewModel by viewModels()
+ private val providerStoppedViewModel : ProviderStoppedViewModel by activityViewModels()
private var _binding: FragmentTorConnectionAssistBinding? = null
private val binding get() = _binding!!
@@ -103,6 +116,7 @@ class TorConnectionAssistFragment : Fragment(), UserInteractionHandler, SystemIn
Log.d(TAG, "shouldOpenHome = $it")
if (it) {
openHome()
+ torConnectionAssistViewModel.shouldOpenHome.value = false
}
}
@@ -200,13 +214,142 @@ class TorConnectionAssistFragment : Fragment(), UserInteractionHandler, SystemIn
binding.torConnectImage.setImageResource(screen.torConnectImageResource)
}
+ @Composable
+ fun TextWithClickable(
+ @StringRes mainTextRes: Int,
+ @StringRes clickableTextRes: Int,
+ onClick: () -> Unit,
+ style: TextStyle,
+ tag: String,
+ ) {
+ Text(
+ text = buildAnnotatedString {
+ val clickableText = stringResource(clickableTextRes)
+ val plainText = stringResource(mainTextRes, clickableText)
+ append(plainText)
+ addLink(
+ clickable = LinkAnnotation.Clickable(
+ tag = tag,
+ styles = TextLinkStyles(
+ style = style.toSpanStyle().copy(textDecoration = TextDecoration.Underline),
+ pressedStyle = style.toSpanStyle(),
+ ),
+ linkInteractionListener = { onClick() }
+ ),
+ start = plainText.indexOf(clickableText),
+ end = plainText.indexOf(clickableText) + clickableText.length,
+ )
+ },
+ style = style,
+ )
+ }
+
+ @Preview
+ @Composable
+ fun DaemonFailedScreen(
+ isDeviceInPowerSaveMode: Boolean = false,
+ maybeConfigIssue: Boolean = false,
+ style: TextStyle = TextStyle(
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ fontWeight = FontWeight(400),
+ color = PhotonColors.LightGrey05,
+ letterSpacing = 0.5.sp,
+ )
+ ) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterVertically),
+ ) {
+ Text(
+ text = stringResource(R.string.connection_assist_provider_stopped_description1),
+ style = style,
+ )
+ Text(
+ text = stringResource(R.string.connection_assist_provider_stopped_description2),
+ style = style.copy(fontWeight = FontWeight.Bold),
+ )
+ if (isDeviceInPowerSaveMode && !maybeConfigIssue) {
+ Row {
+ Image(
+ painter = painterResource(R.drawable.bullet_point),
+ contentDescription = null,
+ modifier = Modifier.padding(6.dp)
+ )
+ Text(
+ style = style,
+ text = stringResource(R.string.connection_assist_provider_stopped_description_battery1),
+ )
+ }
+ }
+ Row {
+ Image(
+ painter = painterResource(R.drawable.bullet_point),
+ contentDescription = null,
+ modifier = Modifier.padding(6.dp)
+ )
+ Text(
+ style = style,
+ text = stringResource(R.string.connection_assist_provider_stopped_description3),
+ )
+ }
+ Text(
+ text = stringResource(R.string.connection_assist_provider_stopped_description4),
+ style = style.copy(fontWeight = FontWeight.Bold),
+ )
+ if (isDeviceInPowerSaveMode && !maybeConfigIssue) {
+ Row {
+ Image(
+ painter = painterResource(R.drawable.bullet_point),
+ contentDescription = null,
+ modifier = Modifier.padding(6.dp),
+ )
+ TextWithClickable(
+ mainTextRes = R.string.connection_assist_provider_stopped_description_turn_off_battery_saver2,
+ clickableTextRes = R.string.connection_assist_provider_stopped_description_turn_off_battery_saver_clickable,
+ onClick = { (requireActivity() as HomeActivity).openBatterySaverSettings() },
+ style = style,
+ tag = BATTERY_SETTINGS_TAG,
+ )
+ }
+ } else {
+ Row {
+ Image(
+ painter = painterResource(R.drawable.bullet_point),
+ contentDescription = null,
+ modifier = Modifier.padding(6.dp),
+ )
+ Text(
+ text = stringResource(R.string.connection_assist_provider_stopped_description5),
+ style = style,
+ )
+ }
+ }
+ Text(
+ text = stringResource(R.string.connection_assist_provider_stopped_description6,
+ stringResource(R.string.connection_assist_provider_stopped_description7)
+ ),
+ style = style,
+ )
+ }
+ }
+
private fun setTitle(screen: ConnectAssistUiState) {
binding.titleLargeTextView.visibility =
if (screen.titleLargeTextViewVisible) View.VISIBLE else View.GONE
binding.titleLargeTextView.text = getString(screen.titleLargeTextViewTextStringResource)
binding.titleDescription.visibility =
if (screen.titleDescriptionVisible) View.VISIBLE else View.GONE
- if (screen.learnMoreStringResource != null && screen.internetErrorDescription != null) {
+ binding.daemonFailedDescription.visibility = View.GONE
+ if (screen == ConnectAssistUiState.ProviderStopped) {
+ binding.titleDescription.visibility = View.GONE
+ binding.daemonFailedDescription.setContent {
+ DaemonFailedScreen(
+ isDeviceInPowerSaveMode = requireContext().getSystemService<PowerManager>()?.isPowerSaveMode ?: false,
+ maybeConfigIssue = providerStoppedViewModel.maybeConfigIssue.collectAsState().value,
+ )
+ }
+ binding.daemonFailedDescription.visibility = View.VISIBLE
+ } else if (screen.learnMoreStringResource != null && screen.internetErrorDescription != null) {
val learnMore: String = "" // getString(screen.learnMoreStringResource) tor-browser#43198 uncomment and add back once we have the "Learn more" screens for relevant pages
val internetErrorDescription: String =
if (screen.internetErrorDescription1 == null) {
@@ -434,6 +577,13 @@ class TorConnectionAssistFragment : Fragment(), UserInteractionHandler, SystemIn
setOnClickListener {
if (screen.torBootstrapButton1ShouldOpenSettings) {
openTorConnectionSettings()
+ } else if (screen.torBootstrapButton1ShouldRestartTor) {
+ requireContext().components.core.geckoRuntime.torIntegrationController.restartProvider()
+ backgroundTintList = AppCompatResources.getColorStateList(
+ requireContext(),
+ R.color.disabled_connect_button_purple,
+ )
+ text = getString(R.string.connection_assist_restarting_connection_button)
} else {
torConnectionAssistViewModel.handleConnect(screen)
}
@@ -561,8 +711,11 @@ class TorConnectionAssistFragment : Fragment(), UserInteractionHandler, SystemIn
}
override fun onBackPressed(): Boolean {
- torConnectionAssistViewModel.handleBackButtonPressed(requireActivity() as HomeActivity)
- return true
+ return torConnectionAssistViewModel.handleBackButtonPressed(requireActivity() as HomeActivity)
+ }
+
+ companion object {
+ const val BATTERY_SETTINGS_TAG = "BATTERY_SETTINGS_TAG"
}
}
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistViewModel.kt
=====================================
@@ -138,17 +138,18 @@ class TorConnectionAssistViewModel(
torConnectStage.collect {
Log.d(TAG, "torConnectStageName: ${it?.name}")
when (it?.name) {
- TorConnectStageName.Disabled -> shouldOpenHome.value = true // TODO use TorConnect.enabled instead to determine this
- TorConnectStageName.Loading -> _torConnectScreen.value = ConnectAssistUiState.Loading
- TorConnectStageName.Start -> _torConnectScreen.value = ConnectAssistUiState.Start
- TorConnectStageName.Bootstrapping -> _torConnectScreen.value = handleBootstrapTrigger(it.bootstrapTrigger)
- TorConnectStageName.Offline -> _torConnectScreen.value = ConnectAssistUiState.Offline
- TorConnectStageName.ChooseRegion -> _torConnectScreen.value = ConnectAssistUiState.ChooseRegion
- TorConnectStageName.RegionNotFound -> _torConnectScreen.value = ConnectAssistUiState.RegionNotFound
- TorConnectStageName.ConfirmRegion -> _torConnectScreen.value = ConnectAssistUiState.ConfirmRegion
- TorConnectStageName.FinalError -> _torConnectScreen.value = ConnectAssistUiState.FinalError
- TorConnectStageName.Bootstrapped -> shouldOpenHome.value = true
- null -> {}
+ TorConnectStageName.Disabled -> shouldOpenHome.value = true // TODO use TorConnect.enabled instead to determine this
+ TorConnectStageName.Loading -> _torConnectScreen.value = ConnectAssistUiState.Loading
+ TorConnectStageName.ProviderStopped -> _torConnectScreen.value = ConnectAssistUiState.ProviderStopped
+ TorConnectStageName.Start -> _torConnectScreen.value = ConnectAssistUiState.Start
+ TorConnectStageName.Bootstrapping -> _torConnectScreen.value = handleBootstrapTrigger(it.bootstrapTrigger)
+ TorConnectStageName.Offline -> _torConnectScreen.value = ConnectAssistUiState.Offline
+ TorConnectStageName.ChooseRegion -> _torConnectScreen.value = ConnectAssistUiState.ChooseRegion
+ TorConnectStageName.RegionNotFound -> _torConnectScreen.value = ConnectAssistUiState.RegionNotFound
+ TorConnectStageName.ConfirmRegion -> _torConnectScreen.value = ConnectAssistUiState.ConfirmRegion
+ TorConnectStageName.FinalError -> _torConnectScreen.value = ConnectAssistUiState.FinalError
+ TorConnectStageName.Bootstrapped -> shouldOpenHome.value = true
+ null -> {}
}
}
}
@@ -168,11 +169,12 @@ class TorConnectionAssistViewModel(
}
}
- fun handleBackButtonPressed(homeActivity: HomeActivity) {
- when (torConnectScreen.value) {
- ConnectAssistUiState.Loading -> homeActivity.shutDown()
- ConnectAssistUiState.Start -> homeActivity.shutDown()
- else -> torAndroidIntegration.startAgain()
+ fun handleBackButtonPressed(homeActivity: HomeActivity): Boolean {
+ return when (torConnectScreen.value) {
+ ConnectAssistUiState.Loading -> homeActivity.shutDown()
+ ConnectAssistUiState.Start -> homeActivity.shutDown()
+ ConnectAssistUiState.ProviderStopped -> false
+ else -> torAndroidIntegration.startAgain().let { true }
}
}
=====================================
mobile/android/fenix/app/src/main/res/drawable/bullet_point.xml
=====================================
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:autoMirrored="true"
+ android:height="6dp"
+ android:viewportHeight="6"
+ android:viewportWidth="6"
+ android:width="6dp">
+ <path
+ android:fillColor="#FBFBFE"
+ android:pathData="M3,3m-3,0a3,3 0,1 1,6 0a3,3 0,1 1,-6 0" />
+</vector>
=====================================
mobile/android/fenix/app/src/main/res/drawable/loading_wheel.xml
=====================================
@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:autoMirrored="true"
+ android:height="16dp"
+ android:viewportHeight="16"
+ android:viewportWidth="16"
+ android:width="16dp">
+ <path
+ android:fillAlpha="0.4"
+ android:fillColor="#FBFBFE"
+ android:fillType="evenOdd"
+ android:pathData="M8,15C8,14.448 7.549,14.009 7.004,13.918C4.164,13.443 2,10.974 2,8C2,4.686 4.686,2 8,2C10.974,2 13.443,4.164 13.918,7.004C14.009,7.549 14.448,8 15,8C15.552,8 16.007,7.55 15.938,7.002C15.447,3.055 12.08,0 8,0C3.582,0 0,3.582 0,8C0,12.08 3.055,15.447 7.002,15.938C7.55,16.007 8,15.552 8,15Z" />
+</vector>
=====================================
mobile/android/fenix/app/src/main/res/drawable/onion_broken.xml
=====================================
@@ -0,0 +1,21 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="40dp" android:viewportHeight="40" android:viewportWidth="40" android:width="40dp">
+
+ <path android:fillColor="#FBFBFE" android:pathData="M33.259,33.258C29.865,36.651 25.178,38.75 20,38.75C9.645,38.75 1.25,30.355 1.25,20C1.25,14.823 3.348,10.135 6.741,6.742L8.813,8.814C5.95,11.677 4.18,15.632 4.18,20C4.18,28.737 11.263,35.82 20,35.82C24.369,35.82 28.324,34.049 31.187,31.186L33.259,33.258Z"/>
+
+ <path android:fillColor="#FBFBFE" android:pathData="M35.284,30.863C37.466,27.798 38.75,24.049 38.75,20C38.75,9.645 30.355,1.25 20,1.25C15.951,1.25 12.202,2.533 9.137,4.716L11.243,6.822C13.751,5.153 16.762,4.18 20,4.18C28.737,4.18 35.82,11.263 35.82,20C35.82,23.238 34.847,26.249 33.178,28.757L35.284,30.863Z"/>
+
+ <path android:fillColor="#FBFBFE" android:pathData="M31.226,26.805C32.431,24.82 33.125,22.491 33.125,20C33.125,12.751 27.249,6.875 20,6.875C17.509,6.875 15.18,7.569 13.195,8.774L15.347,10.926C16.742,10.209 18.324,9.805 20,9.805C25.631,9.805 30.195,14.369 30.195,20C30.195,21.676 29.791,23.258 29.074,24.653L31.226,26.805Z"/>
+
+ <path android:fillColor="#FBFBFE" android:pathData="M29.281,29.281L27.209,27.209C25.364,29.054 22.815,30.195 20,30.195C14.369,30.195 9.805,25.631 9.805,20C9.805,17.185 10.946,14.636 12.79,12.791L10.719,10.72C8.344,13.095 6.875,16.376 6.875,20C6.875,27.249 12.751,33.125 20,33.125C23.625,33.125 26.906,31.656 29.281,29.281Z"/>
+
+ <path android:fillColor="#FBFBFE" android:pathData="M14.696,14.697C13.339,16.054 12.5,17.929 12.5,20C12.5,24.142 15.858,27.5 20,27.5C22.071,27.5 23.946,26.66 25.303,25.303L23.232,23.232C22.405,24.059 21.262,24.57 20,24.57C17.476,24.57 15.43,22.524 15.43,20C15.43,18.738 15.941,17.596 16.768,16.769L14.696,14.697Z"/>
+
+ <path android:fillColor="#FBFBFE" android:pathData="M24.568,20.147C24.569,20.098 24.57,20.049 24.57,20C24.57,17.476 22.524,15.43 20,15.43C19.951,15.43 19.902,15.431 19.853,15.432L17.388,12.967C18.202,12.665 19.081,12.5 20,12.5C24.142,12.5 27.5,15.858 27.5,20C27.5,20.919 27.335,21.799 27.033,22.612L24.568,20.147Z"/>
+
+ <path android:fillColor="#FBFBFE" android:pathData="M20,38.75C9.645,38.75 1.25,30.355 1.25,20C1.25,14.823 3.348,10.135 6.741,6.742L20,20V38.75Z"/>
+
+ <path android:fillColor="#FBFBFE" android:pathData="M20,15.579V1.25C15.951,1.25 12.202,2.533 9.137,4.716L20,15.579Z"/>
+
+ <path android:fillColor="#FF9AA2" android:pathData="M35.291,39.061C35.085,39.062 34.882,39.021 34.692,38.943C34.503,38.864 34.33,38.749 34.186,38.604L2.31,6.731C2.037,6.434 1.889,6.043 1.898,5.64C1.906,5.236 2.07,4.851 2.355,4.566C2.64,4.28 3.025,4.116 3.428,4.107C3.832,4.099 4.223,4.246 4.52,4.519L36.396,36.394C36.688,36.688 36.851,37.085 36.851,37.5C36.851,37.915 36.688,38.312 36.396,38.606C36.25,38.751 36.078,38.866 35.888,38.944C35.699,39.022 35.496,39.062 35.291,39.061Z"/>
+
+</vector>
=====================================
mobile/android/fenix/app/src/main/res/layout/fragment_tor_connection_assist.xml
=====================================
@@ -99,6 +99,19 @@
android:textSize="14sp"
app:layout_constraintTop_toBottomOf="@id/title_large_text_view" />
+ <androidx.compose.ui.platform.ComposeView
+ android:id="@+id/daemon_failed_description"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:paddingHorizontal="24dp"
+ android:paddingVertical="16dp"
+ app:layout_constraintBottom_toTopOf="@id/tor_bootstrap_button_1"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/title_large_text_view"
+ app:layout_constraintVertical_bias="0" />
+
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/quickstart_switch"
android:layout_width="match_parent"
=====================================
mobile/android/fenix/app/src/main/res/navigation/nav_graph.xml
=====================================
@@ -27,6 +27,10 @@
app:popUpTo="@id/torConnectionAssistFragment"
app:popUpToInclusive="true"/>
+ <action
+ android:id="@+id/action_navigate_to_connection_assist_from_anywhere"
+ app:destination="@id/torConnectionAssistFragment" />
+
<action
android:id="@+id/action_global_home"
app:destination="@id/homeFragment"
=====================================
mobile/android/fenix/app/src/main/res/values/preference_keys.xml
=====================================
@@ -652,6 +652,7 @@
<string name="pref_key_tor_network_settings_bridge_config" translatable="false">pref_key_tor_network_settings_bridge_config</string>
<string name="pref_key_tor_logs" translatable="false">pref_key_tor_logs</string>
<string name="pref_key_about_config_shortcut" translatable="false">pref_key_about_config_shortcut</string>
+ <string name="pref_key_test_kill_tor" translatable="false">pref_key_test_kill_tor</string>
<string name="pref_key_tor_network_settings_bridge_config_explanation" translatable="false">pref_key_tor_network_settings_bridge_config_explanation</string>
<string name="pref_key_tor_network_settings_bridge_config_toggle" translatable="false">pref_key_tor_network_settings_bridge_config_toggle</string>
<string name="pref_key_tor_network_settings_bridge_config_builtin_bridge_obfs4" translatable="false">pref_key_tor_network_settings_bridge_config_builtin_bridge_obfs4</string>
=====================================
mobile/android/fenix/app/src/main/res/values/torbrowser_strings.xml
=====================================
@@ -135,6 +135,32 @@
<string name="connection_assist_back_button_content_description_start_again">Start again</string>
<!-- Connection assist. This message is shown briefly after the connection to the Tor network completes. -->
<string name="connection_assist_bootstrap_succeeded_toast_message">Connected to Tor</string>
+ <!-- Connection assist. Here "process" is a noun, referring to the execution of a computer program. "Tor process" specifically refers to the execution of the tor program, which is the program that handles the connection to the "Tor network". -->
+ <string name="connection_assist_provider_stopped_title">The Tor process has stopped working</string>
+ <!-- Connection assist. Here "process" is a noun, referring to the execution of a computer program. -->
+ <string name="connection_assist_provider_stopped_description1">The underlying process that controls your connection to the Tor network has stopped working.</string>
+ <!-- Connection assist. -->
+ <string name="connection_assist_provider_stopped_description2">What could be causing this?</string>
+ <!-- Connection assist. Here "process" is a noun, referring to the execution of a computer program. -->
+ <string name="connection_assist_provider_stopped_description_battery1">Your device has quit the process to save battery.</string>
+ <!-- Connection assist. Here "process" is a noun, referring to the execution of a computer program. "Tor process" specifically refers to the execution of the tor program, which is the program that handles the connection to the "Tor network". -->
+ <string name="connection_assist_provider_stopped_description3">The Tor process has stopped working due to a technical issue.</string>
+ <!-- Connection assist. -->
+ <string name="connection_assist_provider_stopped_description4">What can you do about it?</string>
+ <!-- Connection assist. Here "process" is a noun, referring to the execution of a computer program. "Tor process" specifically refers to the execution of the tor program, which is the program that handles the connection to the "Tor network". -->
+ <string name="connection_assist_provider_stopped_description5">Try restarting the Tor process (this won’t close your browser tabs).</string>
+ <!-- Connection assist.-->
+ <string name="connection_assist_provider_stopped_description_turn_off_battery_saver_clickable">Turn off Battery Saver on your device</string>
+ <!-- Connection assist. "%1$s" will be replaced with connection_assist_provider_stopped_description_turn_off_battery_saver_clickable. Here "process" is a noun, referring to the execution of a computer program. "Tor process" specifically refers to the execution of the tor program, which is the program that handles the connection to the "Tor network". -->
+ <string name="connection_assist_provider_stopped_description_turn_off_battery_saver2">%1$s and try restarting the Tor process (this won’t close your browser tabs).</string>
+ <!-- Connection assist. "%1$s" will be replaced with connection_assist_provider_stopped_description7. -->
+ <string name="connection_assist_provider_stopped_description6">If the problem remains, get in contact with us through one of our %1$s.</string>
+ <!-- Connection assist. -->
+ <string name="connection_assist_provider_stopped_description7">support channels</string>
+ <!-- Connection assist. Here "process" is a noun, referring to the execution of a computer program. "Tor process" specifically refers to the execution of the tor program, which is the program that handles the connection to the "Tor network". -->
+ <string name="connection_assist_restart_connection_button">Restart Tor process</string>
+ <!-- Connection assist. Here "process" is a noun, referring to the execution of a computer program. "Tor process" specifically refers to the execution of the tor program, which is the program that handles the connection to the "Tor network". -->
+ <string name="connection_assist_restarting_connection_button">Restarting Tor process…</string>
<!-- Notification title for closing browser tabs. "%s" will be replaced with the localised application name, such as "Tor Browser". -->
<string name="notification_close_tor_browser_tabs">Close %s’s tabs?</string>
=====================================
mobile/android/fenix/app/src/main/res/xml/secret_settings_preferences.xml
=====================================
@@ -135,6 +135,10 @@
android:key="@string/pref_key_enable_lna_tracker_blocking_enabled"
android:title="@string/preferences_debug_settings_enable_lna_tracker_blocking"
app:iconSpaceReserved="false" />
+ <Preference
+ android:key="@string/pref_key_test_kill_tor"
+ android:title="TEST kill tor daemon"
+ app:iconSpaceReserved="false" />
</PreferenceCategory>
<PreferenceCategory
=====================================
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ProviderStatus.java
=====================================
@@ -0,0 +1,11 @@
+package org.mozilla.geckoview;
+
+import org.mozilla.gecko.util.GeckoBundle;
+
+public class ProviderStatus {
+ public Boolean maybeConfigIssue;
+
+ public ProviderStatus(GeckoBundle bundle) {
+ maybeConfigIssue = bundle.getBoolean("maybeConfigIssue");
+ }
+}
=====================================
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorAndroidIntegration.java
=====================================
@@ -57,6 +57,7 @@ public class TorAndroidIntegration implements BundleEventListener {
private static final String EVENT_BOOTSTRAP_BEGIN_AUTO = "GeckoView:Tor:BootstrapBeginAuto";
private static final String EVENT_BOOTSTRAP_CANCEL = "GeckoView:Tor:BootstrapCancel";
private static final String EVENT_START_AGAIN = "GeckoView:Tor:StartAgain";
+ private static final String EVENT_RESTART_PROVIDER = "GeckoView:Tor:RestartProvider";
private static final String EVENT_QUICKSTART_GET = "GeckoView:Tor:QuickstartGet";
private static final String EVENT_QUICKSTART_SET = "GeckoView:Tor:QuickstartSet";
private static final String EVENT_REGION_NAMES_GET = "GeckoView:Tor:RegionNamesGet";
@@ -706,6 +707,10 @@ public class TorAndroidIntegration implements BundleEventListener {
return EventDispatcher.getInstance().queryVoid(EVENT_START_AGAIN);
}
+ public @NonNull GeckoResult<Void> restartProvider() {
+ return EventDispatcher.getInstance().queryVoid(EVENT_RESTART_PROVIDER);
+ }
+
public interface QuickstartGetter {
void onValue(boolean enabled);
}
=====================================
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorConnectStage.java
=====================================
@@ -13,6 +13,7 @@ public class TorConnectStage {
public Boolean potentiallyBlocked;
public Boolean tryAgain;
public TorBootstrappingStatus bootstrappingStatus;
+ public ProviderStatus providerStatus;
public TorConnectStage(GeckoBundle bundle) {
name = TorConnectStageName.fromString(bundle.getString("name"));
@@ -26,6 +27,7 @@ public class TorConnectStage {
error = new TorConnectError(bundle.getBundle("error"));
}
bootstrappingStatus = new TorBootstrappingStatus(bundle.getBundle("bootstrappingStatus"));
+ providerStatus = new ProviderStatus(bundle.getBundle("providerStatus"));
}
public Boolean isBootstrapped() {
=====================================
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorConnectStageName.java
=====================================
@@ -3,9 +3,10 @@ package org.mozilla.geckoview;
import java.security.InvalidParameterException;
public enum TorConnectStageName {
- // These names should match entries from TorConnectStage in TorConnect.sys.mjs at ~ln163.
+ // These names should match entries from TorConnectStage in TorConnect.sys.mjs at ~ln674.
Disabled("Disabled"),
Loading("Loading"),
+ ProviderStopped("ProviderStopped"),
Start("Start"),
Bootstrapping("Bootstrapping"),
Offline("Offline"),
=====================================
toolkit/components/tor-launcher/TorProcessAndroid.sys.mjs
=====================================
@@ -51,6 +51,11 @@ export class TorProcessAndroid {
* it failed to start tor.
*/
#startReject = null;
+ /**
+ * Tells whether we ever registered ourself as the listener to the various
+ * process events.
+ */
+ #registeredListeners = false;
onExit = () => {};
@@ -74,6 +79,7 @@ export class TorProcessAndroid {
this,
Object.values(TorIncomingEvents)
);
+ this.#registeredListeners = true;
let config;
try {
config = await lazy.EventDispatcher.instance.sendRequestForResult(
@@ -103,10 +109,13 @@ export class TorProcessAndroid {
});
logger.debug("Sent the stop event.");
this.#processHandle = null;
- lazy.EventDispatcher.instance.unregisterListener(
- this,
- Object.values(TorIncomingEvents)
- );
+ if (this.#registeredListeners) {
+ lazy.EventDispatcher.instance.unregisterListener(
+ this,
+ Object.values(TorIncomingEvents)
+ );
+ this.#registeredListeners = false;
+ }
}
onEvent(event, data, _callback) {
=====================================
toolkit/modules/TorAndroidIntegration.sys.mjs
=====================================
@@ -48,6 +48,7 @@ const ListenedEvents = Object.freeze({
bootstrapBeginAuto: "GeckoView:Tor:BootstrapBeginAuto",
bootstrapCancel: "GeckoView:Tor:BootstrapCancel",
startAgain: "GeckoView:Tor:StartAgain",
+ restartProvider: "GeckoView:Tor:RestartProvider",
quickstartGet: "GeckoView:Tor:QuickstartGet",
quickstartSet: "GeckoView:Tor:QuickstartSet",
regionNamesGet: "GeckoView:Tor:RegionNamesGet",
@@ -98,11 +99,6 @@ class TorAndroidIntegrationImpl {
lazy.TorConnect.init();
lazy.TorDomainIsolator.init();
-
- // On Android immediately call firstWindowLoaded. This should be safe to
- // call since it will await the initialisation of the TorProvider set up
- // by TorProviderBuilder.init.
- lazy.TorProviderBuilder.firstWindowLoaded();
}
observe(subj, topic) {
@@ -203,6 +199,9 @@ class TorAndroidIntegrationImpl {
case ListenedEvents.startAgain:
lazy.TorConnect.startAgain();
break;
+ case ListenedEvents.restartProvider:
+ lazy.TorConnect.restartProvider();
+ break;
case ListenedEvents.quickstartGet:
callback?.onSuccess(lazy.TorConnect.quickstart);
return;
=====================================
toolkit/modules/TorConnect.sys.mjs
=====================================
@@ -1109,12 +1109,9 @@ export const TorConnect = {
throw new Error(`Trying to set the stage to ${name} during a bootstrap`);
}
if (!this._providerRunning && name !== TorConnectStage.ProviderStopped) {
- if (!lazy.TorLauncherUtil.isAndroid) {
- // TODO: Remove Android exception.
- throw new Error(
- `Trying to set the stage to ${name} when provider is not running`
- );
- }
+ throw new Error(
+ `Trying to set the stage to ${name} when provider is not running`
+ );
}
lazy.logger.info(`Entering stage ${name}`);
@@ -1596,13 +1593,8 @@ export const TorConnect = {
return;
}
if (!this._providerRunning && stage !== TorConnectStage.ProviderStopped) {
- if (!lazy.TorLauncherUtil.isAndroid) {
- // TODO: Remove Android exception.
- lazy.logger.warn(
- `Cannot move to ${stage} when provider is not running`
- );
- return;
- }
+ lazy.logger.warn(`Cannot move to ${stage} when provider is not running`);
+ return;
}
if (this._stageName === TorConnectStage.Loading) {
if (stage === TorConnectStage.ProviderStopped) {
@@ -1709,13 +1701,7 @@ export const TorConnect = {
// But other methods should take into account that _providerRunning is now
// `false` to early return and guarantee that we enter this
// ProviderStopped stage.
- if (lazy.TorLauncherUtil.isAndroid) {
- // TODO: Remove this Android path when android supports the
- // `ProviderStopped` stage.
- this._makeStageRequest(TorConnectStage.Start, true);
- } else {
- this._makeStageRequest(TorConnectStage.ProviderStopped, true);
- }
+ this._makeStageRequest(TorConnectStage.ProviderStopped, true);
}
}
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/14f24e…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/14f24e…
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! Firefox preference overrides.
by morgan (@morgan) 05 Aug '26
by morgan (@morgan) 05 Aug '26
05 Aug '26
morgan pushed to branch mullvad-browser-153.0esr-16.0-1 at The Tor Project / Applications / Mullvad Browser
Commits:
2ded1d0f by Henry Wilkes at 2026-08-05T15:44:59+00:00
fixup! Firefox preference overrides.
BB 45187: Disable delete private downloads.
- - - - -
2bc2f83b by Henry Wilkes at 2026-08-05T15:45:00+00:00
fixup! BB 42027: Base Browser migration procedures.
BB 45187: Clear the private downloads preferences in case we re-enable
this again in the future.
- - - - -
2 changed files:
- browser/app/profile/001-base-profile.js
- browser/components/ProfileDataUpgrader.sys.mjs
Changes:
=====================================
browser/app/profile/001-base-profile.js
=====================================
@@ -72,6 +72,10 @@ pref("browser.helperApps.deleteTempFileOnExit", true);
// Prevent download stuffing / DOS (tor-browser#41764)
pref("browser.download.enable_spam_prevention", true);
+// tor-browser#45187: Disable offering to delete downloaded files when closing
+// private browsing windows because it does not work as expected in 16.0.
+pref("browser.download.enableDeletePrivate", false);
+
// tor-browser#41131: This is normally gated on
// privacy.sanitize.sanitizeOnShutdown, which is false by default. But in case
// users enable it, make sure background tasks are not used for this, since we
=====================================
browser/components/ProfileDataUpgrader.sys.mjs
=====================================
@@ -1041,6 +1041,8 @@ export let ProfileDataUpgrader = {
// Version 5: 15.0a3: Disable LaterRun using prefs. tor-browser#42630.
// Version 6: 15.0a4: Reset browser colors. tor-browser#43850.
// Version 7: 16.0a10: Reset safe browsing preferences. tor-browser#44802.
+ // Also reset delete downloads preferences.
+ // tor-browser#45187.
const MIGRATION_VERSION = 7;
const MIGRATION_PREF = "basebrowser.migration.version";
@@ -1140,16 +1142,20 @@ export let ProfileDataUpgrader = {
}
}
if (currentVersion < 7) {
- // Clear these preferences since:
- // + They aren't expected to work.
- // + We are hiding the UI to change these. tor-browser#44802.
for (const prefName of [
+ // Clear these preferences since:
+ // + They aren't expected to work.
+ // + We are hiding the UI to change these. tor-browser#44802.
"browser.safebrowsing.phishing.enabled",
"browser.safebrowsing.malware.enabled",
"browser.safebrowsing.downloads.enabled",
"browser.safebrowsing.downloads.remote.block_uncommon",
"browser.safebrowsing.downloads.remote.block_potentially_unwanted",
"urlclassifier.malwareTable",
+ // Clear the downloads preferences for alpha profiles in case we want to
+ // re-offer this feature again in the future. tor-browser#45187.
+ "browser.download.deletePrivate",
+ "browser.download.deletePrivate.chosen",
]) {
Services.prefs.clearUserPref(prefName);
}
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/cf…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/cf…
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! Firefox preference overrides.
by morgan (@morgan) 05 Aug '26
by morgan (@morgan) 05 Aug '26
05 Aug '26
morgan pushed to branch tor-browser-153.0esr-16.0-1 at The Tor Project / Applications / Tor Browser
Commits:
75a95c6d by Henry Wilkes at 2026-08-05T15:40:33+00:00
fixup! Firefox preference overrides.
BB 45187: Disable delete private downloads.
- - - - -
14f24ee0 by Henry Wilkes at 2026-08-05T15:40:33+00:00
fixup! BB 42027: Base Browser migration procedures.
BB 45187: Clear the private downloads preferences in case we re-enable
this again in the future.
- - - - -
2 changed files:
- browser/app/profile/001-base-profile.js
- browser/components/ProfileDataUpgrader.sys.mjs
Changes:
=====================================
browser/app/profile/001-base-profile.js
=====================================
@@ -72,6 +72,10 @@ pref("browser.helperApps.deleteTempFileOnExit", true);
// Prevent download stuffing / DOS (tor-browser#41764)
pref("browser.download.enable_spam_prevention", true);
+// tor-browser#45187: Disable offering to delete downloaded files when closing
+// private browsing windows because it does not work as expected in 16.0.
+pref("browser.download.enableDeletePrivate", false);
+
// tor-browser#41131: This is normally gated on
// privacy.sanitize.sanitizeOnShutdown, which is false by default. But in case
// users enable it, make sure background tasks are not used for this, since we
=====================================
browser/components/ProfileDataUpgrader.sys.mjs
=====================================
@@ -1041,6 +1041,8 @@ export let ProfileDataUpgrader = {
// Version 5: 15.0a3: Disable LaterRun using prefs. tor-browser#42630.
// Version 6: 15.0a4: Reset browser colors. tor-browser#43850.
// Version 7: 16.0a10: Reset safe browsing preferences. tor-browser#44802.
+ // Also reset delete downloads preferences.
+ // tor-browser#45187.
const MIGRATION_VERSION = 7;
const MIGRATION_PREF = "basebrowser.migration.version";
@@ -1140,16 +1142,20 @@ export let ProfileDataUpgrader = {
}
}
if (currentVersion < 7) {
- // Clear these preferences since:
- // + They aren't expected to work.
- // + We are hiding the UI to change these. tor-browser#44802.
for (const prefName of [
+ // Clear these preferences since:
+ // + They aren't expected to work.
+ // + We are hiding the UI to change these. tor-browser#44802.
"browser.safebrowsing.phishing.enabled",
"browser.safebrowsing.malware.enabled",
"browser.safebrowsing.downloads.enabled",
"browser.safebrowsing.downloads.remote.block_uncommon",
"browser.safebrowsing.downloads.remote.block_potentially_unwanted",
"urlclassifier.malwareTable",
+ // Clear the downloads preferences for alpha profiles in case we want to
+ // re-offer this feature again in the future. tor-browser#45187.
+ "browser.download.deletePrivate",
+ "browser.download.deletePrivate.chosen",
]) {
Services.prefs.clearUserPref(prefName);
}
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/72159c…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/72159c…
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! MB 21: Disable the password manager
by morgan (@morgan) 05 Aug '26
by morgan (@morgan) 05 Aug '26
05 Aug '26
morgan pushed to branch mullvad-browser-153.0esr-16.0-1 at The Tor Project / Applications / Mullvad Browser
Commits:
c999d3d0 by Pier Angelo Vendrame at 2026-08-05T15:31:00+00:00
fixup! MB 21: Disable the password manager
MB 538: Use settings config for our customization.
- - - - -
cf1410d6 by Henry Wilkes at 2026-08-05T15:31:00+00:00
amend! MB 34: Hide unsafe and unwanted preferences UI
MB 34: Hide unwanted setting controls in Mullvad Browser.
- - - - -
4 changed files:
- browser/components/preferences/config/passwords-autofill.mjs
- browser/components/preferences/config/search.mjs
- browser/components/preferences/preferences.js
- browser/components/preferences/search.inc.xhtml
Changes:
=====================================
browser/components/preferences/config/passwords-autofill.mjs
=====================================
@@ -675,6 +675,9 @@ SettingGroupManager.registerGroups({
inProgress: false,
id: "passwordsGroup",
subcategory: "logins",
+ // Hide all password controls. mullvad-browser#21.
+ hidden: true,
+ hiddenFromSearch: true,
l10nId: "forms-passwords-header",
headingLevel: 2,
items: [
=====================================
browser/components/preferences/config/search.mjs
=====================================
@@ -286,6 +286,8 @@ Preferences.addSetting({
Preferences.addSetting({
id: "suggestionsInSearchFieldsCheckbox",
deps: ["searchSuggestionsEnabledPref", "urlbarSuggestionsEnabledPref"],
+ // Hide search suggestions. mullvad-browser#34.
+ visible: () => false,
get(_, deps) {
let searchBarVisible =
!!lazy.CustomizableUI.getPlacementOfWidget("search-container");
=====================================
browser/components/preferences/preferences.js
=====================================
@@ -384,7 +384,7 @@ const CONFIG_PANES = Object.freeze({
groupIds: ["passwords", "payments", "addresses"],
module:
"chrome://browser/content/preferences/config/passwords-autofill.mjs",
- visible: () => srdSectionEnabled("passwordsAutofill"),
+ visible: () => false,
},
privacy: {
l10nId: "pane-privacy-section",
=====================================
browser/components/preferences/search.inc.xhtml
=====================================
@@ -17,7 +17,7 @@
<!-- Search Suggestions -->
- <html:setting-group groupid="searchSuggestions" data-srd-migrated="" data-category="paneSearch" hidden="true" data-hidden-from-search="true" />
+ <html:setting-group groupid="searchSuggestions" data-srd-migrated="" data-category="paneSearch" hidden="true" />
<!-- Firefox Suggest / Address Bar -->
<html:setting-group groupid="firefoxSuggest" data-category="paneSearch" hidden="true" data-subcategory="locationBar" data-srd-migrated=""/>
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/a5…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/a5…
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! TB 27476: Implement about:torconnect captive portal within Tor Browser
by morgan (@morgan) 05 Aug '26
by morgan (@morgan) 05 Aug '26
05 Aug '26
morgan pushed to branch tor-browser-153.0esr-16.0-1 at The Tor Project / Applications / Tor Browser
Commits:
72159ca2 by Pier Angelo Vendrame at 2026-08-05T14:10:10+00:00
fixup! TB 27476: Implement about:torconnect captive portal within Tor Browser
TB 45192: Fix about:torconnect redirects for HSTS-preloaded URLs.
- - - - -
1 changed file:
- toolkit/content/aboutNetError.mjs
Changes:
=====================================
toolkit/content/aboutNetError.mjs
=====================================
@@ -1384,28 +1384,29 @@ async function ensureCertErrorCode() {
i++;
errorCode = await retryCertErrorCode();
}
+}
- if (!errorCode) {
- errorCode = gErrorCode;
+async function maybeRedirectToBootstrap() {
+ if (gErrorCode !== "proxyConnectFailure") {
+ return;
}
- if (errorCode === "proxyConnectFailure") {
- let inIframe;
- try {
- inIframe = window.self !== window.top;
- } catch {
- // Assume a frame if access to top is blocked.
- inIframe = true;
- }
- if (!inIframe && (await RPMSendQuery("ShouldShowTorConnect"))) {
- // pass orginal destination as redirect param
- const encodedRedirect = encodeURIComponent(document.location.href);
- document.location.replace(`about:torconnect?redirect=${encodedRedirect}`);
- }
+ let inIframe;
+ try {
+ inIframe = window.self !== window.top;
+ } catch {
+ // Assume a frame if access to top is blocked.
+ inIframe = true;
+ }
+ if (!inIframe && (await RPMSendQuery("ShouldShowTorConnect"))) {
+ // pass orginal destination as redirect param
+ const encodedRedirect = encodeURIComponent(document.location.href);
+ document.location.replace(`about:torconnect?redirect=${encodedRedirect}`);
}
}
async function main() {
await ensureCertErrorCode();
+ await maybeRedirectToBootstrap();
if (!NetErrorCard.isSupported()) {
// Initialize the error registry for legacy path
initializeRegistry();
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/72159ca…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/72159ca…
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 41930: Remove the UI to customize accept_languages.
by morgan (@morgan) 05 Aug '26
by morgan (@morgan) 05 Aug '26
05 Aug '26
morgan pushed to branch mullvad-browser-153.0esr-16.0-1 at The Tor Project / Applications / Mullvad Browser
Commits:
951624bc by Henry Wilkes at 2026-08-05T13:42:16+00:00
fixup! BB 41930: Remove the UI to customize accept_languages.
BB 45176: Add "Request English" checkbox to Website langauges.
- - - - -
a5278b6c by Henry Wilkes at 2026-08-05T13:42:16+00:00
fixup! Base Browser strings
BB 45176: Modify the "Website langauge" description for Tor Browser.
- - - - -
4 changed files:
- browser/components/preferences/config/languages.mjs
- browser/components/preferences/main.inc.xhtml
- browser/components/preferences/main.js
- toolkit/locales/en-US/toolkit/global/base-browser.ftl
Changes:
=====================================
browser/components/preferences/config/languages.mjs
=====================================
@@ -899,7 +899,9 @@ SettingGroupManager.registerGroups({
},
websiteLanguage: {
inProgress: true,
- l10nId: "website-language-heading",
+ // Modify the description to remove any mention of choosing the order of
+ // languages. tor-browser#45176.
+ l10nId: "website-language-heading-no-preferred-order",
headingLevel: 2,
iconSrc: "chrome://global/skin/icons/defaultFavicon.svg",
items: [
@@ -942,9 +944,14 @@ SettingGroupManager.registerGroups({
},
],
},
+ {
+ id: "websiteSpoofEnglish",
+ l10nId: "languages-customize-spoof-english",
+ },
],
},
- websiteSpoofEnglish: {
+ // TODO: Remove once we switch to the new settings redesign. tor-browser#45177
+ websiteSpoofEnglishControl: {
items: [
{
id: "websiteSpoofEnglish",
=====================================
browser/components/preferences/main.inc.xhtml
=====================================
@@ -105,7 +105,7 @@
</hbox>
<!-- TODO: Integrate into the "Languages" setting-group after bugzilla bug
- 1972081. -->
- <html:setting-group groupid="websiteSpoofEnglish"></html:setting-group>
+ <html:setting-group groupid="websiteSpoofEnglishControl"></html:setting-group>
<checkbox id="useSystemLocale" hidden="true"
data-l10n-id="use-system-locale"
=====================================
browser/components/preferences/main.js
=====================================
@@ -820,7 +820,7 @@ var gMainPane = {
initSettingGroup("fonts");
initSettingGroup("browserLanguage");
initSettingGroup("websiteLanguage");
- initSettingGroup("websiteSpoofEnglish");
+ initSettingGroup("websiteSpoofEnglishControl");
initSettingGroup("browsing");
initSettingGroup("keyboardAndScrolling");
initSettingGroup("motionAndLink");
=====================================
toolkit/locales/en-US/toolkit/global/base-browser.ftl
=====================================
@@ -122,6 +122,14 @@ preferences-contrast-control-fixed-color2 =
.accesskey = F
.description = This will be detectable by websites and will make you appear more unique to web trackers.
+## Preferences - Language.
+
+# "{ -brand-short-name }" will be replaced with the localized name of the browser, e.g. "Tor Browser".
+# This is modified from the "website-language-heading" string from Firefox. See Firefox's translation on Pontoon using the "LOCALES" tab: https://pontoon.mozilla.org/ar/firefox/browser/browser/preferences/preferen… .
+website-language-heading-no-preferred-order =
+ .label = Website language
+ .description = Some web pages are displayed in multiple languages. Choose how { -brand-short-name } should request them.
+
## Security level toolbar button.
## Uses sentence case in English (US).
## ".label" is the accessible name, and shown in the overflow menu and when customizing the toolbar.
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/d0…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/d0…
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 41930: Remove the UI to customize accept_languages.
by morgan (@morgan) 05 Aug '26
by morgan (@morgan) 05 Aug '26
05 Aug '26
morgan pushed to branch tor-browser-153.0esr-16.0-1 at The Tor Project / Applications / Tor Browser
Commits:
472b5456 by Henry Wilkes at 2026-08-05T13:26:44+00:00
fixup! BB 41930: Remove the UI to customize accept_languages.
BB 45176: Add "Request English" checkbox to Website langauges.
- - - - -
c81304d6 by Henry Wilkes at 2026-08-05T13:26:44+00:00
fixup! Base Browser strings
BB 45176: Modify the "Website langauge" description for Tor Browser.
- - - - -
4 changed files:
- browser/components/preferences/config/languages.mjs
- browser/components/preferences/main.inc.xhtml
- browser/components/preferences/main.js
- toolkit/locales/en-US/toolkit/global/base-browser.ftl
Changes:
=====================================
browser/components/preferences/config/languages.mjs
=====================================
@@ -899,7 +899,9 @@ SettingGroupManager.registerGroups({
},
websiteLanguage: {
inProgress: true,
- l10nId: "website-language-heading",
+ // Modify the description to remove any mention of choosing the order of
+ // languages. tor-browser#45176.
+ l10nId: "website-language-heading-no-preferred-order",
headingLevel: 2,
iconSrc: "chrome://global/skin/icons/defaultFavicon.svg",
items: [
@@ -942,9 +944,14 @@ SettingGroupManager.registerGroups({
},
],
},
+ {
+ id: "websiteSpoofEnglish",
+ l10nId: "languages-customize-spoof-english",
+ },
],
},
- websiteSpoofEnglish: {
+ // TODO: Remove once we switch to the new settings redesign. tor-browser#45177
+ websiteSpoofEnglishControl: {
items: [
{
id: "websiteSpoofEnglish",
=====================================
browser/components/preferences/main.inc.xhtml
=====================================
@@ -105,7 +105,7 @@
</hbox>
<!-- TODO: Integrate into the "Languages" setting-group after bugzilla bug
- 1972081. -->
- <html:setting-group groupid="websiteSpoofEnglish"></html:setting-group>
+ <html:setting-group groupid="websiteSpoofEnglishControl"></html:setting-group>
<checkbox id="useSystemLocale" hidden="true"
data-l10n-id="use-system-locale"
=====================================
browser/components/preferences/main.js
=====================================
@@ -827,7 +827,7 @@ var gMainPane = {
initSettingGroup("fonts");
initSettingGroup("browserLanguage");
initSettingGroup("websiteLanguage");
- initSettingGroup("websiteSpoofEnglish");
+ initSettingGroup("websiteSpoofEnglishControl");
initSettingGroup("browsing");
initSettingGroup("keyboardAndScrolling");
initSettingGroup("motionAndLink");
=====================================
toolkit/locales/en-US/toolkit/global/base-browser.ftl
=====================================
@@ -122,6 +122,14 @@ preferences-contrast-control-fixed-color2 =
.accesskey = F
.description = This will be detectable by websites and will make you appear more unique to web trackers.
+## Preferences - Language.
+
+# "{ -brand-short-name }" will be replaced with the localized name of the browser, e.g. "Tor Browser".
+# This is modified from the "website-language-heading" string from Firefox. See Firefox's translation on Pontoon using the "LOCALES" tab: https://pontoon.mozilla.org/ar/firefox/browser/browser/preferences/preferen… .
+website-language-heading-no-preferred-order =
+ .label = Website language
+ .description = Some web pages are displayed in multiple languages. Choose how { -brand-short-name } should request them.
+
## Security level toolbar button.
## Uses sentence case in English (US).
## ".label" is the accessible name, and shown in the overflow menu and when customizing the toolbar.
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/7d1427…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/7d1427…
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) 04 Aug '26
by Dan Ballard (@dan) 04 Aug '26
04 Aug '26
Dan Ballard pushed to branch tor-browser-153.0esr-16.0-1 at The Tor Project / Applications / Tor Browser
Commits:
7d1427b6 by clairehurst at 2026-08-04T10:55:01-07:00
fixup! [android] Disable features and functionality
Bug 45173: Disable search optimizations for android
- - - - -
1 changed file:
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
Changes:
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
=====================================
@@ -2121,30 +2121,11 @@ class Settings(
default = true,
)
- var isSearchOptimizationEnabled by booleanPreference(
- key = appContext.getPreferenceKey(R.string.pref_key_search_optimization_feature),
- default = { FxNimbus.features.searchOptimizationOption.value().enabled },
- )
-
- var shouldShowSearchOptimizationCards by booleanPreference(
- key = appContext.getPreferenceKey(R.string.pref_key_search_optimization_cards),
- default = { isSearchOptimizationEnabled },
- )
-
- var shouldShowSearchOptimizationStockCard by booleanPreference(
- key = appContext.getPreferenceKey(R.string.pref_key_search_optimization_stocks),
- default = { FxNimbus.features.searchOptimizationOption.value().showStocksCard },
- )
-
- var shouldShowSearchOptimizationFlightCard by booleanPreference(
- key = appContext.getPreferenceKey(R.string.pref_key_search_optimization_flights),
- default = { FxNimbus.features.searchOptimizationOption.value().showFlightsCard },
- )
-
- var shouldShowSearchOptimizationSportCard by booleanPreference(
- key = appContext.getPreferenceKey(R.string.pref_key_search_optimization_sports),
- default = { FxNimbus.features.searchOptimizationOption.value().showSportsCard },
- )
+ var isSearchOptimizationEnabled = false
+ var shouldShowSearchOptimizationCards = false
+ var shouldShowSearchOptimizationStockCard = false
+ var shouldShowSearchOptimizationFlightCard = false
+ var shouldShowSearchOptimizationSportCard = false
val isTabStripEnabled = false
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/7d1427b…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/7d1427b…
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] Modify UI/UX
by Dan Ballard (@dan) 04 Aug '26
by Dan Ballard (@dan) 04 Aug '26
04 Aug '26
Dan Ballard pushed to branch tor-browser-153.0esr-16.0-1 at The Tor Project / Applications / Tor Browser
Commits:
e79d443c by clairehurst at 2026-08-04T10:00:36-07:00
fixup! [android] Modify UI/UX
Bug 45159: Fix security pane icon colors
- - - - -
1 changed file:
- mobile/android/fenix/app/src/main/res/values/colors.xml
Changes:
=====================================
mobile/android/fenix/app/src/main/res/values/colors.xml
=====================================
@@ -261,7 +261,7 @@
<color name="spinner_selected_item">#1415141A</color>
<!-- Icon colors -->
- <color name="mozac_ui_icons_fill" tools:ignore="UnusedResources">@color/fx_mobile_on_surface</color>
+ <color name="mozac_ui_icons_fill" tools:ignore="UnusedResources">@color/novaWhite</color>
<!-- Add-ons colors -->
<color name="mozac_feature_addons_messagebar_error_background_color" tools:ignore="UnusedResources">@color/fx_mobile_layer_color_critical</color>
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/e79d443…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/e79d443…
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 43864: Modify the urlbar for Base Browser.
by morgan (@morgan) 04 Aug '26
by morgan (@morgan) 04 Aug '26
04 Aug '26
morgan pushed to branch mullvad-browser-153.0esr-16.0-1 at The Tor Project / Applications / Mullvad Browser
Commits:
d0401aad by Henry Wilkes at 2026-08-04T13:57:14+00:00
fixup! BB 43864: Modify the urlbar for Base Browser.
BB 45168: Hide the history search option.
- - - - -
1 changed file:
- browser/components/urlbar/content/SearchModeSwitcher.mjs
Changes:
=====================================
browser/components/urlbar/content/SearchModeSwitcher.mjs
=====================================
@@ -456,10 +456,20 @@ export class SearchModeSwitcher {
// search modes. Hence when the settings redesign is enabled we show
// all local search modes regardless of the prefs.
this.#engines = searchEngines.concat(
- lazy.UrlbarUtils.LOCAL_SEARCH_MODES.filter(
- engine =>
+ lazy.UrlbarUtils.LOCAL_SEARCH_MODES.filter(engine => {
+ // Do not show the search history option in PBM. tor-browser#43864.
+ // Although, it can still be triggered with "^" restrict keyword or
+ // through an app menu item. See also mozilla bug 1980928.
+ if (
+ engine.source === lazy.UrlbarUtils.RESULT_SOURCE.HISTORY &&
+ lazy.PrivateBrowsingUtils.permanentPrivateBrowsing
+ ) {
+ return false;
+ }
+ return (
lazy.settingsRedesignEnabled || lazy.UrlbarPrefs.get(engine.pref)
- )
+ );
+ })
);
}
}
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/d04…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/d04…
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! BB 43864: Modify the urlbar for Base Browser.
by morgan (@morgan) 04 Aug '26
by morgan (@morgan) 04 Aug '26
04 Aug '26
morgan pushed to branch tor-browser-153.0esr-16.0-1 at The Tor Project / Applications / Tor Browser
Commits:
a502c2d2 by Henry Wilkes at 2026-08-04T13:48:31+00:00
fixup! BB 43864: Modify the urlbar for Base Browser.
BB 45168: Hide the history search option.
- - - - -
1 changed file:
- browser/components/urlbar/content/SearchModeSwitcher.mjs
Changes:
=====================================
browser/components/urlbar/content/SearchModeSwitcher.mjs
=====================================
@@ -456,10 +456,20 @@ export class SearchModeSwitcher {
// search modes. Hence when the settings redesign is enabled we show
// all local search modes regardless of the prefs.
this.#engines = searchEngines.concat(
- lazy.UrlbarUtils.LOCAL_SEARCH_MODES.filter(
- engine =>
+ lazy.UrlbarUtils.LOCAL_SEARCH_MODES.filter(engine => {
+ // Do not show the search history option in PBM. tor-browser#43864.
+ // Although, it can still be triggered with "^" restrict keyword or
+ // through an app menu item. See also mozilla bug 1980928.
+ if (
+ engine.source === lazy.UrlbarUtils.RESULT_SOURCE.HISTORY &&
+ lazy.PrivateBrowsingUtils.permanentPrivateBrowsing
+ ) {
+ return false;
+ }
+ return (
lazy.settingsRedesignEnabled || lazy.UrlbarPrefs.get(engine.pref)
- )
+ );
+ })
);
}
}
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/a502c2d…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/a502c2d…
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] 6 commits: fixup! TB 31286: Implementation of bridge, proxy, and firewall settings in...
by morgan (@morgan) 04 Aug '26
by morgan (@morgan) 04 Aug '26
04 Aug '26
morgan pushed to branch tor-browser-153.0esr-16.0-1 at The Tor Project / Applications / Tor Browser
Commits:
0b37b3e9 by Henry Wilkes at 2026-08-04T13:23:04+00:00
fixup! TB 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
TB 45058: Copy code from content/connectionPane.js to
widgets/tor-bridges-display.mjs.
Copy markup within connectionPane.inc.xhtml.
- - - - -
550bff23 by Henry Wilkes at 2026-08-04T13:23:04+00:00
fixup! TB 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
TB 45058: Convert bridge settings to use config.
- - - - -
5088e918 by Henry Wilkes at 2026-08-04T13:23:04+00:00
fixup! TB 40597: Implement TorSettings module
TB 45058: Clean up TorSettings double init logic.
- - - - -
1ab514d3 by Henry Wilkes at 2026-08-04T13:23:04+00:00
fixup! Tor Browser strings
TB 45058: Combine bridge setting strings together.
- - - - -
d064e421 by Henry Wilkes at 2026-08-04T13:23:04+00:00
fixup! Tor Browser localization migration scripts.
TB 45058: Combine bridge setting strings together.
- - - - -
2a72140f by Henry Wilkes at 2026-08-04T13:23:04+00:00
TB 45143: Modify moz-fieldset.
- - - - -
13 changed files:
- browser/components/preferences/preferences.js
- browser/components/preferences/preferences.xhtml
- browser/components/torpreferences/config/connection.mjs
- + browser/components/torpreferences/config/helpers.mjs
- browser/components/torpreferences/content/connectionPane.inc.xhtml
- browser/components/torpreferences/content/torPreferences.css
- browser/components/torpreferences/jar.mn
- + browser/components/torpreferences/widgets/tor-bridges-display.mjs
- toolkit/content/widgets/moz-fieldset/moz-fieldset.css
- toolkit/content/widgets/moz-fieldset/moz-fieldset.mjs
- toolkit/locales/en-US/toolkit/global/tor-browser.ftl
- toolkit/modules/TorSettings.sys.mjs
- + tools/torbrowser/l10n/migrations/bug-45058-bridge-settings.py
Changes:
=====================================
browser/components/preferences/preferences.js
=====================================
@@ -271,7 +271,7 @@ const CONFIG_PANES = Object.freeze({
connection: {
l10nId: "tor-connection-settings-pane",
iconSrc: "chrome://browser/content/torconnect/tor-connect.svg",
- groupIds: ["connectionStatus"],
+ groupIds: ["connectionStatus", "torBridges"],
module: "chrome://browser/content/torpreferences/config/connection.mjs",
visible: () => {
return TorConnect.enabled;
=====================================
browser/components/preferences/preferences.xhtml
=====================================
@@ -108,6 +108,7 @@
<script type="module" src="chrome://browser/content/preferences/widgets/update-information.mjs"></script>
<script type="module" src="chrome://browser/content/preferences/widgets/update-state.mjs"></script>
<script type="module" src="chrome://browser/content/ipprotection/bandwidth-usage.mjs"></script>
+ <script type="module" src="chrome://browser/content/torpreferences/widgets/tor-bridges-display.mjs"></script>
<script type="module" src="chrome://browser/content/torpreferences/widgets/tor-connection-status.mjs"></script>
<script src="chrome://browser/content/torpreferences/bridgemoji/BridgeEmoji.js"/>
</head>
=====================================
browser/components/torpreferences/config/connection.mjs
=====================================
@@ -4,11 +4,33 @@ import { Preferences } from "chrome://global/content/preferences/Preferences.mjs
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
InternetStatus: "moz-src:///toolkit/modules/TorConnect.sys.mjs",
+ openBridgeDialog:
+ "chrome://browser/content/torpreferences/config/helpers.mjs",
+ openUserProvideBridgeDialog:
+ "chrome://browser/content/torpreferences/config/helpers.mjs",
+ TorBridgeSource: "moz-src:///toolkit/modules/TorSettings.sys.mjs",
TorConnect: "moz-src:///toolkit/modules/TorConnect.sys.mjs",
TorConnectStage: "moz-src:///toolkit/modules/TorConnect.sys.mjs",
TorConnectTopics: "moz-src:///toolkit/modules/TorConnect.sys.mjs",
+ TorProviderBuilder:
+ "moz-src:///toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs",
+ TorProviderState:
+ "moz-src:///toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs",
+ TorProviderTopics:
+ "moz-src:///toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs",
+ TorSettings: "moz-src:///toolkit/modules/TorSettings.sys.mjs",
+ TorSettingsTopics: "moz-src:///toolkit/modules/TorSettings.sys.mjs",
});
+// TODO: Change to GetLoxBridges if Lox enabled, and the account is set up.
+const TELEGRAM_USER_NAME = "GetBridgesBot";
+const TELEGRAM_HREF = `https://t.me/${TELEGRAM_USER_NAME}`;
+
+const TOR_BRIDGES_URL_NAME = "bridges.torproject.org";
+const TOR_BRIDGES_HREF = "https://bridges.torproject.org";
+
+const TOR_BRIDGES_EMAIL = "bridges(a)torproject.org";
+
SettingGroupManager.registerGroups({
connectionStatus: {
inProgress: true,
@@ -39,6 +61,123 @@ SettingGroupManager.registerGroups({
},
],
},
+ torBridges: {
+ inProgress: true,
+ l10nId: "tor-bridges-group",
+ supportPage: "tor-manual:bridges",
+ headingLevel: 2,
+ controlAttrs: { "focusable-heading": true },
+ items: [
+ {
+ id: "bridgesEnabled",
+ l10nId: "tor-bridges-use-bridges",
+ control: "moz-toggle",
+ },
+ {
+ id: "torBridgesDisplay",
+ control: "tor-bridges-display",
+ },
+ {
+ id: "newBridgesGroup",
+ control: "moz-fieldset",
+ controlAttrs: {
+ headinglevel: 3,
+ },
+ options: [
+ {
+ control: "moz-box-group",
+ items: [
+ {
+ id: "builtinBridges",
+ l10nId: "tor-bridges-choose-built-in-button",
+ control: "moz-box-button",
+ },
+ {
+ id: "userProvidedBridges",
+ l10nId: "tor-bridges-enter-bridges-button",
+ control: "moz-box-button",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ id: "findMoreBridgesGroup",
+ l10nId: "tor-bridges-find-more-group",
+ control: "moz-fieldset",
+ controlAttrs: {
+ headinglevel: 3,
+ },
+ options: [
+ {
+ control: "moz-box-item",
+ options: [
+ {
+ id: "torBridgesRequestBanner",
+ control: "article",
+ options: [
+ {
+ control: "img",
+ controlAttrs: {
+ alt: "",
+ src: "chrome://browser/content/torpreferences/bridge-bot.svg",
+ },
+ },
+ {
+ control: "p",
+ l10nId: "tor-bridges-request-from-browser2",
+ },
+ {
+ // NOTE: We use the wrapping `div` to simply switch from the
+ // `options` context to the `items` context, with the latter
+ // wrapping the elements in a setting-control.
+ control: "div",
+ items: [
+ {
+ id: "requestBridges",
+ l10nId: "tor-bridges-request-button2",
+ control: "moz-button",
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ control: "moz-box-group",
+ options: [
+ {
+ l10nId: "tor-bridges-source-telegram-link",
+ l10nArgs: { telegramUserName: TELEGRAM_USER_NAME },
+ control: "moz-box-link",
+ iconSrc:
+ "chrome://browser/content/torpreferences/telegram-logo.svg",
+ controlAttrs: {
+ href: TELEGRAM_HREF,
+ },
+ },
+ {
+ l10nId: "tor-bridges-source-web-link",
+ l10nArgs: { url: TOR_BRIDGES_URL_NAME },
+ control: "moz-box-link",
+ iconSrc: "chrome://browser/content/torconnect/network.svg",
+ controlAttrs: {
+ href: TOR_BRIDGES_HREF,
+ },
+ },
+ {
+ l10nId: "tor-bridges-source-email-link",
+ l10nArgs: { address: TOR_BRIDGES_EMAIL },
+ control: "moz-box-item",
+ iconSrc: "chrome://browser/content/torpreferences/mail.svg",
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
});
Preferences.addSetting({
@@ -113,3 +252,277 @@ Preferences.addSetting({
lazy.TorConnect.quickstart = val;
},
});
+
+Preferences.addSetting({
+ id: "torSettingsReady",
+ _ready: false,
+ setup(emitChange) {
+ if (!lazy.TorSettings.enabled) {
+ // Remain in `false`.
+ return;
+ }
+ // Most likely, TorSettings will already be initialised.
+ if (lazy.TorSettings.initialized) {
+ this._ready = true;
+ return;
+ }
+ // Else, wait for it to be initialised.
+ lazy.TorSettings.initializedPromise.then(
+ () => {
+ this._ready = true;
+ emitChange();
+ },
+ error => {
+ // No change in state.
+ console.error("TorSettings failed to initialize.", error);
+ }
+ );
+ },
+ get() {
+ return this._ready;
+ },
+});
+
+Preferences.addSetting({
+ id: "torBridgesRaw",
+ deps: ["torSettingsReady"],
+ _value: null,
+ setup(emitChange) {
+ const observer = subject => {
+ const { changes } = subject.wrappedJSObject;
+ // NOTE: We do not include "bridges.lox_id" in the changes. Instead, any
+ // widgets should wait for 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.
+ if (
+ changes.includes("bridges.source") ||
+ changes.includes("bridges.bridge_strings") ||
+ changes.includes("bridges.builtin_type")
+ ) {
+ // Reset.
+ this._value = null;
+ emitChange();
+ }
+ };
+ Services.obs.addObserver(observer, lazy.TorSettingsTopics.SettingsChanged);
+ return () => {
+ Services.obs.removeObserver(
+ observer,
+ lazy.TorSettingsTopics.SettingsChanged
+ );
+ };
+ },
+ get(_pref, { torSettingsReady }) {
+ if (this._value === null) {
+ if (!torSettingsReady.value) {
+ // TorSettings getter will throw.
+ return null;
+ }
+ const source = lazy.TorSettings.bridges.source;
+ // Cache a value.
+ this._value = {
+ haveBridges: source !== lazy.TorBridgeSource.Invalid,
+ source,
+ builtinType: lazy.TorSettings.bridges.builtin_type,
+ bridgeStrings: lazy.TorSettings.bridges.bridge_strings,
+ };
+ }
+ return this._value;
+ },
+});
+
+Preferences.addSetting({
+ id: "connectedBridgeId",
+ _value: null,
+ setup(emitChange) {
+ const observer = async () => {
+ // NOTE: It should be safe for this method to be called concurrently.
+ let bridge = null;
+ try {
+ if (
+ lazy.TorProviderBuilder.currentState() ===
+ lazy.TorProviderState.Running
+ ) {
+ bridge = (await lazy.TorProviderBuilder.build()).currentBridge;
+ }
+ // Else, bridge is `null` whilst the provider is not running.
+ } catch (e) {
+ console.warn("Could not get current bridge", e);
+ }
+ const prevVal = this._value;
+ this._value = bridge?.fingerprint ?? null;
+ if (prevVal !== this._value) {
+ emitChange();
+ }
+ };
+ Services.obs.addObserver(observer, lazy.TorProviderTopics.BridgeChanged);
+ // NOTE: BridgeChanged is only fired directly by the provider instances,
+ // rather than by TorProviderBuilder. In particular, it will not fire when
+ // the previous provider had a bridge and the new one does not, because
+ // neither provider saw a change in their own bridge. But from the user's
+ // point of view, the overall change would mean that the current bridge has
+ // changed.
+ // Moreover, we want to show no connected bridge whilst we are missing a
+ // provider. Therefore, we also need to listen for a change in provider and
+ // its state.
+ // TODO: Maybe this logic should be moved to TorProviderBuilder itself if it
+ // is ever needed by other parts of the UI.
+ Services.obs.addObserver(
+ observer,
+ lazy.TorProviderTopics.ProviderStateChanged
+ );
+ // Get the initial value.
+ observer();
+
+ return () => {
+ Services.obs.removeObserver(
+ observer,
+ lazy.TorProviderTopics.ProviderStateChanged
+ );
+ Services.obs.removeObserver(
+ observer,
+ lazy.TorProviderTopics.BridgeChanged
+ );
+ };
+ },
+ get() {
+ return this._value;
+ },
+});
+
+Preferences.addSetting({
+ id: "bridgesEnabled",
+ deps: ["torSettingsReady", "torBridgesRaw"],
+ setup(emitChange) {
+ const observer = subject => {
+ const { changes } = subject.wrappedJSObject;
+ if (changes.includes("bridges.enabled")) {
+ emitChange();
+ }
+ };
+ Services.obs.addObserver(observer, lazy.TorSettingsTopics.SettingsChanged);
+ return () => {
+ Services.obs.removeObserver(
+ observer,
+ lazy.TorSettingsTopics.SettingsChanged
+ );
+ };
+ },
+ get(_prefVal, { torSettingsReady }) {
+ if (!torSettingsReady.value) {
+ // TorSettings.bridges will throw before TorSettings has finished
+ // initialisation.
+ return false;
+ }
+ return lazy.TorSettings.bridges.enabled;
+ },
+ set(val) {
+ lazy.TorSettings.changeSettings({
+ bridges: { enabled: val },
+ });
+ },
+ visible({ torSettingsReady }) {
+ return torSettingsReady.value;
+ },
+ disabled({ torBridgesRaw }) {
+ return !torBridgesRaw.value?.haveBridges;
+ },
+});
+
+Preferences.addSetting({
+ id: "torBridgesDisplay",
+ deps: ["torSettingsReady", "torBridgesRaw", "connectedBridgeId"],
+ getControlConfig(config, { torBridgesRaw, connectedBridgeId }) {
+ config.controlAttrs = {
+ ...config.controlAttrs,
+ // Set the `bridges` and `connnectedBridgeId` object *properties* (rather
+ // than attributes) by using the `.` prefix.
+ ".bridges": torBridgesRaw.value,
+ ".connectedBridgeId": connectedBridgeId.value,
+ };
+ return config;
+ },
+ visible({ torSettingsReady }) {
+ return torSettingsReady.value;
+ },
+});
+
+Preferences.addSetting({
+ id: "newBridgesGroup",
+ deps: ["torSettingsReady", "torBridgesRaw"],
+ getControlConfig(config, { torBridgesRaw }) {
+ config.l10nId = torBridgesRaw.value?.haveBridges
+ ? "tor-bridges-replace-bridges-group"
+ : "tor-bridges-add-bridges-group";
+ return config;
+ },
+ visible({ torSettingsReady }) {
+ return torSettingsReady.value;
+ },
+});
+
+Preferences.addSetting({
+ id: "builtinBridges",
+ onUserClick() {
+ lazy.openBridgeDialog(
+ window,
+ "chrome://browser/content/torpreferences/builtinBridgeDialog.xhtml",
+ null,
+ result => {
+ if (!result.type) {
+ return null;
+ }
+ return lazy.TorSettings.changeSettings({
+ bridges: {
+ enabled: true,
+ source: lazy.TorBridgeSource.BuiltIn,
+ builtin_type: result.type,
+ },
+ });
+ }
+ );
+ },
+});
+
+Preferences.addSetting({
+ id: "userProvidedBridges",
+ deps: ["torBridgesRaw"],
+ onUserClick(_event, { torBridgesRaw }) {
+ lazy.openUserProvideBridgeDialog(
+ window,
+ torBridgesRaw.value?.haveBridges ? "replace" : "add"
+ );
+ },
+});
+
+Preferences.addSetting({
+ id: "findMoreBridgesGroup",
+ deps: ["torSettingsReady"],
+ visible({ torSettingsReady }) {
+ return torSettingsReady.value;
+ },
+});
+
+Preferences.addSetting({
+ id: "requestBridges",
+ onUserClick() {
+ lazy.openBridgeDialog(
+ window,
+ "chrome://browser/content/torpreferences/requestBridgeDialog.xhtml",
+ null,
+ result => {
+ if (!result.bridges?.length) {
+ return null;
+ }
+ return lazy.TorSettings.changeSettings({
+ bridges: {
+ enabled: true,
+ source: lazy.TorBridgeSource.BridgeDB,
+ bridge_strings: result.bridges,
+ },
+ });
+ }
+ );
+ },
+});
=====================================
browser/components/torpreferences/config/helpers.mjs
=====================================
@@ -0,0 +1,125 @@
+const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+ TorBridgeSource: "moz-src:///toolkit/modules/TorSettings.sys.mjs",
+ TorConnectParent:
+ "moz-src:///browser/components/torconnect/TorConnectParent.sys.mjs",
+ TorSettings: "moz-src:///toolkit/modules/TorSettings.sys.mjs",
+});
+
+/**
+ * Force the focus to move to the bridge heading.
+ *
+ * @param {Window} win - The preferences window.
+ * @param {boolean} [forceTopHeading=false] - Force the focus to move to the
+ * top "Bridges" setting heading.
+ */
+export function moveFocusToBridgeHeading(win, forceTopHeading = false) {
+ // 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.
+ // 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 (
+ forceTopHeading ||
+ !win.document.getElementById("torBridgesDisplay").focusHeading()
+ ) {
+ win.document
+ .querySelector('setting-group[groupid="torBridges"] moz-fieldset')
+ .focusHeading();
+ }
+}
+
+/**
+ * Open a bridge dialog that will change the users bridges.
+ *
+ * @param {Window} win - The preferences window.
+ * @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.
+ */
+export function openBridgeDialog(win, url, inputData, onAccept) {
+ const result = { accepted: false, connect: false };
+ let savedSettings = null;
+ win.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.
+ lazy.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(() => {
+ moveFocusToBridgeHeading(win);
+ });
+ },
+ },
+ result,
+ inputData
+ );
+}
+
+/**
+ * Open the user provide dialog.
+ *
+ * @param {Window} win - The preferences window.
+ * @param {string} mode - The mode to open the dialog in: "add", "replace" or
+ * "edit".
+ */
+export function openUserProvideBridgeDialog(win, mode) {
+ openBridgeDialog(
+ win,
+ "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 = lazy.TorBridgeSource.Lox;
+ bridges.lox_id = loxId;
+ } else {
+ bridges.source = lazy.TorBridgeSource.UserProvided;
+ bridges.bridge_strings = result.addresses;
+ }
+ return lazy.TorSettings.changeSettings({ bridges });
+ }
+ );
+}
=====================================
browser/components/torpreferences/content/connectionPane.inc.xhtml
=====================================
@@ -1,3 +1,251 @@
+<html:template id="tor-bridges-display-template">
+ <html:div id="tor-bridges-none" hidden="hidden">
+ <html:img id="tor-bridges-none-icon" alt="" />
+ <html:p data-l10n-id="tor-bridges-none-added"></html:p>
+ </html:div>
+ <html:fieldset id="tor-bridges-current" hidden="hidden">
+ <html:legend>
+ <html:h4
+ id="tor-bridges-current-heading-non-search"
+ class="tor-bridges-current-heading tor-focusable-heading tor-small-heading"
+ tabindex="-1"
+ data-l10n-id="tor-bridges-your-bridges"
+ ></html:h4>
+ </html:legend>
+ <html:p
+ id="tor-bridges-user-label"
+ class="tor-bridges-source-label"
+ data-l10n-id="tor-bridges-source-user"
+ ></html:p>
+ <html:p
+ id="tor-bridges-built-in-label"
+ class="tor-bridges-source-label"
+ data-l10n-id="tor-bridges-source-built-in"
+ ></html:p>
+ <html:p
+ id="tor-bridges-requested-label"
+ class="tor-bridges-source-label"
+ data-l10n-id="tor-bridges-source-requested"
+ ></html:p>
+ <html:p 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:p>
+ <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 id="tor-bridges-built-in-display" hidden="hidden">
+ <html:p id="tor-bridges-built-in-type-name"></html:p>
+ <html:p 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:p>
+ <html:p id="tor-bridges-built-in-description"></html:p>
+ </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:h5
+ class="tor-bridges-share-heading tor-small-heading"
+ data-l10n-id="tor-bridges-share-heading"
+ ></html:h5>
+ <html:p
+ id="tor-bridges-share-description"
+ data-l10n-id="tor-bridges-share-description"
+ ></html:p>
+ <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:h5
+ class="tor-bridges-lox-next-unlock-counter tor-small-heading tor-bridges-lox-intro tor-focusable-heading"
+ tabindex="-1"
+ ></html:h5>
+ <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:fieldset>
+</html:template>
+
<!-- Tor panel -->
<script
=====================================
browser/components/torpreferences/content/torPreferences.css
=====================================
@@ -201,20 +201,27 @@ button.spoof-button-disabled {
#tor-bridges-none,
#tor-bridges-current {
- margin-inline: 0;
- margin-block: var(--space-xxlarge);
+ @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;
text-align: center;
- padding-block: 64px;
+ padding-block: 54px;
padding-inline: var(--space-xxlarge);
- gap: var(--space-large);
+ gap: var(--space-small);
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 {
@@ -228,9 +235,13 @@ button.spoof-button-disabled {
.tor-bridges-box,
.tor-bridges-details-box {
padding: var(--space-large);
- border-radius: var(--border-radius-small);
- background: var(--background-color-box-info);
+ 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 {
@@ -250,6 +261,56 @@ button.spoof-button-disabled {
white-space: nowrap;
}
+tor-bridges-display {
+ display: block;
+ margin-block: var(--space-small);
+
+ &.has-tor-bridges {
+ display: grid;
+ grid-template:
+ "heading source button" min-content
+ "bridges bridges bridges" auto
+ "extra extra extra" auto
+ / max-content 1fr max-content;
+ align-items: center;
+ border: var(--card-border);
+ border-radius: var(--border-radius-medium);
+ background-color: var(--card-background-color);
+ padding: var(--space-large);
+ }
+
+ & p {
+ margin: 0;
+ }
+
+ & #tor-bridges-current:not([hidden]) {
+ /* Avoid the special display logic for <fieldset> and <legend>. */
+ display: contents;
+ }
+
+ & legend {
+ grid-area: heading;
+ white-space: nowrap;
+ }
+
+ & .tor-bridges-source-label {
+ white-space: nowrap;
+ padding-inline-end: var(--space-small);
+ border-inline-end: var(--border-width) solid var(--border-color);
+ }
+
+ & :is(#tor-bridges-built-in-display, #tor-bridges-grid-display) {
+ grid-area: bridges;
+ margin-block-start: var(--space-large);
+ border-block-start: var(--border-width) solid var(--border-color);
+ padding-block-start: var(--space-large);
+ }
+
+ & :is(#tor-bridges-share, #tor-bridges-lox-status) {
+ grid-area: extra;
+ }
+}
+
.tor-bridges-current-heading {
grid-area: heading;
}
@@ -681,6 +742,24 @@ button.spoof-button-disabled {
grid-column: 2 / 3;
}
+#torBridgesRequestBanner {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ text-wrap-style: balance;
+ gap: var(--space-large);
+
+ & > * {
+ margin: 0;
+ flex: 0 0 auto;
+ }
+
+ & > p {
+ max-width: var(--size-layout-medium);
+ }
+}
+
#tor-bridges-request-box {
/* Take up the full height in the container. */
align-self: stretch;
=====================================
browser/components/torpreferences/jar.mn
=====================================
@@ -25,6 +25,8 @@ browser.jar:
content/browser/torpreferences/torLogDialog.js (content/torLogDialog.js)
content/browser/torpreferences/torLogDialog.xhtml (content/torLogDialog.xhtml)
content/browser/torpreferences/config/connection.mjs (config/connection.mjs)
+ content/browser/torpreferences/config/helpers.mjs (config/helpers.mjs)
+ content/browser/torpreferences/widgets/tor-bridges-display.mjs (widgets/tor-bridges-display.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)
=====================================
browser/components/torpreferences/widgets/tor-bridges-display.mjs
=====================================
@@ -0,0 +1,1955 @@
+const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+ Lox: "moz-src:///toolkit/components/lox/Lox.sys.mjs",
+ LoxTopics: "moz-src:///toolkit/components/lox/Lox.sys.mjs",
+ moveFocusToBridgeHeading:
+ "chrome://browser/content/torpreferences/config/helpers.mjs",
+ openUserProvideBridgeDialog:
+ "chrome://browser/content/torpreferences/config/helpers.mjs",
+ TorBridgeSource: "moz-src:///toolkit/modules/TorSettings.sys.mjs",
+ TorParsers: "moz-src:///toolkit/components/tor-launcher/TorParsers.sys.mjs",
+ TorSettings: "moz-src:///toolkit/modules/TorSettings.sys.mjs",
+});
+
+/**
+ * Show the bridge QR to the user.
+ *
+ * @param {string} bridgeString - The string to use in the QR.
+ */
+function showBridgeQr(bridgeString) {
+ window.gSubDialog.open(
+ "chrome://browser/content/torpreferences/bridgeQrDialog.xhtml",
+ { features: "resizable=yes" },
+ bridgeString
+ );
+}
+
+/**
+ * Post a new notification, replacing any existing one.
+ *
+ * @param {string} type - The notification type.
+ */
+async function postBridgeNotification(type) {
+ 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;
+ }
+ const bridgeDisplay = document.querySelector("tor-bridges-display");
+ const settingGroup = bridgeDisplay?.closest("setting-group");
+ if (!settingGroup) {
+ console.error("Missing a setting-group for a notification.");
+ return;
+ }
+ if (!settingGroup.checkVisibility()) {
+ // Only ping the user if the bridge settings are visible.
+ // NOTE: Most operations to change the bridges will occur within the
+ // connection settings. However, in principle the user could have multiple
+ // setting tabs open, or they may have the settings open whilst Connection
+ // Assist is setting their bridges.
+ return;
+ }
+ const [message] = await Promise.all([
+ document.l10n.formatValue(updateId),
+ // Wait at least a small amount of time to actually trigger ariaNotify.
+ // Otherwise Orca will ignore the notification when it almost coincides with
+ // a change in focus, which is normally the case.
+ new Promise(resolve => setTimeout(resolve, 500)),
+ ]);
+ bridgeDisplay.ariaNotify(message);
+}
+
+/**
+ * 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);
+
+ this._supportedSources = [
+ lazy.TorBridgeSource.BridgeDB,
+ lazy.TorBridgeSource.UserProvided,
+ lazy.TorBridgeSource.Lox,
+ ];
+ },
+
+ /**
+ * 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;
+
+ this._grid.hidden = false;
+ },
+
+ /**
+ * Deactivate and hide the bridge grid.
+ */
+ deactivate() {
+ if (!this._active) {
+ return;
+ }
+
+ this._active = false;
+
+ this._forceCloseRowMenus();
+
+ this._grid.hidden = true;
+ },
+
+ 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,
+
+ set connectedBridgeId(bridgeId) {
+ 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 = lazy.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 === lazy.TorBridgeSource.UserProvided ||
+ this._bridgeSource === lazy.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 = lazy.TorSettings.bridges.source;
+ if (source !== this._bridgesVal?.source) {
+ // Our value is stale, abort.
+ return;
+ }
+ const strings = lazy.TorSettings.bridges.bridge_strings;
+ const index = strings.indexOf(bridgeLine);
+ if (index === -1) {
+ return;
+ }
+ strings.splice(index, 1);
+
+ if (strings.length) {
+ lazy.TorSettings.changeSettings({
+ bridges: { source, bridge_strings: strings },
+ });
+ } else {
+ // Remove all bridges and disable.
+ lazy.TorSettings.changeSettings({
+ bridges: { source: lazy.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: [],
+
+ /**
+ * The bridges value, set by the setting-control element.
+ *
+ * @type {object}
+ */
+ _bridgesVal: null,
+
+ set bridges(val) {
+ if (val === null) {
+ // Ignore and wait for the initial.
+ return;
+ }
+ const initial = this._bridgesVal === null;
+ this._bridgesVal = val;
+ this._updateRows(initial);
+ },
+
+ /**
+ * Update the grid to show the latest bridge strings.
+ *
+ * @param {boolean} initializing - Whether this is being called as part of
+ * initialization.
+ */
+ _updateRows(initializing) {
+ // 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 = this._bridgesVal.source;
+ if (bridgeSource !== this._bridgeSource) {
+ newSource = true;
+
+ this._bridgeSource = bridgeSource;
+
+ if (this._supportedSources.includes(bridgeSource)) {
+ this.activate();
+ } else {
+ if (this._active && bridgeSource === lazy.TorBridgeSource.Invalid) {
+ lostAllBridges = true;
+ }
+ this.deactivate();
+ }
+ }
+
+ const ordered = this._active
+ ? this._bridgesVal.bridgeStrings.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);
+ }
+ // NOTE: In the case we were previously active and now inactive,
+ // tor-bridges-display will have already moved the focus out of this area.
+
+ // 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) {
+ postBridgeNotification(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"
+ );
+ },
+
+ /**
+ * 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;
+
+ this._area.hidden = false;
+ },
+
+ /**
+ * Deactivate and hide built-in bridge area.
+ */
+ deactivate() {
+ if (!this._active) {
+ return;
+ }
+ this._active = false;
+
+ this._area.hidden = true;
+ },
+
+ /**
+ * Updates the shown connected state.
+ */
+ _updateConnectedState() {
+ this._connectionStatusEl.classList.toggle(
+ "bridge-status-connected",
+ this._bridgeType &&
+ this._connectedBridgeId &&
+ this._bridgeIds.includes(this._connectedBridgeId)
+ );
+ },
+
+ /**
+ * The bridges value, set by the setting-control element.
+ *
+ * @type {object}
+ */
+ _bridgesVal: null,
+ set bridges(val) {
+ if (val === null) {
+ // Ignore and wait for the initial.
+ return;
+ }
+ const initial = this._bridgesVal === null;
+ this._bridgesVal = val;
+ this._updateBridgeType(initial);
+ this._updateBridgeIds();
+ },
+
+ /**
+ * 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 - Whether this is being called as part of
+ * initialization.
+ */
+ async _updateBridgeType(initializing) {
+ let lostAllBridges = false;
+ let newSource = false;
+ const bridgeSource = this._bridgesVal.source;
+ if (bridgeSource !== this._bridgeSource) {
+ newSource = true;
+
+ this._bridgeSource = bridgeSource;
+
+ if (bridgeSource === lazy.TorBridgeSource.BuiltIn) {
+ this.activate();
+ } else {
+ if (this._active && bridgeSource === lazy.TorBridgeSource.Invalid) {
+ lostAllBridges = true;
+ }
+ this.deactivate();
+ // NOTE: In the case we were previously active, tor-bridges-display will
+ // have already moved the focus out of this area.
+ }
+ }
+
+ const bridgeType = this._active ? this._bridgesVal.builtinType : "";
+
+ 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) {
+ postBridgeNotification(notificationType);
+ }
+ }
+ },
+
+ /**
+ * The bridge IDs/fingerprints for the built-in bridges.
+ *
+ * @type {Array<string>}
+ */
+ _bridgeIds: [],
+ /**
+ * Update _bridgeIds
+ */
+ _updateBridgeIds() {
+ this._bridgeIds = [];
+ for (const bridgeLine of this._bridgesVal.bridgeStrings) {
+ try {
+ this._bridgeIds.push(lazy.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,
+
+ set connectedBridgeId(val) {
+ this._connectedBridgeId = val;
+ 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,
+
+ _enabled: false,
+
+ /**
+ * Initialize the bridge pass area.
+ */
+ init() {
+ if (!lazy.Lox.enabled) {
+ // Area should remain inactive and hidden.
+ return;
+ }
+
+ this._enabled = true;
+ 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", () => {
+ window.gSubDialog.open(
+ "chrome://browser/content/torpreferences/loxInviteDialog.xhtml",
+ { features: "resizable=yes" }
+ );
+ });
+ this._unlockAlertButton.addEventListener("click", () => {
+ lazy.Lox.clearEventData(this._loxId);
+ });
+
+ Services.obs.addObserver(this, lazy.LoxTopics.UpdateActiveLoxId);
+ Services.obs.addObserver(this, lazy.LoxTopics.UpdateEvents);
+ Services.obs.addObserver(this, lazy.LoxTopics.UpdateNextUnlock);
+ Services.obs.addObserver(this, lazy.LoxTopics.UpdateRemainingInvites);
+ Services.obs.addObserver(this, lazy.LoxTopics.NewInvite);
+
+ window.addEventListener(
+ "unload",
+ () => {
+ Services.obs.removeObserver(this, lazy.LoxTopics.UpdateActiveLoxId);
+ Services.obs.removeObserver(this, lazy.LoxTopics.UpdateEvents);
+ Services.obs.removeObserver(this, lazy.LoxTopics.UpdateNextUnlock);
+ Services.obs.removeObserver(
+ this,
+ lazy.LoxTopics.UpdateRemainingInvites
+ );
+ Services.obs.removeObserver(this, lazy.LoxTopics.NewInvite);
+ },
+ { once: true }
+ );
+ },
+
+ observe(subject, topic) {
+ switch (topic) {
+ case lazy.LoxTopics.UpdateActiveLoxId:
+ this._updateLoxId();
+ break;
+ case lazy.LoxTopics.UpdateNextUnlock:
+ this._updateNextUnlock();
+ break;
+ case lazy.LoxTopics.UpdateEvents:
+ this._updatePendingEvents();
+ break;
+ case lazy.LoxTopics.UpdateRemainingInvites:
+ this._updateRemainingInvites();
+ break;
+ case lazy.LoxTopics.NewInvite:
+ this._updateHaveExistingInvites();
+ break;
+ }
+ },
+
+ /**
+ * The bridges value, set by the setting-control element.
+ *
+ * @type {object}
+ */
+ _bridgesVal: null,
+ set bridges(val) {
+ if (val === null) {
+ // Ignore and wait for initial.
+ return;
+ }
+ if (!this._enabled) {
+ // Area should remain inactive and hidden.
+ return;
+ }
+ this._bridgesVal = val;
+ this._updateLoxId();
+ },
+
+ /**
+ * 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 =
+ this._bridgesVal?.source === lazy.TorBridgeSource.Lox
+ ? lazy.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
+ ? lazy.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 ? !!lazy.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 lazy.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
+ ? lazy.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 display area.
+ *
+ * @type {Element?}
+ */
+ _displayEl: null,
+ /**
+ * The area for showing current bridges.
+ *
+ * @type {Element?}
+ */
+ _bridgesEl: null,
+ /**
+ * The area for sharing bridge addresses.
+ *
+ * @type {Element?}
+ */
+ _shareEl: null,
+ /**
+ * The area for showing no bridges.
+ *
+ * @type {Element?}
+ */
+ _noBridgesEl: null,
+ /**
+ * A map from the bridge source to its corresponding label.
+ *
+ * @type {?Map<number, Element>}
+ */
+ _sourceLabels: null,
+
+ /**
+ * Whether we have been initialized.
+ *
+ * @type {boolean}
+ */
+ _initialized: false,
+
+ /**
+ * Initialize the bridge settings.
+ *
+ * @param {Element} displayEl - The widget element we are controlling.
+ */
+ init(displayEl) {
+ if (this._initialized) {
+ return;
+ }
+
+ this._displayEl = displayEl;
+ this._bridgesEl = document.getElementById("tor-bridges-current");
+ this._noBridgesEl = document.getElementById("tor-bridges-none");
+
+ this._sourceLabels = new Map([
+ [
+ lazy.TorBridgeSource.BuiltIn,
+ document.getElementById("tor-bridges-built-in-label"),
+ ],
+ [
+ lazy.TorBridgeSource.UserProvided,
+ document.getElementById("tor-bridges-user-label"),
+ ],
+ [
+ lazy.TorBridgeSource.BridgeDB,
+ document.getElementById("tor-bridges-requested-label"),
+ ],
+ [
+ lazy.TorBridgeSource.Lox,
+ document.getElementById("tor-bridges-lox-label"),
+ ],
+ ]);
+ this._shareEl = document.getElementById("tor-bridges-share");
+
+ this._initBridgesMenu();
+ this._initShareArea();
+
+ gBridgeGrid.init();
+ gBuiltinBridgesArea.init();
+ gLoxStatus.init();
+ this._initialized = true;
+ // Re-trigger our current bridges value to pass on to any descendants.
+ this.bridges = this._bridgesVal;
+ this.connectedBridgeId = this._connectedBridgeId;
+ },
+
+ /**
+ * The bridges value, set by the setting-control element.
+ *
+ * @type {object}
+ */
+ _bridgesVal: null,
+
+ set bridges(val) {
+ if (val === null) {
+ // Corresponds to pending TorSettings initialization, wait for a non-null
+ // value.
+ return;
+ }
+ this._bridgesVal = val;
+ if (!this._initialized) {
+ return;
+ }
+ this._updateSource();
+ this._updateBridgeStrings();
+ // Pass on to descendants.
+ gBridgeGrid.bridges = val;
+ gBuiltinBridgesArea.bridges = val;
+ gLoxStatus.bridges = val;
+ },
+
+ /**
+ * The ID of the currently connected bridge, or `null` if there is none.
+ *
+ * @type {string?}
+ */
+ _connectedBridgeId: null,
+
+ set connectedBridgeId(val) {
+ this._connectedBridgeId = val;
+ if (!this._initialized) {
+ return;
+ }
+ // NOTE: This should be safe to call, even when _bridgesVal is still null.
+ gBridgeGrid.connectedBridgeId = val;
+ gBuiltinBridgesArea.connectedBridgeId = val;
+ },
+
+ /**
+ * 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 = this._bridgesVal.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 === lazy.TorBridgeSource.UserProvided ||
+ bridgeSource === lazy.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.
+ lazy.moveFocusToBridgeHeading(window);
+ }
+ },
+
+ /**
+ * Whether we have bridges or not, or null if it is unknown.
+ *
+ * @type {boolean?}
+ */
+ _haveBridges: null,
+
+ /**
+ * Update the _haveBridges value.
+ */
+ _updateHaveBridges() {
+ const haveBridges = this._bridgesVal.haveBridges;
+
+ if (haveBridges === this._haveBridges) {
+ return;
+ }
+
+ this._haveBridges = haveBridges;
+
+ // Add classes to show or hide the "no bridges" and "Your bridges" sections.
+ this._bridgesEl.hidden = !haveBridges;
+ this._noBridgesEl.hidden = haveBridges;
+
+ this._displayEl.classList.toggle("has-tor-bridges", haveBridges);
+ },
+
+ /**
+ * 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 = this._bridgesVal.bridgeStrings;
+
+ 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", () => {
+ lazy.openUserProvideBridgeDialog(window, "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;
+ }
+
+ lazy.TorSettings.changeSettings({
+ // This should always have the side effect of disabling bridges as
+ // well.
+ bridges: { source: lazy.TorBridgeSource.Invalid },
+ });
+ });
+
+ this._bridgesMenu.addEventListener("showing", () => {
+ qrItem.hidden = !this._canShare || !this._canQRBridges;
+ editItem.hidden =
+ this._bridgeSource !== lazy.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 });
+ },
+};
+
+// TODO: Replace gBridgeSettings and #tor-bridges-display-template with proper
+// widgets (using MozLitElement).
+/**
+ * Show the current bridges.
+ */
+class TorBridgesDisplay extends HTMLElement {
+ connectedCallback() {
+ if (this.children.length) {
+ return;
+ }
+ // Take the template children since we only expect one instance of this.
+ this.replaceChildren(
+ ...document.getElementById("tor-bridges-display-template").content
+ .childNodes
+ );
+ gBridgeSettings.init(this);
+ }
+
+ set connectedBridgeId(val) {
+ gBridgeSettings.connectedBridgeId = val;
+ }
+
+ set bridges(val) {
+ gBridgeSettings.bridges = val;
+ }
+
+ /**
+ * Focus the "Your bridges" heading, if it is visible.
+ *
+ * @returns {boolean} - `true` if the heading was visible and focused.
+ */
+ focusHeading() {
+ if (!gBridgeSettings._haveBridges) {
+ // Heading is hidden.
+ return false;
+ }
+ document.getElementById("tor-bridges-current-heading-non-search").focus();
+ return true;
+ }
+}
+customElements.define("tor-bridges-display", TorBridgesDisplay);
=====================================
toolkit/content/widgets/moz-fieldset/moz-fieldset.css
=====================================
@@ -46,6 +46,10 @@ h4,
h5,
h6 {
margin: 0;
+
+ &[tabindex]:focus-visible {
+ outline-offset: var(--focus-outline-offset);
+ }
}
.description {
=====================================
toolkit/content/widgets/moz-fieldset/moz-fieldset.mjs
=====================================
@@ -11,12 +11,24 @@ import { MozLitElement } from "../lit-utils.mjs";
* @type {Record<number, (label: string) => ReturnType<typeof html>>}
*/
const HEADING_LEVEL_TEMPLATES = {
- 1: label => html`<h1 class="text-box-trim-start">${label}</h1>`,
- 2: label => html`<h2 class="text-box-trim-start">${label}</h2>`,
- 3: label => html`<h3 class="text-box-trim-start">${label}</h3>`,
- 4: label => html`<h4>${label}</h4>`,
- 5: label => html`<h5>${label}</h5>`,
- 6: label => html`<h6>${label}</h6>`,
+ 1: (label, tabindex) =>
+ html`<h1 class="text-box-trim-start" tabindex=${ifDefined(tabindex)}>
+ ${label}
+ </h1>`,
+ 2: (label, tabindex) =>
+ html`<h2 class="text-box-trim-start" tabindex=${ifDefined(tabindex)}>
+ ${label}
+ </h2>`,
+ 3: (label, tabindex) =>
+ html`<h3 class="text-box-trim-start" tabindex=${ifDefined(tabindex)}>
+ ${label}
+ </h3>`,
+ 4: (label, tabindex) =>
+ html`<h4 tabindex=${ifDefined(tabindex)}>${label}</h4>`,
+ 5: (label, tabindex) =>
+ html`<h5 tabindex=${ifDefined(tabindex)}>${label}</h5>`,
+ 6: (label, tabindex) =>
+ html`<h6 tabindex=${ifDefined(tabindex)}>${label}</h6>`,
};
/**
@@ -42,6 +54,9 @@ export default class MozFieldset extends MozLitElement {
disabled: { type: Boolean, reflect: true },
iconSrc: { type: String },
badge: { type: String },
+ // Allows the heading to be focusable, but not part of the Tab focus cycle.
+ // See tor-browser#45143.
+ focusableHeading: { type: Boolean, attribute: "focusable-heading" },
};
constructor() {
@@ -132,12 +147,25 @@ export default class MozFieldset extends MozLitElement {
legendTemplate() {
let label =
- HEADING_LEVEL_TEMPLATES[this.headingLevel]?.(this.label) || this.label;
+ HEADING_LEVEL_TEMPLATES[this.headingLevel]?.(
+ this.label,
+ this.focusableHeading ? "-1" : undefined
+ ) || this.label;
return html`<legend part="label">
${this.iconTemplate()}${label}${this.badgeTemplate()}
</legend>`;
}
+ /**
+ * Move the user's focus to the heading, if it is focusable.
+ */
+ focusHeading() {
+ if (!this.focusableHeading) {
+ return;
+ }
+ this.shadowRoot.querySelector("h1,h2,h3,h4,h5,h6")?.focus();
+ }
+
iconTemplate() {
if (!this.iconSrc) {
return "";
=====================================
toolkit/locales/en-US/toolkit/global/tor-browser.ftl
=====================================
@@ -152,6 +152,9 @@ tor-connection-status-connect-button =
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.
# Toggle button for enabling and disabling the use of bridges.
tor-bridges-use-bridges =
@@ -310,12 +313,23 @@ tor-bridges-lox-got-it-button = Got it
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…
@@ -326,6 +340,10 @@ 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
@@ -333,6 +351,12 @@ tor-bridges-provider-telegram-name = Telegram
# $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
@@ -340,15 +364,31 @@ tor-bridges-provider-web-name = Web
# $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.
+# E.g. in English, "Email bridges(a)torproject.org".
+tor-bridges-source-email-link =
+ .label = Gmail or Riseup
+ .description = Email { $address }
tor-bridges-request-from-browser = You can also get bridges from the bridge bot without leaving { -brand-short-name }.
tor-bridges-request-button = Request bridges…
+tor-bridges-request-from-browser2 = Get bridges from the bridge bot without leaving { -brand-short-name }.
+tor-bridges-request-button2 =
+ .label = Request bridges…
## Warning dialog when removing all bridges.
=====================================
toolkit/modules/TorSettings.sys.mjs
=====================================
@@ -358,6 +358,12 @@ class TorSettingsImpl {
*/
#initialized = false;
+ /**
+ * Whether init has been called.
+ *
+ * @type {boolean}
+ */
+ #initCalled = false;
/**
* Whether uninit cleanup has been called.
*
@@ -510,11 +516,12 @@ class TorSettingsImpl {
* Load or init our settings.
*/
async init() {
- if (this.#initialized) {
+ if (this.#initCalled) {
lazy.logger.warn("Called init twice.");
await this.#initializedPromise;
return;
}
+ this.#initCalled = true;
try {
await this.#initInternal();
this.#initialized = true;
=====================================
tools/torbrowser/l10n/migrations/bug-45058-bridge-settings.py
=====================================
@@ -0,0 +1,102 @@
+import re
+
+import fluent.syntax.ast as FTL
+from fluent.migrate.helpers import transforms_from
+from fluent.migrate.transforms import COPY_PATTERN, FluentSource
+from fluent.syntax.visitor import Visitor
+
+
+class RemoveAnchorVisitor(Visitor):
+ """Class to remove <a> and </a> wrappers from a Fluent TextElement."""
+
+ def __init__(self):
+ # Good enough regex for our needs that will match starting and ending
+ # tags.
+ self._anchor_regex = re.compile(r"<\/?[aA](| [^>]*)>")
+ super().__init__()
+
+ def visit_TextElement(self, node):
+ node.value = self._anchor_regex.sub("", node.value)
+
+
+class RemoveAnchorTransform(FluentSource):
+ """Class to remove <a> and </a> wrappers from a Fluent source."""
+
+ def __call__(self, ctx):
+ pattern = ctx.get_fluent_source_pattern(self.path, self.key).clone()
+ # Visit every node in the pattern, replacing each TextElement's content.
+ RemoveAnchorVisitor().visit(pattern)
+ return pattern
+
+
+def migrate(ctx):
+ ctx.add_transforms(
+ "tor-browser.ftl",
+ "tor-browser.ftl",
+ transforms_from(
+ """
+tor-bridges-group =
+ .label = { COPY_PATTERN(path, "tor-bridges-heading") }
+ .description = { COPY_PATTERN(path, "tor-bridges-overview") }
+tor-bridges-add-bridges-group =
+ .label = { COPY_PATTERN(path, "tor-bridges-add-bridges-heading") }
+tor-bridges-replace-bridges-group =
+ .label = { COPY_PATTERN(path, "tor-bridges-replace-bridges-heading") }
+tor-bridges-choose-built-in-button =
+ .label = { COPY_PATTERN(path, "tor-bridges-select-built-in-description") }
+tor-bridges-enter-bridges-button =
+ .label = { COPY_PATTERN(path, "tor-bridges-add-addresses-description") }
+tor-bridges-find-more-group =
+ .label = { COPY_PATTERN(path, "tor-bridges-find-more-heading") }
+ .description = { COPY_PATTERN(path, "tor-bridges-find-more-description") }
+tor-bridges-request-button2 =
+ .label = { COPY_PATTERN(path, "tor-bridges-request-button") }
+tor-bridges-source-email-link =
+ .label = { COPY_PATTERN(path, "tor-bridges-provider-email-name") }
+ .description = { COPY_PATTERN(path, "tor-bridges-provider-email-instruction") }
+""",
+ path="tor-browser.ftl",
+ )
+ + [
+ FTL.Message(
+ id=FTL.Identifier("tor-bridges-source-telegram-link"),
+ value=None,
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY_PATTERN(
+ "tor-browser.ftl",
+ "tor-bridges-provider-telegram-name",
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("description"),
+ value=RemoveAnchorTransform(
+ "tor-browser.ftl",
+ "tor-bridges-provider-telegram-instruction",
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("tor-bridges-source-web-link"),
+ value=None,
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY_PATTERN(
+ "tor-browser.ftl",
+ "tor-bridges-provider-web-name",
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("description"),
+ value=RemoveAnchorTransform(
+ "tor-browser.ftl",
+ "tor-bridges-provider-web-instruction",
+ ),
+ ),
+ ],
+ ),
+ ],
+ )
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/7a41b6…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/7a41b6…
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! [android] Modify build system
by Pier Angelo Vendrame (@pierov) 04 Aug '26
by Pier Angelo Vendrame (@pierov) 04 Aug '26
04 Aug '26
Pier Angelo Vendrame pushed to branch tor-browser-153.0esr-16.0-1 at The Tor Project / Applications / Tor Browser
Commits:
e76d7d68 by Pier Angelo Vendrame at 2026-08-04T13:06:48+00:00
fixup! [android] Modify build system
Revert "fixup! [android] Modify build system"
This reverts commit b827102ad26c2e1b10cdee1ba4cb568e034f4020.
- - - - -
7a41b6fc by Pier Angelo Vendrame at 2026-08-04T13:06:48+00:00
fixup! TB 42669: [android] Use custom no-op app-services
Revert "fixup! TB 42669: [android] Use custom no-op app-services"
This reverts commit becb4a94d8c9185a02c157f395cb0de2e079957c.
- - - - -
4 changed files:
- mobile/android/fenix/app/build.gradle
- mobile/android/gradle/plugins/nimbus-gradle-plugin/src/main/kotlin/org/mozilla/appservices/tooling/nimbus/NimbusAssembleToolsTask.kt
- mobile/android/gradle/plugins/nimbus-gradle-plugin/src/main/kotlin/org/mozilla/appservices/tooling/nimbus/NimbusGradlePlugin.kt
- mobile/android/moz.configure
Changes:
=====================================
mobile/android/fenix/app/build.gradle
=====================================
@@ -291,12 +291,18 @@ androidComponents {
onVariants(selector().all()) { variant ->
def buildType = variant.buildType
+ // When this is set, a-s doesn't attempt to download NIMBUS_FML.
+ if (gradle.mozconfig.substs.NIMBUS_FML) {
+ System.setProperty("nimbusFml", gradle.mozconfig.substs.NIMBUS_FML)
+ }
+
def disableTor = providers.gradleProperty("disableTor").getOrElse(false)
project.logger.debug("----------------------------------------------")
project.logger.debug("Variant name: " + variant.name)
project.logger.debug("Build type: " + buildType)
project.logger.debug("Flavor: " + variant.flavorName)
+ project.logger.debug("nimbusFml: " + providers.gradleProperty("nimbusFml").getOrNull())
project.logger.debug("Tor is disabled: " + disableTor)
variant.buildConfigFields.put("DISABLE_TOR", new BuildConfigField("boolean", "$disableTor", null))
=====================================
mobile/android/gradle/plugins/nimbus-gradle-plugin/src/main/kotlin/org/mozilla/appservices/tooling/nimbus/NimbusAssembleToolsTask.kt
=====================================
@@ -28,6 +28,11 @@ import java.net.URI
import java.security.MessageDigest
import javax.inject.Inject
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.nio.file.StandardCopyOption
+
/**
* A task that fetches a prebuilt `nimbus-fml` binary for the current platform.
*
@@ -113,6 +118,17 @@ abstract class NimbusAssembleToolsTask : DefaultTask() {
@TaskAction
fun assembleTools() {
+ var nimbusFml = System.getenv("NIMBUS_FML") ?: ""
+ if (nimbusFml == "") {
+ nimbusFml = System.getProperty("nimbusFml", "")
+ }
+ if (nimbusFml != "") {
+ val source = File(nimbusFml).toPath()
+ val dest = fmlBinary.get().asFile.toPath()
+ Files.copy(source, dest, StandardCopyOption.REPLACE_EXISTING)
+ return
+ }
+
val binaryFile = fmlBinary.get().asFile
val archiveFileObj = archiveFile.get().asFile
val hashFileObj = hashFile.get().asFile
=====================================
mobile/android/gradle/plugins/nimbus-gradle-plugin/src/main/kotlin/org/mozilla/appservices/tooling/nimbus/NimbusGradlePlugin.kt
=====================================
@@ -118,20 +118,20 @@ class NimbusPlugin : Plugin<Project> {
@Suppress("UNCHECKED_CAST")
val mozconfigSubsts = mozconfig?.get("substs") as? Map<String, Any>
- // This is subtle. We capture `NIMBUS_FML` in the configuration cache as a `String`.
- // If we access `project.gradle...` in the `provider` `Callable` below, we capture the
- // `Project` in the configuration cache, which is not desirable.
- //
- // We can't produce a `File` immediately, because in some configurations, namely
- // `android-gradle-dependencies` tasks, `NIMBUS_FML` is legitimately unset (`null`). So
- // we pass strings around and map to `File` types lazily "by hand".
- //
- // Finally: if this process fails, including with an exception, the framework swallows
- // the details and says something like `MissingValueException`, which can be hard to
- // interpret. Hence, this explanation of the details.
- val nimbusFmlPath = mozconfigSubsts?.get("NIMBUS_FML") as? String
-
- if (nimbusFmlPath != null) {
+ if (mozconfigSubsts?.get("MOZ_APPSERVICES_IN_TREE").isTruthy()) {
+ // This is subtle. We capture `NIMBUS_FML` in the configuration cache as a `String`.
+ // If we access `project.gradle...` in the `provider` `Callable` below, we capture the
+ // `Project` in the configuration cache, which is not desirable.
+ //
+ // We can't produce a `File` immediately, because in some configurations, namely
+ // `android-gradle-dependencies` tasks, `NIMBUS_FML` is legitimately unset (`null`). So
+ // we pass strings around and map to `File` types lazily "by hand".
+ //
+ // Finally: if this process fails, including with an exception, the framework swallows
+ // the details and says something like `MissingValueException`, which can be hard to
+ // interpret. Hence, this explanation of the details.
+ val nimbusFmlPath = mozconfigSubsts?.get("NIMBUS_FML") as? String
+
val fmlBinaryString = project.providers.provider {
nimbusFmlPath
}
=====================================
mobile/android/moz.configure
=====================================
@@ -216,8 +216,9 @@ def check_host_bin_prog(var, prog, toolchain=None):
)
-check_host_bin_prog("EMBEDDED_UNIFFI_BINDGEN", "embedded-uniffi-bindgen")
-check_host_bin_prog("NIMBUS_FML", "nimbus-fml")
+# tb-44669 - this was added in rebase 148 to make android compile but tracking potentially better fix
+#check_host_bin_prog("EMBEDDED_UNIFFI_BINDGEN", "embedded-uniffi-bindgen")
+#check_host_bin_prog("NIMBUS_FML", "nimbus-fml")
project_flag(
"MOZ_ANDROID_NETWORK_STATE",
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/fd46fa…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/fd46fa…
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 43072: Add aria label and description to moz-message-bar.
by morgan (@morgan) 04 Aug '26
by morgan (@morgan) 04 Aug '26
04 Aug '26
morgan pushed to branch mullvad-browser-153.0esr-16.0-1 at The Tor Project / Applications / Mullvad Browser
Commits:
14873cf2 by Henry Wilkes at 2026-08-04T13:01:38+00:00
fixup! BB 43072: Add aria label and description to moz-message-bar.
BB 45186: Remove duplicate alert roles.
We also restrict the `aria-labelledby` and `aria-describedby` attributes
to only be used with the "alert" role.
- - - - -
2 changed files:
- toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs
- toolkit/content/widgets/notificationbox.js
Changes:
=====================================
toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs
=====================================
@@ -65,8 +65,9 @@ export default class MozMessageBar extends MozLitElement {
supportPage: { type: String },
messageL10nId: { type: String },
messageL10nArgs: { type: String },
- role: { type: String, reflect: true },
- useAlertRole: { type: Boolean },
+ // Move the role from the widget to its shadow root, where we can apply
+ // aria-labelledby and aria-describedby. tor-browser#45186.
+ role: { type: String, mapped: true },
};
constructor() {
@@ -127,8 +128,6 @@ export default class MozMessageBar extends MozLitElement {
* @type {string}
*/
this.role = "alert";
-
- this.useAlertRole = true;
}
onActionSlotchange() {
@@ -170,17 +169,6 @@ export default class MozMessageBar extends MozLitElement {
></slot>`;
}
- setAlertRole() {
- // Wait a little for this to render before setting the role for more
- // consistent alerts to screen readers.
- this.useAlertRole = false;
- window.requestAnimationFrame(() => {
- window.requestAnimationFrame(() => {
- this.useAlertRole = true;
- });
- });
- }
-
iconTemplate() {
let iconData = messageTypeToIconData[this.type];
if (iconData) {
@@ -224,6 +212,16 @@ export default class MozMessageBar extends MozLitElement {
}
render() {
+ let ariaLabelledBy;
+ let ariaDescribedBy;
+ if (this.role === "alert") {
+ if (this.heading) {
+ ariaLabelledBy = "heading";
+ ariaDescribedBy = "content";
+ } else {
+ ariaLabelledBy = "content";
+ }
+ }
return html`
<link
rel="stylesheet"
@@ -231,9 +229,9 @@ export default class MozMessageBar extends MozLitElement {
/>
<div
class="container"
- role=${ifDefined(this.useAlertRole ? "alert" : undefined)}
- aria-labelledby=${this.heading ? "heading" : "content"}
- aria-describedby=${ifDefined(this.heading ? "content" : undefined)}
+ role=${ifDefined(this.role || undefined)}
+ aria-labelledby=${ifDefined(ariaLabelledBy)}
+ aria-describedby=${ifDefined(ariaDescribedBy)}
>
${this.iconTemplate()}
<div class="content">
=====================================
toolkit/content/widgets/notificationbox.js
=====================================
@@ -492,6 +492,20 @@
this.control.removeNotification(this);
}
+ setAlertRole() {
+ // Wait a little for this to render before setting the role for more
+ // consistent alerts to screen readers.
+ // tor-browser#45186: "role" is a mapped attribute, so `removeAttribute`
+ // will go undetected by the moz-message-bar widget. Instead we set the
+ // role property directly.
+ this.role = undefined;
+ window.requestAnimationFrame(() => {
+ window.requestAnimationFrame(() => {
+ this.role = "alert";
+ });
+ });
+ }
+
handleEvent(e) {
// If clickjacking delay is active, prevent any "click"/"command" from
// going through. Also restart the delay if the user tries to click too early.
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/148…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/148…
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! BB 43072: Add aria label and description to moz-message-bar.
by morgan (@morgan) 04 Aug '26
by morgan (@morgan) 04 Aug '26
04 Aug '26
morgan pushed to branch tor-browser-153.0esr-16.0-1 at The Tor Project / Applications / Tor Browser
Commits:
fd46faa3 by Henry Wilkes at 2026-08-04T12:43:06+00:00
fixup! BB 43072: Add aria label and description to moz-message-bar.
BB 45186: Remove duplicate alert roles.
We also restrict the `aria-labelledby` and `aria-describedby` attributes
to only be used with the "alert" role.
- - - - -
2 changed files:
- toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs
- toolkit/content/widgets/notificationbox.js
Changes:
=====================================
toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs
=====================================
@@ -65,8 +65,9 @@ export default class MozMessageBar extends MozLitElement {
supportPage: { type: String },
messageL10nId: { type: String },
messageL10nArgs: { type: String },
- role: { type: String, reflect: true },
- useAlertRole: { type: Boolean },
+ // Move the role from the widget to its shadow root, where we can apply
+ // aria-labelledby and aria-describedby. tor-browser#45186.
+ role: { type: String, mapped: true },
};
constructor() {
@@ -127,8 +128,6 @@ export default class MozMessageBar extends MozLitElement {
* @type {string}
*/
this.role = "alert";
-
- this.useAlertRole = true;
}
onActionSlotchange() {
@@ -170,17 +169,6 @@ export default class MozMessageBar extends MozLitElement {
></slot>`;
}
- setAlertRole() {
- // Wait a little for this to render before setting the role for more
- // consistent alerts to screen readers.
- this.useAlertRole = false;
- window.requestAnimationFrame(() => {
- window.requestAnimationFrame(() => {
- this.useAlertRole = true;
- });
- });
- }
-
iconTemplate() {
let iconData = messageTypeToIconData[this.type];
if (iconData) {
@@ -224,6 +212,16 @@ export default class MozMessageBar extends MozLitElement {
}
render() {
+ let ariaLabelledBy;
+ let ariaDescribedBy;
+ if (this.role === "alert") {
+ if (this.heading) {
+ ariaLabelledBy = "heading";
+ ariaDescribedBy = "content";
+ } else {
+ ariaLabelledBy = "content";
+ }
+ }
return html`
<link
rel="stylesheet"
@@ -231,9 +229,9 @@ export default class MozMessageBar extends MozLitElement {
/>
<div
class="container"
- role=${ifDefined(this.useAlertRole ? "alert" : undefined)}
- aria-labelledby=${this.heading ? "heading" : "content"}
- aria-describedby=${ifDefined(this.heading ? "content" : undefined)}
+ role=${ifDefined(this.role || undefined)}
+ aria-labelledby=${ifDefined(ariaLabelledBy)}
+ aria-describedby=${ifDefined(ariaDescribedBy)}
>
${this.iconTemplate()}
<div class="content">
=====================================
toolkit/content/widgets/notificationbox.js
=====================================
@@ -492,6 +492,20 @@
this.control.removeNotification(this);
}
+ setAlertRole() {
+ // Wait a little for this to render before setting the role for more
+ // consistent alerts to screen readers.
+ // tor-browser#45186: "role" is a mapped attribute, so `removeAttribute`
+ // will go undetected by the moz-message-bar widget. Instead we set the
+ // role property directly.
+ this.role = undefined;
+ window.requestAnimationFrame(() => {
+ window.requestAnimationFrame(() => {
+ this.role = "alert";
+ });
+ });
+ }
+
handleEvent(e) {
// If clickjacking delay is active, prevent any "click"/"command" from
// going through. Also restart the delay if the user tries to click too early.
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/fd46faa…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/fd46faa…
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 42730: Patch RemoteSettings to use only local dumps as a data source
by morgan (@morgan) 04 Aug '26
by morgan (@morgan) 04 Aug '26
04 Aug '26
morgan pushed to branch mullvad-browser-153.0esr-16.0-1 at The Tor Project / Applications / Mullvad Browser
Commits:
5bff9b07 by Pier Angelo Vendrame at 2026-08-04T12:41:31+00:00
fixup! BB 42730: Patch RemoteSettings to use only local dumps as a data source
BB 45185: Make sure the Rust remote settings client stays offline.
- - - - -
2 changed files:
- services/settings/Utils.sys.mjs
- third_party/application-services/components/viaduct/src/lib.rs
Changes:
=====================================
services/settings/Utils.sys.mjs
=====================================
@@ -113,6 +113,9 @@ export var Utils = {
log,
get shouldSkipRemoteActivity() {
+ if (AppConstants.BASE_BROWSER_VERSION) {
+ return true;
+ }
if (
(lazy.isRunningTests || Cu.isInAutomation) &&
this.SERVER_URL == "data:,#remote-settings-dummy/v1"
=====================================
third_party/application-services/components/viaduct/src/lib.rs
=====================================
@@ -96,7 +96,8 @@ impl Request {
}
pub fn send(self) -> Result<Response, ViaductError> {
- crate::backend::send(self)
+ // tor-browser#44576: Force an error as a defense-in-depth.
+ Err(ViaductError::NetworkError("Viaduct requests are disabled.".into()))
}
/// Alias for `Request::new(Method::Get, url)`, for convenience.
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/5bf…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/5bf…
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