[tor-commits] [tor-browser] 63/85: Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
gitolite role
git at cupani.torproject.org
Fri Jun 10 17:01:54 UTC 2022
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2
in repository tor-browser.
commit 4a4cd91e58664b7f4217f870b2a703e2b6d9187d
Author: Richard Pospesel <richard at torproject.org>
AuthorDate: Mon Sep 16 15:25:39 2019 -0700
Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
This patch adds a new about:preferences#connection page which allows
modifying bridge, proxy, and firewall settings from within Tor Browser.
All of the functionality present in tor-launcher's Network
Configuration panel is present:
- Setting built-in bridges
- Requesting bridges from BridgeDB via moat
- Using user-provided bridges
- Configuring SOCKS4, SOCKS5, and HTTP/HTTPS proxies
- Setting firewall ports
- Viewing and Copying Tor's logs
- The Networking Settings in General preferences has been removed
Bug 40774: Update about:preferences page to match new UI designs
---
browser/components/moz.build | 1 +
browser/components/preferences/main.inc.xhtml | 54 -
browser/components/preferences/main.js | 14 -
browser/components/preferences/preferences.js | 9 +
browser/components/preferences/preferences.xhtml | 6 +
.../torpreferences/content/bridgeQrDialog.jsm | 51 +
.../torpreferences/content/bridgeQrDialog.xhtml | 23 +
.../torpreferences/content/builtinBridgeDialog.jsm | 142 +++
.../content/builtinBridgeDialog.xhtml | 43 +
.../content/connectionCategory.inc.xhtml | 9 +
.../torpreferences/content/connectionPane.js | 1315 ++++++++++++++++++++
.../torpreferences/content/connectionPane.xhtml | 177 +++
.../content/connectionSettingsDialog.jsm | 393 ++++++
.../content/connectionSettingsDialog.xhtml | 62 +
.../components/torpreferences/content/network.svg | 6 +
.../torpreferences/content/provideBridgeDialog.jsm | 69 +
.../content/provideBridgeDialog.xhtml | 21 +
.../torpreferences/content/requestBridgeDialog.jsm | 211 ++++
.../content/requestBridgeDialog.xhtml | 35 +
.../torpreferences/content/torLogDialog.jsm | 84 ++
.../torpreferences/content/torLogDialog.xhtml | 23 +
.../torpreferences/content/torPreferences.css | 541 ++++++++
.../torpreferences/content/torPreferencesIcon.svg | 8 +
browser/components/torpreferences/jar.mn | 19 +
browser/components/torpreferences/moz.build | 1 +
25 files changed, 3249 insertions(+), 68 deletions(-)
diff --git a/browser/components/moz.build b/browser/components/moz.build
index 1bc09f4093fb7..66de87290bd83 100644
--- a/browser/components/moz.build
+++ b/browser/components/moz.build
@@ -53,6 +53,7 @@ DIRS += [
"syncedtabs",
"uitour",
"urlbar",
+ "torpreferences",
"translation",
]
diff --git a/browser/components/preferences/main.inc.xhtml b/browser/components/preferences/main.inc.xhtml
index a89b89f723a80..594711e614747 100644
--- a/browser/components/preferences/main.inc.xhtml
+++ b/browser/components/preferences/main.inc.xhtml
@@ -671,58 +671,4 @@
<label id="cfrFeaturesLearnMore" class="learnMore" data-l10n-id="browsing-cfr-recommendations-learn-more" is="text-link"/>
</hbox>
</groupbox>
-
-<hbox id="networkProxyCategory"
- class="subcategory"
- hidden="true"
- data-category="paneGeneral">
- <html:h1 data-l10n-id="network-settings-title"/>
-</hbox>
-
-<!-- Network Settings-->
-<groupbox id="connectionGroup" data-category="paneGeneral" hidden="true">
- <label class="search-header" hidden="true"><html:h2 data-l10n-id="network-settings-title"/></label>
-
- <hbox align="center">
- <hbox align="center" flex="1">
- <description id="connectionSettingsDescription" control="connectionSettings"/>
- <spacer width="5"/>
- <label id="connectionSettingsLearnMore" class="learnMore" is="text-link"
- data-l10n-id="network-proxy-connection-learn-more">
- </label>
- <separator orient="vertical"/>
- </hbox>
-
- <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
- <hbox>
- <button id="connectionSettings"
- is="highlightable-button"
- class="accessory-button"
- data-l10n-id="network-proxy-connection-settings"
- searchkeywords="doh trr"
- search-l10n-ids="
- connection-window.title,
- connection-proxy-option-no.label,
- connection-proxy-option-auto.label,
- connection-proxy-option-system.label,
- connection-proxy-option-manual.label,
- connection-proxy-http,
- connection-proxy-https,
- connection-proxy-http-port,
- connection-proxy-socks,
- connection-proxy-socks4,
- connection-proxy-socks5,
- connection-proxy-noproxy,
- connection-proxy-noproxy-desc,
- connection-proxy-https-sharing.label,
- connection-proxy-autotype.label,
- connection-proxy-reload.label,
- connection-proxy-autologin.label,
- connection-proxy-socks-remote-dns.label,
- connection-dns-over-https.label,
- connection-dns-over-https-url-custom.label,
- " />
- </hbox>
- </hbox>
-</groupbox>
</html:template>
diff --git a/browser/components/preferences/main.js b/browser/components/preferences/main.js
index 2a6ba4a3d8e42..501ba9144a314 100644
--- a/browser/components/preferences/main.js
+++ b/browser/components/preferences/main.js
@@ -368,15 +368,6 @@ var gMainPane = {
});
this.updatePerformanceSettingsBox({ duringChangeEvent: false });
this.displayUseSystemLocale();
- let connectionSettingsLink = document.getElementById(
- "connectionSettingsLearnMore"
- );
- let connectionSettingsUrl =
- Services.urlFormatter.formatURLPref("app.support.baseURL") +
- "prefs-connection-settings";
- connectionSettingsLink.setAttribute("href", connectionSettingsUrl);
- this.updateProxySettingsUI();
- initializeProxyUI(gMainPane);
if (Services.prefs.getBoolPref("intl.multilingual.enabled")) {
gMainPane.initBrowserLocale();
@@ -510,11 +501,6 @@ var gMainPane = {
"change",
gMainPane.updateHardwareAcceleration.bind(gMainPane)
);
- setEventListener(
- "connectionSettings",
- "command",
- gMainPane.showConnections
- );
setEventListener(
"browserContainersCheckbox",
"command",
diff --git a/browser/components/preferences/preferences.js b/browser/components/preferences/preferences.js
index a3656f827ffc6..5981bcd38fc83 100644
--- a/browser/components/preferences/preferences.js
+++ b/browser/components/preferences/preferences.js
@@ -13,6 +13,7 @@
/* import-globals-from findInPage.js */
/* import-globals-from ../../base/content/utilityOverlay.js */
/* import-globals-from ../../../toolkit/content/preferencesBindings.js */
+/* import-globals-from ../torpreferences/content/connectionPane.js */
"use strict";
@@ -136,6 +137,14 @@ function init_all() {
register_module("paneSync", gSyncPane);
}
register_module("paneSearchResults", gSearchResultsPane);
+ if (gConnectionPane.enabled) {
+ document.getElementById("category-connection").hidden = false;
+ register_module("paneConnection", gConnectionPane);
+ } else {
+ // Remove the pane from the DOM so it doesn't get incorrectly included in search results.
+ document.getElementById("template-paneConnection").remove();
+ }
+
gSearchResultsPane.init();
gMainPane.preInit();
diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml
index 32184867ac179..9ee09581de32a 100644
--- a/browser/components/preferences/preferences.xhtml
+++ b/browser/components/preferences/preferences.xhtml
@@ -6,12 +6,14 @@
<?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://global/skin/in-content/common.css"?>
+<?xml-stylesheet href="chrome://global/skin/in-content/toggle-button.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
<?xml-stylesheet href="chrome://browser/content/preferences/dialogs/handlers.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/applications.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/search.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/containers.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/privacy.css"?>
+<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
<!DOCTYPE html [
<!ENTITY % aboutTorDTD SYSTEM "chrome://torbutton/locale/aboutTor.dtd">
@@ -154,6 +156,9 @@
<image class="category-icon"/>
<label class="category-name" flex="1" data-l10n-id="pane-experimental-title"></label>
</richlistitem>
+
+#include ../torpreferences/content/connectionCategory.inc.xhtml
+
</richlistbox>
<spacer flex="1"/>
@@ -207,6 +212,7 @@
#include containers.inc.xhtml
#include sync.inc.xhtml
#include experimental.inc.xhtml
+#include ../torpreferences/content/connectionPane.xhtml
</vbox>
</vbox>
</vbox>
diff --git a/browser/components/torpreferences/content/bridgeQrDialog.jsm b/browser/components/torpreferences/content/bridgeQrDialog.jsm
new file mode 100644
index 0000000000000..e63347742ea50
--- /dev/null
+++ b/browser/components/torpreferences/content/bridgeQrDialog.jsm
@@ -0,0 +1,51 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["BridgeQrDialog"];
+
+const { QRCode } = ChromeUtils.import("resource://gre/modules/QRCode.jsm");
+
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+
+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) {
+ // Defer to later until Firefox has populated the dialog with all our elements
+ window.setTimeout(() => {
+ this._populateXUL(window, dialog);
+ }, 0);
+ }
+
+ openDialog(gSubDialog, bridgeString) {
+ this._bridgeString = bridgeString;
+ gSubDialog.open(
+ "chrome://browser/content/torpreferences/bridgeQrDialog.xhtml",
+ { features: "resizable=yes" },
+ this
+ );
+ }
+}
diff --git a/browser/components/torpreferences/content/bridgeQrDialog.xhtml b/browser/components/torpreferences/content/bridgeQrDialog.xhtml
new file mode 100644
index 0000000000000..2a49e4c0e7d9e
--- /dev/null
+++ b/browser/components/torpreferences/content/bridgeQrDialog.xhtml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
+
+<window type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+<dialog id="bridgeQr-dialog" buttons="accept">
+ <html:div id="bridgeQr-container">
+ <html:div id="bridgeQr-target"/>
+ <html:div id="bridgeQr-onionBox"/>
+ <html:div id="bridgeQr-onion"/>
+ </html:div>
+ <script type="application/javascript"><![CDATA[
+ "use strict";
+
+ let dialogObject = window.arguments[0];
+ let dialogElement = document.getElementById("bridgeQr-dialog");
+ dialogObject.init(window, dialogElement);
+ ]]></script>
+</dialog>
+</window>
diff --git a/browser/components/torpreferences/content/builtinBridgeDialog.jsm b/browser/components/torpreferences/content/builtinBridgeDialog.jsm
new file mode 100644
index 0000000000000..1d4dda8f5ca9c
--- /dev/null
+++ b/browser/components/torpreferences/content/builtinBridgeDialog.jsm
@@ -0,0 +1,142 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["BuiltinBridgeDialog"];
+
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+
+const {
+ TorSettings,
+ TorBridgeSource,
+ TorBuiltinBridgeTypes,
+} = ChromeUtils.import("resource:///modules/TorSettings.jsm");
+
+class BuiltinBridgeDialog {
+ constructor() {
+ this._dialog = null;
+ this._bridgeType = "";
+ this._windowPadding = 0;
+ }
+
+ static get selectors() {
+ return {
+ header: "#torPreferences-builtinBridge-header",
+ description: "#torPreferences-builtinBridge-description",
+ radiogroup: "#torPreferences-builtinBridge-typeSelection",
+ obfsRadio: "#torPreferences-builtinBridges-radioObfs",
+ obfsDescr: "#torPreferences-builtinBridges-descrObfs",
+ snowflakeRadio: "#torPreferences-builtinBridges-radioSnowflake",
+ snowflakeDescr: "#torPreferences-builtinBridges-descrSnowflake",
+ meekAzureRadio: "#torPreferences-builtinBridges-radioMeekAzure",
+ meekAzureDescr: "#torPreferences-builtinBridges-descrMeekAzure",
+ };
+ }
+
+ _populateXUL(window, aDialog) {
+ const selectors = BuiltinBridgeDialog.selectors;
+
+ this._dialog = aDialog;
+ const dialogWin = this._dialog.parentElement;
+ {
+ dialogWin.setAttribute("title", TorStrings.settings.builtinBridgeTitle);
+ let windowStyle = window.getComputedStyle(dialogWin);
+ this._windowPadding =
+ parseFloat(windowStyle.paddingLeft) +
+ parseFloat(windowStyle.paddingRight);
+ }
+ const initialWidth = dialogWin.clientWidth - this._windowPadding;
+
+ this._dialog.querySelector(selectors.header).textContent =
+ TorStrings.settings.builtinBridgeHeader;
+ this._dialog.querySelector(selectors.description).textContent =
+ TorStrings.settings.builtinBridgeDescription;
+ let radioGroup = this._dialog.querySelector(selectors.radiogroup);
+
+ let types = {
+ obfs4: {
+ elemRadio: this._dialog.querySelector(selectors.obfsRadio),
+ elemDescr: this._dialog.querySelector(selectors.obfsDescr),
+ label: TorStrings.settings.builtinBridgeObfs4,
+ descr: TorStrings.settings.builtinBridgeObfs4Description,
+ },
+ snowflake: {
+ elemRadio: this._dialog.querySelector(selectors.snowflakeRadio),
+ elemDescr: this._dialog.querySelector(selectors.snowflakeDescr),
+ label: TorStrings.settings.builtinBridgeSnowflake,
+ descr: TorStrings.settings.builtinBridgeSnowflakeDescription,
+ },
+ "meek-azure": {
+ elemRadio: this._dialog.querySelector(selectors.meekAzureRadio),
+ elemDescr: this._dialog.querySelector(selectors.meekAzureDescr),
+ label: TorStrings.settings.builtinBridgeMeekAzure,
+ descr: TorStrings.settings.builtinBridgeMeekAzureDescription,
+ },
+ };
+
+ TorBuiltinBridgeTypes.forEach(type => {
+ types[type].elemRadio.parentElement.setAttribute("hidden", "false");
+ types[type].elemDescr.parentElement.setAttribute("hidden", "false");
+ types[type].elemRadio.setAttribute("label", types[type].label);
+ types[type].elemDescr.textContent = types[type].descr;
+ });
+
+ if (
+ TorSettings.bridges.enabled &&
+ TorSettings.bridges.source == TorBridgeSource.BuiltIn
+ ) {
+ radioGroup.selectedItem =
+ types[TorSettings.bridges.builtin_type]?.elemRadio;
+ this._bridgeType = TorSettings.bridges.builtin_type;
+ } else {
+ radioGroup.selectedItem = null;
+ this._bridgeType = "";
+ }
+
+ // Use the initial width, because the window is expanded when we add texts
+ this.resized(initialWidth);
+
+ this._dialog.addEventListener("dialogaccept", e => {
+ this._bridgeType = radioGroup.value;
+ });
+ this._dialog.addEventListener("dialoghelp", e => {
+ window.top.openTrustedLinkIn(
+ "https://tb-manual.torproject.org/circumvention/",
+ "tab"
+ );
+ });
+ }
+
+ resized(width) {
+ if (this._dialog === null) {
+ return;
+ }
+ const dialogWin = this._dialog.parentElement;
+ if (width === undefined) {
+ width = dialogWin.clientWidth - this._windowPadding;
+ }
+ let windowPos = dialogWin.getBoundingClientRect();
+ dialogWin.querySelectorAll("div").forEach(div => {
+ let divPos = div.getBoundingClientRect();
+ div.style.width = width - (divPos.left - windowPos.left) + "px";
+ });
+ }
+
+ init(window, aDialog) {
+ // defer to later until firefox has populated the dialog with all our elements
+ window.setTimeout(() => {
+ this._populateXUL(window, aDialog);
+ }, 0);
+ }
+
+ openDialog(gSubDialog, aCloseCallback) {
+ gSubDialog.open(
+ "chrome://browser/content/torpreferences/builtinBridgeDialog.xhtml",
+ {
+ features: "resizable=yes",
+ closingCallback: () => {
+ aCloseCallback(this._bridgeType);
+ },
+ },
+ this
+ );
+ }
+}
diff --git a/browser/components/torpreferences/content/builtinBridgeDialog.xhtml b/browser/components/torpreferences/content/builtinBridgeDialog.xhtml
new file mode 100644
index 0000000000000..c1bf202ca1be5
--- /dev/null
+++ b/browser/components/torpreferences/content/builtinBridgeDialog.xhtml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
+
+<window type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+<dialog id="torPreferences-builtinBridge-dialog"
+ buttons="help,accept,cancel">
+ <html:h3 id="torPreferences-builtinBridge-header"></html:h3>
+ <description>
+ <html:div id="torPreferences-builtinBridge-description"><br/></html:div>
+ </description>
+ <radiogroup id="torPreferences-builtinBridge-typeSelection">
+ <hbox hidden="true">
+ <radio id="torPreferences-builtinBridges-radioObfs" value="obfs4"/>
+ </hbox>
+ <hbox hidden="true" class="indent">
+ <html:div id="torPreferences-builtinBridges-descrObfs"></html:div>
+ </hbox>
+ <hbox hidden="true">
+ <radio id="torPreferences-builtinBridges-radioSnowflake" value="snowflake"/>
+ </hbox>
+ <hbox hidden="true" class="indent">
+ <html:div id="torPreferences-builtinBridges-descrSnowflake"></html:div>
+ </hbox>
+ <hbox hidden="true">
+ <radio id="torPreferences-builtinBridges-radioMeekAzure" value="meek-azure"/>
+ </hbox>
+ <hbox hidden="true" class="indent">
+ <html:div id="torPreferences-builtinBridges-descrMeekAzure"></html:div>
+ </hbox>
+ </radiogroup>
+ <script type="application/javascript"><![CDATA[
+ "use strict";
+
+ let builtinBridgeDialog = window.arguments[0];
+ let dialog = document.getElementById("torPreferences-builtinBridge-dialog");
+ builtinBridgeDialog.init(window, dialog);
+ ]]></script>
+</dialog>
+</window>
diff --git a/browser/components/torpreferences/content/connectionCategory.inc.xhtml b/browser/components/torpreferences/content/connectionCategory.inc.xhtml
new file mode 100644
index 0000000000000..15cf24cfe6950
--- /dev/null
+++ b/browser/components/torpreferences/content/connectionCategory.inc.xhtml
@@ -0,0 +1,9 @@
+<richlistitem id="category-connection"
+ class="category"
+ value="paneConnection"
+ helpTopic="prefs-connection"
+ align="center"
+ hidden="true">
+ <image class="category-icon"/>
+ <label id="torPreferences-labelCategory" class="category-name" flex="1" value="Connection"/>
+</richlistitem>
diff --git a/browser/components/torpreferences/content/connectionPane.js b/browser/components/torpreferences/content/connectionPane.js
new file mode 100644
index 0000000000000..309d6498a0c80
--- /dev/null
+++ b/browser/components/torpreferences/content/connectionPane.js
@@ -0,0 +1,1315 @@
+"use strict";
+
+/* global Services, gSubDialog */
+
+const { setTimeout, clearTimeout } = ChromeUtils.import(
+ "resource://gre/modules/Timer.jsm"
+);
+
+const {
+ TorSettings,
+ TorSettingsTopics,
+ TorSettingsData,
+ TorBridgeSource,
+} = ChromeUtils.import("resource:///modules/TorSettings.jsm");
+
+const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+);
+
+const {
+ TorConnect,
+ TorConnectTopics,
+ TorConnectState,
+ TorCensorshipLevel,
+} = ChromeUtils.import("resource:///modules/TorConnect.jsm");
+
+const { TorLogDialog } = ChromeUtils.import(
+ "chrome://browser/content/torpreferences/torLogDialog.jsm"
+);
+
+const { ConnectionSettingsDialog } = ChromeUtils.import(
+ "chrome://browser/content/torpreferences/connectionSettingsDialog.jsm"
+);
+
+const { BridgeQrDialog } = ChromeUtils.import(
+ "chrome://browser/content/torpreferences/bridgeQrDialog.jsm"
+);
+
+const { BuiltinBridgeDialog } = ChromeUtils.import(
+ "chrome://browser/content/torpreferences/builtinBridgeDialog.jsm"
+);
+
+const { RequestBridgeDialog } = ChromeUtils.import(
+ "chrome://browser/content/torpreferences/requestBridgeDialog.jsm"
+);
+
+const { ProvideBridgeDialog } = ChromeUtils.import(
+ "chrome://browser/content/torpreferences/provideBridgeDialog.jsm"
+);
+
+const { MoatRPC } = ChromeUtils.import("resource:///modules/Moat.jsm");
+
+const { QRCode } = ChromeUtils.import("resource://gre/modules/QRCode.jsm");
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "TorStrings",
+ "resource:///modules/TorStrings.jsm"
+);
+
+const InternetStatus = Object.freeze({
+ Unknown: 0,
+ Online: 1,
+ Offline: -1,
+});
+
+/*
+ Connection Pane
+
+ Code for populating the XUL in about:preferences#connection, handling input events, interfacing with tor-launcher
+*/
+const gConnectionPane = (function() {
+ /* CSS selectors for all of the Tor Network DOM elements we need to access */
+ const selectors = {
+ category: {
+ title: "label#torPreferences-labelCategory",
+ },
+ messageBox: {
+ box: "div#torPreferences-connectMessageBox",
+ message: "td#torPreferences-connectMessageBox-message",
+ button: "button#torPreferences-connectMessageBox-button",
+ },
+ torPreferences: {
+ header: "h1#torPreferences-header",
+ description: "span#torPreferences-description",
+ learnMore: "label#torPreferences-learnMore",
+ },
+ status: {
+ internetLabel: "#torPreferences-status-internet-label",
+ internetTest: "#torPreferences-status-internet-test",
+ internetIcon: "#torPreferences-status-internet-statusIcon",
+ internetStatus: "#torPreferences-status-internet-status",
+ torLabel: "#torPreferences-status-tor-label",
+ torIcon: "#torPreferences-status-tor-statusIcon",
+ torStatus: "#torPreferences-status-tor-status",
+ },
+ quickstart: {
+ header: "h2#torPreferences-quickstart-header",
+ description: "span#torPreferences-quickstart-description",
+ enableQuickstartCheckbox: "checkbox#torPreferences-quickstart-toggle",
+ },
+ bridges: {
+ header: "h1#torPreferences-bridges-header",
+ description: "span#torPreferences-bridges-description",
+ learnMore: "label#torPreferences-bridges-learnMore",
+ locationGroup: "#torPreferences-bridges-locationGroup",
+ locationLabel: "#torPreferences-bridges-locationLabel",
+ location: "#torPreferences-bridges-location",
+ locationEntries: "#torPreferences-bridges-locationEntries",
+ chooseForMe: "#torPreferences-bridges-buttonChooseBridgeForMe",
+ currentHeader: "#torPreferences-currentBridges-header",
+ currentHeaderText: "#torPreferences-currentBridges-headerText",
+ switch: "#torPreferences-currentBridges-switch",
+ cards: "#torPreferences-currentBridges-cards",
+ cardTemplate: "#torPreferences-bridgeCard-template",
+ card: ".torPreferences-bridgeCard",
+ cardId: ".torPreferences-bridgeCard-id",
+ cardHeadingAddr: ".torPreferences-bridgeCard-headingAddr",
+ cardConnectedLabel: ".torPreferences-bridgeCard-connectedLabel",
+ 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",
+ requestLabel: "#torPreferences-addBridge-labelRequestBridge",
+ requestButton: "#torPreferences-addBridge-buttonRequestBridge",
+ enterLabel: "#torPreferences-addBridge-labelEnterBridge",
+ enterButton: "#torPreferences-addBridge-buttonEnterBridge",
+ },
+ advanced: {
+ header: "h1#torPreferences-advanced-header",
+ label: "#torPreferences-advanced-label",
+ button: "#torPreferences-advanced-button",
+ torLogsLabel: "label#torPreferences-torLogs",
+ torLogsButton: "button#torPreferences-buttonTorLogs",
+ },
+ }; /* selectors */
+
+ let retval = {
+ // cached frequently accessed DOM elements
+ _enableQuickstartCheckbox: null,
+
+ _internetStatus: InternetStatus.Unknown,
+
+ _controller: null,
+
+ _currentBridge: "",
+
+ // disables the provided list of elements
+ _setElementsDisabled(elements, disabled) {
+ for (let currentElement of elements) {
+ currentElement.disabled = disabled;
+ }
+ },
+
+ // populate xul with strings and cache the relevant elements
+ _populateXUL() {
+ // saves tor settings to disk when navigate away from about:preferences
+ window.addEventListener("blur", val => {
+ TorProtocolService.flushSettings();
+ });
+
+ document
+ .querySelector(selectors.category.title)
+ .setAttribute("value", TorStrings.settings.categoryTitle);
+
+ let prefpane = document.getElementById("mainPrefPane");
+
+ // 'Connect to Tor' Message Bar
+
+ const messageBox = prefpane.querySelector(selectors.messageBox.box);
+ const messageBoxMessage = prefpane.querySelector(
+ selectors.messageBox.message
+ );
+ const messageBoxButton = prefpane.querySelector(
+ selectors.messageBox.button
+ );
+ // wire up connect button
+ messageBoxButton.addEventListener("click", () => {
+ TorConnect.beginBootstrap();
+ TorConnect.openTorConnect();
+ });
+
+ this._populateMessagebox = () => {
+ if (
+ TorConnect.shouldShowTorConnect &&
+ TorConnect.state === TorConnectState.Configuring
+ ) {
+ // set messagebox style and text
+ if (TorProtocolService.torBootstrapErrorOccurred()) {
+ messageBox.parentNode.style.display = null;
+ messageBox.className = "error";
+ messageBoxMessage.innerText = TorStrings.torConnect.tryAgainMessage;
+ messageBoxButton.innerText = TorStrings.torConnect.tryAgain;
+ } else {
+ messageBox.parentNode.style.display = null;
+ messageBox.className = "warning";
+ messageBoxMessage.innerText = TorStrings.torConnect.connectMessage;
+ messageBoxButton.innerText = TorStrings.torConnect.torConnectButton;
+ }
+ } else {
+ // we need to explicitly hide the groupbox, as switching between
+ // the tor pane and other panes will 'unhide' (via the 'hidden'
+ // attribute) the groupbox, offsetting all of the content down
+ // by the groupbox's margin (even if content is 0 height)
+ messageBox.parentNode.style.display = "none";
+ messageBox.className = "hidden";
+ messageBoxMessage.innerText = "";
+ messageBoxButton.innerText = "";
+ }
+ };
+ this._populateMessagebox();
+
+ // Heading
+ prefpane.querySelector(selectors.torPreferences.header).innerText =
+ TorStrings.settings.categoryTitle;
+ prefpane.querySelector(selectors.torPreferences.description).textContent =
+ TorStrings.settings.torPreferencesDescription;
+ {
+ let learnMore = prefpane.querySelector(
+ selectors.torPreferences.learnMore
+ );
+ learnMore.setAttribute("value", TorStrings.settings.learnMore);
+ learnMore.setAttribute(
+ "href",
+ TorStrings.settings.learnMoreTorBrowserURL
+ );
+ }
+
+ // Internet and Tor status
+ prefpane.querySelector(selectors.status.internetLabel).textContent =
+ TorStrings.settings.statusInternetLabel;
+ prefpane.querySelector(selectors.status.torLabel).textContent =
+ TorStrings.settings.statusTorLabel;
+ const internetTest = prefpane.querySelector(
+ selectors.status.internetTest
+ );
+ internetTest.setAttribute(
+ "label",
+ TorStrings.settings.statusInternetTest
+ );
+ internetTest.addEventListener("command", async () => {
+ this.onInternetTest();
+ });
+ const internetIcon = prefpane.querySelector(
+ selectors.status.internetIcon
+ );
+ const internetStatus = prefpane.querySelector(
+ selectors.status.internetStatus
+ );
+ const torIcon = prefpane.querySelector(selectors.status.torIcon);
+ const torStatus = prefpane.querySelector(selectors.status.torStatus);
+ this._populateStatus = () => {
+ switch (this._internetStatus) {
+ case InternetStatus.Unknown:
+ internetTest.removeAttribute("hidden");
+ break;
+ case InternetStatus.Online:
+ internetTest.setAttribute("hidden", "true");
+ internetIcon.className = "online";
+ internetStatus.textContent =
+ TorStrings.settings.statusInternetOnline;
+ break;
+ case InternetStatus.Offline:
+ internetTest.setAttribute("hidden", "true");
+ internetIcon.className = "offline";
+ internetStatus.textContent =
+ TorStrings.settings.statusInternetOffline;
+ break;
+ }
+ if (TorConnect.state === TorConnectState.Bootstrapped) {
+ torIcon.className = "connected";
+ torStatus.textContent = TorStrings.settings.statusTorConnected;
+ } else if (
+ TorConnect.detectedCensorshipLevel > TorCensorshipLevel.None
+ ) {
+ torIcon.className = "blocked";
+ torStatus.textContent = TorStrings.settings.statusTorBlocked;
+ } else {
+ torIcon.className = "";
+ torStatus.textContent = TorStrings.settings.statusTorNotConnected;
+ }
+ };
+ this._populateStatus();
+
+ // Quickstart
+ prefpane.querySelector(selectors.quickstart.header).innerText =
+ TorStrings.settings.quickstartHeading;
+ prefpane.querySelector(selectors.quickstart.description).textContent =
+ TorStrings.settings.quickstartDescription;
+
+ this._enableQuickstartCheckbox = prefpane.querySelector(
+ selectors.quickstart.enableQuickstartCheckbox
+ );
+ this._enableQuickstartCheckbox.setAttribute(
+ "label",
+ TorStrings.settings.quickstartCheckbox
+ );
+ this._enableQuickstartCheckbox.addEventListener("command", e => {
+ const checked = this._enableQuickstartCheckbox.checked;
+ TorSettings.quickstart.enabled = checked;
+ TorSettings.saveToPrefs().applySettings();
+ });
+ this._enableQuickstartCheckbox.checked = TorSettings.quickstart.enabled;
+ Services.obs.addObserver(this, TorSettingsTopics.SettingChanged);
+
+ // Bridge setup
+ prefpane.querySelector(selectors.bridges.header).innerText =
+ TorStrings.settings.bridgesHeading;
+ prefpane.querySelector(selectors.bridges.description).textContent =
+ TorStrings.settings.bridgesDescription;
+ {
+ let learnMore = prefpane.querySelector(selectors.bridges.learnMore);
+ learnMore.setAttribute("value", TorStrings.settings.learnMore);
+ learnMore.setAttribute("href", TorStrings.settings.learnMoreBridgesURL);
+ }
+
+ // Location
+ {
+ const locationGroup = prefpane.querySelector(
+ selectors.bridges.locationGroup
+ );
+ prefpane.querySelector(selectors.bridges.locationLabel).textContent =
+ TorStrings.settings.bridgeLocation;
+ const location = prefpane.querySelector(selectors.bridges.location);
+ const locationEntries = prefpane.querySelector(
+ selectors.bridges.locationEntries
+ );
+ const chooseForMe = prefpane.querySelector(
+ selectors.bridges.chooseForMe
+ );
+ chooseForMe.setAttribute(
+ "label",
+ TorStrings.settings.bridgeChooseForMe
+ );
+ chooseForMe.addEventListener("command", e => {
+ TorConnect.beginAutoBootstrap(location.value);
+ });
+ this._populateLocations = () => {
+ let value = location.value;
+ locationEntries.textContent = "";
+
+ {
+ const item = document.createXULElement("menuitem");
+ item.setAttribute("value", "");
+ item.setAttribute(
+ "label",
+ TorStrings.settings.bridgeLocationAutomatic
+ );
+ locationEntries.appendChild(item);
+ }
+
+ const codes = TorConnect.countryCodes;
+ const items = codes.map(code => {
+ const item = document.createXULElement("menuitem");
+ item.setAttribute("value", code);
+ item.setAttribute(
+ "label",
+ TorConnect.countryNames[code]
+ ? TorConnect.countryNames[code]
+ : code
+ );
+ return item;
+ });
+ items.sort((left, right) =>
+ left.textContent.localeCompare(right.textContent)
+ );
+ locationEntries.append(...items);
+ location.value = value;
+ };
+ this._showAutoconfiguration = () => {
+ if (
+ !TorConnect.shouldShowTorConnect ||
+ !TorProtocolService.torBootstrapErrorOccurred()
+ ) {
+ locationGroup.setAttribute("hidden", "true");
+ return;
+ }
+ // Populate locations, even though we will show only the automatic
+ // item for a moment. In my opinion showing the button immediately is
+ // better then waiting for the Moat query to finish (after a while)
+ // and showing the controls only after that.
+ this._populateLocations();
+ locationGroup.removeAttribute("hidden");
+ if (!TorConnect.countryCodes.length) {
+ TorConnect.getCountryCodes().then(() => this._populateLocations());
+ }
+ };
+ this._showAutoconfiguration();
+ }
+
+ // Bridge cards
+ const bridgeHeader = prefpane.querySelector(
+ selectors.bridges.currentHeader
+ );
+ bridgeHeader.querySelector(
+ selectors.bridges.currentHeaderText
+ ).textContent = TorStrings.settings.bridgeCurrent;
+ const bridgeSwitch = bridgeHeader.querySelector(selectors.bridges.switch);
+ bridgeSwitch.addEventListener("change", () => {
+ TorSettings.bridges.enabled = bridgeSwitch.checked;
+ TorSettings.saveToPrefs();
+ TorSettings.applySettings().then(result => {
+ this._populateBridgeCards();
+ });
+ });
+ const bridgeTemplate = prefpane.querySelector(
+ selectors.bridges.cardTemplate
+ );
+ {
+ const learnMore = bridgeTemplate.querySelector(
+ selectors.bridges.cardLearnMore
+ );
+ learnMore.setAttribute("value", TorStrings.settings.learnMore);
+ learnMore.setAttribute("href", "about:blank");
+ }
+ bridgeTemplate.querySelector(
+ selectors.bridges.cardConnectedLabel
+ ).textContent = TorStrings.settings.statusTorConnected;
+ 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 => {
+ 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(e => {
+ const span = document.createElement("span");
+ span.className = "emoji";
+ span.textContent = e;
+ return span;
+ });
+ const idString = TorStrings.settings.bridgeId;
+ const id = card.querySelector(selectors.bridges.cardId);
+ const details = parseBridgeLine(bridgeString);
+ 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])/)) {
+ if (piece == "#1") {
+ id.append(type);
+ } else if (piece == "#2") {
+ 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.checked && !!strings.length;
+ TorSettings.bridges.bridge_strings = strings.join("\n");
+ TorSettings.saveToPrefs();
+ TorSettings.applySettings().then(result => {
+ 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 && details.id === this._currentBridge) {
+ 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.substr(0, style.width.length - 2);
+ const height = style.height.substr(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")) {
+ 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 = true;
+ this._populateBridgeCards();
+ });
+ const removeAll = prefpane.querySelector(selectors.bridges.removeAll);
+ removeAll.setAttribute("label", TorStrings.settings.bridgeRemoveAll);
+ removeAll.addEventListener("command", () => {
+ this.onRemoveAllBridges();
+ });
+ this._populateBridgeCards = async () => {
+ const collapseThreshold = 4;
+
+ let newStrings = new Set(TorSettings.bridges.bridge_strings);
+ const numBridges = newStrings.size;
+ if (!newStrings.size) {
+ bridgeHeader.setAttribute("hidden", "true");
+ bridgeCards.setAttribute("hidden", "true");
+ showAll.setAttribute("hidden", "true");
+ removeAll.setAttribute("hidden", "true");
+ bridgeCards.textContent = "";
+ return;
+ }
+ bridgeHeader.removeAttribute("hidden");
+ bridgeCards.removeAttribute("hidden");
+ bridgeSwitch.checked = TorSettings.bridges.enabled;
+ bridgeCards.classList.toggle("disabled", !TorSettings.bridges.enabled);
+
+ 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._currentBridge === "") {
+ break;
+ } else if (!bridge.includes(this._currentBridge)) {
+ 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--;
+ }
+
+ // And finally update the buttons
+ if (numBridges > collapseThreshold && !this._currentBridgesExpanded) {
+ showAll.removeAttribute("hidden");
+ if (TorSettings.bridges.enabled) {
+ showAll.classList.add("primary");
+ } else {
+ showAll.classList.remove("primary");
+ }
+ removeAll.setAttribute("hidden", "true");
+ if (TorSettings.bridges.enabled) {
+ // 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.add("list-collapsed");
+ }
+ } else {
+ showAll.setAttribute("hidden", "true");
+ removeAll.removeAttribute("hidden");
+ bridgeCards.classList.remove("list-collapsed");
+ }
+ };
+ this._populateBridgeCards();
+ this._updateConnectedBridges = () => {
+ for (const card of bridgeCards.querySelectorAll(
+ ".currently-connected"
+ )) {
+ card.classList.remove("currently-connected");
+ }
+ if (this._currentBridge === "") {
+ 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._currentBridge}"]`
+ );
+ for (const card of cards) {
+ card.classList.add("currently-connected");
+ }
+ const placeholder = document.createElement("span");
+ bridgeCards.prepend(placeholder);
+ placeholder.replaceWith(...cards);
+ };
+ try {
+ const { controller } = ChromeUtils.import(
+ "resource://torbutton/modules/tor-control-port.js",
+ {}
+ );
+ // Avoid the cache because we set our custom event watcher, and at the
+ // moment, watchers cannot be removed from a controller.
+ controller(true).then(aController => {
+ this._controller = aController;
+ // Getting the circuits may be enough, if we have bootstrapped for a
+ // while, but at the beginning it gives many bridges as connected,
+ // because tor pokes all the bridges to find the best one.
+ // Also, watching circuit events does not work, at the moment, but in
+ // any case, checking the stream has the advantage that we can see if
+ // it really used for a connection, rather than tor having created
+ // this circuit to check if the bridge can be used. We do this by
+ // checking if the stream has SOCKS username, which actually contains
+ // the destination of the stream.
+ this._controller.watchEvent(
+ "STREAM",
+ event =>
+ event.StreamStatus === "SUCCEEDED" && "SOCKS_USERNAME" in event,
+ async event => {
+ const circuitStatuses = await this._controller.getInfo(
+ "circuit-status"
+ );
+ if (!circuitStatuses) {
+ return;
+ }
+ for (const status of circuitStatuses) {
+ if (status.id === event.CircuitID && status.circuit.length) {
+ // The id in the circuit begins with a $ sign
+ const bridgeId = status.circuit[0][0].substr(1);
+ if (bridgeId !== this._currentBridge) {
+ this._currentBridge = bridgeId;
+ this._updateConnectedBridges();
+ }
+ break;
+ }
+ }
+ }
+ );
+ });
+ } catch (err) {
+ console.warn(
+ "We could not load torbutton, bridge statuses will not be updated",
+ err
+ );
+ }
+
+ // Add a new bridge
+ prefpane.querySelector(selectors.bridges.addHeader).textContent =
+ TorStrings.settings.bridgeAdd;
+ prefpane
+ .querySelector(selectors.bridges.addBuiltinLabel)
+ .setAttribute("value", TorStrings.settings.bridgeSelectBrowserBuiltin);
+ {
+ let button = prefpane.querySelector(selectors.bridges.addBuiltinButton);
+ button.setAttribute("label", TorStrings.settings.bridgeSelectBuiltin);
+ button.addEventListener("command", e => {
+ this.onAddBuiltinBridge();
+ });
+ }
+ prefpane
+ .querySelector(selectors.bridges.requestLabel)
+ .setAttribute("value", TorStrings.settings.bridgeRequestFromTorProject);
+ {
+ let button = prefpane.querySelector(selectors.bridges.requestButton);
+ button.setAttribute("label", TorStrings.settings.bridgeRequest);
+ button.addEventListener("command", e => {
+ this.onRequestBridge();
+ });
+ }
+ prefpane
+ .querySelector(selectors.bridges.enterLabel)
+ .setAttribute("value", TorStrings.settings.bridgeEnterKnown);
+ {
+ const button = prefpane.querySelector(selectors.bridges.enterButton);
+ button.setAttribute("label", TorStrings.settings.bridgeAddManually);
+ button.addEventListener("command", e => {
+ this.onAddBridgeManually();
+ });
+ }
+
+ Services.obs.addObserver(this, TorConnectTopics.StateChange);
+
+ // Advanced setup
+ prefpane.querySelector(selectors.advanced.header).innerText =
+ TorStrings.settings.advancedHeading;
+ prefpane.querySelector(selectors.advanced.label).textContent =
+ TorStrings.settings.advancedLabel;
+ {
+ let settingsButton = prefpane.querySelector(selectors.advanced.button);
+ settingsButton.setAttribute(
+ "label",
+ TorStrings.settings.advancedButton
+ );
+ settingsButton.addEventListener("command", () => {
+ this.onAdvancedSettings();
+ });
+ }
+
+ // Tor logs
+ prefpane
+ .querySelector(selectors.advanced.torLogsLabel)
+ .setAttribute("value", TorStrings.settings.showTorDaemonLogs);
+ let torLogsButton = prefpane.querySelector(
+ selectors.advanced.torLogsButton
+ );
+ torLogsButton.setAttribute("label", TorStrings.settings.showLogs);
+ torLogsButton.addEventListener("command", () => {
+ this.onViewTorLogs();
+ });
+ },
+
+ init() {
+ this._populateXUL();
+
+ let onUnload = () => {
+ window.removeEventListener("unload", onUnload);
+ gConnectionPane.uninit();
+ };
+ window.addEventListener("unload", onUnload);
+
+ window.addEventListener("resize", () => {
+ this._checkBridgeCardsHeight();
+ });
+ },
+
+ uninit() {
+ // unregister our observer topics
+ Services.obs.removeObserver(this, TorSettingsTopics.SettingChanged);
+ Services.obs.removeObserver(this, TorConnectTopics.StateChange);
+
+ if (this._controller !== null) {
+ this._controller.close();
+ this._controller = null;
+ }
+ },
+
+ // whether the page should be present in about:preferences
+ get enabled() {
+ return TorProtocolService.ownsTorDaemon;
+ },
+
+ //
+ // Callbacks
+ //
+
+ observe(subject, topic, data) {
+ switch (topic) {
+ // triggered when a TorSettings param has changed
+ case TorSettingsTopics.SettingChanged: {
+ let obj = subject?.wrappedJSObject;
+ switch (data) {
+ case TorSettingsData.QuickStartEnabled: {
+ this._enableQuickstartCheckbox.checked = obj.value;
+ break;
+ }
+ }
+ break;
+ }
+ // triggered when tor connect state changes and we may
+ // need to update the messagebox
+ case TorConnectTopics.StateChange: {
+ this.onStateChange();
+ break;
+ }
+ }
+ },
+
+ async onInternetTest() {
+ const mrpc = new MoatRPC();
+ let status = null;
+ try {
+ await mrpc.init();
+ status = await mrpc.testInternetConnection();
+ } catch (err) {
+ console.log("Error while checking the Internet connection", err);
+ } finally {
+ mrpc.uninit();
+ }
+ if (status) {
+ this._internetStatus = status.successful
+ ? InternetStatus.Online
+ : InternetStatus.Offline;
+ this._populateStatus();
+ }
+ },
+
+ onStateChange() {
+ this._populateMessagebox();
+ this._populateStatus();
+ this._showAutoconfiguration();
+ this._populateBridgeCards();
+ },
+
+ onShowQr(bridgeString) {
+ const dialog = new BridgeQrDialog();
+ dialog.openDialog(gSubDialog, bridgeString);
+ },
+
+ onCopyBridgeAddress(addressElem) {
+ let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
+ Ci.nsIClipboardHelper
+ );
+ clipboard.copyString(addressElem.value);
+ },
+
+ onRemoveAllBridges() {
+ TorSettings.bridges.enabled = false;
+ TorSettings.bridges.bridge_strings = "";
+ TorSettings.saveToPrefs();
+ TorSettings.applySettings().then(result => {
+ this._populateBridgeCards();
+ });
+ },
+
+ onAddBuiltinBridge() {
+ let builtinBridgeDialog = new BuiltinBridgeDialog();
+
+ let sizeObserver = null;
+ {
+ let ds = document.querySelector("#dialogStack");
+ let boxObserver;
+ boxObserver = new MutationObserver(() => {
+ let dialogBox = document.querySelector(".dialogBox");
+ if (dialogBox) {
+ sizeObserver = new MutationObserver(mutations => {
+ for (const m of mutations) {
+ if (m.attributeName === "style") {
+ builtinBridgeDialog.resized();
+ break;
+ }
+ }
+ });
+ sizeObserver.observe(dialogBox, { attributes: true });
+ boxObserver.disconnect();
+ }
+ });
+ boxObserver.observe(ds, { childList: true, subtree: true });
+ }
+
+ builtinBridgeDialog.openDialog(gSubDialog, aBridgeType => {
+ sizeObserver.disconnect();
+
+ if (!aBridgeType) {
+ TorSettings.bridges.enabled = false;
+ TorSettings.bridges.builtin_type = "";
+ } else {
+ TorSettings.bridges.enabled = true;
+ TorSettings.bridges.source = TorBridgeSource.BuiltIn;
+ TorSettings.bridges.builtin_type = aBridgeType;
+ }
+ TorSettings.saveToPrefs();
+ TorSettings.applySettings().then(result => {
+ this._populateBridgeCards();
+ });
+ });
+ },
+
+ // called when the request bridge button is activated
+ onRequestBridge() {
+ let requestBridgeDialog = new RequestBridgeDialog();
+ requestBridgeDialog.openDialog(gSubDialog, aBridges => {
+ if (aBridges.length) {
+ let bridgeStrings = aBridges.join("\n");
+ TorSettings.bridges.enabled = true;
+ TorSettings.bridges.source = TorBridgeSource.BridgeDB;
+ TorSettings.bridges.bridge_strings = bridgeStrings;
+ TorSettings.saveToPrefs();
+ TorSettings.applySettings().then(result => {
+ this._populateBridgeCards();
+ });
+ } else {
+ TorSettings.bridges.enabled = false;
+ }
+ });
+ },
+
+ onAddBridgeManually() {
+ let provideBridgeDialog = new ProvideBridgeDialog();
+ provideBridgeDialog.openDialog(gSubDialog, aBridgeString => {
+ if (aBridgeString.length) {
+ TorSettings.bridges.enabled = true;
+ TorSettings.bridges.source = TorBridgeSource.UserProvided;
+ TorSettings.bridges.bridge_strings = aBridgeString;
+ TorSettings.saveToPrefs();
+ TorSettings.applySettings().then(result => {
+ this._populateBridgeCards();
+ });
+ } else {
+ TorSettings.bridges.enabled = false;
+ TorSettings.bridges.source = TorBridgeSource.Invalid;
+ }
+ });
+ },
+
+ onAdvancedSettings() {
+ let connectionSettingsDialog = new ConnectionSettingsDialog();
+ connectionSettingsDialog.openDialog(gSubDialog);
+ },
+
+ onViewTorLogs() {
+ let torLogDialog = new TorLogDialog();
+ torLogDialog.openDialog(gSubDialog);
+ },
+ };
+ return retval;
+})(); /* gConnectionPane */
+
+function makeBridgeId(bridgeString) {
+ // JS uses UTF-16. While most of these emojis are surrogate pairs, a few
+ // ones fit one UTF-16 character. So we could not use neither indices,
+ // nor substr, nor some function to split the string.
+ const emojis = [
+ "😄️",
+ "😒️",
+ "😉",
+ "😭️",
+ "😂️",
+ "😎️",
+ "🤩️",
+ "😘",
+ "😜️",
+ "😏️",
+ "😷",
+ "🤢",
+ "🤕",
+ "🤧",
+ "🥵",
+ "🥶",
+ "🥴",
+ "😵️",
+ "🤮️",
+ "🤑",
+ "🤔",
+ "🫢",
+ "🤐",
+ "😮💨",
+ "😐",
+ "🤤",
+ "😴",
+ "🤯",
+ "🤠",
+ "🥳",
+ "🥸",
+ "🤓",
+ "🧐",
+ "😨",
+ "😳",
+ "🥺",
+ "🤬",
+ "😈",
+ "👿",
+ "💀",
+ "💩",
+ "🤡",
+ "👺",
+ "👻",
+ "👽",
+ "🦴",
+ "🤖",
+ "😸",
+ "🙈",
+ "🙉",
+ "🙊",
+ "💋",
+ "💖",
+ "💯",
+ "💢",
+ "💧",
+ "💨",
+ "💭",
+ "💤",
+ "👋",
+ "👌",
+ "✌",
+ "👍",
+ "👎",
+ "🤛",
+ "🙌",
+ "💪",
+ "🙏",
+ "✍",
+ "🧠",
+ "👀",
+ "👂",
+ "👅",
+ "🦷",
+ "🐾",
+ "🐶",
+ "🦊",
+ "🦝",
+ "🐈",
+ "🦁",
+ "🐯",
+ "🐴",
+ "🦄",
+ "🦓",
+ "🐮",
+ "🐷",
+ "🐑",
+ "🐪",
+ "🐘",
+ "🐭",
+ "🐰",
+ "🦔",
+ "🦇",
+ "🐻",
+ "🐨",
+ "🐼",
+ "🐔",
+ "🦨",
+ "🦘",
+ "🐦",
+ "🐧",
+ "🦩",
+ "🦉",
+ "🦜",
+ "🪶",
+ "🐸",
+ "🐊",
+ "🐢",
+ "🦎",
+ "🐍",
+ "🦖",
+ "🦀",
+ "🐬",
+ "🐙",
+ "🐌",
+ "🐝",
+ "🐞",
+ "🌸",
+ "🌲",
+ "🌵",
+ "🍀",
+ "🍁",
+ "🍇",
+ "🍉",
+ "🍊",
+ "🍋",
+ "🍌",
+ "🍍",
+ "🍎",
+ "🥥",
+ "🍐",
+ "🍒",
+ "🍓",
+ "🫐",
+ "🥝",
+ "🥔",
+ "🥕",
+ "🧅",
+ "🌰",
+ "🍄",
+ "🍞",
+ "🥞",
+ "🧀",
+ "🍖",
+ "🍔",
+ "🍟",
+ "🍕",
+ "🥚",
+ "🍿",
+ "🧂",
+ "🍙",
+ "🍦",
+ "🍩",
+ "🍪",
+ "🎂",
+ "🍬",
+ "🍭",
+ "🥛",
+ "☕",
+ "🫖",
+ "🍾",
+ "🍷",
+ "🍹",
+ "🍺",
+ "🍴",
+ "🥄",
+ "🫙",
+ "🧭",
+ "🌋",
+ "🪵",
+ "🏡",
+ "🏢",
+ "🏰",
+ "⛲",
+ "⛺",
+ "🎡",
+ "🚂",
+ "🚘",
+ "🚜",
+ "🚲",
+ "🚔",
+ "🚨",
+ "⛽",
+ "🚥",
+ "🚧",
+ "⚓",
+ "⛵",
+ "🛟",
+ "🪂",
+ "🚀",
+ "⌛",
+ "⏰",
+ "🌂",
+ "🌞",
+ "🌙",
+ "🌟",
+ "⛅",
+ "⚡",
+ "🔥",
+ "🌊",
+ "🎃",
+ "🎈",
+ "🎉",
+ "✨",
+ "🎀",
+ "🎁",
+ "🏆",
+ "🏅",
+ "🔮",
+ "🪄",
+ "🎾",
+ "🎳",
+ "🎲",
+ "🎭",
+ "🎨",
+ "🧵",
+ "🎩",
+ "📢",
+ "🔔",
+ "🎵",
+ "🎤",
+ "🎧",
+ "🎷",
+ "🎸",
+ "🥁",
+ "🔋",
+ "🔌",
+ "💻",
+ "💾",
+ "💿",
+ "🎬",
+ "📺",
+ "📷",
+ "🎮",
+ "🧩",
+ "🔍",
+ "💡",
+ "📖",
+ "💰",
+ "💼",
+ "📈",
+ "📌",
+ "📎",
+ "🔒",
+ "🔑",
+ "🔧",
+ "🪛",
+ "🔩",
+ "🧲",
+ "🔬",
+ "🔭",
+ "📡",
+ "🚪",
+ "🪑",
+ "⛔",
+ "🚩",
+ ];
+
+ // FNV-1a implementation that is compatible with other languages
+ const prime = 0x01000193;
+ const offset = 0x811c9dc5;
+ let hash = offset;
+ const encoder = new TextEncoder();
+ for (const charCode of encoder.encode(bridgeString)) {
+ hash = Math.imul(hash ^ charCode, prime);
+ }
+
+ const hashBytes = [
+ ((hash & 0x7f000000) >> 24) | (hash < 0 ? 0x80 : 0),
+ (hash & 0x00ff0000) >> 16,
+ (hash & 0x0000ff00) >> 8,
+ hash & 0x000000ff,
+ ];
+ return hashBytes.map(b => emojis[b]);
+}
+
+function parseBridgeLine(line) {
+ const re = /^([^\s]+\s+)?([0-9a-fA-F\.\[\]\:]+:[0-9]{1,5})\s*([0-9a-fA-F]{40})(\s+.+)?/;
+ const matches = line.match(re);
+ if (!matches) {
+ return null;
+ }
+ let bridge = { addr: matches[2] };
+ if (matches[1] !== undefined) {
+ bridge.transport = matches[1].trim();
+ }
+ if (matches[3] !== undefined) {
+ bridge.id = matches[3].toUpperCase();
+ }
+ if (matches[4] !== undefined) {
+ bridge.args = matches[4].trim();
+ }
+ return bridge;
+}
diff --git a/browser/components/torpreferences/content/connectionPane.xhtml b/browser/components/torpreferences/content/connectionPane.xhtml
new file mode 100644
index 0000000000000..67f98685d8038
--- /dev/null
+++ b/browser/components/torpreferences/content/connectionPane.xhtml
@@ -0,0 +1,177 @@
+<!-- Tor panel -->
+
+<script type="application/javascript"
+ src="chrome://browser/content/torpreferences/connectionPane.js"/>
+<html:template id="template-paneConnection">
+
+<!-- Tor Connect Message Box -->
+<groupbox data-category="paneConnection" hidden="true">
+ <html:div id="torPreferences-connectMessageBox"
+ class="subcategory"
+ data-category="paneConnection"
+ hidden="true">
+ <html:table>
+ <html:tr>
+ <html:td>
+ <html:div id="torPreferences-connectMessageBox-icon"/>
+ </html:td>
+ <html:td id="torPreferences-connectMessageBox-message">
+ </html:td>
+ <html:td>
+ <html:button id="torPreferences-connectMessageBox-button">
+ </html:button>
+ </html:td>
+ </html:tr>
+ </html:table>
+ </html:div>
+</groupbox>
+
+<hbox id="torPreferencesCategory"
+ class="subcategory"
+ data-category="paneConnection"
+ hidden="true">
+ <html:h1 id="torPreferences-header"/>
+</hbox>
+
+<groupbox data-category="paneConnection"
+ hidden="true">
+ <description flex="1">
+ <html:span id="torPreferences-description" class="tail-with-learn-more"/>
+ <label id="torPreferences-learnMore" class="learnMore text-link" is="text-link"/>
+ </description>
+</groupbox>
+
+<groupbox id="torPreferences-status-group"
+ data-category="paneConnection">
+ <hbox id="torPreferences-status-box">
+ <image id="torPreferences-status-internet-icon"/>
+ <html:span id="torPreferences-status-internet-label"/>
+ <button id="torPreferences-status-internet-test"/>
+ <image id="torPreferences-status-internet-statusIcon"/>
+ <html:span id="torPreferences-status-internet-status"/>
+ <image id="torPreferences-status-tor-icon"/>
+ <html:span id="torPreferences-status-tor-label"/>
+ <image id="torPreferences-status-tor-statusIcon"/>
+ <html:span id="torPreferences-status-tor-status"/>
+ </hbox>
+</groupbox>
+
+<!-- Quickstart -->
+<groupbox id="torPreferences-quickstart-group"
+ data-category="paneConnection"
+ hidden="true">
+ <html:h2 id="torPreferences-quickstart-header"/>
+ <description flex="1">
+ <html:span id="torPreferences-quickstart-description"/>
+ </description>
+ <checkbox id="torPreferences-quickstart-toggle"/>
+</groupbox>
+
+<!-- Bridges -->
+<hbox class="subcategory"
+ data-category="paneConnection"
+ hidden="true">
+ <html:h1 id="torPreferences-bridges-header"/>
+</hbox>
+<groupbox id="torPreferences-bridges-group"
+ data-category="paneConnection"
+ hidden="true">
+ <description flex="1">
+ <html:span id="torPreferences-bridges-description" class="tail-with-learn-more"/>
+ <label id="torPreferences-bridges-learnMore" class="learnMore text-link" is="text-link"/>
+ </description>
+ <hbox align="center" id="torPreferences-bridges-locationGroup" hidden="true">
+ <label id="torPreferences-bridges-locationLabel"
+ control="torPreferences-bridges-location"/>
+ <spacer flex="1"/>
+ <menulist id="torPreferences-bridges-location">
+ <menupopup id="torPreferences-bridges-locationEntries"/>
+ </menulist>
+ <button id="torPreferences-bridges-buttonChooseBridgeForMe" class="torMarginFix primary"/>
+ </hbox>
+ <html:h2 id="torPreferences-currentBridges-header">
+ <html:span id="torPreferences-currentBridges-headerText"/>
+ <html:input type="checkbox" id="torPreferences-currentBridges-switch" class="toggle-button"/>
+ </html:h2>
+ <menupopup id="torPreferences-bridgeCard-menu"/>
+ <vbox id="torPreferences-bridgeCard-template" class="torPreferences-bridgeCard">
+ <hbox class="torPreferences-bridgeCard-heading">
+ <html:div class="torPreferences-bridgeCard-id"/>
+ <html:div class="torPreferences-bridgeCard-headingAddr"/>
+ <html:div class="torPreferences-bridgeCard-buttons">
+ <html:span class="torPreferences-bridgeCard-connectedBadge">
+ <image class="torPreferences-bridgeCard-connectedIcon"/>
+ <html:span class="torPreferences-bridgeCard-connectedLabel"/>
+ </html:span>
+ <html:button class="torPreferences-bridgeCard-options stop-click"/>
+ </html:div>
+ </hbox>
+ <box class="torPreferences-bridgeCard-grid">
+ <box class="torPreferences-bridgeCard-qrWrapper">
+ <box class="torPreferences-bridgeCard-qr stop-click">
+ <html:div class="torPreferences-bridgeCard-qrCode"/>
+ <html:div class="torPreferences-bridgeCard-qrOnionBox"/>
+ <html:div class="torPreferences-bridgeCard-qrOnion"/>
+ </box>
+ <html:div class="torPreferences-bridgeCard-filler"/>
+ </box>
+ <description class="torPreferences-bridgeCard-share"></description>
+ <hbox class="torPreferences-bridgeCard-addrBox">
+ <html:input class="torPreferences-bridgeCard-addr torMarginFix stop-click" type="text" readonly="readonly"/>
+ </hbox>
+ <hbox class="torPreferences-bridgeCard-learnMoreBox" align="center">
+ <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"/>
+ <button id="torPreferences-currentBridges-removeAll" class="primary danger-button"/>
+ </vbox>
+ <html:h2 id="torPreferences-addBridge-header"></html:h2>
+ <hbox align="center">
+ <label id="torPreferences-addBridge-labelBuiltinBridge"/>
+ <space flex="1"/>
+ <button id="torPreferences-addBridge-buttonBuiltinBridge" class="torMarginFix"/>
+ </hbox>
+ <hbox align="center">
+ <label id="torPreferences-addBridge-labelRequestBridge"/>
+ <space flex="1"/>
+ <button id="torPreferences-addBridge-buttonRequestBridge" class="torMarginFix"/>
+ </hbox>
+ <hbox align="center">
+ <label id="torPreferences-addBridge-labelEnterBridge"/>
+ <space flex="1"/>
+ <button id="torPreferences-addBridge-buttonEnterBridge" class="torMarginFix"/>
+ </hbox>
+</groupbox>
+
+<!-- Advanced -->
+<hbox class="subcategory"
+ data-category="paneConnection"
+ hidden="true">
+ <html:h1 id="torPreferences-advanced-header"/>
+</hbox>
+<groupbox id="torPreferences-advanced-group"
+ data-category="paneConnection"
+ hidden="true">
+ <box id="torPreferences-advanced-grid">
+ <hbox id="torPreferences-advanced-hbox" align="center">
+ <label id="torPreferences-advanced-label"/>
+ </hbox>
+ <hbox align="center">
+ <button id="torPreferences-advanced-button"/>
+ </hbox>
+ <hbox id="torPreferences-torDaemon-hbox" align="center">
+ <label id="torPreferences-torLogs"/>
+ </hbox>
+ <hbox align="center" data-subcategory="viewlogs">
+ <button id="torPreferences-buttonTorLogs"/>
+ </hbox>
+ </box>
+</groupbox>
+</html:template>
diff --git a/browser/components/torpreferences/content/connectionSettingsDialog.jsm b/browser/components/torpreferences/content/connectionSettingsDialog.jsm
new file mode 100644
index 0000000000000..abc177c43f884
--- /dev/null
+++ b/browser/components/torpreferences/content/connectionSettingsDialog.jsm
@@ -0,0 +1,393 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["ConnectionSettingsDialog"];
+
+const { TorSettings, TorProxyType } = ChromeUtils.import(
+ "resource:///modules/TorSettings.jsm"
+);
+
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+
+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;
+ }
+
+ 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",
+ };
+ }
+
+ // disables the provided list of elements
+ _setElementsDisabled(elements, disabled) {
+ for (let currentElement of elements) {
+ currentElement.disabled = disabled;
+ }
+ }
+
+ _populateXUL(window, aDialog) {
+ const selectors = ConnectionSettingsDialog.selectors;
+
+ this._dialog = aDialog;
+ const dialogWin = this._dialog.parentElement;
+ dialogWin.setAttribute(
+ "title",
+ TorStrings.settings.connectionSettingsDialogTitle
+ );
+ this._dialog.querySelector(selectors.header).textContent =
+ TorStrings.settings.connectionSettingsDialogHeader;
+
+ // Local Proxy
+ this._useProxyCheckbox = this._dialog.querySelector(
+ selectors.useProxyCheckbox
+ );
+ this._useProxyCheckbox.setAttribute(
+ "label",
+ TorStrings.settings.useLocalProxy
+ );
+ this._useProxyCheckbox.addEventListener("command", e => {
+ const checked = this._useProxyCheckbox.checked;
+ this.onToggleProxy(checked);
+ });
+ this._proxyTypeLabel = this._dialog.querySelector(selectors.proxyTypeLabel);
+ this._proxyTypeLabel.setAttribute("value", TorStrings.settings.proxyType);
+
+ let mockProxies = [
+ {
+ value: TorProxyType.Socks4,
+ label: TorStrings.settings.proxyTypeSOCKS4,
+ },
+ {
+ value: TorProxyType.Socks5,
+ label: TorStrings.settings.proxyTypeSOCKS5,
+ },
+ { value: TorProxyType.HTTPS, label: TorStrings.settings.proxyTypeHTTP },
+ ];
+ this._proxyTypeMenulist = this._dialog.querySelector(
+ selectors.proxyTypeList
+ );
+ this._proxyTypeMenulist.addEventListener("command", e => {
+ const value = this._proxyTypeMenulist.value;
+ this.onSelectProxyType(value);
+ });
+ for (let currentProxy of mockProxies) {
+ let menuEntry = window.document.createXULElement("menuitem");
+ menuEntry.setAttribute("value", currentProxy.value);
+ menuEntry.setAttribute("label", currentProxy.label);
+ this._proxyTypeMenulist.querySelector("menupopup").appendChild(menuEntry);
+ }
+
+ this._proxyAddressLabel = this._dialog.querySelector(
+ selectors.proxyAddressLabel
+ );
+ this._proxyAddressLabel.setAttribute(
+ "value",
+ TorStrings.settings.proxyAddress
+ );
+ this._proxyAddressTextbox = this._dialog.querySelector(
+ selectors.proxyAddressTextbox
+ );
+ this._proxyAddressTextbox.setAttribute(
+ "placeholder",
+ TorStrings.settings.proxyAddressPlaceholder
+ );
+ this._proxyAddressTextbox.addEventListener("blur", e => {
+ let value = this._proxyAddressTextbox.value.trim();
+ let colon = value.lastIndexOf(":");
+ if (colon != -1) {
+ let maybePort = parseInt(value.substr(colon + 1));
+ if (!isNaN(maybePort) && maybePort > 0 && maybePort < 65536) {
+ this._proxyAddressTextbox.value = value.substr(0, colon);
+ this._proxyPortTextbox.value = maybePort;
+ }
+ }
+ });
+ this._proxyPortLabel = this._dialog.querySelector(selectors.proxyPortLabel);
+ this._proxyPortLabel.setAttribute("value", TorStrings.settings.proxyPort);
+ this._proxyPortTextbox = this._dialog.querySelector(
+ selectors.proxyPortTextbox
+ );
+ this._proxyUsernameLabel = this._dialog.querySelector(
+ selectors.proxyUsernameLabel
+ );
+ this._proxyUsernameLabel.setAttribute(
+ "value",
+ TorStrings.settings.proxyUsername
+ );
+ this._proxyUsernameTextbox = this._dialog.querySelector(
+ selectors.proxyUsernameTextbox
+ );
+ this._proxyUsernameTextbox.setAttribute(
+ "placeholder",
+ TorStrings.settings.proxyUsernamePasswordPlaceholder
+ );
+ this._proxyPasswordLabel = this._dialog.querySelector(
+ selectors.proxyPasswordLabel
+ );
+ this._proxyPasswordLabel.setAttribute(
+ "value",
+ TorStrings.settings.proxyPassword
+ );
+ this._proxyPasswordTextbox = this._dialog.querySelector(
+ selectors.proxyPasswordTextbox
+ );
+ this._proxyPasswordTextbox.setAttribute(
+ "placeholder",
+ TorStrings.settings.proxyUsernamePasswordPlaceholder
+ );
+
+ this.onToggleProxy(false);
+ if (TorSettings.proxy.enabled) {
+ this.onToggleProxy(true);
+ this.onSelectProxyType(TorSettings.proxy.type);
+ this._proxyAddressTextbox.value = TorSettings.proxy.address;
+ this._proxyPortTextbox.value = TorSettings.proxy.port;
+ this._proxyUsernameTextbox.value = TorSettings.proxy.username;
+ this._proxyPasswordTextbox.value = TorSettings.proxy.password;
+ }
+
+ // Local firewall
+ this._useFirewallCheckbox = this._dialog.querySelector(
+ selectors.useFirewallCheckbox
+ );
+ this._useFirewallCheckbox.setAttribute(
+ "label",
+ TorStrings.settings.useFirewall
+ );
+ this._useFirewallCheckbox.addEventListener("command", e => {
+ const checked = this._useFirewallCheckbox.checked;
+ this.onToggleFirewall(checked);
+ });
+ this._allowedPortsLabel = this._dialog.querySelector(
+ selectors.firewallAllowedPortsLabel
+ );
+ this._allowedPortsLabel.setAttribute(
+ "value",
+ TorStrings.settings.allowedPorts
+ );
+ this._allowedPortsTextbox = this._dialog.querySelector(
+ selectors.firewallAllowedPortsTextbox
+ );
+ this._allowedPortsTextbox.setAttribute(
+ "placeholder",
+ TorStrings.settings.allowedPortsPlaceholder
+ );
+
+ this.onToggleFirewall(false);
+ if (TorSettings.firewall.enabled) {
+ this.onToggleFirewall(true);
+ this._allowedPortsTextbox.value = TorSettings.firewall.allowed_ports.join(
+ ", "
+ );
+ }
+
+ this._dialog.addEventListener("dialogaccept", e => {
+ this._applySettings();
+ });
+ }
+
+ // callback when proxy is toggled
+ onToggleProxy(enabled) {
+ this._useProxyCheckbox.checked = enabled;
+ let disabled = !enabled;
+
+ this._setElementsDisabled(
+ [
+ this._proxyTypeLabel,
+ this._proxyTypeMenulist,
+ this._proxyAddressLabel,
+ this._proxyAddressTextbox,
+ this._proxyPortLabel,
+ this._proxyPortTextbox,
+ this._proxyUsernameLabel,
+ this._proxyUsernameTextbox,
+ this._proxyPasswordLabel,
+ this._proxyPasswordTextbox,
+ ],
+ disabled
+ );
+ if (enabled) {
+ this.onSelectProxyType(this._proxyTypeMenulist.value);
+ }
+ }
+
+ // callback when proxy type is changed
+ onSelectProxyType(value) {
+ if (typeof value === "string") {
+ value = parseInt(value);
+ }
+
+ this._proxyTypeMenulist.value = value;
+ switch (value) {
+ case TorProxyType.Invalid: {
+ this._setElementsDisabled(
+ [
+ this._proxyAddressLabel,
+ this._proxyAddressTextbox,
+ this._proxyPortLabel,
+ this._proxyPortTextbox,
+ this._proxyUsernameLabel,
+ this._proxyUsernameTextbox,
+ this._proxyPasswordLabel,
+ this._proxyPasswordTextbox,
+ ],
+ true
+ ); // DISABLE
+
+ this._proxyAddressTextbox.value = "";
+ this._proxyPortTextbox.value = "";
+ this._proxyUsernameTextbox.value = "";
+ this._proxyPasswordTextbox.value = "";
+ break;
+ }
+ case TorProxyType.Socks4: {
+ this._setElementsDisabled(
+ [
+ this._proxyAddressLabel,
+ this._proxyAddressTextbox,
+ this._proxyPortLabel,
+ this._proxyPortTextbox,
+ ],
+ false
+ ); // ENABLE
+ this._setElementsDisabled(
+ [
+ this._proxyUsernameLabel,
+ this._proxyUsernameTextbox,
+ this._proxyPasswordLabel,
+ this._proxyPasswordTextbox,
+ ],
+ true
+ ); // DISABLE
+
+ this._proxyUsernameTextbox.value = "";
+ this._proxyPasswordTextbox.value = "";
+ break;
+ }
+ case TorProxyType.Socks5:
+ case TorProxyType.HTTPS: {
+ this._setElementsDisabled(
+ [
+ this._proxyAddressLabel,
+ this._proxyAddressTextbox,
+ this._proxyPortLabel,
+ this._proxyPortTextbox,
+ this._proxyUsernameLabel,
+ this._proxyUsernameTextbox,
+ this._proxyPasswordLabel,
+ this._proxyPasswordTextbox,
+ ],
+ false
+ ); // ENABLE
+ break;
+ }
+ }
+ }
+
+ // callback when firewall proxy is toggled
+ onToggleFirewall(enabled) {
+ this._useFirewallCheckbox.checked = enabled;
+ let disabled = !enabled;
+
+ this._setElementsDisabled(
+ [this._allowedPortsLabel, this._allowedPortsTextbox],
+ disabled
+ );
+ }
+
+ // pushes settings from UI to tor
+ _applySettings() {
+ const type = this._useProxyCheckbox.checked
+ ? parseInt(this._proxyTypeMenulist.value)
+ : TorProxyType.Invalid;
+ const address = this._proxyAddressTextbox.value;
+ const port = this._proxyPortTextbox.value;
+ const username = this._proxyUsernameTextbox.value;
+ const password = this._proxyPasswordTextbox.value;
+ switch (type) {
+ case TorProxyType.Invalid:
+ TorSettings.proxy.enabled = false;
+ break;
+ case TorProxyType.Socks4:
+ TorSettings.proxy.enabled = true;
+ TorSettings.proxy.type = type;
+ TorSettings.proxy.address = address;
+ TorSettings.proxy.port = port;
+ break;
+ case TorProxyType.Socks5:
+ TorSettings.proxy.enabled = true;
+ TorSettings.proxy.type = type;
+ TorSettings.proxy.address = address;
+ TorSettings.proxy.port = port;
+ TorSettings.proxy.username = username;
+ TorSettings.proxy.password = password;
+ break;
+ case TorProxyType.HTTPS:
+ TorSettings.proxy.enabled = true;
+ TorSettings.proxy.type = type;
+ TorSettings.proxy.address = address;
+ TorSettings.proxy.port = port;
+ TorSettings.proxy.username = username;
+ TorSettings.proxy.password = password;
+ break;
+ }
+
+ let portListString = this._useFirewallCheckbox.checked
+ ? this._allowedPortsTextbox.value
+ : "";
+ if (portListString) {
+ TorSettings.firewall.enabled = true;
+ TorSettings.firewall.allowed_ports = portListString;
+ } else {
+ TorSettings.firewall.enabled = false;
+ }
+
+ TorSettings.saveToPrefs();
+ TorSettings.applySettings();
+ }
+
+ init(window, aDialog) {
+ // defer to later until firefox has populated the dialog with all our elements
+ window.setTimeout(() => {
+ this._populateXUL(window, aDialog);
+ }, 0);
+ }
+
+ openDialog(gSubDialog) {
+ gSubDialog.open(
+ "chrome://browser/content/torpreferences/connectionSettingsDialog.xhtml",
+ { features: "resizable=yes" },
+ this
+ );
+ }
+}
diff --git a/browser/components/torpreferences/content/connectionSettingsDialog.xhtml b/browser/components/torpreferences/content/connectionSettingsDialog.xhtml
new file mode 100644
index 0000000000000..88aa979256f02
--- /dev/null
+++ b/browser/components/torpreferences/content/connectionSettingsDialog.xhtml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
+
+<window type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+<dialog id="torPreferences-connection-dialog"
+ buttons="accept,cancel">
+ <html:h3 id="torPreferences-connection-header"></html:h3>
+ <box id="torPreferences-connection-grid">
+ <!-- Local Proxy -->
+ <hbox class="torPreferences-connection-checkbox-container">
+ <checkbox id="torPreferences-connection-toggleProxy" label=""/>
+ </hbox>
+ <hbox class="indent" align="center">
+ <label id="torPreferences-localProxy-type"/>
+ </hbox>
+ <hbox align="center">
+ <spacer flex="1"/>
+ <menulist id="torPreferences-localProxy-builtinList" class="torMarginFix">
+ <menupopup/>
+ </menulist>
+ </hbox>
+ <hbox class="indent" align="center">
+ <label id="torPreferences-localProxy-address"/>
+ </hbox>
+ <hbox align="center">
+ <html:input id="torPreferences-localProxy-textboxAddress" type="text" class="torMarginFix"/>
+ <label id="torPreferences-localProxy-port"/>
+ <!-- proxy-port-input class style pulled from preferences.css and used in the vanilla proxy setup menu -->
+ <html:input id="torPreferences-localProxy-textboxPort" class="proxy-port-input torMarginFix" hidespinbuttons="true" type="number" min="0" max="65535" maxlength="5"/>
+ </hbox>
+ <hbox class="indent" align="center">
+ <label id="torPreferences-localProxy-username"/>
+ </hbox>
+ <hbox align="center">
+ <html:input id="torPreferences-localProxy-textboxUsername" type="text" class="torMarginFix"/>
+ <label id="torPreferences-localProxy-password"/>
+ <html:input id="torPreferences-localProxy-textboxPassword" class="torMarginFix" type="password"/>
+ </hbox>
+ <!-- Firewall -->
+ <hbox class="torPreferences-connection-checkbox-container">
+ <checkbox id="torPreferences-connection-toggleFirewall" label=""/>
+ </hbox>
+ <hbox class="indent" align="center">
+ <label id="torPreferences-connection-allowedPorts"/>
+ </hbox>
+ <hbox align="center">
+ <html:input id="torPreferences-connection-textboxAllowedPorts" type="text" class="torMarginFix" value="80,443"/>
+ </hbox>
+ </box>
+ <script type="application/javascript"><![CDATA[
+ "use strict";
+
+ let connectionSettingsDialog = window.arguments[0];
+ let dialog = document.getElementById("torPreferences-connection-dialog");
+ connectionSettingsDialog.init(window, dialog);
+ ]]></script>
+</dialog>
+</window>
diff --git a/browser/components/torpreferences/content/network.svg b/browser/components/torpreferences/content/network.svg
new file mode 100644
index 0000000000000..e1689b5e6d649
--- /dev/null
+++ b/browser/components/torpreferences/content/network.svg
@@ -0,0 +1,6 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
+ <path d="M8.5 1a7.5 7.5 0 1 0 0 15 7.5 7.5 0 0 0 0-15zm2.447 1.75a6.255 6.255 0 0 1 3.756 5.125l-2.229 0A9.426 9.426 0 0 0 10.54 2.75l.407 0zm-2.049 0a8.211 8.211 0 0 1 2.321 5.125l-5.438 0A8.211 8.211 0 0 1 8.102 2.75l.796 0zm-2.846 0 .408 0a9.434 9.434 0 0 0-1.934 5.125l-2.229 0A6.254 6.254 0 0 1 6.052 2.75zm0 11.5a6.252 6.252 0 0 1-3.755-5.125l2.229 0A9.426 9.426 0 0 0 6.46 14.25l-.408 0zm2.05 0a8.211 8.211 0 0 1-2.321-5.125l5.437 0a8.211 8.211 0 0 1-2.321 5.125l-.795 0zm2.846 0-.40 [...]
+</svg>
diff --git a/browser/components/torpreferences/content/provideBridgeDialog.jsm b/browser/components/torpreferences/content/provideBridgeDialog.jsm
new file mode 100644
index 0000000000000..b73a6f533afa6
--- /dev/null
+++ b/browser/components/torpreferences/content/provideBridgeDialog.jsm
@@ -0,0 +1,69 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["ProvideBridgeDialog"];
+
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+
+const { TorSettings, TorBridgeSource } = ChromeUtils.import(
+ "resource:///modules/TorSettings.jsm"
+);
+
+class ProvideBridgeDialog {
+ constructor() {
+ this._dialog = null;
+ this._textarea = null;
+ this._bridgeString = "";
+ }
+
+ static get selectors() {
+ return {
+ header: "#torPreferences-provideBridge-header",
+ textarea: "#torPreferences-provideBridge-textarea",
+ };
+ }
+
+ _populateXUL(aDialog) {
+ const selectors = ProvideBridgeDialog.selectors;
+
+ this._dialog = aDialog;
+ const dialogWin = this._dialog.parentElement;
+ dialogWin.setAttribute("title", TorStrings.settings.provideBridgeTitle);
+ this._dialog.querySelector(selectors.header).textContent =
+ TorStrings.settings.provideBridgeHeader;
+ this._textarea = this._dialog.querySelector(selectors.textarea);
+ this._textarea.setAttribute(
+ "placeholder",
+ TorStrings.settings.provideBridgePlaceholder
+ );
+ if (
+ TorSettings.bridges.enabled &&
+ TorSettings.bridges.source == TorBridgeSource.UserProvided
+ ) {
+ this._textarea.value = TorSettings.bridges.bridge_strings.join("\n");
+ }
+
+ this._dialog.addEventListener("dialogaccept", e => {
+ this._bridgeString = this._textarea.value;
+ });
+ }
+
+ init(window, aDialog) {
+ // defer to later until firefox has populated the dialog with all our elements
+ window.setTimeout(() => {
+ this._populateXUL(aDialog);
+ }, 0);
+ }
+
+ openDialog(gSubDialog, aCloseCallback) {
+ gSubDialog.open(
+ "chrome://browser/content/torpreferences/provideBridgeDialog.xhtml",
+ {
+ features: "resizable=yes",
+ closingCallback: () => {
+ aCloseCallback(this._bridgeString);
+ },
+ },
+ this
+ );
+ }
+}
diff --git a/browser/components/torpreferences/content/provideBridgeDialog.xhtml b/browser/components/torpreferences/content/provideBridgeDialog.xhtml
new file mode 100644
index 0000000000000..28d19cadaf9c9
--- /dev/null
+++ b/browser/components/torpreferences/content/provideBridgeDialog.xhtml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
+
+<window type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+<dialog id="torPreferences-provideBridge-dialog"
+ buttons="help,accept,cancel">
+ <html:h3 id="torPreferences-provideBridge-header"></html:h3>
+ <html:textarea id="torPreferences-provideBridge-textarea" multiline="true" rows="3"/>
+ <script type="application/javascript"><![CDATA[
+ "use strict";
+
+ let provideBridgeDialog = window.arguments[0];
+ let dialog = document.getElementById("torPreferences-provideBridge-dialog");
+ provideBridgeDialog.init(window, dialog);
+ ]]></script>
+</dialog>
+</window>
diff --git a/browser/components/torpreferences/content/requestBridgeDialog.jsm b/browser/components/torpreferences/content/requestBridgeDialog.jsm
new file mode 100644
index 0000000000000..f14bbdcbbb448
--- /dev/null
+++ b/browser/components/torpreferences/content/requestBridgeDialog.jsm
@@ -0,0 +1,211 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["RequestBridgeDialog"];
+
+const { BridgeDB } = ChromeUtils.import("resource:///modules/BridgeDB.jsm");
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+
+class RequestBridgeDialog {
+ constructor() {
+ this._dialog = null;
+ this._submitButton = null;
+ this._dialogHeader = null;
+ this._captchaImage = null;
+ this._captchaEntryTextbox = null;
+ this._captchaRefreshButton = null;
+ this._incorrectCaptchaHbox = null;
+ this._incorrectCaptchaLabel = null;
+ this._bridges = [];
+ }
+
+ static get selectors() {
+ return {
+ submitButton:
+ "accept" /* not really a selector but a key for dialog's getButton */,
+ 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;
+
+ this._dialog = dialog;
+ const dialogWin = dialog.parentElement;
+ dialogWin.setAttribute(
+ "title",
+ TorStrings.settings.requestBridgeDialogTitle
+ );
+ // user may have opened a Request Bridge dialog in another tab, so update the
+ // CAPTCHA image or close out the dialog if we have a bridge list
+ this._dialog.addEventListener("focusin", () => {
+ const uri = BridgeDB.currentCaptchaImage;
+ const bridges = BridgeDB.currentBridges;
+
+ // new captcha image
+ if (uri) {
+ this._setcaptchaImage(uri);
+ } else if (bridges) {
+ this._bridges = bridges;
+ this._submitButton.disabled = false;
+ this._dialog.cancelDialog();
+ }
+ });
+
+ this._submitButton = this._dialog.getButton(selectors.submitButton);
+ this._submitButton.setAttribute("label", TorStrings.settings.submitCaptcha);
+ this._submitButton.disabled = true;
+ this._dialog.addEventListener("dialogaccept", e => {
+ e.preventDefault();
+ this.onSubmitCaptcha();
+ });
+ this._dialog.addEventListener("dialoghelp", e => {
+ window.top.openTrustedLinkIn(
+ "https://tb-manual.torproject.org/bridges/",
+ "tab"
+ );
+ });
+
+ this._dialogHeader = this._dialog.querySelector(selectors.dialogHeader);
+ this._dialogHeader.textContent = TorStrings.settings.contactingBridgeDB;
+
+ this._captchaImage = this._dialog.querySelector(selectors.captchaImage);
+
+ // request captcha from bridge db
+ BridgeDB.requestNewCaptchaImage().then(uri => {
+ this._setcaptchaImage(uri);
+ });
+
+ this._captchaEntryTextbox = this._dialog.querySelector(
+ selectors.captchaEntryTextbox
+ );
+ this._captchaEntryTextbox.setAttribute(
+ "placeholder",
+ TorStrings.settings.captchaTextboxPlaceholder
+ );
+ this._captchaEntryTextbox.disabled = true;
+ // disable submit if entry textbox is empty
+ this._captchaEntryTextbox.oninput = () => {
+ this._submitButton.disabled = this._captchaEntryTextbox.value == "";
+ };
+
+ this._captchaRefreshButton = this._dialog.querySelector(
+ selectors.refreshCaptchaButton
+ );
+ this._captchaRefreshButton.disabled = true;
+
+ this._incorrectCaptchaHbox = this._dialog.querySelector(
+ selectors.incorrectCaptchaHbox
+ );
+ this._incorrectCaptchaLabel = this._dialog.querySelector(
+ selectors.incorrectCaptchaLabel
+ );
+ this._incorrectCaptchaLabel.setAttribute(
+ "value",
+ TorStrings.settings.incorrectCaptcha
+ );
+
+ return true;
+ }
+
+ _setcaptchaImage(uri) {
+ if (uri != this._captchaImage.src) {
+ this._captchaImage.src = uri;
+ this._dialogHeader.textContent = TorStrings.settings.solveTheCaptcha;
+ this._setUIDisabled(false);
+ 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) {
+ // defer to later until firefox has populated the dialog with all our elements
+ window.setTimeout(() => {
+ this._populateXUL(window, dialog);
+ }, 0);
+ }
+
+ close() {
+ BridgeDB.close();
+ }
+
+ /*
+ Event Handlers
+ */
+ onSubmitCaptcha() {
+ let captchaText = this._captchaEntryTextbox.value.trim();
+ // noop if the field is empty
+ if (captchaText == "") {
+ return;
+ }
+
+ // freeze ui while we make request
+ this._setUIDisabled(true);
+ this._incorrectCaptchaHbox.style.visibility = "hidden";
+
+ BridgeDB.submitCaptchaGuess(captchaText)
+ .then(aBridges => {
+ if (aBridges) {
+ this._bridges = aBridges;
+ this._submitButton.disabled = false;
+ // This was successful, but use cancelDialog() to close, since
+ // we intercept the `dialogaccept` event.
+ this._dialog.cancelDialog();
+ } else {
+ this._bridges = [];
+ this._setUIDisabled(false);
+ this._incorrectCaptchaHbox.style.visibility = "visible";
+ }
+ })
+ .catch(aError => {
+ // TODO: handle other errors properly here when we do the bridge settings re-design
+ this._bridges = [];
+ this._setUIDisabled(false);
+ this._incorrectCaptchaHbox.style.visibility = "visible";
+ console.log(aError);
+ });
+ }
+
+ onRefreshCaptcha() {
+ this._setUIDisabled(true);
+ this._captchaImage.src = "";
+ this._dialogHeader.textContent = TorStrings.settings.contactingBridgeDB;
+ this._captchaEntryTextbox.value = "";
+ this._incorrectCaptchaHbox.style.visibility = "hidden";
+
+ BridgeDB.requestNewCaptchaImage().then(uri => {
+ this._setcaptchaImage(uri);
+ });
+ }
+
+ openDialog(gSubDialog, aCloseCallback) {
+ gSubDialog.open(
+ "chrome://browser/content/torpreferences/requestBridgeDialog.xhtml",
+ {
+ features: "resizable=yes",
+ closingCallback: () => {
+ this.close();
+ aCloseCallback(this._bridges);
+ },
+ },
+ this
+ );
+ }
+}
diff --git a/browser/components/torpreferences/content/requestBridgeDialog.xhtml b/browser/components/torpreferences/content/requestBridgeDialog.xhtml
new file mode 100644
index 0000000000000..b7286528a8a5a
--- /dev/null
+++ b/browser/components/torpreferences/content/requestBridgeDialog.xhtml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
+
+<window type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+<dialog id="torPreferences-requestBridge-dialog"
+ buttons="help,accept,cancel">
+ <!-- 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 >:( -->
+ <html:h3 id="torPreferences-requestBridge-header"></html:h3>
+ <!-- init to transparent 400x125 png -->
+ <image id="torPreferences-requestBridge-captchaImage" flex="1"/>
+ <hbox id="torPreferences-requestBridge-inputHbox">
+ <html:input id="torPreferences-requestBridge-captchaTextbox" type="text" style="-moz-box-flex: 1;"/>
+ <button id="torPreferences-requestBridge-refreshCaptchaButton"
+ image="chrome://browser/skin/reload.svg"
+ oncommand="requestBridgeDialog.onRefreshCaptcha();"/>
+ </hbox>
+ <hbox id="torPreferences-requestBridge-incorrectCaptchaHbox" align="center">
+ <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];
+ let dialog = document.getElementById("torPreferences-requestBridge-dialog");
+ requestBridgeDialog.init(window, dialog);
+ ]]></script>
+</dialog>
+</window>
diff --git a/browser/components/torpreferences/content/torLogDialog.jsm b/browser/components/torpreferences/content/torLogDialog.jsm
new file mode 100644
index 0000000000000..94a57b9b165ee
--- /dev/null
+++ b/browser/components/torpreferences/content/torLogDialog.jsm
@@ -0,0 +1,84 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["TorLogDialog"];
+
+const { setTimeout, clearTimeout } = ChromeUtils.import(
+ "resource://gre/modules/Timer.jsm"
+);
+
+const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+);
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+
+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",
+ };
+ }
+
+ _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);
+ });
+
+ this._logTextarea.value = TorProtocolService.getLog();
+ }
+
+ init(window, aDialog) {
+ // defer to later until firefox has populated the dialog with all our elements
+ window.setTimeout(() => {
+ this._populateXUL(aDialog);
+ }, 0);
+ }
+
+ 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
+ );
+ }
+}
diff --git a/browser/components/torpreferences/content/torLogDialog.xhtml b/browser/components/torpreferences/content/torLogDialog.xhtml
new file mode 100644
index 0000000000000..9c17f8132978d
--- /dev/null
+++ b/browser/components/torpreferences/content/torLogDialog.xhtml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
+
+<window type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+<dialog id="torPreferences-torLog-dialog"
+ buttons="accept,extra1">
+ <html:textarea
+ id="torPreferences-torDialog-textarea"
+ multiline="true"
+ readonly="true"/>
+ <script type="application/javascript"><![CDATA[
+ "use strict";
+
+ let torLogDialog = window.arguments[0];
+ let dialog = document.getElementById("torPreferences-torLog-dialog");
+ torLogDialog.init(window, dialog);
+ ]]></script>
+</dialog>
+</window>
\ No newline at end of file
diff --git a/browser/components/torpreferences/content/torPreferences.css b/browser/components/torpreferences/content/torPreferences.css
new file mode 100644
index 0000000000000..31b6e29d679f3
--- /dev/null
+++ b/browser/components/torpreferences/content/torPreferences.css
@@ -0,0 +1,541 @@
+ at import url("chrome://branding/content/tor-styles.css");
+
+#category-connection > .category-icon {
+ list-style-image: url("chrome://browser/content/torpreferences/torPreferencesIcon.svg");
+}
+
+html:dir(rtl) input[type="checkbox"].toggle-button::before {
+ /* For some reason, the rule from toggle-button.css is not applied... */
+ scale: -1;
+}
+
+/* Connect Message Box */
+
+#torPreferences-connectMessageBox {
+ display: block;
+ position: relative;
+
+ width: auto;
+ min-height: 32px;
+ border-radius: 4px;
+ padding: 8px;
+}
+
+#torPreferences-connectMessageBox.hidden {
+ display: none;
+}
+
+#torPreferences-connectMessageBox.error {
+ background-color: var(--red-60);
+ color: white;
+}
+
+#torPreferences-connectMessageBox.warning {
+ background-color: var(--purple-50);
+ color: white;
+}
+
+#torPreferences-connectMessageBox table {
+ border-collapse: collapse;
+}
+
+#torPreferences-connectMessageBox td {
+ vertical-align: middle;
+}
+
+#torPreferences-connectMessageBox td:first-child {
+ width: 16px;
+}
+
+#torPreferences-connectMessageBox-icon {
+ width: 16px;
+ height: 16px;
+
+ mask-repeat: no-repeat !important;
+ mask-size: 16px !important;
+}
+
+#torPreferences-connectMessageBox.error #torPreferences-connectMessageBox-icon
+{
+ mask: url("chrome://browser/skin/onion-slash.svg");
+ background-color: white;
+}
+
+#torPreferences-connectMessageBox.warning #torPreferences-connectMessageBox-icon
+{
+ mask: url("chrome://browser/skin/onion.svg");
+ background-color: white;
+}
+
+#torPreferences-connectMessageBox-message {
+ line-height: 16px;
+ padding-inline-start: 8px;
+}
+
+#torPreferences-connectMessageBox-button {
+ display: block;
+ width: auto;
+
+ border-radius: 4px;
+ border: 0;
+
+ padding-inline: 18px;
+ padding-block: 8px;
+ margin-block: 0px;
+ margin-inline-start: 8px;
+ margin-inline-end: 0px;
+
+ font-size: 1.0em;
+ font-weight: 600;
+ white-space: nowrap;
+
+ color: white;
+}
+
+#torPreferences-connectMessageBox.error #torPreferences-connectMessageBox-button {
+ background-color: var(--red-70);
+}
+
+#torPreferences-connectMessageBox.error #torPreferences-connectMessageBox-button:hover {
+ background-color: var(--red-80);
+}
+
+#torPreferences-connectMessageBox.error #torPreferences-connectMessageBox-button:active {
+ background-color: var(--red-90);
+}
+
+#torPreferences-connectMessageBox.warning #torPreferences-connectMessageBox-button {
+ background-color: var(--purple-70);
+}
+
+#torPreferences-connectMessageBox.warning #torPreferences-connectMessageBox-button:hover {
+ background-color: var(--purple-80);
+}
+
+#torPreferences-connectMessageBox.warning #torPreferences-connectMessageBox-button:active {
+ background-color: var(--purple-90);
+}
+
+/* Status */
+#torPreferences-status-box {
+ display: flex;
+ align-items: center;
+}
+
+#torPreferences-status-internet-icon, #torPreferences-status-tor-icon {
+ width: 18px;
+ height: 18px;
+ margin-inline-end: 8px;
+}
+
+#torPreferences-status-internet-label, #torPreferences-status-tor-label {
+ font-weight: bold;
+}
+
+#torPreferences-status-internet-icon {
+ list-style-image: url("chrome://devtools/skin/images/globe.svg");
+}
+
+#torPreferences-status-internet-statusIcon.online,
+#torPreferences-status-internet-statusIcon.offline,
+#torPreferences-status-tor-statusIcon {
+ margin-inline-start: 12px;
+ margin-inline-end: 9px;
+}
+
+#torPreferences-status-internet-statusIcon.online, #torPreferences-status-tor-statusIcon.connected {
+ list-style-image: url("chrome://devtools/skin/images/check.svg");
+ -moz-context-properties: fill;
+ fill: var(--purple-60);
+}
+
+#torPreferences-status-internet-status {
+ margin-inline-end: 32px;
+}
+
+#torPreferences-status-tor-icon {
+ list-style-image: url("chrome://browser/skin/onion.svg");
+}
+
+#torPreferences-status-internet-icon, #torPreferences-status-tor-icon {
+ -moz-context-properties: fill;
+ fill: var(--in-content-text-color);
+}
+
+#torPreferences-status-tor-statusIcon, #torPreferences-status-internet-statusIcon.offline {
+ list-style-image: url("chrome://browser/skin/warning.svg");
+}
+
+#torPreferences-status-tor-statusIcon.blocked {
+ -moz-context-properties: fill;
+ fill: var(--red-60);
+}
+
+/* Bridge settings */
+#torPreferences-bridges-location {
+ width: 280px;
+}
+
+/* Bridge cards */
+:root {
+ --bridgeCard-animation-time: 0.25s;
+}
+
+#torPreferences-currentBridges-cards {
+ /* The padding is needed because the mask-image creates an unexpected result
+ otherwise... */
+ padding: 24px 4px;
+}
+
+#torPreferences-currentBridges-cards.list-collapsed {
+ mask-image: linear-gradient(rgb(0, 0, 0), rgba(0, 0, 0, 0.1));
+}
+
+#torPreferences-currentBridges-cards.disabled {
+ opacity: 0.4;
+}
+
+.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;
+}
+
+.torPreferences-bridgeCard.expanded {
+ margin: 12px 0;
+ background: var(--in-content-box-background);
+ box-shadow: var(--card-shadow);
+}
+
+.torPreferences-bridgeCard:hover {
+ background: var(--in-content-box-background);
+ box-shadow: var(--card-shadow-hover);
+ cursor: pointer;
+}
+
+.torPreferences-bridgeCard-heading {
+ display: flex;
+ align-items: center;
+}
+
+.torPreferences-bridgeCard-id {
+ font-weight: 700;
+}
+
+.torPreferences-bridgeCard-id .emoji {
+ margin-inline-start: 4px;
+ padding: 4px;
+ font-size: 20px;
+ border-radius: 4px;
+ background: var(--in-content-button-background-hover);
+}
+
+.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;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.expanded .torPreferences-bridgeCard-headingAddr {
+ display: none;
+}
+
+.torPreferences-bridgeCard-buttons {
+ display: flex;
+ align-items: center;
+ margin-inline-start: auto;
+ align-self: center;
+}
+
+.torPreferences-bridgeCard-connectedBadge {
+ display: none;
+ padding: 8px 12px;
+ border-radius: 16px;
+ background: rgba(128, 0, 215, 0.1);
+ color: var(--purple-60);
+}
+
+.currently-connected .torPreferences-bridgeCard-connectedBadge {
+ display: flex;
+}
+
+.torPreferences-bridgeCard-connectedIcon {
+ margin-inline-start: 1px;
+ margin-inline-end: 7px;
+ list-style-image: url("chrome://devtools/skin/images/check.svg");
+ -moz-context-properties: fill;
+ fill: var(--purple-60);
+}
+
+.torPreferences-bridgeCard-options {
+ width: 24px;
+ min-width: 0;
+ height: 24px;
+ min-height: 0;
+ margin-inline-start: 8px;
+ padding: 1px;
+ background-image: url("chrome://global/skin/icons/more.svg");
+ background-repeat: no-repeat;
+ background-position: center center;
+ fill: currentColor;
+ -moz-context-properties: fill;
+}
+
+.torPreferences-bridgeCard-qrWrapper {
+ grid-area: bridge-qr;
+ display: flex;
+ flex-direction: column;
+}
+
+.torPreferences-bridgeCard-qr {
+ width: 126px;
+ position: relative;
+}
+
+.torPreferences-bridgeCard-qrCode {
+ width: 112px;
+ height: 112px;
+ /* Define these colors, as they will be passed to the QR code library */
+ background: var(--in-content-box-background);
+ color: var(--in-content-text-color);
+}
+
+.torPreferences-bridgeCard-qrOnionBox {
+ width: 28px;
+ height: 28px;
+ position: absolute;
+ top: 42px;
+ inset-inline-start: 42px;
+ background: var(--in-content-box-background);
+}
+
+.torPreferences-bridgeCard-qrOnion {
+ width: 16px;
+ height: 16px;
+ position: absolute;
+ top: 48px;
+ inset-inline-start: 48px;
+
+ mask: url("chrome://browser/skin/onion.svg");
+ mask-repeat: no-repeat;
+ mask-size: 16px;
+ background: var(--in-content-text-color);
+}
+
+.torPreferences-bridgeCard-qr:hover .torPreferences-bridgeCard-qrOnionBox {
+ background: var(--in-content-text-color);
+}
+
+.torPreferences-bridgeCard-qr:hover .torPreferences-bridgeCard-qrOnion {
+ mask: url("chrome://global/skin/icons/search-glass.svg");
+ background: var(--in-content-box-background);
+}
+
+.torPreferences-bridgeCard-filler {
+ flex: 1;
+}
+
+.torPreferences-bridgeCard-grid {
+ height: 0; /* We will set it in JS when expanding it! */
+ 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';
+ padding-top: 12px;
+ visibility: hidden;
+}
+
+.expanded .torPreferences-bridgeCard-grid {
+ visibility: visible;
+}
+
+.torPreferences-bridgeCard-grid.to-animate {
+ transition: height var(--bridgeCard-animation-time) ease-out, visibility var(--bridgeCard-animation-time);
+ overflow: hidden;
+}
+
+.torPreferences-bridgeCard-share {
+ grid-area: bridge-share;
+}
+
+.torPreferences-bridgeCard-addrBox {
+ grid-area: bridge-address;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin: 8px 0;
+}
+
+.torPreferences-bridgeCard-addr {
+ width: 100%;
+}
+
+.torPreferences-bridgeCard-leranMoreBox {
+ grid-area: bridge-learn-more;
+}
+
+.torPreferences-bridgeCard-copy {
+ grid-area: bridge-copy;
+}
+
+#torPreferences-bridgeCard-template {
+ display: none;
+}
+
+/* Advanced Settings */
+#torPreferences-advanced-grid {
+ display: grid;
+ grid-template-columns: 1fr auto;
+}
+
+#torPreferences-advanced-group button {
+ min-width: 150px;
+}
+
+#torPreferences-advanced-hbox, #torPreferences-torDaemon-hbox {
+ padding-inline-end: 15px;
+}
+
+h3#torPreferences-requestBridge-header {
+ margin: 0;
+}
+
+image#torPreferences-requestBridge-captchaImage {
+ margin: 16px 0 8px 0;
+ min-height: 140px;
+}
+
+button#torPreferences-requestBridge-refreshCaptchaButton {
+ min-width: initial;
+}
+
+dialog#torPreferences-requestBridge-dialog > hbox {
+ margin-bottom: 1em;
+}
+
+/*
+ Various elements that really should be lining up don't because they have inconsistent margins
+*/
+.torMarginFix {
+ margin-left : 4px;
+ margin-right : 4px;
+}
+
+/* Show bridge QR dialog */
+#bridgeQr-container {
+ position: relative;
+ height: 300px;
+}
+
+#bridgeQr-target {
+ position: absolute;
+ width: 300px;
+ height: 300px;
+ left: calc(50% - 150px);
+ background: var(--in-content-box-background);
+ color: var(--in-content-text-color);
+}
+
+#bridgeQr-onionBox {
+ position: absolute;
+ width: 70px;
+ height: 70px;
+ top: 115px;
+ left: calc(50% - 35px);
+ background-color: var(--in-content-box-background);
+}
+
+#bridgeQr-onion {
+ position: absolute;
+ width: 38px;
+ height: 38px;
+ top: 131px;
+ left: calc(50% - 19px);
+ mask: url("chrome://browser/skin/onion.svg");
+ mask-repeat: no-repeat;
+ mask-size: 38px;
+ background: var(--in-content-text-color);
+}
+
+/* Builtin bridge dialog */
+#torPreferences-builtinBridge-header {
+ margin: 8px 0 10px 0;
+}
+
+#torPreferences-builtinBridge-description {
+ margin-bottom: 18px;
+}
+
+#torPreferences-builtinBridge-typeSelection {
+ margin-bottom: 16px;
+ min-height: 14em; /* Hack: make room for at least 4 lines of content for 3 types + 2 for spacing */
+}
+
+#torPreferences-builtinBridge-typeSelection radio label {
+ font-weight: 700;
+}
+
+/* Request bridge dialog */
+/*
+ This hbox is hidden by css here by default so that the
+ xul dialog allocates enough screen space for the error message
+ element, otherwise it gets cut off since dialog's overflow is hidden
+*/
+hbox#torPreferences-requestBridge-incorrectCaptchaHbox {
+ visibility: hidden;
+}
+
+image#torPreferences-requestBridge-errorIcon {
+ list-style-image: url("chrome://browser/skin/warning.svg");
+}
+
+groupbox#torPreferences-bridges-group textarea {
+ white-space: pre;
+ overflow: auto;
+}
+
+/* Provide bridge dialog */
+#torPreferences-provideBridge-header {
+ margin-top: 8px;
+}
+
+/* Connection settings dialog */
+#torPreferences-connection-header {
+ margin: 4px 0 14px 0;
+}
+
+#torPreferences-connection-grid {
+ display: grid;
+ grid-template-columns: auto 1fr;
+}
+
+.torPreferences-connection-checkbox-container {
+ grid-column: 1 / 3;
+}
+
+#torPreferences-localProxy-textboxAddress,
+#torPreferences-localProxy-textboxUsername,
+#torPreferences-localProxy-textboxPassword,
+#torPreferences-connection-textboxAllowedPorts {
+ -moz-box-flex: 1;
+}
+
+/* Tor logs dialog */
+textarea#torPreferences-torDialog-textarea {
+ -moz-box-flex: 1;
+ font-family: monospace;
+ font-size: 0.8em;
+ white-space: pre;
+ overflow: auto;
+ /* 10 lines */
+ min-height: 20em;
+}
diff --git a/browser/components/torpreferences/content/torPreferencesIcon.svg b/browser/components/torpreferences/content/torPreferencesIcon.svg
new file mode 100644
index 0000000000000..382a061774aaa
--- /dev/null
+++ b/browser/components/torpreferences/content/torPreferencesIcon.svg
@@ -0,0 +1,8 @@
+<svg fill="context-fill" fill-opacity="context-fill-opacity" viewBox="0 0 16 16" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
+ <g clip-rule="evenodd" fill-rule="evenodd">
+ <path d="m11 8c0 1.65686-1.34314 3-3 3-1.65685 0-3-1.34314-3-3 0-1.65685 1.34315-3 3-3 1.65686 0 3 1.34315 3 3zm-1.17187 0c0 1.00965-.81848 1.82813-1.82813 1.82813-1.00964 0-1.82812-.81848-1.82812-1.82813 0-1.00964.81848-1.82812 1.82812-1.82812 1.00965 0 1.82813.81848 1.82813 1.82812z"/>
+ <path d="m7.99999 13.25c2.89951 0 5.25001-2.3505 5.25001-5.25001 0-2.89949-2.3505-5.25-5.25001-5.25-2.89949 0-5.25 2.35051-5.25 5.25 0 2.89951 2.35051 5.25001 5.25 5.25001zm0-1.1719c2.25231 0 4.07811-1.8258 4.07811-4.07811 0-2.25228-1.8258-4.07812-4.07811-4.07812-2.25228 0-4.07812 1.82584-4.07812 4.07812 0 2.25231 1.82584 4.07811 4.07812 4.07811z"/>
+ <path d="m8 15.5c4.1421 0 7.5-3.3579 7.5-7.5 0-4.14214-3.3579-7.5-7.5-7.5-4.14214 0-7.5 3.35786-7.5 7.5 0 4.1421 3.35786 7.5 7.5 7.5zm0-1.1719c3.4949 0 6.3281-2.8332 6.3281-6.3281 0-3.49493-2.8332-6.32812-6.3281-6.32812-3.49493 0-6.32812 2.83319-6.32812 6.32812 0 3.4949 2.83319 6.3281 6.32812 6.3281z"/>
+ </g>
+ <path d="m.5 8c0 4.1421 3.35786 7.5 7.5 7.5v-15c-4.14214 0-7.5 3.35786-7.5 7.5z"/>
+</svg>
\ No newline at end of file
diff --git a/browser/components/torpreferences/jar.mn b/browser/components/torpreferences/jar.mn
new file mode 100644
index 0000000000000..ed3bb441084c9
--- /dev/null
+++ b/browser/components/torpreferences/jar.mn
@@ -0,0 +1,19 @@
+browser.jar:
+ content/browser/torpreferences/bridgeQrDialog.xhtml (content/bridgeQrDialog.xhtml)
+ content/browser/torpreferences/bridgeQrDialog.jsm (content/bridgeQrDialog.jsm)
+ content/browser/torpreferences/builtinBridgeDialog.xhtml (content/builtinBridgeDialog.xhtml)
+ content/browser/torpreferences/builtinBridgeDialog.jsm (content/builtinBridgeDialog.jsm)
+ content/browser/torpreferences/connectionSettingsDialog.xhtml (content/connectionSettingsDialog.xhtml)
+ content/browser/torpreferences/connectionSettingsDialog.jsm (content/connectionSettingsDialog.jsm)
+ content/browser/torpreferences/network.svg (content/network.svg)
+ content/browser/torpreferences/provideBridgeDialog.xhtml (content/provideBridgeDialog.xhtml)
+ content/browser/torpreferences/provideBridgeDialog.jsm (content/provideBridgeDialog.jsm)
+ content/browser/torpreferences/requestBridgeDialog.xhtml (content/requestBridgeDialog.xhtml)
+ content/browser/torpreferences/requestBridgeDialog.jsm (content/requestBridgeDialog.jsm)
+ content/browser/torpreferences/connectionCategory.inc.xhtml (content/connectionCategory.inc.xhtml)
+ content/browser/torpreferences/torLogDialog.jsm (content/torLogDialog.jsm)
+ content/browser/torpreferences/torLogDialog.xhtml (content/torLogDialog.xhtml)
+ content/browser/torpreferences/connectionPane.js (content/connectionPane.js)
+ content/browser/torpreferences/connectionPane.xhtml (content/connectionPane.xhtml)
+ content/browser/torpreferences/torPreferences.css (content/torPreferences.css)
+ content/browser/torpreferences/torPreferencesIcon.svg (content/torPreferencesIcon.svg)
diff --git a/browser/components/torpreferences/moz.build b/browser/components/torpreferences/moz.build
new file mode 100644
index 0000000000000..2661ad7cb9f3d
--- /dev/null
+++ b/browser/components/torpreferences/moz.build
@@ -0,0 +1 @@
+JAR_MANIFESTS += ["jar.mn"]
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the tor-commits
mailing list