[tbb-commits] [Git][tpo/applications/tor-browser][tor-browser-115.1.0esr-13.0-1] 8 commits: fixup! Bug 40933: Add tor-launcher functionality

richard (@richard) git at gitlab.torproject.org
Mon Aug 7 19:56:04 UTC 2023



richard pushed to branch tor-browser-115.1.0esr-13.0-1 at The Tor Project / Applications / Tor Browser


Commits:
d339153b by Pier Angelo Vendrame at 2023-08-07T18:36:05+02:00
fixup! Bug 40933: Add tor-launcher functionality

Use the new functions whenever possible, adjust some property names and
other minor fixes.

- - - - -
e8d3c0b8 by Pier Angelo Vendrame at 2023-08-07T18:36:14+02:00
fixup! Bug 40597: Implement TorSettings module

Changes needed for the new control port implementation.

Also, moved to ES modules and done some refactors on Moat.

- - - - -
c1b05a65 by Pier Angelo Vendrame at 2023-08-07T18:36:15+02:00
fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection

Changes for the new control port implementation and following
ESMification.

- - - - -
d23cb300 by Pier Angelo Vendrame at 2023-08-07T18:36:15+02:00
fixup! Bug 30237: Add v3 onion services client authentication prompt

Changes for the new control port implementation.

- - - - -
c801d620 by Pier Angelo Vendrame at 2023-08-07T18:36:16+02:00
fixup! Bug 7494: Create local home page for TBB.

Use the TorProvider in TorCheckService

- - - - -
fa105e4f by Pier Angelo Vendrame at 2023-08-07T18:36:16+02:00
fixup! Bug 3455: Add DomainIsolator, for isolating circuit by domain.

Remove TorMonitorService and TorProtocolService references.

- - - - -
cd92f30e by Pier Angelo Vendrame at 2023-08-07T18:36:17+02:00
fixup! Bug 41668: Tweaks to the Base Browser updater for Tor Browser

Removed TorMonitorService reference

- - - - -
384dff97 by Pier Angelo Vendrame at 2023-08-07T18:36:17+02:00
fixup! Bug 40933: Add tor-launcher functionality

Remove the final references to TorMonitorService and TorProtocolService.

- - - - -


24 changed files:

- browser/components/abouttor/TorCheckService.sys.mjs
- browser/components/onionservices/content/authPrompt.js
- browser/components/onionservices/content/savedKeysDialog.js
- browser/components/torpreferences/content/builtinBridgeDialog.jsm
- browser/components/torpreferences/content/connectionPane.js
- browser/components/torpreferences/content/connectionSettingsDialog.jsm
- browser/components/torpreferences/content/provideBridgeDialog.jsm
- browser/components/torpreferences/content/requestBridgeDialog.jsm
- browser/components/torpreferences/content/torLogDialog.jsm
- browser/modules/BridgeDB.jsm → browser/modules/BridgeDB.sys.mjs
- browser/modules/Moat.jsm → browser/modules/Moat.sys.mjs
- browser/modules/TorConnect.jsm → browser/modules/TorConnect.sys.mjs
- browser/modules/TorSettings.jsm → browser/modules/TorSettings.sys.mjs
- browser/modules/moz.build
- toolkit/components/tor-launcher/TorBootstrapRequest.sys.mjs
- toolkit/components/tor-launcher/TorControlPort.sys.mjs
- toolkit/components/tor-launcher/TorDomainIsolator.sys.mjs
- − toolkit/components/tor-launcher/TorMonitorService.sys.mjs
- toolkit/components/tor-launcher/TorParsers.sys.mjs
- toolkit/components/tor-launcher/TorProtocolService.sys.mjs → toolkit/components/tor-launcher/TorProvider.sys.mjs
- toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs
- toolkit/components/tor-launcher/TorStartupService.sys.mjs
- toolkit/components/tor-launcher/moz.build
- toolkit/mozapps/update/UpdateService.sys.mjs


Changes:

=====================================
browser/components/abouttor/TorCheckService.sys.mjs
=====================================
@@ -11,14 +11,9 @@ const lazy = {};
 
 ChromeUtils.defineESModuleGetters(lazy, {
   ConsoleAPI: "resource://gre/modules/Console.sys.mjs",
+  TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
 });
 
