tbb-commits
Threads by month
- ----- 2025 -----
- 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
January 2024
- 1 participants
- 128 discussions

[Git][tpo/applications/tor-browser-build][main] Update release-prep template with startpage contact to notify on major ESR transition
by richard (@richard) 24 Jan '24
by richard (@richard) 24 Jan '24
24 Jan '24
richard pushed to branch main at The Tor Project / Applications / tor-browser-build
Commits:
ef63c83c by Richard Pospesel at 2024-01-24T11:30:38+00:00
Update release-prep template with startpage contact to notify on major ESR transition
- - - - -
1 changed file:
- .gitlab/issue_templates/Release Prep - Tor Browser Alpha.md
Changes:
=====================================
.gitlab/issue_templates/Release Prep - Tor Browser Alpha.md
=====================================
@@ -169,6 +169,8 @@
- [ ] Email external partners:
- ***(Optional, after ESR migration)*** Cloudflare: ask-research(a)cloudflare.com
- **NOTE** : We need to provide them with updated user agent string so they can update their internal machinery to prevent Tor Browser users from getting so many CAPTCHAs
+ - ***(Optional, after ESR migration)*** Startpage: admin(a)startpage.com
+ - **NOTE** : Startpage also needs the updated user-agent string for better experience on their onion service sites.
</details>
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/e…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/e…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-115.7.0esr-13.5-1] fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in...
by richard (@richard) 23 Jan '24
by richard (@richard) 23 Jan '24
23 Jan '24
richard pushed to branch tor-browser-115.7.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits:
6211ed8f by Henry Wilkes at 2024-01-23T18:27:26+00:00
fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
Bug 42036: Refactor tor setting dialogs to follow firefox style.
Also, move the user focus to their new bridges when the bridge dialogs
succeed.
- - - - -
16 changed files:
- + browser/components/torpreferences/content/bridgeQrDialog.js
- − browser/components/torpreferences/content/bridgeQrDialog.mjs
- browser/components/torpreferences/content/bridgeQrDialog.xhtml
- browser/components/torpreferences/content/builtinBridgeDialog.mjs → browser/components/torpreferences/content/builtinBridgeDialog.js
- browser/components/torpreferences/content/builtinBridgeDialog.xhtml
- browser/components/torpreferences/content/connectionPane.js
- browser/components/torpreferences/content/connectionSettingsDialog.mjs → browser/components/torpreferences/content/connectionSettingsDialog.js
- browser/components/torpreferences/content/connectionSettingsDialog.xhtml
- browser/components/torpreferences/content/provideBridgeDialog.mjs → browser/components/torpreferences/content/provideBridgeDialog.js
- browser/components/torpreferences/content/provideBridgeDialog.xhtml
- browser/components/torpreferences/content/requestBridgeDialog.mjs → browser/components/torpreferences/content/requestBridgeDialog.js
- browser/components/torpreferences/content/requestBridgeDialog.xhtml
- + browser/components/torpreferences/content/torLogDialog.js
- − browser/components/torpreferences/content/torLogDialog.mjs
- browser/components/torpreferences/content/torLogDialog.xhtml
- browser/components/torpreferences/jar.mn
Changes:
=====================================
browser/components/torpreferences/content/bridgeQrDialog.js
=====================================
@@ -0,0 +1,34 @@
+"use strict";
+
+const { QRCode } = ChromeUtils.importESModule(
+ "resource://gre/modules/QRCode.sys.mjs"
+);
+
+const { TorStrings } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorStrings.sys.mjs"
+);
+
+window.addEventListener(
+ "DOMContentLoaded",
+ () => {
+ const bridgeString = window.arguments[0];
+
+ document.documentElement.setAttribute(
+ "title",
+ TorStrings.settings.scanQrTitle
+ );
+ const target = document.getElementById("bridgeQr-target");
+ const style = window.getComputedStyle(target);
+ const width = style.width.substr(0, style.width.length - 2);
+ const height = style.height.substr(0, style.height.length - 2);
+ new QRCode(target, {
+ text: bridgeString,
+ width,
+ height,
+ colorDark: style.color,
+ colorLight: style.backgroundColor,
+ document,
+ });
+ },
+ { once: true }
+);
=====================================
browser/components/torpreferences/content/bridgeQrDialog.mjs deleted
=====================================
@@ -1,44 +0,0 @@
-import { QRCode } from "resource://gre/modules/QRCode.sys.mjs";
-
-import { TorStrings } from "resource://gre/modules/TorStrings.sys.mjs";
-
-export class BridgeQrDialog {
- constructor() {
- this._bridgeString = "";
- }
-
- static get selectors() {
- return {
- target: "#bridgeQr-target",
- };
- }
-
- _populateXUL(window, dialog) {
- dialog.parentElement.setAttribute("title", TorStrings.settings.scanQrTitle);
- const target = dialog.querySelector(BridgeQrDialog.selectors.target);
- const style = window.getComputedStyle(target);
- const width = style.width.substr(0, style.width.length - 2);
- const height = style.height.substr(0, style.height.length - 2);
- new QRCode(target, {
- text: this._bridgeString,
- width,
- height,
- colorDark: style.color,
- colorLight: style.backgroundColor,
- document: window.document,
- });
- }
-
- init(window, dialog) {
- this._populateXUL(window, dialog);
- }
-
- openDialog(gSubDialog, bridgeString) {
- this._bridgeString = bridgeString;
- gSubDialog.open(
- "chrome://browser/content/torpreferences/bridgeQrDialog.xhtml",
- { features: "resizable=yes" },
- this
- );
- }
-}
=====================================
browser/components/torpreferences/content/bridgeQrDialog.xhtml
=====================================
@@ -9,6 +9,8 @@
xmlns:html="http://www.w3.org/1999/xhtml"
>
<dialog id="bridgeQr-dialog" buttons="accept">
+ <script src="chrome://browser/content/torpreferences/bridgeQrDialog.js" />
+
<html:div>
<html:div id="bridgeQr">
<html:div id="bridgeQr-target" />
@@ -16,16 +18,5 @@
<html:div id="bridgeQr-onion" />
</html:div>
</html:div>
- <script type="application/javascript">
- <![CDATA[
- "use strict";
-
- let dialogObject = window.arguments[0];
- document.addEventListener("DOMContentLoaded", () => {
- let dialogElement = document.getElementById("bridgeQr-dialog");
- dialogObject.init(window, dialogElement);
- });
- ]]>
- </script>
</dialog>
</window>
=====================================
browser/components/torpreferences/content/builtinBridgeDialog.mjs → browser/components/torpreferences/content/builtinBridgeDialog.js
=====================================
@@ -1,38 +1,32 @@
-import { TorStrings } from "resource://gre/modules/TorStrings.sys.mjs";
-
-import {
- TorSettings,
- TorBridgeSource,
-} from "resource://gre/modules/TorSettings.sys.mjs";
-
-import {
- TorConnect,
- TorConnectTopics,
-} from "resource://gre/modules/TorConnect.sys.mjs";
-
-export class BuiltinBridgeDialog {
- /**
- * Create a new instance.
- *
- * @param {Function} onSubmit - A callback for when the user accepts the
- * dialog selection.
- */
- constructor(onSubmit) {
- this.onSubmit = onSubmit;
- this._acceptButton = null;
- this._radioGroup = null;
- }
-
- _populateXUL(window, dialog) {
- const dialogWin = dialog.parentElement;
- dialogWin.setAttribute("title", TorStrings.settings.builtinBridgeHeader);
-
- dialog.querySelector(
- "#torPreferences-builtinBridge-description"
+"use strict";
+
+const { TorStrings } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorStrings.sys.mjs"
+);
+
+const { TorSettings, TorBridgeSource } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorSettings.sys.mjs"
+);
+
+const { TorConnect, TorConnectTopics } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorConnect.sys.mjs"
+);
+
+const gBuiltinBridgeDialog = {
+ init() {
+ this._result = window.arguments[0];
+
+ document.documentElement.setAttribute(
+ "title",
+ TorStrings.settings.builtinBridgeHeader
+ );
+
+ document.getElementById(
+ "torPreferences-builtinBridge-description"
).textContent = TorStrings.settings.builtinBridgeDescription2;
- this._radioGroup = dialog.querySelector(
- "#torPreferences-builtinBridge-typeSelection"
+ this._radioGroup = document.getElementById(
+ "torPreferences-builtinBridge-typeSelection"
);
const typeStrings = {
@@ -84,8 +78,12 @@ export class BuiltinBridgeDialog {
}
this._radioGroup.addEventListener("select", () => this.onSelectChange());
+
+ const dialog = document.getElementById(
+ "torPreferences-builtinBridge-dialog"
+ );
dialog.addEventListener("dialogaccept", () => {
- this.onSubmit(this._radioGroup.value, TorConnect.canBeginBootstrap);
+ this._result.accepted = true;
});
this._acceptButton = dialog.getButton("accept");
@@ -94,20 +92,28 @@ export class BuiltinBridgeDialog {
this.onSelectChange();
this.onAcceptStateChange();
- }
+ },
+
+ uninit() {
+ Services.obs.removeObserver(this, TorConnectTopics.StateChange);
+ },
onSelectChange() {
- this._acceptButton.disabled = !this._radioGroup.value;
- }
+ const value = this._radioGroup.value;
+ this._acceptButton.disabled = !value;
+ this._result.type = value;
+ },
onAcceptStateChange() {
+ const connect = TorConnect.canBeginBootstrap;
+ this._result.connect = connect;
this._acceptButton.setAttribute(
"label",
- TorConnect.canBeginBootstrap
+ connect
? TorStrings.settings.bridgeButtonConnect
: TorStrings.settings.bridgeButtonAccept
);
- }
+ },
observe(subject, topic, data) {
switch (topic) {
@@ -115,27 +121,20 @@ export class BuiltinBridgeDialog {
this.onAcceptStateChange();
break;
}
- }
-
- init(window, aDialog) {
- this._populateXUL(window, aDialog);
- }
-
- close() {
- // Unregister our observer topics.
- Services.obs.removeObserver(this, TorConnectTopics.StateChange);
- }
-
- openDialog(gSubDialog) {
- gSubDialog.open(
- "chrome://browser/content/torpreferences/builtinBridgeDialog.xhtml",
- {
- features: "resizable=yes",
- closingCallback: () => {
- this.close();
- },
+ },
+};
+
+window.addEventListener(
+ "DOMContentLoaded",
+ () => {
+ gBuiltinBridgeDialog.init();
+ window.addEventListener(
+ "unload",
+ () => {
+ gBuiltinBridgeDialog.uninit();
},
- this
+ { once: true }
);
- }
-}
+ },
+ { once: true }
+);
=====================================
browser/components/torpreferences/content/builtinBridgeDialog.xhtml
=====================================
@@ -9,6 +9,8 @@
xmlns:html="http://www.w3.org/1999/xhtml"
>
<dialog id="torPreferences-builtinBridge-dialog" buttons="accept,cancel">
+ <script src="chrome://browser/content/torpreferences/builtinBridgeDialog.js" />
+
<description id="torPreferences-builtinBridge-description"> </description>
<radiogroup id="torPreferences-builtinBridge-typeSelection">
<vbox class="builtin-bridges-option">
@@ -78,16 +80,5 @@
</html:div>
</vbox>
</radiogroup>
- <script type="application/javascript">
- <![CDATA[
- "use strict";
-
- let builtinBridgeDialog = window.arguments[0];
- document.addEventListener("DOMContentLoaded", () => {
- let dialog = document.getElementById("torPreferences-builtinBridge-dialog");
- builtinBridgeDialog.init(window, dialog);
- });
- ]]>
- </script>
</dialog>
</window>
=====================================
browser/components/torpreferences/content/connectionPane.js
=====================================
@@ -24,30 +24,6 @@ const { TorProviderBuilder, TorProviderTopics } = ChromeUtils.importESModule(
const { TorConnect, TorConnectTopics, TorConnectState, TorCensorshipLevel } =
ChromeUtils.importESModule("resource://gre/modules/TorConnect.sys.mjs");
-const { TorLogDialog } = ChromeUtils.importESModule(
- "chrome://browser/content/torpreferences/torLogDialog.mjs"
-);
-
-const { ConnectionSettingsDialog } = ChromeUtils.importESModule(
- "chrome://browser/content/torpreferences/connectionSettingsDialog.mjs"
-);
-
-const { BridgeQrDialog } = ChromeUtils.importESModule(
- "chrome://browser/content/torpreferences/bridgeQrDialog.mjs"
-);
-
-const { BuiltinBridgeDialog } = ChromeUtils.importESModule(
- "chrome://browser/content/torpreferences/builtinBridgeDialog.mjs"
-);
-
-const { RequestBridgeDialog } = ChromeUtils.importESModule(
- "chrome://browser/content/torpreferences/requestBridgeDialog.mjs"
-);
-
-const { ProvideBridgeDialog } = ChromeUtils.importESModule(
- "chrome://browser/content/torpreferences/provideBridgeDialog.mjs"
-);
-
const { MoatRPC } = ChromeUtils.importESModule(
"resource://gre/modules/Moat.sys.mjs"
);
@@ -114,6 +90,19 @@ async function getConnectedBridgeId() {
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
@@ -803,8 +792,7 @@ const gBridgeGrid = {
if (!bridgeLine) {
return;
}
- const dialog = new BridgeQrDialog();
- dialog.openDialog(gSubDialog, bridgeLine);
+ showBridgeQr(bridgeLine);
});
row.menu
.querySelector(".tor-bridges-options-copy-one-menu-item")
@@ -1571,8 +1559,7 @@ const gBridgeSettings = {
if (!this._canQRBridges) {
return;
}
- const dialog = new BridgeQrDialog();
- dialog.openDialog(gSubDialog, this._bridgeStrings);
+ showBridgeQr(this._bridgeStrings);
},
/**
@@ -2136,96 +2123,133 @@ const gConnectionPane = (function () {
},
/**
- * Save and apply settings, then optionally open about:torconnect and start
- * bootstrapping.
+ * Open a bridge dialog that will change the users bridges.
*
- * @param {fucntion} changes - The changes to make.
- * @param {boolean} connect - Whether to open about:torconnect and start
- * bootstrapping if possible.
+ * @param {string} url - The url of the dialog to open.
+ * @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.
*/
- async saveBridgeSettings(changes, connect) {
- // TODO: Move focus into the bridge area.
- // dialog.ownerGlobal.addEventListener("unload", () => gCurrentBridgesArea.takeFocus(), { once: true });
- // or use closedCallback in gSubDialog.open()
- setTorSettings(changes);
-
- if (!connect) {
- return;
- }
-
- // The bridge dialog button is "connect" when Tor is not bootstrapped,
- // so do the connect.
+ openBridgeDialog(url, 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;
+ }
- // Start Bootstrapping, which should use the configured bridges.
- // NOTE: We do this regardless of any previous TorConnect Error.
- if (TorConnect.canBeginBootstrap) {
- TorConnect.beginBootstrap();
- }
- // Open "about:torconnect".
- // FIXME: If there has been a previous bootstrapping error then
- // "about:torconnect" will be trying to get the user to use
- // AutoBootstrapping. It is not set up to handle a forced direct
- // entry to plain Bootstrapping from this dialog so the UI will not
- // be aligned. In particular the
- // AboutTorConnect.uiState.bootstrapCause will be aligned to
- // whatever was shown previously in "about:torconnect" instead.
- TorConnect.openTorConnect();
+ // Wait until the settings are applied before bootstrapping.
+ 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.
+ if (TorConnect.canBeginBootstrap) {
+ TorConnect.beginBootstrap();
+ }
+ // Open "about:torconnect".
+ // FIXME: If there has been a previous bootstrapping error then
+ // "about:torconnect" will be trying to get the user to use
+ // AutoBootstrapping. It is not set up to handle a forced direct
+ // entry to plain Bootstrapping from this dialog so the UI will
+ // not be aligned. In particular the
+ // AboutTorConnect.uiState.bootstrapCause will be aligned to
+ // whatever was shown previously in "about:torconnect" instead.
+ TorConnect.openTorConnect();
+ });
+ },
+ // 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(() => gCurrentBridgesArea.takeFocus());
+ },
+ },
+ result
+ );
},
onAddBuiltinBridge() {
- const builtinBridgeDialog = new BuiltinBridgeDialog(
- (bridgeType, connect) => {
- this.saveBridgeSettings(() => {
+ this.openBridgeDialog(
+ "chrome://browser/content/torpreferences/builtinBridgeDialog.xhtml",
+ result => {
+ if (!result.type) {
+ return null;
+ }
+ return setTorSettings(() => {
TorSettings.bridges.enabled = true;
TorSettings.bridges.source = TorBridgeSource.BuiltIn;
- TorSettings.bridges.builtin_type = bridgeType;
- }, connect);
+ TorSettings.bridges.builtin_type = result.type;
+ });
}
);
- builtinBridgeDialog.openDialog(gSubDialog);
},
// called when the request bridge button is activated
onRequestBridge() {
- const requestBridgeDialog = new RequestBridgeDialog(
- (aBridges, connect) => {
- if (!aBridges.length) {
- return;
+ this.openBridgeDialog(
+ "chrome://browser/content/torpreferences/requestBridgeDialog.xhtml",
+ result => {
+ if (!result.bridges?.length) {
+ return null;
}
-
- const bridgeStrings = aBridges.join("\n");
-
- this.saveBridgeSettings(() => {
+ return setTorSettings(() => {
TorSettings.bridges.enabled = true;
TorSettings.bridges.source = TorBridgeSource.BridgeDB;
- TorSettings.bridges.bridge_strings = bridgeStrings;
- }, connect);
+ TorSettings.bridges.bridge_strings = result.bridges.join("\n");
+ });
}
);
- requestBridgeDialog.openDialog(gSubDialog);
},
onAddBridgeManually() {
- const provideBridgeDialog = new ProvideBridgeDialog(
- (aBridgeString, connect) => {
- this.saveBridgeSettings(() => {
+ this.openBridgeDialog(
+ "chrome://browser/content/torpreferences/provideBridgeDialog.xhtml",
+ result => {
+ if (!result.bridgeStrings) {
+ return null;
+ }
+ return setTorSettings(() => {
TorSettings.bridges.enabled = true;
TorSettings.bridges.source = TorBridgeSource.UserProvided;
- TorSettings.bridges.bridge_strings = aBridgeString;
- }, connect);
+ TorSettings.bridges.bridge_strings = result.bridgeStrings;
+ });
}
);
- provideBridgeDialog.openDialog(gSubDialog);
},
onAdvancedSettings() {
- const connectionSettingsDialog = new ConnectionSettingsDialog();
- connectionSettingsDialog.openDialog(gSubDialog);
+ gSubDialog.open(
+ "chrome://browser/content/torpreferences/connectionSettingsDialog.xhtml",
+ { features: "resizable=yes" }
+ );
},
onViewTorLogs() {
- const torLogDialog = new TorLogDialog();
- torLogDialog.openDialog(gSubDialog);
+ gSubDialog.open(
+ "chrome://browser/content/torpreferences/torLogDialog.xhtml",
+ { features: "resizable=yes" }
+ );
},
};
return retval;
=====================================
browser/components/torpreferences/content/connectionSettingsDialog.mjs → browser/components/torpreferences/content/connectionSettingsDialog.js
=====================================
@@ -1,73 +1,67 @@
-import {
- TorSettings,
- TorProxyType,
-} from "resource://gre/modules/TorSettings.sys.mjs";
+"use strict";
-import { TorStrings } from "resource://gre/modules/TorStrings.sys.mjs";
+const { TorSettings, TorProxyType } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorSettings.sys.mjs"
+);
-export class ConnectionSettingsDialog {
- constructor() {
- this._dialog = null;
- this._useProxyCheckbox = null;
- this._proxyTypeLabel = null;
- this._proxyTypeMenulist = null;
- this._proxyAddressLabel = null;
- this._proxyAddressTextbox = null;
- this._proxyPortLabel = null;
- this._proxyPortTextbox = null;
- this._proxyUsernameLabel = null;
- this._proxyUsernameTextbox = null;
- this._proxyPasswordLabel = null;
- this._proxyPasswordTextbox = null;
- this._useFirewallCheckbox = null;
- this._allowedPortsLabel = null;
- this._allowedPortsTextbox = null;
- }
+const { TorStrings } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorStrings.sys.mjs"
+);
- static get selectors() {
- return {
- header: "#torPreferences-connection-header",
- useProxyCheckbox: "checkbox#torPreferences-connection-toggleProxy",
- proxyTypeLabel: "label#torPreferences-localProxy-type",
- proxyTypeList: "menulist#torPreferences-localProxy-builtinList",
- proxyAddressLabel: "label#torPreferences-localProxy-address",
- proxyAddressTextbox: "input#torPreferences-localProxy-textboxAddress",
- proxyPortLabel: "label#torPreferences-localProxy-port",
- proxyPortTextbox: "input#torPreferences-localProxy-textboxPort",
- proxyUsernameLabel: "label#torPreferences-localProxy-username",
- proxyUsernameTextbox: "input#torPreferences-localProxy-textboxUsername",
- proxyPasswordLabel: "label#torPreferences-localProxy-password",
- proxyPasswordTextbox: "input#torPreferences-localProxy-textboxPassword",
- useFirewallCheckbox: "checkbox#torPreferences-connection-toggleFirewall",
- firewallAllowedPortsLabel: "label#torPreferences-connection-allowedPorts",
- firewallAllowedPortsTextbox:
- "input#torPreferences-connection-textboxAllowedPorts",
- };
- }
+const gConnectionSettingsDialog = {
+ _useProxyCheckbox: null,
+ _proxyTypeLabel: null,
+ _proxyTypeMenulist: null,
+ _proxyAddressLabel: null,
+ _proxyAddressTextbox: null,
+ _proxyPortLabel: null,
+ _proxyPortTextbox: null,
+ _proxyUsernameLabel: null,
+ _proxyUsernameTextbox: null,
+ _proxyPasswordLabel: null,
+ _proxyPasswordTextbox: null,
+ _useFirewallCheckbox: null,
+ _allowedPortsLabel: null,
+ _allowedPortsTextbox: null,
+
+ selectors: {
+ header: "#torPreferences-connection-header",
+ useProxyCheckbox: "checkbox#torPreferences-connection-toggleProxy",
+ proxyTypeLabel: "label#torPreferences-localProxy-type",
+ proxyTypeList: "menulist#torPreferences-localProxy-builtinList",
+ proxyAddressLabel: "label#torPreferences-localProxy-address",
+ proxyAddressTextbox: "input#torPreferences-localProxy-textboxAddress",
+ proxyPortLabel: "label#torPreferences-localProxy-port",
+ proxyPortTextbox: "input#torPreferences-localProxy-textboxPort",
+ proxyUsernameLabel: "label#torPreferences-localProxy-username",
+ proxyUsernameTextbox: "input#torPreferences-localProxy-textboxUsername",
+ proxyPasswordLabel: "label#torPreferences-localProxy-password",
+ proxyPasswordTextbox: "input#torPreferences-localProxy-textboxPassword",
+ useFirewallCheckbox: "checkbox#torPreferences-connection-toggleFirewall",
+ firewallAllowedPortsLabel: "label#torPreferences-connection-allowedPorts",
+ firewallAllowedPortsTextbox:
+ "input#torPreferences-connection-textboxAllowedPorts",
+ },
// disables the provided list of elements
_setElementsDisabled(elements, disabled) {
for (let currentElement of elements) {
currentElement.disabled = disabled;
}
- }
+ },
- _populateXUL(window, aDialog) {
- const selectors = ConnectionSettingsDialog.selectors;
+ init() {
+ const selectors = this.selectors;
- this._dialog = aDialog;
- const dialogWin = this._dialog.parentElement;
- dialogWin.setAttribute(
+ document.documentElement.setAttribute(
"title",
TorStrings.settings.connectionSettingsDialogTitle
);
- this._dialog.querySelector(selectors.header).textContent =
+ document.querySelector(selectors.header).textContent =
TorStrings.settings.connectionSettingsDialogHeader;
// Local Proxy
- this._useProxyCheckbox = this._dialog.querySelector(
- selectors.useProxyCheckbox
- );
+ this._useProxyCheckbox = document.querySelector(selectors.useProxyCheckbox);
this._useProxyCheckbox.setAttribute(
"label",
TorStrings.settings.useLocalProxy
@@ -76,7 +70,7 @@ export class ConnectionSettingsDialog {
const checked = this._useProxyCheckbox.checked;
this.onToggleProxy(checked);
});
- this._proxyTypeLabel = this._dialog.querySelector(selectors.proxyTypeLabel);
+ this._proxyTypeLabel = document.querySelector(selectors.proxyTypeLabel);
this._proxyTypeLabel.setAttribute("value", TorStrings.settings.proxyType);
let mockProxies = [
@@ -90,9 +84,7 @@ export class ConnectionSettingsDialog {
},
{ value: TorProxyType.HTTPS, label: TorStrings.settings.proxyTypeHTTP },
];
- this._proxyTypeMenulist = this._dialog.querySelector(
- selectors.proxyTypeList
- );
+ this._proxyTypeMenulist = document.querySelector(selectors.proxyTypeList);
this._proxyTypeMenulist.addEventListener("command", e => {
const value = this._proxyTypeMenulist.value;
this.onSelectProxyType(value);
@@ -104,14 +96,14 @@ export class ConnectionSettingsDialog {
this._proxyTypeMenulist.querySelector("menupopup").appendChild(menuEntry);
}
- this._proxyAddressLabel = this._dialog.querySelector(
+ this._proxyAddressLabel = document.querySelector(
selectors.proxyAddressLabel
);
this._proxyAddressLabel.setAttribute(
"value",
TorStrings.settings.proxyAddress
);
- this._proxyAddressTextbox = this._dialog.querySelector(
+ this._proxyAddressTextbox = document.querySelector(
selectors.proxyAddressTextbox
);
this._proxyAddressTextbox.setAttribute(
@@ -129,33 +121,31 @@ export class ConnectionSettingsDialog {
}
}
});
- this._proxyPortLabel = this._dialog.querySelector(selectors.proxyPortLabel);
+ this._proxyPortLabel = document.querySelector(selectors.proxyPortLabel);
this._proxyPortLabel.setAttribute("value", TorStrings.settings.proxyPort);
- this._proxyPortTextbox = this._dialog.querySelector(
- selectors.proxyPortTextbox
- );
- this._proxyUsernameLabel = this._dialog.querySelector(
+ this._proxyPortTextbox = document.querySelector(selectors.proxyPortTextbox);
+ this._proxyUsernameLabel = document.querySelector(
selectors.proxyUsernameLabel
);
this._proxyUsernameLabel.setAttribute(
"value",
TorStrings.settings.proxyUsername
);
- this._proxyUsernameTextbox = this._dialog.querySelector(
+ this._proxyUsernameTextbox = document.querySelector(
selectors.proxyUsernameTextbox
);
this._proxyUsernameTextbox.setAttribute(
"placeholder",
TorStrings.settings.proxyUsernamePasswordPlaceholder
);
- this._proxyPasswordLabel = this._dialog.querySelector(
+ this._proxyPasswordLabel = document.querySelector(
selectors.proxyPasswordLabel
);
this._proxyPasswordLabel.setAttribute(
"value",
TorStrings.settings.proxyPassword
);
- this._proxyPasswordTextbox = this._dialog.querySelector(
+ this._proxyPasswordTextbox = document.querySelector(
selectors.proxyPasswordTextbox
);
this._proxyPasswordTextbox.setAttribute(
@@ -174,7 +164,7 @@ export class ConnectionSettingsDialog {
}
// Local firewall
- this._useFirewallCheckbox = this._dialog.querySelector(
+ this._useFirewallCheckbox = document.querySelector(
selectors.useFirewallCheckbox
);
this._useFirewallCheckbox.setAttribute(
@@ -185,14 +175,14 @@ export class ConnectionSettingsDialog {
const checked = this._useFirewallCheckbox.checked;
this.onToggleFirewall(checked);
});
- this._allowedPortsLabel = this._dialog.querySelector(
+ this._allowedPortsLabel = document.querySelector(
selectors.firewallAllowedPortsLabel
);
this._allowedPortsLabel.setAttribute(
"value",
TorStrings.settings.allowedPorts
);
- this._allowedPortsTextbox = this._dialog.querySelector(
+ this._allowedPortsTextbox = document.querySelector(
selectors.firewallAllowedPortsTextbox
);
this._allowedPortsTextbox.setAttribute(
@@ -207,10 +197,11 @@ export class ConnectionSettingsDialog {
TorSettings.firewall.allowed_ports.join(", ");
}
- this._dialog.addEventListener("dialogaccept", e => {
+ const dialog = document.getElementById("torPreferences-connection-dialog");
+ dialog.addEventListener("dialogaccept", e => {
this._applySettings();
});
- }
+ },
// callback when proxy is toggled
onToggleProxy(enabled) {
@@ -235,7 +226,7 @@ export class ConnectionSettingsDialog {
if (enabled) {
this.onSelectProxyType(this._proxyTypeMenulist.value);
}
- }
+ },
// callback when proxy type is changed
onSelectProxyType(value) {
@@ -308,7 +299,7 @@ export class ConnectionSettingsDialog {
break;
}
}
- }
+ },
// callback when firewall proxy is toggled
onToggleFirewall(enabled) {
@@ -319,7 +310,7 @@ export class ConnectionSettingsDialog {
[this._allowedPortsLabel, this._allowedPortsTextbox],
disabled
);
- }
+ },
// pushes settings from UI to tor
_applySettings() {
@@ -372,17 +363,13 @@ export class ConnectionSettingsDialog {
TorSettings.saveToPrefs();
TorSettings.applySettings();
- }
-
- init(window, aDialog) {
- this._populateXUL(window, aDialog);
- }
+ },
+};
- openDialog(gSubDialog) {
- gSubDialog.open(
- "chrome://browser/content/torpreferences/connectionSettingsDialog.xhtml",
- { features: "resizable=yes" },
- this
- );
- }
-}
+window.addEventListener(
+ "DOMContentLoaded",
+ () => {
+ gConnectionSettingsDialog.init();
+ },
+ { once: true }
+);
=====================================
browser/components/torpreferences/content/connectionSettingsDialog.xhtml
=====================================
@@ -9,6 +9,8 @@
xmlns:html="http://www.w3.org/1999/xhtml"
>
<dialog id="torPreferences-connection-dialog" buttons="accept,cancel">
+ <script src="chrome://browser/content/torpreferences/connectionSettingsDialog.js" />
+
<html:h3 id="torPreferences-connection-header">​</html:h3>
<!-- Local Proxy -->
<checkbox id="torPreferences-connection-toggleProxy" label="​" />
@@ -78,16 +80,5 @@
/>
</hbox>
</box>
- <script type="application/javascript">
- <![CDATA[
- "use strict";
-
- let connectionSettingsDialog = window.arguments[0];
- document.addEventListener("DOMContentLoaded", () => {
- let dialog = document.getElementById("torPreferences-connection-dialog");
- connectionSettingsDialog.init(window, dialog);
- });
- ]]>
- </script>
</dialog>
</window>
=====================================
browser/components/torpreferences/content/provideBridgeDialog.mjs → browser/components/torpreferences/content/provideBridgeDialog.js
=====================================
@@ -1,53 +1,44 @@
-import { TorStrings } from "resource://gre/modules/TorStrings.sys.mjs";
-
-import {
- TorSettings,
- TorBridgeSource,
-} from "resource://gre/modules/TorSettings.sys.mjs";
-
-import {
- TorConnect,
- TorConnectTopics,
-} from "resource://gre/modules/TorConnect.sys.mjs";
-
-export class ProvideBridgeDialog {
- constructor(onSubmit) {
- this.onSubmit = onSubmit;
- this._dialog = null;
- this._textarea = null;
- this._acceptButton = null;
- }
-
- static get selectors() {
- return {
- description: "#torPreferences-provideBridge-description",
- textarea: "#torPreferences-provideBridge-textarea",
- };
- }
-
- _populateXUL(window, aDialog) {
- const selectors = ProvideBridgeDialog.selectors;
-
- const openHelp = () => {
+"use strict";
+
+const { TorStrings } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorStrings.sys.mjs"
+);
+
+const { TorSettings, TorBridgeSource } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorSettings.sys.mjs"
+);
+
+const { TorConnect, TorConnectTopics } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorConnect.sys.mjs"
+);
+
+const gProvideBridgeDialog = {
+ init() {
+ this._result = window.arguments[0];
+
+ document.documentElement.setAttribute(
+ "title",
+ TorStrings.settings.provideBridgeTitleAdd
+ );
+ const learnMore = document.createXULElement("label");
+ learnMore.className = "learnMore text-link";
+ learnMore.setAttribute("is", "text-link");
+ learnMore.setAttribute("value", TorStrings.settings.learnMore);
+ learnMore.addEventListener("click", () => {
window.top.openTrustedLinkIn(
TorStrings.settings.learnMoreBridgesURL,
"tab"
);
- };
+ });
- this._dialog = aDialog;
- const dialogWin = this._dialog.parentElement;
- dialogWin.setAttribute("title", TorStrings.settings.provideBridgeTitleAdd);
- const learnMore = window.document.createXULElement("label");
- learnMore.className = "learnMore text-link";
- learnMore.setAttribute("is", "text-link");
- learnMore.setAttribute("value", TorStrings.settings.learnMore);
- learnMore.addEventListener("click", openHelp);
- const descr = this._dialog.querySelector(selectors.description);
- descr.textContent = "";
const pieces = TorStrings.settings.provideBridgeDescription.split("%S");
- descr.append(pieces[0], learnMore, pieces[1] || "");
- this._textarea = this._dialog.querySelector(selectors.textarea);
+ document
+ .getElementById("torPreferences-provideBridge-description")
+ .replaceChildren(pieces[0], learnMore, pieces[1] || "");
+
+ this._textarea = document.getElementById(
+ "torPreferences-provideBridge-textarea"
+ );
this._textarea.setAttribute(
"placeholder",
TorStrings.settings.provideBridgePlaceholder
@@ -58,32 +49,44 @@ export class ProvideBridgeDialog {
this._textarea.value = TorSettings.bridges.bridge_strings.join("\n");
}
- this._dialog.addEventListener("dialogaccept", e => {
- this.onSubmit(this._textarea.value, TorConnect.canBeginBootstrap);
+ const dialog = document.getElementById(
+ "torPreferences-provideBridge-dialog"
+ );
+ dialog.addEventListener("dialogaccept", e => {
+ this._result.accepted = true;
});
- this._acceptButton = this._dialog.getButton("accept");
+ this._acceptButton = dialog.getButton("accept");
Services.obs.addObserver(this, TorConnectTopics.StateChange);
this.onValueChange();
this.onAcceptStateChange();
- }
+ },
+
+ uninit() {
+ Services.obs.removeObserver(this, TorConnectTopics.StateChange);
+ },
onValueChange() {
// TODO: Do some proper value parsing and error reporting. See
// tor-browser#40552.
- this._acceptButton.disabled = !this._textarea.value.trim();
- }
+ const value = this._textarea.value.trim();
+ this._acceptButton.disabled = !value;
+ this._result.bridgeStrings = value;
+ },
onAcceptStateChange() {
+ const connect = TorConnect.canBeginBootstrap;
+ this._result.connect = connect;
+
this._acceptButton.setAttribute(
"label",
- TorConnect.canBeginBootstrap
+ connect
? TorStrings.settings.bridgeButtonConnect
: TorStrings.settings.bridgeButtonAccept
);
- }
+ },
observe(subject, topic, data) {
switch (topic) {
@@ -91,27 +94,20 @@ export class ProvideBridgeDialog {
this.onAcceptStateChange();
break;
}
- }
-
- init(window, aDialog) {
- this._populateXUL(window, aDialog);
- }
-
- close() {
- // Unregister our observer topics.
- Services.obs.removeObserver(this, TorConnectTopics.StateChange);
- }
-
- openDialog(gSubDialog) {
- gSubDialog.open(
- "chrome://browser/content/torpreferences/provideBridgeDialog.xhtml",
- {
- features: "resizable=yes",
- closingCallback: () => {
- this.close();
- },
+ },
+};
+
+window.addEventListener(
+ "DOMContentLoaded",
+ () => {
+ gProvideBridgeDialog.init();
+ window.addEventListener(
+ "unload",
+ () => {
+ gProvideBridgeDialog.uninit();
},
- this
+ { once: true }
);
- }
-}
+ },
+ { once: true }
+);
=====================================
browser/components/torpreferences/content/provideBridgeDialog.xhtml
=====================================
@@ -9,6 +9,8 @@
xmlns:html="http://www.w3.org/1999/xhtml"
>
<dialog id="torPreferences-provideBridge-dialog" buttons="accept,cancel">
+ <script src="chrome://browser/content/torpreferences/provideBridgeDialog.js" />
+
<description>
<html:div id="torPreferences-provideBridge-description"
>​<br />​</html:div
@@ -19,16 +21,5 @@
multiline="true"
rows="3"
/>
- <script type="application/javascript">
- <![CDATA[
- "use strict";
-
- let provideBridgeDialog = window.arguments[0];
- document.addEventListener("DOMContentLoaded", () => {
- let dialog = document.getElementById("torPreferences-provideBridge-dialog");
- provideBridgeDialog.init(window, dialog);
- });
- ]]>
- </script>
</dialog>
</window>
=====================================
browser/components/torpreferences/content/requestBridgeDialog.mjs → browser/components/torpreferences/content/requestBridgeDialog.js
=====================================
@@ -1,44 +1,39 @@
-import { BridgeDB } from "resource://gre/modules/BridgeDB.sys.mjs";
-import { TorStrings } from "resource://gre/modules/TorStrings.sys.mjs";
-
-import {
- TorConnect,
- TorConnectTopics,
-} from "resource://gre/modules/TorConnect.sys.mjs";
-
-export class RequestBridgeDialog {
- constructor(onSubmit) {
- this.onSubmit = onSubmit;
- this._dialog = null;
- this._submitButton = null;
- this._dialogHeader = null;
- this._captchaImage = null;
- this._captchaEntryTextbox = null;
- this._captchaRefreshButton = null;
- this._incorrectCaptchaHbox = null;
- this._incorrectCaptchaLabel = null;
- }
-
- static get selectors() {
- return {
- dialogHeader: "h3#torPreferences-requestBridge-header",
- captchaImage: "image#torPreferences-requestBridge-captchaImage",
- captchaEntryTextbox: "input#torPreferences-requestBridge-captchaTextbox",
- refreshCaptchaButton:
- "button#torPreferences-requestBridge-refreshCaptchaButton",
- incorrectCaptchaHbox:
- "hbox#torPreferences-requestBridge-incorrectCaptchaHbox",
- incorrectCaptchaLabel:
- "label#torPreferences-requestBridge-incorrectCaptchaError",
- };
- }
-
- _populateXUL(window, dialog) {
- const selectors = RequestBridgeDialog.selectors;
+"use strict";
+
+const { BridgeDB } = ChromeUtils.importESModule(
+ "resource://gre/modules/BridgeDB.sys.mjs"
+);
+const { TorStrings } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorStrings.sys.mjs"
+);
+
+const { TorConnect, TorConnectTopics } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorConnect.sys.mjs"
+);
+
+const gRequestBridgeDialog = {
+ selectors: {
+ dialogHeader: "h3#torPreferences-requestBridge-header",
+ captchaImage: "image#torPreferences-requestBridge-captchaImage",
+ captchaEntryTextbox: "input#torPreferences-requestBridge-captchaTextbox",
+ refreshCaptchaButton:
+ "button#torPreferences-requestBridge-refreshCaptchaButton",
+ incorrectCaptchaHbox:
+ "hbox#torPreferences-requestBridge-incorrectCaptchaHbox",
+ incorrectCaptchaLabel:
+ "label#torPreferences-requestBridge-incorrectCaptchaError",
+ },
+
+ init() {
+ this._result = window.arguments[0];
+
+ const selectors = this.selectors;
+
+ this._dialog = document.getElementById(
+ "torPreferences-requestBridge-dialog"
+ );
- this._dialog = dialog;
- const dialogWin = dialog.parentElement;
- dialogWin.setAttribute(
+ document.documentElement.setAttribute(
"title",
TorStrings.settings.requestBridgeDialogTitle
);
@@ -104,16 +99,24 @@ export class RequestBridgeDialog {
Services.obs.addObserver(this, TorConnectTopics.StateChange);
this.onAcceptStateChange();
- }
+ },
+
+ uninit() {
+ BridgeDB.close();
+ // Unregister our observer topics.
+ Services.obs.removeObserver(this, TorConnectTopics.StateChange);
+ },
onAcceptStateChange() {
+ const connect = TorConnect.canBeginBootstrap;
+ this._result.connect = connect;
this._submitButton.setAttribute(
"label",
- TorConnect.canBeginBootstrap
+ connect
? TorStrings.settings.bridgeButtonConnect
: TorStrings.settings.submitCaptcha
);
- }
+ },
observe(subject, topic, data) {
switch (topic) {
@@ -121,7 +124,7 @@ export class RequestBridgeDialog {
this.onAcceptStateChange();
break;
}
- }
+ },
_setcaptchaImage(uri) {
if (uri != this._captchaImage.src) {
@@ -131,27 +134,17 @@ export class RequestBridgeDialog {
this._captchaEntryTextbox.focus();
this._captchaEntryTextbox.select();
}
- }
+ },
_setUIDisabled(disabled) {
this._submitButton.disabled = this._captchaGuessIsEmpty() || disabled;
this._captchaEntryTextbox.disabled = disabled;
this._captchaRefreshButton.disabled = disabled;
- }
+ },
_captchaGuessIsEmpty() {
return this._captchaEntryTextbox.value == "";
- }
-
- init(window, dialog) {
- this._populateXUL(window, dialog);
- }
-
- close() {
- BridgeDB.close();
- // Unregister our observer topics.
- Services.obs.removeObserver(this, TorConnectTopics.StateChange);
- }
+ },
/*
Event Handlers
@@ -169,8 +162,9 @@ export class RequestBridgeDialog {
BridgeDB.submitCaptchaGuess(captchaText)
.then(aBridges => {
- if (aBridges) {
- this.onSubmit(aBridges, TorConnect.canBeginBootstrap);
+ if (aBridges && aBridges.length) {
+ this._result.accepted = true;
+ this._result.bridges = aBridges;
this._submitButton.disabled = false;
// This was successful, but use cancelDialog() to close, since
// we intercept the `dialogaccept` event.
@@ -186,7 +180,7 @@ export class RequestBridgeDialog {
this._incorrectCaptchaHbox.style.visibility = "visible";
console.log(aError);
});
- }
+ },
onRefreshCaptcha() {
this._setUIDisabled(true);
@@ -198,18 +192,20 @@ export class RequestBridgeDialog {
BridgeDB.requestNewCaptchaImage().then(uri => {
this._setcaptchaImage(uri);
});
- }
-
- openDialog(gSubDialog) {
- gSubDialog.open(
- "chrome://browser/content/torpreferences/requestBridgeDialog.xhtml",
- {
- features: "resizable=yes",
- closingCallback: () => {
- this.close();
- },
+ },
+};
+
+window.addEventListener(
+ "DOMContentLoaded",
+ () => {
+ gRequestBridgeDialog.init();
+ window.addEventListener(
+ "unload",
+ () => {
+ gRequestBridgeDialog.uninit();
},
- this
+ { once: true }
);
- }
-}
+ },
+ { once: true }
+);
=====================================
browser/components/torpreferences/content/requestBridgeDialog.xhtml
=====================================
@@ -9,6 +9,8 @@
xmlns:html="http://www.w3.org/1999/xhtml"
>
<dialog id="torPreferences-requestBridge-dialog" buttons="accept,cancel">
+ <script src="chrome://browser/content/torpreferences/requestBridgeDialog.js" />
+
<!-- ok, so ​ is a zero-width space. We need to have *something* in the innerText so that XUL knows how tall the
title node is so that it can determine how large to make the dialog element's inner draw area. If we have nothing
in the innerText, then it collapse to 0 height, and the contents of the dialog ends up partially hidden >:( -->
@@ -30,16 +32,5 @@
<image id="torPreferences-requestBridge-errorIcon" />
<label id="torPreferences-requestBridge-incorrectCaptchaError" flex="1" />
</hbox>
- <script type="application/javascript">
- <![CDATA[
- "use strict";
-
- let requestBridgeDialog = window.arguments[0];
- document.addEventListener("DOMContentLoaded", () => {
- let dialog = document.getElementById("torPreferences-requestBridge-dialog");
- requestBridgeDialog.init(window, dialog);
- });
- ]]>
- </script>
</dialog>
</window>
=====================================
browser/components/torpreferences/content/torLogDialog.js
=====================================
@@ -0,0 +1,62 @@
+"use strict";
+
+const { setTimeout, clearTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+const { TorProviderBuilder } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorProviderBuilder.sys.mjs"
+);
+const { TorStrings } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorStrings.sys.mjs"
+);
+
+window.addEventListener(
+ "DOMContentLoaded",
+ () => {
+ document.documentElement.setAttribute(
+ "title",
+ TorStrings.settings.torLogDialogTitle
+ );
+
+ const dialog = document.getElementById("torPreferences-torLog-dialog");
+ const copyLogButton = dialog.getButton("extra1");
+ copyLogButton.setAttribute("label", TorStrings.settings.copyLog);
+
+ const logText = document.getElementById(
+ "torPreferences-torDialog-textarea"
+ );
+
+ let restoreButtonTimeout = null;
+ copyLogButton.addEventListener("command", () => {
+ // Copy tor log messages to the system clipboard.
+ let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
+ Ci.nsIClipboardHelper
+ );
+ clipboard.copyString(logText.value);
+
+ const label = copyLogButton.querySelector("label");
+ label.setAttribute("value", TorStrings.settings.copied);
+ copyLogButton.classList.add("primary");
+
+ const RESTORE_TIME = 1200;
+ if (restoreButtonTimeout !== null) {
+ clearTimeout(restoreButtonTimeout);
+ }
+ restoreButtonTimeout = setTimeout(() => {
+ label.setAttribute("value", TorStrings.settings.copyLog);
+ copyLogButton.classList.remove("primary");
+ restoreButtonTimeout = null;
+ }, RESTORE_TIME);
+ });
+
+ // A waiting state should not be needed at this point.
+ // Also, we probably cannot even arrive here if the provider failed to
+ // initialize, otherwise we could use a try/catch, and write the exception
+ // text in the logs, instead.
+ TorProviderBuilder.build().then(
+ provider => (logText.value = provider.getLog())
+ );
+ },
+ { once: true }
+);
=====================================
browser/components/torpreferences/content/torLogDialog.mjs deleted
=====================================
@@ -1,78 +0,0 @@
-import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";
-
-import { TorProviderBuilder } from "resource://gre/modules/TorProviderBuilder.sys.mjs";
-import { TorStrings } from "resource://gre/modules/TorStrings.sys.mjs";
-
-export class TorLogDialog {
- constructor() {
- this._dialog = null;
- this._logTextarea = null;
- this._copyLogButton = null;
- this._restoreButtonTimeout = null;
- }
-
- static get selectors() {
- return {
- copyLogButton: "extra1",
- logTextarea: "textarea#torPreferences-torDialog-textarea",
- };
- }
-
- async _populateXUL(aDialog) {
- this._dialog = aDialog;
- const dialogWin = this._dialog.parentElement;
- dialogWin.setAttribute("title", TorStrings.settings.torLogDialogTitle);
-
- this._logTextarea = this._dialog.querySelector(
- TorLogDialog.selectors.logTextarea
- );
-
- this._copyLogButton = this._dialog.getButton(
- TorLogDialog.selectors.copyLogButton
- );
- this._copyLogButton.setAttribute("label", TorStrings.settings.copyLog);
- this._copyLogButton.addEventListener("command", () => {
- this.copyTorLog();
- const label = this._copyLogButton.querySelector("label");
- label.setAttribute("value", TorStrings.settings.copied);
- this._copyLogButton.classList.add("primary");
-
- const RESTORE_TIME = 1200;
- if (this._restoreButtonTimeout !== null) {
- clearTimeout(this._restoreButtonTimeout);
- }
- this._restoreButtonTimeout = setTimeout(() => {
- label.setAttribute("value", TorStrings.settings.copyLog);
- this._copyLogButton.classList.remove("primary");
- this._restoreButtonTimeout = null;
- }, RESTORE_TIME);
- });
-
- // A waiting state should not be needed at this point.
- // Also, we probably cannot even arrive here if the provider failed to
- // initialize, otherwise we could use a try/catch, and write the exception
- // text in the logs, instead.
- const provider = await TorProviderBuilder.build();
- this._logTextarea.value = provider.getLog();
- }
-
- init(window, aDialog) {
- this._populateXUL(aDialog);
- }
-
- copyTorLog() {
- // Copy tor log messages to the system clipboard.
- let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
- Ci.nsIClipboardHelper
- );
- clipboard.copyString(this._logTextarea.value);
- }
-
- openDialog(gSubDialog) {
- gSubDialog.open(
- "chrome://browser/content/torpreferences/torLogDialog.xhtml",
- { features: "resizable=yes" },
- this
- );
- }
-}
=====================================
browser/components/torpreferences/content/torLogDialog.xhtml
=====================================
@@ -9,21 +9,12 @@
xmlns:html="http://www.w3.org/1999/xhtml"
>
<dialog id="torPreferences-torLog-dialog" buttons="accept,extra1">
+ <script src="chrome://browser/content/torpreferences/torLogDialog.js" />
+
<html:textarea
id="torPreferences-torDialog-textarea"
multiline="true"
readonly="true"
/>
- <script type="application/javascript">
- <![CDATA[
- "use strict";
-
- let torLogDialog = window.arguments[0];
- document.addEventListener("DOMContentLoaded", () => {
- let dialog = document.getElementById("torPreferences-torLog-dialog");
- torLogDialog.init(window, dialog);
- });
- ]]>
- </script>
</dialog>
</window>
=====================================
browser/components/torpreferences/jar.mn
=====================================
@@ -2,19 +2,19 @@ browser.jar:
content/browser/torpreferences/bridge.svg (content/bridge.svg)
content/browser/torpreferences/bridge-qr.svg (content/bridge-qr.svg)
content/browser/torpreferences/bridgeQrDialog.xhtml (content/bridgeQrDialog.xhtml)
- content/browser/torpreferences/bridgeQrDialog.mjs (content/bridgeQrDialog.mjs)
+ content/browser/torpreferences/bridgeQrDialog.js (content/bridgeQrDialog.js)
content/browser/torpreferences/builtinBridgeDialog.xhtml (content/builtinBridgeDialog.xhtml)
- content/browser/torpreferences/builtinBridgeDialog.mjs (content/builtinBridgeDialog.mjs)
+ content/browser/torpreferences/builtinBridgeDialog.js (content/builtinBridgeDialog.js)
content/browser/torpreferences/connectionSettingsDialog.xhtml (content/connectionSettingsDialog.xhtml)
- content/browser/torpreferences/connectionSettingsDialog.mjs (content/connectionSettingsDialog.mjs)
+ content/browser/torpreferences/connectionSettingsDialog.js (content/connectionSettingsDialog.js)
content/browser/torpreferences/network.svg (content/network.svg)
content/browser/torpreferences/network-broken.svg (content/network-broken.svg)
content/browser/torpreferences/provideBridgeDialog.xhtml (content/provideBridgeDialog.xhtml)
- content/browser/torpreferences/provideBridgeDialog.mjs (content/provideBridgeDialog.mjs)
+ content/browser/torpreferences/provideBridgeDialog.js (content/provideBridgeDialog.js)
content/browser/torpreferences/requestBridgeDialog.xhtml (content/requestBridgeDialog.xhtml)
- content/browser/torpreferences/requestBridgeDialog.mjs (content/requestBridgeDialog.mjs)
+ content/browser/torpreferences/requestBridgeDialog.js (content/requestBridgeDialog.js)
content/browser/torpreferences/connectionCategory.inc.xhtml (content/connectionCategory.inc.xhtml)
- content/browser/torpreferences/torLogDialog.mjs (content/torLogDialog.mjs)
+ content/browser/torpreferences/torLogDialog.js (content/torLogDialog.js)
content/browser/torpreferences/torLogDialog.xhtml (content/torLogDialog.xhtml)
content/browser/torpreferences/connectionPane.js (content/connectionPane.js)
content/browser/torpreferences/connectionPane.xhtml (content/connectionPane.xhtml)
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/6211ed8…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/6211ed8…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-115.7.0esr-13.5-1] 5 commits: fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in...
by richard (@richard) 23 Jan '24
by richard (@richard) 23 Jan '24
23 Jan '24
richard pushed to branch tor-browser-115.7.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits:
39bde3a7 by Henry Wilkes at 2024-01-23T17:30:24+00:00
fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
Bug 42036: Remove old bridge cards.
- - - - -
8fee6e00 by Henry Wilkes at 2024-01-23T17:30:25+00:00
fixup! Add TorStrings module for localization
Bug 42036: Remove old bridge cards.
- - - - -
dc354cb3 by Henry Wilkes at 2024-01-23T17:30:25+00:00
fixup! Bug 40597: Implement TorSettings module
Bug 42036: Add syncSettings to TorSettings and improve error reporting.
- - - - -
0b412130 by Henry Wilkes at 2024-01-23T17:30:26+00:00
fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
Bug 42036: Implement new bridge settings UI, ready for Lox.
- - - - -
02cf2743 by Henry Wilkes at 2024-01-23T17:30:26+00:00
fixup! Tor Browser strings
Bug 42036: New bridge UI strings.
- - - - -
12 changed files:
- + browser/components/torpreferences/content/bridge-qr.svg
- + browser/components/torpreferences/content/bridge.svg
- browser/components/torpreferences/content/builtinBridgeDialog.mjs
- browser/components/torpreferences/content/builtinBridgeDialog.xhtml
- browser/components/torpreferences/content/connectionPane.js
- browser/components/torpreferences/content/connectionPane.xhtml
- browser/components/torpreferences/content/torPreferences.css
- browser/components/torpreferences/jar.mn
- browser/locales/en-US/browser/tor-browser.ftl
- toolkit/modules/TorSettings.sys.mjs
- toolkit/modules/TorStrings.sys.mjs
- toolkit/torbutton/chrome/locale/en-US/settings.properties
Changes:
=====================================
browser/components/torpreferences/content/bridge-qr.svg
=====================================
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="context-fill" xmlns="http://www.w3.org/2000/svg">
+ <path d="M5.5 3C4.83696 3 4.20107 3.26339 3.73223 3.73223C3.26339 4.20107 3 4.83696 3 5.5V9H4.75V5.5C4.75 5.30109 4.82902 5.11032 4.96967 4.96967C5.11032 4.82902 5.30109 4.75 5.5 4.75H9V3H5.5ZM11 10.25C11 10.4489 10.921 10.6397 10.7803 10.7803C10.6397 10.921 10.4489 11 10.25 11H7.75C7.55109 11 7.36032 10.921 7.21967 10.7803C7.07902 10.6397 7 10.4489 7 10.25V7.75C7 7.55109 7.07902 7.36032 7.21967 7.21967C7.36032 7.07902 7.55109 7 7.75 7H10.25C10.4489 7 10.6397 7.07902 10.7803 7.21967C10.921 7.36032 11 7.55109 11 7.75V10.25ZM17 15H15V13H17V11H15V9H17V7H15V9H13V11H15V13H13V15H11V13H9V15H7V17H9V15H11V17H13V15H15V17H17V15ZM3 18.5V15H4.75V18.5C4.75 18.914 5.086 19.25 5.5 19.25H9V21H5.5C4.83696 21 4.20107 20.7366 3.73223 20.2678C3.26339 19.7989 3 19.163 3 18.5ZM15 3H18.5C19.163 3 19.7989 3.26339 20.2678 3.73223C20.7366 4.20107 21 4.83696 21 5.5V9H19.25V5.5C19.25 5.30109 19.171 5.11032 19.0303 4.96967C18.8897 4.82902 18.6989 4.75 18.5 4.75H15V3ZM21 18.5V15H19.25V18.5C19.25 18.6989 19.171 18.8897 19.0303 19.0303C18.8897 19.171 18.6989 19.25 18.5 19.25H15V21H18.5C19.163 21 19.7989 20.7366 20.2678 20.2678C20.7366 19.7989 21 19.163 21 18.5Z"/>
+</svg>
=====================================
browser/components/torpreferences/content/bridge.svg
=====================================
@@ -0,0 +1,5 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="context-fill" xmlns="http://www.w3.org/2000/svg">
+ <path d="M15.5 11.5C15.5 7.35786 12.1421 4 8 4C3.85786 4 0.5 7.35786 0.5 11.5V12.7461H1.67188V11.5C1.67188 8.00507 4.50507 5.17188 8 5.17188C11.4949 5.17188 14.3281 8.00507 14.3281 11.5V12.7461H15.5V11.5Z"/>
+ <path d="M13.25 11.5C13.25 8.6005 10.8995 6.24999 7.99999 6.24999C5.1005 6.24999 2.74999 8.6005 2.74999 11.5L2.74989 12.7461H3.92177L3.92187 11.5C3.92187 9.24771 5.74771 7.42187 7.99999 7.42187C10.2523 7.42187 12.0781 9.24771 12.0781 11.5L12.0782 12.7461H13.2501L13.25 11.5Z"/>
+ <path d="M8 8.5C9.65686 8.5 11 9.84315 11 11.5L11.0002 12.7461H9.82836L9.82813 11.5C9.82813 10.4904 9.00965 9.67188 8 9.67188C6.99036 9.67188 6.17188 10.4904 6.17188 11.5L6.17164 12.7461H4.99977V11.5C4.99977 9.84315 6.34315 8.5 8 8.5Z"/>
+</svg>
=====================================
browser/components/torpreferences/content/builtinBridgeDialog.mjs
=====================================
@@ -69,10 +69,12 @@ export class BuiltinBridgeDialog {
optionEl.querySelector(
".torPreferences-current-bridge-label"
).textContent = TorStrings.settings.currentBridge;
- optionEl.classList.toggle(
- "current-builtin-bridge-type",
- type === currentBuiltinType
- );
+ optionEl
+ .querySelector(".bridge-status-badge")
+ .classList.toggle(
+ "bridge-status-current-built-in",
+ type === currentBuiltinType
+ );
}
if (currentBuiltinType) {
=====================================
browser/components/torpreferences/content/builtinBridgeDialog.xhtml
=====================================
@@ -20,8 +20,8 @@
aria-describedby="obfs-bridges-current obfs-bridges-description"
value="obfs4"
/>
- <html:span class="torPreferences-current-bridge-badge">
- <image class="torPreferences-current-bridge-icon" />
+ <html:span class="bridge-status-badge">
+ <html:div class="bridge-status-icon"></html:div>
<html:span
id="obfs-bridges-current"
class="torPreferences-current-bridge-label"
@@ -41,8 +41,8 @@
aria-describedby="snowflake-bridges-current snowflake-bridges-description"
value="snowflake"
/>
- <html:span class="torPreferences-current-bridge-badge">
- <image class="torPreferences-current-bridge-icon" />
+ <html:span class="bridge-status-badge">
+ <html:div class="bridge-status-icon"></html:div>
<html:span
id="snowflake-bridges-current"
class="torPreferences-current-bridge-label"
@@ -62,8 +62,8 @@
aria-describedby="meek-bridges-current meek-bridges-description"
value="meek-azure"
/>
- <html:span class="torPreferences-current-bridge-badge">
- <image class="torPreferences-current-bridge-icon" />
+ <html:span class="bridge-status-badge">
+ <html:div class="bridge-status-icon"></html:div>
<html:span
id="meek-bridges-current"
class="torPreferences-current-bridge-label"
=====================================
browser/components/torpreferences/content/connectionPane.js
=====================================
@@ -66,6 +66,1642 @@ const InternetStatus = Object.freeze({
Offline: -1,
});
+/**
+ * Make changes to TorSettings and save them.
+ *
+ * Bulk changes will be frozen together.
+ *
+ * @param {Function} changes - Method to apply changes to TorSettings.
+ */
+async function setTorSettings(changes) {
+ if (!TorSettings.initialized) {
+ console.warning("Ignoring changes to uninitialized TorSettings");
+ return;
+ }
+ TorSettings.freezeNotifications();
+ try {
+ changes();
+ // This will trigger TorSettings.#cleanupSettings()
+ TorSettings.saveToPrefs();
+ try {
+ // May throw.
+ await TorSettings.applySettings();
+ } catch (e) {
+ console.error("Failed to save Tor settings", e);
+ }
+ } finally {
+ TorSettings.thawNotifications();
+ }
+}
+
+/**
+ * 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;
+}
+
+// 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} EmojiCell
+ *
+ * @property {Element} cell - The grid cell element.
+ * @property {Element} img - The grid cell icon.
+ * @property {Element} index - The emoji index.
+ */
+ /**
+ * @typedef {object} BridgeGridRow
+ *
+ * @property {Element} element - The row element.
+ * @property {Element} optionsButton - The options button.
+ * @property {EmojiCell[]} emojis - The emoji cells.
+ * @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, "intl:app-locales-changed");
+ Services.obs.addObserver(this, TorProviderTopics.BridgeChanged);
+
+ this._grid.classList.add("grid-active");
+
+ this._updateEmojiLangCode();
+ this._updateConnectedBridge();
+ },
+
+ /**
+ * Deactivate and hide the bridge grid.
+ */
+ deactivate() {
+ if (!this._active) {
+ return;
+ }
+
+ this._active = false;
+
+ this._forceCloseRowMenus();
+
+ this._grid.classList.remove("grid-active");
+
+ Services.obs.removeObserver(this, "intl:app-locales-changed");
+ Services.obs.removeObserver(this, TorProviderTopics.BridgeChanged);
+ },
+
+ observe(subject, topic, data) {
+ switch (topic) {
+ case TorSettingsTopics.SettingsChanged:
+ const { changes } = subject.wrappedJSObject;
+ if (
+ changes.includes("bridges.source") ||
+ changes.includes("bridges.bridge_strings")
+ ) {
+ this._updateRows();
+ }
+ break;
+ case "intl:app-locales-changed":
+ this._updateEmojiLangCode();
+ 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");
+ }
+ },
+
+ /**
+ * The language code for emoji annotations.
+ *
+ * null if unset.
+ *
+ * @type {string?}
+ */
+ _emojiLangCode: null,
+ /**
+ * A promise that resolves to two JSON structures for bridge-emojis.json and
+ * annotations.json, respectively.
+ *
+ * @type {Promise}
+ */
+ _emojiPromise: Promise.all([
+ fetch(
+ "chrome://browser/content/torpreferences/bridgemoji/bridge-emojis.json"
+ ).then(response => response.json()),
+ fetch(
+ "chrome://browser/content/torpreferences/bridgemoji/annotations.json"
+ ).then(response => response.json()),
+ ]),
+
+ /**
+ * Update _emojiLangCode.
+ */
+ async _updateEmojiLangCode() {
+ let langCode;
+ const emojiAnnotations = (await this._emojiPromise)[1];
+ // Find the first desired locale we have annotations for.
+ // Add "en" as a fallback.
+ for (const bcp47 of [...Services.locale.appLocalesAsBCP47, "en"]) {
+ langCode = bcp47;
+ if (langCode in emojiAnnotations) {
+ break;
+ }
+ // Remove everything after the dash, if there is one.
+ langCode = bcp47.replace(/-.*/, "");
+ if (langCode in emojiAnnotations) {
+ break;
+ }
+ }
+ if (langCode !== this._emojiLangCode) {
+ this._emojiLangCode = langCode;
+ for (const row of this._rows) {
+ this._updateRowEmojis(row);
+ }
+ }
+ },
+
+ /**
+ * Update the bridge emojis to show their corresponding emoji with an
+ * annotation that matches the current locale.
+ *
+ * @param {BridgeGridRow} row - The row to update the emojis of.
+ */
+ async _updateRowEmojis(row) {
+ if (!this._emojiLangCode) {
+ // No lang code yet, wait until it is updated.
+ return;
+ }
+
+ const [emojiList, emojiAnnotations] = await this._emojiPromise;
+ const unknownString = await document.l10n.formatValue(
+ "tor-bridges-emoji-unknown"
+ );
+
+ for (const { cell, img, index } of row.emojis) {
+ const emoji = emojiList[index];
+ let emojiName;
+ if (!emoji) {
+ // Unexpected.
+ img.removeAttribute("src");
+ } else {
+ const cp = emoji.codePointAt(0).toString(16);
+ img.setAttribute(
+ "src",
+ `chrome://browser/content/torpreferences/bridgemoji/svgs/${cp}.svg`
+ );
+ emojiName = emojiAnnotations[this._emojiLangCode][cp];
+ }
+ if (!emojiName) {
+ console.error(`No emoji for index ${index}`);
+ emojiName = unknownString;
+ }
+ document.l10n.setAttributes(cell, "tor-bridges-emoji-cell", {
+ emojiName,
+ });
+ }
+ },
+
+ /**
+ * 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");
+ row.emojis = makeBridgeId(bridgeLine).map(index => {
+ const cell = document.createElement("span");
+ // Each emoji is its own cell, we rely on the fact that makeBridgeId
+ // always returns four indices.
+ cell.setAttribute("role", "gridcell");
+ cell.classList.add("tor-bridges-grid-cell", "tor-bridges-emoji-cell");
+
+ const img = document.createElement("img");
+ img.classList.add("tor-bridges-emoji-icon");
+ // Accessible name will be set on the cell itself.
+ img.setAttribute("alt", "");
+
+ cell.appendChild(img);
+ emojiBlock.appendChild(cell);
+ // Image and text is set in _updateRowEmojis.
+ return { cell, img, index };
+ });
+
+ 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 });
+ }
+
+ // TODO: properly handle "vanilla" bridges?
+ document.l10n.setAttributes(
+ row.element.querySelector(".tor-bridges-type-cell"),
+ "tor-bridges-type-prefix",
+ { type: details?.transport ?? "vanilla" }
+ );
+
+ row.element.querySelector(".tor-bridges-address-cell").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);
+ this._updateRowEmojis(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();
+ });
+
+ row.menu
+ .querySelector(".tor-bridges-options-qr-one-menu-item")
+ .addEventListener("click", () => {
+ const bridgeLine = row.bridgeLine;
+ if (!bridgeLine) {
+ return;
+ }
+ const dialog = new BridgeQrDialog();
+ dialog.openDialog(gSubDialog, 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);
+ });
+ row.menu
+ .querySelector(".tor-bridges-options-remove-one-menu-item")
+ .addEventListener("click", () => {
+ const bridgeLine = row.bridgeLine;
+ const strings = TorSettings.bridges.bridge_strings;
+ const index = strings.indexOf(bridgeLine);
+ if (index === -1) {
+ return;
+ }
+ strings.splice(index, 1);
+
+ setTorSettings(() => {
+ TorSettings.bridges.bridge_strings = strings;
+ });
+ });
+ },
+
+ /**
+ * 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],
+
+ /**
+ * 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.classList.add("built-in-active");
+
+ this._updateBridgeIds();
+ this._updateConnectedBridge();
+ },
+
+ /**
+ * Deactivate and hide built-in bridge area.
+ */
+ deactivate() {
+ if (!this._active) {
+ return;
+ }
+ this._active = false;
+
+ this._area.classList.remove("built-in-active");
+
+ Services.obs.removeObserver(this, TorProviderTopics.BridgeChanged);
+ },
+
+ observe(subject, topic, data) {
+ 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 {Object<string,object>}
+ */
+ _bridgeTypeStrings: {
+ // TODO: Change to Fluent ids.
+ obfs4: {
+ name: TorStrings.settings.builtinBridgeObfs4Title,
+ description: TorStrings.settings.builtinBridgeObfs4Description2,
+ },
+ snowflake: {
+ name: TorStrings.settings.builtinBridgeSnowflake,
+ description: TorStrings.settings.builtinBridgeSnowflakeDescription2,
+ },
+ "meek-azure": {
+ name: TorStrings.settings.builtinBridgeMeekAzure,
+ description: TorStrings.settings.builtinBridgeMeekAzureDescription2,
+ },
+ },
+
+ /**
+ * 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
+ );
+ */
+ this._nameEl.textContent = bridgeStrings.name;
+ this._descriptionEl.textContent = 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 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 heading for the bridge settings.
+ *
+ * @type {Element?}
+ */
+ _bridgesSettingsHeading: null,
+ /**
+ * The current bridges heading, at the start of the area.
+ *
+ * @type {Element?}
+ */
+ _currentBridgesHeading: null,
+ /**
+ * The area for showing no bridges.
+ *
+ * @type {Element?}
+ */
+ _noBridgesEl: null,
+
+ /**
+ * Initialize the bridge settings.
+ */
+ init() {
+ gBridgesNotification.init();
+
+ this._bridgesSettingsHeading = document.getElementById(
+ "torPreferences-bridges-header"
+ );
+ this._currentBridgesHeading = document.getElementById(
+ "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._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;
+ }
+ setTorSettings(() => {
+ TorSettings.bridges.enabled = this._toggleButton.pressed;
+ });
+ });
+
+ Services.obs.addObserver(this, TorSettingsTopics.SettingsChanged);
+
+ gBridgeGrid.init();
+ gBuiltinBridgesArea.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();
+
+ Services.obs.removeObserver(this, TorSettingsTopics.SettingsChanged);
+ },
+
+ observe(subject, topic, data) {
+ 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,
+
+ /**
+ * 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);
+
+ this._bridgesEl.classList.toggle(
+ "source-built-in",
+ bridgeSource === TorBridgeSource.BuiltIn
+ );
+ this._bridgesEl.classList.toggle(
+ "source-user",
+ bridgeSource === TorBridgeSource.UserProvided
+ );
+ this._bridgesEl.classList.toggle(
+ "source-requested",
+ bridgeSource === TorBridgeSource.BridgeDB
+ );
+
+ // 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.toggle("no-bridges", !haveBridges);
+ this._groupEl.classList.toggle("have-bridges", haveBridges);
+ },
+
+ /**
+ * 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 _currentBridgesHeading element is visible before
+ // we focus it.
+ this._updateHaveBridges();
+ if (this._haveBridges) {
+ // Move focus to the start of the area, which is the heading.
+ // It has tabindex="-1" so should be focusable, even though it is not part
+ // of the usual tab navigation.
+ this._currentBridgesHeading.focus();
+ } else {
+ // Move focus to the top of the bridge settings.
+ this._bridgesSettingsHeading.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;
+ }
+ const dialog = new BridgeQrDialog();
+ dialog.openDialog(gSubDialog, 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", () => {
+ // TODO: move to gBridgeSettings.
+ // TODO: Change dialog title. Do not allow Lox invite.
+ gConnectionPane.onAddBridgeManually();
+ });
+
+ // 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", () => {
+ // 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_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL;
+
+ // TODO: Update the text, and remove old strings.
+ const buttonIndex = Services.prompt.confirmEx(
+ parentWindow,
+ TorStrings.settings.bridgeRemoveAllDialogTitle,
+ TorStrings.settings.bridgeRemoveAllDialogDescription,
+ flags,
+ TorStrings.settings.remove,
+ null,
+ null,
+ null,
+ {}
+ );
+
+ if (buttonIndex !== 0) {
+ return;
+ }
+
+ setTorSettings(() => {
+ // This should always have the side effect of disabling bridges as
+ // well.
+ TorSettings.bridges.bridge_strings = [];
+ });
+ });
+
+ this._bridgesMenu.addEventListener("showing", () => {
+ const canCopy = this._bridgeSource !== TorBridgeSource.BuiltIn;
+ qrItem.hidden = !this._canQRBridges || !canCopy;
+ copyItem.hidden = !canCopy;
+ 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 });
+ },
+};
+
/*
Connection Pane
@@ -96,29 +1732,6 @@ const gConnectionPane = (function () {
location: "#torPreferences-bridges-location",
locationEntries: "#torPreferences-bridges-locationEntries",
chooseForMe: "#torPreferences-bridges-buttonChooseBridgeForMe",
- currentHeader: "#torPreferences-currentBridges-header",
- currentDescription: "#torPreferences-currentBridges-description",
- currentDescriptionText: "#torPreferences-currentBridges-descriptionText",
- controls: "#torPreferences-currentBridges-controls",
- switch: "#torPreferences-currentBridges-switch",
- cards: "#torPreferences-currentBridges-cards",
- cardTemplate: "#torPreferences-bridgeCard-template",
- card: ".torPreferences-bridgeCard",
- cardId: ".torPreferences-bridgeCard-id",
- cardHeadingManualLink: ".torPreferences-bridgeCard-manualLink",
- cardHeadingAddr: ".torPreferences-bridgeCard-headingAddr",
- cardConnectedLabel: ".torPreferences-current-bridge-label",
- cardOptions: ".torPreferences-bridgeCard-options",
- cardMenu: "#torPreferences-bridgeCard-menu",
- cardQrGrid: ".torPreferences-bridgeCard-grid",
- cardQrContainer: ".torPreferences-bridgeCard-qr",
- cardQr: ".torPreferences-bridgeCard-qrCode",
- cardShare: ".torPreferences-bridgeCard-share",
- cardAddr: ".torPreferences-bridgeCard-addr",
- cardLearnMore: ".torPreferences-bridgeCard-learnMore",
- cardCopy: ".torPreferences-bridgeCard-copyButton",
- showAll: "#torPreferences-currentBridges-showAll",
- removeAll: "#torPreferences-currentBridges-removeAll",
addHeader: "#torPreferences-addBridge-header",
addBuiltinLabel: "#torPreferences-addBridge-labelBuiltinBridge",
addBuiltinButton: "#torPreferences-addBridge-buttonBuiltinBridge",
@@ -142,8 +1755,6 @@ const gConnectionPane = (function () {
_internetStatus: InternetStatus.Unknown,
- _currentBridgeId: null,
-
// populate xul with strings and cache the relevant elements
_populateXUL() {
// saves tor settings to disk when navigate away from about:preferences
@@ -387,390 +1998,6 @@ const gConnectionPane = (function () {
this._showAutoconfiguration();
}
- // Bridge cards
- const bridgeHeader = prefpane.querySelector(
- selectors.bridges.currentHeader
- );
- bridgeHeader.textContent = TorStrings.settings.bridgeCurrent;
- const bridgeControls = prefpane.querySelector(selectors.bridges.controls);
- const bridgeSwitch = prefpane.querySelector(selectors.bridges.switch);
- bridgeSwitch.setAttribute("label", TorStrings.settings.allBridgesEnabled);
- bridgeSwitch.addEventListener("toggle", () => {
- TorSettings.bridges.enabled = bridgeSwitch.pressed;
- TorSettings.saveToPrefs();
- TorSettings.applySettings().finally(() => {
- this._populateBridgeCards();
- });
- });
- const bridgeDescription = prefpane.querySelector(
- selectors.bridges.currentDescription
- );
- bridgeDescription.querySelector(
- selectors.bridges.currentDescriptionText
- ).textContent = TorStrings.settings.bridgeCurrentDescription;
- const bridgeTemplate = prefpane.querySelector(
- selectors.bridges.cardTemplate
- );
- {
- const learnMore = bridgeTemplate.querySelector(
- selectors.bridges.cardLearnMore
- );
- learnMore.setAttribute("value", TorStrings.settings.learnMore);
- learnMore.setAttribute(
- "href",
- TorStrings.settings.learnMoreBridgesCardURL
- );
- if (TorStrings.settings.learnMoreBridgesCardURL.startsWith("about:")) {
- learnMore.setAttribute("useoriginprincipal", "true");
- }
- }
- {
- const manualLink = bridgeTemplate.querySelector(
- selectors.bridges.cardHeadingManualLink
- );
- manualLink.setAttribute("value", TorStrings.settings.whatAreThese);
- manualLink.setAttribute(
- "href",
- TorStrings.settings.learnMoreBridgesCardURL
- );
- if (TorStrings.settings.learnMoreBridgesCardURL.startsWith("about:")) {
- manualLink.setAttribute("useoriginprincipal", "true");
- }
- }
- bridgeTemplate.querySelector(
- selectors.bridges.cardConnectedLabel
- ).textContent = TorStrings.settings.connectedBridge;
- bridgeTemplate
- .querySelector(selectors.bridges.cardCopy)
- .setAttribute("label", TorStrings.settings.bridgeCopy);
- bridgeTemplate.querySelector(selectors.bridges.cardShare).textContent =
- TorStrings.settings.bridgeShare;
- const bridgeCards = prefpane.querySelector(selectors.bridges.cards);
- const bridgeMenu = prefpane.querySelector(selectors.bridges.cardMenu);
-
- this._addBridgeCard = bridgeString => {
- const card = bridgeTemplate.cloneNode(true);
- card.removeAttribute("id");
- const grid = card.querySelector(selectors.bridges.cardQrGrid);
- card.addEventListener("click", e => {
- if (
- card.classList.contains("currently-connected") ||
- bridgeCards.classList.contains("single-card")
- ) {
- return;
- }
- let target = e.target;
- let apply = true;
- while (target !== null && target !== card && apply) {
- // Deal with mixture of "command" and "click" events
- apply = !target.classList?.contains("stop-click");
- target = target.parentElement;
- }
- if (apply) {
- if (card.classList.toggle("expanded")) {
- grid.classList.add("to-animate");
- grid.style.height = `${grid.scrollHeight}px`;
- } else {
- // Be sure we still have the to-animate class
- grid.classList.add("to-animate");
- grid.style.height = "";
- }
- }
- });
- const emojis = makeBridgeId(bridgeString).map(emojiIndex => {
- const img = document.createElement("img");
- img.classList.add("emoji");
- // Image is set in _updateBridgeEmojis.
- img.dataset.emojiIndex = emojiIndex;
- return img;
- });
- const idString = TorStrings.settings.bridgeId;
- const id = card.querySelector(selectors.bridges.cardId);
- let details;
- try {
- details = TorParsers.parseBridgeLine(bridgeString);
- } catch (e) {
- console.error(`Detected invalid bridge line: ${bridgeString}`, e);
- }
- if (details && details.id !== undefined) {
- card.setAttribute("data-bridge-id", details.id);
- }
- // TODO: properly handle "vanilla" bridges?
- const type =
- details && details.transport !== undefined
- ? details.transport
- : "vanilla";
- for (const piece of idString.split(/(%[12]\$S)/)) {
- if (piece == "%1$S") {
- id.append(type);
- } else if (piece == "%2$S") {
- id.append(...emojis);
- } else {
- id.append(piece);
- }
- }
- card.querySelector(selectors.bridges.cardHeadingAddr).textContent =
- bridgeString;
- const optionsButton = card.querySelector(selectors.bridges.cardOptions);
- if (TorSettings.bridges.source === TorBridgeSource.BuiltIn) {
- optionsButton.setAttribute("hidden", "true");
- } else {
- // Cloning the menupopup element does not work as expected.
- // Therefore, we use only one, and just before opening it, we remove
- // its previous items, and add the ones relative to the bridge whose
- // button has been pressed.
- optionsButton.addEventListener("click", () => {
- const menuItem = document.createXULElement("menuitem");
- menuItem.setAttribute("label", TorStrings.settings.remove);
- menuItem.classList.add("menuitem-iconic");
- menuItem.image = "chrome://global/skin/icons/delete.svg";
- menuItem.addEventListener("command", e => {
- const strings = TorSettings.bridges.bridge_strings;
- const index = strings.indexOf(bridgeString);
- if (index !== -1) {
- strings.splice(index, 1);
- }
- TorSettings.bridges.enabled =
- bridgeSwitch.pressed && !!strings.length;
- TorSettings.bridges.bridge_strings = strings.join("\n");
- TorSettings.saveToPrefs();
- TorSettings.applySettings().finally(() => {
- this._populateBridgeCards();
- });
- });
- if (bridgeMenu.firstChild) {
- bridgeMenu.firstChild.remove();
- }
- bridgeMenu.append(menuItem);
- bridgeMenu.openPopup(optionsButton, {
- position: "bottomleft topleft",
- });
- });
- }
- const bridgeAddr = card.querySelector(selectors.bridges.cardAddr);
- bridgeAddr.setAttribute("value", bridgeString);
- const bridgeCopy = card.querySelector(selectors.bridges.cardCopy);
- let restoreTimeout = null;
- bridgeCopy.addEventListener("command", e => {
- this.onCopyBridgeAddress(bridgeAddr);
- const label = bridgeCopy.querySelector("label");
- label.setAttribute("value", TorStrings.settings.copied);
- bridgeCopy.classList.add("primary");
-
- const RESTORE_TIME = 1200;
- if (restoreTimeout !== null) {
- clearTimeout(restoreTimeout);
- }
- restoreTimeout = setTimeout(() => {
- label.setAttribute("value", TorStrings.settings.bridgeCopy);
- bridgeCopy.classList.remove("primary");
- restoreTimeout = null;
- }, RESTORE_TIME);
- });
- if (details?.id && details.id === this._currentBridgeId) {
- card.classList.add("currently-connected");
- bridgeCards.prepend(card);
- } else {
- bridgeCards.append(card);
- }
- // Add the QR only after appending the card, to have the computed style
- try {
- const container = card.querySelector(selectors.bridges.cardQr);
- const style = getComputedStyle(container);
- const width = style.width.substring(0, style.width.length - 2);
- const height = style.height.substring(0, style.height.length - 2);
- new QRCode(container, {
- text: bridgeString,
- width,
- height,
- colorDark: style.color,
- colorLight: style.backgroundColor,
- document,
- });
- container.parentElement.addEventListener("click", () => {
- this.onShowQr(bridgeString);
- });
- } catch (err) {
- // TODO: Add a generic image in case of errors such as code overflow.
- // It should never happen with correct codes, but after all this
- // content can be generated by users...
- console.error("Could not generate the QR code for the bridge:", err);
- }
- };
- this._checkBridgeCardsHeight = () => {
- for (const card of bridgeCards.children) {
- // Expanded cards have the height set manually to their details for
- // the CSS animation. However, when resizing the window, we may need
- // to adjust their height.
- if (
- card.classList.contains("expanded") ||
- card.classList.contains("currently-connected")
- ) {
- const grid = card.querySelector(selectors.bridges.cardQrGrid);
- // Reset it first, to avoid having a height that is higher than
- // strictly needed. Also, remove the to-animate class, because the
- // animation interferes with this process!
- grid.classList.remove("to-animate");
- grid.style.height = "";
- grid.style.height = `${grid.scrollHeight}px`;
- }
- }
- };
- this._currentBridgesExpanded = false;
- const showAll = prefpane.querySelector(selectors.bridges.showAll);
- showAll.setAttribute("label", TorStrings.settings.bridgeShowAll);
- showAll.addEventListener("command", () => {
- this._currentBridgesExpanded = !this._currentBridgesExpanded;
- this._populateBridgeCards();
- if (!this._currentBridgesExpanded) {
- bridgeSwitch.scrollIntoView({ behavior: "smooth" });
- }
- });
- const removeAll = prefpane.querySelector(selectors.bridges.removeAll);
- removeAll.setAttribute("label", TorStrings.settings.bridgeRemoveAll);
- removeAll.addEventListener("command", () => {
- this._confirmBridgeRemoval();
- });
- this._populateBridgeCards = () => {
- const collapseThreshold = 4;
-
- const newStrings = new Set(TorSettings.bridges.bridge_strings);
- const numBridges = newStrings.size;
- const noBridges = !numBridges;
- bridgeHeader.hidden = noBridges;
- bridgeDescription.hidden = noBridges;
- bridgeControls.hidden = noBridges;
- bridgeCards.hidden = noBridges;
- if (noBridges) {
- showAll.hidden = true;
- removeAll.hidden = true;
- bridgeCards.textContent = "";
- return;
- }
- // Changing the pressed property on moz-toggle should not trigger its
- // "toggle" event.
- bridgeSwitch.pressed = TorSettings.bridges.enabled;
- bridgeCards.classList.toggle("disabled", !TorSettings.bridges.enabled);
- bridgeCards.classList.toggle("single-card", numBridges === 1);
-
- let shownCards = 0;
- const toShow = this._currentBridgesExpanded
- ? numBridges
- : collapseThreshold;
-
- // Do not remove all the old cards, because it makes scrollbar "jump"
- const currentCards = bridgeCards.querySelectorAll(
- selectors.bridges.card
- );
- for (const card of currentCards) {
- const string = card.querySelector(selectors.bridges.cardAddr).value;
- const hadString = newStrings.delete(string);
- if (!hadString || shownCards == toShow) {
- card.remove();
- } else {
- shownCards++;
- }
- }
-
- // Add only the new strings that remained in the set
- for (const bridge of newStrings) {
- if (shownCards >= toShow) {
- if (!this._currentBridgeId) {
- break;
- } else if (!bridge.includes(this._currentBridgeId)) {
- continue;
- }
- }
- this._addBridgeCard(bridge);
- shownCards++;
- }
-
- // If we know the connected bridge, we may have added more than the ones
- // we should actually show (but the connected ones have been prepended,
- // if needed). So, remove any exceeding ones.
- while (shownCards > toShow) {
- bridgeCards.lastElementChild.remove();
- shownCards--;
- }
-
- // Newly added emojis.
- this._updateBridgeEmojis();
-
- // And finally update the buttons
- removeAll.hidden = false;
- showAll.classList.toggle("primary", TorSettings.bridges.enabled);
- if (numBridges > collapseThreshold) {
- showAll.hidden = false;
- showAll.setAttribute(
- "aria-expanded",
- // Boolean value gets converted to string "true" or "false".
- this._currentBridgesExpanded
- );
- showAll.setAttribute(
- "label",
- this._currentBridgesExpanded
- ? TorStrings.settings.bridgeShowFewer
- : TorStrings.settings.bridgeShowAll
- );
- // We do not want both collapsed and disabled at the same time,
- // because we use collapsed only to display a gradient on the list.
- bridgeCards.classList.toggle(
- "list-collapsed",
- !this._currentBridgesExpanded && TorSettings.bridges.enabled
- );
- } else {
- // NOTE: We do not expect the showAll button to have focus when we
- // hide it since we do not expect `numBridges` to decrease whilst
- // this button is focused.
- showAll.hidden = true;
- bridgeCards.classList.remove("list-collapsed");
- }
- };
- this._populateBridgeCards();
- this._updateConnectedBridges = () => {
- for (const card of bridgeCards.querySelectorAll(
- ".currently-connected"
- )) {
- card.classList.remove("currently-connected");
- card.querySelector(selectors.bridges.cardQrGrid).style.height = "";
- }
- if (!this._currentBridgeId) {
- return;
- }
- // Make sure we have the connected bridge in the list
- this._populateBridgeCards();
- // At the moment, IDs do not have to be unique (and it is a concrete
- // case also with built-in bridges!). E.g., one line for the IPv4
- // address and one for the IPv6 address, so use querySelectorAll
- const cards = bridgeCards.querySelectorAll(
- `[data-bridge-id="${this._currentBridgeId}"]`
- );
- for (const card of cards) {
- card.classList.add("currently-connected");
- }
- const placeholder = document.createElement("span");
- bridgeCards.prepend(placeholder);
- placeholder.replaceWith(...cards);
- this._checkBridgeCardsHeight();
- };
- this._checkConnectedBridge = async () => {
- // TODO: 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);
- }
- if (bridge?.fingerprint !== this._currentBridgeId) {
- this._currentBridgeId = bridge?.fingerprint ?? null;
- this._updateConnectedBridges();
- }
- };
- this._checkConnectedBridge();
-
// Add a new bridge
prefpane.querySelector(selectors.bridges.addHeader).textContent =
TorStrings.settings.bridgeAdd;
@@ -804,34 +2031,6 @@ const gConnectionPane = (function () {
});
}
- this._confirmBridgeRemoval = () => {
- const aParentWindow =
- Services.wm.getMostRecentWindow("navigator:browser");
-
- const ps = Services.prompt;
- const btnFlags =
- ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING +
- ps.BUTTON_POS_0_DEFAULT +
- ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL;
-
- const notUsed = { value: false };
- const btnIndex = ps.confirmEx(
- aParentWindow,
- TorStrings.settings.bridgeRemoveAllDialogTitle,
- TorStrings.settings.bridgeRemoveAllDialogDescription,
- btnFlags,
- TorStrings.settings.remove,
- null,
- null,
- null,
- notUsed
- );
-
- if (btnIndex === 0) {
- this.onRemoveAllBridges();
- }
- };
-
// Advanced setup
prefpane.querySelector(selectors.advanced.header).innerText =
TorStrings.settings.advancedHeading;
@@ -862,11 +2061,11 @@ const gConnectionPane = (function () {
});
Services.obs.addObserver(this, TorConnectTopics.StateChange);
- Services.obs.addObserver(this, TorProviderTopics.BridgeChanged);
- Services.obs.addObserver(this, "intl:app-locales-changed");
},
init() {
+ gBridgeSettings.init();
+
TorSettings.initializedPromise.then(() => this._populateXUL());
const onUnload = () => {
@@ -874,21 +2073,14 @@ const gConnectionPane = (function () {
gConnectionPane.uninit();
};
window.addEventListener("unload", onUnload);
-
- window.addEventListener("resize", () => {
- this._checkBridgeCardsHeight();
- });
- window.addEventListener("hashchange", () => {
- this._checkBridgeCardsHeight();
- });
},
uninit() {
+ gBridgeSettings.uninit();
+
// unregister our observer topics
Services.obs.removeObserver(this, TorSettingsTopics.SettingsChanged);
Services.obs.removeObserver(this, TorConnectTopics.StateChange);
- Services.obs.removeObserver(this, TorProviderTopics.BridgeChanged);
- Services.obs.removeObserver(this, "intl:app-locales-changed");
},
// whether the page should be present in about:preferences
@@ -916,64 +2108,6 @@ const gConnectionPane = (function () {
this.onStateChange();
break;
}
- case TorProviderTopics.BridgeChanged: {
- this._checkConnectedBridge();
- break;
- }
- case "intl:app-locales-changed": {
- this._updateBridgeEmojis();
- break;
- }
- }
- },
-
- /**
- * Update the bridge emojis to show their corresponding emoji with an
- * annotation that matches the current locale.
- */
- async _updateBridgeEmojis() {
- if (!this._emojiPromise) {
- this._emojiPromise = Promise.all([
- fetch(
- "chrome://browser/content/torpreferences/bridgemoji/bridge-emojis.json"
- ).then(response => response.json()),
- fetch(
- "chrome://browser/content/torpreferences/bridgemoji/annotations.json"
- ).then(response => response.json()),
- ]);
- }
- const [emojiList, emojiAnnotations] = await this._emojiPromise;
- let langCode;
- // Find the first desired locale we have annotations for.
- // Add "en" as a fallback.
- for (const bcp47 of [...Services.locale.appLocalesAsBCP47, "en"]) {
- langCode = bcp47;
- if (langCode in emojiAnnotations) {
- break;
- }
- // Remove everything after the dash, if there is one.
- langCode = bcp47.replace(/-.*/, "");
- if (langCode in emojiAnnotations) {
- break;
- }
- }
- for (const img of document.querySelectorAll(".emoji[data-emoji-index]")) {
- const emoji = emojiList[img.dataset.emojiIndex];
- if (!emoji) {
- // Unexpected.
- console.error(`No emoji for index ${img.dataset.emojiIndex}`);
- img.removeAttribute("src");
- img.removeAttribute("alt");
- img.removeAttribute("title");
- continue;
- }
- const cp = emoji.codePointAt(0).toString(16);
- img.setAttribute(
- "src",
- `chrome://browser/content/torpreferences/bridgemoji/svgs/${cp}.svg`
- );
- img.setAttribute("alt", emoji);
- img.setAttribute("title", emojiAnnotations[langCode][cp]);
}
},
@@ -999,51 +2133,21 @@ const gConnectionPane = (function () {
onStateChange() {
this._populateStatus();
this._showAutoconfiguration();
- this._populateBridgeCards();
- },
-
- onShowQr(bridgeString) {
- const dialog = new BridgeQrDialog();
- dialog.openDialog(gSubDialog, bridgeString);
- },
-
- onCopyBridgeAddress(addressElem) {
- const clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
- Ci.nsIClipboardHelper
- );
- clipboard.copyString(addressElem.value);
- },
-
- onRemoveAllBridges() {
- TorSettings.bridges.enabled = false;
- TorSettings.bridges.bridge_strings = "";
- if (TorSettings.bridges.source === TorBridgeSource.BuiltIn) {
- TorSettings.bridges.builtin_type = "";
- }
- TorSettings.saveToPrefs();
- TorSettings.applySettings().finally(() => {
- this._populateBridgeCards();
- });
},
/**
* Save and apply settings, then optionally open about:torconnect and start
* bootstrapping.
*
+ * @param {fucntion} changes - The changes to make.
* @param {boolean} connect - Whether to open about:torconnect and start
* bootstrapping if possible.
*/
- async saveBridgeSettings(connect) {
- TorSettings.saveToPrefs();
- // FIXME: This can throw if the user adds a bridge manually with invalid
- // content. Should be addressed by tor-browser#41913.
- try {
- await TorSettings.applySettings();
- } catch (e) {
- console.error("Applying settings failed", e);
- }
-
- this._populateBridgeCards();
+ async saveBridgeSettings(changes, connect) {
+ // TODO: Move focus into the bridge area.
+ // dialog.ownerGlobal.addEventListener("unload", () => gCurrentBridgesArea.takeFocus(), { once: true });
+ // or use closedCallback in gSubDialog.open()
+ setTorSettings(changes);
if (!connect) {
return;
@@ -1071,11 +2175,11 @@ const gConnectionPane = (function () {
onAddBuiltinBridge() {
const builtinBridgeDialog = new BuiltinBridgeDialog(
(bridgeType, connect) => {
- TorSettings.bridges.enabled = true;
- TorSettings.bridges.source = TorBridgeSource.BuiltIn;
- TorSettings.bridges.builtin_type = bridgeType;
-
- this.saveBridgeSettings(connect);
+ this.saveBridgeSettings(() => {
+ TorSettings.bridges.enabled = true;
+ TorSettings.bridges.source = TorBridgeSource.BuiltIn;
+ TorSettings.bridges.builtin_type = bridgeType;
+ }, connect);
}
);
builtinBridgeDialog.openDialog(gSubDialog);
@@ -1088,12 +2192,14 @@ const gConnectionPane = (function () {
if (!aBridges.length) {
return;
}
+
const bridgeStrings = aBridges.join("\n");
- TorSettings.bridges.enabled = true;
- TorSettings.bridges.source = TorBridgeSource.BridgeDB;
- TorSettings.bridges.bridge_strings = bridgeStrings;
- this.saveBridgeSettings(connect);
+ this.saveBridgeSettings(() => {
+ TorSettings.bridges.enabled = true;
+ TorSettings.bridges.source = TorBridgeSource.BridgeDB;
+ TorSettings.bridges.bridge_strings = bridgeStrings;
+ }, connect);
}
);
requestBridgeDialog.openDialog(gSubDialog);
@@ -1102,11 +2208,11 @@ const gConnectionPane = (function () {
onAddBridgeManually() {
const provideBridgeDialog = new ProvideBridgeDialog(
(aBridgeString, connect) => {
- TorSettings.bridges.enabled = true;
- TorSettings.bridges.source = TorBridgeSource.UserProvided;
- TorSettings.bridges.bridge_strings = aBridgeString;
-
- this.saveBridgeSettings(connect);
+ this.saveBridgeSettings(() => {
+ TorSettings.bridges.enabled = true;
+ TorSettings.bridges.source = TorBridgeSource.UserProvided;
+ TorSettings.bridges.bridge_strings = aBridgeString;
+ }, connect);
}
);
provideBridgeDialog.openDialog(gSubDialog);
=====================================
browser/components/torpreferences/content/connectionPane.xhtml
=====================================
@@ -67,7 +67,7 @@
<!-- Bridges -->
<hbox class="subcategory" data-category="paneConnection" hidden="true">
- <html:h1 id="torPreferences-bridges-header" />
+ <html:h1 id="torPreferences-bridges-header" tabindex="-1" />
</hbox>
<groupbox
id="torPreferences-bridges-group"
@@ -103,73 +103,196 @@
class="primary"
/>
</hbox>
- <html:h2 id="torPreferences-currentBridges-header"> </html:h2>
- <description flex="1" id="torPreferences-currentBridges-description">
- <html:span id="torPreferences-currentBridges-descriptionText" />
- </description>
- <hbox align="center" id="torPreferences-currentBridges-controls">
- <html:moz-toggle
- id="torPreferences-currentBridges-switch"
- label-align-after=""
- />
- <spacer flex="1" />
- <button id="torPreferences-currentBridges-removeAll" />
- </hbox>
- <menupopup id="torPreferences-bridgeCard-menu" />
- <vbox
- id="torPreferences-bridgeCard-template"
- class="torPreferences-bridgeCard"
- >
- <hbox class="torPreferences-bridgeCard-heading">
- <html:div class="torPreferences-bridgeCard-id" />
- <label
- class="torPreferences-bridgeCard-manualLink learnMore text-link stop-click"
- is="text-link"
- />
- <html:div class="torPreferences-bridgeCard-headingAddr" />
- <html:div class="torPreferences-bridgeCard-buttons">
- <html:span class="torPreferences-current-bridge-badge">
- <image class="torPreferences-current-bridge-icon" />
- <html:span class="torPreferences-current-bridge-label"></html:span>
+ <html:moz-toggle
+ id="tor-bridges-enabled-toggle"
+ label-align-after=""
+ 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. -->
+ <html:div id="tor-bridges-update-area" hidden="hidden">
+ <!-- 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">
+ <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">
+ <html:div id="tor-bridges-current-header-bar">
+ <html:h2
+ id="tor-bridges-current-heading"
+ tabindex="-1"
+ data-l10n-id="tor-bridges-your-bridges"
+ ></html:h2>
+ <html:span
+ id="tor-bridges-user-label"
+ data-l10n-id="tor-bridges-source-user"
+ ></html:span>
+ <html:span
+ id="tor-bridges-built-in-label"
+ data-l10n-id="tor-bridges-source-built-in"
+ ></html:span>
+ <html:span
+ id="tor-bridges-requested-label"
+ data-l10n-id="tor-bridges-source-requested"
+ ></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">
+ <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">
+ <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"
+ role="grid"
+ aria-labelledby="tor-bridges-current-heading"
+ ></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>
+ <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">
+ <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:button class="torPreferences-bridgeCard-options stop-click" />
</html:div>
- </hbox>
- <box class="torPreferences-bridgeCard-grid">
- <box class="torPreferences-bridgeCard-qrWrapper">
- <html:div class="torPreferences-bridgeCard-qr stop-click">
- <html:div class="torPreferences-bridgeCard-qrCode" />
- <html:div class="torPreferences-bridgeCard-qrOnionBox" />
- <html:div class="torPreferences-bridgeCard-qrOnion" />
- </html:div>
- </box>
- <description class="torPreferences-bridgeCard-share"></description>
- <hbox class="torPreferences-bridgeCard-addrBox">
- <html:input
- class="torPreferences-bridgeCard-addr stop-click"
- type="text"
- readonly="readonly"
- />
- </hbox>
- <!-- tor-browser#41977 disable this learn more link until we have an alternate manual entry -->
- <hbox class="torPreferences-bridgeCard-learnMoreBox" align="center" hidden="true">
- <label
- class="torPreferences-bridgeCard-learnMore learnMore text-link stop-click"
- is="text-link"
- />
- </hbox>
- <hbox class="torPreferences-bridgeCard-copy" align="center">
- <button class="torPreferences-bridgeCard-copyButton stop-click" />
- </hbox>
- </box>
- </vbox>
- <vbox id="torPreferences-currentBridges-cards"></vbox>
- <vbox align="center">
- <button
- id="torPreferences-currentBridges-showAll"
- aria-controls="torPreferences-currentBridges-cards"
- />
- </vbox>
+ </html:template>
+ <html:div id="tor-bridges-share">
+ <html:h3
+ id="tor-bridges-share-heading"
+ data-l10n-id="tor-bridges-share-heading"
+ ></html:h3>
+ <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>
<html:h2 id="torPreferences-addBridge-header"></html:h2>
<hbox align="center">
<label id="torPreferences-addBridge-labelBuiltinBridge" flex="1" />
=====================================
browser/components/torpreferences/content/torPreferences.css
=====================================
@@ -69,282 +69,391 @@
}
/* Bridge settings */
-#torPreferences-bridges-location {
- width: 280px;
-}
-#torPreferences-bridges-location menuitem[disabled="true"] {
- color: var(--in-content-button-text-color, inherit);
- font-weight: 700;
+.bridge-status-badge {
+ display: flex;
+ min-width: max-content;
+ align-items: center;
+ gap: 0.5em;
+ font-size: 0.85em;
}
-/* Bridge cards */
-:root {
- --bridgeCard-animation-time: 0.25s;
+.bridge-status-badge:not(
+ .bridge-status-connected,
+ .bridge-status-none,
+ .bridge-status-current-built-in
+) {
+ display: none;
}
-#torPreferences-currentBridges-cards {
- /* The padding is needed because the mask-image creates an unexpected result
- otherwise... */
- padding: 24px 4px;
+.bridge-status-badge.bridge-status-connected {
+ color: var(--purple-60);
}
-#torPreferences-currentBridges-cards.list-collapsed {
- mask-image: linear-gradient(rgb(0, 0, 0) 0% 75%, rgba(0, 0, 0, 0.1));
+@media (prefers-color-scheme: dark) {
+ .bridge-status-badge.bridge-status-connected {
+ color: var(--purple-30);
+ }
}
-#torPreferences-currentBridges-cards.disabled {
- opacity: 0.4;
+.bridge-status-badge.bridge-status-current-built-in {
+ color: var(--in-content-accent-color);
}
-.torPreferences-bridgeCard {
- padding: 16px 12px;
- /* define border-radius here because of the transition */
- border-radius: 4px;
- transition: margin var(--bridgeCard-animation-time), box-shadow 150ms;
- cursor: pointer;
+.bridge-status-badge > * {
+ flex: 0 0 auto;
}
-.torPreferences-bridgeCard.expanded,
-.torPreferences-bridgeCard.currently-connected,
-.single-card .torPreferences-bridgeCard {
- margin: 12px 0;
- background: var(--in-content-box-background);
- box-shadow: var(--card-shadow);
+.bridge-status-icon {
+ width: 16px;
+ height: 16px;
+ background-repeat: no-repeat;
+ background-position: center center;
+ -moz-context-properties: fill;
+ fill: currentColor;
}
-.torPreferences-bridgeCard:hover {
- background: var(--in-content-box-background);
- box-shadow: var(--card-shadow-hover);
+.bridge-status-badge:is(
+ .bridge-status-connected,
+ .bridge-status-current-built-in
+) .bridge-status-icon {
+ background-image: url("chrome://global/skin/icons/check.svg");
}
-.single-card .torPreferences-bridgeCard,
-.torPreferences-bridgeCard.currently-connected {
- cursor: default;
+.bridge-status-badge.bridge-status-none .bridge-status-icon {
+ /* Hide the icon. */
+ display: none;
}
-.torPreferences-bridgeCard-heading {
- display: flex;
- align-items: center;
+#tor-bridges-enabled-toggle {
+ margin-block: 16px;
+ width: max-content;
}
-.torPreferences-bridgeCard-id {
- display: flex;
- align-items: center;
- font-weight: 700;
+#tor-bridges-update-area {
+ /* Still accessible to screen reader, but not visual. */
+ position: absolute;
+ clip-path: inset(50%);
}
-.torPreferences-bridgeCard-id .emoji {
- width: 20px;
- height: 20px;
- margin-inline-start: 4px;
- padding: 4px;
- font-size: 20px;
- border-radius: 4px;
- background: var(--in-content-box-background-odd);
+#torPreferences-bridges-group:not(.have-bridges, .no-bridges) {
+ /* Hide bridge settings whilst not initialized. */
+ display: none;
}
-#torPreferences-currentBridges-cards:not(
- .single-card
-) .torPreferences-bridgeCard:not(
- .expanded,
- .currently-connected
-) .torPreferences-bridgeCard-manualLink {
+#torPreferences-bridges-group:not(.have-bridges) #tor-bridges-current {
display: none;
}
-.torPreferences-bridgeCard-manualLink {
- margin: 0 8px;
+#torPreferences-bridges-group:not(.no-bridges) #tor-bridges-none {
+ display: none;
}
-.torPreferences-bridgeCard-headingAddr {
- /* flex extends the element when needed, but without setting a width (any) the
- overflow + ellipses does not work. */
- width: 20px;
- flex: 1;
- margin: 0 8px;
- overflow: hidden;
- color: var(--text-color-deemphasized);
- white-space: nowrap;
- text-overflow: ellipsis;
+#tor-bridges-current:not(.source-built-in) #tor-bridges-built-in-label {
+ display: none;
}
-.expanded .torPreferences-bridgeCard-headingAddr,
-.currently-connected .torPreferences-bridgeCard-headingAddr,
-.single-card .torPreferences-bridgeCard-headingAddr {
+#tor-bridges-current:not(.source-user) #tor-bridges-user-label {
display: none;
}
-.torPreferences-bridgeCard-buttons {
- display: flex;
- align-items: center;
- margin-inline-start: auto;
- align-self: center;
+#tor-bridges-current:not(.source-requested) #tor-bridges-requested-label {
+ display: none;
}
-.torPreferences-current-bridge-badge {
- /* Hidden by default, otherwise display is "flex". */
+#tor-bridges-current:not(
+ .source-user,
+ .source-requested
+) #tor-bridges-share {
display: none;
- align-items: center;
- font-size: 0.85em;
}
-:is(
- .builtin-bridges-option.current-builtin-bridge-type,
- .torPreferences-bridgeCard.currently-connected
-) .torPreferences-current-bridge-badge {
- display: flex;
+#tor-bridges-none,
+#tor-bridges-current {
+ margin-inline: 0;
+ margin-block: 32px;
+ line-height: 1.8;
}
-.torPreferences-current-bridge-icon {
- margin-inline-start: 1px;
- margin-inline-end: 7px;
- list-style-image: url("chrome://global/skin/icons/check.svg");
+#tor-bridges-none {
+ display: grid;
+ justify-items: center;
+ text-align: center;
+ padding-block: 64px;
+ padding-inline: 32px;
+ gap: 16px;
+ border-radius: 4px;
+ color: var(--text-color-deemphasized);
+ border: 2px dashed color-mix(in srgb, currentColor 20%, transparent);
+}
+
+#tor-bridges-none-icon {
+ width: 20px;
+ height: 20px;
+ content: url("chrome://browser/content/torpreferences/bridge.svg");
-moz-context-properties: fill;
fill: currentColor;
- flex: 0 0 auto;
}
-.torPreferences-bridgeCard .torPreferences-current-bridge-badge {
- color: var(--purple-60);
- margin-inline-end: 12px;
+#tor-bridges-current {
+ padding: 16px;
+ border-radius: 4px;
+ background: var(--in-content-box-info-background);
}
-@media (prefers-color-scheme: dark) {
- .torPreferences-bridgeCard .torPreferences-current-bridge-badge {
- color: var(--purple-30);
- }
+#tor-bridges-current-header-bar {
+ display: flex;
+ min-width: max-content;
+ align-items: center;
+ border-block-end: 1px solid var(--in-content-border-color);
+ padding-block-end: 16px;
+ margin-block-end: 16px;
}
-.torPreferences-bridgeCard-options {
- width: 24px;
- min-width: 0;
- height: 24px;
- min-height: 0;
- margin-inline-start: 8px;
- padding: 1px;
+#tor-bridges-current-header-bar > * {
+ flex: 0 0 auto;
+}
+
+#tor-bridges-current-heading {
+ margin: 0;
+ margin-inline-end: 2em;
+ font-size: inherit;
+ flex: 1 0 auto;
+}
+
+.tor-bridges-options-button {
+ padding: 3px;
+ margin: 0;
+ min-height: auto;
+ min-width: auto;
+ box-sizing: content-box;
+ width: 16px;
+ height: 16px;
background-image: url("chrome://global/skin/icons/more.svg");
background-repeat: no-repeat;
background-position: center center;
- fill: currentColor;
+ background-origin: content-box;
+ background-size: contain;
-moz-context-properties: fill;
+ fill: currentColor;
}
-#torPreferences-bridgeCard-menu menuitem {
- fill: currentColor;
- -moz-context-properties: fill;
+#tor-bridges-all-options-button {
+ margin-inline-start: 8px;
}
-.torPreferences-bridgeCard-qrWrapper {
- grid-area: bridge-qr;
- display: block; /* So it doesn't stretch the child vertically. */
- margin-inline-end: 14px;
+#tor-bridges-built-in-display {
+ display: grid;
+ grid-template:
+ "type status" min-content
+ "description description" auto
+ / max-content 1fr;
+ gap: 4px 1.5em;
+ margin-block-end: 16px;
}
-.torPreferences-bridgeCard-qr {
- --qr-one: black;
- --qr-zero: white;
- background: var(--qr-zero);
- position: relative;
- padding: 4px;
- border-radius: 2px;
+#tor-bridges-built-in-display:not(.built-in-active) {
+ display: none;
}
-.torPreferences-bridgeCard-qrCode {
- width: 112px;
- height: 112px;
- /* Define these colors, as they will be passed to the QR code library */
- background: var(--qr-zero);
- color: var(--qr-one);
+#tor-bridges-built-in-type-name {
+ font-weight: 700;
+ grid-area: type;
}
-.torPreferences-bridgeCard-qrOnionBox {
- width: 28px;
- height: 28px;
- position: absolute;
- top: calc(50% - 14px);
- inset-inline-start: calc(50% - 14px);
- background: var(--qr-zero);
+#tor-bridges-built-in-connected {
+ grid-area: status;
+ justify-self: end;
}
-.torPreferences-bridgeCard-qrOnion {
- width: 16px;
- height: 16px;
- position: absolute;
- top: calc(50% - 8px);
- inset-inline-start: calc(50% - 8px);
+#tor-bridges-built-in-description {
+ grid-area: description;
+}
- mask: url("chrome://browser/content/torpreferences/bridge-qr-onion-mask.svg");
- mask-repeat: no-repeat;
- mask-size: 16px;
- background: var(--qr-one);
+#tor-bridges-grid-display {
+ display: grid;
+ grid-template-columns: max-content repeat(4, max-content) 1fr;
+ --tor-bridges-grid-column-gap: 8px;
+ --tor-bridges-grid-column-short-gap: 4px;
}
-.torPreferences-bridgeCard-qr:hover .torPreferences-bridgeCard-qrOnionBox {
- background: var(--qr-one);
+#tor-bridges-grid-display:not(.grid-active) {
+ display: none;
}
-.torPreferences-bridgeCard-qr:hover .torPreferences-bridgeCard-qrOnion {
- mask: url("chrome://global/skin/icons/search-glass.svg");
- background: var(--qr-zero);
+.tor-bridges-grid-row {
+ /* We want each row to act as a row of three items in the
+ * #tor-bridges-grid-display grid layout.
+ * We also want a 16px spacing between rows, and 8px spacing between columns,
+ * which are outside the .tor-bridges-grid-cell's border area. So that
+ * clicking these gaps will not focus any item, and their focus outlines do
+ * not overlap.
+ * Moreover, we also want each row to show its .tor-bridges-options-cell when
+ * the .tor-bridges-grid-row has :hover.
+ *
+ * We could use "display: contents" on the row and set a "gap: 16px 8px" on
+ * the parent so that its items fall into the parent layout. However, the gap
+ * between the items would mean there are places where no row has :hover. So
+ * if the user glided across the grid, the options button would visibly
+ * disappear any time the pointer entered a gap, causing the display to feel
+ * "jumpy".
+ *
+ * Instead, we use a "subgrid" layout for each .tor-bridges-grid-row, and
+ * using padding, rather than a gap, for the vertical spacing. Therefore,
+ * every part of the grid is covered by a row, so moving the pointer over the
+ * grid will always have one row with :hover, so one of the options cell will
+ * always be visible.
+ */
+ display: grid;
+ grid-column: 1 / -1;
+ grid-template-columns: subgrid;
+ /* Add 16px gap between rows, plus 8px at the start and end of the grid. */
+ padding-block: 8px;
}
-.torPreferences-bridgeCard-grid {
- height: 0; /* We will set it in JS when expanding it! */
+.tor-bridges-grid-cell:focus-visible {
+ outline: var(--in-content-focus-outline);
+ outline-offset: var(--in-content-focus-outline-offset);
+}
+
+.tor-bridges-grid-cell {
+ /* The cell is stretched to the height of the row, so that each focus outline
+ * shares the same height, but we want to center-align the content within,
+ * which is either a single Element or a TextNode. */
display: grid;
- grid-template-rows: auto 1fr;
- grid-template-columns: auto 1fr auto;
- grid-template-areas:
- 'bridge-qr bridge-share bridge-share'
- 'bridge-qr bridge-address bridge-address'
- 'bridge-qr bridge-learn-more bridge-copy';
- visibility: hidden;
+ align-content: center;
}
-.expanded .torPreferences-bridgeCard-grid,
-.currently-connected .torPreferences-bridgeCard-grid,
-.single-card .torPreferences-bridgeCard-grid {
- padding-top: 12px;
- visibility: visible;
+.tor-bridges-type-cell {
+ margin-inline-end: var(--tor-bridges-grid-column-gap);
}
-.currently-connected .torPreferences-bridgeCard-grid,
-.single-card .torPreferences-bridgeCard-grid {
- height: auto;
+.tor-bridges-emojis-block {
+ /* Emoji block occupies four columns, but with a smaller gap. */
+ display: contents;
}
-.torPreferences-bridgeCard-grid.to-animate {
- transition: height var(--bridgeCard-animation-time) ease-out, visibility var(--bridgeCard-animation-time);
- overflow: hidden;
+.tor-bridges-emoji-cell:not(:last-child) {
+ margin-inline-end: var(--tor-bridges-grid-column-short-gap);
}
-.torPreferences-bridgeCard-share {
- grid-area: bridge-share;
+.tor-bridges-emoji-icon {
+ display: block;
+ box-sizing: content-box;
+ width: 16px;
+ height: 16px;
+ background: var(--in-content-button-background);
+ border-radius: 4px;
+ padding: 8px;
}
-.torPreferences-bridgeCard-addrBox {
- grid-area: bridge-address;
+.tor-bridges-grid-end-block {
+ /* The last three cells all share a single grid item slot in the
+ * #tor-bridges-grid-display layout.
+ * This is because we do not want to align its cells between rows. */
+ min-width: max-content;
display: flex;
- align-items: center;
- justify-content: center;
- margin: 8px 0;
+ /* Choose "stretch" instead of "center" so that focus outline is a consistent
+ * height between cells. */
+ align-items: stretch;
+ margin-inline-start: var(--tor-bridges-grid-column-gap);
+ gap: var(--tor-bridges-grid-column-gap);
}
-input.torPreferences-bridgeCard-addr {
- width: 100%;
+.tor-bridges-address-cell {
+ /* base size */
+ width: 10em;
+ flex: 1 0 auto;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
color: var(--text-color-deemphasized);
}
-.torPreferences-bridgeCard-leranMoreBox {
- grid-area: bridge-learn-more;
+.tor-bridges-status-cell,
+.tor-bridges-options-cell {
+ flex: 0 0 auto;
}
-.torPreferences-bridgeCard-copy {
- grid-area: bridge-copy;
+/* Hide the options button if the row does not have hover or focus. */
+.tor-bridges-grid-row:not(
+ :hover,
+ :focus-within
+) .tor-bridges-options-cell,
+/* Hide the status cell when it shows "No status" if the cell does not have
+ * focus. */
+.tor-bridges-grid-row.hide-status .tor-bridges-status-cell:not(:focus) {
+ /* Still accessible to screen reader, but not visual and does not contribute
+ * to the parent flex layout. */
+ /* NOTE: We assume that the height of these cell's content is equal to or less
+ * than the other cells, so there won't be a jump in row height when they
+ * become visual again and contribute to the layout. */
+ position: absolute;
+ clip-path: inset(50%);
}
-#torPreferences-bridgeCard-template {
- display: none;
+#tor-bridges-share {
+ margin-block-start: 24px;
+ border-radius: 4px;
+ border: 1px solid var(--in-content-border-color);
+ padding: 16px;
+ display: grid;
+ grid-template:
+ "heading heading heading" min-content
+ /* If the description spans one line, it will be center-aligned with the
+ * buttons, otherwise it will start to expand upwards. */
+ "description . ." 1fr
+ "description copy qr" min-content
+ / 1fr max-content max-content;
+ gap: 0 8px;
+ align-items: center;
+}
+
+#tor-bridges-share-heading {
+ grid-area: heading;
+ font-size: inherit;
+ margin: 0;
+ font-weight: 700;
+}
+
+#tor-bridges-share-description {
+ grid-area: description;
+}
+
+#tor-bridges-copy-addresses-button {
+ grid-area: copy;
+ margin: 0;
+ /* Match the QR height if it is higher than ours. */
+ min-height: auto;
+ line-height: 1;
+ align-self: stretch;
+}
+
+#tor-bridges-qr-addresses-button {
+ grid-area: qr;
+ padding: 5px;
+ margin: 0;
+ min-height: auto;
+ min-width: auto;
+ box-sizing: content-box;
+ width: 24px;
+ height: 24px;
+ background-image: url("chrome://browser/content/torpreferences/bridge-qr.svg");
+ background-repeat: no-repeat;
+ background-position: center center;
+ background-origin: content-box;
+ background-size: contain;
+ -moz-context-properties: fill;
+ fill: currentColor;
+}
+
+#torPreferences-bridges-location {
+ width: 280px;
+}
+
+#torPreferences-bridges-location menuitem[disabled="true"] {
+ color: var(--in-content-button-text-color, inherit);
+ font-weight: 700;
}
/* Advanced Settings */
@@ -446,10 +555,6 @@ dialog#torPreferences-requestBridge-dialog > hbox {
font-weight: 700;
}
-.builtin-bridges-option .torPreferences-current-bridge-badge {
- color: var(--in-content-accent-color);
-}
-
/* Request bridge dialog */
/*
This hbox is hidden by css here by default so that the
=====================================
browser/components/torpreferences/jar.mn
=====================================
@@ -1,4 +1,6 @@
browser.jar:
+ content/browser/torpreferences/bridge.svg (content/bridge.svg)
+ content/browser/torpreferences/bridge-qr.svg (content/bridge-qr.svg)
content/browser/torpreferences/bridgeQrDialog.xhtml (content/bridgeQrDialog.xhtml)
content/browser/torpreferences/bridgeQrDialog.mjs (content/bridgeQrDialog.mjs)
content/browser/torpreferences/builtinBridgeDialog.xhtml (content/builtinBridgeDialog.xhtml)
=====================================
browser/locales/en-US/browser/tor-browser.ftl
=====================================
@@ -44,3 +44,80 @@ tor-browser-home-message-testing = This is an unstable version of Tor Browser fo
# Shown in Home settings, corresponds to the default about:tor home page.
home-mode-choice-tor =
.label = Tor Browser Home
+
+## Tor Bridges Settings
+
+# Toggle button for enabling and disabling the use of bridges.
+tor-bridges-use-bridges =
+ .label = Use bridges
+
+tor-bridges-none-added = No bridges added
+tor-bridges-your-bridges = Your bridges
+tor-bridges-source-user = Added by you
+tor-bridges-source-built-in = Built-in
+tor-bridges-source-requested = Requested from Tor
+# The "..." menu button for all current bridges.
+tor-bridges-options-button =
+ .title = All bridges
+# Shown in the "..." menu for all bridges when the user can generate a QR code for all of their bridges.
+tor-bridges-menu-item-qr-all-bridge-addresses = Show QR code
+ .accesskey = Q
+# Shown in the "..." menu for all bridges when the user can copy all of their bridges.
+tor-bridges-menu-item-copy-all-bridge-addresses = Copy bridge addresses
+ .accesskey = C
+# Only shown in the "..." menu for bridges added by the user.
+tor-bridges-menu-item-edit-all-bridges = Edit bridges
+ .accesskey = E
+# Shown in the "..." menu for all current bridges.
+tor-bridges-menu-item-remove-all-bridges = Remove all bridges
+ .accesskey = R
+
+# Shown when one of the built-in bridges is in use.
+tor-bridges-built-in-status-connected = Connected
+
+# Shown at the start of a Tor bridge line.
+# $type (String) - The Tor bridge type ("snowflake", "obfs4", "meek-azure").
+tor-bridges-type-prefix = { $type } bridge:
+# The name and accessible description for a bridge emoji cell. Each bridge address can be hashed into four emojis shown to the user (bridgemoji feature). This cell corresponds to a *single* such emoji. The "title" should just be emojiName. The "aria-description" should give screen reader users enough of a hint that the cell contains a single emoji.
+# $emojiName (String) - The name of the emoji, already localized.
+# E.g. with Orca screen reader in en-US this would read "unicorn. Row 2 Column 2. Emoji".
+tor-bridges-emoji-cell =
+ .title = { $emojiName }
+ .aria-description = Emoji
+# The emoji name to show on hover when a bridge emoji's name is unknown.
+tor-bridges-emoji-unknown = Unknown
+# Shown when the bridge has been used for the most recent Tor circuit, i.e. the most recent bridge we have connected to.
+tor-bridges-status-connected = Connected
+# Used when the bridge has no status, i.e. the *absence* of a status to report to the user. This is only visibly shown when the status cell has keyboard focus.
+tor-bridges-status-none = No status
+# The "..." menu button for an individual bridge row.
+tor-bridges-individual-bridge-options-button =
+ .title = Bridge options
+# Shown in the "..." menu for an individual bridge. Shows the QR code for this one bridge.
+tor-bridges-menu-item-qr-address = Show QR code
+ .accesskey = Q
+# Shown in the "..." menu for an individual bridge. Copies the single bridge address to clipboard.
+tor-bridges-menu-item-copy-address = Copy bridge address
+ .accesskey = C
+# Shown in the "..." menu for an individual bridge. Removes this one bridge.
+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.
+tor-bridges-update-removed-all-bridges = All of your Tor bridges have been removed.
+# Update text for screen reader users when their bridges have changed in some arbitrary way.
+tor-bridges-update-changed-bridges = Your Tor bridges have changed.
+
+# Shown for requested bridges and bridges added by the user.
+tor-bridges-share-heading = Help others connect
+#
+tor-bridges-share-description = Share your bridges with trusted contacts.
+tor-bridges-copy-addresses-button = Copy addresses
+tor-bridges-qr-addresses-button =
+ .title = Show QR code
=====================================
toolkit/modules/TorSettings.sys.mjs
=====================================
@@ -175,6 +175,14 @@ class TorSettingsImpl {
allowed_ports: [],
},
};
+ /**
+ * Accumulated errors from trying to set settings.
+ *
+ * Only added to if not null.
+ *
+ * @type {Array<Error>?}
+ */
+ #settingErrors = null;
/**
* The recommended pluggable transport.
@@ -224,16 +232,33 @@ class TorSettingsImpl {
enabled: {},
});
this.#addProperties("bridges", {
+ /**
+ * Whether the bridges are enabled or not.
+ *
+ * @type {boolean}
+ */
enabled: {},
+ /**
+ * The current bridge source.
+ *
+ * @type {integer}
+ */
source: {
- transform: val => {
+ transform: (val, addError) => {
if (Object.values(TorBridgeSource).includes(val)) {
return val;
}
- lazy.logger.error(`Not a valid bridge source: "${val}"`);
+ addError(`Not a valid bridge source: "${val}"`);
return TorBridgeSource.Invalid;
},
},
+ /**
+ * The current bridge strings.
+ *
+ * Can only be non-empty if the "source" is not Invalid.
+ *
+ * @type {Array<string>}
+ */
bridge_strings: {
transform: val => {
if (Array.isArray(val)) {
@@ -244,13 +269,15 @@ class TorSettingsImpl {
copy: val => [...val],
equal: (val1, val2) => this.#arrayEqual(val1, val2),
},
+ /**
+ * The built-in type to use when using the BuiltIn "source", or empty when
+ * using any other source.
+ *
+ * @type {string}
+ */
builtin_type: {
- callback: val => {
+ callback: (val, addError) => {
if (!val) {
- // Make sure that the source is not BuiltIn
- if (this.bridges.source === TorBridgeSource.BuiltIn) {
- this.bridges.source = TorBridgeSource.Invalid;
- }
return;
}
const bridgeStrings = this.#getBuiltinBridges(val);
@@ -258,39 +285,28 @@ class TorSettingsImpl {
this.bridges.bridge_strings = bridgeStrings;
return;
}
- lazy.logger.error(`No built-in ${val} bridges found`);
- // Change to be empty, this will trigger this callback again,
- // but with val as "".
- this.bridges.builtin_type == "";
+
+ addError(`No built-in ${val} bridges found`);
+ // Set as invalid, which will make the builtin_type "" and set the
+ // bridge_strings to be empty at the next #cleanupSettings.
+ this.bridges.source = TorBridgeSource.Invalid;
},
},
});
this.#addProperties("proxy", {
- enabled: {
- callback: val => {
- if (val) {
- return;
- }
- // Reset proxy settings.
- this.proxy.type = TorProxyType.Invalid;
- this.proxy.address = "";
- this.proxy.port = 0;
- this.proxy.username = "";
- this.proxy.password = "";
- },
- },
+ enabled: {},
type: {
- transform: val => {
+ transform: (val, addError) => {
if (Object.values(TorProxyType).includes(val)) {
return val;
}
- lazy.logger.error(`Not a valid proxy type: "${val}"`);
+ addError(`Not a valid proxy type: "${val}"`);
return TorProxyType.Invalid;
},
},
address: {},
port: {
- transform: val => {
+ transform: (val, addError) => {
if (val === 0) {
// This is a valid value that "unsets" the port.
// Keep this value without giving a warning.
@@ -298,15 +314,11 @@ class TorSettingsImpl {
return 0;
}
// Unset to 0 if invalid null is returned.
- return this.#parsePort(val, false) ?? 0;
+ return this.#parsePort(val, false, addError) ?? 0;
},
},
- username: {
- transform: val => val ?? "",
- },
- password: {
- transform: val => val ?? "",
- },
+ username: {},
+ password: {},
uri: {
getter: () => {
const { type, address, port, username, password } = this.proxy;
@@ -329,20 +341,16 @@ class TorSettingsImpl {
},
});
this.#addProperties("firewall", {
- enabled: {
- callback: val => {
- if (!val) {
- this.firewall.allowed_ports = "";
- }
- },
- },
+ enabled: {},
allowed_ports: {
- transform: val => {
+ transform: (val, addError) => {
if (!Array.isArray(val)) {
val = val === "" ? [] : val.split(",");
}
// parse and remove duplicates
- const portSet = new Set(val.map(p => this.#parsePort(p, true)));
+ const portSet = new Set(
+ val.map(p => this.#parsePort(p, true, addError))
+ );
// parsePort returns null for failed parses, so remove it.
portSet.delete(null);
return [...portSet];
@@ -353,6 +361,39 @@ class TorSettingsImpl {
});
}
+ /**
+ * Clean the setting values after making some changes, so that the values do
+ * not contradict each other.
+ */
+ #cleanupSettings() {
+ this.freezeNotifications();
+ try {
+ if (this.bridges.source === TorBridgeSource.Invalid) {
+ this.bridges.enabled = false;
+ this.bridges.bridge_strings = [];
+ }
+ if (!this.bridges.bridge_strings.length) {
+ this.bridges.enabled = false;
+ this.bridges.source = TorBridgeSource.Invalid;
+ }
+ if (this.bridges.source !== TorBridgeSource.BuiltIn) {
+ this.bridges.builtin_type = "";
+ }
+ if (!this.proxy.enabled) {
+ this.proxy.type = TorProxyType.Invalid;
+ this.proxy.address = "";
+ this.proxy.port = 0;
+ this.proxy.username = "";
+ this.proxy.password = "";
+ }
+ if (!this.firewall.enabled) {
+ this.firewall.allowed_ports = [];
+ }
+ } finally {
+ this.thawNotifications();
+ }
+ }
+
/**
* The current number of freezes applied to the notifications.
*
@@ -435,6 +476,13 @@ class TorSettingsImpl {
const group = {};
for (const name in propParams) {
const { getter, transform, callback, copy, equal } = propParams[name];
+ // Method for adding setting errors.
+ const addError = message => {
+ message = `TorSettings.${groupname}.${name}: ${message}`;
+ lazy.logger.error(message);
+ // Only add to #settingErrors if it is not null.
+ this.#settingErrors?.push(message);
+ };
Object.defineProperty(group, name, {
get: getter
? () => {
@@ -467,16 +515,20 @@ class TorSettingsImpl {
this.freezeNotifications();
try {
if (transform) {
- val = transform(val);
+ val = transform(val, addError);
}
const isEqual = equal ? equal(val, prevVal) : val === prevVal;
if (!isEqual) {
- if (callback) {
- callback(val);
- }
+ // Set before the callback.
this.#settings[groupname][name] = val;
this.#notificationQueue.add(`${groupname}.${name}`);
+
+ if (callback) {
+ callback(val, addError);
+ }
}
+ } catch (e) {
+ addError(e.message);
} finally {
this.thawNotifications();
}
@@ -503,11 +555,12 @@ class TorSettingsImpl {
* @param {string|integer} val - The value to parse.
* @param {boolean} trim - Whether a string value can be stripped of
* whitespace before parsing.
+ * @param {function} addError - Callback to add error messages to.
*
* @return {integer?} - The port number, or null if the given value was not
* valid.
*/
- #parsePort(val, trim) {
+ #parsePort(val, trim, addError) {
if (typeof val === "string") {
if (trim) {
val = val.trim();
@@ -516,12 +569,12 @@ class TorSettingsImpl {
if (this.#portRegex.test(val)) {
val = Number.parseInt(val, 10);
} else {
- lazy.logger.error(`Invalid port string "${val}"`);
+ addError(`Invalid port string "${val}"`);
return null;
}
}
if (!Number.isInteger(val) || val < 1 || val > 65535) {
- lazy.logger.error(`Port out of range: ${val}`);
+ addError(`Port out of range: ${val}`);
return null;
}
return val;
@@ -739,6 +792,8 @@ class TorSettingsImpl {
""
);
}
+
+ this.#cleanupSettings();
}
/**
@@ -748,6 +803,7 @@ class TorSettingsImpl {
lazy.logger.debug("saveToPrefs()");
this.#checkIfInitialized();
+ this.#cleanupSettings();
/* Quickstart */
Services.prefs.setBoolPref(
@@ -847,6 +903,8 @@ class TorSettingsImpl {
async #applySettings(allowUninitialized) {
lazy.logger.debug("#applySettings()");
+ this.#cleanupSettings();
+
const settingsMap = new Map();
// #applySettings can be called only when #allowUninitialized is false
@@ -928,6 +986,8 @@ class TorSettingsImpl {
const backup = this.getSettings();
const backupNotifications = [...this.#notificationQueue];
+ // Start collecting errors.
+ this.#settingErrors = [];
// Hold off on lots of notifications until all settings are changed.
this.freezeNotifications();
@@ -946,25 +1006,11 @@ class TorSettingsImpl {
case TorBridgeSource.UserProvided:
this.bridges.bridge_strings = settings.bridges.bridge_strings;
break;
- case TorBridgeSource.BuiltIn: {
+ case TorBridgeSource.BuiltIn:
this.bridges.builtin_type = settings.bridges.builtin_type;
- if (!this.bridges.bridge_strings.length) {
- // No bridges were found when setting the builtin_type.
- throw new Error(
- `No available builtin bridges of type ${settings.bridges.builtin_type}`
- );
- }
break;
- }
case TorBridgeSource.Invalid:
break;
- default:
- if (settings.bridges.enabled) {
- throw new Error(
- `Bridge source '${settings.source}' is not a valid source`
- );
- }
- break;
}
}
@@ -985,6 +1031,12 @@ class TorSettingsImpl {
this.firewall.allowed_ports = settings.firewall.allowed_ports;
}
}
+
+ this.#cleanupSettings();
+
+ if (this.#settingErrors.length) {
+ throw Error(this.#settingErrors.join("; "));
+ }
} catch (ex) {
// Restore the old settings without any new notifications generated from
// the above code.
@@ -1001,6 +1053,8 @@ class TorSettingsImpl {
lazy.logger.error("setSettings failed", ex);
} finally {
this.thawNotifications();
+ // Stop collecting errors.
+ this.#settingErrors = null;
}
lazy.logger.debug("setSettings result", this.#settings);
=====================================
toolkit/modules/TorStrings.sys.mjs
=====================================
@@ -3,8 +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/.
-"use strict";
-
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
@@ -86,7 +84,6 @@ const Loader = {
statusTorNotConnected: "Not Connected",
statusTorBlocked: "Potentially Blocked",
learnMore: "Learn more",
- whatAreThese: "What are these?",
// Quickstart
quickstartHeading: "Quickstart",
quickstartDescription:
@@ -101,22 +98,10 @@ const Loader = {
bridgeLocationFrequent: "Frequently selected locations",
bridgeLocationOther: "Other locations",
bridgeChooseForMe: "Choose a Bridge For Me…",
- bridgeCurrent: "Your Current Bridges",
- bridgeCurrentDescription:
- "You can keep one or more bridges saved, and Tor will choose which one to use when you connect. Tor will automatically switch to use another bridge when needed.",
- bridgeId: "%1$S bridge: %2$S",
currentBridge: "Current bridge",
- connectedBridge: "Connected",
remove: "Remove",
bridgeDisableBuiltIn: "Disable built-in bridges",
- bridgeShare:
- "Share this bridge using the QR code or by copying its address:",
- bridgeCopy: "Copy Bridge Address",
copied: "Copied!",
- bridgeShowAll: "Show All Bridges",
- bridgeShowFewer: "Show Fewer Bridges",
- allBridgesEnabled: "Use current bridges",
- bridgeRemoveAll: "Remove All Bridges",
bridgeRemoveAllDialogTitle: "Remove all bridges?",
bridgeRemoveAllDialogDescription:
"If these bridges were received from torproject.org or added manually, this action cannot be undone",
@@ -199,8 +184,6 @@ const Loader = {
...tsb.getStrings(strings),
learnMoreTorBrowserURL: "about:manual#about",
learnMoreBridgesURL: "about:manual#bridges",
- learnMoreBridgesCardURL: "about:manual#bridges_bridge-moji",
- learnMoreCircumventionURL: "about:manual#circumvention",
};
} /* Tor Network Settings Strings */,
=====================================
toolkit/torbutton/chrome/locale/en-US/settings.properties
=====================================
@@ -32,23 +32,11 @@ settings.bridgeLocationAutomatic=Automatic
settings.bridgeLocationFrequent=Frequently selected locations
settings.bridgeLocationOther=Other locations
settings.bridgeChooseForMe=Choose a Bridge For Me…
-settings.bridgeCurrent=Your Current Bridges
-settings.bridgeCurrentDescription=You can keep one or more bridges saved, and Tor will choose which one to use when you connect. Tor will automatically switch to use another bridge when needed.
-# Translation note: %1$S = bridge type; %2$S = bridge emoji id
-settings.bridgeId=%1$S bridge: %2$S
-settings.connectedBridge=Connected
settings.currentBridge=Current bridge
settings.remove=Remove
settings.bridgeDisableBuiltIn=Disable built-in bridges
-settings.bridgeShare=Share this bridge using the QR code or by copying its address:
-settings.whatAreThese=What are these?
-settings.bridgeCopy=Copy Bridge Address
settings.copied=Copied!
-settings.bridgeShowAll=Show All Bridges
-settings.bridgeShowFewer=Show Fewer Bridges
-settings.allBridgesEnabled=Use current bridges
-settings.bridgeRemoveAll=Remove All Bridges
settings.bridgeRemoveAllDialogTitle=Remove all bridges?
settings.bridgeRemoveAllDialogDescription=If these bridges were received from torproject.org or added manually, this action cannot be undone
settings.bridgeAdd=Add a New Bridge
@@ -121,3 +109,19 @@ settings.allowedPortsPlaceholder=Comma-separated values
# Log dialog
settings.torLogDialogTitle=Tor Logs
settings.copyLog=Copy Tor Log to Clipboard
+
+
+# TODO: Remove
+
+settings.bridgeCurrent=Your Current Bridges
+settings.bridgeCurrentDescription=You can keep one or more bridges saved, and Tor will choose which one to use when you connect. Tor will automatically switch to use another bridge when needed.
+# Translation note: %1$S = bridge type; %2$S = bridge emoji id
+settings.bridgeId=%1$S bridge: %2$S
+settings.connectedBridge=Connected
+settings.bridgeShare=Share this bridge using the QR code or by copying its address:
+settings.whatAreThese=What are these?
+settings.bridgeCopy=Copy Bridge Address
+settings.bridgeShowAll=Show All Bridges
+settings.bridgeShowFewer=Show Fewer Bridges
+settings.allBridgesEnabled=Use current bridges
+settings.bridgeRemoveAll=Remove All Bridges
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/cbeecf…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/cbeecf…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-update-responses][main] release: new version, 13.0.9
by richard (@richard) 23 Jan '24
by richard (@richard) 23 Jan '24
23 Jan '24
richard pushed to branch main at The Tor Project / Applications / Tor Browser update responses
Commits:
870a5f0b by Richard Pospesel at 2024-01-23T13:22:31+00:00
release: new version, 13.0.9
- - - - -
20 changed files:
- update_3/release/13.0.6-13.0.9-linux-i686-ALL.xml
- update_3/release/13.0.6-13.0.9-linux-x86_64-ALL.xml
- update_3/release/13.0.6-13.0.9-macos-ALL.xml
- update_3/release/13.0.6-13.0.9-windows-i686-ALL.xml
- update_3/release/13.0.6-13.0.9-windows-x86_64-ALL.xml
- update_3/release/13.0.7-13.0.9-linux-i686-ALL.xml
- update_3/release/13.0.7-13.0.9-linux-x86_64-ALL.xml
- update_3/release/13.0.7-13.0.9-macos-ALL.xml
- update_3/release/13.0.7-13.0.9-windows-i686-ALL.xml
- update_3/release/13.0.7-13.0.9-windows-x86_64-ALL.xml
- update_3/release/13.0.8-13.0.9-linux-i686-ALL.xml
- update_3/release/13.0.8-13.0.9-linux-x86_64-ALL.xml
- update_3/release/13.0.8-13.0.9-macos-ALL.xml
- update_3/release/13.0.8-13.0.9-windows-i686-ALL.xml
- update_3/release/13.0.8-13.0.9-windows-x86_64-ALL.xml
- update_3/release/13.0.9-linux-i686-ALL.xml
- update_3/release/13.0.9-linux-x86_64-ALL.xml
- update_3/release/13.0.9-macos-ALL.xml
- update_3/release/13.0.9-windows-i686-ALL.xml
- update_3/release/13.0.9-windows-x86_64-ALL.xml
Changes:
=====================================
update_3/release/13.0.6-13.0.9-linux-i686-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686-13…" hashFunction="SHA512" hashValue="d36e7dd3a39de83fb9a71fd5a8b7262aca98bb97953287fe9a3ab90c0a95ad97c40d240834d702b57182b599efc21f92aa15b6b62f5e180f0617d8fb678e035e" size="121912046" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686--1…" hashFunction="SHA512" hashValue="e347bbb01cab053e900bece0cc37f4b80170c5ccdfd41504d88ad3c9b1cfd9eda836257db99f8311ad7f2974c52293c38280be9cc2c28392aa491f1b883c25c4" size="14969289" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686-13…" hashFunction="SHA512" hashValue="09f66723dade529f77267546a8cab70bb71c8621bc7c67e7975ce67139c0f8b305125b4cf38b4a77cea4182c382a215013ac718dc6ee3561b3046266f2d234c8" size="121912074" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686--1…" hashFunction="SHA512" hashValue="b539153addf108f35781a2564a84f659a42fc0c01fdc5f05b50747abc4eafefa4a5630918d82e502421fb56c24d9307c3d50f00dfb8ba5d22dbb7c6d8dc0ef58" size="14969329" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.6-13.0.9-linux-x86_64-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="4c26112f794839450194bac302923e4fe8f4f9d15568b638b876760ed064e21f1c1eae0262f8b68d628e4ea78ad0466b4e841b859dc6183da1caf9d622590597" size="121131338" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="4a4d0aa0fe3ff7ba342286a08af1dff0b024fb6db37b43f31b481f4148364ce833d7c48de8eb4a1831244968225a8473077d4cea4cd14a306b3dac2fc67bc397" size="14359671" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="d4f4a4a2a403dc597e475cc335a83e42774ff571baac4e1385e19a8d199a6fa9cae1ac42ee8d082b0ec01fb5e34f02c5482cd2723203c8d282c67349e43f5be8" size="121131366" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="f4a2e180f439e1f94c2f8da7b1e46d6b35e2c77919ae0e65fc3fcfbc2d89ab565e28e23c57a87980100ad9459863b7eaf7e6258443ff61a444691ffb4af51291" size="14359711" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.6-13.0.9-macos-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="16.0.0"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos-13.0.9_…" hashFunction="SHA512" hashValue="86aaf0b4deb8eab82620ceb4829783e652735174e1106e92981aeb2ad7c5eeb576fbef9114d75f038ff26ae67bdc3e74c042677d28e18174d5ac1447b9cff0fe" size="169201961" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos--13.0.6…" hashFunction="SHA512" hashValue="bafe6b8d4b68915b41e16a31b44ad1237841d234c5f39546d1a09f7f251f4e2d79c1be5eaccbb7216887603931b850a151000869ca302b6bb7a2b3f5b8d58db3" size="46429595" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="16.0.0"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos-13.0.9_…" hashFunction="SHA512" hashValue="b407a03608c5b816ab80cce5866d8126eabc68796cb022dafcb9b80f654aafe56eceb0f30477007f930ee8392c06817c5ce0873124cf325238ed6246eee6d870" size="169203413" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos--13.0.6…" hashFunction="SHA512" hashValue="d6b0de2b7d506ecde5f393a10d3392ceb9dd28bd7e9d75242b9a2a069257518c86d776602af374eae9bbf8d49f9cbb3a379561f8691f46ad36904d94ef2b07e2" size="46430283" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.6-13.0.9-windows-i686-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="475cd3c2d31d174fbd30ac592da6d4b9257c61baf09832903bc07a5dac55089131738e3edbc2def7561c6477a2d3db78857249337496473be57c966b7ef809a7" size="109175033" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="82084ddf48c29805bfdf7d7c56282018915cbbaebcfa3710428bcd9e023f17a1b9c7013f1c211b4144142face81edc19214f2c1a9de5bcd02a7c7307ae53331e" size="21150270" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="a220ac388f2974ee8c8f4162f6d3258ba3412875d1fd5bb0e7c52cf01fcbbcb548e2875753807c3b8a7feef4b01788940d7dd506343c3b817fbc7b419848db8b" size="109175061" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="08db2cfaadd37427cae650fcd1a2cd884b3b5124a2004bfeea8633a810b31fca142160e2495d06ec73fe6370e6edeb1df7d6c9c781531fa55d91908a66d8ddda" size="21150310" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.6-13.0.9-windows-x86_64-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="d196e644f3a75bd877219c1e1e2fc070affdb312826696dc928e2fdec3d6bc513dfb7aac005b115192446a56ab188649accf3c89ec729855176e87af82c6915e" size="109340435" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="8f48e537a44c0a506c7d5004b8cc4bac4f9ba503a1303935b7ee250396bca84ad34d739c8fcacf7800098c9776d8eec845900145ae86093e7da590d384f1be6b" size="20153176" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="071dc63319396dfe4a835d57646a4dcae73942d10a1b11f3967494d3a92e16664c9bc77127a5ffee357b7b32bc73ba3bf7665a40208f90e94093238e2a0e4bb2" size="109340463" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="1b78269c4d8c9a8522ab29eda37ba4efed7f472320411703247f5b9019f3c558ac2026b4eaab8c8b239599d6d4df5944aa48a0de5757d4784dc0c0b021810793" size="20153216" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.7-13.0.9-linux-i686-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686-13…" hashFunction="SHA512" hashValue="d36e7dd3a39de83fb9a71fd5a8b7262aca98bb97953287fe9a3ab90c0a95ad97c40d240834d702b57182b599efc21f92aa15b6b62f5e180f0617d8fb678e035e" size="121912046" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686--1…" hashFunction="SHA512" hashValue="7a2865c0f36bfde2084de0e9f7a35ecfa0c5850ef3e79b3d3b2a4fe13bb79681059c515722ae46dd46d3ee904a9d33309f15c57654ffcfbe374b14ffd4064258" size="11560810" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686-13…" hashFunction="SHA512" hashValue="09f66723dade529f77267546a8cab70bb71c8621bc7c67e7975ce67139c0f8b305125b4cf38b4a77cea4182c382a215013ac718dc6ee3561b3046266f2d234c8" size="121912074" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686--1…" hashFunction="SHA512" hashValue="26ab2df3f8c18c92a7b5c8af181fbfd697d97e9164d9f820056a5c3ce97c63dcb0c6dd76708232fcc37387324fd0c0f0d57b708f91aae3d82b5e958fb072b127" size="11560838" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.7-13.0.9-linux-x86_64-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="4c26112f794839450194bac302923e4fe8f4f9d15568b638b876760ed064e21f1c1eae0262f8b68d628e4ea78ad0466b4e841b859dc6183da1caf9d622590597" size="121131338" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="84c5ae313b6df6d901cdd08ec852deaaf8a89beeda1b3b53076e045fc06fdee36e48932a3c3321e6445b3c382ca489f8f1883959ed7c6a4603bd594a39312ecc" size="11429422" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="d4f4a4a2a403dc597e475cc335a83e42774ff571baac4e1385e19a8d199a6fa9cae1ac42ee8d082b0ec01fb5e34f02c5482cd2723203c8d282c67349e43f5be8" size="121131366" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="611af646854a54d98ede8da1d9a72e445c91d68b70b5bd697dede7bc399c5cf6c32b1b7c518e8312a27caf283a7283df0677f9d140e01c4e3eb2e80e9dd8de8c" size="11429450" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.7-13.0.9-macos-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="16.0.0"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos-13.0.9_…" hashFunction="SHA512" hashValue="86aaf0b4deb8eab82620ceb4829783e652735174e1106e92981aeb2ad7c5eeb576fbef9114d75f038ff26ae67bdc3e74c042677d28e18174d5ac1447b9cff0fe" size="169201961" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos--13.0.7…" hashFunction="SHA512" hashValue="0869d892ac0f2014534f46456668f8ce8595391afa8b3c28c5983cd71f08214b9a164f1c7d7367378f5b5f3b4e4e1f4d2f4a417487f29165f71f5c15bde28b54" size="42821574" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="16.0.0"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos-13.0.9_…" hashFunction="SHA512" hashValue="b407a03608c5b816ab80cce5866d8126eabc68796cb022dafcb9b80f654aafe56eceb0f30477007f930ee8392c06817c5ce0873124cf325238ed6246eee6d870" size="169203413" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos--13.0.7…" hashFunction="SHA512" hashValue="324eb7c8ed16e5eb6c455447e3bcd7bcb1387d27764901b99ddce24ab5e653dc9cd6db7f2164c4e295b71d772edf574c16e81a9235ba1cecc1b7f89fd4482d32" size="42827222" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.7-13.0.9-windows-i686-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="475cd3c2d31d174fbd30ac592da6d4b9257c61baf09832903bc07a5dac55089131738e3edbc2def7561c6477a2d3db78857249337496473be57c966b7ef809a7" size="109175033" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="25cf3385153b06ab684a4a2454cc44d57518caa7cabd3dede389dad686932dbc477d40165de8b2d954d43973e5065046aa380f9cc0542e48ce3e7b926037642b" size="18349134" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="a220ac388f2974ee8c8f4162f6d3258ba3412875d1fd5bb0e7c52cf01fcbbcb548e2875753807c3b8a7feef4b01788940d7dd506343c3b817fbc7b419848db8b" size="109175061" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="529d6c756a630f146534ffef87ab538c0ac2534c8e4b44b7fac2e96c0032ccb31b24953c8443626e028d07cc005d8d31a86adefd9a0ad8a6dbfd93f7066697b1" size="18349162" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.7-13.0.9-windows-x86_64-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="d196e644f3a75bd877219c1e1e2fc070affdb312826696dc928e2fdec3d6bc513dfb7aac005b115192446a56ab188649accf3c89ec729855176e87af82c6915e" size="109340435" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="0cb6d22edfeb0af7830a1ea86ea83314e620b04dc74e4ae3216011c72e31121fd130b4ec3ccb6af89e4269ca048d60efdb4a885c7d53c35eace2a0ee010d0c04" size="17160296" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="071dc63319396dfe4a835d57646a4dcae73942d10a1b11f3967494d3a92e16664c9bc77127a5ffee357b7b32bc73ba3bf7665a40208f90e94093238e2a0e4bb2" size="109340463" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="2fe6e0d4b0618d7a41a4d5604b1934d303531229475d8f7f62dc3914e4e0846a5abbf4e0238685ce9ddd013a805ce431b6d362c512ae4f102cc30b666eb63a72" size="17160324" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.8-13.0.9-linux-i686-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686-13…" hashFunction="SHA512" hashValue="d36e7dd3a39de83fb9a71fd5a8b7262aca98bb97953287fe9a3ab90c0a95ad97c40d240834d702b57182b599efc21f92aa15b6b62f5e180f0617d8fb678e035e" size="121912046" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686--1…" hashFunction="SHA512" hashValue="6ca7d353bf303a2302ef050cadf092920248a2601c3d9322bd59403679305319e502b4c916ac5f9d52733dba07883b9d0a086087e5a9ab40be35a81044503060" size="11561086" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686-13…" hashFunction="SHA512" hashValue="09f66723dade529f77267546a8cab70bb71c8621bc7c67e7975ce67139c0f8b305125b4cf38b4a77cea4182c382a215013ac718dc6ee3561b3046266f2d234c8" size="121912074" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686--1…" hashFunction="SHA512" hashValue="45d8f7cc409b94494e3bc8c5c205df499207bd768ad3a73fe2750316e886fca9a13f519c1d06de149e2b346b7049c4d651b2403bc670a2659241ac5e0e305a52" size="11561126" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.8-13.0.9-linux-x86_64-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="4c26112f794839450194bac302923e4fe8f4f9d15568b638b876760ed064e21f1c1eae0262f8b68d628e4ea78ad0466b4e841b859dc6183da1caf9d622590597" size="121131338" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="a487a662396760fb53c492fea9b5420866b6be2f2aa7a02fa79d1069caab63d484174f05c0917d48c64e55dcc36f7e0c38e34c12bb6b9f90652d82d25d57b4b4" size="11431798" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="d4f4a4a2a403dc597e475cc335a83e42774ff571baac4e1385e19a8d199a6fa9cae1ac42ee8d082b0ec01fb5e34f02c5482cd2723203c8d282c67349e43f5be8" size="121131366" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="f472d50fbb16f21feb5f4a941af2fe22ac002b562b599a6c42f3e497a142bbf19388a7577475fddabfaf7eb05a02b65693b9a08857cda839d108f2cdbd568372" size="11431838" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.8-13.0.9-macos-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="16.0.0"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos-13.0.9_…" hashFunction="SHA512" hashValue="86aaf0b4deb8eab82620ceb4829783e652735174e1106e92981aeb2ad7c5eeb576fbef9114d75f038ff26ae67bdc3e74c042677d28e18174d5ac1447b9cff0fe" size="169201961" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos--13.0.8…" hashFunction="SHA512" hashValue="b31ee3228b074853fa531d53d09994aacb2d25e5e64e2da69befb06f7dfc191f43268c60cec6cbdf84b6b3624f75483f8f7e5d36d6b0532314bd84dd50ee0088" size="42830202" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="16.0.0"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos-13.0.9_…" hashFunction="SHA512" hashValue="b407a03608c5b816ab80cce5866d8126eabc68796cb022dafcb9b80f654aafe56eceb0f30477007f930ee8392c06817c5ce0873124cf325238ed6246eee6d870" size="169203413" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos--13.0.8…" hashFunction="SHA512" hashValue="0305411dfdc93affa9d052bc8772acf66b63f5d48b5a76511eebc2e8ad9e27baa273d711c7e2eb7822b1fcdba3308d8a7fccf7934a8d3f48555757c7f6afeea0" size="42822506" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.8-13.0.9-windows-i686-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="475cd3c2d31d174fbd30ac592da6d4b9257c61baf09832903bc07a5dac55089131738e3edbc2def7561c6477a2d3db78857249337496473be57c966b7ef809a7" size="109175033" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="4067411ab90cb78f4045874d7b90ad69637fe351feba220e00cd56025408f8d43fa1a682e8b7ecf32fe0b8313b82516dffb231065a3b94f8b4fc6f1806c75e89" size="13209426" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="a220ac388f2974ee8c8f4162f6d3258ba3412875d1fd5bb0e7c52cf01fcbbcb548e2875753807c3b8a7feef4b01788940d7dd506343c3b817fbc7b419848db8b" size="109175061" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="541a6cf4ddcbed4877e93b7721fffa05caeba48a12619fcdb784a1ea19c989efa0dfce25547500330bf54bb4a182df6dd5a5c3e48f8238ee16eb79d2475066ed" size="13209466" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.8-13.0.9-windows-x86_64-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="d196e644f3a75bd877219c1e1e2fc070affdb312826696dc928e2fdec3d6bc513dfb7aac005b115192446a56ab188649accf3c89ec729855176e87af82c6915e" size="109340435" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="df61d91b79ca5a77df1144be8dbb874d8d60cd30b87ea46ec4583294d5bc5e0694c049f6de6a740db5093fa98e6720f77b4918f49c5f8e69d0bbc13f5fb7260e" size="11908612" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="071dc63319396dfe4a835d57646a4dcae73942d10a1b11f3967494d3a92e16664c9bc77127a5ffee357b7b32bc73ba3bf7665a40208f90e94093238e2a0e4bb2" size="109340463" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="824c8d9a0d6d4b5741c6725ff15914c79c17d378c941d44e4e27e11557b148a1c62cd49376581ca74a22eb044f64978734974d1456a6a5cd34c0f1241707a9bd" size="11908652" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.9-linux-i686-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686-13…" hashFunction="SHA512" hashValue="d36e7dd3a39de83fb9a71fd5a8b7262aca98bb97953287fe9a3ab90c0a95ad97c40d240834d702b57182b599efc21f92aa15b6b62f5e180f0617d8fb678e035e" size="121912046" type="complete"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686-13…" hashFunction="SHA512" hashValue="09f66723dade529f77267546a8cab70bb71c8621bc7c67e7975ce67139c0f8b305125b4cf38b4a77cea4182c382a215013ac718dc6ee3561b3046266f2d234c8" size="121912074" type="complete"></patch></update></updates>
=====================================
update_3/release/13.0.9-linux-x86_64-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="4c26112f794839450194bac302923e4fe8f4f9d15568b638b876760ed064e21f1c1eae0262f8b68d628e4ea78ad0466b4e841b859dc6183da1caf9d622590597" size="121131338" type="complete"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="d4f4a4a2a403dc597e475cc335a83e42774ff571baac4e1385e19a8d199a6fa9cae1ac42ee8d082b0ec01fb5e34f02c5482cd2723203c8d282c67349e43f5be8" size="121131366" type="complete"></patch></update></updates>
=====================================
update_3/release/13.0.9-macos-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="16.0.0"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos-13.0.9_…" hashFunction="SHA512" hashValue="86aaf0b4deb8eab82620ceb4829783e652735174e1106e92981aeb2ad7c5eeb576fbef9114d75f038ff26ae67bdc3e74c042677d28e18174d5ac1447b9cff0fe" size="169201961" type="complete"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="16.0.0"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos-13.0.9_…" hashFunction="SHA512" hashValue="b407a03608c5b816ab80cce5866d8126eabc68796cb022dafcb9b80f654aafe56eceb0f30477007f930ee8392c06817c5ce0873124cf325238ed6246eee6d870" size="169203413" type="complete"></patch></update></updates>
=====================================
update_3/release/13.0.9-windows-i686-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="475cd3c2d31d174fbd30ac592da6d4b9257c61baf09832903bc07a5dac55089131738e3edbc2def7561c6477a2d3db78857249337496473be57c966b7ef809a7" size="109175033" type="complete"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="a220ac388f2974ee8c8f4162f6d3258ba3412875d1fd5bb0e7c52cf01fcbbcb548e2875753807c3b8a7feef4b01788940d7dd506343c3b817fbc7b419848db8b" size="109175061" type="complete"></patch></update></updates>
=====================================
update_3/release/13.0.9-windows-x86_64-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="d196e644f3a75bd877219c1e1e2fc070affdb312826696dc928e2fdec3d6bc513dfb7aac005b115192446a56ab188649accf3c89ec729855176e87af82c6915e" size="109340435" type="complete"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="071dc63319396dfe4a835d57646a4dcae73942d10a1b11f3967494d3a92e16664c9bc77127a5ffee357b7b32bc73ba3bf7665a40208f90e94093238e2a0e4bb2" size="109340463" type="complete"></patch></update></updates>
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-update-responses…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-update-responses…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build] Pushed new tag tbb-13.0.9-build2
by richard (@richard) 23 Jan '24
by richard (@richard) 23 Jan '24
23 Jan '24
richard pushed new tag tbb-13.0.9-build2 at The Tor Project / Applications / tor-browser-build
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/tree/tbb…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][maint-13.0] Bug 41055: Prepare Tor Browser Stable 13.0.9
by richard (@richard) 23 Jan '24
by richard (@richard) 23 Jan '24
23 Jan '24
richard pushed to branch maint-13.0 at The Tor Project / Applications / tor-browser-build
Commits:
e5f6056c by Richard Pospesel at 2024-01-23T10:58:37+00:00
Bug 41055: Prepare Tor Browser Stable 13.0.9
- add tor-browser-build#41066 to changelog
- - - - -
2 changed files:
- projects/browser/Bundle-Data/Docs-TBB/ChangeLog.txt
- rbm.conf
Changes:
=====================================
projects/browser/Bundle-Data/Docs-TBB/ChangeLog.txt
=====================================
@@ -28,6 +28,8 @@ Tor Browser 13.0.9 - January 23 2024
* Bug 41016: Switch from bullseye to bookworm for desktop platforms [tor-browser-build]
* Windows
* Bug 41015: Enable std::filesystem on libc++ on Windows [tor-browser-build]
+ * Android
+ * Bug 41066: The Android x86 APK for 13.0.9 is too big [tor-browser-build]
Tor Browser 13.5a3 - December 22 2023
* All Platforms
=====================================
rbm.conf
=====================================
@@ -82,7 +82,7 @@ buildconf:
var:
torbrowser_version: '13.0.9'
- torbrowser_build: 'build1'
+ torbrowser_build: 'build2'
torbrowser_incremental_from:
- '[% IF c("var/tor-browser") %]13.0.8[% END %]'
- '13.0.7'
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/e…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/e…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][maint-13.0] Bug 41066: Compress the APKs more
by richard (@richard) 23 Jan '24
by richard (@richard) 23 Jan '24
23 Jan '24
richard pushed to branch maint-13.0 at The Tor Project / Applications / tor-browser-build
Commits:
4ae6c26c by Pier Angelo Vendrame at 2024-01-23T10:48:57+01:00
Bug 41066: Compress the APKs more
Our APK was refused by the Play Store as too big.
As a workaround, for this release we can try to compress the APK more,
by extracting it and repacking it with 7-zip.
The preferred and long-term solution would be to switch to Android App
Bundles, but this might require some changes to our signing scripts.
- - - - -
2 changed files:
- projects/browser/build.android
- projects/browser/config
Changes:
=====================================
projects/browser/build.android
=====================================
@@ -31,13 +31,19 @@ mv $rootdir/[% c('input_files_by_name/noscript') %] "$noscript_path"
mv $rootdir/allowed_addons.json $assets_dir/allowed_addons.json
-[% c('zip', {
- zip_src => [ '$assets_dir' ],
- zip_args => '$apk',
- }) %]
+mkdir apk
+pushd apk
+7zz x "$apk"
+cp -R ../assets ./
+find -type f -exec touch -m -t '[% USE date; date.format(pc("firefox-android", "timestamp"), format = "%Y%m%d%H%M") %]' {} \;
+find -type f ! -name resources.arsc -printf '%P\n' | sort > ../files.txt
+7zz a -tzip -mx9 -mtc- -spf ../repacked.apk @../files.txt
+# resources.arsc must not be compressed as per the APK specifications
+7zz a -tzip -mm=Copy -mtc- ../repacked.apk resources.arsc
+popd
aligned_apk=$(basename $apk .apk)_aligned.apk
-zipalign -vp 4 $apk $aligned_apk
+zipalign -vp 4 repacked.apk $aligned_apk
# Sign a QA build. This .apk is not a debug version and doesn't contain a debug
# flag in the manifest.
=====================================
projects/browser/config
=====================================
@@ -46,7 +46,13 @@ targets:
var:
verify_allowed_addons: 1
arch_deps:
- - openjdk-11-jdk-headless
+ - 7zip
+ - openjdk-17-jdk-headless
+ container:
+ # 7zip is in backports in bullseye, and we can already use Java 17 for
+ # apksigner.
+ suite: bookworm
+ arch: amd64
torbrowser:
var:
prefs_file: 000-tor-browser.js
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/4…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/4…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/firefox-android][firefox-android-115.2.1-13.5-1] fixup! Enable the connect assist experiments on alpha
by Pier Angelo Vendrame (@pierov) 23 Jan '24
by Pier Angelo Vendrame (@pierov) 23 Jan '24
23 Jan '24
Pier Angelo Vendrame pushed to branch firefox-android-115.2.1-13.5-1 at The Tor Project / Applications / firefox-android
Commits:
47c80363 by clairehurst at 2024-01-22T12:30:48-07:00
fixup! Enable the connect assist experiments on alpha
- - - - -
1 changed file:
- fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
Changes:
=====================================
fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
=====================================
@@ -1213,19 +1213,22 @@ abstract class BaseBrowserFragment :
}
private fun handleBetaHtmlTorConnect() {
- if (getCurrentTab()?.content?.url == "about:torconnect") {
+ val currentTab = getCurrentTab() ?: return
+ if (currentTab.content.url == "about:torconnect") {
if (!requireActivity().settings().useNewBootstrap) {
- requireContext().components.useCases.tabsUseCases.removeAllTabs()
+ requireContext().components.useCases.tabsUseCases.removeTab(currentTab.id)
(requireActivity() as HomeActivity).navHost.navController.navigate(
NavGraphDirections.actionStartupTorbootstrap(),
)
} else if (!requireActivity().settings().useNewBootstrapHtmlUi) {
- requireContext().components.useCases.tabsUseCases.removeAllTabs()
+ requireContext().components.useCases.tabsUseCases.removeTab(currentTab.id)
(requireActivity() as HomeActivity).navigateToHome()
} else {
// This just makes it not flash (be visible for a split second) before handleTabSelected() hides it again
browserToolbarView.view.visibility = View.GONE
}
+ } else if (currentTab.content.url == "about:tor") {
+ requireContext().components.useCases.tabsUseCases.removeTab(currentTab.id)
}
}
View it on GitLab: https://gitlab.torproject.org/tpo/applications/firefox-android/-/commit/47c…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/firefox-android/-/commit/47c…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/mullvad-browser][mullvad-browser-115.7.0esr-13.5-1] Bug 1867408 - NSS backport
by ma1 (@ma1) 22 Jan '24
by ma1 (@ma1) 22 Jan '24
22 Jan '24
ma1 pushed to branch mullvad-browser-115.7.0esr-13.5-1 at The Tor Project / Applications / Mullvad Browser
Commits:
00f5c6d8 by hackademix at 2024-01-22T17:00:26+01:00
Bug 1867408 - NSS backport
- - - - -
1 changed file:
- security/nss/lib/ssl/sslsecur.c
Changes:
=====================================
security/nss/lib/ssl/sslsecur.c
=====================================
@@ -488,7 +488,12 @@ ssl_SendSavedWriteData(sslSocket *ss)
if (rv < 0) {
return rv;
}
- ss->pendingBuf.len -= rv;
+ if (rv > ss->pendingBuf.len) {
+ PORT_Assert(0); /* This shouldn't happen */
+ ss->pendingBuf.len = 0;
+ } else {
+ ss->pendingBuf.len -= rv;
+ }
if (ss->pendingBuf.len > 0 && rv > 0) {
/* UGH !! This shifts the whole buffer down by copying it */
PORT_Memmove(ss->pendingBuf.buf, ss->pendingBuf.buf + rv,
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/00f…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/00f…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/mullvad-browser][mullvad-browser-115.7.0esr-13.0-1] Bug 1867408 - NSS backport
by ma1 (@ma1) 22 Jan '24
by ma1 (@ma1) 22 Jan '24
22 Jan '24
ma1 pushed to branch mullvad-browser-115.7.0esr-13.0-1 at The Tor Project / Applications / Mullvad Browser
Commits:
e0af4349 by hackademix at 2024-01-22T16:57:26+01:00
Bug 1867408 - NSS backport
- - - - -
1 changed file:
- security/nss/lib/ssl/sslsecur.c
Changes:
=====================================
security/nss/lib/ssl/sslsecur.c
=====================================
@@ -488,7 +488,12 @@ ssl_SendSavedWriteData(sslSocket *ss)
if (rv < 0) {
return rv;
}
- ss->pendingBuf.len -= rv;
+ if (rv > ss->pendingBuf.len) {
+ PORT_Assert(0); /* This shouldn't happen */
+ ss->pendingBuf.len = 0;
+ } else {
+ ss->pendingBuf.len -= rv;
+ }
if (ss->pendingBuf.len > 0 && rv > 0) {
/* UGH !! This shifts the whole buffer down by copying it */
PORT_Memmove(ss->pendingBuf.buf, ss->pendingBuf.buf + rv,
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/e0a…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/e0a…
You're receiving this email because of your account on gitlab.torproject.org.
1
0