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

[tor-commits] [tor-browser/tor-browser-78.12.0esr-11.0-1] fixup! Bug 27476: Implement about:torconnect captive portal within Tor Browser



commit 73d57c01c2bfe398c02586ae76dab677d843df22
Author: Richard Pospesel <richard@xxxxxxxxxxxxxx>
Date:   Fri Jul 16 17:32:01 2021 +0200

    fixup! Bug 27476: Implement about:torconnect captive portal within Tor Browser
---
 browser/actors/NetErrorParent.jsm                  |   6 +-
 browser/base/content/browser.js                    |   5 +-
 browser/components/BrowserGlue.jsm                 |  32 +-
 browser/components/torconnect/TorConnectParent.jsm | 200 +++++----
 .../torconnect/content/aboutTorConnect.js          | 466 +++++++++-----------
 .../torconnect/content/aboutTorConnect.xhtml       |   9 -
 .../torconnect/content/torBootstrapUrlbar.js       | 195 ++++-----
 .../components/torpreferences/content/torPane.js   |   8 +-
 browser/components/urlbar/UrlbarInput.jsm          |   6 +-
 browser/modules/TorConnect.jsm                     | 477 +++++++++++++++++++--
 browser/modules/TorProtocolService.jsm             | 101 +++--
 .../processsingleton/MainProcessSingleton.jsm      |   5 +
 toolkit/modules/RemotePageAccessManager.jsm        |  28 +-
 13 files changed, 941 insertions(+), 597 deletions(-)

diff --git a/browser/actors/NetErrorParent.jsm b/browser/actors/NetErrorParent.jsm
index fa3cbf23fcb7..6dce9af5aad0 100644
--- a/browser/actors/NetErrorParent.jsm
+++ b/browser/actors/NetErrorParent.jsm
@@ -17,8 +17,8 @@ const { SessionStore } = ChromeUtils.import(
 );
 const { HomePage } = ChromeUtils.import("resource:///modules/HomePage.jsm");
 
-const { TorProtocolService } = ChromeUtils.import(
-  "resource:///modules/TorProtocolService.jsm"
+const { TorConnect } = ChromeUtils.import(
+  "resource:///modules/TorConnect.jsm"
 );
 
 const PREF_SSL_IMPACT_ROOTS = [
@@ -324,7 +324,7 @@ class NetErrorParent extends JSWindowActorParent {
         }
         break;
       case "ShouldShowTorConnect":
-        return TorProtocolService.shouldShowTorConnect();
+        return TorConnect.shouldShowTorConnect;
     }
     return undefined;
   }
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index 916cd69320cb..996ef6dcdd7f 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -77,7 +77,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
   TabModalPrompt: "chrome://global/content/tabprompts.jsm",
   TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
   TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm",
-  TorProtocolService: "resource:///modules/TorProtocolService.jsm",
+  TorConnect: "resource:///modules/TorConnect.jsm",
   Translation: "resource:///modules/translation/TranslationParent.jsm",
   OnionAliasStore: "resource:///modules/OnionAliasStore.jsm",
   UITour: "resource:///modules/UITour.jsm",
