[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [tor-browser] 67/85: fixup! Bug 27476: Implement about:torconnect captive portal within Tor Browser



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

pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2
in repository tor-browser.

commit d3f26be63d2106c48f47dabd51ff6416d97a9e64
Author: Pier Angelo Vendrame <pierov@xxxxxxxxxxxxxx>
AuthorDate: Thu Apr 7 12:23:55 2022 +0200

    fixup! Bug 27476: Implement about:torconnect captive portal within Tor Browser
    
    Changes introduced by !275
---
 browser/components/torconnect/TorConnectParent.jsm |  53 +-
 .../torconnect/content/aboutTorConnect.css         | 178 ++++--
 .../torconnect/content/aboutTorConnect.js          | 675 +++++++++++++--------
 .../torconnect/content/aboutTorConnect.xhtml       |  24 +-
 browser/components/torconnect/content/globe.svg    |   4 -
 browser/components/torconnect/jar.mn               |   1 -
 toolkit/modules/RemotePageAccessManager.jsm        |   7 +-
 7 files changed, 596 insertions(+), 346 deletions(-)

diff --git a/browser/components/torconnect/TorConnectParent.jsm b/browser/components/torconnect/TorConnectParent.jsm
index dd3d1b2410f95..3c2a56934c145 100644
--- a/browser/components/torconnect/TorConnectParent.jsm
+++ b/browser/components/torconnect/TorConnectParent.jsm
@@ -4,13 +4,18 @@ var EXPORTED_SYMBOLS = ["TorConnectParent"];
 
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
-const { TorConnect, TorConnectTopics, TorConnectState, TorCensorshipLevel } = ChromeUtils.import(
-  "resource:///modules/TorConnect.jsm"
-);
+const {
+  InternetStatus,
+  TorConnect,
+  TorConnectTopics,
+  TorConnectState,
+} = ChromeUtils.import("resource:///modules/TorConnect.jsm");
 const { TorSettings, TorSettingsTopics, TorSettingsData } = ChromeUtils.import(
   "resource:///modules/TorSettings.jsm"
 );
 
+const BroadcastTopic = "about-torconnect:broadcast";
+
 /*
 This object is basically a marshalling interface between the TorConnect module
 and a particular about:torconnect page
@@ -24,15 +29,16 @@ class TorConnectParent extends JSWindowActorParent {
 
     this.state = {
       State: TorConnect.state,
-      DetectedCensorshiplevel: TorConnect.detectedCensorshiplevel,
       StateChanged: false,
+      PreviousState: TorConnectState.Initial,
       ErrorMessage: TorConnect.errorMessage,
       ErrorDetails: TorConnect.errorDetails,
       BootstrapProgress: TorConnect.bootstrapProgress,
       BootstrapStatus: TorConnect.bootstrapStatus,
+      InternetStatus: TorConnect.internetStatus,
       ShowViewLog: TorConnect.logHasWarningOrError,
       QuickStartEnabled: TorSettings.quickstart.enabled,
-      CountryCodes: TorConnect.countryCodes,
+      UIState: TorConnect.uiState,
     };
 
     // JSWindowActiveParent derived objects cannot observe directly, so create a member
@@ -50,6 +56,7 @@ class TorConnectParent extends JSWindowActorParent {
         self.state.StateChanged = false;
         switch (aTopic) {
           case TorConnectTopics.StateChange: {
+            self.state.PreviousState = self.state.State;
             self.state.State = obj.state;
             self.state.StateChanged = true;
 
@@ -73,13 +80,7 @@ class TorConnectParent extends JSWindowActorParent {
           case TorConnectTopics.BootstrapError: {
             self.state.ErrorMessage = obj.message;
             self.state.ErrorDetails = obj.details;
-            self.state.DetectedCensorshiplevel = obj.censorshipLevel;
-
-            // With severe censorshp, we offer user list of countries to try
-            if (self.state.DetectedCensorshiplevel == TorCensorshipLevel.Severe) {
-              self.state.CountryCodes = TorConnect.countryCodes;
-            }
-
+            self.state.InternetStatus = TorConnect.internetStatus;
             self.state.ShowViewLog = true;
             break;
           }
@@ -114,6 +115,17 @@ class TorConnectParent extends JSWindowActorParent {
       this.torConnectObserver,
       TorSettingsTopics.SettingChanged
     );
+
+    this.userActionObserver = {
+      observe(aSubject, aTopic, aData) {
+        let obj = aSubject?.wrappedJSObject;
+        if (obj) {
+          obj.connState = self.state;
+          self.sendAsyncMessage("torconnect:user-action", obj);
+        }
+      },
+    };
+    Services.obs.addObserver(this.userActionObserver, BroadcastTopic);
   }
 
   willDestroy() {
@@ -126,6 +138,7 @@ class TorConnectParent extends JSWindowActorParent {
       this.torConnectObserver,
       TorSettingsTopics.SettingChanged
     );
+    Services.obs.removeObserver(this.userActionObserver, BroadcastTopic);
   }
 
   async receiveMessage(message) {
@@ -137,9 +150,6 @@ class TorConnectParent extends JSWindowActorParent {
       case "torconnect:open-tor-preferences":
         TorConnect.openTorPreferences();
         break;
-      case "torconnect:view-tor-logs":
-        TorConnect.viewTorLogs();
-        break;
       case "torconnect:cancel-bootstrap":
         TorConnect.cancelBootstrap();
         break;
@@ -149,21 +159,32 @@ class TorConnectParent extends JSWindowActorParent {
       case "torconnect:begin-autobootstrap":
         TorConnect.beginAutoBootstrap(message.data);
         break;
+      case "torconnect:view-tor-logs":
+        TorConnect.viewTorLogs();
+        break;
       case "torconnect:restart":
         Services.startup.quit(
           Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit
         );
         break;
+      case "torconnect:set-ui-state":
+        TorConnect.uiState = message.data;
+        this.state.UIState = TorConnect.uiState;
+        break;
+      case "torconnect:broadcast-user-action":
+        Services.obs.notifyObservers(message.data, BroadcastTopic);
+        break;
       case "torconnect:get-init-args":
         // called on AboutTorConnect.init(), pass down all state data it needs to init
 
         // pretend this is a state transition on init
         // so we always get fresh UI
         this.state.StateChanged = true;
+        this.state.UIState = TorConnect.uiState;
         return {
           TorStrings,
           TorConnectState,
-          TorCensorshipLevel,
+          InternetStatus,
           Direction: Services.locale.isAppLocaleRTL ? "rtl" : "ltr",
           State: this.state,
           CountryNames: TorConnect.countryNames,
diff --git a/browser/components/torconnect/content/aboutTorConnect.css b/browser/components/torconnect/content/aboutTorConnect.css
index 0a3dc9fbd75fd..ae6a76db9fe75 100644
--- a/browser/components/torconnect/content/aboutTorConnect.css
+++ b/browser/components/torconnect/content/aboutTorConnect.css
@@ -10,22 +10,22 @@
   --onion-radius: 75px;
 }
 
-/* override firefox's default blue focus coloring */
-:focus {
+input[type="checkbox"]:focus, select:focus {
   outline:  none!important;
   box-shadow: 0 0 0 3px var(--purple-30) !important;
   border:  1px var(--purple-80) solid !important;
 }
 
-@media (prefers-color-scheme: dark)
+@media (-moz-toolbar-prefers-color-scheme: dark)
 {
-  :focus {
+  input[type="checkbox"]:focus, select:focus {
     box-shadow: 0 0 0 3px var(--purple-50)!important;
   }
 }
 
 #breadcrumbs {
   display: flex;
+  align-items: center;
   margin: 0 0 24px 0;
   color: var(--grey-40);
 }
@@ -38,15 +38,24 @@
   display: flex;
   margin: 0;
   margin-inline-start: 20px;
+  padding: 8px;
 }
 
 .breadcrumb-item {
+  align-items: center;
   cursor: pointer;
   color: var(--in-content-text-color);
+  border-radius: 4px;
 }
 
 .breadcrumb-item:hover {
-  color: var(--blue-60);
+  color: var(--in-content-accent-color);
+  background-color: var(--in-content-button-background-hover);
+}
+
+.breadcrumb-item:active {
+  color: var(--in-content-accent-color-active);
+  background-color: var(--in-content-button-background-active);
 }
 
 .breadcrumb-separator {
@@ -60,30 +69,44 @@
 
 .breadcrumb-icon {
   display: inline list-item;
+  height: 16px;
   list-style-position: inside;
   fill: currentColor;
   -moz-context-properties: fill;
 }
 
-.breadcrumb-label {
-  margin-top: -1px;
+.breadcrumb-item.active {
+  color: var(--in-content-accent-color);
 }
 
-#breadcrumbs .active {
-  color: var(--blue-60);
+.breadcrumb-item.disabled, .breadcrumb-item.disabled:hover, .breadcrumb-item.disabled:active {
+  color: var(--in-content-text-color);
+  opacity: 0.4;
+  cursor: default;
 }
 
-#breadcrumbs .disabled, #breadcrumbs .disabled:hover {
-  color: var(--green-90-a40);
-  cursor: default;
+.breadcrumb-item.error {
+  color: var(--in-content-danger-button-background);
+}
+
+.breadcrumb-item.error:hover {
+  color: var(--in-content-danger-button-background-hover);
+}
+
+.breadcrumb-item.error:active {
+  color: var(--in-content-danger-button-background-active);
 }
 
-#breadcrumbs .error {
-  color: var(--red-60);
+.breadcrumb-item.hidden, .breadcrumb-separator.hidden {
+  display: none;
 }
 
-#connection-assist {
-  margin-left: 0;
+#connect-to-tor {
+  margin-inline-start: 0;
+}
+
+#connect-to-tor-icon {
+  list-style-image: url("chrome://browser/content/torconnect/onion.svg");
 }
 
 #connection-assist-icon {
@@ -102,30 +125,28 @@
   list-style-image: url("chrome://browser/content/torconnect/bridge.svg");
 }
 
-button.primary {
-  background-color: var(--purple-60)!important;
-  color: white!important;
-  fill: white!important;
-}
-
-button.primary:hover {
-  background-color: var(--purple-70)!important;
-  color: white!important;
-  fill: white!important;
-}
-
-button.primary:active {
-  background-color: var(--purple-80)!important;
-  color: white!important;
-  fill: white!important;
+button {
+  --purple-button-text-color: rgb(251,251,254);
+  --in-content-primary-button-text-color: var(--purple-button-text-color);
+  --in-content-primary-button-background: var(--purple-60);
+  --in-content-primary-button-text-color-hover: var(--purple-button-text-color);
+  --in-content-primary-button-background-hover: var(--purple-70);
+  --in-content-primary-button-text-color-active: var(--purple-button-text-color);
+  --in-content-primary-button-background-active: var(--purple-80);
+  --in-content-focus-outline-color: var(--purple-60);
+  fill: white;
 }
 
-div#locationDropdownLabel {
+#locationDropdownLabel {
   margin-block: auto;
   margin-inline: 4px;
 }
 
-/* these two follow similar css in error-pages.css for buttons */
+#locationDropdownLabel.error {
+  color: var(--in-content-danger-button-background)
+}
+
+/* this follows similar css in error-pages.css for buttons */
 @media only screen and (min-width: 480px) {
   form#locationDropdown {
     margin-inline: 4px;
@@ -136,14 +157,13 @@ div#locationDropdownLabel {
 }
 
 @media only screen and (max-width: 480px) {
-  form#locationDropdown,
-  div#locationDropdownLabel {
-    margin: 0.66em 0 0;
+  #tryAgainButton {
+    margin-top: 4px;
   }
+}
 
-  form#locationDropdown {
-    width: 100%;
-  }
+form#locationDropdown {
+  width: 240px;
 }
 
 form#locationDropdown select {
@@ -182,21 +202,79 @@ input[type="checkbox"]:not(:disabled):active:checked {
   background-color: var(--purple-80)!important;
 }
 
-#progressBackground {
-  position:fixed;
-  padding:0;
-  margin:0;
-  top:0;
-  left:0;
+:root {
+  --progressbar-shadow-start: rgba(255, 255, 255, 0.7);
+  --progressbar-gradient: linear-gradient(90deg, #FC00FF 0%, #00DBDE 50%, #FC00FF 100%);
+}
+
+@media (-moz-toolbar-prefers-color-scheme: dark) {
+  :root {
+    --progressbar-shadow-start: rgba(28, 27, 34, 0.7);
+  }
+}
+
+#progressBar {
+  position: fixed;
+  top: 0;
+  inset-inline-start: 0;
   width: 0%;
+  padding: 0;
+  margin: 0;
+  animation: progressAnimation 5s ease infinite;
+}
+
+#progressBackground {
+  height: 66px;
+  margin-top: -26px;
+  background-image:
+    linear-gradient(var(--progressbar-shadow-start), var(--in-content-page-background) 100%),
+    var(--progressbar-gradient);
+  background-position: inherit;
+  filter: blur(5px);
+  border-end-end-radius: 33px;
+}
+
+#progressSolid {
+  position: absolute;
+  top: 0;
+  width: 100%;
   height: 7px;
-  background-image: linear-gradient(90deg, rgb(20, 218, 221) 0%, rgb(128, 109, 236) 100%);
-  border-radius: 0;
+  background-image: var(--progressbar-gradient);
+  background-position: inherit;
+}
+
+#progressBackground, #progressSolid {
+  background-size: 200% 100%;
+}
+
+@keyframes progressAnimation {
+  0% {
+    background-position: 200%;
+  }
+  50% {
+    background-position: 100%;
+  }
+  100% {
+    background-position: 0%;
+  }
+}
+
+@keyframes progressAnimation {
+  0% {
+    background-position: 200%;
+  }
+  50% {
+    background-position: 100%;
+  }
+  100% {
+    background-position: 0%;
+  }
 }
 
 #connectPageContainer {
   margin-top: 10vh;
-  width: 50%;
+  width: 100%;
+  max-width: 45em;
 }
 
 #quickstartCheckbox, #quickstartCheckboxLabel {
@@ -238,7 +316,7 @@ body {
   fill: var(--onion-color);
 }
 
-.title.error {
+.title.offline, .title.assist, .title.final {
   background-image: url("chrome://browser/content/torconnect/connection-failure.svg");
 }
 
diff --git a/browser/components/torconnect/content/aboutTorConnect.js b/browser/components/torconnect/content/aboutTorConnect.js
index 8710eef95a49e..8b5f7216e9ef8 100644
--- a/browser/components/torconnect/content/aboutTorConnect.js
+++ b/browser/components/torconnect/content/aboutTorConnect.js
@@ -5,13 +5,23 @@
 // populated in AboutTorConnect.init()
 let TorStrings = {};
 let TorConnectState = {};
-let TorCensorshipLevel = {};
+let InternetStatus = {};
+
+const UIStates = Object.freeze({
+  ConnectToTor: "ConnectToTor",
+  Offline: "Offline",
+  ConnectionAssist: "ConnectionAssist",
+  CouldNotLocate: "CouldNotLocate",
+  LocationConfirm: "LocationConfirm",
+  FinalError: "FinalError",
+});
 
 const BreadcrumbStatus = Object.freeze({
-  Disabled: -1,
-  Default: 0,
-  Active: 1,
-  Error: 2,
+  Hidden: "hidden",
+  Disabled: "disabled",
+  Default: "default",
+  Active: "active",
+  Error: "error",
 });
 
 class AboutTorConnect {
@@ -23,24 +33,27 @@ class AboutTorConnect {
     },
     progress: {
       description: "p#connectShortDescText",
-      meter: "div#progressBackground",
+      meter: "div#progressBar",
     },
     breadcrumbs: {
       container: "#breadcrumbs",
+      connectToTor: {
+        link: "#connect-to-tor",
+        label: "#connect-to-tor .breadcrumb-label",
+      },
       connectionAssist: {
+        separator: "#connection-assist-separator",
         link: "#connection-assist",
         label: "#connection-assist .breadcrumb-label",
       },
-      locationSettings: {
-        link: "#location-settings",
-        label: "#location-settings .breadcrumb-label",
-      },
       tryBridge: {
+        separator: "#try-bridge-separator",
         link: "#try-bridge",
         label: "#try-bridge .breadcrumb-label",
       },
     },
     viewLog: {
+      container: "#viewLogContainer",
       link: "span#viewLogLink",
     },
     quickstart: {
@@ -54,10 +67,9 @@ class AboutTorConnect {
       cancel: "button#cancelButton",
       connect: "button#connectButton",
       tryBridge: "button#tryBridgeButton",
-      locationDropdownLabel: "div#locationDropdownLabel",
+      locationDropdownLabel: "#locationDropdownLabel",
       locationDropdown: "form#locationDropdown",
       locationDropdownSelect: "form#locationDropdown select",
-      tryAgain: "button#tryAgainButton",
     },
   });
 
@@ -74,17 +86,23 @@ class AboutTorConnect {
     breadcrumbContainer: document.querySelector(
       this.selectors.breadcrumbs.container
     ),
+    connectToTorLink: document.querySelector(
+      this.selectors.breadcrumbs.connectToTor.link
+    ),
+    connectToTorLabel: document.querySelector(
+      this.selectors.breadcrumbs.connectToTor.label
+    ),
+    connectionAssistSeparator: document.querySelector(
+      this.selectors.breadcrumbs.connectionAssist.separator
+    ),
     connectionAssistLink: document.querySelector(
       this.selectors.breadcrumbs.connectionAssist.link
     ),
     connectionAssistLabel: document.querySelector(
       this.selectors.breadcrumbs.connectionAssist.label
     ),
-    locationSettingsLink: document.querySelector(
-      this.selectors.breadcrumbs.locationSettings.link
-    ),
-    locationSettingsLabel: document.querySelector(
-      this.selectors.breadcrumbs.locationSettings.label
+    tryBridgeSeparator: document.querySelector(
+      this.selectors.breadcrumbs.tryBridge.separator
     ),
     tryBridgeLink: document.querySelector(
       this.selectors.breadcrumbs.tryBridge.link
@@ -92,6 +110,7 @@ class AboutTorConnect {
     tryBridgeLabel: document.querySelector(
       this.selectors.breadcrumbs.tryBridge.label
     ),
+    viewLogContainer: document.querySelector(this.selectors.viewLog.container),
     viewLogLink: document.querySelector(this.selectors.viewLog.link),
     quickstartContainer: document.querySelector(
       this.selectors.quickstart.container
@@ -104,7 +123,6 @@ class AboutTorConnect {
     configureButton: document.querySelector(this.selectors.buttons.configure),
     cancelButton: document.querySelector(this.selectors.buttons.cancel),
     connectButton: document.querySelector(this.selectors.buttons.connect),
-    tryBridgeButton: document.querySelector(this.selectors.buttons.tryBridge),
     locationDropdownLabel: document.querySelector(
       this.selectors.buttons.locationDropdownLabel
     ),
@@ -114,27 +132,44 @@ class AboutTorConnect {
     locationDropdownSelect: document.querySelector(
       this.selectors.buttons.locationDropdownSelect
     ),
-    tryAgainButton: document.querySelector(this.selectors.buttons.tryAgain),
+    tryBridgeButton: document.querySelector(this.selectors.buttons.tryBridge),
   });
 
   // a redirect url can be passed as a query parameter for the page to
   // forward us to once bootstrap completes (otherwise the window will just close)
   redirect = null;
 
+  uiState = {
+    currentState: UIStates.ConnectToTor,
+    connectIsTryAgain: false,
+    allowAutomaticLocation: true,
+    selectedLocation: "automatic",
+    bootstrapCause: UIStates.ConnectToTor,
+  };
+
   locations = {};
 
+  constructor() {
+    this.uiStates = Object.freeze(
+      Object.fromEntries([
+        [UIStates.ConnectToTor, this.showConnectToTor.bind(this)],
+        [UIStates.Offline, this.showOffline.bind(this)],
+        [UIStates.ConnectionAssist, this.showConnectionAssistant.bind(this)],
+        [UIStates.CouldNotLocate, this.showCouldNotLocate.bind(this)],
+        [UIStates.LocationConfirm, this.showLocationConfirmation.bind(this)],
+        [UIStates.FinalError, this.showFinalError.bind(this)],
+      ])
+    );
+  }
+
   beginBootstrap() {
-    this.hide(this.elements.connectButton);
-    this.hide(this.elements.quickstartContainer);
-    this.show(this.elements.cancelButton);
-    this.elements.cancelButton.focus();
     RPMSendAsyncMessage("torconnect:begin-bootstrap");
   }
 
   beginAutoBootstrap(countryCode) {
-    this.hide(this.elements.tryBridgeButton);
-    this.show(this.elements.cancelButton);
-    this.elements.cancelButton.focus();
+    if (countryCode === "automatic") {
+      countryCode = "";
+    }
     RPMSendAsyncMessage("torconnect:begin-autobootstrap", countryCode);
   }
 
@@ -142,16 +177,24 @@ class AboutTorConnect {
     RPMSendAsyncMessage("torconnect:cancel-bootstrap");
   }
 
+  transitionUIState(nextState, connState) {
+    if (nextState !== this.uiState.currentState) {
+      this.uiState.currentState = nextState;
+      this.saveUIState();
+    }
+    this.uiStates[nextState](connState);
+  }
+
+  saveUIState() {
+    RPMSendAsyncMessage("torconnect:set-ui-state", this.uiState);
+  }
+
   /*
   Element helper methods
   */
 
   show(element, primary) {
-    if (primary) {
-      element.classList.add("primary");
-    } else {
-      element.classList.remove("primary");
-    }
+    element.classList.toggle("primary", primary !== undefined && primary);
     element.removeAttribute("hidden");
   }
 
@@ -160,14 +203,14 @@ class AboutTorConnect {
   }
 
   hideButtons() {
+    this.hide(this.elements.quickstartContainer);
     this.hide(this.elements.restartButton);
     this.hide(this.elements.configureButton);
     this.hide(this.elements.cancelButton);
     this.hide(this.elements.connectButton);
-    this.hide(this.elements.tryBridgeButton);
     this.hide(this.elements.locationDropdownLabel);
     this.hide(this.elements.locationDropdown);
-    this.hide(this.elements.tryAgainButton);
+    this.hide(this.elements.tryBridgeButton);
   }
 
   populateLocations() {
@@ -187,31 +230,29 @@ class AboutTorConnect {
     locationNodes.sort((left, right) =>
       left.textContent.localeCompare(right.textContent)
     );
-
     this.elements.locationDropdownSelect.append(
       selectCountryRegion,
       ...locationNodes
     );
   }
 
-  populateSpecialLocations(specialLocations) {
-    this.removeSpecialLocations();
-    if (!specialLocations || !specialLocations.length) {
+  populateFrequentLocations(locations) {
+    this.removeFrequentLocations();
+    if (!locations || !locations.length) {
       return;
     }
 
     const locationNodes = [];
-    for (const code of specialLocations) {
+    for (const code of locations) {
       const option = document.createElement("option");
       option.value = code;
-
+      option.className = "frequent-location";
       // codes (partially) come from rdsys service, so make sure we have a
       // string defined for it
       let name = this.locations[code];
       if (!name) {
         name = code;
       }
-
       option.textContent = name;
       locationNodes.push(option);
     }
@@ -220,28 +261,27 @@ class AboutTorConnect {
       left.textContent.localeCompare(right.textContent)
     );
 
-    const disabledDividerNode = document.createElement("option");
-    disabledDividerNode.setAttribute("disabled", true);
-    disabledDividerNode.className = "divider";
+    const frequentGroup = document.createElement("optgroup");
+    frequentGroup.setAttribute(
+      "label",
+      TorStrings.torConnect.frequentLocations
+    );
+    frequentGroup.className = "frequent-location";
+    const locationGroup = document.createElement("optgroup");
+    locationGroup.setAttribute("label", TorStrings.torConnect.otherLocations);
+    locationGroup.className = "frequent-location";
+    // options[0] is either "Select Country or Region" or "Automatic"
     this.elements.locationDropdownSelect.options[0].after(
+      frequentGroup,
       ...locationNodes,
-      disabledDividerNode
+      locationGroup
     );
   }
 
-  removeSpecialLocations() {
+  removeFrequentLocations() {
     const select = this.elements.locationDropdownSelect;
-    if (select.querySelector(".divider") === null) {
-      return;
-    }
-
-    while (select.options.length > 1) {
-      // Skip the "select country/region" option
-      const opt = select.options[1];
-      opt.remove();
-      if (opt.className === "divider") {
-        break;
-      }
+    for (const option of select.querySelectorAll(".frequent-location")) {
+      option.remove();
     }
   }
 
@@ -251,20 +291,15 @@ class AboutTorConnect {
       selectedIndex
     ];
     if (!selectedOption.value) {
-      this.elements.tryAgainButton.setAttribute("disabled", "disabled");
+      this.elements.tryBridgeButton.setAttribute("disabled", "disabled");
     } else {
-      this.elements.tryAgainButton.removeAttribute("disabled");
+      this.elements.tryBridgeButton.removeAttribute("disabled");
     }
   }
 
   setTitle(title, className) {
     this.elements.titleText.textContent = title;
-    if (className !== "error") {
-      this.elements.title.classList.remove("error");
-    }
-    if (className !== "location") {
-      this.elements.title.classList.remove("location");
-    }
+    this.elements.title.className = "title";
     if (className) {
       this.elements.title.classList.add(className);
     }
@@ -286,28 +321,30 @@ class AboutTorConnect {
     }
   }
 
-  setBreadcrumbsStatus(connectionAssist, locationSettings, tryBridge) {
+  setBreadcrumbsStatus(connectToTor, connectionAssist, tryBridge) {
     this.elements.breadcrumbContainer.classList.remove("hidden");
-    let elems = [
-      [this.elements.connectionAssistLink, connectionAssist],
-      [this.elements.locationSettingsLink, locationSettings],
-      [this.elements.tryBridgeLink, tryBridge],
+    const elems = [
+      [this.elements.connectToTorLink, connectToTor, null],
+      [
+        this.elements.connectionAssistLink,
+        connectionAssist,
+        this.elements.connectionAssistSeparator,
+      ],
+      [
+        this.elements.tryBridgeLink,
+        tryBridge,
+        this.elements.tryBridgeSeparator,
+      ],
     ];
-    elems.forEach(([elem, status]) => {
-      elem.classList.remove("disabled");
-      elem.classList.remove("active");
-      elem.classList.remove("error");
-      switch (status) {
-        case BreadcrumbStatus.Disabled:
-          elem.classList.add("disabled");
-          break;
-        case BreadcrumbStatus.Active:
-          elem.classList.add("active");
-          break;
-        case BreadcrumbStatus.Error:
-          elem.classList.add("error");
-          break;
+    elems.forEach(([elem, status, separator]) => {
+      elem.classList.remove(BreadcrumbStatus.Hidden);
+      elem.classList.remove(BreadcrumbStatus.Disabled);
+      elem.classList.remove(BreadcrumbStatus.Active);
+      elem.classList.remove(BreadcrumbStatus.Error);
+      if (status !== "") {
+        elem.classList.add(status);
       }
+      separator?.classList.toggle("hidden", status === BreadcrumbStatus.Hidden);
     });
   }
 
@@ -328,124 +365,59 @@ class AboutTorConnect {
   /* Per-state updates */
 
   update_Initial(state) {
-    const hasError = false;
-    const showProgressbar = false;
-
-    this.setTitle(TorStrings.torConnect.torConnect, hasError ? "error" : "");
-    this.setProgress(
-      TorStrings.settings.torPreferencesDescription,
-      showProgressbar
-    );
-    this.hide(this.elements.quickstartContainer);
-    this.hide(this.elements.viewLogLink);
-    this.hideButtons();
+    this.showConnectToTor(state);
   }
 
   update_Configuring(state) {
-    const hasError = state.ErrorMessage != null;
-    const showProgressbar = false;
-
-    this.hide(this.elements.quickstartContainer);
-    this.hide(this.elements.viewLogLink);
-    this.hideButtons();
-
-    if (hasError) {
-      switch (state.DetectedCensorshiplevel) {
-        case TorCensorshipLevel.None:
-          // we shouldn't be able to get here
-          break;
-        case TorCensorshipLevel.Moderate:
-          // bootstrap failed once, offer auto bootstrap
-          this.showConnectionAssistant(state.ErrorDetails);
-          if (state.StateChanged) {
-            this.elements.tryBridgeButton.focus();
-          }
-          break;
-        case TorCensorshipLevel.Severe:
-          // autobootstrap failed, verify correct location
-          this.showLocationSettings(state.CountryCodes, state.ErrorMessage);
-          if (state.StateChanged) {
-            this.elements.tryAgainButton.focus();
-          }
-          break;
-        case TorCensorshipLevel.Extreme:
-          // finally offer to restart tor-browser or go to configure options
-          this.showFinalError(state);
-          break;
-      }
-    } else {
-      this.setTitle(TorStrings.torConnect.torConnect, "");
-      this.setLongText(TorStrings.settings.torPreferencesDescription);
-      this.setProgress("", showProgressbar);
-      this.show(this.elements.quickstartContainer);
-      this.show(this.elements.configureButton);
-      this.show(this.elements.connectButton, true);
-      if (state.StateChanged) {
-        this.elements.connectButton.focus();
-      }
-      this.elements.connectButton.textContent =
-        TorStrings.torConnect.torConnectButton;
+    if (
+      state.StateChanged &&
+      (state.PreviousState === TorConnectState.Bootstrapping ||
+        state.PreviousState === TorConnectState.AutoBootstrapping)
+    ) {
+      // The bootstrap has been cancelled
+      this.transitionUIState(this.uiState.bootstrapCause, state);
     }
   }
 
   update_AutoBootstrapping(state) {
-    const showProgressbar = true;
-
-    if (state.DetectedCensorshiplevel >= TorCensorshipLevel.Severe) {
-      this.setTitle(TorStrings.torConnect.tryingBridgeAgain, "");
-    } else {
-      this.setTitle(TorStrings.torConnect.tryingBridge, "");
-    }
-    this.showConfigureConnectionLink(TorStrings.torConnect.assistDescription);
-    this.setProgress(
-      state.BootstrapStatus,
-      showProgressbar,
-      state.BootstrapProgress
-    );
-    this.setBreadcrumbsStatus(
-      BreadcrumbStatus.Disabled,
-      BreadcrumbStatus.Disabled,
-      BreadcrumbStatus.Active
-    );
-    if (state.ShowViewLog) {
-      this.show(this.elements.viewLogLink);
-    } else {
-      this.hide(this.elements.viewLogLink);
-    }
-    this.hideButtons();
-    this.show(this.elements.cancelButton, true);
-    if (state.StateChanged) {
-      this.elements.cancelButton.focus();
-    }
+    this.showBootstrapping(state);
   }
 
   update_Bootstrapping(state) {
-    const showProgressbar = true;
-
-    this.setTitle(TorStrings.torConnect.torConnecting, "");
-    this.setLongText(TorStrings.settings.torPreferencesDescription);
-    this.setProgress("", showProgressbar, state.BootstrapProgress);
-    this.hideBreadcrumbs();
-    if (state.ShowViewLog) {
-      this.show(this.elements.viewLogLink);
-    } else {
-      this.hide(this.elements.viewLogLink);
-    }
-    this.hideButtons();
-    this.show(this.elements.cancelButton, true);
-    if (state.StateChanged) {
-      this.elements.cancelButton.focus();
-    }
+    this.showBootstrapping(state);
   }
 
   update_Error(state) {
-    const showProgressbar = false;
-
-    this.setTitle(state.ErrorMessage, "error");
-    this.setLongText("");
-    this.setProgress(state.ErrorDetails, showProgressbar);
-    this.hideButtons();
-    this.show(this.elements.viewLogLink);
+    if (!this.uiState.connectIsTryAgain) {
+      // TorConnect.hasBootstrapEverFailed remains false in case of Internet
+      // offline
+      this.uiState.connectIsTryAgain = true;
+      this.saveUIState();
+    }
+    if (!state.StateChanged) {
+      return;
+    }
+    if (state.InternetStatus === InternetStatus.Offline) {
+      this.transitionUIState(UIStates.Offline, state);
+    } else if (state.PreviousState === TorConnectState.Bootstrapping) {
+      this.transitionUIState(UIStates.ConnectionAssist, state);
+    } else if (state.PreviousState === TorConnectState.AutoBootstrapping) {
+      if (this.uiState.bootstrapCause === UIStates.ConnectionAssist) {
+        this.transitionUIState(
+          this.getLocation() === "automatic"
+            ? UIStates.CouldNotLocate
+            : UIStates.LocationConfirm,
+          state
+        );
+      } else {
+        this.transitionUIState(UIStates.FinalError, state);
+      }
+    } else {
+      console.error(
+        "We received an error starting from an unexpected state",
+        state
+      );
+    }
   }
 
   update_Bootstrapped(state) {
@@ -467,23 +439,165 @@ class AboutTorConnect {
     // it isn't in use (eg using tor-launcher or system tor)
   }
 
-  showConnectionAssistant(error) {
-    const hasError = !!error;
-    this.setTitle(
-      TorStrings.torConnect.couldNotConnect,
-      hasError ? "error" : ""
+  showConnectToTor(state) {
+    this.setTitle(TorStrings.torConnect.torConnect, "");
+    this.setLongText(TorStrings.settings.torPreferencesDescription);
+    this.setProgress("", false);
+    this.hide(this.elements.viewLogContainer);
+    this.hideButtons();
+    this.show(this.elements.quickstartContainer);
+    this.show(this.elements.configureButton);
+    this.show(this.elements.connectButton, true);
+    if (state?.StateChanged) {
+      this.elements.connectButton.focus();
+    }
+    if (this.uiState.connectIsTryAgain) {
+      this.setBreadcrumbsStatus(
+        BreadcrumbStatus.Active,
+        BreadcrumbStatus.Default,
+        BreadcrumbStatus.Disabled
+      );
+      this.elements.connectButton.textContent = TorStrings.torConnect.tryAgain;
+    }
+    this.uiState.bootstrapCause = UIStates.ConnectToTor;
+    this.saveUIState();
+  }
+
+  showBootstrapping(state) {
+    const showProgressbar = true;
+    let title = "";
+    let description = "";
+    const breadcrumbs = [
+      BreadcrumbStatus.Disabled,
+      BreadcrumbStatus.Disabled,
+      BreadcrumbStatus.Disabled,
+    ];
+    switch (this.uiState.bootstrapCause) {
+      case UIStates.ConnectToTor:
+        breadcrumbs[0] = BreadcrumbStatus.Active;
+        title = this.uiState.connectIsTryAgain
+          ? TorStrings.torConnect.tryAgain
+          : TorStrings.torConnect.torConnecting;
+        description = TorStrings.settings.torPreferencesDescription;
+        break;
+      case UIStates.ConnectionAssist:
+        breadcrumbs[2] = BreadcrumbStatus.Active;
+        title = TorStrings.torConnect.tryingBridge;
+        description = TorStrings.torConnect.assistDescription;
+        break;
+      case UIStates.CouldNotLocate:
+        breadcrumbs[2] = BreadcrumbStatus.Active;
+        title = TorStrings.torConnect.tryingBridgeAgain;
+        description = TorStrings.torConnect.errorLocationDescription;
+        break;
+      case UIStates.LocationConfirm:
+        breadcrumbs[2] = BreadcrumbStatus.Active;
+        title = TorStrings.torConnect.tryingBridgeAgain;
+        description = TorStrings.torConnect.isLocationCorrectDescription;
+        break;
+    }
+    this.setTitle(title, "");
+    this.showConfigureConnectionLink(description);
+    this.setProgress("", showProgressbar, state.BootstrapProgress);
+    this.setBreadcrumbsStatus(...breadcrumbs);
+    this.hideButtons();
+    if (state.ShowViewLog) {
+      this.show(this.elements.viewLogContainer);
+    } else {
+      this.hide(this.elements.viewLogContainer);
+    }
+    this.show(this.elements.cancelButton, true);
+    if (state.StateChanged) {
+      this.elements.cancelButton.focus();
+    }
+  }
+
+  showOffline(error) {
+    this.setTitle(TorStrings.torConnect.noInternet, "offline");
+    this.setLongText(TorStrings.torConnect.noInternetDescription);
+    this.setProgress(error, false);
+    this.setBreadcrumbsStatus(
+      BreadcrumbStatus.Default,
+      BreadcrumbStatus.Active,
+      BreadcrumbStatus.Hidden
     );
+    this.show(this.elements.viewLogContainer);
+    this.hideButtons();
+    this.show(this.elements.configureButton);
+    this.show(this.elements.connectButton, true);
+    this.elements.connectButton.textContent = TorStrings.torConnect.tryAgain;
+  }
+
+  showConnectionAssistant(state) {
+    this.setTitle(TorStrings.torConnect.couldNotConnect, "assist");
     this.showConfigureConnectionLink(TorStrings.torConnect.assistDescription);
-    this.setProgress(error, false);
+    this.setProgress(state?.ErrorDetails, false);
     this.setBreadcrumbsStatus(
+      BreadcrumbStatus.Default,
       BreadcrumbStatus.Active,
+      BreadcrumbStatus.Disabled
+    );
+    this.showLocationForm(false, TorStrings.torConnect.tryBridge);
+    if (state?.StateChanged) {
+      this.elements.tryBridgeButton.focus();
+    }
+    this.uiState.bootstrapCause = UIStates.ConnectionAssist;
+    this.saveUIState();
+  }
+
+  showCouldNotLocate(state) {
+    this.uiState.allowAutomaticLocation = false;
+    this.setTitle(TorStrings.torConnect.errorLocation, "location");
+    this.showConfigureConnectionLink(
+      TorStrings.torConnect.errorLocationDescription
+    );
+    this.setProgress(state.ErrorMessage, false);
+    this.setBreadcrumbsStatus(
       BreadcrumbStatus.Default,
+      BreadcrumbStatus.Active,
       BreadcrumbStatus.Disabled
     );
+    this.show(this.elements.viewLogContainer);
+    this.showLocationForm(true, TorStrings.torConnect.tryBridge);
+    if (state.StateChanged) {
+      this.elements.tryBridgeButton.focus();
+    }
+    this.uiState.bootstrapCause = UIStates.CouldNotLocate;
+    this.saveUIState();
+  }
+
+  showLocationConfirmation(state) {
+    this.setTitle(TorStrings.torConnect.isLocationCorrect, "location");
+    this.showConfigureConnectionLink(
+      TorStrings.torConnect.isLocationCorrectDescription
+    );
+    this.setProgress(state.ErrorMessage, false);
+    this.setBreadcrumbsStatus(
+      BreadcrumbStatus.Default,
+      BreadcrumbStatus.Default,
+      BreadcrumbStatus.Active
+    );
+    this.show(this.elements.viewLogContainer);
+    this.showLocationForm(true, TorStrings.torConnect.tryAgain);
+    if (state.StateChanged) {
+      this.elements.tryBridgeButton.focus();
+    }
+    this.uiState.bootstrapCause = UIStates.LocationConfirm;
+    this.saveUIState();
+  }
+
+  showFinalError(state) {
+    this.setTitle(TorStrings.torConnect.finalError, "final");
+    this.setLongText(TorStrings.torConnect.finalErrorDescription);
+    this.setProgress(state ? state.ErrorDetails : "", false);
+    this.setBreadcrumbsStatus(
+      BreadcrumbStatus.Default,
+      BreadcrumbStatus.Default,
+      BreadcrumbStatus.Active
+    );
     this.hideButtons();
-    this.show(this.elements.configureButton);
-    this.show(this.elements.connectButton);
-    this.show(this.elements.tryBridgeButton, true);
+    this.show(this.elements.restartButton);
+    this.show(this.elements.configureButton, true);
   }
 
   showConfigureConnectionLink(text) {
@@ -495,80 +609,100 @@ class AboutTorConnect {
       e.preventDefault();
       RPMSendAsyncMessage("torconnect:open-tor-preferences");
     });
-    this.setLongText(pieces[0], link, pieces[1]);
-  }
-
-  showLocationSettings(locations, error) {
-    const hasError = !!error;
-    if (hasError) {
-      this.setTitle(TorStrings.torConnect.errorLocation, "location");
-      this.setLongText(TorStrings.torConnect.errorLocationDescription);
-      this.setBreadcrumbsStatus(
-        BreadcrumbStatus.Disabled,
-        BreadcrumbStatus.Error,
-        BreadcrumbStatus.Disabled
-      );
-      this.elements.tryAgainButton.textContent = TorStrings.torConnect.tryAgain;
+    if (pieces.length > 1) {
+      const first = pieces.shift();
+      this.setLongText(first, link, ...pieces);
     } else {
-      this.setTitle(TorStrings.torConnect.addLocation, "location");
-      this.showConfigureConnectionLink(
-        TorStrings.torConnect.addLocationDescription
-      );
-      this.setBreadcrumbsStatus(
-        BreadcrumbStatus.Default,
-        BreadcrumbStatus.Active,
-        BreadcrumbStatus.Disabled
-      );
-      this.elements.tryAgainButton.textContent =
-        TorStrings.torConnect.tryBridge;
+      this.setLongText(text);
     }
-    this.setProgress(error, false);
+  }
+
+  showLocationForm(isError, buttonLabel) {
     this.hideButtons();
-    if (!locations || !locations.length) {
-      RPMSendQuery("torconnect:get-country-codes").then(codes => {
-        if (codes && codes.length) {
-          this.populateSpecialLocations(codes);
-        }
-      });
+    RPMSendQuery("torconnect:get-country-codes").then(codes => {
+      if (codes && codes.length) {
+        this.populateFrequentLocations(codes);
+        this.setLocationFromState();
+      }
+    });
+    let firstOpt = this.elements.locationDropdownSelect.options[0];
+    if (this.uiState.allowAutomaticLocation) {
+      firstOpt.value = "automatic";
+      firstOpt.textContent = TorStrings.torConnect.automatic;
     } else {
-      this.populateSpecialLocations(locations);
+      firstOpt.value = "";
+      firstOpt.textContent = TorStrings.torConnect.selectCountryRegion;
     }
+    this.setLocationFromState();
     this.validateLocation();
     this.show(this.elements.locationDropdownLabel);
     this.show(this.elements.locationDropdown);
-    this.show(this.elements.tryAgainButton, true);
+    this.elements.locationDropdownLabel.classList.toggle("error", isError);
+    this.show(this.elements.tryBridgeButton, true);
+    this.elements.tryBridgeButton.classList.toggle("danger-button", isError);
+    if (buttonLabel !== undefined) {
+      this.elements.tryBridgeButton.textContent = buttonLabel;
+    }
   }
 
-  showFinalError(state) {
-    this.setTitle(TorStrings.torConnect.finalError, "error");
-    this.setLongText(TorStrings.torConnect.finalErrorDescription);
-    this.setProgress(state ? state.ErrorDetails : "", false);
-    this.hideButtons();
-    this.show(this.elements.restartButton);
-    this.show(this.elements.configureButton);
-    this.show(this.elements.connectButton, true);
+  getLocation() {
+    const selectedIndex = this.elements.locationDropdownSelect.selectedIndex;
+    return this.elements.locationDropdownSelect.options[selectedIndex].value;
+  }
+
+  setLocationFromState() {
+    if (this.getLocation() === this.uiState.selectedLocation) {
+      return;
+    }
+    const options = this.elements.locationDropdownSelect.options;
+    // We need to do this way, because we have repeated values that break
+    // the .value way to select (which would however require the label,
+    // rather than the code)...
+    for (let i = 0; i < options.length; i++) {
+      if (options[i].value === this.uiState.selectedLocation) {
+        this.elements.locationDropdownSelect.selectedIndex = i;
+        break;
+      }
+    }
+    this.validateLocation();
   }
 
   initElements(direction) {
     document.documentElement.setAttribute("dir", direction);
 
+    this.elements.connectToTorLink.addEventListener("click", event => {
+      if (this.uiState.currentState === UIStates.ConnectToTor) {
+        return;
+      }
+      this.transitionUIState(UIStates.ConnectToTor, null);
+      RPMSendAsyncMessage("torconnect:broadcast-user-action", {
+        uiState: UIStates.ConnectToTor,
+      });
+    });
+    this.elements.connectToTorLabel.textContent =
+      TorStrings.torConnect.torConnect;
     this.elements.connectionAssistLink.addEventListener("click", event => {
-      if (!this.elements.connectionAssistLink.classList.contains("disabled")) {
-        this.showConnectionAssistant();
+      if (
+        this.elements.connectionAssistLink.classList.contains(
+          BreadcrumbStatus.Active
+        ) ||
+        this.elements.connectionAssistLink.classList.contains(
+          BreadcrumbStatus.Disabled
+        )
+      ) {
+        return;
       }
+      this.transitionUIState(UIStates.ConnectionAssist, null);
+      RPMSendAsyncMessage("torconnect:broadcast-user-action", {
+        uiState: UIStates.ConnectionAssist,
+      });
     });
     this.elements.connectionAssistLabel.textContent =
       TorStrings.torConnect.breadcrumbAssist;
-    this.elements.locationSettingsLink.addEventListener("click", event => {
-      if (!this.elements.connectionAssistLink.classList.contains("disabled")) {
-        this.showLocationSettings();
-      }
-    });
-    this.elements.locationSettingsLabel.textContent =
-      TorStrings.torConnect.breadcrumbLocation;
     this.elements.tryBridgeLabel.textContent =
       TorStrings.torConnect.breadcrumbTryBridge;
 
+    this.hide(this.elements.viewLogContainer);
     this.elements.viewLogLink.textContent = TorStrings.torConnect.viewLog;
     this.elements.viewLogLink.addEventListener("click", event => {
       RPMSendAsyncMessage("torconnect:view-tor-logs");
@@ -606,26 +740,25 @@ class AboutTorConnect {
 
     this.populateLocations();
     this.elements.locationDropdownSelect.addEventListener("change", () => {
+      this.uiState.selectedLocation = this.getLocation();
+      this.saveUIState();
       this.validateLocation();
-    });
-
-    this.elements.tryBridgeButton.textContent = TorStrings.torConnect.tryBridge;
-    this.elements.tryBridgeButton.addEventListener("click", () => {
-      this.beginAutoBootstrap();
+      RPMSendAsyncMessage("torconnect:broadcast-user-action", {
+        location: this.uiState.selectedLocation,
+      });
     });
 
     this.elements.locationDropdownLabel.textContent =
       TorStrings.torConnect.yourLocation;
 
-    this.elements.tryAgainButton.textContent = TorStrings.torConnect.tryAgain;
-    this.elements.tryAgainButton.setAttribute("disabled", "disabled");
-    this.elements.tryAgainButton.addEventListener("click", () => {
-      let selectedIndex = this.elements.locationDropdownSelect.selectedIndex;
-      let selectedOption = this.elements.locationDropdownSelect.options[
-        selectedIndex
-      ];
-
-      this.beginAutoBootstrap(selectedOption.value);
+    this.elements.tryBridgeButton.textContent = TorStrings.torConnect.tryBridge;
+    this.elements.tryBridgeButton.addEventListener("click", () => {
+      const value = this.getLocation();
+      if (value === "automatic") {
+        this.beginAutoBootstrap();
+      } else {
+        this.beginAutoBootstrap(value);
+      }
     });
   }
 
@@ -634,6 +767,15 @@ class AboutTorConnect {
     RPMAddMessageListener("torconnect:state-change", ({ data }) => {
       this.updateUI(data);
     });
+    RPMAddMessageListener("torconnect:user-action", ({ data }) => {
+      if (data.location) {
+        this.uiState.selectedLocation = data.location;
+        this.setLocationFromState();
+      }
+      if (data.uiState !== undefined) {
+        this.transitionUIState(data.uiState, data.connState);
+      }
+    });
   }
 
   initKeyboardShortcuts() {
@@ -664,13 +806,20 @@ class AboutTorConnect {
     // various constants
     TorStrings = Object.freeze(args.TorStrings);
     TorConnectState = Object.freeze(args.TorConnectState);
-    TorCensorshipLevel = Object.freeze(args.TorCensorshipLevel);
+    InternetStatus = Object.freeze(args.InternetStatus);
     this.locations = args.CountryNames;
 
     this.initElements(args.Direction);
     this.initObservers();
     this.initKeyboardShortcuts();
 
+    if (Object.keys(args.State.UIState).length) {
+      this.uiState = args.State.UIState;
+    } else {
+      args.State.UIState = this.uiState;
+      this.saveUIState();
+    }
+    this.uiStates[this.uiState.currentState](args.State);
     // populate UI based on current state
     this.updateUI(args.State);
   }
diff --git a/browser/components/torconnect/content/aboutTorConnect.xhtml b/browser/components/torconnect/content/aboutTorConnect.xhtml
index a98af43e2d53f..77d2e68895708 100644
--- a/browser/components/torconnect/content/aboutTorConnect.xhtml
+++ b/browser/components/torconnect/content/aboutTorConnect.xhtml
@@ -7,19 +7,22 @@
     <link rel="stylesheet" href="chrome://browser/content/torconnect/aboutTorConnect.css" type="text/css" media="all" />
   </head>
   <body>
-    <div id="progressBackground"></div>
+    <div id="progressBar">
+      <div id="progressBackground" />
+      <div id="progressSolid" />
+    </div>
     <div id="connectPageContainer" class="container">
       <div id="breadcrumbs" class="hidden">
-        <span id="connection-assist" class="breadcrumb-item">
-          <span id="connection-assist-icon" class="breadcrumb-icon" />
+        <span id="connect-to-tor" class="breadcrumb-item">
+          <span id="connect-to-tor-icon" class="breadcrumb-icon" />
           <span class="breadcrumb-label"/>
         </span>
-        <span class="breadcrumb-separator breadcrumb-icon" />
-        <span id="location-settings" class="breadcrumb-item">
-          <span id="location-settings-icon" class="breadcrumb-icon" />
+        <span id="connection-assist-separator" class="breadcrumb-separator breadcrumb-icon" />
+        <span id="connection-assist" class="breadcrumb-item">
+          <span id="connection-assist-icon" class="breadcrumb-icon" />
           <span class="breadcrumb-label"/>
         </span>
-        <span class="breadcrumb-separator breadcrumb-icon" />
+        <span id="try-bridge-separator" class="breadcrumb-separator breadcrumb-icon" />
         <span id="try-bridge" class="breadcrumb-item">
           <span id="try-bridge-icon" class="breadcrumb-icon" />
           <span class="breadcrumb-label"/>
@@ -37,7 +40,7 @@
         </div>
 
         <div id="viewLogContainer">
-          <span id="viewLogLink" hidden="true"></span>
+          <span id="viewLogLink"></span>
         </div>
 
         <div id="quickstartContainer">
@@ -50,13 +53,12 @@
           <button id="configureButton" hidden="true"></button>
           <button id="cancelButton" hidden="true"></button>
           <button id="connectButton" class="primary" hidden="true"></button>
-          <button id="tryBridgeButton" class="primary" hidden="true"></button>
-          <div id="locationDropdownLabel"/>
+          <label id="locationDropdownLabel" for="countries"/>
           <form id="locationDropdown" hidden="true">
             <select id="countries">
             </select>
           </form>
-          <button id="tryAgainButton" class="primary" hidden="true"></button>
+          <button id="tryBridgeButton" class="primary" hidden="true"></button>
         </div>
       </div>
     </div>
diff --git a/browser/components/torconnect/content/globe.svg b/browser/components/torconnect/content/globe.svg
deleted file mode 100644
index f4d1f19b43ce8..0000000000000
--- a/browser/components/torconnect/content/globe.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg";>
-    <path d="M8 0.5C6.01088 0.5 4.10322 1.29018 2.6967 2.6967C1.29018 4.10322 0.5 6.01088 0.5 8C0.5 9.98912 1.29018 11.8968 2.6967 13.3033C4.10322 14.7098 6.01088 15.5 8 15.5C9.98912 15.5 11.8968 14.7098 13.3033 13.3033C14.7098 11.8968 15.5 9.98912 15.5 8C15.5 6.01088 14.7098 4.10322 13.3033 2.6967C11.8968 1.29018 9.98912 0.5 8 0.5ZM10.447 2.25C11.4738 2.69088 12.3637 3.39877 13.0242 4.30006C13.6848 5.20135 14.0918 6.26313 14.203 7.375H11.974C11.8509 5.51288 11.1778 3.72922 10.04 2.25H10 [...]
-</svg>
diff --git a/browser/components/torconnect/jar.mn b/browser/components/torconnect/jar.mn
index 8ca0b0651523e..043bd1435cda5 100644
--- a/browser/components/torconnect/jar.mn
+++ b/browser/components/torconnect/jar.mn
@@ -5,7 +5,6 @@ browser.jar:
     content/browser/torconnect/aboutTorConnect.js                  (content/aboutTorConnect.js)
     content/browser/torconnect/arrow-right.svg                     (content/arrow-right.svg)
     content/browser/torconnect/bridge.svg                          (content/bridge.svg)
-    content/browser/torconnect/globe.svg                           (content/globe.svg)
     content/browser/torconnect/connection-failure.svg              (content/connection-failure.svg)
     content/browser/torconnect/connection-location.svg             (content/connection-location.svg)
     content/browser/torconnect/onion.svg                           (content/onion.svg)
diff --git a/toolkit/modules/RemotePageAccessManager.jsm b/toolkit/modules/RemotePageAccessManager.jsm
index 225429d95b667..73c48281276b9 100644
--- a/toolkit/modules/RemotePageAccessManager.jsm
+++ b/toolkit/modules/RemotePageAccessManager.jsm
@@ -219,7 +219,10 @@ let RemotePageAccessManager = {
       RPMSendQuery: ["FetchUpdateData"],
     },
     "about:torconnect": {
-      RPMAddMessageListener: ["torconnect:state-change"],
+      RPMAddMessageListener: [
+        "torconnect:state-change",
+        "torconnect:user-action",
+      ],
       RPMSendAsyncMessage: [
         "torconnect:open-tor-preferences",
         "torconnect:begin-bootstrap",
@@ -228,6 +231,8 @@ let RemotePageAccessManager = {
         "torconnect:set-quickstart",
         "torconnect:view-tor-logs",
         "torconnect:restart",
+        "torconnect:set-ui-state",
+        "torconnect:broadcast-user-action",
       ],
       RPMSendQuery: [
         "torconnect:get-init-args",

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits