[tbb-commits] [Git][tpo/applications/tor-browser][tor-browser-115.10.0esr-13.5-1] 20 commits: fixup! Bug 40597: Implement TorSettings module

Pier Angelo Vendrame (@pierov) git at gitlab.torproject.org
Mon Apr 15 16:00:36 UTC 2024



Pier Angelo Vendrame pushed to branch tor-browser-115.10.0esr-13.5-1 at The Tor Project / Applications / Tor Browser


Commits:
3865ebab by Pier Angelo Vendrame at 2024-04-15T17:59:33+02:00
fixup! Bug 40597: Implement TorSettings module

Move TorSettings and TorLauncherUtil to lazy imnports.
Also removed XPCOMUtils in favor of ChromeUtils.

- - - - -
d53805c2 by Pier Angelo Vendrame at 2024-04-15T17:59:36+02:00
fixup! Temporary changes to about:torconnect for Android.

- - - - -
7720009e by Pier Angelo Vendrame at 2024-04-15T17:59:36+02:00
fixup! Bug 40933: Add tor-launcher functionality

Rename TorControlPort.sys.mjs's `TorError` to a less generic
`TorProtocolError`.

- - - - -
bb516579 by Pier Angelo Vendrame at 2024-04-15T17:59:37+02:00
dropme! Bug 42247: Android helpers for the TorProvider

Revert the changes to TorProvider*.sys.mjs to move them to the correct
commit.

- - - - -
76623561 by Dan Ballard at 2024-04-15T17:59:37+02:00
fixup! Bug 40933: Add tor-launcher functionality

Bug 41187: Add support for tor logs in android integration

- - - - -
8d41f191 by Pier Angelo Vendrame at 2024-04-15T17:59:37+02:00
fixup! Bug 40933: Add tor-launcher functionality

Do not localize errors in TorProcess, but keep it only as a pure backend
file. Moreover, the localized errors would be only shown in the console.
Instead, report an error code to let them bubble up to the
TorProviderBuilder, where we show them in the "Restart Tor" prompt.

- - - - -
05bda694 by Pier Angelo Vendrame at 2024-04-15T17:59:38+02:00
fixup! Bug 27476: Implement about:torconnect captive portal within Tor Browser

Formatted comments.

- - - - -
d783693d by Pier Angelo Vendrame at 2024-04-15T17:59:38+02:00
fixup! Bug 40933: Add tor-launcher functionality

Bug 42479: Remove localization from the backend files.

Also, use an Error object when the bootstrap fail, in preparation to the
removal of TorBootstrapRequest.

- - - - -
7dc831bf by Pier Angelo Vendrame at 2024-04-15T17:59:38+02:00
fixup! Add TorStrings module for localization

Load some torlauncher strings to show in about:torconnect in
TorStrings.sys.mjs.

We could move them to torConnect.properties, but maybe we are still in
time for moving all of them to Fluent, so I preferred reducing the
number of migrations.

Also, remove unused strings from torlauncher.properties.

- - - - -
04fbc3dc by Pier Angelo Vendrame at 2024-04-15T17:59:39+02:00
fixup! Bug 40597: Implement TorSettings module

Bug 42479: Improve TorConnect error handling.

To do so, also remove localized error messages.
Use error codes, and let the frontend use the localized messages.

- - - - -
00c53294 by Pier Angelo Vendrame at 2024-04-15T17:59:39+02:00
fixup! Bug 27476: Implement about:torconnect captive portal within Tor Browser

Bug 42479: Improve TorConnect error handling.

Consume the new error codes and transform them into localized error
messages.

- - - - -
13ca87b4 by Pier Angelo Vendrame at 2024-04-15T17:59:39+02:00
fixup! Bug 42247: Android helpers for the TorProvider

Bug 42479: Improve TorConnect error handling.

Pass the new TorConnect error codes to Android.

- - - - -
1e2083d7 by Pier Angelo Vendrame at 2024-04-15T17:59:40+02:00
fixup! Bug 40933: Add tor-launcher functionality

This partially reverts commit 34f7e83335cde6653d897717741c9c8d41f858b7.

It removes the errorCode logic, and throws non-localized error
messages.

Also, it removes the no-longer used getFormattedLocalizedString.

- - - - -
05516a28 by Pier Angelo Vendrame at 2024-04-15T17:59:40+02:00
fixup! Bug 40933: Add tor-launcher functionality

Changed a few calls to #stop since this method now expects an Erro
object.

Do not redefine topics, import them from TorProviderBuilder.sys.mjs.

Use lazy imports for our code.

- - - - -
01d7d557 by Pier Angelo Vendrame at 2024-04-15T17:59:40+02:00
fixup! Bug 40597: Implement TorSettings module

Various fixes to address tor-browser!968.

- - - - -
02df5609 by Pier Angelo Vendrame at 2024-04-15T17:59:41+02:00
fixup! Bug 27476: Implement about:torconnect captive portal within Tor Browser

Various fixes to address tor-browser!968.

- - - - -
76ee8c58 by Pier Angelo Vendrame at 2024-04-15T17:59:41+02:00
fixup! Bug 42247: Android helpers for the TorProvider

TorConnectTopics.BootstrapError is now just Error, and it passes
directly the Error object.

- - - - -
686fb7e7 by henry at 2024-04-15T17:59:41+02:00
fixup! Add TorStrings module for localization

Drop unused strings.
- - - - -
ec7b6520 by henry at 2024-04-15T17:59:42+02:00
fixup! Bug 27476: Implement about:torconnect captive portal within Tor Browser

Fix the offline error handler.
- - - - -
e162a17a by henry at 2024-04-15T17:59:42+02:00
fixup! Bug 40597: Implement TorSettings module

Remove an outdated comment.
- - - - -


12 changed files:

- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorIntegrationAndroid.java
- toolkit/components/tor-launcher/TorBootstrapRequest.sys.mjs
- toolkit/components/tor-launcher/TorControlPort.sys.mjs
- toolkit/components/tor-launcher/TorLauncherUtil.sys.mjs
- toolkit/components/tor-launcher/TorProcess.sys.mjs
- toolkit/components/tor-launcher/TorProvider.sys.mjs
- toolkit/components/torconnect/TorConnectParent.sys.mjs
- toolkit/components/torconnect/content/aboutTorConnect.js
- toolkit/modules/TorAndroidIntegration.sys.mjs
- toolkit/modules/TorConnect.sys.mjs
- toolkit/modules/TorStrings.sys.mjs
- toolkit/torbutton/chrome/locale/en-US/torlauncher.properties


Changes:

=====================================
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorIntegrationAndroid.java
=====================================
@@ -42,10 +42,10 @@ public class TorIntegrationAndroid implements BundleEventListener {
     private static final String EVENT_TOR_STOP = "GeckoView:Tor:StopTor";
     private static final String EVENT_MEEK_START = "GeckoView:Tor:StartMeek";
     private static final String EVENT_MEEK_STOP = "GeckoView:Tor:StopMeek";
-    private static final String EVENT_BOOTSTRAP_STATE_CHANGED = "GeckoView:Tor:BootstrapStateChanged";
+    private static final String EVENT_CONNECT_STATE_CHANGED = "GeckoView:Tor:ConnectStateChanged";
+    private static final String EVENT_CONNECT_ERROR = "GeckoView:Tor:ConnectError";
     private static final String EVENT_BOOTSTRAP_PROGRESS = "GeckoView:Tor:BootstrapProgress";
     private static final String EVENT_BOOTSTRAP_COMPLETE = "GeckoView:Tor:BootstrapComplete";
-    private static final String EVENT_BOOTSTRAP_ERROR = "GeckoView:Tor:BootstrapError";
     private static final String EVENT_TOR_LOGS = "GeckoView:Tor:Logs";
     private static final String EVENT_SETTINGS_READY = "GeckoView:Tor:SettingsReady";
     private static final String EVENT_SETTINGS_CHANGED = "GeckoView:Tor:SettingsChanged";
@@ -115,10 +115,10 @@ public class TorIntegrationAndroid implements BundleEventListener {
                         EVENT_MEEK_STOP,
                         EVENT_SETTINGS_READY,
                         EVENT_SETTINGS_CHANGED,
-                        EVENT_BOOTSTRAP_STATE_CHANGED,
+                        EVENT_CONNECT_STATE_CHANGED,
+                        EVENT_CONNECT_ERROR,
                         EVENT_BOOTSTRAP_PROGRESS,
                         EVENT_BOOTSTRAP_COMPLETE,
-                        EVENT_BOOTSTRAP_ERROR,
                         EVENT_TOR_LOGS,
                         EVENT_SETTINGS_OPEN);
     }