@@ -2494,7 +2494,8 @@ var gBrowserInit = {
       let uri = window.arguments[0];
       let defaultArgs = BrowserHandler.defaultArgs;
 
-      if (TorProtocolService.shouldShowTorConnect()) {
+      if (TorConnect.shouldShowTorConnect) {
+        TorConnect.setURIsToLoad(uri);
         return "about:torconnect";
       }
 
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm
index 8735783cee2b..cb77f4d82a3e 100644
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -17,31 +17,6 @@ const { AppConstants } = ChromeUtils.import(
   "resource://gre/modules/AppConstants.jsm"
 );
 
-// TorProtocolService and TorConnect modules need to be lazily-loaded
-// here because they will trigger generation of the random password used
-// to talk to the tor daemon in tor-launcher. Generating the random
-// password will initialize the cryptographic service ( nsNSSComponent )
-//
-// If this service is init'd before the profile has been setup, it will
-// use the fallback init path which behaves as if security.nocertdb=true
-//
-// We make these module getters so init happens when they are needed
-// (when init'ing the OnionAliasStore). With theze getters, the password
-// generation is triggered in torbutton after the 'profile-after-change'
-// topic (so after the profile is initialized)
-
-ChromeUtils.defineModuleGetter(
-  this,
-  "TorProtocolService",
-  "resource:///modules/TorProtocolService.jsm"
-);
-
-ChromeUtils.defineModuleGetter(
-  this,
-  "TorConnect",
-  "resource:///modules/TorConnect.jsm"
-);
-
 ChromeUtils.defineModuleGetter(
   this,
   "ActorManagerParent",
@@ -2531,14 +2506,17 @@ BrowserGlue.prototype = {
 
       {
         task: () => {
-          if (TorProtocolService.isBootstrapDone() || !TorProtocolService.ownsTorDaemon) {
+          const { TorConnect, TorConnectTopics } = ChromeUtils.import(
+            "resource:///modules/TorConnect.jsm"
+          );
+          if (!TorConnect.shouldShowTorConnect) {
             // we will take this path when the user is using the legacy tor launcher or
             // when Tor Browser didn't launch its own tor.
             OnionAliasStore.init();
           } else {
             // this path is taken when using about:torconnect, we wait to init
             // after we are bootstrapped and connected to tor
-            const topic = "torconnect:bootstrap-complete";
+            const topic = TorConnectTopics.BootstrapComplete;
             let bootstrapObserver = {
               observe(aSubject, aTopic, aData) {
                 if (aTopic === topic) {
diff --git a/browser/components/torconnect/TorConnectParent.jsm b/browser/components/torconnect/TorConnectParent.jsm
index c34fab76ddbb..3937bf3ebcf8 100644
--- a/browser/components/torconnect/TorConnectParent.jsm
+++ b/browser/components/torconnect/TorConnectParent.jsm
@@ -3,123 +3,139 @@
 var EXPORTED_SYMBOLS = ["TorConnectParent"];
 
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-const { TorProtocolService } = ChromeUtils.import(
-  "resource:///modules/TorProtocolService.jsm"
-);
 const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
-const { TorLauncherUtil } = ChromeUtils.import(
-  "resource://torlauncher/modules/tl-util.jsm"
-);
-
-const { TorConnect } = ChromeUtils.import(
+const { TorConnect, TorConnectTopics, TorConnectState } = ChromeUtils.import(
   "resource:///modules/TorConnect.jsm"
 );
 
-const kTorProcessReadyTopic = "TorProcessIsReady";
-const kTorProcessExitedTopic = "TorProcessExited";
-const kTorProcessDidNotStartTopic = "TorProcessDidNotStart";
-const kTorShowProgressPanelTopic = "TorShowProgressPanel";
-const kTorBootstrapStatusTopic = "TorBootstrapStatus";
-const kTorBootstrapErrorTopic = "TorBootstrapError";
-const kTorLogHasWarnOrErrTopic = "TorLogHasWarnOrErr";
-
-const gActiveTopics = [
-  kTorProcessReadyTopic,
-  kTorProcessExitedTopic,
-  kTorProcessDidNotStartTopic,
-  kTorShowProgressPanelTopic,
-  kTorBootstrapStatusTopic,
-  kTorBootstrapErrorTopic,
-  kTorLogHasWarnOrErrTopic,
-  "torconnect:bootstrap-complete",
-];
-
-const gTorLauncherPrefs = {
+const TorLauncherPrefs = Object.freeze({
   quickstart: "extensions.torlauncher.quickstart",
-}
+});
+
+/*
+This object is basically a marshalling interface between the TorConnect module
+and a particular about:torconnect page
+*/
 
 class TorConnectParent extends JSWindowActorParent {
   constructor(...args) {
     super(...args);
 
     const self = this;
-    this.gObserver = {
-      observe(aSubject, aTopic, aData) {
-        const obj = aSubject?.wrappedJSObject;
-        if (obj) {
-          obj.handled = true;
-        }
-        self.sendAsyncMessage(aTopic, obj);
-      },
-    };
 
-    for (const topic of gActiveTopics) {
-      Services.obs.addObserver(this.gObserver, topic);
-    }
+    this.state = {
+      State: TorConnect.state,
+      ErrorMessage: TorConnect.errorMessage,
+      ErrorDetails: TorConnect.errorDetails,
+      BootstrapProgress: TorConnect.bootstrapProgress,
+      BootstrapStatus: TorConnect.bootstrapStatus,
+      ShowCopyLog: TorConnect.logHasWarningOrError,
+      QuickStartEnabled: Services.prefs.getBoolPref(TorLauncherPrefs.quickstart, false),
+    };
 
-    this.quickstartObserver = {
+    // JSWindowActiveParent derived objects cannot observe directly, so create a member
+    // object to do our observing for us
+    //
+    // This object converts the various lifecycle events from the TorConnect module, and
+    // maintains a state object which we pass down to our about:torconnect page, which uses
+    // the state object to update its UI
+    this.torConnectObserver = {
       observe(aSubject, aTopic, aData) {
-        if (aTopic === "nsPref:changed" &&
-            aData == gTorLauncherPrefs.quickstart) {
-          self.sendAsyncMessage("TorQuickstartPrefChanged", Services.prefs.getBoolPref(gTorLauncherPrefs.quickstart));
+        let obj = aSubject?.wrappedJSObject;
+
+        // update our state struct based on received torconnect topics and forward on
+        // to aboutTorConnect.js
+        switch(aTopic) {
+          case TorConnectTopics.StateChange: {
+            self.state.State = obj.state;
+            // clear any previous error information if we are bootstrapping
+            if (self.state.State === TorConnectState.Bootstrapping) {
+              self.state.ErrorMessage = null;
+              self.state.ErrorDetails = null;
+            }
+            break;
+          }
+          case TorConnectTopics.BootstrapProgress: {
+            self.state.BootstrapProgress = obj.progress;
+            self.state.BootstrapStatus = obj.status;
+            self.state.ShowCopyLog = obj.hasWarnings;
+            break;
+          }
+          case TorConnectTopics.BootstrapComplete: {
+            // tells about:torconnect pages to close themselves
+            // this flag will only be set if an about:torconnect page
+            // reaches the Bootstrapped state, so if a user
+            // navigates to about:torconnect manually after bootstrap, the page
+            // will not auto-close on them
+            self.state.Close = true;
+            break;
+          }
+          case TorConnectTopics.BootstrapError: {
+            self.state.ErrorMessage = obj.message;
+            self.state.ErrorDetails = obj.details;
+            self.state.ShowCopyLog = true;
+            break;
+          }
+          case TorConnectTopics.FatalError: {
+            // TODO: handle
+            break;
+          }
+          case "nsPref:changed": {
+            if (aData === TorLauncherPrefs.quickstart) {
+              self.state.QuickStartEnabled = Services.prefs.getBoolPref(TorLauncherPrefs.quickstart);
+            }
+            break;
+          }
+          default: {
+            console.log(`TorConnect: unhandled observe topic '${aTopic}'`);
+          }
         }
+
+        self.sendAsyncMessage("torconnect:state-change", self.state);
       },
+    };
+
+    // observe all of the torconnect:.* topics
+    for (const key in TorConnectTopics) {
+      const topic = TorConnectTopics[key];
+      Services.obs.addObserver(this.torConnectObserver, topic);
     }
-    Services.prefs.addObserver(gTorLauncherPrefs.quickstart, this.quickstartObserver);
+    Services.prefs.addObserver(TorLauncherPrefs.quickstart, this.torConnectObserver);
   }
 
   willDestroy() {
-    for (const topic of gActiveTopics) {
-      Services.obs.removeObserver(this.gObserver, topic);
+    // stop observing all of our torconnect:.* topics
+    for (const key in TorConnectTopics) {
+      const topic = TorConnectTopics[key];
+      Services.obs.removeObserver(this.torConnectObserver, topic);
     }
-  }
-
-
-  _OpenTorAdvancedPreferences() {
-    const win = this.browsingContext.top.embedderElement.ownerGlobal;
-    win.openTrustedLinkIn("about:preferences#tor", "tab");
-  }
-
-  _TorCopyLog() {
-    // Copy tor log messages to the system clipboard.
-    const chSvc = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
-      Ci.nsIClipboardHelper
-    );
-    const countObj = { value: 0 };
-    chSvc.copyString(TorProtocolService.getLog(countObj));
-    const count = countObj.value;
-    return TorLauncherUtil.getFormattedLocalizedString(
-      "copiedNLogMessagesShort",
-      [count],
-      1
-    );
+    Services.prefs.removeObserver(TorLauncherPrefs.quickstart, this.torConnectObserver);
   }
 
   receiveMessage(message) {
     switch (message.name) {
-      case "TorBootstrapErrorOccurred":
-        return TorProtocolService.torBootstrapErrorOccurred();
-      case "TorRetrieveBootstrapStatus":
-        return TorProtocolService.retrieveBootstrapStatus();
-      case "OpenTorAdvancedPreferences":
-        return this._OpenTorAdvancedPreferences();
-      case "GetLocalizedBootstrapStatus":
-        const { status, keyword } = message.data;
-        return TorLauncherUtil.getLocalizedBootstrapStatus(status, keyword);
-      case "TorCopyLog":
-        return this._TorCopyLog();
-      case "TorIsNetworkDisabled":
-        return TorProtocolService.isNetworkDisabled();
-      case "TorStopBootstrap":
-        return TorProtocolService.torStopBootstrap();
-      case "TorConnect":
-        return TorProtocolService.connect();
-      case "GetDirection":
-        return Services.locale.isAppLocaleRTL ? "rtl" : "ltr";
-      case "GetTorStrings":
-        return TorStrings;
-      case "TorLogHasWarnOrErr":
-        return TorProtocolService.torLogHasWarnOrErr();
+      case "torconnect:set-quickstart":
+        Services.prefs.setBoolPref(TorLauncherPrefs.quickstart, message.data);
+        break;
+      case "torconnect:open-tor-preferences":
+        TorConnect.openTorPreferences();
+        break;
+      case "torconnect:copy-tor-logs":
+        return TorConnect.copyTorLogs();
+      case "torconnect:cancel-bootstrap":
+        TorConnect.cancelBootstrap();
+        break;
+      case "torconnect:begin-bootstrap":
+        TorConnect.beginBootstrap();
+        break;
+      case "torconnect:get-init-args":
+        // called on AboutTorConnect.init(), pass down all state data it needs to init
+        return {
+            TorStrings: TorStrings,
+            TorConnectState: TorConnectState,
+            Direction: Services.locale.isAppLocaleRTL ? "rtl" : "ltr",
+            State: this.state,
+        };
     }
     return undefined;
   }
diff --git a/browser/components/torconnect/content/aboutTorConnect.js b/browser/components/torconnect/content/aboutTorConnect.js
index 19fd335ccd13..8b269d2fc82b 100644
--- a/browser/components/torconnect/content/aboutTorConnect.js
+++ b/browser/components/torconnect/content/aboutTorConnect.js
@@ -2,299 +2,258 @@
 
 /* eslint-env mozilla/frame-script */
 
-const kTorProcessReadyTopic = "TorProcessIsReady";
-const kTorProcessExitedTopic = "TorProcessExited";
-const kTorProcessDidNotStartTopic = "TorProcessDidNotStart";
-const kTorBootstrapStatusTopic = "TorBootstrapStatus";
-const kTorBootstrapErrorTopic = "TorBootstrapError";
-const kTorLogHasWarnOrErrTopic = "TorLogHasWarnOrErr";
-const kTorQuickstartPrefChanged = "TorQuickstartPrefChanged";
-
-const TorLauncherPrefs = {
-  quickstart: "extensions.torlauncher.quickstart",
-  prompt_at_startup: "extensions.torlauncher.prompt_at_startup",
-}
+// populated in AboutTorConnect.init()
+let TorStrings = {};
+let TorConnectState = {};
 
 class AboutTorConnect {
-  log(...args) {
-    console.log(...args);
-  }
-
-  logError(...args) {
-    console.error(...args);
-  }
+  selectors = Object.freeze({
+    textContainer: {
+      title: "div.title",
+      titleText: "h1.title-text",
+    },
+    progress: {
+      description: "p#connectShortDescText",
+      meter: "div#progressBackground",
+    },
+    copyLog: {
+      link: "span#copyLogLink",
+      tooltip: "div#copyLogTooltip",
+      tooltipText: "span#copyLogTooltipText",
+    },
+    quickstart: {
+      checkbox: "input#quickstartCheckbox",
+      label: "label#quickstartCheckboxLabel",
+    },
+    buttons: {
+      connect: "button#connectButton",
+      cancel: "button#cancelButton",
+      advanced: "button#advancedButton",
+    },
+  })
+
+  elements = Object.freeze({
+    title: document.querySelector(this.selectors.textContainer.title),
+    titleText: document.querySelector(this.selectors.textContainer.titleText),
+    progressDescription: document.querySelector(this.selectors.progress.description),
+    progressMeter: document.querySelector(this.selectors.progress.meter),
+    copyLogLink: document.querySelector(this.selectors.copyLog.link),
+    copyLogTooltip: document.querySelector(this.selectors.copyLog.tooltip),
+    copyLogTooltipText: document.querySelector(this.selectors.copyLog.tooltipText),
+    quickstartCheckbox: document.querySelector(this.selectors.quickstart.checkbox),
+    quickstartLabel: document.querySelector(this.selectors.quickstart.label),
+    connectButton: document.querySelector(this.selectors.buttons.connect),
+    cancelButton: document.querySelector(this.selectors.buttons.cancel),
+    advancedButton: document.querySelector(this.selectors.buttons.advanced),
+  })
+
+  beginBootstrap() {
+    this.hide(this.elements.connectButton);
+    this.show(this.elements.cancelButton);
+    this.elements.cancelButton.focus();
+    RPMSendAsyncMessage("torconnect:begin-bootstrap");
+  }
+
+  cancelBootstrap() {
+    RPMSendAsyncMessage("torconnect:cancel-bootstrap");
+  }
+
+  /*
+  Element helper methods
+  */
+
+  show(element) {
+    element.removeAttribute("hidden");
+  }
+
+  hide(element) {
+    element.setAttribute("hidden", "true");
+  }
+
+  setTitle(title, error) {
+    this.elements.titleText.textContent = title;
+    document.title = title;
 
-  logDebug(...args) {
-    console.debug(...args);
+    if (error) {
+      this.elements.title.classList.add("error");
+    } else {
+      this.elements.title.classList.remove("error");
+    }
   }
 
-  getElem(id) {
-    return document.getElementById(id);
-  }
-  get elemProgressContent() {
-    return this.getElem("progressContent");
-  }
-  get elemProgressDesc() {
-    return this.getElem("connectShortDescText");
-  }
-  get elemProgressMeter() {
-    return this.getElem("progressBackground");
-  }
-  get elemCopyLogLink() {
-    return this.getElem("copyLogLink");
-  }
-  get elemCopyLogTooltip() {
-    return this.getElem("copyLogTooltip");
-  }
-  get elemCopyLogTooltipText() {
-    return this.getElem("copyLogTooltipText");
-  }
-  get elemQuickstartCheckbox() {
-    return this.getElem("quickstartCheckbox");
-  }
-  get elemQuickstartLabel() {
-    return this.getElem("quickstartCheckboxLabel");
-  }
-  get elemConnectButton() {
-    return this.getElem("connectButton");
-  }
-  get elemAdvancedButton() {
-    return this.getElem("advancedButton");
-  }
-  get elemCancelButton() {
-    return this.getElem("cancelButton");
-  }
-  get elemTextContainer() {
-    return this.getElem("text-container");
-  }
-  get elemTitle() {
-    return this.elemTextContainer.getElementsByClassName("title")[0];
+  setProgress(description, visible, percent) {
+    this.elements.progressDescription.textContent = description;
+    if (visible) {
+      this.show(this.elements.progressMeter);
+      this.elements.progressMeter.style.width = `${percent}%`;
+    } else {
+      this.hide(this.elements.progressMeter);
+    }
   }
 
-  static get STATE_INITIAL() {
-    return "STATE_INITIAL";
-  }
+  /*
+  These methods update the UI based on the current TorConnect state
+  */
 
-  static get STATE_BOOTSTRAPPING() {
-    return "STATE_BOOTSTRAPPING";
-  }
+  updateUI(state) {
+    console.log(state);
 
-  static get STATE_BOOTSTRAPPED() {
-    return "STATE_BOOTSTRAPPED";
-  }
+    // calls update_$state()
+    this[`update_${state.State}`](state);
 
-  static get STATE_BOOTSTRAP_ERROR() {
-    return "STATE_BOOTSTRAP_ERROR";
+    if (state.ShowCopyLog) {
+      this.showCopyLog();
+    }
+    this.elements.quickstartCheckbox.checked = state.QuickStartEnabled;
   }
 
-  get state() {
-    return this._state;
-  }
+  /* Per-state updates */
 
-  setInitialUI() {
-    this.setTitle(this.torStrings.torConnect.torConnect);
-    this.elemProgressDesc.textContent =
-      this.torStrings.settings.torPreferencesDescription;
-    this.showElem(this.elemConnectButton);
-    this.elemConnectButton.focus();
-    this.showElem(this.elemAdvancedButton);
-    this.hideElem(this.elemCopyLogLink);
-    this.hideElem(this.elemCancelButton);
-    this.hideElem(this.elemProgressContent);
-    this.hideElem(this.elemProgressMeter);
-    this.elemTitle.classList.remove("error");
-  }
+  update_Initial(state) {
+    const hasError = false;
+    const showProgressbar = false;
 
-  setBootstrappingUI() {
-    this.setTitle(this.torStrings.torConnect.torConnecting);
-    this.hideElem(this.elemConnectButton);
-    this.hideElem(this.elemAdvancedButton);
-    this.hideElem(this.elemCopyLogLink);
-    this.showElem(this.elemCancelButton);
-    this.elemCancelButton.focus();
-    this.showElem(this.elemProgressContent);
-    this.showElem(this.elemProgressMeter);
-    this.elemTitle.classList.remove("error");
+    this.setTitle(TorStrings.torConnect.torConnect, hasError);
+    this.setProgress(TorStrings.settings.torPreferencesDescription, showProgressbar);
+    this.hide(this.elements.copyLogLink);
+    this.hide(this.elements.connectButton);
+    this.hide(this.elements.advancedButton);
+    this.hide(this.elements.cancelButton);
   }
 
-  setBootstrapErrorUI() {
-    this.setTitle(this.torStrings.torConnect.torBootstrapFailed);
-    this.elemConnectButton.textContent = this.torStrings.torConnect.tryAgain;
-    this.showElem(this.elemConnectButton);
-    this.hideElem(this.elemCancelButton);
-    this.showElem(this.elemAdvancedButton);
-    this.elemAdvancedButton.focus();
-    this.showElem(this.elemProgressContent);
-    this.hideElem(this.elemProgressMeter);
-    this.elemTitle.classList.add("error");
-  }
+  update_Configuring(state) {
+    const hasError = state.ErrorMessage != null;
+    const showProgressbar = false;
 
-  set state(state) {
-    const oldState = this.state;
-    if (oldState === state) {
-      return;
-    }
-    this._state = state;
-    switch (this.state) {
-      case AboutTorConnect.STATE_INITIAL:
-        this.setInitialUI();
-        break;
-      case AboutTorConnect.STATE_BOOTSTRAPPING:
-        this.setBootstrappingUI();
-        break;
-      case AboutTorConnect.STATE_BOOTSTRAP_ERROR:
-        this.setBootstrapErrorUI();
-        break;
-      case AboutTorConnect.STATE_BOOTSTRAPPED:
-        window.close();
-        break;
+    if (hasError) {
+      this.setTitle(state.ErrorMessage, hasError);
+      this.setProgress(state.ErrorDetails, showProgressbar);
+      this.show(this.elements.copyLogLink);
+      this.elements.connectButton.textContent = TorStrings.torConnect.tryAgain;
+    } else {
+      this.setTitle(TorStrings.torConnect.torConnect, hasError);
+      this.setProgress(TorStrings.settings.torPreferencesDescription, showProgressbar);
+      this.hide(this.elements.copyLogLink);
+      this.elements.connectButton.textContent = TorStrings.torConnect.torConnectButton;
     }
+    this.show(this.elements.connectButton);
+    this.elements.connectButton.focus();
+    this.show(this.elements.advancedButton);
+    this.hide(this.elements.cancelButton);
   }
 
-  async showErrorMessage(aErrorObj) {
-    if (aErrorObj && aErrorObj.message) {
-      this.setTitle(aErrorObj.message);
-      if (aErrorObj.details) {
-        this.elemProgressDesc.textContent = aErrorObj.details;
-      }
-    }
-
-    this.showCopyLog();
-    this.showElem(this.elemConnectButton);
+  update_AutoConfiguring(state) {
+    // TODO: noop until this state is used
   }
 
-  showElem(elem) {
-    elem.removeAttribute("hidden");
-  }
+  update_Bootstrapping(state) {
+    const hasError = false;
+    const showProgressbar = true;
 
-  hideElem(elem) {
-    elem.setAttribute("hidden", "true");
+    this.setTitle(state.BootstrapStatus ? state.BootstrapStatus : TorStrings.torConnect.torConnecting, hasError);
+    this.setProgress(TorStrings.settings.torPreferencesDescription, showProgressbar, state.BootstrapProgress);
+    if (state.ShowCopyLog) {
+      this.show(this.elements.copyLogLink);
+    } else {
+      this.hide(this.elements.copyLogLink);
+    }
+    this.hide(this.elements.connectButton);
+    this.hide(this.elements.advancedButton);
+    this.show(this.elements.cancelButton);
+    this.elements.cancelButton.focus();
   }
 
-  async connect() {
-    // reset the text to original description
-    // in case we are trying again after an error (clears out error text)
-    this.elemProgressDesc.textContent =
-      this.torStrings.settings.torPreferencesDescription;
+  update_Error(state) {
+    const hasError = true;
+    const showProgressbar = false;
 
-    this.state = AboutTorConnect.STATE_BOOTSTRAPPING;
-    const error = await RPMSendQuery("TorConnect");
-    if (error) {
-      if (error.details) {
-        this.showErrorMessage({ message: error.details }, true);
-        this.showSaveSettingsError(error.details);
-      }
-    }
+    this.setTitle(state.ErrorMessage, hasError);
+    this.setProgress(state.ErrorDetails, showProgressbar);
+    this.show(this.elements.copyLogLink);
+    this.elements.connectButton.textContent = TorStrings.torConnect.tryAgain;
+    this.show(this.elements.connectButton);
+    this.show(this.elements.advancedButton);
+    this.hide(this.elements.cancelButton);
   }
 
-  showCopyLog() {
-    this.elemCopyLogLink.removeAttribute("hidden");
+  update_FatalError(state) {
+    // TODO: noop until this state is used
   }
 
-  async updateBootstrapProgress(status) {
-    let labelText = await RPMSendQuery("GetLocalizedBootstrapStatus", {
-      status,
-      keyword: "TAG",
-    });
-    let percentComplete = status.PROGRESS ? status.PROGRESS : 0;
-    this.elemProgressMeter.style.width = `${percentComplete}%`;
-
-    if (await RPMSendQuery("TorBootstrapErrorOccurred")) {
-      this.state = AboutTorConnect.STATE_BOOTSTRAP_ERROR;
-      return;
-    } else if (await RPMSendQuery("TorIsNetworkDisabled")) {
-      // If tor network is not connected, let's go to the initial state, even
-      // if bootstrap state is greater than 0.
-      this.state = AboutTorConnect.STATE_INITIAL;
-      return;
-    } else if (percentComplete > 0) {
-      this.state = AboutTorConnect.STATE_BOOTSTRAPPING;
-    }
+  update_Bootstrapped(state) {
+    const hasError = false;
+    const showProgressbar = true;
 
-    // Due to async, status might have changed. Do not override desc if so.
-    if (this.state === AboutTorConnect.STATE_BOOTSTRAPPING) {
-      this.hideElem(this.elemConnectButton);
+    this.setTitle(TorStrings.torConnect.torConnected, hasError);
+    this.setProgress(TorStrings.settings.torPreferencesDescription, showProgressbar, 100);
+    this.hide(this.elements.connectButton);
+    this.hide(this.elements.advancedButton);
+    this.hide(this.elements.cancelButton);
+
+    // only close the window if directed
+    if (state.Close) {
+      window.close();
     }
   }
 
-  stopTorBootstrap() {
-    RPMSendAsyncMessage("TorStopBootstrap");
+  update_Disabled(state) {
+    // TODO: we should probably have some UX here if a user goes to about:torconnect when
+    // it isn't in use (eg using tor-launcher or system tor)
   }
 
-  setTitle(title) {
-    const titleElement = document.querySelector(".title-text");
-    titleElement.textContent = title;
-    document.title = title;
-  }
+  async initElements(direction, quickstart) {
 
-  async initElements() {
-    this.elemAdvancedButton.textContent = this.torStrings.torConnect.torConfigure;
-    this.elemAdvancedButton.addEventListener("click", () => {
-      RPMSendAsyncMessage("OpenTorAdvancedPreferences");
-    });
+    document.documentElement.setAttribute("dir", direction);
 
-    // sets the text content while keping the child elements intact
-    this.elemCopyLogLink.childNodes[0].nodeValue =
-      this.torStrings.torConnect.copyLog;
-    this.elemCopyLogLink.addEventListener("click", async (event) => {
-      const copiedMessage = await RPMSendQuery("TorCopyLog");
-      aboutTorConnect.elemCopyLogTooltipText.textContent = copiedMessage;
-      aboutTorConnect.elemCopyLogTooltip.style.visibility = "visible";
+    // sets the text content while keeping the child elements intact
+    this.elements.copyLogLink.childNodes[0].nodeValue =
+      TorStrings.torConnect.copyLog;
+    this.elements.copyLogLink.addEventListener("click", async (event) => {
+      const copiedMessage = await RPMSendQuery("torconnect:copy-tor-logs");
+      this.elements.copyLogTooltipText.textContent = copiedMessage;
+      this.elements.copyLogTooltipText.style.visibility = "visible";
 
       // clear previous timeout if one already exists
-      if (aboutTorConnect.copyLogTimeoutId) {
-        clearTimeout(aboutTorConnect.copyLogTimeoutId);
+      if (this.copyLogTimeoutId) {
+        clearTimeout(this.copyLogTimeoutId);
       }
 
       // hide tooltip after X ms
       const TOOLTIP_TIMEOUT = 2000;
-      aboutTorConnect.copyLogTimeoutId = setTimeout(function() {
-        aboutTorConnect.elemCopyLogTooltip.style.visibility = "hidden";
-        aboutTorConnect.copyLogTimeoutId = 0;
+      this.copyLogTimeoutId = setTimeout(function() {
+        this.elements.copyLogTooltipText.style.visibility = "hidden";
+        this.copyLogTimeoutId = 0;
       }, TOOLTIP_TIMEOUT);
     });
 
+    this.elements.quickstartCheckbox.checked = quickstart
+    this.elements.quickstartCheckbox.addEventListener("change", () => {
+      const quickstart = this.elements.quickstartCheckbox.checked;
+      RPMSendAsyncMessage("torconnect:set-quickstart", quickstart);
+    });
+    this.elements.quickstartLabel.textContent = TorStrings.settings.quickstartCheckbox;
 
-    this.elemQuickstartLabel.textContent = this.torStrings.settings.quickstartCheckbox;
-    this.elemQuickstartCheckbox.addEventListener("change", () => {
-      const quickstart = this.elemQuickstartCheckbox.checked;
-      RPMSetBoolPref(TorLauncherPrefs.quickstart, quickstart);
+    this.elements.connectButton.textContent =
+      TorStrings.torConnect.torConnectButton;
+    this.elements.connectButton.addEventListener("click", () => {
+      this.beginBootstrap();
     });
-    this.elemQuickstartCheckbox.checked = await RPMGetBoolPref(TorLauncherPrefs.quickstart);
 
-    this.elemConnectButton.textContent =
-      this.torStrings.torConnect.torConnectButton;
-    this.elemConnectButton.addEventListener("click", () => {
-      this.connect();
+    this.elements.advancedButton.textContent = TorStrings.torConnect.torConfigure;
+    this.elements.advancedButton.addEventListener("click", () => {
+      RPMSendAsyncMessage("torconnect:open-tor-preferences");
     });
 
-    this.elemCancelButton.textContent = this.torStrings.torConnect.cancel;
-    this.elemCancelButton.addEventListener("click", () => {
-      this.stopTorBootstrap();
+    this.elements.cancelButton.textContent = TorStrings.torConnect.cancel;
+    this.elements.cancelButton.addEventListener("click", () => {
+      this.cancelBootstrap();
     });
   }
 
   initObservers() {
-    RPMAddMessageListener(kTorBootstrapErrorTopic, ({ data }) => {
-      this.showCopyLog();
-      this.stopTorBootstrap();
-      this.showErrorMessage(data);
-    });
-    RPMAddMessageListener(kTorLogHasWarnOrErrTopic, () => {
-      this.showCopyLog();
-    });
-    RPMAddMessageListener(kTorProcessDidNotStartTopic, ({ data }) => {
-      this.showErrorMessage(data);
-    });
-    RPMAddMessageListener(kTorBootstrapStatusTopic, ({ data }) => {
-      this.updateBootstrapProgress(data);
-    });
-    RPMAddMessageListener(kTorQuickstartPrefChanged, ({ data }) => {
-      // update checkbox with latest quickstart pref value
-      this.elemQuickstartCheckbox.checked = data;
-    });
-    RPMAddMessageListener("torconnect:bootstrap-complete", () => {
-      this.state = AboutTorConnect.STATE_BOOTSTRAPPED;
+    // TorConnectParent feeds us state blobs to we use to update our UI
+    RPMAddMessageListener("torconnect:state-change", ({ data }) => {
+      this.updateUI(data);
     });
   }
 
@@ -304,34 +263,25 @@ class AboutTorConnect {
       // integers, so we must resort to a string compare here :(
       // see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code for relevant documentation
       if (evt.code === "Escape") {
-        this.stopTorBootstrap();
+        this.cancelBootstrap();
       }
     };
   }
 
   async init() {
-    this.torStrings = await RPMSendQuery("GetTorStrings");
-    document.documentElement.setAttribute(
-      "dir",
-      await RPMSendQuery("GetDirection")
-    );
-    this.initElements();
+
+    let args = await RPMSendQuery("torconnect:get-init-args");
+
+    // various constants
+    TorStrings = Object.freeze(args.TorStrings);
+    TorConnectState = Object.freeze(args.TorConnectState);
+
+    this.initElements(args.Direction);
     this.initObservers();
     this.initKeyboardShortcuts();
-    this.state = AboutTorConnect.STATE_INITIAL;
-
-    // Request the most recent bootstrap status info so that a
-    // TorBootstrapStatus notification is generated as soon as possible.
-    RPMSendAsyncMessage("TorRetrieveBootstrapStatus");
-
-    // quickstart is the user set pref for starting tor automatically
-    // prompt_at_startup will be set to false after successful bootstrap, and true on error
-    // by tor-launcher, so we want to keep the connect screen up when prompt_at_startup is true
-    ///  even if quickstart is enabled so user can potentially resolve errors on next launch
-    if (await RPMGetBoolPref(TorLauncherPrefs.quickstart) &&
-       !await RPMGetBoolPref(TorLauncherPrefs.prompt_at_startup)) {
-      this.connect();
-    }
+
+    // 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 0a0721afb7db..595bbdf9a70a 100644
--- a/browser/components/torconnect/content/aboutTorConnect.xhtml
+++ b/browser/components/torconnect/content/aboutTorConnect.xhtml
@@ -19,15 +19,6 @@
           </div>
         </div>
 
-        <div id="progressContent" hidden="true">
-          <div class="tbb-header" pack="center">
-            <image class="tbb-logo"/>
-          </div>
-          <div flex="1">
-            <div id="progressDesc"/>
-          </div>
-        </div>
-
         <div id="copyLogContainer">
           <span id="copyLogLink" hidden="true">
             <div id="copyLogTooltip">
diff --git a/browser/components/torconnect/content/torBootstrapUrlbar.js b/browser/components/torconnect/content/torBootstrapUrlbar.js
index 55a595b2dbab..e4fd6f5ab910 100644
--- a/browser/components/torconnect/content/torBootstrapUrlbar.js
+++ b/browser/components/torconnect/content/torBootstrapUrlbar.js
@@ -2,135 +2,88 @@
 
 "use strict";
 
- const TorConnectionStatus = {
-   invalid: -1,
-   offline: 0,
-   connecting: 1,
-   connected: 2,
-   failure: 3,
- };
-var TorBootstrapUrlbar;
+const { TorConnect, TorConnectTopics, TorConnectState } = ChromeUtils.import(
+  "resource:///modules/TorConnect.jsm"
+);
+const { TorStrings } = ChromeUtils.import(
+  "resource:///modules/TorStrings.jsm"
+);
 
-{
-  const { TorProtocolService } = ChromeUtils.import(
-    "resource:///modules/TorProtocolService.jsm"
-  );
-  const { TorLauncherUtil } = ChromeUtils.import(
-    "resource://torlauncher/modules/tl-util.jsm"
-  );
-  const { TorStrings } = ChromeUtils.import(
-    "resource:///modules/TorStrings.jsm"
-  );
-
-  const kTorProcessReadyTopic = "TorProcessIsReady";
-  const kTorProcessExitedTopic = "TorProcessExited";
-  const kTorProcessDidNotStartTopic = "TorProcessDidNotStart";
-  const kTorBootstrapStatusTopic = "TorBootstrapStatus";
-  const kTorBootstrapErrorTopic = "TorBootstrapError";
-
-  const gActiveTopics = [
-    kTorProcessReadyTopic,
-    kTorProcessExitedTopic,
-    kTorProcessDidNotStartTopic,
-    kTorBootstrapStatusTopic,
-    kTorBootstrapErrorTopic,
-  ];
-
-  TorBootstrapUrlbar = {
-    _connectionStatus: TorConnectionStatus.invalid,
-    get ConnectionStatus() {
-      return this._connectionStatus;
+var TorBootstrapUrlbar = {
+  selectors: Object.freeze({
+    torConnect: {
+      box: "hbox#torconnect-box",
+      label: "label#torconnect-label",
     },
+  }),
 
-    _torConnectBox : null,
-    get TorConnectBox() {
-      if (!this._torConnectBox) {
-        this._torConnectBox =
-          browser.ownerGlobal.document.getElementById("torconnect-box");
-      }
-      return this._torConnectBox;
-    },
+  elements: null,
 
-    _torConnectLabel : null,
-    get TorConnectLabel() {
-      if (!this._torConnectLabel) {
-        this._torConnectLabel =
-          browser.ownerGlobal.document.getElementById("torconnect-label");
+  updateTorConnectBox: function(state) {
+    switch(state)
+    {
+      case TorConnectState.Initial:
+      case TorConnectState.Configuring:
+      case TorConnectState.AutoConfiguring:
+      case TorConnectState.Error:
+      case TorConnectState.FatalError: {
+        this.elements.torConnectBox.removeAttribute("hidden");
+        this.elements.torConnectLabel.textContent = TorStrings.torConnect.offline;
+        this.elements.inputContainer.setAttribute("torconnect", "offline");
+        break;
       }
-      return this._torConnectLabel;
-    },
-
-    _updateConnectionStatus(percentComplete = 0) {
-      if (TorProtocolService.ownsTorDaemon &&
-          !TorLauncherUtil.useLegacyLauncher) {
-        if (TorProtocolService.isNetworkDisabled()) {
-            if (TorProtocolService.torBootstrapErrorOccurred()) {
-              this._connectionStatus = TorConnectionStatus.failure;
-            } else {
-              this._connectionStatus = TorConnectionStatus.offline;
-            }
-        } else if (percentComplete < 100) {
-          this._connectionStatus = TorConnectionStatus.connecting;
-        } else if (percentComplete === 100) {
-          this._connectionStatus = TorConnectionStatus.connected;
-        }
+      case TorConnectState.Bootstrapping: {
+        this.elements.torConnectBox.removeAttribute("hidden");
+        this.elements.torConnectLabel.textContent =
+          TorStrings.torConnect.torConnectingConcise;
+        this.elements.inputContainer.setAttribute("torconnect", "connecting");
+        break;
       }
-      else
-      {
-        this._connectionStatus = TorConnectionStatus.invalid;
+      case TorConnectState.Bootstrapped: {
+        this.elements.torConnectBox.removeAttribute("hidden");
+        this.elements.torConnectLabel.textContent =
+          TorStrings.torConnect.torConnectedConcise;
+        this.elements.inputContainer.setAttribute("torconnect", "connected");
+        // hide torconnect box after 5 seconds
+        setTimeout(() => {
+          this.elements.torConnectBox.setAttribute("hidden", "true");
+        }, 5000);
+        break;
       }
-
-      switch(this._connectionStatus)
-      {
-        case TorConnectionStatus.failure:
-        case TorConnectionStatus.offline:
-          this.TorConnectBox.removeAttribute("hidden");
-          this.TorConnectLabel.textContent = TorStrings.torConnect.offline;
-          gURLBar._inputContainer.setAttribute("torconnect", "offline");
-          break;
-        case TorConnectionStatus.connecting:
-          this.TorConnectLabel.textContent =
-            TorStrings.torConnect.torConnectingConcise;
-          gURLBar._inputContainer.setAttribute("torconnect", "connecting");
-          break;
-        case TorConnectionStatus.connected:
-          this.TorConnectLabel.textContent =
-            TorStrings.torConnect.torConnectedConcise;
-          gURLBar._inputContainer.setAttribute("torconnect", "connected");
-          // hide torconnect box after 5 seconds
-          let self = this;
-          setTimeout(function() {
-            self.TorConnectBox.setAttribute("hidden", "true");
-          }, 5000);
-          break;
+      case TorConnectState.Disabled: {
+        this.elements.torConnectBox.setAttribute("hidden", "true");
+        break;
       }
-    },
+      default:
+        break;
+    }
+  },
 
-    observe(aSubject, aTopic, aData) {
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic === TorConnectTopics.StateChange) {
       const obj = aSubject?.wrappedJSObject;
+      this.updateTorConnectBox(obj?.state);
+    }
+  },
+
+  init: function() {
+    if (TorConnect.shouldShowTorConnect) {
+      // browser isn't populated until init
+      this.elements = Object.freeze({
+        torConnectBox: browser.ownerGlobal.document.querySelector(this.selectors.torConnect.box),
+        torConnectLabel: browser.ownerGlobal.document.querySelector(this.selectors.torConnect.label),
+        inputContainer: gURLBar._inputContainer,
+      })
+      Services.obs.addObserver(this, TorConnectTopics.StateChange);
+      this.observing = true;
+      this.updateTorConnectBox(TorConnect.state);
+    }
+  },
+
+  uninit: function() {
+    if (this.observing) {
+      Services.obs.removeObserver(this, TorConnectTopics.StateChange);
+    }
+  },
+};
 
-      switch (aTopic) {
-        case kTorProcessReadyTopic:
-        case kTorProcessExitedTopic:
-        case kTorProcessDidNotStartTopic:
-        case kTorBootstrapErrorTopic:
-          this._updateConnectionStatus();
-          break;
-        case kTorBootstrapStatusTopic:
-          let percentComplete = obj.PROGRESS ? obj.PROGRESS : 0;
-          this._updateConnectionStatus(percentComplete);
-          break;
-      }
-    },
-    init() {
-      for (const topic of gActiveTopics) {
-        Services.obs.addObserver(this, topic);
-      }
-    },
-    uninit() {
-      for (const topic of gActiveTopics) {
-        Services.obs.removeObserver(this, topic);
-      }
-    },
-  };
-}
diff --git a/browser/components/torpreferences/content/torPane.js b/browser/components/torpreferences/content/torPane.js
index 01609ddda090..59ecdec6d1d9 100644
--- a/browser/components/torpreferences/content/torPane.js
+++ b/browser/components/torpreferences/content/torPane.js
@@ -6,6 +6,10 @@ const { TorProtocolService } = ChromeUtils.import(
   "resource:///modules/TorProtocolService.jsm"
 );
 
+const { TorConnect } = ChromeUtils.import(
+  "resource:///modules/TorConnect.jsm"
+);
+
 const {
   TorBridgeSource,
   TorBridgeSettings,
@@ -188,14 +192,14 @@ const gTorPane = (function() {
       this._messageBoxButton = prefpane.querySelector(selectors.messageBox.button);
       // wire up connect button
       this._messageBoxButton.addEventListener("click", () => {
-        TorProtocolService.connect();
+        TorConnect.beginBootstrap();
         let win = Services.wm.getMostRecentWindow("navigator:browser");
         // switch to existing about:torconnect tab or create a new one
         win.switchToTabHavingURI("about:torconnect", true);
       });
 
       let populateMessagebox = () => {
-        if (TorProtocolService.shouldShowTorConnect()) {
+        if (TorConnect.shouldShowTorConnect) {
           // set messagebox style and text
           if (TorProtocolService.torBootstrapErrorOccurred()) {
             this._messageBox.className = "error";
diff --git a/browser/components/urlbar/UrlbarInput.jsm b/browser/components/urlbar/UrlbarInput.jsm
index f727c386701c..60b5b9163d67 100644
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -10,8 +10,8 @@ const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 
-const { TorProtocolService } = ChromeUtils.import(
-  "resource:///modules/TorProtocolService.jsm"
+const { TorConnect } = ChromeUtils.import(
+  "resource:///modules/TorConnect.jsm"
 );
 
 // in certain scenarios we want user input uris to open in a new tab if they do so from the
@@ -24,7 +24,7 @@ function maybeUpdateOpenLocationForTorConnect(openUILinkWhere, currentURI, desti
         // we are trying to open in same tab
         openUILinkWhere === "current" &&
         // only if user still has not bootstrapped
-        TorProtocolService.shouldShowTorConnect() &&
+        TorConnect.shouldShowTorConnect &&
         // and user is not just navigating to about:torconnect
         destinationURI !== "about:torconnect") {
       return "tab";
diff --git a/browser/modules/TorConnect.jsm b/browser/modules/TorConnect.jsm
index 3125c84558db..5d2b826cfa10 100644
--- a/browser/modules/TorConnect.jsm
+++ b/browser/modules/TorConnect.jsm
@@ -1,6 +1,6 @@
 "use strict";
 
-var EXPORTED_SYMBOLS = ["TorConnect"];
+var EXPORTED_SYMBOLS = ["TorConnect", "TorConnectTopics", "TorConnectState"];
 
 const { Services } = ChromeUtils.import(
     "resource://gre/modules/Services.jsm"
@@ -10,53 +10,476 @@ const { BrowserWindowTracker } = ChromeUtils.import(
     "resource:///modules/BrowserWindowTracker.jsm"
 );
 
-const { TorProtocolService } = ChromeUtils.import(
+const { TorProtocolService, TorProcessStatus } = ChromeUtils.import(
     "resource:///modules/TorProtocolService.jsm"
 );
 
-// TODO: move the bootstrap state management out of each of the individual
-// about:torconnect pages and stick it here
-var TorConnect = (() => {
+const { TorLauncherUtil } = ChromeUtils.import(
+    "resource://torlauncher/modules/tl-util.jsm"
+);
+
+/* Browser observer topis */
+const BrowserTopics = Object.freeze({
+    ProfileAfterChange: "profile-after-change",
+});
+
+/* tor-launcher observer topics */
+const TorTopics = Object.freeze({
+    ProcessIsReady: "TorProcessIsReady",
+    BootstrapStatus: "TorBootstrapStatus",
+    BootstrapError: "TorBootstrapError",
+    ProcessExited: "TorProcessExited",
+    LogHasWarnOrErr: "TorLogHasWarnOrErr",
+});
+
+/* Relevant prefs used by tor-launcher */
+const TorLauncherPrefs = Object.freeze({
+  quickstart: "extensions.torlauncher.quickstart",
+  prompt_at_startup: "extensions.torlauncher.prompt_at_startup",
+});
+
+const TorConnectState = Object.freeze({
+    /* Our initial state */
+    Initial: "Initial",
+    /* In-between initial boot and bootstrapping, users can change tor network settings during this state */
+    Configuring: "Configuring",
+    /* Geo-location and setting bridges/etc */
+    AutoConfiguring: "AutoConfiguring",
+    /* Tor is bootstrapping */
+    Bootstrapping: "Bootstrapping",
+    /* Passthrough state back to Configuring or Fatal */
+    Error: "Error",
+    /* An unrecoverable error */
+    FatalError: "FatalError",
+    /* Final state, after successful bootstrap */
+    Bootstrapped: "Bootstrapped",
+    /* If we are using System tor or the legacy Tor-Launcher */
+    Disabled: "Disabled",
+});
+
+/*
+
+                                               TorConnect State Transitions
+
+                                              â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??
+                                              â??       Disabled       â??
+                                              â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??
+                                                â?²
+                                                â?? legacyOrSystemTor()
+                                                â??
+                                              â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??
+                      â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â?? â??       Initial        â?? â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??
+                      â??                       â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??                            â??
+                      â??                         â??                                                 â??
+                      â??                         â?? beginBootstrap()                                â??
+                      â??                         â?¼                                                 â??
+â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??    â??  bootstrapComplete()  â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??  â??  beginBootstrap()
+â??  Bootstrapped  â?? â??â??â??â?¼â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â?? â??                 Bootstrapping                  â?? â??â?¼â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??
+â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??    â??                       â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??  â??                 â??
+                      â??                         â??                       â?²                    â??    â??                 â??
+                      â??                         â?? cancelBootstrap()     â?? beginBootstrap()   â??â??â??â??â??â?¼â??â??â??â??â??â??â??â??â??â??â??â??â??â??   â??
+                      â??                         â?¼                       â??                         â??             â??   â??
+                      â??   beginConfigure()    â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??  â??             â??   â??
+                      â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â?¶ â??                                                â??  â??             â??   â??
+                                              â??                                                â??  â??             â??   â??
+                       beginConfigure()       â??                                                â??  â??             â??   â??
+                 â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â?¶ â??                  Configuring                   â??  â??             â??   â??
+                 â??                            â??                                                â??  â??             â??   â??
+                 â??                            â??                                                â??  â??             â??   â??
+                 â??    â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â?¶ â??                                                â??  â??             â??   â??
+                 â??    â??                       â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??  â??             â??   â??
+                 â??    â??                         â??                       â??                         â??             â??   â??
+                 â??    â?? cancelAutoconfigure()   â?? autoConfigure()       â??                    â??â??â??â??â??â?¼â??â??â??â??â??â??â??â??â??â??â??â??â??â?¼â??â??â??â??
+                 â??    â??                         â?¼                       â??                    â??    â??             â??
+                 â??    â??                       â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??  â??                    â??    â??             â??
+                 â??    â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â?? â??   AutoConfiguring    â?? â??â?¼â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??    â??             â??
+                 â??                            â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??  â??                         â??             â??
+                 â??                              â??                       â??                         â?? onError()   â??
+                 â??                              â?? onError()             â?? onError()               â??             â??
+                 â??                              â?¼                       â?¼                         â??             â??
+                 â??                            â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??  â??             â??
+                 â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â?? â??                     Error                      â?? â??â??             â??
+                                              â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??                â??
+                                                â??                                            â?²   onError()      â??
+                                                â?? onFatalError()                             â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??
+                                                â?¼
+                                              â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??
+                                              â??      FatalError      â??
+                                              â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??â??
+
+*/
+
+
+/* Maps allowed state transitions
+   TorConnectStateTransitions[state] maps to an array of allowed states to transition to
+*/
+const TorConnectStateTransitions =
+    Object.freeze(new Map([
+        [TorConnectState.Initial,
+            [TorConnectState.Disabled,
+             TorConnectState.Bootstrapping,
+             TorConnectState.Configuring,
+             TorConnectState.Error]],
+        [TorConnectState.Configuring,
+            [TorConnectState.AutoConfiguring,
+             TorConnectState.Bootstrapping,
+             TorConnectState.Error]],
+        [TorConnectState.AutoConfiguring,
+            [TorConnectState.Configuring,
+             TorConnectState.Bootstrapping,
+             TorConnectState.Error]],
+        [TorConnectState.Bootstrapping,
+            [TorConnectState.Configuring,
+             TorConnectState.Bootstrapped,
+             TorConnectState.Error]],
+        [TorConnectState.Error,
+            [TorConnectState.Configuring,
+             TorConnectState.FatalError]],
+        // terminal states
+        [TorConnectState.FatalError, []],
+        [TorConnectState.Bootstrapped, []],
+        [TorConnectState.Disabled, []],
+    ]));
+
+/* Topics Notified by the TorConnect module */
+const TorConnectTopics = Object.freeze({
+    StateChange: "torconnect:state-change",
+    BootstrapProgress: "torconnect:bootstrap-progress",
+    BootstrapComplete: "torconnect:bootstrap-complete",
+    BootstrapError: "torconnect:bootstrap-error",
+    FatalError: "torconnect:fatal-error",
+});
+
+const TorConnect = (() => {
     let retval = {
-        init : function() {
-            let topics = [
-                "TorBootstrapStatus",
-            ];
 
-            for(const topic of topics) {
-                Services.obs.addObserver(this, topic);
+        _state: TorConnectState.Initial,
+        _bootstrapProgress: 0,
+        _bootstrapStatus: null,
+        _errorMessage: null,
+        _errorDetails: null,
+        _logHasWarningOrError: false,
+        // init to about:tor as fallback in case setURIsToLoad is somehow never called
+        _urisToLoad: ["about:tor"],
+
+        /* These functions are called after transitioning to a new state */
+        _transitionCallbacks: Object.freeze(new Map([
+            /* Initial is never transitioned to */
+            [TorConnectState.Initial, null],
+            /* Configuring */
+            [TorConnectState.Configuring, (self) => {
+                // TODO move this to the transition function
+                if (this._state === TorConnectState.Bootstrapping) {
+                    TorProtocolService.torStopBootstrap();
+                }
+            }],
+            /* AutoConfiguring */
+            [TorConnectState.AutoConfiguring, (self) => {
+
+            }],
+            /* Bootstrapping */
+            [TorConnectState.Bootstrapping, (self) => {
+                let error = TorProtocolService.connect();
+                if (error) {
+                    self.onError(error.message, error.details);
+                } else {
+                    self._errorMessage = self._errorDetails = null;
+                }
+            }],
+            /* Bootstrapped */
+            [TorConnectState.Bootstrapped, (self) => {
+                // open home page(s) in new tabs
+                const win = BrowserWindowTracker.getTopWindow()
+
+                let location="tab";
+                for (const uri of self._urisToLoad) {
+                    win.openTrustedLinkIn(uri, location);
+                    // open subsequent tabs behind first tab
+                    location = "tabshifted";
+                }
+                Services.obs.notifyObservers(null, TorConnectTopics.BootstrapComplete);
+            }],
+            /* Error */
+            [TorConnectState.Error, (self, errorMessage, errorDetails, fatal) => {
+                self._errorMessage = errorMessage;
+                self._errorDetails = errorDetails;
+
+                Services.obs.notifyObservers({message: errorMessage, details: errorDetails}, TorConnectTopics.BootstrapError);
+                if (fatal) {
+                    self.onFatalError();
+                } else {
+                    self.beginConfigure();
+                }
+            }],
+            /* FatalError */
+            [TorConnectState.FatalError, (self) => {
+                Services.obs.notifyObservers(null, TorConnectTopics.FatalError);
+            }],
+            /* Disabled */
+            [TorConnectState.Disabled, (self) => {
+
+            }],
+        ])),
+
+        _changeState: function(newState, ...args) {
+            const oldState = this._state;
+
+            // ensure this is a valid state transition
+            if (!TorConnectStateTransitions.get(oldState)?.includes(newState)) {
+                throw Error(`TorConnect: Attempted invalid state transition from ${oldState} to ${newState}`);
             }
+
+            console.log(`TorConnect: transitioning state from ${oldState} to ${newState}`);
+
+            // call our transition function and forward any args
+            this._transitionCallbacks.get(newState)(this, ...args);
+
+            // finally, set our new state
+            this._state = newState;
+
+            Services.obs.notifyObservers({state: newState}, TorConnectTopics.StateChange);
+        },
+
+        // init should be called on app-startup in MainProcessingSingleton.jsm
+        init : function() {
+            console.log("TorConnect: Init");
+
+            // delay remaining init until after profile-after-change
+            Services.obs.addObserver(this, BrowserTopics.ProfileAfterChange);
         },
 
         observe: function(subject, topic, data) {
+            console.log(`TorConnect: observed ${topic}`);
+
             switch(topic) {
-            case "TorBootstrapStatus":
-                const obj = subject?.wrappedJSObject;
-                if (obj?.PROGRESS === 100) {
-                    // open home page(s) in new tabs
-                    const win = BrowserWindowTracker.getTopWindow()
-                    const urls = Services.prefs.getStringPref("browser.startup.homepage").split('|');
-
-                    let location="tab";
-                    for(const url of urls) {
-                        win.openTrustedLinkIn(url, location);
-                        // open subsequent tabs behind first tab
-                        location = "tabshifted";
+
+            /* Determine which state to move to from Initial */
+            case BrowserTopics.ProfileAfterChange: {
+                if (TorLauncherUtil.useLegacyLauncher || !TorProtocolService.ownsTorDaemon) {
+                    // Disabled
+                    this.legacyOrSystemTor();
+                } else {
+                    // register the Tor topics we always care about
+                    for (const topicKey in TorTopics) {
+                        const topic = TorTopics[topicKey];
+                        Services.obs.addObserver(this, topic);
+                        console.log(`TorConnect: observing topic '${topic}'`);
                     }
 
-                    Services.obs.notifyObservers(null, "torconnect:bootstrap-complete");
+                    if (TorProtocolService.torProcessStatus == TorProcessStatus.Running) {
+                        if (this.shouldQuickStart) {
+                            // Quickstart
+                            this.beginBootstrap();
+                        } else {
+                            // Configuring
+                            this.beginConfigure();
+                        }
+                    }
                 }
+
+                Services.obs.removeObserver(this, topic);
                 break;
+            }
+            /* Transition out of Initial if Tor daemon wasn't running yet in BrowserTopics.ProfileAfterChange */
+            case TorTopics.ProcessIsReady: {
+                if (this.state === TorConnectState.Initial)
+                {
+                    if (this.shouldQuickStart) {
+                        // Quickstart
+                        this.beginBootstrap();
+                    } else {
+                        // Configuring
+                        this.beginConfigure();
+                    }
+                }
+                break;
+            }
+            /* Updates our bootstrap status */
+            case TorTopics.BootstrapStatus: {
+                if (this._state != TorConnectState.Bootstrapping) {
+                    console.log(`TorConnect: observed ${TorTopics.BootstrapStatus} topic while in state TorConnectState.${this._state}`);
+                    break;
+                }
+
+                const obj = subject?.wrappedJSObject;
+                if (obj) {
+                    this._bootstrapProgress= obj.PROGRESS;
+                    this._bootstrapStatus = TorLauncherUtil.getLocalizedBootstrapStatus(obj, "TAG");
+
+                    console.log(`TorConnect: Bootstrapping ${this._bootstrapProgress}% complete (${this._bootstrapStatus})`);
+                    Services.obs.notifyObservers({
+                        progress: this._bootstrapProgress,
+                        status: this._bootstrapStatus,
+                        hasWarnings: this._logHasWarningOrError
+                    }, TorConnectTopics.BootstrapProgress);
+
+                    if (this._bootstrapProgress === 100) {
+                        this.bootstrapComplete();
+                    }
+                }
+                break;
+            }
+            /* Handle bootstrap error*/
+            case TorTopics.BootstrapError: {
+                const obj = subject?.wrappedJSObject;
+                TorProtocolService.torStopBootstrap();
+                this.onError(obj.message, obj.details);
+                break;
+            }
+            case TorTopics.LogHasWarnOrErr: {
+                this._logHasWarningOrError = true;
+                break;
+            }
             default:
                 // ignore
                 break;
             }
         },
 
-        shouldShowTorConnect : function() {
-            return TorProtocolService.shouldShowTorConnect();
+        /*
+        Various getters
+        */
+
+        get shouldShowTorConnect() {
+                   // TorBrowser must control the daemon
+            return (TorProtocolService.ownsTorDaemon &&
+                   // and we're not using the legacy launcher
+                   !TorLauncherUtil.useLegacyLauncher &&
+                   // legacy checks, TODO: maybe this should be in terms of our own state?
+                   (TorProtocolService.isNetworkDisabled() || !TorProtocolService.isBootstrapDone()));
+        },
+
+        get shouldQuickStart() {
+                   // quickstart must be enabled
+            return Services.prefs.getBoolPref(TorLauncherPrefs.quickstart, false) &&
+                   // and the previous bootstrap attempt must have succeeded
+                   !Services.prefs.getBoolPref(TorLauncherPrefs.prompt_at_startup, true);
+        },
+
+        get state() {
+            return this._state;
+        },
+
+        get bootstrapProgress() {
+            return this._bootstrapProgress;
+        },
+
+        get bootstrapStatus() {
+            return this._bootstrapStatus;
+        },
+
+        get errorMessage() {
+            return this._errorMessage;
+        },
+
+        get errorDetails() {
+            return this._errorDetails;
+        },
+
+        get logHasWarningOrError() {
+            return this._logHasWarningOrError;
+        },
+
+        /*
+        These functions tell TorConnect to transition states
+        */
+
+        legacyOrSystemTor: function() {
+            console.log("TorConnect: legacyOrSystemTor()");
+            this._changeState(TorConnectState.Disabled);
+        },
+
+        beginBootstrap: function() {
+            console.log("TorConnect: beginBootstrap()");
+            this._changeState(TorConnectState.Bootstrapping);
+        },
+
+        beginConfigure: function() {
+            console.log("TorConnect: beginConfigure()");
+            this._changeState(TorConnectState.Configuring);
+        },
+
+        autoConfigure: function() {
+            console.log("TorConnect: autoConfigure()");
+            // TODO: implement
+            throw Error("TorConnect: not implemented");
+        },
+
+        cancelAutoConfigure: function() {
+            console.log("TorConnect: cancelAutoConfigure()");
+            // TODO: implement
+            throw Error("TorConnect: not implemented");
+        },
+
+        cancelBootstrap: function() {
+            console.log("TorConnect: cancelBootstrap()");
+            this._changeState(TorConnectState.Configuring);
+        },
+
+        bootstrapComplete: function() {
+            console.log("TorConnect: bootstrapComplete()");
+            this._changeState(TorConnectState.Bootstrapped);
+        },
+
+        onError: function(message, details) {
+            console.log("TorConnect: onError()");
+            this._changeState(TorConnectState.Error, message, details, false);
+        },
+
+        onFatalError: function() {
+            console.log("TorConnect: onFatalError()");
+            // TODO: implement
+            throw Error("TorConnect: not implemented");
+        },
+
+        /*
+        Further external commands and helper methods
+        */
+        openTorPreferences: function() {
+            const win = BrowserWindowTracker.getTopWindow()
+            win.openTrustedLinkIn("about:preferences#tor", "tab");
+        },
+
+        copyTorLogs: function() {
+            // Copy tor log messages to the system clipboard.
+            const chSvc = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
+              Ci.nsIClipboardHelper
+            );
+            const countObj = { value: 0 };
+            chSvc.copyString(TorProtocolService.getLog(countObj));
+            const count = countObj.value;
+            return TorLauncherUtil.getFormattedLocalizedString(
+              "copiedNLogMessagesShort",
+              [count],
+              1
+            );
+        },
+
+        // called from browser.js on browser startup, passed in either the user's homepage(s)
+        // or uris passed via command-line
+        setURIsToLoad: function(uriVariant) {
+            // convert the object we get from browser.js
+            let uris = ((v) => {
+                if (v instanceof Ci.nsIArray) {
+                    // Transform the nsIArray of nsISupportsString's into a JS Array of
+                    // JS strings.
+                    return Array.from(
+                      v.enumerate(Ci.nsISupportsString),
+                      supportStr => supportStr.data
+                    );
+                } else if (v instanceof Ci.nsISupportsString) {
+                    return [v.data];
+                } else if (typeof v === "string") {
+                    return v.split("|");
+                }
+                // about:tor as safe fallback
+                return ["about:tor"];
+            })(uriVariant);
+
+            console.log(`TorConnect: will load after bootstrap => ${uris.join(", ")}`);
+            this._urisToLoad = uris;
         },
     };
     retval.init();
     return retval;
-})(); /* TorConnect */
\ No newline at end of file
+})(); /* TorConnect */
diff --git a/browser/modules/TorProtocolService.jsm b/browser/modules/TorProtocolService.jsm
index fc7f2c884aa2..e6c78b9a0eb1 100644
--- a/browser/modules/TorProtocolService.jsm
+++ b/browser/modules/TorProtocolService.jsm
@@ -2,26 +2,59 @@
 
 "use strict";
 
-var EXPORTED_SYMBOLS = ["TorProtocolService"];
+var EXPORTED_SYMBOLS = ["TorProtocolService", "TorProcessStatus"];
 
-const { TorLauncherUtil } = ChromeUtils.import(
-  "resource://torlauncher/modules/tl-util.jsm"
+const { Services } = ChromeUtils.import(
+    "resource://gre/modules/Services.jsm"
 );
 
-var TorProtocolService = {
-  _tlps: Cc["@torproject.org/torlauncher-protocol-service;1"].getService(
-    Ci.nsISupports
-  ).wrappedJSObject,
+// see tl-process.js
+const TorProcessStatus = Object.freeze({
+  Unknown: 0,
+  Starting: 1,
+  Running: 2,
+  Exited: 3,
+});
+
+/* Browser observer topis */
+const BrowserTopics = Object.freeze({
+    ProfileAfterChange: "profile-after-change",
+});
 
-  _tlproc: Cc["@torproject.org/torlauncher-process-service;1"].getService(
-    Ci.nsISupports
-  ).wrappedJSObject,
+var TorProtocolService = {
+  _TorLauncherUtil: function() {
+      let { TorLauncherUtil } = ChromeUtils.import(
+        "resource://torlauncher/modules/tl-util.jsm"
+      );
+      return TorLauncherUtil;
+    }(),
+  _TorLauncherProtocolService: null,
+  _TorProcessService: null,
 
   // maintain a map of tor settings set by Tor Browser so that we don't
   // repeatedly set the same key/values over and over
   // this map contains string keys to primitive or array values
   _settingsCache: new Map(),
 
+  init() {
+    Services.obs.addObserver(this, BrowserTopics.ProfileAfterChange);
+  },
+
+  observe(subject, topic, data) {
+    if (topic === BrowserTopics.ProfileAfterChange) {
+      // we have to delay init'ing this or else the crypto service inits too early without a profile
+      // which breaks the password manager
+      this._TorLauncherProtocolService = Cc["@torproject.org/torlauncher-protocol-service;1"].getService(
+        Ci.nsISupports
+      ).wrappedJSObject;
+      this._TorProcessService = Cc["@torproject.org/torlauncher-process-service;1"].getService(
+        Ci.nsISupports
+      ).wrappedJSObject,
+
+      Services.obs.removeObserver(this, topic);
+    }
+  },
+
   _typeof(aValue) {
     switch (typeof aValue) {
       case "boolean":
@@ -124,7 +157,7 @@ var TorProtocolService = {
       }
 
       let errorObject = {};
-      if (!this._tlps.TorSetConfWithReply(settingsObject, errorObject)) {
+      if (!this._TorLauncherProtocolService.TorSetConfWithReply(settingsObject, errorObject)) {
         throw new Error(errorObject.details);
       }
 
@@ -137,8 +170,8 @@ var TorProtocolService = {
 
   _readSetting(aSetting) {
     this._assertValidSettingKey(aSetting);
-    let reply = this._tlps.TorGetConf(aSetting);
-    if (this._tlps.TorCommandSucceeded(reply)) {
+    let reply = this._TorLauncherProtocolService.TorGetConf(aSetting);
+    if (this._TorLauncherProtocolService.TorCommandSucceeded(reply)) {
       return reply.lineArray;
     }
     throw new Error(reply.lineArray.join("\n"));
@@ -207,22 +240,22 @@ var TorProtocolService = {
 
   getLog(countObj) {
     countObj = countObj || { value: 0 };
-    let torLog = this._tlps.TorGetLog(countObj);
+    let torLog = this._TorLauncherProtocolService.TorGetLog(countObj);
     return torLog;
   },
 
   // true if we launched and control tor, false if using system tor
   get ownsTorDaemon() {
-    return TorLauncherUtil.shouldStartAndOwnTor;
+    return this._TorLauncherUtil.shouldStartAndOwnTor;
   },
 
   // Assumes `ownsTorDaemon` is true
   isNetworkDisabled() {
-    const reply = TorProtocolService._tlps.TorGetConfBool(
+    const reply = TorProtocolService._TorLauncherProtocolService.TorGetConfBool(
       "DisableNetwork",
       true
     );
-    if (TorProtocolService._tlps.TorCommandSucceeded(reply)) {
+    if (TorProtocolService._TorLauncherProtocolService.TorCommandSucceeded(reply)) {
       return reply.retVal;
     }
     return true;
@@ -232,22 +265,22 @@ var TorProtocolService = {
     let settings = {};
     settings.DisableNetwork = false;
     let errorObject = {};
-    if (!this._tlps.TorSetConfWithReply(settings, errorObject)) {
+    if (!this._TorLauncherProtocolService.TorSetConfWithReply(settings, errorObject)) {
       throw new Error(errorObject.details);
     }
   },
 
   sendCommand(cmd) {
-    return this._tlps.TorSendCommand(cmd);
+    return this._TorLauncherProtocolService.TorSendCommand(cmd);
   },
 
   retrieveBootstrapStatus() {
-    return this._tlps.TorRetrieveBootstrapStatus();
+    return this._TorLauncherProtocolService.TorRetrieveBootstrapStatus();
   },
 
   _GetSaveSettingsErrorMessage(aDetails) {
     try {
-      return TorLauncherUtil.getSaveSettingsErrorMessage(aDetails);
+      return this._TorLauncherUtil.getSaveSettingsErrorMessage(aDetails);
     } catch (e) {
       console.log("GetSaveSettingsErrorMessage error", e);
       return "Unexpected Error";
@@ -258,7 +291,7 @@ var TorProtocolService = {
     let result = false;
     const error = {};
     try {
-      result = this._tlps.TorSetConfWithReply(settings, error);
+      result = this._TorLauncherProtocolService.TorSetConfWithReply(settings, error);
     } catch (e) {
       console.log("TorSetConfWithReply error", e);
       error.details = this._GetSaveSettingsErrorMessage(e.message);
@@ -267,23 +300,15 @@ var TorProtocolService = {
   },
 
   isBootstrapDone() {
-    return this._tlproc.mIsBootstrapDone;
+    return this._TorProcessService.mIsBootstrapDone;
   },
 
   clearBootstrapError() {
-    return this._tlproc.TorClearBootstrapError();
-  },
-
-  shouldShowTorConnect() {
-    return (
-      this.ownsTorDaemon &&
-      !TorLauncherUtil.useLegacyLauncher &&
-      (this.isNetworkDisabled() || !this.isBootstrapDone())
-    );
+    return this._TorProcessService.TorClearBootstrapError();
   },
 
   torBootstrapErrorOccurred() {
-    return this._tlproc.TorBootstrapErrorOccurred;
+    return this._TorProcessService.TorBootstrapErrorOccurred;
   },
 
   // Resolves to null if ok, or an error otherwise
@@ -306,7 +331,7 @@ var TorProtocolService = {
   },
 
   torLogHasWarnOrErr() {
-    return this._tlps.TorLogHasWarnOrErr;
+    return this._TorLauncherProtocolService.TorLogHasWarnOrErr;
   },
 
   torStopBootstrap() {
@@ -327,4 +352,12 @@ var TorProtocolService = {
     }
     this.retrieveBootstrapStatus();
   },
+
+  get torProcessStatus() {
+    if (this._TorProcessService) {
+      return this._TorProcessService.TorProcessStatus;
+    }
+    return TorProcessStatus.Unknown;
+  },
 };
+TorProtocolService.init();
\ No newline at end of file
diff --git a/toolkit/components/processsingleton/MainProcessSingleton.jsm b/toolkit/components/processsingleton/MainProcessSingleton.jsm
index db1e2dc8f568..ea9288dccbb3 100644
--- a/toolkit/components/processsingleton/MainProcessSingleton.jsm
+++ b/toolkit/components/processsingleton/MainProcessSingleton.jsm
@@ -24,6 +24,11 @@ MainProcessSingleton.prototype = {
           null
         );
 
+        ChromeUtils.import(
+          "resource:///modules/TorConnect.jsm",
+          null
+        );
+
         // Load this script early so that console.* is initialized
         // before other frame scripts.
         Services.mm.loadFrameScript(
diff --git a/toolkit/modules/RemotePageAccessManager.jsm b/toolkit/modules/RemotePageAccessManager.jsm
index 0927391c2ba7..54230e1175ec 100644
--- a/toolkit/modules/RemotePageAccessManager.jsm
+++ b/toolkit/modules/RemotePageAccessManager.jsm
@@ -181,28 +181,18 @@ let RemotePageAccessManager = {
       RPMRemoveMessageListener: ["*"],
     },
     "about:torconnect": {
-      RPMAddMessageListener: ["*"],
+      RPMAddMessageListener: [
+        "torconnect:state-change",
+      ],
       RPMSendAsyncMessage: [
-        "OpenTorAdvancedPreferences",
-        "TorRetrieveBootstrapStatus",
-        "TorStopBootstrap",
+        "torconnect:open-tor-preferences",
+        "torconnect:begin-bootstrap",
+        "torconnect:cancel-bootstrap",
+        "torconnect:set-quickstart",
       ],
       RPMSendQuery: [
-        "GetDirection",
-        "GetLocalizedBootstrapStatus",
-        "GetTorStrings",
-        "TorBootstrapErrorOccurred",
-        "TorConnect",
-        "TorCopyLog",
-        "TorIsNetworkDisabled",
-        "TorLogHasWarnOrErr",
-      ],
-      RPMGetBoolPref: [
-        "extensions.torlauncher.quickstart",
-        "extensions.torlauncher.prompt_at_startup",
-      ],
-      RPMSetBoolPref: [
-        "extensions.torlauncher.quickstart",
+        "torconnect:get-init-args",
+        "torconnect:copy-tor-logs",
       ],
     },
   },



_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits