[tor-commits] [tor-browser] 03/05: squash! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection

gitolite role git at cupani.torproject.org
Tue Apr 5 20:42:33 UTC 2022


This is an automated email from the git hooks/post-receive script.

richard pushed a commit to branch tor-browser-91.8.0esr-11.5-1
in repository tor-browser.

commit 207037ff0a0f29a5c152e76649bc4024466be940
Author: Pier Angelo Vendrame <pierov at torproject.org>
AuthorDate: Thu Feb 10 10:35:38 2022 +0100

    squash! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
    
    Bug 40774: Update about:preferences page to match new UI designs
---
 browser/components/preferences/preferences.js      |   10 +-
 browser/components/preferences/preferences.xhtml   |    5 +-
 .../torpreferences/content/bridgeQrDialog.jsm      |   51 +
 .../torpreferences/content/bridgeQrDialog.xhtml    |   23 +
 .../torpreferences/content/builtinBridgeDialog.jsm |  142 +++
 .../content/builtinBridgeDialog.xhtml              |   43 +
 ...gory.inc.xhtml => connectionCategory.inc.xhtml} |    8 +-
 .../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 |   32 +-
 .../content/requestBridgeDialog.xhtml              |   10 +-
 .../torpreferences/content/torLogDialog.jsm        |   18 +
 .../components/torpreferences/content/torPane.js   |  940 --------------
 .../torpreferences/content/torPane.xhtml           |  157 ---
 .../torpreferences/content/torPreferences.css      |  394 +++++-
 browser/components/torpreferences/jar.mn           |   15 +-
 21 files changed, 2739 insertions(+), 1152 deletions(-)

diff --git a/browser/components/preferences/preferences.js b/browser/components/preferences/preferences.js
index ce338584142ed..5981bcd38fc83 100644
--- a/browser/components/preferences/preferences.js
+++ b/browser/components/preferences/preferences.js
@@ -13,7 +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/torPane.js */
+/* import-globals-from ../torpreferences/content/connectionPane.js */
 
 "use strict";
 
@@ -137,12 +137,12 @@ function init_all() {
     register_module("paneSync", gSyncPane);
   }
   register_module("paneSearchResults", gSearchResultsPane);
-  if (gTorPane.enabled) {
-    document.getElementById("category-tor").hidden = false;
-    register_module("paneTor", gTorPane);
+  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-paneTor").remove();
+    document.getElementById("template-paneConnection").remove();
   }
 
   gSearchResultsPane.init();
diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml
index 07ab5cc7b626d..30ce70079adb7 100644
--- a/browser/components/preferences/preferences.xhtml
+++ b/browser/components/preferences/preferences.xhtml
@@ -6,6 +6,7 @@
 <?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"?>
@@ -158,7 +159,7 @@
           <label class="category-name" flex="1" data-l10n-id="pane-experimental-title"></label>
         </richlistitem>
 
-#include ../torpreferences/content/torCategory.inc.xhtml
+#include ../torpreferences/content/connectionCategory.inc.xhtml
 
       </richlistbox>
 
@@ -213,7 +214,7 @@
 #include containers.inc.xhtml
 #include sync.inc.xhtml
 #include experimental.inc.xhtml
-#include ../torpreferences/content/torPane.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/torCategory.inc.xhtml b/browser/components/torpreferences/content/connectionCategory.inc.xhtml
similarity index 57%
rename from browser/components/torpreferences/content/torCategory.inc.xhtml
rename to browser/components/torpreferences/content/connectionCategory.inc.xhtml
index abe56200f571b..15cf24cfe6950 100644
--- a/browser/components/torpreferences/content/torCategory.inc.xhtml
+++ b/browser/components/torpreferences/content/connectionCategory.inc.xhtml
@@ -1,9 +1,9 @@
-<richlistitem id="category-tor"
+<richlistitem id="category-connection"
               class="category"
-              value="paneTor"
-              helpTopic="prefs-tor"
+              value="paneConnection"
+              helpTopic="prefs-connection"
               align="center"
               hidden="true">
   <image class="category-icon"/>
-  <label id="torPreferences-labelCategory" class="category-name" flex="1" value="Tor"/>
+  <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
index 44ae11762def8..f14bbdcbbb448 100644
--- a/browser/components/torpreferences/content/requestBridgeDialog.jsm
+++ b/browser/components/torpreferences/content/requestBridgeDialog.jsm
@@ -9,7 +9,7 @@ class RequestBridgeDialog {
   constructor() {
     this._dialog = null;
     this._submitButton = null;
-    this._dialogDescription = null;
+    this._dialogHeader = null;
     this._captchaImage = null;
     this._captchaEntryTextbox = null;
     this._captchaRefreshButton = null;
@@ -22,7 +22,7 @@ class RequestBridgeDialog {
     return {
       submitButton:
         "accept" /* not really a selector but a key for dialog's getButton */,
-      dialogDescription: "description#torPreferences-requestBridge-description",
+      dialogHeader: "h3#torPreferences-requestBridge-header",
       captchaImage: "image#torPreferences-requestBridge-captchaImage",
       captchaEntryTextbox: "input#torPreferences-requestBridge-captchaTextbox",
       refreshCaptchaButton:
@@ -34,7 +34,7 @@ class RequestBridgeDialog {
     };
   }
 
-  _populateXUL(dialog) {
+  _populateXUL(window, dialog) {
     const selectors = RequestBridgeDialog.selectors;
 
     this._dialog = dialog;
@@ -66,12 +66,15 @@ class RequestBridgeDialog {
       e.preventDefault();
       this.onSubmitCaptcha();
     });
+    this._dialog.addEventListener("dialoghelp", e => {
+      window.top.openTrustedLinkIn(
+        "https://tb-manual.torproject.org/bridges/",
+        "tab"
+      );
+    });
 
-    this._dialogDescription = this._dialog.querySelector(
-      selectors.dialogDescription
-    );
-    this._dialogDescription.textContent =
-      TorStrings.settings.contactingBridgeDB;
+    this._dialogHeader = this._dialog.querySelector(selectors.dialogHeader);
+    this._dialogHeader.textContent = TorStrings.settings.contactingBridgeDB;
 
     this._captchaImage = this._dialog.querySelector(selectors.captchaImage);
 
@@ -115,7 +118,7 @@ class RequestBridgeDialog {
   _setcaptchaImage(uri) {
     if (uri != this._captchaImage.src) {
       this._captchaImage.src = uri;
-      this._dialogDescription.textContent = TorStrings.settings.solveTheCaptcha;
+      this._dialogHeader.textContent = TorStrings.settings.solveTheCaptcha;
       this._setUIDisabled(false);
       this._captchaEntryTextbox.focus();
       this._captchaEntryTextbox.select();
@@ -135,7 +138,7 @@ class RequestBridgeDialog {
   init(window, dialog) {
     // defer to later until firefox has populated the dialog with all our elements
     window.setTimeout(() => {
-      this._populateXUL(dialog);
+      this._populateXUL(window, dialog);
     }, 0);
   }
 
@@ -176,15 +179,14 @@ class RequestBridgeDialog {
         this._bridges = [];
         this._setUIDisabled(false);
         this._incorrectCaptchaHbox.style.visibility = "visible";
-        console.log(eError);
+        console.log(aError);
       });
   }
 
   onRefreshCaptcha() {
     this._setUIDisabled(true);
     this._captchaImage.src = "";
-    this._dialogDescription.textContent =
-      TorStrings.settings.contactingBridgeDB;
+    this._dialogHeader.textContent = TorStrings.settings.contactingBridgeDB;
     this._captchaEntryTextbox.value = "";
     this._incorrectCaptchaHbox.style.visibility = "hidden";
 
@@ -201,9 +203,9 @@ class RequestBridgeDialog {
         closingCallback: () => {
           this.close();
           aCloseCallback(this._bridges);
-        }
+        },
       },
-      this,
+      this
     );
   }
 }
diff --git a/browser/components/torpreferences/content/requestBridgeDialog.xhtml b/browser/components/torpreferences/content/requestBridgeDialog.xhtml
index 64c4507807fbf..b7286528a8a5a 100644
--- a/browser/components/torpreferences/content/requestBridgeDialog.xhtml
+++ b/browser/components/torpreferences/content/requestBridgeDialog.xhtml
@@ -7,11 +7,11 @@
         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="accept,cancel">
+        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
-       description 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 >:( -->
-  <description id="torPreferences-requestBridge-description">​</description>
+       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">
@@ -32,4 +32,4 @@
     requestBridgeDialog.init(window, dialog);
   ]]></script>
 </dialog>
-</window>
\ No newline at end of file
+</window>
diff --git a/browser/components/torpreferences/content/torLogDialog.jsm b/browser/components/torpreferences/content/torLogDialog.jsm
index ecc684d878c20..94a57b9b165ee 100644
--- a/browser/components/torpreferences/content/torLogDialog.jsm
+++ b/browser/components/torpreferences/content/torLogDialog.jsm
@@ -2,6 +2,10 @@
 
 var EXPORTED_SYMBOLS = ["TorLogDialog"];
 
+const { setTimeout, clearTimeout } = ChromeUtils.import(
+  "resource://gre/modules/Timer.jsm"
+);
+
 const { TorProtocolService } = ChromeUtils.import(
   "resource:///modules/TorProtocolService.jsm"
 );
@@ -12,6 +16,7 @@ class TorLogDialog {
     this._dialog = null;
     this._logTextarea = null;
     this._copyLogButton = null;
+    this._restoreButtonTimeout = null;
   }
 
   static get selectors() {
@@ -36,6 +41,19 @@ class TorLogDialog {
     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();
diff --git a/browser/components/torpreferences/content/torPane.js b/browser/components/torpreferences/content/torPane.js
deleted file mode 100644
index 58eec7ff74aa6..0000000000000
--- a/browser/components/torpreferences/content/torPane.js
+++ /dev/null
@@ -1,940 +0,0 @@
-"use strict";
-
-/* global Services */
-
-const { TorSettings, TorSettingsTopics, TorSettingsData, TorBridgeSource, TorBuiltinBridgeTypes, TorProxyType } = ChromeUtils.import(
-  "resource:///modules/TorSettings.jsm"
-);
-
-const { TorProtocolService } = ChromeUtils.import(
-  "resource:///modules/TorProtocolService.jsm"
-);
-
-const { TorConnect, TorConnectTopics, TorConnectState } = ChromeUtils.import(
-  "resource:///modules/TorConnect.jsm"
-);
-
-const { TorLogDialog } = ChromeUtils.import(
-  "chrome://browser/content/torpreferences/torLogDialog.jsm"
-);
-
-const { RequestBridgeDialog } = ChromeUtils.import(
-  "chrome://browser/content/torpreferences/requestBridgeDialog.jsm"
-);
-
-ChromeUtils.defineModuleGetter(
-  this,
-  "TorStrings",
-  "resource:///modules/TorStrings.jsm"
-);
-
-/*
-  Tor Pane
-
-  Code for populating the XUL in about:preferences#tor, handling input events, interfacing with tor-launcher
-*/
-const gTorPane = (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",
-    },
-    quickstart: {
-      header: "h2#torPreferences-quickstart-header",
-      description: "span#torPreferences-quickstart-description",
-      enableQuickstartCheckbox: "checkbox#torPreferences-quickstart-toggle",
-    },
-    bridges: {
-      header: "h2#torPreferences-bridges-header",
-      description: "span#torPreferences-bridges-description",
-      learnMore: "label#torPreferences-bridges-learnMore",
-      useBridgeCheckbox: "checkbox#torPreferences-bridges-toggle",
-      bridgeSelectionRadiogroup:
-        "radiogroup#torPreferences-bridges-bridgeSelection",
-      builtinBridgeOption: "radio#torPreferences-bridges-radioBuiltin",
-      builtinBridgeList: "menulist#torPreferences-bridges-builtinList",
-      requestBridgeOption: "radio#torPreferences-bridges-radioRequestBridge",
-      requestBridgeButton: "button#torPreferences-bridges-buttonRequestBridge",
-      requestBridgeTextarea:
-        "textarea#torPreferences-bridges-textareaRequestBridge",
-      provideBridgeOption: "radio#torPreferences-bridges-radioProvideBridge",
-      provideBridgeDescription:
-        "description#torPreferences-bridges-descriptionProvideBridge",
-      provideBridgeTextarea:
-        "textarea#torPreferences-bridges-textareaProvideBridge",
-    },
-    advanced: {
-      header: "h2#torPreferences-advanced-header",
-      description: "span#torPreferences-advanced-description",
-      learnMore: "label#torPreferences-advanced-learnMore",
-      useProxyCheckbox: "checkbox#torPreferences-advanced-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-advanced-toggleFirewall",
-      firewallAllowedPortsLabel: "label#torPreferences-advanced-allowedPorts",
-      firewallAllowedPortsTextbox:
-        "input#torPreferences-advanced-textboxAllowedPorts",
-      torLogsLabel: "label#torPreferences-torLogs",
-      torLogsButton: "button#torPreferences-buttonTorLogs",
-    },
-  }; /* selectors */
-
-  let retval = {
-    // cached frequently accessed DOM elements
-    _messageBox: null,
-    _messageBoxMessage: null,
-    _messageBoxButton: null,
-    _enableQuickstartCheckbox: null,
-    _useBridgeCheckbox: null,
-    _bridgeSelectionRadiogroup: null,
-    _builtinBridgeOption: null,
-    _builtinBridgeMenulist: null,
-    _requestBridgeOption: null,
-    _requestBridgeButton: null,
-    _requestBridgeTextarea: null,
-    _provideBridgeOption: null,
-    _provideBridgeTextarea: null,
-    _useProxyCheckbox: null,
-    _proxyTypeLabel: null,
-    _proxyTypeMenulist: null,
-    _proxyAddressLabel: null,
-    _proxyAddressTextbox: null,
-    _proxyPortLabel: null,
-    _proxyPortTextbox: null,
-    _proxyUsernameLabel: null,
-    _proxyUsernameTextbox: null,
-    _proxyPasswordLabel: null,
-    _proxyPasswordTextbox: null,
-    _useFirewallCheckbox: null,
-    _allowedPortsLabel: null,
-    _allowedPortsTextbox: null,
-
-    // tor network settings
-    _bridgeSettings: null,
-    _proxySettings: null,
-    _firewallSettings: null,
-
-    // 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
-
-      this._messageBox = prefpane.querySelector(selectors.messageBox.box);
-      this._messageBoxMessage = prefpane.querySelector(selectors.messageBox.message);
-      this._messageBoxButton = prefpane.querySelector(selectors.messageBox.button);
-      // wire up connect button
-      this._messageBoxButton.addEventListener("click", () => {
-        TorConnect.beginBootstrap();
-        TorConnect.openTorConnect();
-      });
-
-      this._populateMessagebox = () => {
-        if (TorConnect.shouldShowTorConnect &&
-            TorConnect.state === TorConnectState.Configuring) {
-          // set messagebox style and text
-          if (TorProtocolService.torBootstrapErrorOccurred()) {
-            this._messageBox.parentNode.style.display = null;
-            this._messageBox.className = "error";
-            this._messageBoxMessage.innerText = TorStrings.torConnect.tryAgainMessage;
-            this._messageBoxButton.innerText = TorStrings.torConnect.tryAgain;
-          } else {
-            this._messageBox.parentNode.style.display = null;
-            this._messageBox.className = "warning";
-            this._messageBoxMessage.innerText = TorStrings.torConnect.connectMessage;
-            this._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)
-          this._messageBox.parentNode.style.display = "none";
-          this._messageBox.className = "hidden";
-          this._messageBoxMessage.innerText = "";
-          this._messageBoxButton.innerText = "";
-        }
-      }
-      this._populateMessagebox();
-      Services.obs.addObserver(this, TorConnectTopics.StateChange);
-
-      // update the messagebox whenever we come back to the page
-      window.addEventListener("focus", val => {
-        this._populateMessagebox();
-      });
-
-      // Heading
-      prefpane.querySelector(selectors.torPreferences.header).innerText =
-        TorStrings.settings.torPreferencesHeading;
-      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
-        );
-      }
-
-      // 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);
-      }
-
-      this._useBridgeCheckbox = prefpane.querySelector(
-        selectors.bridges.useBridgeCheckbox
-      );
-      this._useBridgeCheckbox.setAttribute(
-        "label",
-        TorStrings.settings.useBridge
-      );
-      this._useBridgeCheckbox.addEventListener("command", e => {
-        const checked = this._useBridgeCheckbox.checked;
-        gTorPane.onToggleBridge(checked).onUpdateBridgeSettings();
-      });
-      this._bridgeSelectionRadiogroup = prefpane.querySelector(
-        selectors.bridges.bridgeSelectionRadiogroup
-      );
-      this._bridgeSelectionRadiogroup.value = TorBridgeSource.BuiltIn;
-      this._bridgeSelectionRadiogroup.addEventListener("command", e => {
-        const value = this._bridgeSelectionRadiogroup.value;
-        gTorPane.onSelectBridgeOption(value).onUpdateBridgeSettings();
-      });
-
-      // Builtin bridges
-      this._builtinBridgeOption = prefpane.querySelector(
-        selectors.bridges.builtinBridgeOption
-      );
-      this._builtinBridgeOption.setAttribute(
-        "label",
-        TorStrings.settings.selectBridge
-      );
-      this._builtinBridgeOption.setAttribute("value", TorBridgeSource.BuiltIn);
-      this._builtinBridgeMenulist = prefpane.querySelector(
-        selectors.bridges.builtinBridgeList
-      );
-      this._builtinBridgeMenulist.addEventListener("command", e => {
-        gTorPane.onUpdateBridgeSettings();
-      });
-
-      // Request bridge
-      this._requestBridgeOption = prefpane.querySelector(
-        selectors.bridges.requestBridgeOption
-      );
-      this._requestBridgeOption.setAttribute(
-        "label",
-        TorStrings.settings.requestBridgeFromTorProject
-      );
-      this._requestBridgeOption.setAttribute("value", TorBridgeSource.BridgeDB);
-      this._requestBridgeButton = prefpane.querySelector(
-        selectors.bridges.requestBridgeButton
-      );
-      this._requestBridgeButton.setAttribute(
-        "label",
-        TorStrings.settings.requestNewBridge
-      );
-      this._requestBridgeButton.addEventListener("command", () =>
-        gTorPane.onRequestBridge()
-      );
-      this._requestBridgeTextarea = prefpane.querySelector(
-        selectors.bridges.requestBridgeTextarea
-      );
-
-      // Provide a bridge
-      this._provideBridgeOption = prefpane.querySelector(
-        selectors.bridges.provideBridgeOption
-      );
-      this._provideBridgeOption.setAttribute(
-        "label",
-        TorStrings.settings.provideBridge
-      );
-      this._provideBridgeOption.setAttribute(
-        "value",
-        TorBridgeSource.UserProvided
-      );
-      prefpane.querySelector(
-        selectors.bridges.provideBridgeDescription
-      ).textContent = TorStrings.settings.provideBridgeDirections;
-      this._provideBridgeTextarea = prefpane.querySelector(
-        selectors.bridges.provideBridgeTextarea
-      );
-      this._provideBridgeTextarea.setAttribute(
-        "placeholder",
-        TorStrings.settings.provideBridgePlaceholder
-      );
-      this._provideBridgeTextarea.addEventListener("blur", () => {
-        gTorPane.onUpdateBridgeSettings();
-      });
-
-      // Advanced setup
-      prefpane.querySelector(selectors.advanced.header).innerText =
-        TorStrings.settings.advancedHeading;
-      prefpane.querySelector(selectors.advanced.description).textContent =
-        TorStrings.settings.advancedDescription;
-      {
-        let learnMore = prefpane.querySelector(selectors.advanced.learnMore);
-        learnMore.setAttribute("value", TorStrings.settings.learnMore);
-        learnMore.setAttribute(
-          "href",
-          TorStrings.settings.learnMoreNetworkSettingsURL
-        );
-      }
-
-      // Local Proxy
-      this._useProxyCheckbox = prefpane.querySelector(
-        selectors.advanced.useProxyCheckbox
-      );
-      this._useProxyCheckbox.setAttribute(
-        "label",
-        TorStrings.settings.useLocalProxy
-      );
-      this._useProxyCheckbox.addEventListener("command", e => {
-        const checked = this._useProxyCheckbox.checked;
-        gTorPane.onToggleProxy(checked).onUpdateProxySettings();
-      });
-      this._proxyTypeLabel = prefpane.querySelector(
-        selectors.advanced.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 = prefpane.querySelector(
-        selectors.advanced.proxyTypeList
-      );
-      this._proxyTypeMenulist.addEventListener("command", e => {
-        const value = this._proxyTypeMenulist.value;
-        gTorPane.onSelectProxyType(value).onUpdateProxySettings();
-      });
-      for (let currentProxy of mockProxies) {
-        let menuEntry = document.createXULElement("menuitem");
-        menuEntry.setAttribute("value", currentProxy.value);
-        menuEntry.setAttribute("label", currentProxy.label);
-        this._proxyTypeMenulist
-          .querySelector("menupopup")
-          .appendChild(menuEntry);
-      }
-
-      this._proxyAddressLabel = prefpane.querySelector(
-        selectors.advanced.proxyAddressLabel
-      );
-      this._proxyAddressLabel.setAttribute(
-        "value",
-        TorStrings.settings.proxyAddress
-      );
-      this._proxyAddressTextbox = prefpane.querySelector(
-        selectors.advanced.proxyAddressTextbox
-      );
-      this._proxyAddressTextbox.setAttribute(
-        "placeholder",
-        TorStrings.settings.proxyAddressPlaceholder
-      );
-      this._proxyAddressTextbox.addEventListener("blur", () => {
-        gTorPane.onUpdateProxySettings();
-      });
-      this._proxyPortLabel = prefpane.querySelector(
-        selectors.advanced.proxyPortLabel
-      );
-      this._proxyPortLabel.setAttribute("value", TorStrings.settings.proxyPort);
-      this._proxyPortTextbox = prefpane.querySelector(
-        selectors.advanced.proxyPortTextbox
-      );
-      this._proxyPortTextbox.addEventListener("blur", () => {
-        gTorPane.onUpdateProxySettings();
-      });
-      this._proxyUsernameLabel = prefpane.querySelector(
-        selectors.advanced.proxyUsernameLabel
-      );
-      this._proxyUsernameLabel.setAttribute(
-        "value",
-        TorStrings.settings.proxyUsername
-      );
-      this._proxyUsernameTextbox = prefpane.querySelector(
-        selectors.advanced.proxyUsernameTextbox
-      );
-      this._proxyUsernameTextbox.setAttribute(
-        "placeholder",
-        TorStrings.settings.proxyUsernamePasswordPlaceholder
-      );
-      this._proxyUsernameTextbox.addEventListener("blur", () => {
-        gTorPane.onUpdateProxySettings();
-      });
-      this._proxyPasswordLabel = prefpane.querySelector(
-        selectors.advanced.proxyPasswordLabel
-      );
-      this._proxyPasswordLabel.setAttribute(
-        "value",
-        TorStrings.settings.proxyPassword
-      );
-      this._proxyPasswordTextbox = prefpane.querySelector(
-        selectors.advanced.proxyPasswordTextbox
-      );
-      this._proxyPasswordTextbox.setAttribute(
-        "placeholder",
-        TorStrings.settings.proxyUsernamePasswordPlaceholder
-      );
-      this._proxyPasswordTextbox.addEventListener("blur", () => {
-        gTorPane.onUpdateProxySettings();
-      });
-
-      // Local firewall
-      this._useFirewallCheckbox = prefpane.querySelector(
-        selectors.advanced.useFirewallCheckbox
-      );
-      this._useFirewallCheckbox.setAttribute(
-        "label",
-        TorStrings.settings.useFirewall
-      );
-      this._useFirewallCheckbox.addEventListener("command", e => {
-        const checked = this._useFirewallCheckbox.checked;
-        gTorPane.onToggleFirewall(checked).onUpdateFirewallSettings();
-      });
-      this._allowedPortsLabel = prefpane.querySelector(
-        selectors.advanced.firewallAllowedPortsLabel
-      );
-      this._allowedPortsLabel.setAttribute(
-        "value",
-        TorStrings.settings.allowedPorts
-      );
-      this._allowedPortsTextbox = prefpane.querySelector(
-        selectors.advanced.firewallAllowedPortsTextbox
-      );
-      this._allowedPortsTextbox.setAttribute(
-        "placeholder",
-        TorStrings.settings.allowedPortsPlaceholder
-      );
-      this._allowedPortsTextbox.addEventListener("blur", () => {
-        gTorPane.onUpdateFirewallSettings();
-      });
-
-      // 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", () => {
-        gTorPane.onViewTorLogs();
-      });
-
-      // Disable all relevant elements by default
-      this._setElementsDisabled(
-        [
-          this._builtinBridgeOption,
-          this._builtinBridgeMenulist,
-          this._requestBridgeOption,
-          this._requestBridgeButton,
-          this._requestBridgeTextarea,
-          this._provideBridgeOption,
-          this._provideBridgeTextarea,
-          this._proxyTypeLabel,
-          this._proxyTypeMenulist,
-          this._proxyAddressLabel,
-          this._proxyAddressTextbox,
-          this._proxyPortLabel,
-          this._proxyPortTextbox,
-          this._proxyUsernameLabel,
-          this._proxyUsernameTextbox,
-          this._proxyPasswordLabel,
-          this._proxyPasswordTextbox,
-          this._allowedPortsLabel,
-          this._allowedPortsTextbox,
-        ],
-        true
-      );
-
-      // init bridge UI
-      for (let currentBridge of TorBuiltinBridgeTypes) {
-        let menuEntry = document.createXULElement("menuitem");
-        menuEntry.setAttribute("value", currentBridge);
-        menuEntry.setAttribute("label", currentBridge);
-        this._builtinBridgeMenulist
-          .querySelector("menupopup")
-          .appendChild(menuEntry);
-      }
-
-      if (TorSettings.bridges.enabled) {
-        this.onSelectBridgeOption(TorSettings.bridges.source);
-        this.onToggleBridge(
-          TorSettings.bridges.source != TorBridgeSource.Invalid
-        );
-        switch (TorSettings.bridges.source) {
-          case TorBridgeSource.Invalid:
-            break;
-          case TorBridgeSource.BuiltIn:
-            this._builtinBridgeMenulist.value = TorSettings.bridges.builtin_type;
-            break;
-          case TorBridgeSource.BridgeDB:
-            this._requestBridgeTextarea.value = TorSettings.bridges.bridge_strings.join("\n");
-            break;
-          case TorBridgeSource.UserProvided:
-            this._provideBridgeTextarea.value = TorSettings.bridges.bridge_strings.join("\n");
-            break;
-        }
-      }
-
-      // init proxy UI
-      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;
-      }
-
-      // init firewall
-      if (TorSettings.firewall.enabled) {
-        this.onToggleFirewall(true);
-        this._allowedPortsTextbox.value = TorSettings.firewall.allowed_ports.join(", ");
-      }
-    },
-
-    init() {
-      this._populateXUL();
-
-      let onUnload = () => {
-        window.removeEventListener("unload", onUnload);
-        gTorPane.uninit();
-      };
-      window.addEventListener("unload", onUnload);
-    },
-
-    uninit() {
-      // unregister our observer topics
-      Services.obs.removeObserver(this, TorSettingsTopics.SettingChanged);
-      Services.obs.removeObserver(this, TorConnectTopics.StateChange);
-    },
-
-    // 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._populateMessagebox();
-          break;
-        }
-      }
-    },
-
-    // callback when using bridges toggled
-    onToggleBridge(enabled) {
-      this._useBridgeCheckbox.checked = enabled;
-      let disabled = !enabled;
-
-      // first disable all the bridge related elements
-      this._setElementsDisabled(
-        [
-          this._builtinBridgeOption,
-          this._builtinBridgeMenulist,
-          this._requestBridgeOption,
-          this._requestBridgeButton,
-          this._requestBridgeTextarea,
-          this._provideBridgeOption,
-          this._provideBridgeTextarea,
-        ],
-        disabled
-      );
-
-      // and selectively re-enable based on the radiogroup's current value
-      if (enabled) {
-        this.onSelectBridgeOption(this._bridgeSelectionRadiogroup.value);
-      } else {
-        this.onSelectBridgeOption(TorBridgeSource.Invalid);
-      }
-      return this;
-    },
-
-    // callback when a bridge option is selected
-    onSelectBridgeOption(source) {
-      if (typeof source === "string") {
-        source = parseInt(source);
-      }
-
-      // disable all of the bridge elements under radio buttons
-      this._setElementsDisabled(
-        [
-          this._builtinBridgeMenulist,
-          this._requestBridgeButton,
-          this._requestBridgeTextarea,
-          this._provideBridgeTextarea,
-        ],
-        true
-      );
-
-      if (source != TorBridgeSource.Invalid) {
-        this._bridgeSelectionRadiogroup.value = source;
-      }
-
-      switch (source) {
-        case TorBridgeSource.BuiltIn: {
-          this._setElementsDisabled([this._builtinBridgeMenulist], false);
-          break;
-        }
-        case TorBridgeSource.BridgeDB: {
-          this._setElementsDisabled(
-            [this._requestBridgeButton, this._requestBridgeTextarea],
-            false
-          );
-          break;
-        }
-        case TorBridgeSource.UserProvided: {
-          this._setElementsDisabled([this._provideBridgeTextarea], false);
-          break;
-        }
-      }
-      return this;
-    },
-
-    // called when the request bridge button is activated
-    onRequestBridge() {
-      let requestBridgeDialog = new RequestBridgeDialog();
-      requestBridgeDialog.openDialog(
-        gSubDialog,
-        aBridges => {
-          if (aBridges.length > 0) {
-            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._requestBridgeTextarea.value = bridgeStrings;
-            });
-          }
-        }
-      );
-      return this;
-    },
-
-    // pushes bridge settings from UI to tor
-    onUpdateBridgeSettings() {
-      let source = this._useBridgeCheckbox.checked
-        ? parseInt(this._bridgeSelectionRadiogroup.value)
-        : TorBridgeSource.Invalid;
-
-      switch (source) {
-        case TorBridgeSource.Invalid: {
-          TorSettings.bridges.enabled = false;
-        }
-        break;
-        case TorBridgeSource.BuiltIn: {
-          // if there is a built-in bridge already selected, use that
-          let bridgeType = this._builtinBridgeMenulist.value;
-          console.log(`bridge type: ${bridgeType}`);
-          if (bridgeType) {
-            TorSettings.bridges.enabled = true;
-            TorSettings.bridges.source = TorBridgeSource.BuiltIn;
-            TorSettings.bridges.builtin_type = bridgeType;
-          } else {
-            TorSettings.bridges.enabled = false;
-          }
-          break;
-        }
-        case TorBridgeSource.BridgeDB: {
-          // if there are bridgedb bridges saved in the text area, use them
-          let bridgeStrings = this._requestBridgeTextarea.value;
-          if (bridgeStrings) {
-            TorSettings.bridges.enabled = true;
-            TorSettings.bridges.source = TorBridgeSource.BridgeDB;
-            TorSettings.bridges.bridge_strings = bridgeStrings;
-          } else {
-            TorSettings.bridges.enabled = false;
-          }
-          break;
-        }
-        case TorBridgeSource.UserProvided: {
-          // if bridges already exist in the text area, use them
-          let bridgeStrings = this._provideBridgeTextarea.value;
-          if (bridgeStrings) {
-            TorSettings.bridges.enabled = true;
-            TorSettings.bridges.source = TorBridgeSource.UserProvided;
-            TorSettings.bridges.bridge_strings = bridgeStrings;
-          } else {
-            TorSettings.bridges.enabled = false;
-          }
-          break;
-        }
-      }
-      TorSettings.saveToPrefs();
-      TorSettings.applySettings();
-
-      return this;
-    },
-
-    // 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
-      );
-      this.onSelectProxyType(this._proxyTypeMenulist.value);
-      return this;
-    },
-
-    // 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;
-        }
-      }
-      return this;
-    },
-
-    // pushes proxy settings from UI to tor
-    onUpdateProxySettings() {
-      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;
-      }
-      TorSettings.saveToPrefs();
-      TorSettings.applySettings();
-
-      return this;
-    },
-
-    // callback when firewall proxy is toggled
-    onToggleFirewall(enabled) {
-      this._useFirewallCheckbox.checked = enabled;
-      let disabled = !enabled;
-
-      this._setElementsDisabled(
-        [this._allowedPortsLabel, this._allowedPortsTextbox],
-        disabled
-      );
-
-      return this;
-    },
-
-    // pushes firewall settings from UI to tor
-    onUpdateFirewallSettings() {
-
-      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();
-
-      return this;
-    },
-
-    onViewTorLogs() {
-      let torLogDialog = new TorLogDialog();
-      torLogDialog.openDialog(gSubDialog);
-    },
-  };
-  return retval;
-})(); /* gTorPane */
diff --git a/browser/components/torpreferences/content/torPane.xhtml b/browser/components/torpreferences/content/torPane.xhtml
deleted file mode 100644
index 7c8071f2cf106..0000000000000
--- a/browser/components/torpreferences/content/torPane.xhtml
+++ /dev/null
@@ -1,157 +0,0 @@
-<!-- Tor panel -->
-
-<script type="application/javascript"
-        src="chrome://browser/content/torpreferences/torPane.js"/>
-<html:template id="template-paneTor">
-
-<!-- Tor Connect Message Box -->
-<groupbox data-category="paneTor" hidden="true">
-  <html:div id="torPreferences-connectMessageBox"
-            class="subcategory"
-            data-category="paneTor"
-            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="paneTor"
-      hidden="true">
-  <html:h1 id="torPreferences-header"/>
-</hbox>
-
-<groupbox data-category="paneTor"
-          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>
-
-<!-- Quickstart -->
-<groupbox id="torPreferences-quickstart-group"
-          data-category="paneTor"
-          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 -->
-<groupbox id="torPreferences-bridges-group"
-          data-category="paneTor"
-          hidden="true">
-  <html:h2 id="torPreferences-bridges-header"/>
-  <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>
-  <checkbox id="torPreferences-bridges-toggle"/>
-  <radiogroup id="torPreferences-bridges-bridgeSelection">
-    <hbox class="indent">
-      <radio id="torPreferences-bridges-radioBuiltin"/>
-      <spacer flex="1"/>
-      <menulist id="torPreferences-bridges-builtinList" class="torMarginFix">
-        <menupopup/>
-      </menulist>
-    </hbox>
-    <vbox class="indent">
-      <hbox>
-        <radio id="torPreferences-bridges-radioRequestBridge"/>
-        <space flex="1"/>
-        <button id="torPreferences-bridges-buttonRequestBridge" class="torMarginFix"/>
-      </hbox>
-      <html:textarea
-        id="torPreferences-bridges-textareaRequestBridge"
-        class="indent torMarginFix"
-        multiline="true"
-        rows="3"
-        readonly="true"/>
-    </vbox>
-    <hbox class="indent" flex="1">
-      <vbox flex="1">
-        <radio id="torPreferences-bridges-radioProvideBridge"/>
-        <description id="torPreferences-bridges-descriptionProvideBridge" class="indent"/>
-        <html:textarea
-          id="torPreferences-bridges-textareaProvideBridge"
-          class="indent torMarginFix"
-          multiline="true"
-          rows="3"/>
-      </vbox>
-    </hbox>
-  </radiogroup>
-</groupbox>
-
-<!-- Advanced -->
-<groupbox id="torPreferences-advanced-group"
-          data-category="paneTor"
-          hidden="true">
-  <html:h2 id="torPreferences-advanced-header"/>
-  <description flex="1">
-    <html:span id="torPreferences-advanced-description" class="tail-with-learn-more"/>
-    <label id="torPreferences-advanced-learnMore" class="learnMore text-link" is="text-link" style="display:none"/>
-  </description>
-  <box id="torPreferences-advanced-grid">
-    <!-- Local Proxy -->
-    <hbox class="torPreferences-advanced-checkbox-container">
-      <checkbox id="torPreferences-advanced-toggleProxy"/>
-    </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-advanced-checkbox-container">
-      <checkbox id="torPreferences-advanced-toggleFirewall"/>
-    </hbox>
-    <hbox class="indent" align="center">
-      <label id="torPreferences-advanced-allowedPorts"/>
-    </hbox>
-    <hbox align="center">
-      <html:input id="torPreferences-advanced-textboxAllowedPorts" type="text" class="torMarginFix" value="80,443"/>
-    </hbox>
-  </box>
-  <hbox id="torPreferences-torDaemon-hbox" align="center">
-    <label id="torPreferences-torLogs"/>
-    <spacer flex="1"/>
-    <button id="torPreferences-buttonTorLogs" class="torMarginFix"/>
-  </hbox>
-</groupbox>
-</html:template>
\ No newline at end of file
diff --git a/browser/components/torpreferences/content/torPreferences.css b/browser/components/torpreferences/content/torPreferences.css
index b6eb0a740e5e4..31b6e29d679f3 100644
--- a/browser/components/torpreferences/content/torPreferences.css
+++ b/browser/components/torpreferences/content/torPreferences.css
@@ -1,9 +1,14 @@
 @import url("chrome://branding/content/tor-styles.css");
 
-#category-tor > .category-icon {
+#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 {
@@ -64,8 +69,7 @@
 
 #torPreferences-connectMessageBox-message {
   line-height: 16px;
-  font-size: 1.11em;
-  padding-left: 8px!important;
+  padding-inline-start: 8px;
 }
 
 #torPreferences-connectMessageBox-button {
@@ -112,36 +116,302 @@
   background-color: var(--purple-90);
 }
 
-/* Advanced Settings */
+/* Status */
+#torPreferences-status-box {
+  display: flex;
+  align-items: center;
+}
 
-#torPreferences-advanced-grid {
+#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-columns: auto 1fr;
+  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;
 }
 
-.torPreferences-advanced-checkbox-container {
-  grid-column: 1 / 3;
+.expanded .torPreferences-bridgeCard-grid {
+  visibility: visible;
 }
 
-#torPreferences-localProxy-textboxAddress,
-#torPreferences-localProxy-textboxUsername,
-#torPreferences-localProxy-textboxPassword,
-#torPreferences-advanced-textboxAllowedPorts {
-  -moz-box-flex: 1;
+.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;
 }
 
-hbox#torPreferences-torDaemon-hbox {
-  margin-top: 20px;
+#torPreferences-advanced-group button {
+  min-width: 150px;
 }
 
-description#torPreferences-requestBridge-description {
-  /*margin-bottom: 1em;*/
-  min-height: 2em;
+#torPreferences-advanced-hbox, #torPreferences-torDaemon-hbox {
+  padding-inline-end: 15px;
+}
+
+h3#torPreferences-requestBridge-header {
+  margin: 0;
 }
 
 image#torPreferences-requestBridge-captchaImage {
-  margin: 1em;
-  min-height: 125px;
+  margin: 16px 0 8px 0;
+  min-height: 140px;
 }
 
 button#torPreferences-requestBridge-refreshCaptchaButton {
@@ -160,6 +430,61 @@ dialog#torPreferences-requestBridge-dialog > hbox {
   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
@@ -178,6 +503,33 @@ groupbox#torPreferences-bridges-group textarea {
   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;
@@ -186,4 +538,4 @@ textarea#torPreferences-torDialog-textarea {
   overflow: auto;
   /* 10 lines */
   min-height: 20em;
-}
\ No newline at end of file
+}
diff --git a/browser/components/torpreferences/jar.mn b/browser/components/torpreferences/jar.mn
index 552c92b2feff6..ed3bb441084c9 100644
--- a/browser/components/torpreferences/jar.mn
+++ b/browser/components/torpreferences/jar.mn
@@ -1,10 +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/torCategory.inc.xhtml             (content/torCategory.inc.xhtml)
+    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/torPane.js                        (content/torPane.js)
-    content/browser/torpreferences/torPane.xhtml                     (content/torPane.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)

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the tor-commits mailing list