-ChromeUtils.defineModuleGetter(
-  lazy,
-  "TorProtocolService",
-  "resource://gre/modules/TorProtocolService.jsm"
-);
-
 export const TorCheckService = {
   kCheckNotInitiated: 0, // Possible values for status.
   kCheckSuccessful: 1,
@@ -109,7 +104,7 @@ export const TorCheckService = {
 
     let listeners;
     try {
-      listeners = await lazy.TorProtocolService.getSocksListeners();
+      listeners = await lazy.TorProviderBuilder.build().getSocksListeners();
     } catch (e) {
       this._logger.error("Failed to get the SOCKS listerner addresses.", e);
       return false;


=====================================
browser/components/onionservices/content/authPrompt.js
=====================================
@@ -4,10 +4,13 @@
 
 /* globals gBrowser, PopupNotifications, Services, XPCOMUtils */
 
+ChromeUtils.defineESModuleGetters(this, {
+  TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
+});
+
 XPCOMUtils.defineLazyModuleGetters(this, {
   OnionAuthUtil: "chrome://browser/content/onionservices/authUtil.jsm",
   CommonUtils: "resource://services-common/utils.js",
-  TorProtocolService: "resource://gre/modules/TorProtocolService.jsm",
   TorStrings: "resource:///modules/TorStrings.jsm",
 });
 
@@ -203,7 +206,8 @@ const OnionAuthPrompt = (function () {
 
         let checkboxElem = this._getCheckboxElement();
         let isPermanent = checkboxElem && checkboxElem.checked;
-        TorProtocolService.onionAuthAdd(onionServiceId, base64key, isPermanent)
+        TorProviderBuilder.build()
+          .onionAuthAdd(onionServiceId, base64key, isPermanent)
           .then(aResponse => {
             // Success! Reload the page.
             this._browser.sendMessageToActor(


=====================================
browser/components/onionservices/content/savedKeysDialog.js
=====================================
@@ -8,11 +8,9 @@ ChromeUtils.defineModuleGetter(
   "resource:///modules/TorStrings.jsm"
 );
 
-ChromeUtils.defineModuleGetter(
-  this,
-  "TorProtocolService",
-  "resource://gre/modules/TorProtocolService.jsm"
-);
+ChromeUtils.defineESModuleGetters(this, {
+  TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
+});
 
 var gOnionServicesSavedKeysDialog = {
   selector: {
@@ -54,6 +52,7 @@ var gOnionServicesSavedKeysDialog = {
           await this._deleteOneKey(indexesToDelete[i]);
         }
       } catch (e) {
+        console.error("Removing a saved key failed", e);
         if (e.torMessage) {
           this._showError(e.torMessage);
         } else {
@@ -125,22 +124,16 @@ var gOnionServicesSavedKeysDialog = {
     try {
       this._tree.view = this;
 
-      const keyInfoList = await TorProtocolService.onionAuthViewKeys();
+      const keyInfoList = await TorProviderBuilder.build().onionAuthViewKeys();
       if (keyInfoList) {
         // Filter out temporary keys.
-        this._keyInfoList = keyInfoList.filter(aKeyInfo => {
-          if (!aKeyInfo.Flags) {
-            return false;
-          }
-
-          const flags = aKeyInfo.Flags.split(",");
-          return flags.includes("Permanent");
-        });
-
+        this._keyInfoList = keyInfoList.filter(aKeyInfo =>
+          aKeyInfo.flags?.includes("Permanent")
+        );
         // Sort by the .onion address.
         this._keyInfoList.sort((aObj1, aObj2) => {
-          const hsAddr1 = aObj1.hsAddress.toLowerCase();
-          const hsAddr2 = aObj2.hsAddress.toLowerCase();
+          const hsAddr1 = aObj1.address.toLowerCase();
+          const hsAddr2 = aObj2.address.toLowerCase();
           if (hsAddr1 < hsAddr2) {
             return -1;
           }
@@ -164,7 +157,7 @@ var gOnionServicesSavedKeysDialog = {
   // This method may throw; callers should catch errors.
   async _deleteOneKey(aIndex) {
     const keyInfoObj = this._keyInfoList[aIndex];
-    await TorProtocolService.onionAuthRemove(keyInfoObj.hsAddress);
+    await TorProviderBuilder.build().onionAuthRemove(keyInfoObj.address);
     this._tree.view.selection.clearRange(aIndex, aIndex);
     this._keyInfoList.splice(aIndex, 1);
     this._tree.rowCountChanged(aIndex + 1, -1);
@@ -193,26 +186,20 @@ var gOnionServicesSavedKeysDialog = {
 
   // XUL tree widget view implementation.
   get rowCount() {
-    return this._keyInfoList ? this._keyInfoList.length : 0;
+    return this._keyInfoList?.length ?? 0;
   },
 
   getCellText(aRow, aCol) {
-    let val = "";
     if (this._keyInfoList && aRow < this._keyInfoList.length) {
       const keyInfo = this._keyInfoList[aRow];
       if (aCol.id.endsWith("-siteCol")) {
-        val = keyInfo.hsAddress;
+        return keyInfo.address;
       } else if (aCol.id.endsWith("-keyCol")) {
-        val = keyInfo.typeAndKey;
-        // Omit keyType because it is always "x25519".
-        const idx = val.indexOf(":");
-        if (idx > 0) {
-          val = val.substring(idx + 1);
-        }
+        // keyType is always "x25519", so do not show it.
+        return keyInfo.keyBlob;
       }
     }
-
-    return val;
+    return "";
   },
 
   isSeparator(index) {


=====================================
browser/components/torpreferences/content/builtinBridgeDialog.jsm
=====================================
@@ -7,10 +7,10 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
 
 const { TorSettings, TorBridgeSource, TorBuiltinBridgeTypes } =
-  ChromeUtils.import("resource:///modules/TorSettings.jsm");
+  ChromeUtils.importESModule("resource:///modules/TorSettings.sys.mjs");
 
-const { TorConnect, TorConnectTopics } = ChromeUtils.import(
-  "resource:///modules/TorConnect.jsm"
+const { TorConnect, TorConnectTopics } = ChromeUtils.importESModule(
+  "resource:///modules/TorConnect.sys.mjs"
 );
 
 class BuiltinBridgeDialog {


=====================================
browser/components/torpreferences/content/connectionPane.js
=====================================
@@ -12,20 +12,17 @@ const { setTimeout, clearTimeout } = ChromeUtils.import(
 );
 
 const { TorSettings, TorSettingsTopics, TorSettingsData, TorBridgeSource } =
-  ChromeUtils.import("resource:///modules/TorSettings.jsm");
+  ChromeUtils.importESModule("resource:///modules/TorSettings.sys.mjs");
 
 const { TorParsers } = ChromeUtils.importESModule(
   "resource://gre/modules/TorParsers.sys.mjs"
 );
-const { TorProtocolService } = ChromeUtils.importESModule(
-  "resource://gre/modules/TorProtocolService.sys.mjs"
-);
-const { TorMonitorService, TorMonitorTopics } = ChromeUtils.import(
-  "resource://gre/modules/TorMonitorService.jsm"
+const { TorProviderBuilder, TorProviderTopics } = ChromeUtils.importESModule(
+  "resource://gre/modules/TorProviderBuilder.sys.mjs"
 );
 
 const { TorConnect, TorConnectTopics, TorConnectState, TorCensorshipLevel } =
-  ChromeUtils.import("resource:///modules/TorConnect.jsm");
+  ChromeUtils.importESModule("resource:///modules/TorConnect.sys.mjs");
 
 const { TorLogDialog } = ChromeUtils.import(
   "chrome://browser/content/torpreferences/torLogDialog.jsm"
@@ -51,7 +48,9 @@ const { ProvideBridgeDialog } = ChromeUtils.import(
   "chrome://browser/content/torpreferences/provideBridgeDialog.jsm"
 );
 
-const { MoatRPC } = ChromeUtils.import("resource:///modules/Moat.jsm");
+const { MoatRPC } = ChromeUtils.importESModule(
+  "resource:///modules/Moat.sys.mjs"
+);
 
 const { QRCode } = ChromeUtils.import("resource://gre/modules/QRCode.jsm");
 
@@ -156,7 +155,7 @@ const gConnectionPane = (function () {
     _populateXUL() {
       // saves tor settings to disk when navigate away from about:preferences
       window.addEventListener("blur", val => {
-        TorProtocolService.flushSettings();
+        TorProviderBuilder.build().flushSettings();
       });
 
       document
@@ -751,7 +750,7 @@ const gConnectionPane = (function () {
         // TODO: We could make sure TorSettings is in sync by monitoring also
         // changes of settings. At that point, we could query it, instead of
         // doing a query over the control port.
-        const bridge = TorMonitorService.currentBridge;
+        const bridge = TorProviderBuilder.build().currentBridge;
         if (bridge?.fingerprint !== this._currentBridgeId) {
           this._currentBridgeId = bridge?.fingerprint ?? null;
           this._updateConnectedBridges();
@@ -850,7 +849,7 @@ const gConnectionPane = (function () {
       });
 
       Services.obs.addObserver(this, TorConnectTopics.StateChange);
-      Services.obs.addObserver(this, TorMonitorTopics.BridgeChanged);
+      Services.obs.addObserver(this, TorProviderTopics.BridgeChanged);
       Services.obs.addObserver(this, "intl:app-locales-changed");
     },
 
@@ -875,7 +874,7 @@ const gConnectionPane = (function () {
       // unregister our observer topics
       Services.obs.removeObserver(this, TorSettingsTopics.SettingChanged);
       Services.obs.removeObserver(this, TorConnectTopics.StateChange);
-      Services.obs.removeObserver(this, TorMonitorTopics.BridgeChanged);
+      Services.obs.removeObserver(this, TorProviderTopics.BridgeChanged);
       Services.obs.removeObserver(this, "intl:app-locales-changed");
     },
 
@@ -907,7 +906,7 @@ const gConnectionPane = (function () {
           this.onStateChange();
           break;
         }
-        case TorMonitorTopics.BridgeChanged: {
+        case TorProviderTopics.BridgeChanged: {
           if (data?.fingerprint !== this._currentBridgeId) {
             this._checkConnectedBridge();
           }


=====================================
browser/components/torpreferences/content/connectionSettingsDialog.jsm
=====================================
@@ -2,8 +2,8 @@
 
 var EXPORTED_SYMBOLS = ["ConnectionSettingsDialog"];
 
-const { TorSettings, TorProxyType } = ChromeUtils.import(
-  "resource:///modules/TorSettings.jsm"
+const { TorSettings, TorProxyType } = ChromeUtils.importESModule(
+  "resource:///modules/TorSettings.sys.mjs"
 );
 
 const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");


=====================================
browser/components/torpreferences/content/provideBridgeDialog.jsm
=====================================
@@ -6,12 +6,12 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
 
-const { TorSettings, TorBridgeSource } = ChromeUtils.import(
-  "resource:///modules/TorSettings.jsm"
+const { TorSettings, TorBridgeSource } = ChromeUtils.importESModule(
+  "resource:///modules/TorSettings.sys.mjs"
 );
 
-const { TorConnect, TorConnectTopics } = ChromeUtils.import(
-  "resource:///modules/TorConnect.jsm"
+const { TorConnect, TorConnectTopics } = ChromeUtils.importESModule(
+  "resource:///modules/TorConnect.sys.mjs"
 );
 
 class ProvideBridgeDialog {


=====================================
browser/components/torpreferences/content/requestBridgeDialog.jsm
=====================================
@@ -4,11 +4,13 @@ var EXPORTED_SYMBOLS = ["RequestBridgeDialog"];
 
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-const { BridgeDB } = ChromeUtils.import("resource:///modules/BridgeDB.jsm");
+const { BridgeDB } = ChromeUtils.importESModule(
+  "resource:///modules/BridgeDB.sys.mjs"
+);
 const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
 
-const { TorConnect, TorConnectTopics } = ChromeUtils.import(
-  "resource:///modules/TorConnect.jsm"
+const { TorConnect, TorConnectTopics } = ChromeUtils.importESModule(
+  "resource:///modules/TorConnect.sys.mjs"
 );
 
 class RequestBridgeDialog {


=====================================
browser/components/torpreferences/content/torLogDialog.jsm
=====================================
@@ -2,12 +2,12 @@
 
 var EXPORTED_SYMBOLS = ["TorLogDialog"];
 
-const { setTimeout, clearTimeout } = ChromeUtils.import(
-  "resource://gre/modules/Timer.jsm"
+const { setTimeout, clearTimeout } = ChromeUtils.importESModule(
+  "resource://gre/modules/Timer.sys.mjs"
 );
 
-const { TorMonitorService } = ChromeUtils.import(
-  "resource://gre/modules/TorMonitorService.jsm"
+const { TorProviderBuilder } = ChromeUtils.importESModule(
+  "resource://gre/modules/TorProviderBuilder.sys.mjs"
 );
 const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
 
@@ -56,7 +56,7 @@ class TorLogDialog {
       }, RESTORE_TIME);
     });
 
-    this._logTextarea.value = TorMonitorService.getLog();
+    this._logTextarea.value = TorProviderBuilder.build().getLog();
   }
 
   init(window, aDialog) {


=====================================
browser/modules/BridgeDB.jsm → browser/modules/BridgeDB.sys.mjs
=====================================
@@ -1,10 +1,14 @@
-"use strict";
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-var EXPORTED_SYMBOLS = ["BridgeDB"];
+const lazy = {};
 
-const { MoatRPC } = ChromeUtils.import("resource:///modules/Moat.jsm");
+ChromeUtils.defineESModuleGetters(lazy, {
+  MoatRPC: "resource:///modules/Moat.sys.mjs",
+});
 
-var BridgeDB = {
+export var BridgeDB = {
   _moatRPC: null,
   _challenge: null,
   _image: null,
@@ -20,7 +24,7 @@ var BridgeDB = {
 
   async submitCaptchaGuess(solution) {
     if (!this._moatRPC) {
-      this._moatRPC = new MoatRPC();
+      this._moatRPC = new lazy.MoatRPC();
       await this._moatRPC.init();
     }
 
@@ -37,7 +41,7 @@ var BridgeDB = {
   async requestNewCaptchaImage() {
     try {
       if (!this._moatRPC) {
-        this._moatRPC = new MoatRPC();
+        this._moatRPC = new lazy.MoatRPC();
         await this._moatRPC.init();
       }
 


=====================================
browser/modules/Moat.jsm → browser/modules/Moat.sys.mjs
=====================================
@@ -1,24 +1,19 @@
-"use strict";
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-var EXPORTED_SYMBOLS = ["MoatRPC"];
+import {
+  TorSettings,
+  TorBridgeSource,
+} from "resource:///modules/TorSettings.sys.mjs";
 
-const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const lazy = {};
 
-const { Subprocess } = ChromeUtils.import(
-  "resource://gre/modules/Subprocess.jsm"
-);
-
-const { TorLauncherUtil } = ChromeUtils.import(
-  "resource://gre/modules/TorLauncherUtil.jsm"
-);
-
-const { TorProtocolService } = ChromeUtils.import(
-  "resource://gre/modules/TorProtocolService.jsm"
-);
-
-const { TorSettings, TorBridgeSource } = ChromeUtils.import(
-  "resource:///modules/TorSettings.jsm"
-);
+ChromeUtils.defineESModuleGetters(lazy, {
+  Subprocess: "resource://gre/modules/Subprocess.sys.mjs",
+  TorLauncherUtil: "resource://gre/modules/TorLauncherUtil.sys.mjs",
+  TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
+});
 
 const TorLauncherPrefs = Object.freeze({
   bridgedb_front: "extensions.torlauncher.bridgedb_front",
@@ -26,73 +21,54 @@ const TorLauncherPrefs = Object.freeze({
   moat_service: "extensions.torlauncher.moat_service",
 });
 
-// Config keys used to query tor daemon properties
-const TorConfigKeys = Object.freeze({
-  clientTransportPlugin: "ClientTransportPlugin",
-});
-
 //
 // Launches and controls the PT process lifetime
 //
 class MeekTransport {
-  constructor() {
-    this._inited = false;
-    this._meekClientProcess = null;
-    this._meekProxyType = null;
-    this._meekProxyAddress = null;
-    this._meekProxyPort = 0;
-    this._meekProxyUsername = null;
-    this._meekProxyPassword = null;
-  }
+  // These members are used by consumers to setup the proxy to do requests over
+  // meek. They are passed to newProxyInfoWithAuth.
+  proxyType = null;
+  proxyAddress = null;
+  proxyPort = 0;
+  proxyUsername = null;
+  proxyPassword = null;
+
+  #inited = false;
+  #meekClientProcess = null;
 
   // launches the meekprocess
   async init() {
     // ensure we haven't already init'd
-    if (this._inited) {
+    if (this.#inited) {
       throw new Error("MeekTransport: Already initialized");
     }
 
-    // cleanup function for killing orphaned pt process
-    let onException = () => {};
     try {
       // figure out which pluggable transport to use
       const supportedTransports = ["meek", "meek_lite"];
-      let transportPlugins = await TorProtocolService.readStringArraySetting(
-        TorConfigKeys.clientTransportPlugin
+      const proxy = (
+        await lazy.TorProviderBuilder.build().getPluggableTransports()
+      ).find(
+        pt =>
+          pt.type === "exec" &&
+          supportedTransports.some(t => pt.transports.includes(t))
       );
+      if (!proxy) {
+        throw new Error("No supported transport found.");
+      }
 
-      let { meekTransport, meekClientPath, meekClientArgs } = (() => {
-        for (const line of transportPlugins) {
-          let tokens = line.split(" ");
-          if (tokens.length > 2 && tokens[1] == "exec") {
-            let transportArray = tokens[0].split(",").map(aStr => aStr.trim());
-            let transport = transportArray.find(aTransport =>
-              supportedTransports.includes(aTransport)
-            );
-
-            if (transport != undefined) {
-              return {
-                meekTransport: transport,
-                meekClientPath: tokens[2],
-                meekClientArgs: tokens.slice(3),
-              };
-            }
-          }
-        }
-
-        return {
-          meekTransport: null,
-          meekClientPath: null,
-          meekClientArgs: null,
-        };
-      })();
-
+      const meekTransport = proxy.transports.find(t =>
+        supportedTransports.includes(t)
+      );
       // Convert meek client path to absolute path if necessary
-      let meekWorkDir = TorLauncherUtil.getTorFile("pt-startup-dir", false);
-      if (TorLauncherUtil.isPathRelative(meekClientPath)) {
-        let meekPath = meekWorkDir.clone();
-        meekPath.appendRelativePath(meekClientPath);
-        meekClientPath = meekPath.path;
+      const meekWorkDir = lazy.TorLauncherUtil.getTorFile(
+        "pt-startup-dir",
+        false
+      );
+      if (lazy.TorLauncherUtil.isPathRelative(proxy.pathToBinary)) {
+        const meekPath = meekWorkDir.clone();
+        meekPath.appendRelativePath(proxy.pathToBinary);
+        proxy.pathToBinary = meekPath.path;
       }
 
       // Construct the per-connection arguments.
@@ -105,16 +81,13 @@ class MeekTransport {
       //   First the "<Key>=<Value>" formatted arguments MUST be escaped,
       //   such that all backslash, equal sign, and semicolon characters
       //   are escaped with a backslash.
-      let escapeArgValue = aValue => {
-        if (!aValue) {
-          return "";
-        }
-
-        let rv = aValue.replace(/\\/g, "\\\\");
-        rv = rv.replace(/=/g, "\\=");
-        rv = rv.replace(/;/g, "\\;");
-        return rv;
-      };
+      const escapeArgValue = aValue =>
+        aValue
+          ? aValue
+              .replaceAll("\\", "\\\\")
+              .replaceAll("=", "\\=")
+              .replaceAll(";", "\\;")
+          : "";
 
       if (meekReflector) {
         meekClientEscapedArgs += "url=";
@@ -132,10 +105,10 @@ class MeekTransport {
       }
 
       // Setup env and start meek process
-      let ptStateDir = TorLauncherUtil.getTorFile("tordatadir", false);
+      const ptStateDir = lazy.TorLauncherUtil.getTorFile("tordatadir", false);
       ptStateDir.append("pt_state"); // Match what tor uses.
 
-      let envAdditions = {
+      const envAdditions = {
         TOR_PT_MANAGED_TRANSPORT_VER: "1",
         TOR_PT_STATE_LOCATION: ptStateDir.path,
         TOR_PT_EXIT_ON_STDIN_CLOSE: "1",
@@ -145,9 +118,9 @@ class MeekTransport {
         envAdditions.TOR_PT_PROXY = TorSettings.proxy.uri;
       }
 
-      let opts = {
-        command: meekClientPath,
-        arguments: meekClientArgs,
+      const opts = {
+        command: proxy.pathToBinary,
+        arguments: proxy.options.split(/s+/),
         workdir: meekWorkDir.path,
         environmentAppend: true,
         environment: envAdditions,
@@ -155,27 +128,23 @@ class MeekTransport {
       };
 
       // Launch meek client
-      let meekClientProcess = await Subprocess.call(opts);
-      // kill our process if exception is thrown
-      onException = () => {
-        meekClientProcess.kill();
-      };
+      this.#meekClientProcess = await lazy.Subprocess.call(opts);
 
       // Callback chain for reading stderr
-      let stderrLogger = async () => {
-        if (this._meekClientProcess) {
-          let errString = await this._meekClientProcess.stderr.readString();
-          console.log(`MeekTransport: stderr => ${errString}`);
-          await stderrLogger();
+      const stderrLogger = async () => {
+        while (this.#meekClientProcess) {
+          const errString = await this.#meekClientProcess.stderr.readString();
+          if (errString) {
+            console.log(`MeekTransport: stderr => ${errString}`);
+          }
         }
       };
       stderrLogger();
 
       // Read pt's stdout until terminal (CMETHODS DONE) is reached
       // returns array of lines for parsing
-      let getInitLines = async (stdout = "") => {
-        let string = await meekClientProcess.stdout.readString();
-        stdout += string;
+      const getInitLines = async (stdout = "") => {
+        stdout += await this.#meekClientProcess.stdout.readString();
 
         // look for the final message
         const CMETHODS_DONE = "CMETHODS DONE";
@@ -188,20 +157,16 @@ class MeekTransport {
       };
 
       // read our lines from pt's stdout
-      let meekInitLines = await getInitLines();
+      const meekInitLines = await getInitLines();
       // tokenize our pt lines
-      let meekInitTokens = meekInitLines.map(line => {
-        let tokens = line.split(" ");
+      const meekInitTokens = meekInitLines.map(line => {
+        const tokens = line.split(" ");
         return {
           keyword: tokens[0],
           args: tokens.slice(1),
         };
       });
 
-      let meekProxyType = null;
-      let meekProxyAddr = null;
-      let meekProxyPort = 0;
-
       // parse our pt tokens
       for (const { keyword, args } of meekInitTokens) {
         const argsJoined = args.join(" ");
@@ -251,9 +216,9 @@ class MeekTransport {
             }
 
             // convert proxy type to strings used by protocol-proxy-servce
-            meekProxyType = proxyType === "socks5" ? "socks" : "socks4";
-            meekProxyAddr = addr;
-            meekProxyPort = port;
+            this.proxyType = proxyType === "socks5" ? "socks" : "socks4";
+            this.proxyAddress = addr;
+            this.proxyPort = port;
 
             break;
           }
@@ -278,49 +243,47 @@ class MeekTransport {
         }
       }
 
-      this._meekClientProcess = meekClientProcess;
       // register callback to cleanup on process exit
-      this._meekClientProcess.wait().then(exitObj => {
-        this._meekClientProcess = null;
+      this.#meekClientProcess.wait().then(exitObj => {
+        this.#meekClientProcess = null;
         this.uninit();
       });
 
-      this._meekProxyType = meekProxyType;
-      this._meekProxyAddress = meekProxyAddr;
-      this._meekProxyPort = meekProxyPort;
-
       // socks5
-      if (meekProxyType === "socks") {
+      if (this.proxyType === "socks") {
         if (meekClientEscapedArgs.length <= 255) {
-          this._meekProxyUsername = meekClientEscapedArgs;
-          this._meekProxyPassword = "\x00";
+          this.proxyUsername = meekClientEscapedArgs;
+          this.proxyPassword = "\x00";
         } else {
-          this._meekProxyUsername = meekClientEscapedArgs.substring(0, 255);
-          this._meekProxyPassword = meekClientEscapedArgs.substring(255);
+          this.proxyUsername = meekClientEscapedArgs.substring(0, 255);
+          this.proxyPassword = meekClientEscapedArgs.substring(255);
         }
         // socks4
       } else {
-        this._meekProxyUsername = meekClientEscapedArgs;
-        this._meekProxyPassword = undefined;
+        this.proxyUsername = meekClientEscapedArgs;
+        this.proxyPassword = undefined;
       }
 
-      this._inited = true;
+      this.#inited = true;
     } catch (ex) {
-      onException();
+      if (this.#meekClientProcess) {
+        this.#meekClientProcess.kill();
+        this.#meekClientProcess = null;
+      }
       throw ex;
     }
   }
 
   async uninit() {
-    this._inited = false;
-
-    await this._meekClientProcess?.kill();
-    this._meekClientProcess = null;
-    this._meekProxyType = null;
-    this._meekProxyAddress = null;
-    this._meekProxyPort = 0;
-    this._meekProxyUsername = null;
-    this._meekProxyPassword = null;
+    this.#inited = false;
+
+    await this.#meekClientProcess?.kill();
+    this.#meekClientProcess = null;
+    this.proxyType = null;
+    this.proxyAddress = null;
+    this.proxyPort = 0;
+    this.proxyUsername = null;
+    this.proxyPassword = null;
   }
 }
 
@@ -328,21 +291,25 @@ class MeekTransport {
 // Callback object with a cached promise for the returned Moat data
 //
 class MoatResponseListener {
+  #response = "";
+  #responsePromise;
+  #resolve;
+  #reject;
   constructor() {
-    this._response = "";
+    this.#response = "";
     // we need this promise here because await nsIHttpChannel::asyncOpen does
     // not return only once the request is complete, it seems to return
     // after it begins, so we have to get the result from this listener object.
     // This promise is only resolved once onStopRequest is called
-    this._responsePromise = new Promise((resolve, reject) => {
-      this._resolve = resolve;
-      this._reject = reject;
+    this.#responsePromise = new Promise((resolve, reject) => {
+      this.#resolve = resolve;
+      this.#reject = reject;
     });
   }
 
   // callers wait on this for final response
   response() {
-    return this._responsePromise;
+    return this.#responsePromise;
   }
 
   // noop
@@ -352,16 +319,17 @@ class MoatResponseListener {
   onStopRequest(request, status) {
     try {
       if (!Components.isSuccessCode(status)) {
-        const errorMessage = TorLauncherUtil.getLocalizedStringForError(status);
-        this._reject(new Error(errorMessage));
+        const errorMessage =
+          lazy.TorLauncherUtil.getLocalizedStringForError(status);
+        this.#reject(new Error(errorMessage));
       }
       if (request.responseStatus != 200) {
-        this._reject(new Error(request.responseStatusText));
+        this.#reject(new Error(request.responseStatusText));
       }
     } catch (err) {
-      this._reject(err);
+      this.#reject(err);
     }
-    this._resolve(this._response);
+    this.#resolve(this.#response);
   }
 
   // read response data
@@ -370,30 +338,32 @@ class MoatResponseListener {
       "@mozilla.org/scriptableinputstream;1"
     ].createInstance(Ci.nsIScriptableInputStream);
     scriptableStream.init(stream);
-    this._response += scriptableStream.read(length);
+    this.#response += scriptableStream.read(length);
   }
 }
 
 class InternetTestResponseListener {
+  #promise;
+  #resolve;
+  #reject;
   constructor() {
-    this._promise = new Promise((resolve, reject) => {
-      this._resolve = resolve;
-      this._reject = reject;
+    this.#promise = new Promise((resolve, reject) => {
+      this.#resolve = resolve;
+      this.#reject = reject;
     });
   }
 
   // callers wait on this for final response
   get status() {
-    return this._promise;
+    return this.#promise;
   }
 
   onStartRequest(request) {}
 
   // resolve or reject our Promise
   onStopRequest(request, status) {
-    let statuses = {};
     try {
-      statuses = {
+      const statuses = {
         components: status,
         successful: Components.isSuccessCode(status),
       };
@@ -408,56 +378,51 @@ class InternetTestResponseListener {
           err
         );
       }
+      this.#resolve(statuses);
     } catch (err) {
-      this._reject(err);
+      this.#reject(err);
     }
-    this._resolve(statuses);
   }
 
   onDataAvailable(request, stream, offset, length) {
-    //  We do not care of the actual data, as long as we have a successful
+    // We do not care of the actual data, as long as we have a successful
     // connection
   }
 }
 
 // constructs the json objects and sends the request over moat
-class MoatRPC {
-  constructor() {
-    this._meekTransport = null;
-    this._inited = false;
-  }
+export class MoatRPC {
+  #inited = false;
+  #meekTransport = null;
 
   get inited() {
-    return this._inited;
+    return this.#inited;
   }
 
   async init() {
-    if (this._inited) {
+    if (this.#inited) {
       throw new Error("MoatRPC: Already initialized");
     }
 
     let meekTransport = new MeekTransport();
     await meekTransport.init();
-    this._meekTransport = meekTransport;
-    this._inited = true;
+    this.#meekTransport = meekTransport;
+    this.#inited = true;
   }
 
   async uninit() {
-    await this._meekTransport?.uninit();
-    this._meekTransport = null;
-    this._inited = false;
+    await this.#meekTransport?.uninit();
+    this.#meekTransport = null;
+    this.#inited = false;
   }
 
-  _makeHttpHandler(uriString) {
-    if (!this._inited) {
+  #makeHttpHandler(uriString) {
+    if (!this.#inited) {
       throw new Error("MoatRPC: Not initialized");
     }
 
-    const proxyType = this._meekTransport._meekProxyType;
-    const proxyAddress = this._meekTransport._meekProxyAddress;
-    const proxyPort = this._meekTransport._meekProxyPort;
-    const proxyUsername = this._meekTransport._meekProxyUsername;
-    const proxyPassword = this._meekTransport._meekProxyPassword;
+    const { proxyType, proxyAddress, proxyPort, proxyUsername, proxyPassword } =
+      this.#meekTransport;
 
     const proxyPS = Cc[
       "@mozilla.org/network/protocol-proxy-service;1"
@@ -511,11 +476,11 @@ class MoatRPC {
     return ch;
   }
 
-  async _makeRequest(procedure, args) {
+  async #makeRequest(procedure, args) {
     const procedureURIString = `${Services.prefs.getStringPref(
       TorLauncherPrefs.moat_service
     )}/${procedure}`;
-    const ch = this._makeHttpHandler(procedureURIString);
+    const ch = this.#makeHttpHandler(procedureURIString);
 
     // Arrange for the POST data to be sent.
     const argsJson = JSON.stringify(args);
@@ -544,7 +509,7 @@ class MoatRPC {
     const uri = `${Services.prefs.getStringPref(
       TorLauncherPrefs.moat_service
     )}/circumvention/countries`;
-    const ch = this._makeHttpHandler(uri);
+    const ch = this.#makeHttpHandler(uri);
     ch.requestMethod = "HEAD";
 
     const listener = new InternetTestResponseListener();
@@ -582,7 +547,7 @@ class MoatRPC {
           },
         ],
       };
-      const response = await this._makeRequest("fetch", args);
+      const response = await this.#makeRequest("fetch", args);
       if ("errors" in response) {
         const code = response.errors[0].code;
         const detail = response.errors[0].detail;
@@ -623,7 +588,7 @@ class MoatRPC {
         },
       ],
     };
-    const response = await this._makeRequest("check", args);
+    const response = await this.#makeRequest("check", args);
     if ("errors" in response) {
       const code = response.errors[0].code;
       const detail = response.errors[0].detail;
@@ -642,7 +607,7 @@ class MoatRPC {
 
   // Convert received settings object to format used by TorSettings module
   // In the event of error, just return null
-  _fixupSettings(settings) {
+  #fixupSettings(settings) {
     try {
       let retval = TorSettings.defaultSettings();
       if ("bridges" in settings) {
@@ -691,11 +656,11 @@ class MoatRPC {
   // Converts a list of settings objects received from BridgeDB to a list of settings objects
   // understood by the TorSettings module
   // In the event of error, returns and empty list
-  _fixupSettingsList(settingsList) {
+  #fixupSettingsList(settingsList) {
     try {
       let retval = [];
       for (let settings of settingsList) {
-        settings = this._fixupSettings(settings);
+        settings = this.#fixupSettings(settings);
         if (settings != null) {
           retval.push(settings);
         }
@@ -724,7 +689,7 @@ class MoatRPC {
       transports: transports ? transports : [],
       country,
     };
-    const response = await this._makeRequest("circumvention/settings", args);
+    const response = await this.#makeRequest("circumvention/settings", args);
     let settings = {};
     if ("errors" in response) {
       const code = response.errors[0].code;
@@ -739,7 +704,7 @@ class MoatRPC {
 
       throw new Error(`MoatRPC: ${detail} (${code})`);
     } else if ("settings" in response) {
-      settings.settings = this._fixupSettingsList(response.settings);
+      settings.settings = this.#fixupSettingsList(response.settings);
     }
     if ("country" in response) {
       settings.country = response.country;
@@ -753,7 +718,7 @@ class MoatRPC {
   // for
   async circumvention_countries() {
     const args = {};
-    return this._makeRequest("circumvention/countries", args);
+    return this.#makeRequest("circumvention/countries", args);
   }
 
   // Request a copy of the builtin bridges, takes the following parameters:
@@ -766,7 +731,7 @@ class MoatRPC {
     const args = {
       transports: transports ? transports : [],
     };
-    const response = await this._makeRequest("circumvention/builtin", args);
+    const response = await this.#makeRequest("circumvention/builtin", args);
     if ("errors" in response) {
       const code = response.errors[0].code;
       const detail = response.errors[0].detail;
@@ -791,13 +756,13 @@ class MoatRPC {
     const args = {
       transports: transports ? transports : [],
     };
-    const response = await this._makeRequest("circumvention/defaults", args);
+    const response = await this.#makeRequest("circumvention/defaults", args);
     if ("errors" in response) {
       const code = response.errors[0].code;
       const detail = response.errors[0].detail;
       throw new Error(`MoatRPC: ${detail} (${code})`);
     } else if ("settings" in response) {
-      return this._fixupSettingsList(response.settings);
+      return this.#fixupSettingsList(response.settings);
     }
     return [];
   }


=====================================
browser/modules/TorConnect.jsm → browser/modules/TorConnect.sys.mjs
=====================================
@@ -1,36 +1,32 @@
-"use strict";
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-var EXPORTED_SYMBOLS = [
-  "InternetStatus",
-  "TorConnect",
-  "TorConnectTopics",
-  "TorConnectState",
-];
+import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";
 
-const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const lazy = {};
 
-const { setTimeout, clearTimeout } = ChromeUtils.import(
-  "resource://gre/modules/Timer.jsm"
-);
+ChromeUtils.defineESModuleGetters(lazy, {
+  MoatRPC: "resource:///modules/Moat.sys.mjs",
+  TorBootstrapRequest: "resource://gre/modules/TorBootstrapRequest.sys.mjs",
+  TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
+});
 
-const { BrowserWindowTracker } = ChromeUtils.import(
+// TODO: Should we move this to the about:torconnect actor?
+ChromeUtils.defineModuleGetter(
+  lazy,
+  "BrowserWindowTracker",
   "resource:///modules/BrowserWindowTracker.jsm"
 );
 
-const { TorMonitorService } = ChromeUtils.import(
-  "resource://gre/modules/TorMonitorService.jsm"
-);
-const { TorBootstrapRequest } = ChromeUtils.import(
-  "resource://gre/modules/TorBootstrapRequest.jsm"
-);
-
-const { TorSettings, TorSettingsTopics, TorBuiltinBridgeTypes } =
-  ChromeUtils.import("resource:///modules/TorSettings.jsm");
+import {
+  TorSettings,
+  TorSettingsTopics,
+  TorBuiltinBridgeTypes,
+} from "resource:///modules/TorSettings.sys.mjs";
 
 const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
 
-const { MoatRPC } = ChromeUtils.import("resource:///modules/Moat.jsm");
-
 const TorTopics = Object.freeze({
   LogHasWarnOrErr: "TorLogHasWarnOrErr",
   ProcessExited: "TorProcessExited",
@@ -46,7 +42,7 @@ const TorConnectPrefs = Object.freeze({
   allow_internet_test: "torbrowser.bootstrap.allow_internet_test",
 });
 
-const TorConnectState = Object.freeze({
+export const TorConnectState = Object.freeze({
   /* Our initial state */
   Initial: "Initial",
   /* In-between initial boot and bootstrapping, users can change tor network settings during this state */
@@ -156,7 +152,7 @@ const TorConnectStateTransitions = Object.freeze(
 );
 
 /* Topics Notified by the TorConnect module */
-const TorConnectTopics = Object.freeze({
+export const TorConnectTopics = Object.freeze({
   StateChange: "torconnect:state-change",
   BootstrapProgress: "torconnect:bootstrap-progress",
   BootstrapComplete: "torconnect:bootstrap-complete",
@@ -238,7 +234,7 @@ const debug_sleep = async ms => {
   });
 };
 
-const InternetStatus = Object.freeze({
+export const InternetStatus = Object.freeze({
   Unknown: -1,
   Offline: 0,
   Online: 1,
@@ -302,7 +298,7 @@ class InternetTest {
     // waiting both for the bootstrap, and for the Internet test.
     // However, managing Moat with async/await is much easier as it avoids a
     // callback hell, and it makes extra explicit that we are uniniting it.
-    const mrpc = new MoatRPC();
+    const mrpc = new lazy.MoatRPC();
     let status = null;
     let error = null;
     try {
@@ -340,7 +336,7 @@ class InternetTest {
   }
 }
 
-const TorConnect = (() => {
+export const TorConnect = (() => {
   let retval = {
     _state: TorConnectState.Initial,
     _bootstrapProgress: 0,
@@ -459,7 +455,7 @@ const TorConnect = (() => {
                 return;
               }
 
-              const tbr = new TorBootstrapRequest();
+              const tbr = new lazy.TorBootstrapRequest();
               const internetTest = new InternetTest();
               let cancelled = false;
 
@@ -604,7 +600,7 @@ const TorConnect = (() => {
 
               // lookup user's potential censorship circumvention settings from Moat service
               try {
-                this.mrpc = new MoatRPC();
+                this.mrpc = new lazy.MoatRPC();
                 await this.mrpc.init();
 
                 if (this.transitioning) {
@@ -678,7 +674,7 @@ const TorConnect = (() => {
                     await TorSettings.applySettings();
 
                     // build out our bootstrap request
-                    const tbr = new TorBootstrapRequest();
+                    const tbr = new lazy.TorBootstrapRequest();
                     tbr.onbootstrapstatus = (progress, status) => {
                       TorConnect._updateBootstrapStatus(progress, status);
                     };
@@ -915,7 +911,7 @@ const TorConnect = (() => {
      * @type {boolean}
      */
     get enabled() {
-      return TorMonitorService.ownsTorDaemon;
+      return lazy.TorProviderBuilder.build().ownsTorDaemon;
     },
 
     get shouldShowTorConnect() {
@@ -1053,7 +1049,7 @@ const TorConnect = (() => {
         Further external commands and helper methods
         */
     openTorPreferences() {
-      const win = BrowserWindowTracker.getTopWindow();
+      const win = lazy.BrowserWindowTracker.getTopWindow();
       win.switchToTabHavingURI("about:preferences#connection", true);
     },
 
@@ -1073,7 +1069,7 @@ const TorConnect = (() => {
      *   begin AutoBootstrapping, if possible.
      */
     openTorConnect(options) {
-      const win = BrowserWindowTracker.getTopWindow();
+      const win = lazy.BrowserWindowTracker.getTopWindow();
       win.switchToTabHavingURI("about:torconnect", true, {
         ignoreQueryString: true,
       });
@@ -1094,7 +1090,7 @@ const TorConnect = (() => {
     },
 
     viewTorLogs() {
-      const win = BrowserWindowTracker.getTopWindow();
+      const win = lazy.BrowserWindowTracker.getTopWindow();
       win.switchToTabHavingURI("about:preferences#connection-viewlogs", true);
     },
 
@@ -1104,7 +1100,7 @@ const TorConnect = (() => {
       if (this._countryCodes.length) {
         return this._countryCodes;
       }
-      const mrpc = new MoatRPC();
+      const mrpc = new lazy.MoatRPC();
       try {
         await mrpc.init();
         this._countryCodes = await mrpc.circumvention_countries();


=====================================
browser/modules/TorSettings.jsm → browser/modules/TorSettings.sys.mjs
=====================================
@@ -1,36 +1,22 @@
-"use strict";
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-var EXPORTED_SYMBOLS = [
-  "TorSettings",
-  "TorSettingsTopics",
-  "TorSettingsData",
-  "TorBridgeSource",
-  "TorBuiltinBridgeTypes",
-  "TorProxyType",
-];
+const lazy = {};
 
-const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-const { TorMonitorService } = ChromeUtils.import(
-  "resource://gre/modules/TorMonitorService.jsm"
-);
-const { TorProtocolService } = ChromeUtils.import(
-  "resource://gre/modules/TorProtocolService.jsm"
-);
-
-/* tor-launcher observer topics */
-const TorTopics = Object.freeze({
-  ProcessIsReady: "TorProcessIsReady",
+ChromeUtils.defineESModuleGetters(lazy, {
+  TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
+  TorProviderTopics: "resource://gre/modules/TorProviderBuilder.sys.mjs",
 });
 
 /* TorSettings observer topics */
-const TorSettingsTopics = Object.freeze({
+export const TorSettingsTopics = Object.freeze({
   Ready: "torsettings:ready",
   SettingChanged: "torsettings:setting-changed",
 });
 
 /* TorSettings observer data (for SettingChanged topic) */
-const TorSettingsData = Object.freeze({
+export const TorSettingsData = Object.freeze({
   QuickStartEnabled: "torsettings:quickstart_enabled",
 });
 
@@ -98,21 +84,21 @@ const TorConfigKeys = Object.freeze({
   clientTransportPlugin: "ClientTransportPlugin",
 });
 
-const TorBridgeSource = Object.freeze({
+export const TorBridgeSource = Object.freeze({
   Invalid: -1,
   BuiltIn: 0,
   BridgeDB: 1,
   UserProvided: 2,
 });
 
-const TorProxyType = Object.freeze({
+export const TorProxyType = Object.freeze({
   Invalid: -1,
   Socks4: 0,
   Socks5: 1,
   HTTPS: 2,
 });
 
-const TorBuiltinBridgeTypes = Object.freeze(
+export const TorBuiltinBridgeTypes = Object.freeze(
   (() => {
     const bridgeListBranch = Services.prefs.getBranch(
       TorLauncherPrefs.default_bridge
@@ -254,7 +240,7 @@ const arrayCopy = function (array) {
 
 /* TorSettings module */
 
-const TorSettings = (() => {
+export const TorSettings = (() => {
   const self = {
     _settings: null,
 
@@ -288,7 +274,8 @@ const TorSettings = (() => {
 
     /* load or init our settings, and register observers */
     init() {
-      if (TorMonitorService.ownsTorDaemon) {
+      const provider = lazy.TorProviderBuilder.build();
+      if (provider.ownsTorDaemon) {
         // if the settings branch exists, load settings from prefs
         if (Services.prefs.getBoolPref(TorSettingsPrefs.enabled, false)) {
           this.loadFromPrefs();
@@ -296,9 +283,9 @@ const TorSettings = (() => {
           // otherwise load defaults
           this._settings = this.defaultSettings();
         }
-        Services.obs.addObserver(this, TorTopics.ProcessIsReady);
+        Services.obs.addObserver(this, lazy.TorProviderTopics.ProcessIsReady);
 
-        if (TorMonitorService.isRunning) {
+        if (provider.isRunning) {
           this.handleProcessReady();
         }
       }
@@ -309,8 +296,11 @@ const TorSettings = (() => {
       console.log(`TorSettings: Observed ${topic}`);
 
       switch (topic) {
-        case TorTopics.ProcessIsReady:
-          Services.obs.removeObserver(this, TorTopics.ProcessIsReady);
+        case lazy.TorProviderTopics.ProcessIsReady:
+          Services.obs.removeObserver(
+            this,
+            lazy.TorProviderTopics.ProcessIsReady
+          );
           await this.handleProcessReady();
           break;
       }
@@ -569,7 +559,7 @@ const TorSettings = (() => {
       }
 
       /* Push to Tor */
-      await TorProtocolService.writeSettings(settingsMap);
+      await lazy.TorProviderBuilder.build().writeSettings(settingsMap);
 
       return this;
     },


=====================================
browser/modules/moz.build
=====================================
@@ -123,7 +123,7 @@ XPCSHELL_TESTS_MANIFESTS += ["test/unit/xpcshell.ini"]
 EXTRA_JS_MODULES += [
     "AboutNewTab.jsm",
     "AsyncTabSwitcher.jsm",
-    "BridgeDB.jsm",
+    "BridgeDB.sys.mjs",
     "BrowserUIUtils.jsm",
     "BrowserUsageTelemetry.jsm",
     "BrowserWindowTracker.jsm",
@@ -135,7 +135,7 @@ EXTRA_JS_MODULES += [
     "FeatureCallout.sys.mjs",
     "HomePage.jsm",
     "LaterRun.jsm",
-    'Moat.jsm',
+    "Moat.sys.mjs",
     "NewTabPagePreloading.jsm",
     "OpenInTabsUtils.jsm",
     "PageActions.jsm",
@@ -149,8 +149,8 @@ EXTRA_JS_MODULES += [
     "SitePermissions.sys.mjs",
     "TabsList.jsm",
     "TabUnloader.jsm",
-    "TorConnect.jsm",
-    "TorSettings.jsm",
+    "TorConnect.sys.mjs",
+    "TorSettings.sys.mjs",
     "TorStrings.jsm",
     "TransientPrefs.jsm",
     "URILoadingHelper.sys.mjs",


=====================================
toolkit/components/tor-launcher/TorBootstrapRequest.sys.mjs
=====================================
@@ -1,6 +1,6 @@
 import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";
 
-import { TorProtocolService } from "resource://gre/modules/TorProtocolService.sys.mjs";
+import { TorProviderBuilder } from "resource://gre/modules/TorProviderBuilder.sys.mjs";
 import { TorLauncherUtil } from "resource://gre/modules/TorLauncherUtil.sys.mjs";
 
 /* tor-launcher observer topics */
@@ -13,19 +13,23 @@ export const TorTopics = Object.freeze({
 // modeled after XMLHttpRequest
 // nicely encapsulates the observer register/unregister logic
 export class TorBootstrapRequest {
+  // number of ms to wait before we abandon the bootstrap attempt
+  // a value of 0 implies we never wait
+  timeout = 0;
+
+  // callbacks for bootstrap process status updates
+  onbootstrapstatus = (progress, status) => {};
+  onbootstrapcomplete = () => {};
+  onbootstraperror = (message, details) => {};
+
+  // internal resolve() method for bootstrap
+  #bootstrapPromiseResolve = null;
+  #bootstrapPromise = null;
+  #timeoutID = null;
+  #provider = null;
+
   constructor() {
-    // number of ms to wait before we abandon the bootstrap attempt
-    // a value of 0 implies we never wait
-    this.timeout = 0;
-    // callbacks for bootstrap process status updates
-    this.onbootstrapstatus = (progress, status) => {};
-    this.onbootstrapcomplete = () => {};
-    this.onbootstraperror = (message, details) => {};
-
-    // internal resolve() method for bootstrap
-    this._bootstrapPromiseResolve = null;
-    this._bootstrapPromise = null;
-    this._timeoutID = null;
+    this.#provider = TorProviderBuilder.build();
   }
 
   observe(subject, topic, data) {
@@ -41,15 +45,16 @@ export class TorBootstrapRequest {
           if (this.onbootstrapcomplete) {
             this.onbootstrapcomplete();
           }
-          this._bootstrapPromiseResolve(true);
-          clearTimeout(this._timeoutID);
+          this.#bootstrapPromiseResolve(true);
+          clearTimeout(this.#timeoutID);
+          this.#timeoutID = null;
         }
 
         break;
       }
       case TorTopics.BootstrapError: {
         console.info("TorBootstrapRequest: observerd TorBootstrapError", obj);
-        this._stop(obj?.message, obj?.details);
+        this.#stop(obj?.message, obj?.details);
         break;
       }
     }
@@ -57,12 +62,12 @@ export class TorBootstrapRequest {
 
   // resolves 'true' if bootstrap succeeds, false otherwise
   bootstrap() {
-    if (this._bootstrapPromise) {
-      return this._bootstrapPromise;
+    if (this.#bootstrapPromise) {
+      return this.#bootstrapPromise;
     }
 
-    this._bootstrapPromise = new Promise((resolve, reject) => {
-      this._bootstrapPromiseResolve = resolve;
+    this.#bootstrapPromise = new Promise((resolve, reject) => {
+      this.#bootstrapPromiseResolve = resolve;
 
       // register ourselves to listen for bootstrap events
       Services.obs.addObserver(this, TorTopics.BootstrapStatus);
@@ -70,10 +75,10 @@ export class TorBootstrapRequest {
 
       // optionally cancel bootstrap after a given timeout
       if (this.timeout > 0) {
-        this._timeoutID = setTimeout(async () => {
-          this._timeoutID = null;
+        this.#timeoutID = setTimeout(async () => {
+          this.#timeoutID = null;
           // TODO: Translate, if really used
-          await this._stop(
+          await this.#stop(
             "Tor Bootstrap process timed out",
             `Bootstrap attempt abandoned after waiting ${this.timeout} ms`
           );
@@ -81,38 +86,45 @@ export class TorBootstrapRequest {
       }
 
       // wait for bootstrapping to begin and maybe handle error
-      TorProtocolService.connect().catch(err => {
-        this._stop(err.message, "");
+      this.#provider.connect().catch(err => {
+        this.#stop(err.message, "");
       });
     }).finally(() => {
       // and remove ourselves once bootstrap is resolved
       Services.obs.removeObserver(this, TorTopics.BootstrapStatus);
       Services.obs.removeObserver(this, TorTopics.BootstrapError);
-      this._bootstrapPromise = null;
+      this.#bootstrapPromise = null;
     });
 
-    return this._bootstrapPromise;
+    return this.#bootstrapPromise;
   }
 
   async cancel() {
-    await this._stop();
+    await this.#stop();
   }
 
   // Internal implementation. Do not use directly, but call cancel, instead.
-  async _stop(message, details) {
+  async #stop(message, details) {
     // first stop our bootstrap timeout before handling the error
-    if (this._timeoutID !== null) {
-      clearTimeout(this._timeoutID);
-      this._timeoutID = null;
+    if (this.#timeoutID !== null) {
+      clearTimeout(this.#timeoutID);
+      this.#timeoutID = null;
     }
 
-    // stopBootstrap never throws
-    await TorProtocolService.stopBootstrap();
+    try {
+      await this.#provider.stopBootstrap();
+    } catch (e) {
+      console.error("Failed to stop the bootstrap.", e);
+      if (!message) {
+        message = e.message;
+        details = "";
+      }
+    }
 
     if (this.onbootstraperror && message) {
       this.onbootstraperror(message, details);
     }
 
-    this._bootstrapPromiseResolve(false);
+    this.#bootstrapPromiseResolve(false);
   }
 }


=====================================
toolkit/components/tor-launcher/TorControlPort.sys.mjs
=====================================
@@ -274,6 +274,44 @@ class AsyncSocket {
  * the command
  */
 
+/**
+ * @typedef {object} Bridge
+ * @property {string} transport The transport of the bridge, or vanilla if not
+ * specified.
+ * @property {string} addr The IP address and port of the bridge
+ * @property {string} id The fingerprint of the bridge
+ * @property {string} args Optional arguments passed to the bridge
+ */
+/**
+ * @typedef {object} PTInfo The information about a pluggable transport
+ * @property {string[]} transports An array with all the transports supported by
+ * this configuration.
+ * @property {string} type Either socks4, socks5 or exec
+ * @property {string} [ip] The IP address of the proxy (only for socks4 and
+ * socks5)
+ * @property {integer} [port] The port of the proxy (only for socks4 and socks5)
+ * @property {string} [pathToBinary] Path to the binary that is run (only for
+ * exec)
+ * @property {string} [options] Optional options passed to the binary (only for
+ * exec)
+ */
+/**
+ * @typedef {object} OnionAuthKeyInfo
+ * @property {string} address The address of the onion service
+ * @property {string} typeAndKey Onion service key and type of key, as
+ * `type:base64-private-key`
+ * @property {string} Flags Additional flags, such as Permanent
+ */
+/**
+ * @callback EventFilterCallback
+ * @param {any} data Either a raw string, or already parsed data
+ * @returns {boolean}
+ */
+/**
+ * @callback EventCallback
+ * @param {any} data Either a raw string, or already parsed data
+ */
+
 class TorError extends Error {
   constructor(command, reply) {
     super(`${command} -> ${reply}`);
@@ -584,319 +622,6 @@ class ControlSocket {
   }
 }
 
-// ## utils
-// A namespace for utility functions
-let utils = {};
-
-// __utils.identity(x)__.
-// Returns its argument unchanged.
-utils.identity = function (x) {
-  return x;
-};
-
-// __utils.capture(string, regex)__.
-// Takes a string and returns an array of capture items, where regex must have a single
-// capturing group and use the suffix /.../g to specify a global search.
-utils.capture = function (string, regex) {
-  let matches = [];
-  // Special trick to use string.replace for capturing multiple matches.
-  string.replace(regex, function (a, captured) {
-    matches.push(captured);
-  });
-  return matches;
-};
-
-// __utils.extractor(regex)__.
-// Returns a function that takes a string and returns an array of regex matches. The
-// regex must use the suffix /.../g to specify a global search.
-utils.extractor = function (regex) {
-  return function (text) {
-    return utils.capture(text, regex);
-  };
-};
-
-// __utils.splitLines(string)__.
-// Splits a string into an array of strings, each corresponding to a line.
-utils.splitLines = function (string) {
-  return string.split(/\r?\n/);
-};
-
-// __utils.splitAtSpaces(string)__.
-// Splits a string into chunks between spaces. Does not split at spaces
-// inside pairs of quotation marks.
-utils.splitAtSpaces = utils.extractor(/((\S*?"(.*?)")+\S*|\S+)/g);
-
-// __utils.splitAtFirst(string, regex)__.
-// Splits a string at the first instance of regex match. If no match is
-// found, returns the whole string.
-utils.splitAtFirst = function (string, regex) {
-  let match = string.match(regex);
-  return match
-    ? [
-        string.substring(0, match.index),
-        string.substring(match.index + match[0].length),
-      ]
-    : string;
-};
-
-// __utils.splitAtEquals(string)__.
-// Splits a string into chunks between equals. Does not split at equals
-// inside pairs of quotation marks.
-utils.splitAtEquals = utils.extractor(/(([^=]*?"(.*?)")+[^=]*|[^=]+)/g);
-
-// __utils.mergeObjects(arrayOfObjects)__.
-// Takes an array of objects like [{"a":"b"},{"c":"d"}] and merges to a single object.
-// Pure function.
-utils.mergeObjects = function (arrayOfObjects) {
-  let result = {};
-  for (let obj of arrayOfObjects) {
-    for (let key in obj) {
-      result[key] = obj[key];
-    }
-  }
-  return result;
-};
-
-// __utils.listMapData(parameterString, listNames)__.
-// Takes a list of parameters separated by spaces, of which the first several are
-// unnamed, and the remainder are named, in the form `NAME=VALUE`. Apply listNames
-// to the unnamed parameters, and combine them in a map with the named parameters.
-// Example: `40 FAILED 0 95.78.59.36:80 REASON=CANT_ATTACH`
-//
-//     utils.listMapData("40 FAILED 0 95.78.59.36:80 REASON=CANT_ATTACH",
-//                       ["streamID", "event", "circuitID", "IP"])
-//     // --> {"streamID" : "40", "event" : "FAILED", "circuitID" : "0",
-//     //      "address" : "95.78.59.36:80", "REASON" : "CANT_ATTACH"}"
-utils.listMapData = function (parameterString, listNames) {
-  // Split out the space-delimited parameters.
-  let parameters = utils.splitAtSpaces(parameterString),
-    dataMap = {};
-  // Assign listNames to the first n = listNames.length parameters.
-  for (let i = 0; i < listNames.length; ++i) {
-    dataMap[listNames[i]] = parameters[i];
-  }
-  // Read key-value pairs and copy these to the dataMap.
-  for (let i = listNames.length; i < parameters.length; ++i) {
-    let [key, value] = utils.splitAtEquals(parameters[i]);
-    if (key && value) {
-      dataMap[key] = value;
-    }
-  }
-  return dataMap;
-};
-
-// ## info
-// A namespace for functions related to tor's GETINFO and GETCONF command.
-let info = {};
-
-// __info.keyValueStringsFromMessage(messageText)__.
-// Takes a message (text) response to GETINFO or GETCONF and provides
-// a series of key-value strings, which are either multiline (with a `250+` prefix):
-//
-//     250+config/defaults=
-//     AccountingMax "0 bytes"
-//     AllowDotExit "0"
-//     .
-//
-// or single-line (with a `250-` or `250 ` prefix):
-//
-//     250-version=0.2.6.0-alpha-dev (git-b408125288ad6943)
-info.keyValueStringsFromMessage = utils.extractor(
-  /^(250\+[\s\S]+?^\.|250[- ].+?)$/gim
-);
-
-// __info.applyPerLine(transformFunction)__.
-// Returns a function that splits text into lines,
-// and applies transformFunction to each line.
-info.applyPerLine = function (transformFunction) {
-  return function (text) {
-    return utils.splitLines(text.trim()).map(transformFunction);
-  };
-};
-
-// __info.routerStatusParser(valueString)__.
-// Parses a router status entry as, described in
-// https://gitweb.torproject.org/torspec.git/tree/dir-spec.txt
-// (search for "router status entry")
-info.routerStatusParser = function (valueString) {
-  let lines = utils.splitLines(valueString),
-    objects = [];
-  for (let line of lines) {
-    // Drop first character and grab data following it.
-    let myData = line.substring(2),
-      // Accumulate more maps with data, depending on the first character in the line.
-      dataFun = {
-        r: data =>
-          utils.listMapData(data, [
-            "nickname",
-            "identity",
-            "digest",
-            "publicationDate",
-            "publicationTime",
-            "IP",
-            "ORPort",
-            "DirPort",
-          ]),
-        a: data => ({ IPv6: data }),
-        s: data => ({ statusFlags: utils.splitAtSpaces(data) }),
-        v: data => ({ version: data }),
-        w: data => utils.listMapData(data, []),
-        p: data => ({ portList: data.split(",") }),
-      }[line.charAt(0)];
-    if (dataFun !== undefined) {
-      objects.push(dataFun(myData));
-    }
-  }
-  return utils.mergeObjects(objects);
-};
-
-// __info.circuitStatusParser(line)__.
-// Parse the output of a circuit status line.
-info.circuitStatusParser = function (line) {
-  let data = utils.listMapData(line, ["id", "status", "circuit"]),
-    circuit = data.circuit;
-  // Parse out the individual circuit IDs and names.
-  if (circuit) {
-    data.circuit = circuit.split(",").map(function (x) {
-      return x.split(/~|=/);
-    });
-  }
-  return data;
-};
-
-// __info.streamStatusParser(line)__.
-// Parse the output of a stream status line.
-info.streamStatusParser = function (text) {
-  return utils.listMapData(text, [
-    "StreamID",
-    "StreamStatus",
-    "CircuitID",
-    "Target",
-  ]);
-};
-
-// TODO: fix this parsing logic to handle bridgeLine correctly
-// fingerprint/id is an optional parameter
-// __info.bridgeParser(bridgeLine)__.
-// Takes a single line from a `getconf bridge` result and returns
-// a map containing the bridge's type, address, and ID.
-info.bridgeParser = function (bridgeLine) {
-  let result = {},
-    tokens = bridgeLine.split(/\s+/);
-  // First check if we have a "vanilla" bridge:
-  if (tokens[0].match(/^\d+\.\d+\.\d+\.\d+/)) {
-    result.type = "vanilla";
-    [result.address, result.ID] = tokens;
-    // Several bridge types have a similar format:
-  } else {
-    result.type = tokens[0];
-    if (
-      [
-        "flashproxy",
-        "fte",
-        "meek",
-        "meek_lite",
-        "obfs3",
-        "obfs4",
-        "scramblesuit",
-        "snowflake",
-      ].includes(result.type)
-    ) {
-      [result.address, result.ID] = tokens.slice(1);
-    }
-  }
-  return result.type ? result : null;
-};
-
-// __info.parsers__.
-// A map of GETINFO and GETCONF keys to parsing function, which convert
-// result strings to JavaScript data.
-info.parsers = {
-  "ns/id/": info.routerStatusParser,
-  "ip-to-country/": utils.identity,
-  "circuit-status": info.applyPerLine(info.circuitStatusParser),
-  bridge: info.bridgeParser,
-  // Currently unused parsers:
-  //  "ns/name/" : info.routerStatusParser,
-  //  "stream-status" : info.applyPerLine(info.streamStatusParser),
-  //  "version" : utils.identity,
-  //  "config-file" : utils.identity,
-};
-
-// __info.getParser(key)__.
-// Takes a key and determines the parser function that should be used to
-// convert its corresponding valueString to JavaScript data.
-info.getParser = function (key) {
-  return (
-    info.parsers[key] ||
-    info.parsers[key.substring(0, key.lastIndexOf("/") + 1)]
-  );
-};
-
-// __info.stringToValue(string)__.
-// Converts a key-value string as from GETINFO or GETCONF to a value.
-info.stringToValue = function (string) {
-  // key should look something like `250+circuit-status=` or `250-circuit-status=...`
-  // or `250 circuit-status=...`
-  let matchForKey = string.match(/^250[ +-](.+?)=/),
-    key = matchForKey ? matchForKey[1] : null;
-  if (key === null) {
-    return null;
-  }
-  // matchResult finds a single-line result for `250-` or `250 `,
-  // or a multi-line one for `250+`.
-  let matchResult =
-      string.match(/^250[ -].+?=(.*)$/) ||
-      string.match(/^250\+.+?=([\s\S]*?)^\.$/m),
-    // Retrieve the captured group (the text of the value in the key-value pair)
-    valueString = matchResult ? matchResult[1] : null,
-    // Get the parser function for the key found.
-    parse = info.getParser(key.toLowerCase());
-  if (parse === undefined) {
-    throw new Error("No parser found for '" + key + "'");
-  }
-  // Return value produced by the parser.
-  return parse(valueString);
-};
-
-/**
- * @typedef {object} Bridge
- * @property {string} transport The transport of the bridge, or vanilla if not
- * specified.
- * @property {string} addr The IP address and port of the bridge
- * @property {string} id The fingerprint of the bridge
- * @property {string} args Optional arguments passed to the bridge
- */
-/**
- * @typedef {object} PTInfo The information about a pluggable transport
- * @property {string[]} transports An array with all the transports supported by
- * this configuration.
- * @property {string} type Either socks4, socks5 or exec
- * @property {string} [ip] The IP address of the proxy (only for socks4 and
- * socks5)
- * @property {integer} [port] The port of the proxy (only for socks4 and socks5)
- * @property {string} [pathToBinary] Path to the binary that is run (only for
- * exec)
- * @property {string} [options] Optional options passed to the binary (only for
- * exec)
- */
-/**
- * @typedef {object} OnionAuthKeyInfo
- * @property {string} address The address of the onion service
- * @property {string} typeAndKey Onion service key and type of key, as
- * `type:base64-private-key`
- * @property {string} Flags Additional flags, such as Permanent
- */
-/**
- * @callback EventFilterCallback
- * @param {any} data Either a raw string, or already parsed data
- * @returns {boolean}
- */
-/**
- * @callback EventCallback
- * @param {any} data Either a raw string, or already parsed data
- */
-
 class TorController {
   /**
    * The control socket
@@ -905,16 +630,6 @@ class TorController {
    */
   #socket;
 
-  /**
-   * A map of EVENT keys to parsing functions, which convert result strings to
-   * JavaScript data.
-   */
-  #eventParsers = {
-    stream: info.streamStatusParser,
-    // Currently unused:
-    // "circ" : info.circuitStatusParser,
-  };
-
   /**
    * Builds a new TorController.
    *
@@ -981,18 +696,6 @@ class TorController {
     await this.#sendCommandSimple(`authenticate ${password || ""}`);
   }
 
-  /**
-   * Sends a GETINFO for a single key.
-   *
-   * @param {string} key The key to get value for
-   * @returns {any} The return value depends on the requested key
-   */
-  async getInfo(key) {
-    this.#expectString(key, "key");
-    const response = await this.sendCommand(`getinfo ${key}`);
-    return this.#getMultipleResponseValues(response)[0];
-  }
-
   /**
    * Sends a GETINFO for a single key.
    * control-spec.txt says "one ReplyLine is sent for each requested value", so,
@@ -1054,9 +757,7 @@ class TorController {
     const addresses = [v4[5]];
     // a address:port
     // dir-spec.txt also states only the first one should be taken
-    // TODO: The consumers do not care about the port or the square brackets
-    // either. Remove them when integrating this function with the rest
-    const v6 = reply.match(/^a\s+(\[[0-9a-fA-F:]+\]:[0-9]{1,5})$/m);
+    const v6 = reply.match(/^a\s+\[([0-9a-fA-F:]+)\]:\d{1,5}$/m);
     if (v6) {
       addresses.push(v6[1]);
     }
@@ -1091,23 +792,6 @@ class TorController {
 
   // Configuration
 
-  /**
-   * Sends a GETCONF for a single key.
-   * GETCONF with a single argument returns results with one or more lines that
-   * look like `250[- ]key=value`.
-   * Any GETCONF lines that contain a single keyword only are currently dropped.
-   * So we can use similar parsing to that for getInfo.
-   *
-   * @param {string} key The key to get value for
-   * @returns {any} A parsed config value (it depends if a parser is known)
-   */
-  async getConf(key) {
-    this.#expectString(key, "key");
-    return this.#getMultipleResponseValues(
-      await this.sendCommand(`getconf ${key}`)
-    );
-  }
-
   /**
    * Sends a GETCONF for a single key.
    * The function could be easily generalized to get multiple keys at once, but
@@ -1264,12 +948,14 @@ class TorController {
       // TODO: Change the consumer and make the fields more consistent with what
       // we get (e.g., separate key and type, and use a boolen for permanent).
       const info = {
-        hsAddress: match.groups.HSAddress,
-        typeAndKey: `${match.groups.KeyType}:${match.groups.PrivateKeyBlob}`,
+        address: match.groups.HSAddress,
+        keyType: match.groups.KeyType,
+        keyBlob: match.groups.PrivateKeyBlob,
+        flags: [],
       };
       const maybeFlags = match.groups.other?.match(/Flags=(\S+)/);
       if (maybeFlags) {
-        info.Flags = maybeFlags[1];
+        info.flags = maybeFlags[1].split(",");
       }
       return info;
     });
@@ -1369,28 +1055,12 @@ class TorController {
    * first.
    *
    * @param {string} type The event type to catch
-   * @param {EventFilterCallback?} filter An optional callback to filter
-   * events for which the callback will be called. If null, all events will be
-   * passed.
    * @param {EventCallback} callback The callback that will handle the event
-   * @param {boolean} raw Tell whether to ignore the data parser, even if
-   * supported
    */
-  watchEvent(type, filter, callback, raw = false) {
+  watchEvent(type, callback) {
     this.#expectString(type, "type");
     const start = `650 ${type}`;
-    this.#socket.addNotificationCallback(new RegExp(`^${start}`), message => {
-      // Remove also the initial text
-      const dataText = message.substring(start.length + 1);
-      const parser = this.#eventParsers[type.toLowerCase()];
-      const data = dataText && parser ? parser(dataText) : null;
-      // FIXME: This is the original code, but we risk of not filtering on the
-      // data, if we ask for raw data (which we always do at the moment, but we
-      // do not use a filter either...)
-      if (filter === null || filter(data)) {
-        callback(data && !raw ? data : message);
-      }
-    });
+    this.#socket.addNotificationCallback(new RegExp(`^${start}`), callback);
   }
 
   // Other helpers
@@ -1453,19 +1123,6 @@ class TorController {
       )
     );
   }
-
-  /**
-   * Process multiple responses to a GETINFO or GETCONF request.
-   *
-   * @param {string} message The message to process
-   * @returns {object[]} The keys depend on the message
-   */
-  #getMultipleResponseValues(message) {
-    return info
-      .keyValueStringsFromMessage(message)
-      .map(info.stringToValue)
-      .filter(x => x);
-  }
 }
 
 const controlPortInfo = {};


=====================================
toolkit/components/tor-launcher/TorDomainIsolator.sys.mjs
=====================================
@@ -12,6 +12,11 @@ import {
 
 const lazy = {};
 
+ChromeUtils.defineESModuleGetters(lazy, {
+  TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
+  TorProviderTopics: "resource://gre/modules/TorProviderBuilder.sys.mjs",
+});
+
 XPCOMUtils.defineLazyServiceGetters(lazy, {
   ProtocolProxyService: [
     "@mozilla.org/network/protocol-proxy-service;1",
@@ -19,11 +24,6 @@ XPCOMUtils.defineLazyServiceGetters(lazy, {
   ],
 });
 
-ChromeUtils.defineESModuleGetters(lazy, {
-  TorMonitorTopics: "resource://gre/modules/TorMonitorService.sys.mjs",
-  TorProtocolService: "resource://gre/modules/TorProtocolService.sys.mjs",
-});
-
 const logger = new ConsoleAPI({
   prefix: "TorDomainIsolator",
   maxLogLevel: "warn",
@@ -143,7 +143,7 @@ class TorDomainIsolatorImpl {
 
     Services.prefs.addObserver(NON_TOR_PROXY_PREF, this);
     Services.obs.addObserver(this, NEW_IDENTITY_TOPIC);
-    Services.obs.addObserver(this, lazy.TorMonitorTopics.StreamSucceeded);
+    Services.obs.addObserver(this, lazy.TorProviderTopics.StreamSucceeded);
 
     this.#cleanupIntervalId = setInterval(
       this.#clearKnownCircuits.bind(this),
@@ -158,7 +158,7 @@ class TorDomainIsolatorImpl {
   uninit() {
     Services.prefs.removeObserver(NON_TOR_PROXY_PREF, this);
     Services.obs.removeObserver(this, NEW_IDENTITY_TOPIC);
-    Services.obs.removeObserver(this, lazy.TorMonitorTopics.StreamSucceeded);
+    Services.obs.removeObserver(this, lazy.TorProviderTopics.StreamSucceeded);
     clearInterval(this.#cleanupIntervalId);
     this.#cleanupIntervalId = null;
     this.clearIsolation();
@@ -257,12 +257,12 @@ class TorDomainIsolatorImpl {
       );
       this.clearIsolation();
       try {
-        await lazy.TorProtocolService.newnym();
+        await lazy.TorProviderBuilder.build().newnym();
       } catch (e) {
         logger.error("Could not send the newnym command", e);
         // TODO: What UX to use here? See tor-browser#41708
       }
-    } else if (topic === lazy.TorMonitorTopics.StreamSucceeded) {
+    } else if (topic === lazy.TorProviderTopics.StreamSucceeded) {
       const { username, password, circuit } = subject.wrappedJSObject;
       this.#updateCircuit(username, password, circuit);
     }
@@ -553,7 +553,7 @@ class TorDomainIsolatorImpl {
 
     data = await Promise.all(
       circuit.map(fingerprint =>
-        lazy.TorProtocolService.getNodeInfo(fingerprint)
+        lazy.TorProviderBuilder.build().getNodeInfo(fingerprint)
       )
     );
     this.#knownCircuits.set(id, data);


=====================================
toolkit/components/tor-launcher/TorMonitorService.sys.mjs deleted
=====================================
@@ -1,42 +0,0 @@
-// Copyright (c) 2022, The Tor Project, Inc.
-
-import { TorProviderTopics } from "resource://gre/modules/TorProviderBuilder.sys.mjs";
-
-const lazy = {};
-ChromeUtils.defineESModuleGetters(lazy, {
-  TorProtocolService: "resource://gre/modules/TorProtocolService.sys.mjs",
-});
-
-export const TorMonitorTopics = Object.freeze({
-  BridgeChanged: TorProviderTopics.BridgeChanged,
-  StreamSucceeded: TorProviderTopics.StreamSucceeded,
-});
-
-/**
- * This service monitors an existing Tor instance, or starts one, if needed, and
- * then starts monitoring it.
- *
- * This is the service which should be queried to know information about the
- * status of the bootstrap, the logs, etc...
- */
-export const TorMonitorService = {
-  get currentBridge() {
-    return lazy.TorProtocolService.currentBridge;
-  },
-
-  get ownsTorDaemon() {
-    return lazy.TorProtocolService.ownsTorDaemon;
-  },
-
-  get isRunning() {
-    return lazy.TorProtocolService.isRunning;
-  },
-
-  get isBootstrapDone() {
-    return lazy.TorProtocolService.isBootstrapDone;
-  },
-
-  getLog() {
-    return lazy.TorProtocolService.getLog();
-  },
-};


=====================================
toolkit/components/tor-launcher/TorParsers.sys.mjs
=====================================
@@ -269,11 +269,14 @@ export const TorParsers = Object.freeze({
   },
 
   parseBridgeLine(line) {
+    if (!line) {
+      return null;
+    }
     const re =
       /\s*(?:(?<transport>\S+)\s+)?(?<addr>[0-9a-fA-F\.\[\]\:]+:\d{1,5})(?:\s+(?<id>[0-9a-fA-F]{40}))?(?:\s+(?<args>.+))?/;
     const match = re.exec(line);
     if (!match) {
-      throw new Error("Invalid bridge line.");
+      throw new Error(`Invalid bridge line: ${line}.`);
     }
     const bridge = match.groups;
     if (!bridge.transport) {


=====================================
toolkit/components/tor-launcher/TorProtocolService.sys.mjs → toolkit/components/tor-launcher/TorProvider.sys.mjs
=====================================
@@ -1,4 +1,6 @@
-// Copyright (c) 2021, The Tor Project, Inc.
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";
 import { ConsoleAPI } from "resource://gre/modules/Console.sys.mjs";
@@ -11,7 +13,6 @@ import {
 import { TorProviderTopics } from "resource://gre/modules/TorProviderBuilder.sys.mjs";
 
 const lazy = {};
-
 ChromeUtils.defineESModuleGetters(lazy, {
   controller: "resource://gre/modules/TorControlPort.sys.mjs",
   configureControlPortModule: "resource://gre/modules/TorControlPort.sys.mjs",
@@ -21,7 +22,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
 
 const logger = new ConsoleAPI({
   maxLogLevel: "warn",
-  prefix: "TorProtocolService",
+  maxLogLevelPref: "browser.tor_provider.log_level",
+  prefix: "TorProvider",
 });
 
 /**
@@ -70,7 +72,7 @@ const ControlConnTimings = Object.freeze({
  * It can start a new tor instance, or connect to an existing one.
  * In the former case, it also takes its ownership by default.
  */
-class TorProvider {
+export class TorProvider {
   #inited = false;
 
   // Maintain a map of tor settings set by Tor Browser so that we don't
@@ -85,7 +87,6 @@ class TorProvider {
   #SOCKSPortInfo = null; // An object that contains ipcFile, host, port.
 
   #controlConnection = null; // This is cached and reused.
-  #connectionQueue = [];
 
   // Public methods
 
@@ -123,39 +124,34 @@ class TorProvider {
   // takes a Map containing tor settings
   // throws on error
   async writeSettings(aSettingsObj) {
+    const entries =
+      aSettingsObj instanceof Map
+        ? Array.from(aSettingsObj.entries())
+        : Object.entries(aSettingsObj);
     // only write settings that have changed
-    const newSettings = Array.from(aSettingsObj).filter(([setting, value]) => {
-      // make sure we have valid data here
-      this.#assertValidSetting(setting, value);
-
+    const newSettings = entries.filter(([setting, value]) => {
       if (!this.#settingsCache.has(setting)) {
         // no cached setting, so write
         return true;
       }
 
       const cachedValue = this.#settingsCache.get(setting);
-      if (value === cachedValue) {
-        return false;
-      } else if (Array.isArray(value) && Array.isArray(cachedValue)) {
-        // compare arrays member-wise
-        if (value.length !== cachedValue.length) {
-          return true;
-        }
-        for (let i = 0; i < value.length; i++) {
-          if (value[i] !== cachedValue[i]) {
-            return true;
-          }
-        }
-        return false;
+      // Arrays are the only special case for which === could fail.
+      // The other values we accept (strings, booleans, numbers, null and
+      // undefined) work correctly with ===.
+      if (Array.isArray(value) && Array.isArray(cachedValue)) {
+        return (
+          value.length !== cachedValue.length ||
+          value.some((val, idx) => val !== cachedValue[idx])
+        );
       }
-      // some other different values
-      return true;
+      return value !== cachedValue;
     });
 
     // only write if new setting to save
     if (newSettings.length) {
-      const settingsObject = Object.fromEntries(newSettings);
-      await this.setConfWithReply(settingsObject);
+      const conn = await this.#getConnection();
+      await conn.setConf(Object.fromEntries(newSettings));
 
       // save settings to cache after successfully writing to Tor
       for (const [setting, value] of newSettings) {
@@ -164,23 +160,15 @@ class TorProvider {
     }
   }
 
-  async readStringArraySetting(aSetting) {
-    const value = await this.#readSetting(aSetting);
-    this.#settingsCache.set(aSetting, value);
-    return value;
-  }
-
   // writes current tor settings to disk
   async flushSettings() {
-    await this.sendCommand("SAVECONF");
+    const conn = await this.#getConnection();
+    await conn.flushSettings();
   }
 
   async connect() {
-    const kTorConfKeyDisableNetwork = "DisableNetwork";
-    const settings = {};
-    settings[kTorConfKeyDisableNetwork] = false;
-    await this.setConfWithReply(settings);
-    await this.sendCommand("SAVECONF");
+    const conn = await this.#getConnection();
+    await conn.setNetworkEnabled(true);
     this.clearBootstrapError();
     this.retrieveBootstrapStatus();
   }
@@ -188,12 +176,8 @@ class TorProvider {
   async stopBootstrap() {
     // Tell tor to disable use of the network; this should stop the bootstrap
     // process.
-    try {
-      const settings = { DisableNetwork: true };
-      await this.setConfWithReply(settings);
-    } catch (e) {
-      logger.error("Error stopping bootstrap", e);
-    }
+    const conn = await this.#getConnection();
+    await conn.setNetworkEnabled(false);
     // We are not interested in waiting for this, nor in **catching its error**,
     // so we do not await this. We just want to be notified when the bootstrap
     // status is actually updated through observers.
@@ -201,28 +185,31 @@ class TorProvider {
   }
 
   async newnym() {
-    return this.sendCommand("SIGNAL NEWNYM");
+    const conn = await this.#getConnection();
+    await conn.newnym();
   }
 
   // Ask tor which ports it is listening to for SOCKS connections.
   // At the moment this is used only in TorCheckService.
   async getSocksListeners() {
-    const cmd = "GETINFO";
-    const keyword = "net/listeners/socks";
-    const response = await this.sendCommand(cmd, keyword);
-    return TorParsers.parseReply(cmd, keyword, response);
+    const conn = await this.#getConnection();
+    return conn.getSocksListeners();
   }
 
   async getBridges() {
+    const conn = await this.#getConnection();
     // Ideally, we would not need this function, because we should be the one
     // setting them with TorSettings. However, TorSettings is not notified of
     // change of settings. So, asking tor directly with the control connection
     // is the most reliable way of getting the configured bridges, at the
     // moment. Also, we are using this for the circuit display, which should
     // work also when we are not configuring the tor daemon, but just using it.
-    return this.#withConnection(conn => {
-      return conn.getConf("bridge");
-    });
+    return conn.getBridges();
+  }
+
+  async getPluggableTransports() {
+    const conn = await this.#getConnection();
+    return conn.getPluggableTransports();
   }
 
   /**
@@ -232,68 +219,55 @@ class TorProvider {
    * @returns {Promise<NodeData>}
    */
   async getNodeInfo(id) {
-    return this.#withConnection(async conn => {
-      const node = {
-        fingerprint: id,
-        ipAddrs: [],
-        bridgeType: null,
-        regionCode: null,
-      };
-      const bridge = (await conn.getConf("bridge"))?.find(
-        foundBridge => foundBridge.ID?.toUpperCase() === id.toUpperCase()
-      );
-      const addrRe = /^\[?([^\]]+)\]?:\d+$/;
-      if (bridge) {
-        node.bridgeType = bridge.type ?? "";
-        // Attempt to get an IP address from bridge address string.
-        const ip = bridge.address.match(addrRe)?.[1];
-        if (ip && !ip.startsWith("0.")) {
-          node.ipAddrs.push(ip);
-        }
-      } else {
-        // Either dealing with a relay, or a bridge whose fingerprint is not
-        // saved in torrc.
-        const info = await conn.getInfo(`ns/id/${id}`);
-        if (info.IP && !info.IP.startsWith("0.")) {
-          node.ipAddrs.push(info.IP);
-        }
-        const ip6 = info.IPv6?.match(addrRe)?.[1];
-        if (ip6) {
-          node.ipAddrs.push(ip6);
-        }
+    const conn = await this.#getConnection();
+    const node = {
+      fingerprint: id,
+      ipAddrs: [],
+      bridgeType: null,
+      regionCode: null,
+    };
+    const bridge = (await conn.getBridges())?.find(
+      foundBridge => foundBridge.id?.toUpperCase() === id.toUpperCase()
+    );
+    if (bridge) {
+      node.bridgeType = bridge.transport ?? "";
+      // Attempt to get an IP address from bridge address string.
+      const ip = bridge.addr.match(/^\[?([^\]]+)\]?:\d+$/)?.[1];
+      if (ip && !ip.startsWith("0.")) {
+        node.ipAddrs.push(ip);
       }
-      if (node.ipAddrs.length) {
-        // Get the country code for the node's IP address.
-        let regionCode;
-        try {
-          // Expect a 2-letter ISO3166-1 code, which should also be a valid
-          // BCP47 Region subtag.
-          regionCode = await conn.getInfo("ip-to-country/" + node.ipAddrs[0]);
-        } catch {}
+    } else {
+      node.ipAddrs = await conn.getNodeAddresses(id);
+    }
+    if (node.ipAddrs.length) {
+      // Get the country code for the node's IP address.
+      try {
+        // Expect a 2-letter ISO3166-1 code, which should also be a valid
+        // BCP47 Region subtag.
+        const regionCode = await conn.getIPCountry(node.ipAddrs[0]);
         if (regionCode && regionCode !== "??") {
           node.regionCode = regionCode.toUpperCase();
         }
+      } catch (e) {
+        logger.warn(`Cannot get a country for IP ${node.ipAddrs[0]}`, e);
       }
-      return node;
-    });
+    }
+    return node;
   }
 
-  async onionAuthAdd(hsAddress, b64PrivateKey, isPermanent) {
-    return this.#withConnection(conn => {
-      return conn.onionAuthAdd(hsAddress, b64PrivateKey, isPermanent);
-    });
+  async onionAuthAdd(address, b64PrivateKey, isPermanent) {
+    const conn = await this.#getConnection();
+    return conn.onionAuthAdd(address, b64PrivateKey, isPermanent);
   }
 
-  async onionAuthRemove(hsAddress) {
-    return this.#withConnection(conn => {
-      return conn.onionAuthRemove(hsAddress);
-    });
+  async onionAuthRemove(address) {
+    const conn = await this.#getConnection();
+    return conn.onionAuthRemove(address);
   }
 
   async onionAuthViewKeys() {
-    return this.#withConnection(conn => {
-      return conn.onionAuthViewKeys();
-    });
+    const conn = await this.#getConnection();
+    return conn.onionAuthViewKeys();
   }
 
   // TODO: transform the following 4 functions in getters.
@@ -333,106 +307,6 @@ class TorProvider {
     return this.#SOCKSPortInfo;
   }
 
-  // Public, but called only internally
-
-  // Executes a command on the control port.
-  // Return a reply object or null if a fatal error occurs.
-  async sendCommand(cmd, args) {
-    const maxTimeout = 1000;
-    let leftConnAttempts = 5;
-    let timeout = 250;
-    let reply;
-    while (leftConnAttempts-- > 0) {
-      const response = await this.#trySend(cmd, args, leftConnAttempts === 0);
-      if (response.connected) {
-        reply = response.reply;
-        break;
-      }
-      // We failed to acquire the controller after multiple attempts.
-      // Try again after some time.
-      logger.warn(
-        "sendCommand: Acquiring control connection failed, trying again later.",
-        cmd,
-        args
-      );
-      await new Promise(resolve => setTimeout(() => resolve(), timeout));
-      timeout = Math.min(2 * timeout, maxTimeout);
-    }
-
-    // We sent the command, but we still got an empty response.
-    // Something must be busted elsewhere.
-    if (!reply) {
-      throw new Error(`${cmd} sent an empty response`);
-    }
-
-    // TODO: Move the parsing of the reply to the controller, because anyone
-    // calling sendCommand on it actually wants a parsed reply.
-
-    reply = TorParsers.parseCommandResponse(reply);
-    if (!TorParsers.commandSucceeded(reply)) {
-      if (reply?.lineArray) {
-        throw new Error(reply.lineArray.join("\n"));
-      }
-      throw new Error(`${cmd} failed with code ${reply.statusCode}`);
-    }
-
-    return reply;
-  }
-
-  // Perform a SETCONF command.
-  // aSettingsObj should be a JavaScript object with keys (property values)
-  // that correspond to tor config. keys. The value associated with each
-  // key should be a simple string, a string array, or a Boolean value.
-  // If an associated value is undefined or null, a key with no value is
-  // passed in the SETCONF command.
-  // Throws in case of error, or returns a reply object.
-  async setConfWithReply(settings) {
-    if (!settings) {
-      throw new Error("Empty settings object");
-    }
-    const args = Object.entries(settings)
-      .map(([key, val]) => {
-        if (val === undefined || val === null) {
-          return key;
-        }
-        const valType = typeof val;
-        let rv = `${key}=`;
-        if (valType === "boolean") {
-          rv += val ? "1" : "0";
-        } else if (Array.isArray(val)) {
-          rv += val.map(TorParsers.escapeString).join(` ${key}=`);
-        } else if (valType === "string") {
-          rv += TorParsers.escapeString(val);
-        } else {
-          logger.error(`Got unsupported type for ${key}`, val);
-          throw new Error(`Unsupported type ${valType} (key ${key})`);
-        }
-        return rv;
-      })
-      .filter(arg => arg);
-    if (!args.length) {
-      throw new Error("No settings to set");
-    }
-
-    await this.sendCommand("SETCONF", args.join(" "));
-  }
-
-  // Public, never called?
-
-  async readBoolSetting(aSetting) {
-    let value = await this.#readBoolSetting(aSetting);
-    this.#settingsCache.set(aSetting, value);
-    return value;
-  }
-
-  async readStringSetting(aSetting) {
-    let value = await this.#readStringSetting(aSetting);
-    this.#settingsCache.set(aSetting, value);
-    return value;
-  }
-
-  // Private
-
   async #setSockets() {
     try {
       const isWindows = TorLauncherUtil.isWindows;
@@ -511,167 +385,24 @@ class TorProvider {
     }
   }
 
-  #assertValidSettingKey(aSetting) {
-    // ensure the 'key' is a string
-    if (typeof aSetting !== "string") {
-      throw new Error(
-        `Expected setting of type string but received ${typeof aSetting}`
-      );
-    }
-  }
-
-  #assertValidSetting(aSetting, aValue) {
-    this.#assertValidSettingKey(aSetting);
-    switch (typeof aValue) {
-      case "boolean":
-      case "string":
-        return;
-      case "object":
-        if (aValue === null) {
-          return;
-        } else if (Array.isArray(aValue)) {
-          for (const element of aValue) {
-            if (typeof element !== "string") {
-              throw new Error(
-                `Setting '${aSetting}' array contains value of invalid type '${typeof element}'`
-              );
-            }
-          }
-          return;
-        }
-      // fall through
-      default:
-        throw new Error(
-          `Invalid object type received for setting '${aSetting}'`
-        );
-    }
-  }
-
-  // Perform a GETCONF command.
-  async #readSetting(aSetting) {
-    this.#assertValidSettingKey(aSetting);
-
-    const cmd = "GETCONF";
-    let reply = await this.sendCommand(cmd, aSetting);
-    return TorParsers.parseReply(cmd, aSetting, reply);
-  }
-
-  async #readStringSetting(aSetting) {
-    let lineArray = await this.#readSetting(aSetting);
-    if (lineArray.length !== 1) {
-      throw new Error(
-        `Expected an array with length 1 but received array of length ${lineArray.length}`
-      );
-    }
-    return lineArray[0];
-  }
-
-  async #readBoolSetting(aSetting) {
-    const value = this.#readStringSetting(aSetting);
-    switch (value) {
-      case "0":
-        return false;
-      case "1":
-        return true;
-      default:
-        throw new Error(`Expected boolean (1 or 0) but received '${value}'`);
-    }
-  }
-
-  async #trySend(cmd, args, rethrow) {
-    let connected = false;
-    let reply;
-    let leftAttempts = 2;
-    while (leftAttempts-- > 0) {
-      let conn;
-      try {
-        conn = await this.#getConnection();
-      } catch (e) {
-        logger.error("Cannot get a connection to the control port", e);
-        if (leftAttempts == 0 && rethrow) {
-          throw e;
-        }
-      }
-      if (!conn) {
-        continue;
-      }
-      // If we _ever_ got a connection, the caller should not try again
-      connected = true;
-      try {
-        reply = await conn.sendCommand(cmd + (args ? " " + args : ""));
-        if (reply) {
-          // Return for reuse.
-          this.#returnConnection();
-        } else {
-          // Connection is bad.
-          logger.warn(
-            "sendCommand returned an empty response, taking the connection as broken and closing it."
-          );
-          this.#closeConnection();
-        }
-      } catch (e) {
-        logger.error(`Cannot send the command ${cmd}`, e);
-        this.#closeConnection();
-        if (leftAttempts == 0 && rethrow) {
-          throw e;
-        }
-      }
-    }
-    return { connected, reply };
-  }
-
-  // Opens an authenticated connection, sets it to this.#controlConnection, and
-  // return it.
   async #getConnection() {
-    if (!this.#controlConnection) {
+    if (!this.#controlConnection?.isOpen) {
       this.#controlConnection = await lazy.controller();
     }
-    if (this.#controlConnection.inUse) {
-      await new Promise((resolve, reject) =>
-        this.#connectionQueue.push({ resolve, reject })
-      );
-    } else {
-      this.#controlConnection.inUse = true;
-    }
     return this.#controlConnection;
   }
 
-  #returnConnection() {
-    if (this.#connectionQueue.length) {
-      this.#connectionQueue.shift().resolve();
-    } else {
-      this.#controlConnection.inUse = false;
-    }
-  }
-
-  async #withConnection(func) {
-    // TODO: Make more robust?
-    const conn = await this.#getConnection();
-    try {
-      return await func(conn);
-    } finally {
-      this.#returnConnection();
-    }
-  }
-
-  // If aConn is omitted, the cached connection is closed.
   #closeConnection() {
     if (this.#controlConnection) {
       logger.info("Closing the control connection");
       this.#controlConnection.close();
       this.#controlConnection = null;
     }
-    for (const promise of this.#connectionQueue) {
-      promise.reject("Connection closed");
-    }
-    this.#connectionQueue = [];
   }
 
   async #reconnect() {
     this.#closeConnection();
-    const conn = await this.#getConnection();
-    logger.debug("Reconnected to the control port.");
-    this.#returnConnection(conn);
+    await this.#getConnection();
   }
 
   async #readAuthenticationCookie(aPath) {
@@ -777,8 +508,9 @@ class TorProvider {
     if (this.ownsTorDaemon) {
       // When we own the tor daemon, we listen to more events, that are used
       // for about:torconnect or for showing the logs in the settings page.
-      this._eventHandlers.set("STATUS_CLIENT", (_eventType, lines) =>
-        this._processBootstrapStatus(lines[0], false)
+      this._eventHandlers.set(
+        "STATUS_CLIENT",
+        this._processStatusClient.bind(this)
       );
       this._eventHandlers.set("NOTICE", this._processLog.bind(this));
       this._eventHandlers.set("WARN", this._processLog.bind(this));
@@ -809,23 +541,10 @@ class TorProvider {
       throw new Error("Event monitor connection not available");
     }
 
-    // TODO: Unify with TorProtocolService.sendCommand and put everything in the
-    // reviewed torbutton replacement.
-    const cmd = "GETINFO";
-    const key = "status/bootstrap-phase";
-    let reply = await this._connection.sendCommand(`${cmd} ${key}`);
-
-    // A typical reply looks like:
-    //  250-status/bootstrap-phase=NOTICE BOOTSTRAP PROGRESS=100 TAG=done SUMMARY="Done"
-    //  250 OK
-    reply = TorParsers.parseCommandResponse(reply);
-    if (!TorParsers.commandSucceeded(reply)) {
-      throw new Error(`${cmd} failed`);
-    }
-    reply = TorParsers.parseReply(cmd, key, reply);
-    if (reply.length) {
-      this._processBootstrapStatus(reply[0], true);
-    }
+    this._processBootstrapStatus(
+      await this._connection.getBootstrapPhase(),
+      true
+    );
   }
 
   // Returns captured log message as a text string (one message per line).
@@ -1058,37 +777,32 @@ class TorProvider {
   _monitorEvent(type, callback) {
     logger.info(`Watching events of type ${type}.`);
     let replyObj = {};
-    this._connection.watchEvent(
-      type,
-      null,
-      line => {
-        if (!line) {
-          return;
-        }
-        logger.debug("Event response: ", line);
-        const isComplete = TorParsers.parseReplyLine(line, replyObj);
-        if (!isComplete || replyObj._parseError || !replyObj.lineArray.length) {
-          return;
-        }
-        const reply = replyObj;
-        replyObj = {};
-        if (reply.statusCode !== TorStatuses.EventNotification) {
-          logger.error("Unexpected event status code:", reply.statusCode);
-          return;
-        }
-        if (!reply.lineArray[0].startsWith(`${type} `)) {
-          logger.error("Wrong format for the first line:", reply.lineArray[0]);
-          return;
-        }
-        reply.lineArray[0] = reply.lineArray[0].substring(type.length + 1);
-        try {
-          callback(type, reply.lineArray);
-        } catch (e) {
-          logger.error("Exception while handling an event", reply, e);
-        }
-      },
-      true
-    );
+    this._connection.watchEvent(type, line => {
+      if (!line) {
+        return;
+      }
+      logger.debug("Event response: ", line);
+      const isComplete = TorParsers.parseReplyLine(line, replyObj);
+      if (!isComplete || replyObj._parseError || !replyObj.lineArray.length) {
+        return;
+      }
+      const reply = replyObj;
+      replyObj = {};
+      if (reply.statusCode !== TorStatuses.EventNotification) {
+        logger.error("Unexpected event status code:", reply.statusCode);
+        return;
+      }
+      if (!reply.lineArray[0].startsWith(`${type} `)) {
+        logger.error("Wrong format for the first line:", reply.lineArray[0]);
+        return;
+      }
+      reply.lineArray[0] = reply.lineArray[0].substring(type.length + 1);
+      try {
+        callback(type, reply.lineArray);
+      } catch (e) {
+        logger.error("Exception while handling an event", reply, e);
+      }
+    });
   }
 
   _processLog(type, lines) {
@@ -1116,15 +830,12 @@ class TorProvider {
   // to TorBootstrapStatus observers.
   // If aSuppressErrors is true, errors are ignored. This is used when we
   // are handling the response to a "GETINFO status/bootstrap-phase" command.
-  _processBootstrapStatus(aStatusMsg, aSuppressErrors) {
-    const statusObj = TorParsers.parseBootstrapStatus(aStatusMsg);
-    if (!statusObj) {
-      return;
-    }
-
+  _processBootstrapStatus(statusObj, suppressErrors) {
     // Notify observers
-    statusObj.wrappedJSObject = statusObj;
-    Services.obs.notifyObservers(statusObj, "TorBootstrapStatus");
+    Services.obs.notifyObservers(
+      { wrappedJSObject: statusObj },
+      "TorBootstrapStatus"
+    );
 
     if (statusObj.PROGRESS === 100) {
       this._isBootstrapDone = true;
@@ -1141,7 +852,7 @@ class TorProvider {
     if (
       statusObj.TYPE === "WARN" &&
       statusObj.RECOMMENDATION !== "ignore" &&
-      !aSuppressErrors
+      !suppressErrors
     ) {
       this._notifyBootstrapError(statusObj);
     }
@@ -1184,6 +895,15 @@ class TorProvider {
     }
   }
 
+  _processStatusClient(_type, lines) {
+    const statusObj = TorParsers.parseBootstrapStatus(lines[0]);
+    if (!statusObj) {
+      // No `BOOTSTRAP` in the line
+      return;
+    }
+    this._processBootstrapStatus(statusObj, false);
+  }
+
   async _processCircEvent(_type, lines) {
     const builtEvent =
       /^(?<CircuitID>[a-zA-Z0-9]{1,16})\sBUILT\s(?<Path>(?:,?\$[0-9a-fA-F]{40}(?:~[a-zA-Z0-9]{1,19})?)+)/.exec(
@@ -1295,7 +1015,3 @@ class TorProvider {
     this.clearBootstrapError();
   }
 }
-
-// TODO: Stop defining TorProtocolService, make the builder instance the
-// TorProvider.
-export const TorProtocolService = new TorProvider();


=====================================
toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs
=====================================
@@ -4,7 +4,7 @@
 
 const lazy = {};
 ChromeUtils.defineESModuleGetters(lazy, {
-  TorProtocolService: "resource://gre/modules/TorProtocolService.sys.mjs",
+  TorProvider: "resource://gre/modules/TorProvider.sys.mjs",
 });
 
 export const TorProviderTopics = Object.freeze({
@@ -19,16 +19,25 @@ export const TorProviderTopics = Object.freeze({
 });
 
 export class TorProviderBuilder {
+  static #provider = null;
+
   static async init() {
-    await lazy.TorProtocolService.init();
+    const provider = new lazy.TorProvider();
+    await provider.init();
+    // Assign it only when initialization succeeds.
+    TorProviderBuilder.#provider = provider;
   }
 
   static uninit() {
-    lazy.TorProtocolService.uninit();
+    TorProviderBuilder.#provider.uninit();
+    TorProviderBuilder.#provider = null;
   }
 
   // TODO: Switch to an async build?
   static build() {
-    return lazy.TorProtocolService;
+    if (!TorProviderBuilder.#provider) {
+      throw new Error("TorProviderBuilder has not been initialized yet.");
+    }
+    return TorProviderBuilder.#provider;
   }
 }


=====================================
toolkit/components/tor-launcher/TorStartupService.sys.mjs
=====================================
@@ -3,22 +3,13 @@ const lazy = {};
 // We will use the modules only when the profile is loaded, so prefer lazy
 // loading
 ChromeUtils.defineESModuleGetters(lazy, {
+  TorConnect: "resource:///modules/TorConnect.sys.mjs",
   TorDomainIsolator: "resource://gre/modules/TorDomainIsolator.sys.mjs",
   TorLauncherUtil: "resource://gre/modules/TorLauncherUtil.sys.mjs",
   TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
+  TorSettings: "resource:///modules/TorSettings.sys.mjs",
 });
 
-ChromeUtils.defineModuleGetter(
-  lazy,
-  "TorConnect",
-  "resource:///modules/TorConnect.jsm"
-);
-ChromeUtils.defineModuleGetter(
-  lazy,
-  "TorSettings",
-  "resource:///modules/TorSettings.jsm"
-);
-
 /* Browser observer topis */
 const BrowserTopics = Object.freeze({
   ProfileAfterChange: "profile-after-change",


=====================================
toolkit/components/tor-launcher/moz.build
=====================================
@@ -3,10 +3,9 @@ EXTRA_JS_MODULES += [
     "TorControlPort.sys.mjs",
     "TorDomainIsolator.sys.mjs",
     "TorLauncherUtil.sys.mjs",
-    "TorMonitorService.sys.mjs",
     "TorParsers.sys.mjs",
     "TorProcess.sys.mjs",
-    "TorProtocolService.sys.mjs",
+    "TorProvider.sys.mjs",
     "TorProviderBuilder.sys.mjs",
     "TorStartupService.sys.mjs",
 ]


=====================================
toolkit/mozapps/update/UpdateService.sys.mjs
=====================================
@@ -23,18 +23,13 @@ ChromeUtils.defineESModuleGetters(lazy, {
   AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
   CertUtils: "resource://gre/modules/CertUtils.sys.mjs",
   DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
+  TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
   UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
   WindowsRegistry: "resource://gre/modules/WindowsRegistry.sys.mjs",
   ctypes: "resource://gre/modules/ctypes.sys.mjs",
   setTimeout: "resource://gre/modules/Timer.sys.mjs",
 });
 
-ChromeUtils.defineModuleGetter(
-  lazy,
-  "TorMonitorService",
-  "resource://gre/modules/TorMonitorService.jsm"
-);
-
 XPCOMUtils.defineLazyServiceGetter(
   lazy,
   "AUS",
@@ -394,10 +389,11 @@ XPCOMUtils.defineLazyGetter(
 );
 
 function _shouldRegisterBootstrapObserver(errorCode) {
+  const provider = lazy.TorProviderBuilder.build();
   return (
     errorCode == PROXY_SERVER_CONNECTION_REFUSED &&
-    !lazy.TorMonitorService.isBootstrapDone &&
-    lazy.TorMonitorService.ownsTorDaemon
+    !provider.isBootstrapDone &&
+    provider.ownsTorDaemon
   );
 }
 
@@ -5833,10 +5829,7 @@ Downloader.prototype = {
       // we choose to compute these hashes.
       hash = hash.finish(false);
       digest = Array.from(hash, (c, i) =>
-        hash
-          .charCodeAt(i)
-          .toString(16)
-          .padStart(2, "0")
+        hash.charCodeAt(i).toString(16).padStart(2, "0")
       ).join("");
     } catch (e) {
       LOG(



View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/ab8a15b721bc218d4b1588f06337c96054cac91b...384dff974998a27132ad4669f2ff2a0a07a4c274

-- 
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/ab8a15b721bc218d4b1588f06337c96054cac91b...384dff974998a27132ad4669f2ff2a0a07a4c274
You're receiving this email because of your account on gitlab.torproject.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.torproject.org/pipermail/tbb-commits/attachments/20230807/c5826d54/attachment-0001.htm>


More information about the tbb-commits mailing list