@@ -148,28 +148,29 @@ public class TorIntegrationAndroid implements BundleEventListener {
             } else {
                 Log.w(TAG, "Ignoring a settings changed event that did not have the new settings.");
             }
-        } else if (EVENT_BOOTSTRAP_STATE_CHANGED.equals(event)) {
+        } else if (EVENT_CONNECT_STATE_CHANGED.equals(event)) {
             String state = message.getString("state");
             for (BootstrapStateChangeListener listener: mBootstrapStateListeners) {
                 listener.onBootstrapStateChange(state);
             }
+        } else if (EVENT_CONNECT_ERROR.equals(event)) {
+            String code = message.getString("code");
+            String msg = message.getString("message");
+            String phase = message.getString("phase");
+            String reason = message.getString("reason");
+            for (BootstrapStateChangeListener listener: mBootstrapStateListeners) {
+                listener.onBootstrapError(code, msg, phase, reason);
+            }
         } else if (EVENT_BOOTSTRAP_PROGRESS.equals(event)) {
             double progress = message.getDouble("progress");
-            String status = message.getString("status");
             boolean hasWarnings = message.getBoolean("hasWarnings");
             for (BootstrapStateChangeListener listener: mBootstrapStateListeners) {
-                listener.onBootstrapProgress(progress, status, hasWarnings);
+                listener.onBootstrapProgress(progress, hasWarnings);
             }
         } else if (EVENT_BOOTSTRAP_COMPLETE.equals(event)) {
             for (BootstrapStateChangeListener listener: mBootstrapStateListeners) {
                 listener.onBootstrapComplete();
             }
-        } else if (EVENT_BOOTSTRAP_ERROR.equals(event)) {
-            String msg = message.getString("message");
-            String details = message.getString("details");
-            for (BootstrapStateChangeListener listener: mBootstrapStateListeners) {
-                listener.onBootstrapError(msg, details);
-            }
         } else if (EVENT_TOR_LOGS.equals(event)) {
             String msg = message.getString("message");
             String type = message.getString("logType");
@@ -575,9 +576,9 @@ public class TorIntegrationAndroid implements BundleEventListener {
 
     public interface BootstrapStateChangeListener {
         void onBootstrapStateChange(String state);
-        void onBootstrapProgress(double progress, String status, boolean hasWarnings);
+        void onBootstrapProgress(double progress, boolean hasWarnings);
         void onBootstrapComplete();
-        void onBootstrapError(String message, String details);
+        void onBootstrapError(String code, String message, String phase, String reason);
         void onSettingsRequested();
     }
 


=====================================
toolkit/components/tor-launcher/TorBootstrapRequest.sys.mjs
=====================================
@@ -1,17 +1,15 @@
 import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";
 
-import { TorProviderBuilder } from "resource://gre/modules/TorProviderBuilder.sys.mjs";
-import { TorLauncherUtil } from "resource://gre/modules/TorLauncherUtil.sys.mjs";
-
-/* tor-launcher observer topics */
-export const TorTopics = Object.freeze({
-  BootstrapStatus: "TorBootstrapStatus",
-  BootstrapError: "TorBootstrapError",
-  LogHasWarnOrErr: "TorLogHasWarnOrErr",
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+  TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
+  TorProviderTopics: "resource://gre/modules/TorProviderBuilder.sys.mjs",
 });
 
 // modeled after XMLHttpRequest
 // nicely encapsulates the observer register/unregister logic
+// TODO: Remove this class, and move its logic inside the TorProvider.
 export class TorBootstrapRequest {
   // number of ms to wait before we abandon the bootstrap attempt
   // a value of 0 implies we never wait
@@ -20,7 +18,7 @@ export class TorBootstrapRequest {
   // callbacks for bootstrap process status updates
   onbootstrapstatus = (progress, status) => {};
   onbootstrapcomplete = () => {};
-  onbootstraperror = (message, details) => {};
+  onbootstraperror = error => {};
 
   // internal resolve() method for bootstrap
   #bootstrapPromiseResolve = null;
@@ -30,10 +28,10 @@ export class TorBootstrapRequest {
   observe(subject, topic, data) {
     const obj = subject?.wrappedJSObject;
     switch (topic) {
-      case TorTopics.BootstrapStatus: {
+      case lazy.TorProviderTopics.BootstrapStatus: {
         const progress = obj.PROGRESS;
-        const status = TorLauncherUtil.getLocalizedBootstrapStatus(obj, "TAG");
         if (this.onbootstrapstatus) {
+          const status = obj.TAG;
           this.onbootstrapstatus(progress, status);
         }
         if (progress === 100) {
@@ -47,9 +45,11 @@ export class TorBootstrapRequest {
 
         break;
       }
-      case TorTopics.BootstrapError: {
+      case lazy.TorProviderTopics.BootstrapError: {
         console.info("TorBootstrapRequest: observerd TorBootstrapError", obj);
-        this.#stop(obj?.message, obj?.details);
+        const error = new Error(obj.summary);
+        Object.assign(error, obj);
+        this.#stop(error);
         break;
       }
     }
@@ -65,17 +65,17 @@ export class TorBootstrapRequest {
       this.#bootstrapPromiseResolve = resolve;
 
       // register ourselves to listen for bootstrap events
-      Services.obs.addObserver(this, TorTopics.BootstrapStatus);
-      Services.obs.addObserver(this, TorTopics.BootstrapError);
+      Services.obs.addObserver(this, lazy.TorProviderTopics.BootstrapStatus);
+      Services.obs.addObserver(this, lazy.TorProviderTopics.BootstrapError);
 
       // optionally cancel bootstrap after a given timeout
       if (this.timeout > 0) {
-        this.#timeoutID = setTimeout(async () => {
+        this.#timeoutID = setTimeout(() => {
           this.#timeoutID = null;
-          // TODO: Translate, if really used
-          await this.#stop(
-            "Tor Bootstrap process timed out",
-            `Bootstrap attempt abandoned after waiting ${this.timeout} ms`
+          this.#stop(
+            new Error(
+              `Bootstrap attempt abandoned after waiting ${this.timeout} ms`
+            )
           );
         }, this.timeout);
       }
@@ -83,15 +83,15 @@ export class TorBootstrapRequest {
       // Wait for bootstrapping to begin and maybe handle error.
       // Notice that we do not resolve the promise here in case of success, but
       // we do it from the BootstrapStatus observer.
-      TorProviderBuilder.build()
+      lazy.TorProviderBuilder.build()
         .then(provider => provider.connect())
         .catch(err => {
-          this.#stop(err.message, err.torMessage);
+          this.#stop(err);
         });
     }).finally(() => {
       // and remove ourselves once bootstrap is resolved
-      Services.obs.removeObserver(this, TorTopics.BootstrapStatus);
-      Services.obs.removeObserver(this, TorTopics.BootstrapError);
+      Services.obs.removeObserver(this, lazy.TorProviderTopics.BootstrapStatus);
+      Services.obs.removeObserver(this, lazy.TorProviderTopics.BootstrapError);
       this.#bootstrapPromise = null;
     });
 
@@ -103,7 +103,7 @@ export class TorBootstrapRequest {
   }
 
   // Internal implementation. Do not use directly, but call cancel, instead.
-  async #stop(message, details) {
+  async #stop(error) {
     // first stop our bootstrap timeout before handling the error
     if (this.#timeoutID !== null) {
       clearTimeout(this.#timeoutID);
@@ -112,7 +112,7 @@ export class TorBootstrapRequest {
 
     let provider;
     try {
-      provider = await TorProviderBuilder.build();
+      provider = await lazy.TorProviderBuilder.build();
     } catch {
       // This was probably the error that lead to stop in the first place.
       // No need to continue propagating it.
@@ -121,14 +121,13 @@ export class TorBootstrapRequest {
       await provider?.stopBootstrap();
     } catch (e) {
       console.error("Failed to stop the bootstrap.", e);
-      if (!message) {
-        message = e.message;
-        details = "";
+      if (!error) {
+        error = e;
       }
     }
 
-    if (this.onbootstraperror && message) {
-      this.onbootstraperror(message, details);
+    if (this.onbootstraperror && error) {
+      this.onbootstraperror(error);
     }
 
     this.#bootstrapPromiseResolve(false);


=====================================
toolkit/components/tor-launcher/TorControlPort.sys.mjs
=====================================
@@ -311,10 +311,10 @@ class AsyncSocket {
  * @param {string} message The message to handle
  */
 
-class TorError extends Error {
+class TorProtocolError extends Error {
   constructor(command, reply) {
     super(`${command} -> ${reply}`);
-    this.name = "TorError";
+    this.name = "TorProtocolError";
     const info = reply.match(/(?<code>\d{3})(?:\s(?<message>.+))?/);
     this.torStatusCode = info.groups.code;
     if (info.groups.message) {
@@ -591,7 +591,7 @@ export class TorController {
   async #sendCommandSimple(command) {
     const reply = await this.#sendCommand(command);
     if (!/^250 OK\s*$/i.test(reply)) {
-      throw new TorError(command, reply);
+      throw new TorProtocolError(command, reply);
     }
   }
 
@@ -672,7 +672,7 @@ export class TorController {
       reply.match(/^250-([^=]+)=(.*)$/m) ||
       reply.match(/^250\+([^=]+)=\r?\n(.*?)\r?\n^\.\r?\n^250 OK\s*$/ms);
     if (!match || match[1] !== key) {
-      throw new TorError(cmd, reply);
+      throw new TorProtocolError(cmd, reply);
     }
     return match[2];
   }
@@ -784,7 +784,7 @@ export class TorController {
       TorParsers.unescapeString(m[1])
     );
     if (!values.length) {
-      throw new TorError(cmd, reply);
+      throw new TorProtocolError(cmd, reply);
     }
     return values;
   }
@@ -896,7 +896,7 @@ export class TorController {
     const message = await this.#sendCommand(cmd);
     // Either `250-CLIENT`, or `250 OK` if no keys are available.
     if (!message.startsWith("250")) {
-      throw new TorError(cmd, message);
+      throw new TorProtocolError(cmd, message);
     }
     const re =
       /^250-CLIENT\s+(?<HSAddress>[A-Za-z2-7]+)\s+(?<KeyType>[^:]+):(?<PrivateKeyBlob>\S+)(?:\s(?<other>.+))?$/gim;
@@ -936,7 +936,7 @@ export class TorController {
     const reply = await this.#sendCommand(cmd);
     const status = reply.substring(0, 3);
     if (status !== "250" && status !== "251" && status !== "252") {
-      throw new TorError(cmd, reply);
+      throw new TorProtocolError(cmd, reply);
     }
   }
 
@@ -952,7 +952,7 @@ export class TorController {
     const reply = await this.#sendCommand(cmd);
     const status = reply.substring(0, 3);
     if (status !== "250" && status !== "251") {
-      throw new TorError(cmd, reply);
+      throw new TorProtocolError(cmd, reply);
     }
   }
 
@@ -1085,6 +1085,7 @@ export class TorController {
       );
     }
     const status = {
+      // Type is actually StatusSeverity in the specifications.
       TYPE: match[1],
       ...this.#getKeyValues(match[2]),
     };


=====================================
toolkit/components/tor-launcher/TorLauncherUtil.sys.mjs
=====================================
@@ -417,70 +417,6 @@ export const TorLauncherUtil = Object.freeze({
     return aStringName;
   },
 
-  // "torlauncher." is prepended to aStringName.
-  getFormattedLocalizedString(aStringName, aArray, aLen) {
-    if (!aStringName || !aArray) {
-      return aStringName;
-    }
-    try {
-      const key = kPropNamePrefix + aStringName;
-      return this._stringBundle.formatStringFromName(key, aArray, aLen);
-    } catch (e) {}
-    return aStringName;
-  },
-
-  getLocalizedBootstrapStatus(aStatusObj, aKeyword) {
-    if (!aStatusObj || !aKeyword) {
-      return "";
-    }
-
-    let result;
-    let fallbackStr;
-    if (aStatusObj[aKeyword]) {
-      let val = aStatusObj[aKeyword].toLowerCase();
-      let key;
-      if (aKeyword === "TAG") {
-        // The bootstrap status tags in tagMap below are used by Tor
-        // versions prior to 0.4.0.x. We map each one to the tag that will
-        // produce the localized string that is the best fit.
-        const tagMap = {
-          conn_dir: "conn",
-          handshake_dir: "onehop_create",
-          conn_or: "enough_dirinfo",
-          handshake_or: "ap_conn",
-        };
-        if (val in tagMap) {
-          val = tagMap[val];
-        }
-
-        key = "bootstrapStatus." + val;
-        fallbackStr = aStatusObj.SUMMARY;
-      } else if (aKeyword === "REASON") {
-        if (val === "connectreset") {
-          val = "connectrefused";
-        }
-
-        key = "bootstrapWarning." + val;
-        fallbackStr = aStatusObj.WARNING;
-      }
-
-      result = TorLauncherUtil.getLocalizedString(key);
-      if (result === key) {
-        result = undefined;
-      }
-    }
-
-    if (!result) {
-      result = fallbackStr;
-    }
-
-    if (aKeyword === "REASON" && aStatusObj.HOSTADDR) {
-      result += " - " + aStatusObj.HOSTADDR;
-    }
-
-    return result ? result : "";
-  },
-
   /**
    * Determine what kind of SOCKS port has been requested for this session or
    * the browser has been configured for.


=====================================
toolkit/components/tor-launcher/TorProcess.sys.mjs
=====================================
@@ -193,54 +193,54 @@ export class TorProcess {
 
   #makeArgs() {
     this.#exeFile = lazy.TorLauncherUtil.getTorFile("tor", false);
+    if (!this.#exeFile) {
+      throw new Error("Could not find the tor binary.");
+    }
     const torrcFile = lazy.TorLauncherUtil.getTorFile("torrc", true);
+    if (!torrcFile) {
+      // FIXME: Is this still a fatal error?
+      throw new Error("Could not find the torrc.");
+    }
     // Get the Tor data directory first so it is created before we try to
     // construct paths to files that will be inside it.
     this.#dataDir = lazy.TorLauncherUtil.getTorFile("tordatadir", true);
+    if (!this.#dataDir) {
+      throw new Error("Could not find the tor data directory.");
+    }
     const onionAuthDir = lazy.TorLauncherUtil.getTorFile(
       "toronionauthdir",
       true
     );
-    let detailsKey;
-    if (!this.#exeFile) {
-      detailsKey = "tor_missing";
-    } else if (!torrcFile) {
-      detailsKey = "torrc_missing";
-    } else if (!this.#dataDir) {
-      detailsKey = "datadir_missing";
-    } else if (!onionAuthDir) {
-      detailsKey = "onionauthdir_missing";
-    }
-    if (detailsKey) {
-      const details = lazy.TorLauncherUtil.getLocalizedString(detailsKey);
-      const key = "unable_to_start_tor";
-      const err = lazy.TorLauncherUtil.getFormattedLocalizedString(
-        key,
-        [details],
-        1
-      );
-      throw new Error(err);
+    if (!onionAuthDir) {
+      throw new Error("Could not find the tor onion authentication directory.");
     }
 
+    this.#args = [];
+    this.#args.push("-f", torrcFile.path);
+    this.#args.push("DataDirectory", this.#dataDir.path);
+    this.#args.push("ClientOnionAuthDir", onionAuthDir.path);
+
+    // TODO: Create this starting from pt_config.json (tor-browser#42357).
     const torrcDefaultsFile = lazy.TorLauncherUtil.getTorFile(
       "torrc-defaults",
       false
     );
-    // The geoip and geoip6 files are in the same directory as torrc-defaults.
-    const geoipFile = torrcDefaultsFile.clone();
-    geoipFile.leafName = "geoip";
-    const geoip6File = torrcDefaultsFile.clone();
-    geoip6File.leafName = "geoip6";
-
-    this.#args = [];
     if (torrcDefaultsFile) {
       this.#args.push("--defaults-torrc", torrcDefaultsFile.path);
+      // The geoip and geoip6 files are in the same directory as torrc-defaults.
+      // TODO: Change TorFile to return the generic path to these files to make
+      // them independent from the torrc-defaults.
+      const geoipFile = torrcDefaultsFile.clone();
+      geoipFile.leafName = "geoip";
+      this.#args.push("GeoIPFile", geoipFile.path);
+      const geoip6File = torrcDefaultsFile.clone();
+      geoip6File.leafName = "geoip6";
+      this.#args.push("GeoIPv6File", geoip6File.path);
+    } else {
+      logger.warn(
+        "torrc-defaults was not found, some functionalities will be disabled."
+      );
     }
-    this.#args.push("-f", torrcFile.path);
-    this.#args.push("DataDirectory", this.#dataDir.path);
-    this.#args.push("ClientOnionAuthDir", onionAuthDir.path);
-    this.#args.push("GeoIPFile", geoipFile.path);
-    this.#args.push("GeoIPv6File", geoip6File.path);
   }
 
   /**


=====================================
toolkit/components/tor-launcher/TorProvider.sys.mjs
=====================================
@@ -911,6 +911,7 @@ export class TorProvider {
    * @param {object} status The status object
    */
   onBootstrapStatus(status) {
+    logger.debug("Received bootstrap status update", status);
     this.#processBootstrapStatus(status, true);
   }
 
@@ -939,6 +940,7 @@ export class TorProvider {
 
     this.#isBootstrapDone = false;
 
+    // Can TYPE ever be ERR for STATUS_CLIENT?
     if (
       isNotification &&
       statusObj.TYPE === "WARN" &&
@@ -959,20 +961,7 @@ export class TorProvider {
     } catch (e) {
       logger.warn(`Cannot set ${Preferences.PromptAtStartup}`, e);
     }
-    // TODO: Move l10n to the above layers?
-    const phase = TorLauncherUtil.getLocalizedBootstrapStatus(statusObj, "TAG");
-    const reason = TorLauncherUtil.getLocalizedBootstrapStatus(
-      statusObj,
-      "REASON"
-    );
-    const details = TorLauncherUtil.getFormattedLocalizedString(
-      "tor_bootstrap_failed_details",
-      [phase, reason],
-      2
-    );
-    logger.error(
-      `Tor bootstrap error: [${statusObj.TAG}/${statusObj.REASON}] ${details}`
-    );
+    logger.error("Tor bootstrap error", statusObj);
 
     if (
       statusObj.TAG !== this.#lastWarning.phase ||
@@ -981,11 +970,19 @@ export class TorProvider {
       this.#lastWarning.phase = statusObj.TAG;
       this.#lastWarning.reason = statusObj.REASON;
 
-      const message = TorLauncherUtil.getLocalizedString(
-        "tor_bootstrap_failed"
-      );
+      // FIXME: currently, this is observed only by TorBoostrapRequest.
+      // We should remove that class, and use an async method to do the
+      // bootstrap here.
+      // At that point, the lastWarning mechanism will probably not be necessary
+      // anymore, since the first error eligible for notification will as a
+      // matter of fact cancel the bootstrap.
       Services.obs.notifyObservers(
-        { message, details },
+        {
+          phase: statusObj.TAG,
+          reason: statusObj.REASON,
+          summary: statusObj.SUMMARY,
+          warning: statusObj.WARNING,
+        },
         TorProviderTopics.BootstrapError
       );
     }


=====================================
toolkit/components/torconnect/TorConnectParent.sys.mjs
=====================================
@@ -37,10 +37,9 @@ export class TorConnectParent extends JSWindowActorParent {
       State: TorConnect.state,
       StateChanged: false,
       PreviousState: TorConnectState.Initial,
-      ErrorMessage: TorConnect.errorMessage,
+      ErrorCode: TorConnect.errorCode,
       ErrorDetails: TorConnect.errorDetails,
       BootstrapProgress: TorConnect.bootstrapProgress,
-      BootstrapStatus: TorConnect.bootstrapStatus,
       InternetStatus: TorConnect.internetStatus,
       DetectedLocation: TorConnect.detectedLocation,
       ShowViewLog: TorConnect.logHasWarningOrError,
@@ -59,36 +58,36 @@ export class TorConnectParent extends JSWindowActorParent {
       this.state.QuickStartEnabled = false;
     }
 
-    // JSWindowActiveParent derived objects cannot observe directly, so create a member
-    // object to do our observing for us
+    // JSWindowActiveParent derived objects cannot observe directly, so create a
+    // member object to do our observing for us.
     //
-    // This object converts the various lifecycle events from the TorConnect module, and
-    // maintains a state object which we pass down to our about:torconnect page, which uses
-    // the state object to update its UI
+    // This object converts the various lifecycle events from the TorConnect
+    // module, and maintains a state object which we pass down to our
+    // about:torconnect page, which uses the state object to update its UI.
     this.torConnectObserver = {
       observe(aSubject, aTopic, aData) {
         let obj = aSubject?.wrappedJSObject;
 
-        // update our state struct based on received torconnect topics and forward on
-        // to aboutTorConnect.js
+        // Update our state struct based on received torconnect topics and
+        // forward on to aboutTorConnect.js.
         self.state.StateChanged = false;
         switch (aTopic) {
           case TorConnectTopics.StateChange: {
             self.state.PreviousState = self.state.State;
             self.state.State = obj.state;
             self.state.StateChanged = true;
-
-            // clear any previous error information if we are bootstrapping
+            // Clear any previous error information if we are bootstrapping.
             if (self.state.State === TorConnectState.Bootstrapping) {
-              self.state.ErrorMessage = null;
+              self.state.ErrorCode = null;
               self.state.ErrorDetails = null;
             }
+            self.state.BootstrapProgress = TorConnect.bootstrapProgress;
+            self.state.ShowViewLog = TorConnect.logHasWarningOrError;
             self.state.HasEverFailed = TorConnect.hasEverFailed;
             break;
           }
           case TorConnectTopics.BootstrapProgress: {
             self.state.BootstrapProgress = obj.progress;
-            self.state.BootstrapStatus = obj.status;
             self.state.ShowViewLog = obj.hasWarnings;
             break;
           }
@@ -96,9 +95,9 @@ export class TorConnectParent extends JSWindowActorParent {
             // noop
             break;
           }
-          case TorConnectTopics.BootstrapError: {
-            self.state.ErrorMessage = obj.message;
-            self.state.ErrorDetails = obj.details;
+          case TorConnectTopics.Error: {
+            self.state.ErrorCode = obj.code;
+            self.state.ErrorDetails = obj;
             self.state.InternetStatus = TorConnect.internetStatus;
             self.state.DetectedLocation = TorConnect.detectedLocation;
             self.state.ShowViewLog = true;
@@ -134,7 +133,7 @@ export class TorConnectParent extends JSWindowActorParent {
       },
     };
 
-    // observe all of the torconnect:.* topics
+    // Observe all of the torconnect:.* topics.
     for (const key in TorConnectTopics) {
       const topic = TorConnectTopics[key];
       Services.obs.addObserver(this.torConnectObserver, topic);
@@ -158,7 +157,7 @@ export class TorConnectParent extends JSWindowActorParent {
   }
 
   willDestroy() {
-    // stop observing all of our torconnect:.* topics
+    // Stop observing all of our torconnect:.* topics.
     for (const key in TorConnectTopics) {
       const topic = TorConnectTopics[key];
       Services.obs.removeObserver(this.torConnectObserver, topic);
@@ -213,7 +212,8 @@ export class TorConnectParent extends JSWindowActorParent {
         Services.obs.notifyObservers(message.data, BroadcastTopic);
         break;
       case "torconnect:get-init-args":
-        // called on AboutTorConnect.init(), pass down all state data it needs to init
+        // Called on AboutTorConnect.init(), pass down all state data it needs
+        // to init.
 
         // pretend this is a state transition on init
         // so we always get fresh UI


=====================================
toolkit/components/torconnect/content/aboutTorConnect.js
=====================================
@@ -347,6 +347,59 @@ class AboutTorConnect {
     this.elements.breadcrumbContainer.classList.add("hidden");
   }
 
+  getLocalizedStatus(status) {
+    const aliases = {
+      conn_dir: "conn",
+      handshake_dir: "onehop_create",
+      conn_or: "enough_dirinfo",
+      handshake_or: "ap_conn",
+    };
+    if (status in aliases) {
+      status = aliases[status];
+    }
+    return TorStrings.torConnect.bootstrapStatus[status] ?? status;
+  }
+
+  getMaybeLocalizedError(state) {
+    if (!state?.ErrorCode) {
+      return "";
+    }
+    switch (state.ErrorCode) {
+      case "Offline":
+        return TorStrings.torConnect.offline;
+      case "BootstrapError": {
+        const details = state.ErrorDetails?.cause;
+        if (!details?.phase || !details?.reason) {
+          return TorStrings.torConnect.torBootstrapFailed;
+        }
+        let status = this.getLocalizedStatus(details.phase);
+        const reason =
+          TorStrings.torConnect.bootstrapWarning[details.reason] ??
+          details.reason;
+        return TorStrings.torConnect.bootstrapFailedDetails
+          .replace("%1$S", status)
+          .replace("%2$S", reason);
+      }
+      case "CannotDetermineCountry":
+        return TorStrings.torConnect.cannotDetermineCountry;
+      case "NoSettingsForCountry":
+        return TorStrings.torConnect.noSettingsForCountry;
+      case "AllSettingsFailed":
+        return TorStrings.torConnect.autoBootstrappingAllFailed;
+      case "ExternaError":
+        // A standard JS error, or something for which we do probably do not
+        // have a translation. Returning the original message is the best we can
+        // do.
+        return state.ErrorDetails.message;
+      default:
+        console.warn(
+          `Unknown error code: ${state.ErrorCode}`,
+          state.ErrorDetails
+        );
+        return state.ErrorDetails?.message ?? state.ErrorCode;
+    }
+  }
+
   /*
   These methods update the UI based on the current TorConnect state
   */
@@ -505,10 +558,10 @@ class AboutTorConnect {
     }
   }
 
-  showOffline(error) {
+  showOffline(state) {
     this.setTitle(TorStrings.torConnect.noInternet, "offline");
     this.setLongText(TorStrings.torConnect.noInternetDescription);
-    this.setProgress(error, false);
+    this.setProgress(this.getMaybeLocalizedError(state), false);
     this.setBreadcrumbsStatus(
       BreadcrumbStatus.Default,
       BreadcrumbStatus.Active,
@@ -524,7 +577,7 @@ class AboutTorConnect {
   showConnectionAssistant(state) {
     this.setTitle(TorStrings.torConnect.couldNotConnect, "assist");
     this.showConfigureConnectionLink(TorStrings.torConnect.assistDescription);
-    this.setProgress(state?.ErrorDetails, false);
+    this.setProgress(this.getMaybeLocalizedError(state), false);
     this.setBreadcrumbsStatus(
       BreadcrumbStatus.Default,
       BreadcrumbStatus.Active,
@@ -544,7 +597,7 @@ class AboutTorConnect {
     this.showConfigureConnectionLink(
       TorStrings.torConnect.errorLocationDescription
     );
-    this.setProgress(state.ErrorMessage, false);
+    this.setProgress(TorStrings.torConnect.cannotDetermineCountry, false);
     this.setBreadcrumbsStatus(
       BreadcrumbStatus.Default,
       BreadcrumbStatus.Active,
@@ -564,7 +617,7 @@ class AboutTorConnect {
     this.showConfigureConnectionLink(
       TorStrings.torConnect.isLocationCorrectDescription
     );
-    this.setProgress(state.ErrorMessage, false);
+    this.setProgress(this.getMaybeLocalizedError(state), false);
     this.setBreadcrumbsStatus(
       BreadcrumbStatus.Default,
       BreadcrumbStatus.Default,
@@ -582,7 +635,7 @@ class AboutTorConnect {
   showFinalError(state) {
     this.setTitle(TorStrings.torConnect.finalError, "final");
     this.setLongText(TorStrings.torConnect.finalErrorDescription);
-    this.setProgress(state ? state.ErrorDetails : "", false);
+    this.setProgress(this.getMaybeLocalizedError(state), false);
     this.setBreadcrumbsStatus(
       BreadcrumbStatus.Default,
       BreadcrumbStatus.Default,
@@ -666,7 +719,7 @@ class AboutTorConnect {
   }
 
   initElements(direction) {
-    const isAndroid = navigator.userAgent.indexOf("Android") !== -1;
+    const isAndroid = navigator.userAgent.includes("Android");
     document.body.classList.toggle("android", isAndroid);
 
     document.documentElement.setAttribute("dir", direction);


=====================================
toolkit/modules/TorAndroidIntegration.sys.mjs
=====================================
@@ -29,10 +29,10 @@ const logger = new ConsoleAPI({
 const EmittedEvents = Object.freeze({
   settingsReady: "GeckoView:Tor:SettingsReady",
   settingsChanged: "GeckoView:Tor:SettingsChanged",
-  bootstrapStateChanged: "GeckoView:Tor:BootstrapStateChanged",
+  connectStateChanged: "GeckoView:Tor:ConnectStateChanged",
+  connectError: "GeckoView:Tor:ConnectError",
   bootstrapProgress: "GeckoView:Tor:BootstrapProgress",
   bootstrapComplete: "GeckoView:Tor:BootstrapComplete",
-  bootstrapError: "GeckoView:Tor:BootstrapError",
   torLogs: "GeckoView:Tor:Logs",
 });
 
@@ -98,15 +98,14 @@ class TorAndroidIntegrationImpl {
         break;
       case lazy.TorConnectTopics.StateChange:
         lazy.EventDispatcher.instance.sendRequest({
-          type: EmittedEvents.bootstrapStateChanged,
+          type: EmittedEvents.connectStateChanged,
           state: subj.wrappedJSObject.state ?? "",
         });
         break;
       case lazy.TorConnectTopics.BootstrapProgress:
         lazy.EventDispatcher.instance.sendRequest({
           type: EmittedEvents.bootstrapProgress,
-          progress: subj.wrappedJSObject.progress ?? "",
-          status: subj.wrappedJSObject.status ?? 0,
+          progress: subj.wrappedJSObject.progress ?? 0,
           hasWarnings: subj.wrappedJSObject.hasWarnings ?? false,
         });
         break;
@@ -115,11 +114,13 @@ class TorAndroidIntegrationImpl {
           type: EmittedEvents.bootstrapComplete,
         });
         break;
-      case lazy.TorConnectTopics.BootstrapError:
+      case lazy.TorConnectTopics.Error:
         lazy.EventDispatcher.instance.sendRequest({
-          type: EmittedEvents.bootstrapError,
+          type: EmittedEvents.connectError,
+          code: subj.wrappedJSObject.code ?? "",
           message: subj.wrappedJSObject.message ?? "",
-          details: subj.wrappedJSObject.details ?? "",
+          phase: subj.wrappedJSObject.cause?.phase ?? "",
+          reason: subj.wrappedJSObject.cause?.reason ?? "",
         });
         break;
       case lazy.TorProviderTopics.TorLog:


=====================================
toolkit/modules/TorConnect.sys.mjs
=====================================
@@ -2,7 +2,6 @@
  * 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 { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
 import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";
 
 const lazy = {};
@@ -13,6 +12,9 @@ ChromeUtils.defineESModuleGetters(lazy, {
   MoatRPC: "resource://gre/modules/Moat.sys.mjs",
   TorBootstrapRequest: "resource://gre/modules/TorBootstrapRequest.sys.mjs",
   TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
+  TorProviderTopics: "resource://gre/modules/TorProviderBuilder.sys.mjs",
+  TorLauncherUtil: "resource://gre/modules/TorLauncherUtil.sys.mjs",
+  TorSettings: "resource://gre/modules/TorSettings.sys.mjs",
 });
 
 // TODO: Should we move this to the about:torconnect actor?
@@ -22,16 +24,6 @@ ChromeUtils.defineModuleGetter(
   "resource:///modules/BrowserWindowTracker.jsm"
 );
 
-import { TorLauncherUtil } from "resource://gre/modules/TorLauncherUtil.sys.mjs";
-import { TorSettings } from "resource://gre/modules/TorSettings.sys.mjs";
-
-import { TorStrings } from "resource://gre/modules/TorStrings.sys.mjs";
-
-const TorTopics = Object.freeze({
-  LogHasWarnOrErr: "TorLogHasWarnOrErr",
-  ProcessExited: "TorProcessExited",
-});
-
 /* Relevant prefs used by tor-launcher */
 const TorLauncherPrefs = Object.freeze({
   prompt_at_startup: "extensions.torlauncher.prompt_at_startup",
@@ -60,7 +52,23 @@ export const TorConnectState = Object.freeze({
   Disabled: "Disabled",
 });
 
-XPCOMUtils.defineLazyGetter(
+export class TorConnectError extends Error {
+  static Offline = "Offline";
+  static BootstrapError = "BootstrapError";
+  static CannotDetermineCountry = "CannotDetermineCountry";
+  static NoSettingsForCountry = "NoSettingsForCountry";
+  static AllSettingsFailed = "AllSettingsFailed";
+  static ExternalError = "ExternalError";
+
+  constructor(code, cause) {
+    super(cause?.message ?? `TorConnectError: ${code}`, cause ? { cause } : {});
+    this.name = "TorConnectError";
+    this.code = code;
+  }
+}
+Object.freeze(TorConnectError);
+
+ChromeUtils.defineLazyGetter(
   lazy,
   "logger",
   () =>
@@ -122,7 +130,7 @@ export const TorConnectTopics = Object.freeze({
   StateChange: "torconnect:state-change",
   BootstrapProgress: "torconnect:bootstrap-progress",
   BootstrapComplete: "torconnect:bootstrap-complete",
-  BootstrapError: "torconnect:bootstrap-error",
+  Error: "torconnect:error",
 });
 
 // The StateCallback is the base class to implement the various states.
@@ -173,7 +181,7 @@ class StateCallback {
         `${this.#state}'s run threw, transitioning to the Error state.`,
         err
       );
-      this.changeState(TorConnectState.Error, err?.message, err?.details);
+      this.changeState(TorConnectState.Error, err);
     }
   }
 
@@ -263,14 +271,13 @@ class ConfiguringState extends StateCallback {
   }
 
   run() {
-    // The configuring state does not do anything.
+    TorConnect._bootstrapProgress = 0;
   }
 }
 
 class BootstrappingState extends StateCallback {
   #bootstrap = null;
-  #bootstrapError = "";
-  #bootstrapErrorDetails = "";
+  #bootstrapError = null;
   #internetTest = null;
   #cancelled = false;
 
@@ -291,13 +298,13 @@ class BootstrappingState extends StateCallback {
 
     this.#bootstrap = new lazy.TorBootstrapRequest();
     this.#bootstrap.onbootstrapstatus = (progress, status) => {
-      TorConnect._updateBootstrapStatus(progress, status);
+      TorConnect._updateBootstrapProgress(progress, status);
     };
     this.#bootstrap.onbootstrapcomplete = () => {
       this.#internetTest.cancel();
       this.changeState(TorConnectState.Bootstrapped);
     };
-    this.#bootstrap.onbootstraperror = (message, details) => {
+    this.#bootstrap.onbootstraperror = error => {
       if (this.#cancelled) {
         // We ignore this error since it occurred after cancelling (by the
         // user). We assume the error is just a side effect of the cancelling.
@@ -305,13 +312,12 @@ class BootstrappingState extends StateCallback {
         // "Building circuits: Establishing a Tor circuit failed".
         // TODO: Maybe move this logic deeper in the process to know when to
         // filter out such errors triggered by cancelling.
-        lazy.logger.warn(`Post-cancel error => ${message}; ${details}`);
+        lazy.logger.warn("Post-cancel error.", error);
         return;
       }
       // We have to wait for the Internet test to finish before sending the
       // bootstrap error
-      this.#bootstrapError = message;
-      this.#bootstrapErrorDetails = details;
+      this.#bootstrapError = error;
       this.#maybeTransitionToError();
     };
 
@@ -352,22 +358,23 @@ class BootstrappingState extends StateCallback {
     // Do not transition to the offline error until we are sure that also the
     // bootstrap failed, in case Moat is down but the bootstrap can proceed
     // anyway.
-    if (this.#bootstrapError === "") {
+    if (!this.#bootstrapError) {
       return;
     }
     if (this.#internetTest.status === InternetStatus.Offline) {
       this.changeState(
         TorConnectState.Error,
-        TorStrings.torConnect.offline,
-        ""
+        new TorConnectError(TorConnectError.Offline)
       );
     } else {
       // Give priority to the bootstrap error, in case the Internet test fails
       TorConnect._hasBootstrapEverFailed = true;
       this.changeState(
         TorConnectState.Error,
-        this.#bootstrapError,
-        this.#bootstrapErrorDetails
+        new TorConnectError(
+          TorConnectError.BootstrapError,
+          this.#bootstrapError
+        )
       );
     }
   }
@@ -393,10 +400,12 @@ class BootstrappingState extends StateCallback {
       TorConnect._detectedLocation =
         codes[Math.floor(Math.random() * codes.length)];
     }
+    const err = new Error("Censorship simulation");
+    err.phase = "conn";
+    err.reason = "noroute";
     this.changeState(
       TorConnectState.Error,
-      "Bootstrap failed (for debugging purposes)",
-      "Error: Censorship simulation"
+      new TorConnectError(TorConnectError.BootstrapError, err)
     );
     return true;
   }
@@ -461,8 +470,7 @@ class AutoBootstrappingState extends StateCallback {
       if (!this.transitioning) {
         this.changeState(
           TorConnectState.Error,
-          "Error: censorship simulation",
-          ""
+          new TorConnectError(TorConnectError.AllSettingsFailed)
         );
       }
       return true;
@@ -475,8 +483,7 @@ class AutoBootstrappingState extends StateCallback {
       if (!this.transitioning) {
         this.changeState(
           TorConnectState.Error,
-          "Error: Severe Censorship simulation",
-          ""
+          new TorConnectError(TorConnectError.CannotDetermineCountry)
         );
       }
       return true;
@@ -507,7 +514,7 @@ class AutoBootstrappingState extends StateCallback {
     // them.
     const maybeSettings = await Promise.race([
       this.#moat.circumvention_settings(
-        [...TorSettings.builtinBridgeTypes, "vanilla"],
+        [...lazy.TorSettings.builtinBridgeTypes, "vanilla"],
         countryCode
       ),
       // This might set maybeSettings to undefined.
@@ -523,7 +530,7 @@ class AutoBootstrappingState extends StateCallback {
       // Keep consistency with the other call.
       this.#settings = await Promise.race([
         this.#moat.circumvention_defaults([
-          ...TorSettings.builtinBridgeTypes,
+          ...lazy.TorSettings.builtinBridgeTypes,
           "vanilla",
         ]),
         // This might set this.#settings to undefined.
@@ -532,22 +539,12 @@ class AutoBootstrappingState extends StateCallback {
     }
 
     if (!this.#settings?.length && !this.transitioning) {
-      // Both localized and fallback have, we can just throw to transition to
-      // the error state (but only if we aren't already transitioning).
-      // TODO: Let the UI layer localize the strings.
-
       if (!TorConnect._detectedLocation) {
         // unable to determine country
-        this.#throwError(
-          TorStrings.torConnect.autoBootstrappingFailed,
-          TorStrings.torConnect.cannotDetermineCountry
-        );
+        throw new TorConnectError(TorConnectError.CannotDetermineCountry);
       } else {
         // no settings available for country
-        this.#throwError(
-          TorStrings.torConnect.autoBootstrappingFailed,
-          TorStrings.torConnect.noSettingsForCountry
-        );
+        throw new TorConnectError(TorConnectError.NoSettingsForCountry);
       }
     }
   }
@@ -585,17 +582,17 @@ class AutoBootstrappingState extends StateCallback {
       // We need to merge with old settings, in case the user is using a proxy
       // or is behind a firewall.
       await provider.writeSettings({
-        ...TorSettings.getSettings(),
+        ...lazy.TorSettings.getSettings(),
         ...currentSetting,
       });
 
       // Build out our bootstrap request.
       const bootstrap = new lazy.TorBootstrapRequest();
       bootstrap.onbootstrapstatus = (progress, status) => {
-        TorConnect._updateBootstrapStatus(progress, status);
+        TorConnect._updateBootstrapProgress(progress, status);
       };
-      bootstrap.onbootstraperror = (message, details) => {
-        lazy.logger.error(`Auto-Bootstrap error => ${message}; ${details}`);
+      bootstrap.onbootstraperror = error => {
+        lazy.logger.error("Auto-Bootstrap error", error);
       };
 
       // Begin the bootstrap.
@@ -620,14 +617,14 @@ class AutoBootstrappingState extends StateCallback {
       }
       if (success) {
         // Persist the current settings to preferences.
-        TorSettings.setSettings(currentSetting);
-        TorSettings.saveToPrefs();
+        lazy.TorSettings.setSettings(currentSetting);
+        lazy.TorSettings.saveToPrefs();
         // Do not await `applySettings`. Otherwise this opens up a window of
         // time where the user can still "Cancel" the bootstrap.
         // We are calling `applySettings` just to be on the safe side, but the
         // settings we are passing now should be exactly the same we already
         // passed earlier.
-        TorSettings.applySettings().catch(e =>
+        lazy.TorSettings.applySettings().catch(e =>
           lazy.logger.error("TorSettings.applySettings threw unexpectedly.", e)
         );
         this.changeState(TorConnectState.Bootstrapped);
@@ -638,19 +635,10 @@ class AutoBootstrappingState extends StateCallback {
     // Only explicitly change state here if something else has not transitioned
     // us.
     if (!this.transitioning) {
-      this.#throwError(
-        TorStrings.torConnect.autoBootstrappingFailed,
-        TorStrings.torConnect.autoBootstrappingAllFailed
-      );
+      throw new TorConnectError(TorConnectError.AllSettingsFailed);
     }
   }
 
-  #throwError(message, details) {
-    let err = new Error(message);
-    err.details = details;
-    throw err;
-  }
-
   transitionRequested() {
     this.#transitionResolve();
   }
@@ -662,7 +650,7 @@ class AutoBootstrappingState extends StateCallback {
 
     if (this.#changedSettings && nextState !== TorConnectState.Bootstrapped) {
       try {
-        await TorSettings.applySettings();
+        await lazy.TorSettings.applySettings();
       } catch (e) {
         // We cannot do much if the original settings were bad or
         // if the connection closed, so just report it in the
@@ -698,17 +686,15 @@ class ErrorState extends StateCallback {
     ErrorState.#hasEverHappened = true;
   }
 
-  run(errorMessage, errorDetails) {
-    TorConnect._errorMessage = errorMessage;
-    TorConnect._errorDetails = errorDetails;
-    lazy.logger.error(
-      `Entering error state (${errorMessage}, ${errorDetails})`
-    );
+  run(error) {
+    if (!(error instanceof TorConnectError)) {
+      error = new TorConnectError(TorConnectError.ExternalError, error);
+    }
+    TorConnect._errorCode = error.code;
+    TorConnect._errorDetails = error;
+    lazy.logger.error(`Entering error state (${error.code})`, error);
 
-    Services.obs.notifyObservers(
-      { message: errorMessage, details: errorDetails },
-      TorConnectTopics.BootstrapError
-    );
+    Services.obs.notifyObservers(error, TorConnectTopics.Error);
 
     this.changeState(TorConnectState.Configuring);
   }
@@ -835,7 +821,6 @@ class InternetTest {
 export const TorConnect = {
   _stateHandler: new InitialState(),
   _bootstrapProgress: 0,
-  _bootstrapStatus: null,
   _internetStatus: InternetStatus.Unknown,
   // list of country codes Moat has settings for
   _countryCodes: [],
@@ -851,7 +836,7 @@ export const TorConnect = {
     })()
   ),
   _detectedLocation: "",
-  _errorMessage: null,
+  _errorCode: null,
   _errorDetails: null,
   _logHasWarningOrError: false,
   _hasBootstrapEverFailed: false,
@@ -925,17 +910,15 @@ export const TorConnect = {
     this._stateHandler.begin(...args);
   },
 
-  _updateBootstrapStatus(progress, status) {
+  _updateBootstrapProgress(progress, status) {
     this._bootstrapProgress = progress;
-    this._bootstrapStatus = status;
 
     lazy.logger.info(
-      `Bootstrapping ${this._bootstrapProgress}% complete (${this._bootstrapStatus})`
+      `Bootstrapping ${this._bootstrapProgress}% complete (${status})`
     );
     Services.obs.notifyObservers(
       {
         progress: TorConnect._bootstrapProgress,
-        status: TorConnect._bootstrapStatus,
         hasWarnings: TorConnect._logHasWarningOrError,
       },
       TorConnectTopics.BootstrapProgress
@@ -964,11 +947,13 @@ export const TorConnect = {
       // So, we prefer initializing TorConnect as soon as possible, so that
       // the UI will be able to detect it is in the Initializing state and act
       // consequently.
-      TorSettings.initializedPromise.then(() => this._settingsInitialized());
+      lazy.TorSettings.initializedPromise.then(() =>
+        this._settingsInitialized()
+      );
 
       // register the Tor topics we always care about
-      observeTopic(TorTopics.ProcessExited);
-      observeTopic(TorTopics.LogHasWarnOrErr);
+      observeTopic(lazy.TorProviderTopics.ProcessExited);
+      observeTopic(lazy.TorProviderTopics.HasWarnOrErr);
     }
   },
 
@@ -976,11 +961,11 @@ export const TorConnect = {
     lazy.logger.debug(`Observed ${topic}`);
 
     switch (topic) {
-      case TorTopics.LogHasWarnOrErr: {
+      case lazy.TorProviderTopics.HasWarnOrErr: {
         this._logHasWarningOrError = true;
         break;
       }
-      case TorTopics.ProcessExited: {
+      case lazy.TorProviderTopics.ProcessExited: {
         // Treat a failure as a possibly broken configuration.
         // So, prevent quickstart at the next start.
         Services.prefs.setBoolPref(TorLauncherPrefs.prompt_at_startup, true);
@@ -1048,7 +1033,7 @@ export const TorConnect = {
     // FIXME: This is called before the TorProvider is ready.
     // As a matter of fact, at the moment it is equivalent to the following
     // line, but this might become a problem in the future.
-    return TorLauncherUtil.shouldStartAndOwnTor;
+    return lazy.TorLauncherUtil.shouldStartAndOwnTor;
   },
 
   get shouldShowTorConnect() {
@@ -1089,7 +1074,7 @@ export const TorConnect = {
   get shouldQuickStart() {
     // quickstart must be enabled
     return (
-      TorSettings.quickstart.enabled &&
+      lazy.TorSettings.quickstart.enabled &&
       // and the previous bootstrap attempt must have succeeded
       !Services.prefs.getBoolPref(TorLauncherPrefs.prompt_at_startup, true)
     );
@@ -1103,10 +1088,6 @@ export const TorConnect = {
     return this._bootstrapProgress;
   },
 
-  get bootstrapStatus() {
-    return this._bootstrapStatus;
-  },
-
   get internetStatus() {
     return this._internetStatus;
   },
@@ -1123,8 +1104,8 @@ export const TorConnect = {
     return this._detectedLocation;
   },
 
-  get errorMessage() {
-    return this._errorMessage;
+  get errorCode() {
+    return this._errorCode;
   },
 
   get errorDetails() {
@@ -1195,7 +1176,7 @@ export const TorConnect = {
     Further external commands and helper methods
    */
   openTorPreferences() {
-    if (TorLauncherUtil.isAndroid) {
+    if (lazy.TorLauncherUtil.isAndroid) {
       lazy.EventDispatcher.instance.sendRequest({
         type: "GeckoView:Tor:OpenSettings",
       });


=====================================
toolkit/modules/TorStrings.sys.mjs
=====================================
@@ -51,11 +51,11 @@ class TorPropertyStringBundle {
     return `$(${key})`;
   }
 
-  getStrings(strings) {
+  getStrings(strings, additionalPrefix = "") {
     return Object.fromEntries(
       Object.entries(strings).map(([key, fallback]) => [
         key,
-        this.getString(key, fallback),
+        this.getString(additionalPrefix + key, fallback),
       ])
     );
   }
@@ -143,7 +143,7 @@ const Loader = {
       frequentLocations: "Frequently selected locations",
       otherLocations: "Other locations",
 
-      // TorConnect.jsm error messages
+      // TorConnect error messages
       offline: "Internet not reachable",
       autoBootstrappingFailed: "Automatic configuration failed",
       autoBootstrappingAllFailed: "None of the configurations we tried worked",
@@ -158,11 +158,67 @@ const Loader = {
       titlebarStatusConnected: "Connected",
     };
 
+    // Some strings were used through TorLauncherUtils.
+    // However, we need to use them in about:torconnect, which cannot access
+    // privileged code.
+    const bootstrapStatus = {
+      starting: "Starting",
+      conn_pt: "Connecting to bridge",
+      conn_done_pt: "Connected to bridge",
+      conn_proxy: "Connecting to proxy",
+      conn_done_proxy: "Connected to proxy",
+      conn: "Connecting to a Tor relay",
+      conn_done: "Connected to a Tor relay",
+      handshake: "Negotiating with a Tor relay",
+      handshake_done: "Finished negotiating with a Tor relay",
+      onehop_create: "Establishing an encrypted directory connection",
+      requesting_status: "Retrieving network status",
+      loading_status: "Loading network status",
+      loading_keys: "Loading authority certificates",
+      requesting_descriptors: "Requesting relay information",
+      loading_descriptors: "Loading relay information",
+      enough_dirinfo: "Finished loading relay information",
+      ap_conn_pt: "Building circuits: Connecting to bridge",
+      ap_conn_done_pt: "Building circuits: Connected to bridge",
+      ap_conn_proxy: "Building circuits: Connecting to proxy",
+      ap_conn_done_proxy: "Building circuits: Connected to proxy",
+      ap_conn: "Building circuits: Connecting to a Tor relay",
+      ap_conn_done: "Building circuits: Connected to a Tor relay",
+      ap_handshake: "Building circuits: Negotiating with a Tor relay",
+      ap_handshake_done:
+        "Building circuits: Finished negotiating with a Tor relay",
+      circuit_create: "Building circuits: Establishing a Tor circuit",
+      done: "Connected to the Tor network!",
+    };
+    const bootstrapWarning = {
+      done: "done",
+      connectrefused: "connection refused",
+      misc: "miscellaneous",
+      resourcelimit: "insufficient resources",
+      identity: "identity mismatch",
+      timeout: "connection timeout",
+      noroute: "no route to host",
+      ioerror: "read/write error",
+      pt_missing: "missing pluggable transport",
+    };
+
     const tsb = new TorPropertyStringBundle(
       "chrome://torbutton/locale/torConnect.properties",
       "torConnect."
     );
-    return tsb.getStrings(strings);
+    const tlsb = new TorPropertyStringBundle(
+      "chrome://torbutton/locale/torlauncher.properties",
+      "torlauncher."
+    );
+    return {
+      ...tsb.getStrings(strings),
+      bootstrapFailedDetails: tlsb.getString(
+        "tor_bootstrap_failed_details",
+        "%1$S failed (%2$S)."
+      ),
+      bootstrapStatus: tlsb.getStrings(bootstrapStatus, "bootstrapStatus."),
+      bootstrapWarning: tlsb.getStrings(bootstrapWarning, "bootstrapWarning."),
+    };
   },
 
   /*


=====================================
toolkit/torbutton/chrome/locale/en-US/torlauncher.properties
=====================================
@@ -4,20 +4,15 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 torlauncher.error_title=Tor Launcher
-
 torlauncher.tor_exited_during_startup=Tor exited during startup. This might be due to an error in your torrc file, a bug in Tor or another program on your system, or faulty hardware. Until you fix the underlying problem and restart Tor, Tor Browser will not start.
 torlauncher.tor_exited=Tor unexpectedly exited. This might be due to a bug in Tor itself, another program on your system, or faulty hardware. Until you restart Tor, Tor Browser will not be able to reach any websites. If the problem persists, please send a copy of your Tor Log to the support team.
 torlauncher.tor_exited2=Restarting Tor will not close your browser tabs.
 torlauncher.restart_tor=Restart Tor
-torlauncher.tor_bootstrap_failed=Tor failed to establish a Tor network connection.
-torlauncher.tor_bootstrap_failed_details=%1$S failed (%2$S).
 
-torlauncher.unable_to_start_tor=Unable to start Tor.\n\n%S
-torlauncher.tor_missing=The Tor executable is missing.
-torlauncher.torrc_missing=The torrc file is missing and could not be created.
-torlauncher.datadir_missing=The Tor data directory does not exist and could not be created.
-torlauncher.onionauthdir_missing=The Tor onion authentication directory does not exist and could not be created.
-torlauncher.password_hash_missing=Failed to get hashed password.
+
+# Translation note: %1$S is a bootstrap phase from torlauncher.bootstrapStatus,
+# %2$S is the error from torlauncher.bootstrapWarning
+torlauncher.tor_bootstrap_failed_details=%1$S failed (%2$S).
 
 torlauncher.bootstrapStatus.starting=Starting
 torlauncher.bootstrapStatus.conn_pt=Connecting to bridge



View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/89a5a7fd311640a4b50b951bcf8ef1b035aa37bb...e162a17a667124583c2716c2393959e66a02444d

-- 
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/89a5a7fd311640a4b50b951bcf8ef1b035aa37bb...e162a17a667124583c2716c2393959e66a02444d
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/20240415/7373bf55/attachment-0001.htm>


More information about the tbb-commits mailing list