Pier Angelo Vendrame pushed to branch tor-browser-128.4.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits:
-
8fee92cd
by Henry Wilkes at 2024-11-13T08:23:22+00:00
-
1086febd
by Henry Wilkes at 2024-11-13T08:23:22+00:00
-
55d915e5
by Henry Wilkes at 2024-11-13T08:23:22+00:00
-
802af522
by Henry Wilkes at 2024-11-13T08:23:22+00:00
-
cc17defe
by Henry Wilkes at 2024-11-13T08:23:22+00:00
-
771381c5
by Henry Wilkes at 2024-11-13T08:23:22+00:00
-
fafc6ff3
by Henry Wilkes at 2024-11-13T08:23:22+00:00
-
2ddd2029
by Henry Wilkes at 2024-11-13T08:23:22+00:00
-
3a27b920
by Henry Wilkes at 2024-11-13T08:23:22+00:00
-
4aa64ccc
by Henry Wilkes at 2024-11-13T08:23:22+00:00
-
56837486
by Henry Wilkes at 2024-11-13T08:23:22+00:00
-
ebe8a652
by Henry Wilkes at 2024-11-13T08:23:22+00:00
-
8f95d948
by Henry Wilkes at 2024-11-13T08:23:22+00:00
-
bba68db8
by Henry Wilkes at 2024-11-13T08:23:22+00:00
-
a6be0160
by Henry Wilkes at 2024-11-13T08:23:22+00:00
-
be125ecd
by Henry Wilkes at 2024-11-13T08:23:22+00:00
-
1f6fc087
by Henry Wilkes at 2024-11-13T08:23:22+00:00
18 changed files:
- browser/base/content/browser.js
- browser/base/content/browser.js.globals
- browser/components/torpreferences/content/builtinBridgeDialog.js
- browser/components/torpreferences/content/connectionPane.js
- browser/components/torpreferences/content/provideBridgeDialog.js
- browser/components/torpreferences/content/requestBridgeDialog.js
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerGV.kt
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorIntegrationAndroid.java
- toolkit/components/lox/Lox.sys.mjs
- toolkit/components/torconnect/TorConnectChild.sys.mjs
- toolkit/components/torconnect/TorConnectParent.sys.mjs
- toolkit/components/torconnect/content/aboutTorConnect.js
- toolkit/components/torconnect/content/torConnectTitlebarStatus.js
- toolkit/components/torconnect/content/torConnectUrlbarButton.js
- toolkit/modules/RemotePageAccessManager.sys.mjs
- toolkit/modules/TorAndroidIntegration.sys.mjs
- toolkit/modules/TorConnect.sys.mjs
Changes:
| ... | ... | @@ -85,7 +85,7 @@ ChromeUtils.defineESModuleGetters(this, { |
| 85 | 85 | TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
|
| 86 | 86 | TorDomainIsolator: "resource://gre/modules/TorDomainIsolator.sys.mjs",
|
| 87 | 87 | TorConnect: "resource://gre/modules/TorConnect.sys.mjs",
|
| 88 | - TorConnectState: "resource://gre/modules/TorConnect.sys.mjs",
|
|
| 88 | + TorConnectStage: "resource://gre/modules/TorConnect.sys.mjs",
|
|
| 89 | 89 | TorConnectTopics: "resource://gre/modules/TorConnect.sys.mjs",
|
| 90 | 90 | TorUIUtils: "resource:///modules/TorUIUtils.sys.mjs",
|
| 91 | 91 | TranslationsParent: "resource://gre/actors/TranslationsParent.sys.mjs",
|
| ... | ... | @@ -276,7 +276,7 @@ |
| 276 | 276 | "TorDomainIsolator",
|
| 277 | 277 | "gTorCircuitPanel",
|
| 278 | 278 | "TorConnect",
|
| 279 | - "TorConnectState",
|
|
| 279 | + "TorConnectStage",
|
|
| 280 | 280 | "TorConnectTopics",
|
| 281 | 281 | "gTorConnectUrlbarButton",
|
| 282 | 282 | "gTorConnectTitlebarStatus",
|
| ... | ... | @@ -79,14 +79,14 @@ const gBuiltinBridgeDialog = { |
| 79 | 79 | |
| 80 | 80 | this._acceptButton = dialog.getButton("accept");
|
| 81 | 81 | |
| 82 | - Services.obs.addObserver(this, TorConnectTopics.StateChange);
|
|
| 82 | + Services.obs.addObserver(this, TorConnectTopics.StageChange);
|
|
| 83 | 83 | |
| 84 | 84 | this.onSelectChange();
|
| 85 | 85 | this.onAcceptStateChange();
|
| 86 | 86 | },
|
| 87 | 87 | |
| 88 | 88 | uninit() {
|
| 89 | - Services.obs.removeObserver(this, TorConnectTopics.StateChange);
|
|
| 89 | + Services.obs.removeObserver(this, TorConnectTopics.StageChange);
|
|
| 90 | 90 | },
|
| 91 | 91 | |
| 92 | 92 | onSelectChange() {
|
| ... | ... | @@ -107,7 +107,7 @@ const gBuiltinBridgeDialog = { |
| 107 | 107 | |
| 108 | 108 | observe(subject, topic) {
|
| 109 | 109 | switch (topic) {
|
| 110 | - case TorConnectTopics.StateChange:
|
|
| 110 | + case TorConnectTopics.StageChange:
|
|
| 111 | 111 | this.onAcceptStateChange();
|
| 112 | 112 | break;
|
| 113 | 113 | }
|
| ... | ... | @@ -22,7 +22,7 @@ const { TorProviderBuilder, TorProviderTopics } = ChromeUtils.importESModule( |
| 22 | 22 | "resource://gre/modules/TorProviderBuilder.sys.mjs"
|
| 23 | 23 | );
|
| 24 | 24 | |
| 25 | -const { TorConnect, TorConnectTopics, TorConnectState, TorCensorshipLevel } =
|
|
| 25 | +const { TorConnect, TorConnectTopics, TorConnectStage, TorCensorshipLevel } =
|
|
| 26 | 26 | ChromeUtils.importESModule("resource://gre/modules/TorConnect.sys.mjs");
|
| 27 | 27 | |
| 28 | 28 | const { MoatRPC } = ChromeUtils.importESModule(
|
| ... | ... | @@ -2195,18 +2195,7 @@ const gBridgeSettings = { |
| 2195 | 2195 | |
| 2196 | 2196 | // Start Bootstrapping, which should use the configured bridges.
|
| 2197 | 2197 | // NOTE: We do this regardless of any previous TorConnect Error.
|
| 2198 | - if (TorConnect.canBeginBootstrap) {
|
|
| 2199 | - TorConnect.beginBootstrap();
|
|
| 2200 | - }
|
|
| 2201 | - // Open "about:torconnect".
|
|
| 2202 | - // FIXME: If there has been a previous bootstrapping error then
|
|
| 2203 | - // "about:torconnect" will be trying to get the user to use
|
|
| 2204 | - // AutoBootstrapping. It is not set up to handle a forced direct
|
|
| 2205 | - // entry to plain Bootstrapping from this dialog so the UI will
|
|
| 2206 | - // not be aligned. In particular the
|
|
| 2207 | - // AboutTorConnect.uiState.bootstrapCause will be aligned to
|
|
| 2208 | - // whatever was shown previously in "about:torconnect" instead.
|
|
| 2209 | - TorConnect.openTorConnect();
|
|
| 2198 | + TorConnect.openTorConnect({ beginBootstrapping: "hard" });
|
|
| 2210 | 2199 | });
|
| 2211 | 2200 | },
|
| 2212 | 2201 | // closedCallback should be called after gSubDialog has already
|
| ... | ... | @@ -2322,27 +2311,27 @@ const gNetworkStatus = { |
| 2322 | 2311 | "network-status-tor-connect-button"
|
| 2323 | 2312 | );
|
| 2324 | 2313 | this._torConnectButton.addEventListener("click", () => {
|
| 2325 | - TorConnect.openTorConnect({ beginBootstrap: true });
|
|
| 2314 | + TorConnect.openTorConnect({ beginBootstrapping: "soft" });
|
|
| 2326 | 2315 | });
|
| 2327 | 2316 | |
| 2328 | 2317 | this._updateInternetStatus("unknown");
|
| 2329 | 2318 | this._updateTorConnectionStatus();
|
| 2330 | 2319 | |
| 2331 | - Services.obs.addObserver(this, TorConnectTopics.StateChange);
|
|
| 2320 | + Services.obs.addObserver(this, TorConnectTopics.StageChange);
|
|
| 2332 | 2321 | },
|
| 2333 | 2322 | |
| 2334 | 2323 | /**
|
| 2335 | 2324 | * Un-initialize the area.
|
| 2336 | 2325 | */
|
| 2337 | 2326 | uninit() {
|
| 2338 | - Services.obs.removeObserver(this, TorConnectTopics.StateChange);
|
|
| 2327 | + Services.obs.removeObserver(this, TorConnectTopics.StageChange);
|
|
| 2339 | 2328 | },
|
| 2340 | 2329 | |
| 2341 | 2330 | observe(subject, topic) {
|
| 2342 | 2331 | switch (topic) {
|
| 2343 | 2332 | // triggered when tor connect state changes and we may
|
| 2344 | 2333 | // need to update the messagebox
|
| 2345 | - case TorConnectTopics.StateChange: {
|
|
| 2334 | + case TorConnectTopics.StageChange: {
|
|
| 2346 | 2335 | this._updateTorConnectionStatus();
|
| 2347 | 2336 | break;
|
| 2348 | 2337 | }
|
| ... | ... | @@ -2433,7 +2422,8 @@ const gNetworkStatus = { |
| 2433 | 2422 | const buttonHadFocus = this._torConnectButton.contains(
|
| 2434 | 2423 | document.activeElement
|
| 2435 | 2424 | );
|
| 2436 | - const isBootstrapped = TorConnect.state === TorConnectState.Bootstrapped;
|
|
| 2425 | + const isBootstrapped =
|
|
| 2426 | + TorConnect.stageName === TorConnectStage.Bootstrapped;
|
|
| 2437 | 2427 | const isBlocked = !isBootstrapped && TorConnect.potentiallyBlocked;
|
| 2438 | 2428 | let l10nId;
|
| 2439 | 2429 | if (isBootstrapped) {
|
| ... | ... | @@ -2527,7 +2517,8 @@ const gConnectionPane = (function () { |
| 2527 | 2517 | );
|
| 2528 | 2518 | chooseForMe.addEventListener("command", () => {
|
| 2529 | 2519 | TorConnect.openTorConnect({
|
| 2530 | - beginAutoBootstrap: location.value,
|
|
| 2520 | + beginBootstrapping: "hard",
|
|
| 2521 | + regionCode: location.value,
|
|
| 2531 | 2522 | });
|
| 2532 | 2523 | });
|
| 2533 | 2524 | this._populateLocations = () => {
|
| ... | ... | @@ -2558,7 +2549,7 @@ const gConnectionPane = (function () { |
| 2558 | 2549 | locationEntries.append(...items);
|
| 2559 | 2550 | };
|
| 2560 | 2551 | locationEntries.append(
|
| 2561 | - createItem("", TorStrings.settings.bridgeLocationAutomatic)
|
|
| 2552 | + createItem("automatic", TorStrings.settings.bridgeLocationAutomatic)
|
|
| 2562 | 2553 | );
|
| 2563 | 2554 | if (TorConnect.countryCodes.length) {
|
| 2564 | 2555 | locationEntries.append(
|
| ... | ... | @@ -2607,7 +2598,7 @@ const gConnectionPane = (function () { |
| 2607 | 2598 | this.onViewTorLogs();
|
| 2608 | 2599 | });
|
| 2609 | 2600 | |
| 2610 | - Services.obs.addObserver(this, TorConnectTopics.StateChange);
|
|
| 2601 | + Services.obs.addObserver(this, TorConnectTopics.StageChange);
|
|
| 2611 | 2602 | },
|
| 2612 | 2603 | |
| 2613 | 2604 | init() {
|
| ... | ... | @@ -2629,7 +2620,7 @@ const gConnectionPane = (function () { |
| 2629 | 2620 | |
| 2630 | 2621 | // unregister our observer topics
|
| 2631 | 2622 | Services.obs.removeObserver(this, TorSettingsTopics.SettingsChanged);
|
| 2632 | - Services.obs.removeObserver(this, TorConnectTopics.StateChange);
|
|
| 2623 | + Services.obs.removeObserver(this, TorConnectTopics.StageChange);
|
|
| 2633 | 2624 | },
|
| 2634 | 2625 | |
| 2635 | 2626 | // whether the page should be present in about:preferences
|
| ... | ... | @@ -2653,7 +2644,7 @@ const gConnectionPane = (function () { |
| 2653 | 2644 | }
|
| 2654 | 2645 | // triggered when tor connect state changes and we may
|
| 2655 | 2646 | // need to update the messagebox
|
| 2656 | - case TorConnectTopics.StateChange: {
|
|
| 2647 | + case TorConnectTopics.StageChange: {
|
|
| 2657 | 2648 | this._showAutoconfiguration();
|
| 2658 | 2649 | break;
|
| 2659 | 2650 | }
|
| ... | ... | @@ -128,14 +128,14 @@ const gProvideBridgeDialog = { |
| 128 | 128 | this.onDialogAccept(event)
|
| 129 | 129 | );
|
| 130 | 130 | |
| 131 | - Services.obs.addObserver(this, TorConnectTopics.StateChange);
|
|
| 131 | + Services.obs.addObserver(this, TorConnectTopics.StageChange);
|
|
| 132 | 132 | |
| 133 | 133 | this.setPage("entry");
|
| 134 | 134 | this.checkValue();
|
| 135 | 135 | },
|
| 136 | 136 | |
| 137 | 137 | uninit() {
|
| 138 | - Services.obs.removeObserver(this, TorConnectTopics.StateChange);
|
|
| 138 | + Services.obs.removeObserver(this, TorConnectTopics.StageChange);
|
|
| 139 | 139 | },
|
| 140 | 140 | |
| 141 | 141 | /**
|
| ... | ... | @@ -512,7 +512,7 @@ const gProvideBridgeDialog = { |
| 512 | 512 | |
| 513 | 513 | observe(subject, topic) {
|
| 514 | 514 | switch (topic) {
|
| 515 | - case TorConnectTopics.StateChange:
|
|
| 515 | + case TorConnectTopics.StageChange:
|
|
| 516 | 516 | this.onAcceptStateChange();
|
| 517 | 517 | break;
|
| 518 | 518 | }
|
| ... | ... | @@ -91,14 +91,14 @@ const gRequestBridgeDialog = { |
| 91 | 91 | selectors.incorrectCaptchaHbox
|
| 92 | 92 | );
|
| 93 | 93 | |
| 94 | - Services.obs.addObserver(this, TorConnectTopics.StateChange);
|
|
| 94 | + Services.obs.addObserver(this, TorConnectTopics.StageChange);
|
|
| 95 | 95 | this.onAcceptStateChange();
|
| 96 | 96 | },
|
| 97 | 97 | |
| 98 | 98 | uninit() {
|
| 99 | 99 | BridgeDB.close();
|
| 100 | 100 | // Unregister our observer topics.
|
| 101 | - Services.obs.removeObserver(this, TorConnectTopics.StateChange);
|
|
| 101 | + Services.obs.removeObserver(this, TorConnectTopics.StageChange);
|
|
| 102 | 102 | },
|
| 103 | 103 | |
| 104 | 104 | onAcceptStateChange() {
|
| ... | ... | @@ -113,7 +113,7 @@ const gRequestBridgeDialog = { |
| 113 | 113 | |
| 114 | 114 | observe(subject, topic) {
|
| 115 | 115 | switch (topic) {
|
| 116 | - case TorConnectTopics.StateChange:
|
|
| 116 | + case TorConnectTopics.StageChange:
|
|
| 117 | 117 | this.onAcceptStateChange();
|
| 118 | 118 | break;
|
| 119 | 119 | }
|
| ... | ... | @@ -1438,7 +1438,4 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, TorIn |
| 1438 | 1438 | navHost.navController.navigate(NavGraphDirections.actionStartupHome())
|
| 1439 | 1439 | }
|
| 1440 | 1440 | override fun onBootstrapError(code: String?, message: String?, phase: String?, reason: String?) = Unit
|
| 1441 | - override fun onSettingsRequested() {
|
|
| 1442 | - navHost.navController.navigate(NavGraphDirections.actionGlobalSettingsFragment())
|
|
| 1443 | - }
|
|
| 1444 | 1441 | } |
| ... | ... | @@ -332,20 +332,28 @@ class TorControllerGV( |
| 332 | 332 | // TorEventsBootstrapStateChangeListener
|
| 333 | 333 | override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) {
|
| 334 | 334 | Log.d(TAG, "onBootstrapProgress($progress, $hasWarnings)")
|
| 335 | + // TODO: onBootstrapProgress should only be used to change the shown
|
|
| 336 | + // bootstrap percentage or a Tor log option during a "Bootstrapping"
|
|
| 337 | + // stage.
|
|
| 338 | + // The progress value should not be used to change the `lastKnownStatus`
|
|
| 339 | + // value or determine if a bootstrap has started or completed. The
|
|
| 340 | + // TorConnectStage should be used instead.
|
|
| 335 | 341 | if (progress == 100.0) {
|
| 336 | 342 | lastKnownStatus = TorConnectState.Bootstrapped
|
| 337 | 343 | wasTorBootstrapped = true
|
| 338 | 344 | onTorConnected()
|
| 339 | - } else {
|
|
| 340 | - lastKnownStatus = TorConnectState.Bootstrapping
|
|
| 345 | + } else if (lastKnownStatus == TorConnectState.Bootstrapping) {
|
|
| 341 | 346 | onTorConnecting()
|
| 342 | - |
|
| 343 | 347 | }
|
| 344 | 348 | onTorStatusUpdate("", lastKnownStatus.toTorStatus().status, progress)
|
| 345 | 349 | }
|
| 346 | 350 | |
| 347 | 351 | // TorEventsBootstrapStateChangeListener
|
| 348 | 352 | override fun onBootstrapComplete() {
|
| 353 | + // TODO: There should be no need to respond to the BootstrapComplete
|
|
| 354 | + // event if we are already handling TorConnectStage.Bootstrapped.
|
|
| 355 | + // In particular, `lastKnownStatus` and onTorConnected should be set in
|
|
| 356 | + // response to a change in TorConnectStage instead.
|
|
| 349 | 357 | lastKnownStatus = TorConnectState.Bootstrapped
|
| 350 | 358 | this.onTorConnected()
|
| 351 | 359 | }
|
| ... | ... | @@ -354,9 +362,4 @@ class TorControllerGV( |
| 354 | 362 | override fun onBootstrapError(code: String?, message: String?, phase: String?, reason: String?) {
|
| 355 | 363 | lastKnownError = TorError(code ?: "", message ?: "", phase ?: "", reason ?: "")
|
| 356 | 364 | }
|
| 357 | - |
|
| 358 | - // TorEventsBootstrapStateChangeListener
|
|
| 359 | - override fun onSettingsRequested() {
|
|
| 360 | - // noop
|
|
| 361 | - }
|
|
| 362 | 365 | } |
| ... | ... | @@ -44,7 +44,6 @@ public class TorIntegrationAndroid implements BundleEventListener { |
| 44 | 44 | private static final String EVENT_TOR_LOGS = "GeckoView:Tor:Logs";
|
| 45 | 45 | private static final String EVENT_SETTINGS_READY = "GeckoView:Tor:SettingsReady";
|
| 46 | 46 | private static final String EVENT_SETTINGS_CHANGED = "GeckoView:Tor:SettingsChanged";
|
| 47 | - private static final String EVENT_SETTINGS_OPEN = "GeckoView:Tor:OpenSettings";
|
|
| 48 | 47 | |
| 49 | 48 | // Events we emit
|
| 50 | 49 | private static final String EVENT_SETTINGS_GET = "GeckoView:Tor:SettingsGet";
|
| ... | ... | @@ -118,8 +117,7 @@ public class TorIntegrationAndroid implements BundleEventListener { |
| 118 | 117 | EVENT_CONNECT_ERROR,
|
| 119 | 118 | EVENT_BOOTSTRAP_PROGRESS,
|
| 120 | 119 | EVENT_BOOTSTRAP_COMPLETE,
|
| 121 | - EVENT_TOR_LOGS,
|
|
| 122 | - EVENT_SETTINGS_OPEN);
|
|
| 120 | + EVENT_TOR_LOGS);
|
|
| 123 | 121 | }
|
| 124 | 122 | |
| 125 | 123 | @Override // BundleEventListener
|
| ... | ... | @@ -176,10 +174,6 @@ public class TorIntegrationAndroid implements BundleEventListener { |
| 176 | 174 | for (TorLogListener listener : mLogListeners) {
|
| 177 | 175 | listener.onLog(type, msg);
|
| 178 | 176 | }
|
| 179 | - } else if (EVENT_SETTINGS_OPEN.equals(event)) {
|
|
| 180 | - for (BootstrapStateChangeListener listener : mBootstrapStateListeners) {
|
|
| 181 | - listener.onSettingsRequested();
|
|
| 182 | - }
|
|
| 183 | 177 | }
|
| 184 | 178 | }
|
| 185 | 179 | |
| ... | ... | @@ -641,8 +635,6 @@ public class TorIntegrationAndroid implements BundleEventListener { |
| 641 | 635 | void onBootstrapComplete();
|
| 642 | 636 | |
| 643 | 637 | void onBootstrapError(String code, String message, String phase, String reason);
|
| 644 | - |
|
| 645 | - void onSettingsRequested();
|
|
| 646 | 638 | }
|
| 647 | 639 | |
| 648 | 640 | public interface TorLogListener {
|
| ... | ... | @@ -23,7 +23,7 @@ ChromeUtils.defineESModuleGetters(lazy, { |
| 23 | 23 | DomainFrontRequestResponseError:
|
| 24 | 24 | "resource://gre/modules/DomainFrontedRequests.sys.mjs",
|
| 25 | 25 | TorConnect: "resource://gre/modules/TorConnect.sys.mjs",
|
| 26 | - TorConnectState: "resource://gre/modules/TorConnect.sys.mjs",
|
|
| 26 | + TorConnectStage: "resource://gre/modules/TorConnect.sys.mjs",
|
|
| 27 | 27 | TorSettings: "resource://gre/modules/TorSettings.sys.mjs",
|
| 28 | 28 | TorSettingsTopics: "resource://gre/modules/TorSettings.sys.mjs",
|
| 29 | 29 | TorBridgeSource: "resource://gre/modules/TorSettings.sys.mjs",
|
| ... | ... | @@ -1049,7 +1049,7 @@ class LoxImpl { |
| 1049 | 1049 | const method = "POST";
|
| 1050 | 1050 | const contentType = "application/vnd.api+json";
|
| 1051 | 1051 | |
| 1052 | - if (lazy.TorConnect.state === lazy.TorConnectState.Bootstrapped) {
|
|
| 1052 | + if (lazy.TorConnect.stageName === lazy.TorConnectStage.Bootstrapped) {
|
|
| 1053 | 1053 | let request;
|
| 1054 | 1054 | try {
|
| 1055 | 1055 | request = await fetch(url, {
|
| ... | ... | @@ -77,7 +77,7 @@ export class TorConnectChild extends RemotePageChild { |
| 77 | 77 | receiveMessage(message) {
|
| 78 | 78 | super.receiveMessage(message);
|
| 79 | 79 | |
| 80 | - if (message.name === "torconnect:state-change") {
|
|
| 80 | + if (message.name === "torconnect:stage-change") {
|
|
| 81 | 81 | this.#maybeRedirect();
|
| 82 | 82 | }
|
| 83 | 83 | }
|
| ... | ... | @@ -2,29 +2,20 @@ |
| 2 | 2 | |
| 3 | 3 | import { TorStrings } from "resource://gre/modules/TorStrings.sys.mjs";
|
| 4 | 4 | import {
|
| 5 | - InternetStatus,
|
|
| 6 | 5 | TorConnect,
|
| 7 | 6 | TorConnectTopics,
|
| 8 | - TorConnectState,
|
|
| 9 | 7 | } from "resource://gre/modules/TorConnect.sys.mjs";
|
| 10 | 8 | import {
|
| 11 | 9 | TorSettings,
|
| 12 | 10 | TorSettingsTopics,
|
| 13 | 11 | } from "resource://gre/modules/TorSettings.sys.mjs";
|
| 14 | 12 | |
| 15 | -const BroadcastTopic = "about-torconnect:broadcast";
|
|
| 16 | - |
|
| 17 | 13 | const lazy = {};
|
| 18 | 14 | |
| 19 | 15 | ChromeUtils.defineESModuleGetters(lazy, {
|
| 20 | 16 | HomePage: "resource:///modules/HomePage.sys.jsm",
|
| 21 | 17 | });
|
| 22 | 18 | |
| 23 | -const log = console.createInstance({
|
|
| 24 | - maxLogLevel: "Warn",
|
|
| 25 | - prefix: "TorConnectParent",
|
|
| 26 | -});
|
|
| 27 | - |
|
| 28 | 19 | /*
|
| 29 | 20 | This object is basically a marshalling interface between the TorConnect module
|
| 30 | 21 | and a particular about:torconnect page
|
| ... | ... | @@ -40,31 +31,6 @@ export class TorConnectParent extends JSWindowActorParent { |
| 40 | 31 | |
| 41 | 32 | const self = this;
|
| 42 | 33 | |
| 43 | - this.state = {
|
|
| 44 | - State: TorConnect.state,
|
|
| 45 | - StateChanged: false,
|
|
| 46 | - PreviousState: TorConnectState.Initial,
|
|
| 47 | - ErrorCode: TorConnect.errorCode,
|
|
| 48 | - ErrorDetails: TorConnect.errorDetails,
|
|
| 49 | - BootstrapProgress: TorConnect.bootstrapProgress,
|
|
| 50 | - InternetStatus: TorConnect.internetStatus,
|
|
| 51 | - DetectedLocation: TorConnect.detectedLocation,
|
|
| 52 | - ShowViewLog: TorConnect.logHasWarningOrError,
|
|
| 53 | - HasEverFailed: TorConnect.hasEverFailed,
|
|
| 54 | - UIState: TorConnect.uiState,
|
|
| 55 | - };
|
|
| 56 | - |
|
| 57 | - // Workaround for a race condition, but we should fix it asap.
|
|
| 58 | - // about:torconnect is loaded before TorSettings is actually initialized.
|
|
| 59 | - // The getter might throw and the page not loaded correctly as a result.
|
|
| 60 | - // Silence any warning for now, but we should really fix it.
|
|
| 61 | - // See also tor-browser#41921.
|
|
| 62 | - try {
|
|
| 63 | - this.state.QuickStartEnabled = TorSettings.quickstart.enabled;
|
|
| 64 | - } catch (e) {
|
|
| 65 | - this.state.QuickStartEnabled = false;
|
|
| 66 | - }
|
|
| 67 | - |
|
| 68 | 34 | // JSWindowActiveParent derived objects cannot observe directly, so create a
|
| 69 | 35 | // member object to do our observing for us.
|
| 70 | 36 | //
|
| ... | ... | @@ -72,103 +38,54 @@ export class TorConnectParent extends JSWindowActorParent { |
| 72 | 38 | // module, and maintains a state object which we pass down to our
|
| 73 | 39 | // about:torconnect page, which uses the state object to update its UI.
|
| 74 | 40 | this.torConnectObserver = {
|
| 75 | - observe(aSubject, aTopic) {
|
|
| 76 | - let obj = aSubject?.wrappedJSObject;
|
|
| 77 | - |
|
| 78 | - // Update our state struct based on received torconnect topics and
|
|
| 79 | - // forward on to aboutTorConnect.js.
|
|
| 80 | - self.state.StateChanged = false;
|
|
| 81 | - switch (aTopic) {
|
|
| 82 | - case TorConnectTopics.StateChange: {
|
|
| 83 | - self.state.PreviousState = self.state.State;
|
|
| 84 | - self.state.State = obj.state;
|
|
| 85 | - self.state.StateChanged = true;
|
|
| 86 | - // Clear any previous error information if we are bootstrapping.
|
|
| 87 | - if (self.state.State === TorConnectState.Bootstrapping) {
|
|
| 88 | - self.state.ErrorCode = null;
|
|
| 89 | - self.state.ErrorDetails = null;
|
|
| 90 | - }
|
|
| 91 | - self.state.BootstrapProgress = TorConnect.bootstrapProgress;
|
|
| 92 | - self.state.ShowViewLog = TorConnect.logHasWarningOrError;
|
|
| 93 | - self.state.HasEverFailed = TorConnect.hasEverFailed;
|
|
| 94 | - break;
|
|
| 95 | - }
|
|
| 96 | - case TorConnectTopics.BootstrapProgress: {
|
|
| 97 | - self.state.BootstrapProgress = obj.progress;
|
|
| 98 | - self.state.ShowViewLog = obj.hasWarnings;
|
|
| 99 | - break;
|
|
| 100 | - }
|
|
| 101 | - case TorConnectTopics.BootstrapComplete: {
|
|
| 102 | - // noop
|
|
| 41 | + observe(subject, topic) {
|
|
| 42 | + const obj = subject?.wrappedJSObject;
|
|
| 43 | + switch (topic) {
|
|
| 44 | + case TorConnectTopics.StageChange:
|
|
| 45 | + self.sendAsyncMessage("torconnect:stage-change", obj);
|
|
| 103 | 46 | break;
|
| 104 | - }
|
|
| 105 | - case TorConnectTopics.Error: {
|
|
| 106 | - self.state.ErrorCode = obj.code;
|
|
| 107 | - self.state.ErrorDetails = obj;
|
|
| 108 | - self.state.InternetStatus = TorConnect.internetStatus;
|
|
| 109 | - self.state.DetectedLocation = TorConnect.detectedLocation;
|
|
| 110 | - self.state.ShowViewLog = true;
|
|
| 47 | + case TorConnectTopics.BootstrapProgress:
|
|
| 48 | + self.sendAsyncMessage("torconnect:bootstrap-progress", obj);
|
|
| 111 | 49 | break;
|
| 112 | - }
|
|
| 113 | - case TorSettingsTopics.Ready: {
|
|
| 114 | - if (
|
|
| 115 | - self.state.QuickStartEnabled !== TorSettings.quickstart.enabled
|
|
| 116 | - ) {
|
|
| 117 | - self.state.QuickStartEnabled = TorSettings.quickstart.enabled;
|
|
| 118 | - } else {
|
|
| 119 | - return;
|
|
| 50 | + case TorSettingsTopics.SettingsChanged:
|
|
| 51 | + if (!obj.changes.includes("quickstart.enabled")) {
|
|
| 52 | + break;
|
|
| 120 | 53 | }
|
| 54 | + // eslint-disable-next-lined no-fallthrough
|
|
| 55 | + case TorSettingsTopics.Ready:
|
|
| 56 | + self.sendAsyncMessage(
|
|
| 57 | + "torconnect:quickstart-changed",
|
|
| 58 | + TorSettings.quickstart.enabled
|
|
| 59 | + );
|
|
| 121 | 60 | break;
|
| 122 | - }
|
|
| 123 | - case TorSettingsTopics.SettingsChanged: {
|
|
| 124 | - if (
|
|
| 125 | - aSubject.wrappedJSObject.changes.includes("quickstart.enabled")
|
|
| 126 | - ) {
|
|
| 127 | - self.state.QuickStartEnabled = TorSettings.quickstart.enabled;
|
|
| 128 | - } else {
|
|
| 129 | - // this isn't a setting torconnect cares about
|
|
| 130 | - return;
|
|
| 131 | - }
|
|
| 132 | - break;
|
|
| 133 | - }
|
|
| 134 | - default: {
|
|
| 135 | - log.warn(`TorConnect: unhandled observe topic '${aTopic}'`);
|
|
| 136 | - }
|
|
| 137 | 61 | }
|
| 138 | - |
|
| 139 | - self.sendAsyncMessage("torconnect:state-change", self.state);
|
|
| 140 | 62 | },
|
| 141 | 63 | };
|
| 142 | 64 | |
| 143 | - // Observe all of the torconnect:.* topics.
|
|
| 144 | - for (const key in TorConnectTopics) {
|
|
| 145 | - const topic = TorConnectTopics[key];
|
|
| 146 | - Services.obs.addObserver(this.torConnectObserver, topic);
|
|
| 147 | - }
|
|
| 65 | + Services.obs.addObserver(
|
|
| 66 | + this.torConnectObserver,
|
|
| 67 | + TorConnectTopics.StageChange
|
|
| 68 | + );
|
|
| 69 | + Services.obs.addObserver(
|
|
| 70 | + this.torConnectObserver,
|
|
| 71 | + TorConnectTopics.BootstrapProgress
|
|
| 72 | + );
|
|
| 148 | 73 | Services.obs.addObserver(this.torConnectObserver, TorSettingsTopics.Ready);
|
| 149 | 74 | Services.obs.addObserver(
|
| 150 | 75 | this.torConnectObserver,
|
| 151 | 76 | TorSettingsTopics.SettingsChanged
|
| 152 | 77 | );
|
| 153 | - |
|
| 154 | - this.userActionObserver = {
|
|
| 155 | - observe(aSubject) {
|
|
| 156 | - let obj = aSubject?.wrappedJSObject;
|
|
| 157 | - if (obj) {
|
|
| 158 | - obj.connState = self.state;
|
|
| 159 | - self.sendAsyncMessage("torconnect:user-action", obj);
|
|
| 160 | - }
|
|
| 161 | - },
|
|
| 162 | - };
|
|
| 163 | - Services.obs.addObserver(this.userActionObserver, BroadcastTopic);
|
|
| 164 | 78 | }
|
| 165 | 79 | |
| 166 | 80 | willDestroy() {
|
| 167 | - // Stop observing all of our torconnect:.* topics.
|
|
| 168 | - for (const key in TorConnectTopics) {
|
|
| 169 | - const topic = TorConnectTopics[key];
|
|
| 170 | - Services.obs.removeObserver(this.torConnectObserver, topic);
|
|
| 171 | - }
|
|
| 81 | + Services.obs.removeObserver(
|
|
| 82 | + this.torConnectObserver,
|
|
| 83 | + TorConnectTopics.StageChange
|
|
| 84 | + );
|
|
| 85 | + Services.obs.removeObserver(
|
|
| 86 | + this.torConnectObserver,
|
|
| 87 | + TorConnectTopics.BootstrapProgress
|
|
| 88 | + );
|
|
| 172 | 89 | Services.obs.removeObserver(
|
| 173 | 90 | this.torConnectObserver,
|
| 174 | 91 | TorSettingsTopics.Ready
|
| ... | ... | @@ -177,7 +94,6 @@ export class TorConnectParent extends JSWindowActorParent { |
| 177 | 94 | this.torConnectObserver,
|
| 178 | 95 | TorSettingsTopics.SettingsChanged
|
| 179 | 96 | );
|
| 180 | - Services.obs.removeObserver(this.userActionObserver, BroadcastTopic);
|
|
| 181 | 97 | }
|
| 182 | 98 | |
| 183 | 99 | async receiveMessage(message) {
|
| ... | ... | @@ -192,48 +108,57 @@ export class TorConnectParent extends JSWindowActorParent { |
| 192 | 108 | TorSettings.saveToPrefs().applySettings();
|
| 193 | 109 | break;
|
| 194 | 110 | case "torconnect:open-tor-preferences":
|
| 195 | - TorConnect.openTorPreferences();
|
|
| 196 | - break;
|
|
| 197 | - case "torconnect:cancel-bootstrap":
|
|
| 198 | - TorConnect.cancelBootstrap();
|
|
| 199 | - break;
|
|
| 200 | - case "torconnect:begin-bootstrap":
|
|
| 201 | - TorConnect.beginBootstrap();
|
|
| 202 | - break;
|
|
| 203 | - case "torconnect:begin-autobootstrap":
|
|
| 204 | - TorConnect.beginAutoBootstrap(message.data);
|
|
| 111 | + this.browsingContext.top.embedderElement.ownerGlobal.openPreferences(
|
|
| 112 | + "connection"
|
|
| 113 | + );
|
|
| 205 | 114 | break;
|
| 206 | 115 | case "torconnect:view-tor-logs":
|
| 207 | - TorConnect.viewTorLogs();
|
|
| 116 | + this.browsingContext.top.embedderElement.ownerGlobal.openPreferences(
|
|
| 117 | + "connection-viewlogs"
|
|
| 118 | + );
|
|
| 208 | 119 | break;
|
| 209 | 120 | case "torconnect:restart":
|
| 210 | 121 | Services.startup.quit(
|
| 211 | 122 | Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit
|
| 212 | 123 | );
|
| 213 | 124 | break;
|
| 214 | - case "torconnect:set-ui-state":
|
|
| 215 | - TorConnect.uiState = message.data;
|
|
| 216 | - this.state.UIState = TorConnect.uiState;
|
|
| 125 | + case "torconnect:start-again":
|
|
| 126 | + TorConnect.startAgain();
|
|
| 127 | + break;
|
|
| 128 | + case "torconnect:choose-region":
|
|
| 129 | + TorConnect.chooseRegion();
|
|
| 130 | + break;
|
|
| 131 | + case "torconnect:begin-bootstrapping":
|
|
| 132 | + TorConnect.beginBootstrapping(message.data.regionCode);
|
|
| 217 | 133 | break;
|
| 218 | - case "torconnect:broadcast-user-action":
|
|
| 219 | - Services.obs.notifyObservers(message.data, BroadcastTopic);
|
|
| 134 | + case "torconnect:cancel-bootstrapping":
|
|
| 135 | + TorConnect.cancelBootstrapping();
|
|
| 220 | 136 | break;
|
| 221 | - case "torconnect:get-init-args":
|
|
| 137 | + case "torconnect:get-init-args": {
|
|
| 222 | 138 | // Called on AboutTorConnect.init(), pass down all state data it needs
|
| 223 | 139 | // to init.
|
| 224 | 140 | |
| 225 | - // pretend this is a state transition on init
|
|
| 226 | - // so we always get fresh UI
|
|
| 227 | - this.state.StateChanged = true;
|
|
| 228 | - this.state.UIState = TorConnect.uiState;
|
|
| 141 | + let quickstartEnabled = false;
|
|
| 142 | + |
|
| 143 | + // Workaround for a race condition, but we should fix it asap.
|
|
| 144 | + // about:torconnect is loaded before TorSettings is actually initialized.
|
|
| 145 | + // The getter might throw and the page not loaded correctly as a result.
|
|
| 146 | + // Silence any warning for now, but we should really fix it.
|
|
| 147 | + // See also tor-browser#41921.
|
|
| 148 | + try {
|
|
| 149 | + quickstartEnabled = TorSettings.quickstart.enabled;
|
|
| 150 | + } catch (e) {
|
|
| 151 | + // Do not throw.
|
|
| 152 | + }
|
|
| 153 | + |
|
| 229 | 154 | return {
|
| 230 | 155 | TorStrings,
|
| 231 | - TorConnectState,
|
|
| 232 | - InternetStatus,
|
|
| 233 | 156 | Direction: Services.locale.isAppLocaleRTL ? "rtl" : "ltr",
|
| 234 | - State: this.state,
|
|
| 235 | 157 | CountryNames: TorConnect.countryNames,
|
| 158 | + stage: TorConnect.stage,
|
|
| 159 | + quickstartEnabled,
|
|
| 236 | 160 | };
|
| 161 | + }
|
|
| 237 | 162 | case "torconnect:get-country-codes":
|
| 238 | 163 | return TorConnect.getCountryCodes();
|
| 239 | 164 | }
|
| ... | ... | @@ -7,8 +7,6 @@ |
| 7 | 7 | |
| 8 | 8 | // populated in AboutTorConnect.init()
|
| 9 | 9 | let TorStrings = {};
|
| 10 | -let TorConnectState = {};
|
|
| 11 | -let InternetStatus = {};
|
|
| 12 | 10 | |
| 13 | 11 | const UIStates = Object.freeze({
|
| 14 | 12 | ConnectToTor: "ConnectToTor",
|
| ... | ... | @@ -135,53 +133,23 @@ class AboutTorConnect { |
| 135 | 133 | tryBridgeButton: document.querySelector(this.selectors.buttons.tryBridge),
|
| 136 | 134 | });
|
| 137 | 135 | |
| 138 | - uiState = {
|
|
| 139 | - currentState: UIStates.ConnectToTor,
|
|
| 140 | - allowAutomaticLocation: true,
|
|
| 141 | - selectedLocation: "automatic",
|
|
| 142 | - bootstrapCause: UIStates.ConnectToTor,
|
|
| 143 | - };
|
|
| 136 | + selectedLocation;
|
|
| 137 | + shownStage = null;
|
|
| 144 | 138 | |
| 145 | 139 | locations = {};
|
| 146 | 140 | |
| 147 | - constructor() {
|
|
| 148 | - this.uiStates = Object.freeze(
|
|
| 149 | - Object.fromEntries([
|
|
| 150 | - [UIStates.ConnectToTor, this.showConnectToTor.bind(this)],
|
|
| 151 | - [UIStates.Offline, this.showOffline.bind(this)],
|
|
| 152 | - [UIStates.ConnectionAssist, this.showConnectionAssistant.bind(this)],
|
|
| 153 | - [UIStates.CouldNotLocate, this.showCouldNotLocate.bind(this)],
|
|
| 154 | - [UIStates.LocationConfirm, this.showLocationConfirmation.bind(this)],
|
|
| 155 | - [UIStates.FinalError, this.showFinalError.bind(this)],
|
|
| 156 | - ])
|
|
| 157 | - );
|
|
| 158 | - }
|
|
| 159 | - |
|
| 160 | - beginBootstrap() {
|
|
| 161 | - RPMSendAsyncMessage("torconnect:begin-bootstrap");
|
|
| 162 | - }
|
|
| 163 | - |
|
| 164 | - beginAutoBootstrap(countryCode) {
|
|
| 165 | - if (countryCode === "automatic") {
|
|
| 166 | - countryCode = "";
|
|
| 167 | - }
|
|
| 168 | - RPMSendAsyncMessage("torconnect:begin-autobootstrap", countryCode);
|
|
| 141 | + beginBootstrapping() {
|
|
| 142 | + RPMSendAsyncMessage("torconnect:begin-bootstrapping", {});
|
|
| 169 | 143 | }
|
| 170 | 144 | |
| 171 | - cancelBootstrap() {
|
|
| 172 | - RPMSendAsyncMessage("torconnect:cancel-bootstrap");
|
|
| 173 | - }
|
|
| 174 | - |
|
| 175 | - transitionUIState(nextState, connState) {
|
|
| 176 | - if (nextState !== this.uiState.currentState) {
|
|
| 177 | - this.uiState.currentState = nextState;
|
|
| 178 | - this.saveUIState();
|
|
| 179 | - }
|
|
| 180 | - this.uiStates[nextState](connState);
|
|
| 145 | + beginAutoBootstrapping(regionCode) {
|
|
| 146 | + RPMSendAsyncMessage("torconnect:begin-bootstrapping", {
|
|
| 147 | + regionCode,
|
|
| 148 | + });
|
|
| 181 | 149 | }
|
| 182 | 150 | |
| 183 | - saveUIState() {
|
|
| 184 | - RPMSendAsyncMessage("torconnect:set-ui-state", this.uiState);
|
|
| 151 | + cancelBootstrapping() {
|
|
| 152 | + RPMSendAsyncMessage("torconnect:cancel-bootstrapping");
|
|
| 185 | 153 | }
|
| 186 | 154 | |
| 187 | 155 | /*
|
| ... | ... | @@ -305,19 +273,6 @@ class AboutTorConnect { |
| 305 | 273 | this.elements.longContentText.append(...args);
|
| 306 | 274 | }
|
| 307 | 275 | |
| 308 | - setProgress(description, visible, percent) {
|
|
| 309 | - this.elements.progressDescription.textContent = description;
|
|
| 310 | - if (visible) {
|
|
| 311 | - this.show(this.elements.progressMeter);
|
|
| 312 | - this.elements.progressMeter.style.setProperty(
|
|
| 313 | - "--progress-percent",
|
|
| 314 | - `${percent}%`
|
|
| 315 | - );
|
|
| 316 | - } else {
|
|
| 317 | - this.hide(this.elements.progressMeter);
|
|
| 318 | - }
|
|
| 319 | - }
|
|
| 320 | - |
|
| 321 | 276 | setBreadcrumbsStatus(connectToTor, connectionAssist, tryBridge) {
|
| 322 | 277 | this.elements.breadcrumbContainer.classList.remove("hidden");
|
| 323 | 278 | const elems = [
|
| ... | ... | @@ -362,22 +317,17 @@ class AboutTorConnect { |
| 362 | 317 | return TorStrings.torConnect.bootstrapStatus[status] ?? status;
|
| 363 | 318 | }
|
| 364 | 319 | |
| 365 | - getMaybeLocalizedError(state) {
|
|
| 366 | - if (!state?.ErrorCode) {
|
|
| 367 | - return "";
|
|
| 368 | - }
|
|
| 369 | - switch (state.ErrorCode) {
|
|
| 320 | + getMaybeLocalizedError(error) {
|
|
| 321 | + switch (error.code) {
|
|
| 370 | 322 | case "Offline":
|
| 371 | 323 | return TorStrings.torConnect.offline;
|
| 372 | 324 | case "BootstrapError": {
|
| 373 | - const details = state.ErrorDetails?.cause;
|
|
| 374 | - if (!details?.phase || !details?.reason) {
|
|
| 325 | + if (!error.phase || !error.reason) {
|
|
| 375 | 326 | return TorStrings.torConnect.torBootstrapFailed;
|
| 376 | 327 | }
|
| 377 | - let status = this.getLocalizedStatus(details.phase);
|
|
| 328 | + let status = this.getLocalizedStatus(error.phase);
|
|
| 378 | 329 | const reason =
|
| 379 | - TorStrings.torConnect.bootstrapWarning[details.reason] ??
|
|
| 380 | - details.reason;
|
|
| 330 | + TorStrings.torConnect.bootstrapWarning[error.reason] ?? error.reason;
|
|
| 381 | 331 | return TorStrings.torConnect.bootstrapFailedDetails
|
| 382 | 332 | .replace("%1$S", status)
|
| 383 | 333 | .replace("%2$S", reason);
|
| ... | ... | @@ -392,13 +342,10 @@ class AboutTorConnect { |
| 392 | 342 | // A standard JS error, or something for which we do probably do not
|
| 393 | 343 | // have a translation. Returning the original message is the best we can
|
| 394 | 344 | // do.
|
| 395 | - return state.ErrorDetails.message;
|
|
| 345 | + return error.message;
|
|
| 396 | 346 | default:
|
| 397 | - console.warn(
|
|
| 398 | - `Unknown error code: ${state.ErrorCode}`,
|
|
| 399 | - state.ErrorDetails
|
|
| 400 | - );
|
|
| 401 | - return state.ErrorDetails?.message ?? state.ErrorCode;
|
|
| 347 | + console.warn(`Unknown error code: ${error.code}`, error);
|
|
| 348 | + return error.message || error.code;
|
|
| 402 | 349 | }
|
| 403 | 350 | }
|
| 404 | 351 | |
| ... | ... | @@ -406,109 +353,119 @@ class AboutTorConnect { |
| 406 | 353 | These methods update the UI based on the current TorConnect state
|
| 407 | 354 | */
|
| 408 | 355 | |
| 409 | - updateUI(state) {
|
|
| 410 | - // calls update_$state()
|
|
| 411 | - this[`update_${state.State}`](state);
|
|
| 412 | - this.elements.quickstartToggle.pressed = state.QuickStartEnabled;
|
|
| 413 | - }
|
|
| 356 | + updateStage(stage) {
|
|
| 357 | + if (stage.name === this.shownStage) {
|
|
| 358 | + return;
|
|
| 359 | + }
|
|
| 414 | 360 | |
| 415 | - /* Per-state updates */
|
|
| 361 | + this.shownStage = stage.name;
|
|
| 362 | + this.selectedLocation = stage.defaultRegion;
|
|
| 416 | 363 | |
| 417 | - update_Initial(state) {
|
|
| 418 | - this.showConnectToTor(state);
|
|
| 419 | - }
|
|
| 364 | + let showProgress = false;
|
|
| 365 | + let showLog = false;
|
|
| 366 | + switch (stage.name) {
|
|
| 367 | + case "Disabled":
|
|
| 368 | + console.error("Should not be open when TorConnect is disabled");
|
|
| 369 | + break;
|
|
| 370 | + case "Loading":
|
|
| 371 | + case "Start":
|
|
| 372 | + // Loading is not currnetly handled, treat the same as "Start", but UI
|
|
| 373 | + // will be unresponsive.
|
|
| 374 | + this.showStart(stage.tryAgain, stage.potentiallyBlocked);
|
|
| 375 | + break;
|
|
| 376 | + case "Bootstrapping":
|
|
| 377 | + showProgress = true;
|
|
| 378 | + this.showBootstrapping(stage.bootstrapTrigger, stage.tryAgain);
|
|
| 379 | + break;
|
|
| 380 | + case "Offline":
|
|
| 381 | + showLog = true;
|
|
| 382 | + this.showOffline();
|
|
| 383 | + break;
|
|
| 384 | + case "ChooseRegion":
|
|
| 385 | + showLog = true;
|
|
| 386 | + this.showChooseRegion(stage.error);
|
|
| 387 | + break;
|
|
| 388 | + case "RegionNotFound":
|
|
| 389 | + showLog = true;
|
|
| 390 | + this.showRegionNotFound();
|
|
| 391 | + break;
|
|
| 392 | + case "ConfirmRegion":
|
|
| 393 | + showLog = true;
|
|
| 394 | + this.showConfirmRegion(stage.error);
|
|
| 395 | + break;
|
|
| 396 | + case "FinalError":
|
|
| 397 | + showLog = true;
|
|
| 398 | + this.showFinalError(stage.error);
|
|
| 399 | + break;
|
|
| 400 | + case "Bootstrapped":
|
|
| 401 | + showProgress = true;
|
|
| 402 | + this.showBootstrapped();
|
|
| 403 | + break;
|
|
| 404 | + default:
|
|
| 405 | + console.error(`Unknown stage ${stage.name}`);
|
|
| 406 | + break;
|
|
| 407 | + }
|
|
| 420 | 408 | |
| 421 | - update_Configuring(state) {
|
|
| 422 | - if (
|
|
| 423 | - state.StateChanged &&
|
|
| 424 | - (state.PreviousState === TorConnectState.Bootstrapping ||
|
|
| 425 | - state.PreviousState === TorConnectState.AutoBootstrapping)
|
|
| 426 | - ) {
|
|
| 427 | - // The bootstrap has been cancelled
|
|
| 428 | - this.transitionUIState(this.uiState.bootstrapCause, state);
|
|
| 409 | + if (showProgress) {
|
|
| 410 | + this.show(this.elements.progressMeter);
|
|
| 411 | + } else {
|
|
| 412 | + this.hide(this.elements.progressMeter);
|
|
| 429 | 413 | }
|
| 430 | - }
|
|
| 431 | 414 | |
| 432 | - update_AutoBootstrapping(state) {
|
|
| 433 | - this.showBootstrapping(state);
|
|
| 434 | - }
|
|
| 415 | + this.updateBootstrappingStatus(stage.bootstrappingStatus);
|
|
| 435 | 416 | |
| 436 | - update_Bootstrapping(state) {
|
|
| 437 | - this.showBootstrapping(state);
|
|
| 417 | + if (showLog) {
|
|
| 418 | + this.show(this.elements.viewLogButton);
|
|
| 419 | + } else {
|
|
| 420 | + this.hide(this.elements.viewLogButton);
|
|
| 421 | + }
|
|
| 438 | 422 | }
|
| 439 | 423 | |
| 440 | - update_Error(state) {
|
|
| 441 | - if (!state.StateChanged) {
|
|
| 442 | - return;
|
|
| 443 | - }
|
|
| 444 | - if (state.InternetStatus === InternetStatus.Offline) {
|
|
| 445 | - this.transitionUIState(UIStates.Offline, state);
|
|
| 446 | - } else if (state.PreviousState === TorConnectState.Bootstrapping) {
|
|
| 447 | - this.transitionUIState(UIStates.ConnectionAssist, state);
|
|
| 448 | - } else if (state.PreviousState === TorConnectState.AutoBootstrapping) {
|
|
| 449 | - if (this.uiState.bootstrapCause === UIStates.ConnectionAssist) {
|
|
| 450 | - if (this.getLocation() === "automatic") {
|
|
| 451 | - this.uiState.allowAutomaticLocation = false;
|
|
| 452 | - if (!state.DetectedLocation) {
|
|
| 453 | - this.transitionUIState(UIStates.CouldNotLocate, state);
|
|
| 454 | - return;
|
|
| 455 | - }
|
|
| 456 | - // Change the location only here, to avoid overriding any user change/
|
|
| 457 | - // insisting with the detected location
|
|
| 458 | - this.setLocation(state.DetectedLocation);
|
|
| 459 | - }
|
|
| 460 | - this.transitionUIState(UIStates.LocationConfirm, state);
|
|
| 461 | - } else {
|
|
| 462 | - this.transitionUIState(UIStates.FinalError, state);
|
|
| 463 | - }
|
|
| 464 | - } else {
|
|
| 465 | - console.error(
|
|
| 466 | - "We received an error starting from an unexpected state",
|
|
| 467 | - state
|
|
| 468 | - );
|
|
| 424 | + updateBootstrappingStatus(data) {
|
|
| 425 | + this.elements.progressMeter.style.setProperty(
|
|
| 426 | + "--progress-percent",
|
|
| 427 | + `${data.progress}%`
|
|
| 428 | + );
|
|
| 429 | + if (this.shownStage === "Bootstrapping" && data.hasWarning) {
|
|
| 430 | + // When bootstrapping starts, we hide the log button, but we re-show it if
|
|
| 431 | + // we get a warning.
|
|
| 432 | + this.show(this.elements.viewLogButton);
|
|
| 469 | 433 | }
|
| 470 | 434 | }
|
| 471 | 435 | |
| 472 | - update_Bootstrapped(_state) {
|
|
| 473 | - const showProgressbar = true;
|
|
| 436 | + updateQuickstart(enabled) {
|
|
| 437 | + this.elements.quickstartToggle.pressed = enabled;
|
|
| 438 | + }
|
|
| 474 | 439 | |
| 440 | + showBootstrapped() {
|
|
| 475 | 441 | this.setTitle(TorStrings.torConnect.torConnected, "");
|
| 476 | 442 | this.setLongText(TorStrings.settings.torPreferencesDescription);
|
| 477 | - this.setProgress("", showProgressbar, 100);
|
|
| 443 | + this.elements.progressDescription.textContent = "";
|
|
| 478 | 444 | this.hideButtons();
|
| 479 | 445 | }
|
| 480 | 446 | |
| 481 | - update_Disabled(_state) {
|
|
| 482 | - // TODO: we should probably have some UX here if a user goes to about:torconnect when
|
|
| 483 | - // it isn't in use (eg using tor-launcher or system tor)
|
|
| 484 | - }
|
|
| 485 | - |
|
| 486 | - showConnectToTor(state) {
|
|
| 447 | + showStart(tryAgain, potentiallyBlocked) {
|
|
| 487 | 448 | this.setTitle(TorStrings.torConnect.torConnect, "");
|
| 488 | 449 | this.setLongText(TorStrings.settings.torPreferencesDescription);
|
| 489 | - this.setProgress("", false);
|
|
| 490 | - this.hide(this.elements.viewLogButton);
|
|
| 450 | + this.elements.progressDescription.textContent = "";
|
|
| 491 | 451 | this.hideButtons();
|
| 492 | 452 | this.show(this.elements.quickstartContainer);
|
| 493 | 453 | this.show(this.elements.configureButton);
|
| 494 | 454 | this.show(this.elements.connectButton, true);
|
| 495 | - if (state?.StateChanged) {
|
|
| 496 | - this.elements.connectButton.focus();
|
|
| 455 | + this.elements.connectButton.focus();
|
|
| 456 | + if (tryAgain) {
|
|
| 457 | + this.elements.connectButton.textContent = TorStrings.torConnect.tryAgain;
|
|
| 497 | 458 | }
|
| 498 | - if (state?.HasEverFailed) {
|
|
| 459 | + if (potentiallyBlocked) {
|
|
| 499 | 460 | this.setBreadcrumbsStatus(
|
| 500 | 461 | BreadcrumbStatus.Active,
|
| 501 | 462 | BreadcrumbStatus.Default,
|
| 502 | 463 | BreadcrumbStatus.Disabled
|
| 503 | 464 | );
|
| 504 | - this.elements.connectButton.textContent = TorStrings.torConnect.tryAgain;
|
|
| 505 | 465 | }
|
| 506 | - this.uiState.bootstrapCause = UIStates.ConnectToTor;
|
|
| 507 | - this.saveUIState();
|
|
| 508 | 466 | }
|
| 509 | 467 | |
| 510 | - showBootstrapping(state) {
|
|
| 511 | - const showProgressbar = true;
|
|
| 468 | + showBootstrapping(trigger, tryAgain) {
|
|
| 512 | 469 | let title = "";
|
| 513 | 470 | let description = "";
|
| 514 | 471 | const breadcrumbs = [
|
| ... | ... | @@ -516,128 +473,114 @@ class AboutTorConnect { |
| 516 | 473 | BreadcrumbStatus.Disabled,
|
| 517 | 474 | BreadcrumbStatus.Disabled,
|
| 518 | 475 | ];
|
| 519 | - switch (this.uiState.bootstrapCause) {
|
|
| 520 | - case UIStates.ConnectToTor:
|
|
| 476 | + switch (trigger) {
|
|
| 477 | + case "Start":
|
|
| 478 | + case "Offline":
|
|
| 521 | 479 | breadcrumbs[0] = BreadcrumbStatus.Active;
|
| 522 | - title = state.HasEverFailed
|
|
| 480 | + title = tryAgain
|
|
| 523 | 481 | ? TorStrings.torConnect.tryAgain
|
| 524 | 482 | : TorStrings.torConnect.torConnecting;
|
| 525 | 483 | description = TorStrings.settings.torPreferencesDescription;
|
| 526 | 484 | break;
|
| 527 | - case UIStates.ConnectionAssist:
|
|
| 485 | + case "ChooseRegion":
|
|
| 528 | 486 | breadcrumbs[2] = BreadcrumbStatus.Active;
|
| 529 | 487 | title = TorStrings.torConnect.tryingBridge;
|
| 530 | 488 | description = TorStrings.torConnect.assistDescription;
|
| 531 | 489 | break;
|
| 532 | - case UIStates.CouldNotLocate:
|
|
| 490 | + case "RegionNotFound":
|
|
| 533 | 491 | breadcrumbs[2] = BreadcrumbStatus.Active;
|
| 534 | 492 | title = TorStrings.torConnect.tryingBridgeAgain;
|
| 535 | 493 | description = TorStrings.torConnect.errorLocationDescription;
|
| 536 | 494 | break;
|
| 537 | - case UIStates.LocationConfirm:
|
|
| 495 | + case "ConfirmRegion":
|
|
| 538 | 496 | breadcrumbs[2] = BreadcrumbStatus.Active;
|
| 539 | 497 | title = TorStrings.torConnect.tryingBridgeAgain;
|
| 540 | 498 | description = TorStrings.torConnect.isLocationCorrectDescription;
|
| 541 | 499 | break;
|
| 500 | + default:
|
|
| 501 | + console.warn("Unrecognized bootstrap trigger", trigger);
|
|
| 502 | + break;
|
|
| 542 | 503 | }
|
| 543 | 504 | this.setTitle(title, "");
|
| 544 | 505 | this.showConfigureConnectionLink(description);
|
| 545 | - this.setProgress("", showProgressbar, state.BootstrapProgress);
|
|
| 546 | - if (state.HasEverFailed) {
|
|
| 506 | + this.elements.progressDescription.textContent = "";
|
|
| 507 | + if (tryAgain) {
|
|
| 547 | 508 | this.setBreadcrumbsStatus(...breadcrumbs);
|
| 548 | 509 | } else {
|
| 549 | 510 | this.hideBreadcrumbs();
|
| 550 | 511 | }
|
| 551 | 512 | this.hideButtons();
|
| 552 | - if (state.ShowViewLog) {
|
|
| 553 | - this.show(this.elements.viewLogButton);
|
|
| 554 | - } else {
|
|
| 555 | - this.hide(this.elements.viewLogButton);
|
|
| 556 | - }
|
|
| 557 | 513 | this.show(this.elements.cancelButton);
|
| 558 | - if (state.StateChanged) {
|
|
| 559 | - this.elements.cancelButton.focus();
|
|
| 560 | - }
|
|
| 514 | + this.elements.cancelButton.focus();
|
|
| 561 | 515 | }
|
| 562 | 516 | |
| 563 | - showOffline(state) {
|
|
| 517 | + showOffline() {
|
|
| 564 | 518 | this.setTitle(TorStrings.torConnect.noInternet, "offline");
|
| 565 | 519 | this.setLongText(TorStrings.torConnect.noInternetDescription);
|
| 566 | - this.setProgress(this.getMaybeLocalizedError(state), false);
|
|
| 520 | + this.elements.progressDescription.textContent =
|
|
| 521 | + TorStrings.torConnect.offline;
|
|
| 567 | 522 | this.setBreadcrumbsStatus(
|
| 568 | 523 | BreadcrumbStatus.Default,
|
| 569 | 524 | BreadcrumbStatus.Active,
|
| 570 | 525 | BreadcrumbStatus.Hidden
|
| 571 | 526 | );
|
| 572 | - this.show(this.elements.viewLogButton);
|
|
| 573 | 527 | this.hideButtons();
|
| 574 | 528 | this.show(this.elements.configureButton);
|
| 575 | 529 | this.show(this.elements.connectButton, true);
|
| 576 | 530 | this.elements.connectButton.textContent = TorStrings.torConnect.tryAgain;
|
| 577 | 531 | }
|
| 578 | 532 | |
| 579 | - showConnectionAssistant(state) {
|
|
| 533 | + showChooseRegion(error) {
|
|
| 580 | 534 | this.setTitle(TorStrings.torConnect.couldNotConnect, "assist");
|
| 581 | 535 | this.showConfigureConnectionLink(TorStrings.torConnect.assistDescription);
|
| 582 | - this.setProgress(this.getMaybeLocalizedError(state), false);
|
|
| 536 | + this.elements.progressDescription.textContent =
|
|
| 537 | + this.getMaybeLocalizedError(error);
|
|
| 583 | 538 | this.setBreadcrumbsStatus(
|
| 584 | 539 | BreadcrumbStatus.Default,
|
| 585 | 540 | BreadcrumbStatus.Active,
|
| 586 | 541 | BreadcrumbStatus.Disabled
|
| 587 | 542 | );
|
| 588 | - this.showLocationForm(false, TorStrings.torConnect.tryBridge);
|
|
| 589 | - if (state?.StateChanged) {
|
|
| 590 | - this.elements.tryBridgeButton.focus();
|
|
| 591 | - }
|
|
| 592 | - this.uiState.bootstrapCause = UIStates.ConnectionAssist;
|
|
| 593 | - this.saveUIState();
|
|
| 543 | + this.showLocationForm(true, TorStrings.torConnect.tryBridge);
|
|
| 544 | + this.elements.tryBridgeButton.focus();
|
|
| 594 | 545 | }
|
| 595 | 546 | |
| 596 | - showCouldNotLocate(state) {
|
|
| 597 | - this.uiState.allowAutomaticLocation = false;
|
|
| 547 | + showRegionNotFound() {
|
|
| 598 | 548 | this.setTitle(TorStrings.torConnect.errorLocation, "location");
|
| 599 | 549 | this.showConfigureConnectionLink(
|
| 600 | 550 | TorStrings.torConnect.errorLocationDescription
|
| 601 | 551 | );
|
| 602 | - this.setProgress(TorStrings.torConnect.cannotDetermineCountry, false);
|
|
| 552 | + this.elements.progressDescription.textContent =
|
|
| 553 | + TorStrings.torConnect.cannotDetermineCountry;
|
|
| 603 | 554 | this.setBreadcrumbsStatus(
|
| 604 | 555 | BreadcrumbStatus.Default,
|
| 605 | 556 | BreadcrumbStatus.Active,
|
| 606 | 557 | BreadcrumbStatus.Disabled
|
| 607 | 558 | );
|
| 608 | - this.show(this.elements.viewLogButton);
|
|
| 609 | - this.showLocationForm(true, TorStrings.torConnect.tryBridge);
|
|
| 610 | - if (state.StateChanged) {
|
|
| 611 | - this.elements.tryBridgeButton.focus();
|
|
| 612 | - }
|
|
| 613 | - this.uiState.bootstrapCause = UIStates.CouldNotLocate;
|
|
| 614 | - this.saveUIState();
|
|
| 559 | + this.showLocationForm(false, TorStrings.torConnect.tryBridge);
|
|
| 560 | + this.elements.tryBridgeButton.focus();
|
|
| 615 | 561 | }
|
| 616 | 562 | |
| 617 | - showLocationConfirmation(state) {
|
|
| 563 | + showConfirmRegion(error) {
|
|
| 618 | 564 | this.setTitle(TorStrings.torConnect.isLocationCorrect, "location");
|
| 619 | 565 | this.showConfigureConnectionLink(
|
| 620 | 566 | TorStrings.torConnect.isLocationCorrectDescription
|
| 621 | 567 | );
|
| 622 | - this.setProgress(this.getMaybeLocalizedError(state), false);
|
|
| 568 | + this.elements.progressDescription.textContent =
|
|
| 569 | + this.getMaybeLocalizedError(error);
|
|
| 623 | 570 | this.setBreadcrumbsStatus(
|
| 624 | 571 | BreadcrumbStatus.Default,
|
| 625 | 572 | BreadcrumbStatus.Default,
|
| 626 | 573 | BreadcrumbStatus.Active
|
| 627 | 574 | );
|
| 628 | - this.show(this.elements.viewLogButton);
|
|
| 629 | - this.showLocationForm(true, TorStrings.torConnect.tryAgain);
|
|
| 630 | - if (state.StateChanged) {
|
|
| 631 | - this.elements.tryBridgeButton.focus();
|
|
| 632 | - }
|
|
| 633 | - this.uiState.bootstrapCause = UIStates.LocationConfirm;
|
|
| 634 | - this.saveUIState();
|
|
| 575 | + this.showLocationForm(false, TorStrings.torConnect.tryAgain);
|
|
| 576 | + this.elements.tryBridgeButton.focus();
|
|
| 635 | 577 | }
|
| 636 | 578 | |
| 637 | - showFinalError(state) {
|
|
| 579 | + showFinalError(error) {
|
|
| 638 | 580 | this.setTitle(TorStrings.torConnect.finalError, "final");
|
| 639 | 581 | this.setLongText(TorStrings.torConnect.finalErrorDescription);
|
| 640 | - this.setProgress(this.getMaybeLocalizedError(state), false);
|
|
| 582 | + this.elements.progressDescription.textContent =
|
|
| 583 | + this.getMaybeLocalizedError(error);
|
|
| 641 | 584 | this.setBreadcrumbsStatus(
|
| 642 | 585 | BreadcrumbStatus.Default,
|
| 643 | 586 | BreadcrumbStatus.Default,
|
| ... | ... | @@ -665,7 +608,7 @@ class AboutTorConnect { |
| 665 | 608 | }
|
| 666 | 609 | }
|
| 667 | 610 | |
| 668 | - showLocationForm(isError, buttonLabel) {
|
|
| 611 | + showLocationForm(isChoose, buttonLabel) {
|
|
| 669 | 612 | this.hideButtons();
|
| 670 | 613 | RPMSendQuery("torconnect:get-country-codes").then(codes => {
|
| 671 | 614 | if (codes && codes.length) {
|
| ... | ... | @@ -674,7 +617,7 @@ class AboutTorConnect { |
| 674 | 617 | }
|
| 675 | 618 | });
|
| 676 | 619 | let firstOpt = this.elements.locationDropdownSelect.options[0];
|
| 677 | - if (this.uiState.allowAutomaticLocation) {
|
|
| 620 | + if (isChoose) {
|
|
| 678 | 621 | firstOpt.value = "automatic";
|
| 679 | 622 | firstOpt.textContent = TorStrings.torConnect.automatic;
|
| 680 | 623 | } else {
|
| ... | ... | @@ -685,7 +628,7 @@ class AboutTorConnect { |
| 685 | 628 | this.validateLocation();
|
| 686 | 629 | this.show(this.elements.locationDropdownLabel);
|
| 687 | 630 | this.show(this.elements.locationDropdown);
|
| 688 | - this.elements.locationDropdownLabel.classList.toggle("error", isError);
|
|
| 631 | + this.elements.locationDropdownLabel.classList.toggle("error", !isChoose);
|
|
| 689 | 632 | this.show(this.elements.tryBridgeButton, true);
|
| 690 | 633 | if (buttonLabel !== undefined) {
|
| 691 | 634 | this.elements.tryBridgeButton.textContent = buttonLabel;
|
| ... | ... | @@ -697,12 +640,8 @@ class AboutTorConnect { |
| 697 | 640 | return this.elements.locationDropdownSelect.options[selectedIndex].value;
|
| 698 | 641 | }
|
| 699 | 642 | |
| 700 | - setLocation(code) {
|
|
| 701 | - if (!code) {
|
|
| 702 | - code = this.uiState.selectedLocation;
|
|
| 703 | - } else {
|
|
| 704 | - this.uiState.selectedLocation = code;
|
|
| 705 | - }
|
|
| 643 | + setLocation() {
|
|
| 644 | + const code = this.selectedLocation;
|
|
| 706 | 645 | if (this.getLocation() === code) {
|
| 707 | 646 | return;
|
| 708 | 647 | }
|
| ... | ... | @@ -726,13 +665,7 @@ class AboutTorConnect { |
| 726 | 665 | document.documentElement.setAttribute("dir", direction);
|
| 727 | 666 | |
| 728 | 667 | this.elements.connectToTorLink.addEventListener("click", () => {
|
| 729 | - if (this.uiState.currentState === UIStates.ConnectToTor) {
|
|
| 730 | - return;
|
|
| 731 | - }
|
|
| 732 | - this.transitionUIState(UIStates.ConnectToTor, null);
|
|
| 733 | - RPMSendAsyncMessage("torconnect:broadcast-user-action", {
|
|
| 734 | - uiState: UIStates.ConnectToTor,
|
|
| 735 | - });
|
|
| 668 | + RPMSendAsyncMessage("torconnect:start-again");
|
|
| 736 | 669 | });
|
| 737 | 670 | this.elements.connectToTorLabel.textContent =
|
| 738 | 671 | TorStrings.torConnect.torConnect;
|
| ... | ... | @@ -747,10 +680,7 @@ class AboutTorConnect { |
| 747 | 680 | ) {
|
| 748 | 681 | return;
|
| 749 | 682 | }
|
| 750 | - this.transitionUIState(UIStates.ConnectionAssist, null);
|
|
| 751 | - RPMSendAsyncMessage("torconnect:broadcast-user-action", {
|
|
| 752 | - uiState: UIStates.ConnectionAssist,
|
|
| 753 | - });
|
|
| 683 | + RPMSendAsyncMessage("torconnect:choose-region");
|
|
| 754 | 684 | });
|
| 755 | 685 | this.elements.connectionAssistLabel.textContent =
|
| 756 | 686 | TorStrings.torConnect.breadcrumbAssist;
|
| ... | ... | @@ -786,23 +716,18 @@ class AboutTorConnect { |
| 786 | 716 | |
| 787 | 717 | this.elements.cancelButton.textContent = TorStrings.torConnect.cancel;
|
| 788 | 718 | this.elements.cancelButton.addEventListener("click", () => {
|
| 789 | - this.cancelBootstrap();
|
|
| 719 | + this.cancelBootstrapping();
|
|
| 790 | 720 | });
|
| 791 | 721 | |
| 792 | 722 | this.elements.connectButton.textContent =
|
| 793 | 723 | TorStrings.torConnect.torConnectButton;
|
| 794 | 724 | this.elements.connectButton.addEventListener("click", () => {
|
| 795 | - this.beginBootstrap();
|
|
| 725 | + this.beginBootstrapping();
|
|
| 796 | 726 | });
|
| 797 | 727 | |
| 798 | 728 | this.populateLocations();
|
| 799 | 729 | this.elements.locationDropdownSelect.addEventListener("change", () => {
|
| 800 | - this.uiState.selectedLocation = this.getLocation();
|
|
| 801 | - this.saveUIState();
|
|
| 802 | 730 | this.validateLocation();
|
| 803 | - RPMSendAsyncMessage("torconnect:broadcast-user-action", {
|
|
| 804 | - location: this.uiState.selectedLocation,
|
|
| 805 | - });
|
|
| 806 | 731 | });
|
| 807 | 732 | |
| 808 | 733 | this.elements.locationDropdownLabel.textContent =
|
| ... | ... | @@ -811,10 +736,8 @@ class AboutTorConnect { |
| 811 | 736 | this.elements.tryBridgeButton.textContent = TorStrings.torConnect.tryBridge;
|
| 812 | 737 | this.elements.tryBridgeButton.addEventListener("click", () => {
|
| 813 | 738 | const value = this.getLocation();
|
| 814 | - if (value === "automatic") {
|
|
| 815 | - this.beginAutoBootstrap();
|
|
| 816 | - } else {
|
|
| 817 | - this.beginAutoBootstrap(value);
|
|
| 739 | + if (value) {
|
|
| 740 | + this.beginAutoBootstrapping(value);
|
|
| 818 | 741 | }
|
| 819 | 742 | });
|
| 820 | 743 | |
| ... | ... | @@ -846,17 +769,14 @@ class AboutTorConnect { |
| 846 | 769 | |
| 847 | 770 | initObservers() {
|
| 848 | 771 | // TorConnectParent feeds us state blobs to we use to update our UI
|
| 849 | - RPMAddMessageListener("torconnect:state-change", ({ data }) => {
|
|
| 850 | - this.updateUI(data);
|
|
| 772 | + RPMAddMessageListener("torconnect:stage-change", ({ data }) => {
|
|
| 773 | + this.updateStage(data);
|
|
| 851 | 774 | });
|
| 852 | - RPMAddMessageListener("torconnect:user-action", ({ data }) => {
|
|
| 853 | - if (data.location) {
|
|
| 854 | - this.uiState.selectedLocation = data.location;
|
|
| 855 | - this.setLocation();
|
|
| 856 | - }
|
|
| 857 | - if (data.uiState !== undefined) {
|
|
| 858 | - this.transitionUIState(data.uiState, data.connState);
|
|
| 859 | - }
|
|
| 775 | + RPMAddMessageListener("torconnect:bootstrap-progress", ({ data }) => {
|
|
| 776 | + this.updateBootstrappingStatus(data);
|
|
| 777 | + });
|
|
| 778 | + RPMAddMessageListener("torconnect:quickstart-change", ({ data }) => {
|
|
| 779 | + this.updateQuickstart(data);
|
|
| 860 | 780 | });
|
| 861 | 781 | }
|
| 862 | 782 | |
| ... | ... | @@ -866,7 +786,7 @@ class AboutTorConnect { |
| 866 | 786 | // integers, so we must resort to a string compare here :(
|
| 867 | 787 | // see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code for relevant documentation
|
| 868 | 788 | if (evt.code === "Escape") {
|
| 869 | - this.cancelBootstrap();
|
|
| 789 | + this.cancelBootstrapping();
|
|
| 870 | 790 | }
|
| 871 | 791 | };
|
| 872 | 792 | }
|
| ... | ... | @@ -876,23 +796,14 @@ class AboutTorConnect { |
| 876 | 796 | |
| 877 | 797 | // various constants
|
| 878 | 798 | TorStrings = Object.freeze(args.TorStrings);
|
| 879 | - TorConnectState = Object.freeze(args.TorConnectState);
|
|
| 880 | - InternetStatus = Object.freeze(args.InternetStatus);
|
|
| 881 | 799 | this.locations = args.CountryNames;
|
| 882 | 800 | |
| 883 | 801 | this.initElements(args.Direction);
|
| 884 | 802 | this.initObservers();
|
| 885 | 803 | this.initKeyboardShortcuts();
|
| 886 | 804 | |
| 887 | - if (Object.keys(args.State.UIState).length) {
|
|
| 888 | - this.uiState = args.State.UIState;
|
|
| 889 | - } else {
|
|
| 890 | - args.State.UIState = this.uiState;
|
|
| 891 | - this.saveUIState();
|
|
| 892 | - }
|
|
| 893 | - this.uiStates[this.uiState.currentState](args.State);
|
|
| 894 | - // populate UI based on current state
|
|
| 895 | - this.updateUI(args.State);
|
|
| 805 | + this.updateStage(args.stage);
|
|
| 806 | + this.updateQuickstart(args.quickstartEnabled);
|
|
| 896 | 807 | }
|
| 897 | 808 | }
|
| 898 | 809 |
| ... | ... | @@ -38,7 +38,7 @@ var gTorConnectTitlebarStatus = { |
| 38 | 38 | // The title also acts as an accessible name for the role="status".
|
| 39 | 39 | this.node.setAttribute("title", this._strings.titlebarStatusName);
|
| 40 | 40 | |
| 41 | - this._observeTopic = TorConnectTopics.StateChange;
|
|
| 41 | + this._observeTopic = TorConnectTopics.StageChange;
|
|
| 42 | 42 | this._stateListener = {
|
| 43 | 43 | observe: (subject, topic) => {
|
| 44 | 44 | if (topic !== this._observeTopic) {
|
| ... | ... | @@ -66,17 +66,16 @@ var gTorConnectTitlebarStatus = { |
| 66 | 66 | let textId;
|
| 67 | 67 | let connected = false;
|
| 68 | 68 | let potentiallyBlocked = false;
|
| 69 | - switch (TorConnect.state) {
|
|
| 70 | - case TorConnectState.Disabled:
|
|
| 69 | + switch (TorConnect.stageName) {
|
|
| 70 | + case TorConnectStage.Disabled:
|
|
| 71 | 71 | // Hide immediately.
|
| 72 | 72 | this.node.hidden = true;
|
| 73 | 73 | return;
|
| 74 | - case TorConnectState.Bootstrapped:
|
|
| 74 | + case TorConnectStage.Bootstrapped:
|
|
| 75 | 75 | textId = "titlebarStatusConnected";
|
| 76 | 76 | connected = true;
|
| 77 | 77 | break;
|
| 78 | - case TorConnectState.Bootstrapping:
|
|
| 79 | - case TorConnectState.AutoBootstrapping:
|
|
| 78 | + case TorConnectStage.Bootstrapping:
|
|
| 80 | 79 | textId = "titlebarStatusConnecting";
|
| 81 | 80 | break;
|
| 82 | 81 | default:
|
| ... | ... | @@ -55,13 +55,13 @@ var gTorConnectUrlbarButton = { |
| 55 | 55 | this.connect();
|
| 56 | 56 | });
|
| 57 | 57 | |
| 58 | - this._observeTopic = TorConnectTopics.StateChange;
|
|
| 58 | + this._observeTopic = TorConnectTopics.StageChange;
|
|
| 59 | 59 | this._stateListener = {
|
| 60 | 60 | observe: (subject, topic) => {
|
| 61 | 61 | if (topic !== this._observeTopic) {
|
| 62 | 62 | return;
|
| 63 | 63 | }
|
| 64 | - this._torConnectStateChanged();
|
|
| 64 | + this._torConnectStageChanged();
|
|
| 65 | 65 | },
|
| 66 | 66 | };
|
| 67 | 67 | Services.obs.addObserver(this._stateListener, this._observeTopic);
|
| ... | ... | @@ -84,7 +84,7 @@ var gTorConnectUrlbarButton = { |
| 84 | 84 | // switching selected browser.
|
| 85 | 85 | gBrowser.addProgressListener(this._locationListener);
|
| 86 | 86 | |
| 87 | - this._torConnectStateChanged();
|
|
| 87 | + this._torConnectStageChanged();
|
|
| 88 | 88 | },
|
| 89 | 89 | |
| 90 | 90 | /**
|
| ... | ... | @@ -105,17 +105,17 @@ var gTorConnectUrlbarButton = { |
| 105 | 105 | * Begin the tor connection bootstrapping process.
|
| 106 | 106 | */
|
| 107 | 107 | connect() {
|
| 108 | - TorConnect.openTorConnect({ beginBootstrap: true });
|
|
| 108 | + TorConnect.openTorConnect({ beginBootstrapping: "soft" });
|
|
| 109 | 109 | },
|
| 110 | 110 | |
| 111 | 111 | /**
|
| 112 | - * Callback for when the TorConnect state changes.
|
|
| 112 | + * Callback for when the TorConnect stage changes.
|
|
| 113 | 113 | */
|
| 114 | - _torConnectStateChanged() {
|
|
| 115 | - if (TorConnect.state === TorConnectState.Disabled) {
|
|
| 114 | + _torConnectStageChanged() {
|
|
| 115 | + if (TorConnect.stageName === TorConnectStage.Disabled) {
|
|
| 116 | 116 | // NOTE: We do not uninit early when we reach the
|
| 117 | - // TorConnectState.Bootstrapped state because we can still leave the
|
|
| 118 | - // Bootstrapped state if the tor process exists early and needs a restart.
|
|
| 117 | + // TorConnectStage.Bootstrapped stage because we can still leave the
|
|
| 118 | + // Bootstrapped stage if the tor process exists early and needs a restart.
|
|
| 119 | 119 | this.uninit();
|
| 120 | 120 | return;
|
| 121 | 121 | }
|
| ... | ... | @@ -239,19 +239,19 @@ export let RemotePageAccessManager = { |
| 239 | 239 | },
|
| 240 | 240 | "about:torconnect": {
|
| 241 | 241 | RPMAddMessageListener: [
|
| 242 | - "torconnect:state-change",
|
|
| 243 | - "torconnect:user-action",
|
|
| 242 | + "torconnect:stage-change",
|
|
| 243 | + "torconnect:bootstrap-progress",
|
|
| 244 | + "torconnect:quickstart-change",
|
|
| 244 | 245 | ],
|
| 245 | 246 | RPMSendAsyncMessage: [
|
| 246 | 247 | "torconnect:open-tor-preferences",
|
| 247 | - "torconnect:begin-bootstrap",
|
|
| 248 | - "torconnect:begin-autobootstrap",
|
|
| 249 | - "torconnect:cancel-bootstrap",
|
|
| 248 | + "torconnect:begin-bootstrapping",
|
|
| 249 | + "torconnect:cancel-bootstrapping",
|
|
| 250 | 250 | "torconnect:set-quickstart",
|
| 251 | 251 | "torconnect:view-tor-logs",
|
| 252 | 252 | "torconnect:restart",
|
| 253 | - "torconnect:set-ui-state",
|
|
| 254 | - "torconnect:broadcast-user-action",
|
|
| 253 | + "torconnect:start-again",
|
|
| 254 | + "torconnect:choose-region",
|
|
| 255 | 255 | ],
|
| 256 | 256 | RPMSendQuery: [
|
| 257 | 257 | "torconnect:get-init-args",
|
| ... | ... | @@ -83,6 +83,7 @@ class TorAndroidIntegrationImpl { |
| 83 | 83 | |
| 84 | 84 | observe(subj, topic) {
|
| 85 | 85 | switch (topic) {
|
| 86 | + // TODO: Replace with StageChange.
|
|
| 86 | 87 | case lazy.TorConnectTopics.StateChange:
|
| 87 | 88 | lazy.EventDispatcher.instance.sendRequest({
|
| 88 | 89 | type: EmittedEvents.connectStateChanged,
|
| ... | ... | @@ -101,6 +102,7 @@ class TorAndroidIntegrationImpl { |
| 101 | 102 | type: EmittedEvents.bootstrapComplete,
|
| 102 | 103 | });
|
| 103 | 104 | break;
|
| 105 | + // TODO: Replace with StageChange stage.error.
|
|
| 104 | 106 | case lazy.TorConnectTopics.Error:
|
| 105 | 107 | lazy.EventDispatcher.instance.sendRequest({
|
| 106 | 108 | type: EmittedEvents.connectError,
|
| ... | ... | @@ -159,17 +161,23 @@ class TorAndroidIntegrationImpl { |
| 159 | 161 | await lazy.TorSettings.saveToPrefs();
|
| 160 | 162 | break;
|
| 161 | 163 | case ListenedEvents.bootstrapBegin:
|
| 162 | - lazy.TorConnect.beginBootstrap();
|
|
| 164 | + lazy.TorConnect.beginBootstrapping();
|
|
| 163 | 165 | break;
|
| 164 | 166 | case ListenedEvents.bootstrapBeginAuto:
|
| 165 | - lazy.TorConnect.beginAutoBootstrap(data.countryCode);
|
|
| 167 | + // TODO: The countryCode should be set to "automatic" by the caller
|
|
| 168 | + // rather than `null`, so we can just pass in `data.countryCode`
|
|
| 169 | + // directly.
|
|
| 170 | + lazy.TorConnect.beginBootstrapping(data.countryCode || "automatic");
|
|
| 166 | 171 | break;
|
| 167 | 172 | case ListenedEvents.bootstrapCancel:
|
| 168 | - lazy.TorConnect.cancelBootstrap();
|
|
| 173 | + lazy.TorConnect.cancelBootstrapping();
|
|
| 169 | 174 | break;
|
| 175 | + // TODO: Replace with TorConnect.stage.
|
|
| 170 | 176 | case ListenedEvents.bootstrapGetState:
|
| 171 | 177 | callback?.onSuccess(lazy.TorConnect.state);
|
| 172 | 178 | return;
|
| 179 | + // TODO: Expose TorConnect.startAgain() to allow users to begin
|
|
| 180 | + // from the start again.
|
|
| 173 | 181 | }
|
| 174 | 182 | callback?.onSuccess();
|
| 175 | 183 | } catch (e) {
|
| ... | ... | @@ -8,7 +8,6 @@ const lazy = {}; |
| 8 | 8 | |
| 9 | 9 | ChromeUtils.defineESModuleGetters(lazy, {
|
| 10 | 10 | BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
|
| 11 | - EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
|
|
| 12 | 11 | MoatRPC: "resource://gre/modules/Moat.sys.mjs",
|
| 13 | 12 | TorBootstrapRequest: "resource://gre/modules/TorBootstrapRequest.sys.mjs",
|
| 14 | 13 | TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
| ... | ... | @@ -79,240 +78,181 @@ ChromeUtils.defineLazyGetter(lazy, "logger", () => |
| 79 | 78 | })
|
| 80 | 79 | );
|
| 81 | 80 | |
| 82 | -/*
|
|
| 83 | - TorConnect State Transitions
|
|
| 84 | - |
|
| 85 | - ┌─────────┐ ┌────────┐
|
|
| 86 | - │ ▼ ▼ │
|
|
| 87 | - │ ┌──────────────────────────────────────────────────────────┐ │
|
|
| 88 | - ┌─┼────── │ Error │ ◀───┐ │
|
|
| 89 | - │ │ └──────────────────────────────────────────────────────────┘ │ │
|
|
| 90 | - │ │ ▲ │ │
|
|
| 91 | - │ │ │ │ │
|
|
| 92 | - │ │ │ │ │
|
|
| 93 | - │ │ ┌───────────────────────┐ ┌──────────┐ │ │
|
|
| 94 | - │ │ ┌──── │ Initial │ ────────────────────▶ │ Disabled │ │ │
|
|
| 95 | - │ │ │ └───────────────────────┘ └──────────┘ │ │
|
|
| 96 | - │ │ │ │ │ │
|
|
| 97 | - │ │ │ │ beginBootstrap() │ │
|
|
| 98 | - │ │ │ ▼ │ │
|
|
| 99 | - │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │
|
|
| 100 | - │ │ │ │ Bootstrapping │ ────┘ │
|
|
| 101 | - │ │ │ └──────────────────────────────────────────────────────────┘ │
|
|
| 102 | - │ │ │ │ ▲ │ │
|
|
| 103 | - │ │ │ │ cancelBootstrap() │ beginBootstrap() └────┐ │
|
|
| 104 | - │ │ │ ▼ │ │ │
|
|
| 105 | - │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │
|
|
| 106 | - │ │ └───▶ │ │ ─┼────┘
|
|
| 107 | - │ │ │ │ │
|
|
| 108 | - │ │ │ │ │
|
|
| 109 | - │ │ │ Configuring │ │
|
|
| 110 | - │ │ │ │ │
|
|
| 111 | - │ │ │ │ │
|
|
| 112 | - └─┼─────▶ │ │ │
|
|
| 113 | - │ └──────────────────────────────────────────────────────────┘ │
|
|
| 114 | - │ │ ▲ ▲ │
|
|
| 115 | - │ │ beginAutoBootstrap() │ cancelBootstrap() │ │
|
|
| 116 | - │ ▼ │ │ │
|
|
| 117 | - │ ┌───────────────────────┐ │ │ │
|
|
| 118 | - └────── │ AutoBootstrapping │ ─┘ │ │
|
|
| 119 | - └───────────────────────┘ │ │
|
|
| 120 | - │ │ │
|
|
| 121 | - │ ┌────────────────────────────────┘ │
|
|
| 122 | - ▼ │ │
|
|
| 123 | - ┌───────────────────────┐ │
|
|
| 124 | - │ Bootstrapped │ ◀───────────────────────────────────┘
|
|
| 125 | - └───────────────────────┘
|
|
| 126 | -*/
|
|
| 127 | - |
|
| 128 | 81 | /* Topics Notified by the TorConnect module */
|
| 129 | 82 | export const TorConnectTopics = Object.freeze({
|
| 83 | + StageChange: "torconnect:stage-change",
|
|
| 84 | + // TODO: Remove torconnect:state-change when pages have switched to stage.
|
|
| 130 | 85 | StateChange: "torconnect:state-change",
|
| 131 | 86 | BootstrapProgress: "torconnect:bootstrap-progress",
|
| 132 | 87 | BootstrapComplete: "torconnect:bootstrap-complete",
|
| 88 | + // TODO: Remove torconnect:error when pages have switched to stage.
|
|
| 133 | 89 | Error: "torconnect:error",
|
| 134 | 90 | });
|
| 135 | 91 | |
| 136 | -// The StateCallback is the base class to implement the various states.
|
|
| 137 | -// All states should extend it and implement a `run` function, which can
|
|
| 138 | -// optionally be async, and define an array of valid transitions.
|
|
| 139 | -// The parent class will handle everything else, including the transition to
|
|
| 140 | -// other states when the run function is complete etc...
|
|
| 141 | -// A system is also provided to allow this function to early-out. The runner
|
|
| 142 | -// should check the transitioning getter when appropriate and return.
|
|
| 143 | -// In addition to that, a state can implement a transitionRequested callback,
|
|
| 144 | -// which can be used in conjunction with a mechanism like Promise.race.
|
|
| 145 | -// This allows to handle, for example, users' requests to cancel a bootstrap
|
|
| 146 | -// attempt.
|
|
| 147 | -// A state can optionally define a cleanup function, that will be run in all
|
|
| 148 | -// cases before transitioning to the next state.
|
|
| 149 | -class StateCallback {
|
|
| 150 | - #state;
|
|
| 151 | - #promise;
|
|
| 152 | - #transitioning = false;
|
|
| 153 | - |
|
| 154 | - constructor(stateName) {
|
|
| 155 | - this.#state = stateName;
|
|
| 156 | - }
|
|
| 157 | - |
|
| 158 | - async begin(...args) {
|
|
| 159 | - lazy.logger.trace(`Entering ${this.#state} state`);
|
|
| 160 | - // Make sure we always have an actual promise.
|
|
| 161 | - try {
|
|
| 162 | - this.#promise = Promise.resolve(this.run(...args));
|
|
| 163 | - } catch (err) {
|
|
| 164 | - this.#promise = Promise.reject(err);
|
|
| 165 | - }
|
|
| 166 | - try {
|
|
| 167 | - // If the callback throws, transition to error as soon as possible.
|
|
| 168 | - await this.#promise;
|
|
| 169 | - lazy.logger.info(`${this.#state}'s run is done`);
|
|
| 170 | - } catch (err) {
|
|
| 171 | - if (this.transitioning) {
|
|
| 172 | - lazy.logger.error(
|
|
| 173 | - `A transition from ${
|
|
| 174 | - this.#state
|
|
| 175 | - } is already happening, silencing this exception.`,
|
|
| 176 | - err
|
|
| 177 | - );
|
|
| 178 | - return;
|
|
| 179 | - }
|
|
| 180 | - lazy.logger.error(
|
|
| 181 | - `${this.#state}'s run threw, transitioning to the Error state.`,
|
|
| 182 | - err
|
|
| 183 | - );
|
|
| 184 | - this.changeState(TorConnectState.Error, err);
|
|
| 185 | - }
|
|
| 186 | - }
|
|
| 187 | - |
|
| 188 | - async end(nextState) {
|
|
| 189 | - lazy.logger.trace(
|
|
| 190 | - `Ending state ${this.#state} (to transition to ${nextState})`
|
|
| 191 | - );
|
|
| 192 | - |
|
| 193 | - if (this.#transitioning) {
|
|
| 194 | - // Should we check turn this into an error?
|
|
| 195 | - // It will make dealing with the error state harder.
|
|
| 196 | - lazy.logger.warn("this.#transitioning is already true.");
|
|
| 197 | - }
|
|
| 198 | - |
|
| 199 | - // Signal we should bail out ASAP.
|
|
| 200 | - this.#transitioning = true;
|
|
| 201 | - if (this.transitionRequested) {
|
|
| 202 | - this.transitionRequested();
|
|
| 203 | - }
|
|
| 204 | - |
|
| 205 | - lazy.logger.debug(
|
|
| 206 | - `Waiting for the ${
|
|
| 207 | - this.#state
|
|
| 208 | - }'s callback to return before the transition.`
|
|
| 209 | - );
|
|
| 210 | - try {
|
|
| 211 | - await this.#promise;
|
|
| 212 | - } finally {
|
|
| 213 | - lazy.logger.debug(`Calling ${this.#state}'s cleanup, if implemented.`);
|
|
| 214 | - if (this.cleanup) {
|
|
| 215 | - try {
|
|
| 216 | - await this.cleanup(nextState);
|
|
| 217 | - lazy.logger.debug(`${this.#state}'s cleanup function done.`);
|
|
| 218 | - } catch (e) {
|
|
| 219 | - lazy.logger.warn(`${this.#state}'s cleanup function threw.`, e);
|
|
| 220 | - }
|
|
| 221 | - }
|
|
| 222 | - }
|
|
| 223 | - }
|
|
| 224 | - |
|
| 225 | - changeState(stateName, ...args) {
|
|
| 226 | - TorConnect._changeState(stateName, ...args);
|
|
| 227 | - }
|
|
| 228 | - |
|
| 229 | - get transitioning() {
|
|
| 230 | - return this.#transitioning;
|
|
| 231 | - }
|
|
| 232 | - |
|
| 233 | - get state() {
|
|
| 234 | - return this.#state;
|
|
| 235 | - }
|
|
| 236 | -}
|
|
| 237 | - |
|
| 238 | -// async method to sleep for a given amount of time
|
|
| 239 | -const debugSleep = async ms => {
|
|
| 240 | - return new Promise(resolve => {
|
|
| 241 | - setTimeout(resolve, ms);
|
|
| 242 | - });
|
|
| 243 | -};
|
|
| 244 | - |
|
| 245 | -class InitialState extends StateCallback {
|
|
| 246 | - allowedTransitions = Object.freeze([
|
|
| 247 | - TorConnectState.Disabled,
|
|
| 248 | - TorConnectState.Bootstrapping,
|
|
| 249 | - TorConnectState.Configuring,
|
|
| 250 | - TorConnectState.Error,
|
|
| 251 | - ]);
|
|
| 252 | - |
|
| 253 | - constructor() {
|
|
| 254 | - super(TorConnectState.Initial);
|
|
| 255 | - }
|
|
| 256 | - |
|
| 257 | - run() {
|
|
| 258 | - // TODO: Block this transition until we successfully build a TorProvider.
|
|
| 259 | - }
|
|
| 260 | -}
|
|
| 261 | - |
|
| 262 | -class ConfiguringState extends StateCallback {
|
|
| 263 | - allowedTransitions = Object.freeze([
|
|
| 264 | - TorConnectState.AutoBootstrapping,
|
|
| 265 | - TorConnectState.Bootstrapping,
|
|
| 266 | - TorConnectState.Error,
|
|
| 267 | - ]);
|
|
| 268 | - |
|
| 269 | - constructor() {
|
|
| 270 | - super(TorConnectState.Configuring);
|
|
| 271 | - }
|
|
| 272 | - |
|
| 273 | - run() {
|
|
| 274 | - TorConnect._bootstrapProgress = 0;
|
|
| 275 | - }
|
|
| 276 | -}
|
|
| 277 | - |
|
| 278 | -class BootstrappingState extends StateCallback {
|
|
| 92 | +/**
|
|
| 93 | + * @callback ProgressCallback
|
|
| 94 | + *
|
|
| 95 | + * @param {integer} progress - The progress percent.
|
|
| 96 | + */
|
|
| 97 | +/**
|
|
| 98 | + * @typedef {object} BootstrapOptions
|
|
| 99 | + *
|
|
| 100 | + * Options for a bootstrap attempt.
|
|
| 101 | + *
|
|
| 102 | + * @property {boolean} [options.simulateCensorship] - Whether to simulate a
|
|
| 103 | + * failing bootstrap.
|
|
| 104 | + * @property {integer} [options.simulateDelay] - The delay in microseconds to
|
|
| 105 | + * apply to simulated bootstraps.
|
|
| 106 | + * @property {object} [options.simulateMoatResponse] - Simulate a Moat response
|
|
| 107 | + * for circumvention settings. Should include a "settings" property, and
|
|
| 108 | + * optionally a "country" property. You may add a "simulateCensorship"
|
|
| 109 | + * property to some of the settings to make only their bootstrap attempts
|
|
| 110 | + * fail.
|
|
| 111 | + * @property {boolean} [options.testInternet] - Whether to also test the
|
|
| 112 | + * internet connection.
|
|
| 113 | + * @property {boolean} [options.simulateOffline] - Whether to simulate an
|
|
| 114 | + * offline test result. This will not cause the bootstrap to fail.
|
|
| 115 | + * @property {string} [options.regionCode] - The region code to use to fetch
|
|
| 116 | + * auto-bootstrap settings, or "automatic" to automatically choose the region.
|
|
| 117 | + */
|
|
| 118 | +/**
|
|
| 119 | + * @typedef {object} BootstrapResult
|
|
| 120 | + *
|
|
| 121 | + * The result of a bootstrap attempt.
|
|
| 122 | + *
|
|
| 123 | + * @property {string} [result] - The bootstrap result.
|
|
| 124 | + * @property {Error} [error] - An error from the attempt.
|
|
| 125 | + */
|
|
| 126 | +/**
|
|
| 127 | + * @callback ResolveBootstrap
|
|
| 128 | + *
|
|
| 129 | + * Resolve a bootstrap attempt.
|
|
| 130 | + *
|
|
| 131 | + * @param {BootstrapResult} - The result, or error.
|
|
| 132 | + */
|
|
| 133 | + |
|
| 134 | +/**
|
|
| 135 | + * Each instance can be used to attempt one bootstrapping.
|
|
| 136 | + */
|
|
| 137 | +class BootstrapAttempt {
|
|
| 138 | + /**
|
|
| 139 | + * The ongoing bootstrap request.
|
|
| 140 | + *
|
|
| 141 | + * @type {?TorBootstrapRequest}
|
|
| 142 | + */
|
|
| 279 | 143 | #bootstrap = null;
|
| 144 | + /**
|
|
| 145 | + * The error returned by the bootstrap request, if any.
|
|
| 146 | + *
|
|
| 147 | + * @type {?Error}
|
|
| 148 | + */
|
|
| 280 | 149 | #bootstrapError = null;
|
| 150 | + /**
|
|
| 151 | + * The ongoing internet test, if any.
|
|
| 152 | + *
|
|
| 153 | + * @type {?InternetTest}
|
|
| 154 | + */
|
|
| 281 | 155 | #internetTest = null;
|
| 156 | + /**
|
|
| 157 | + * The method to call to complete the `run` promise.
|
|
| 158 | + *
|
|
| 159 | + * @type {?ResolveBootstrap}
|
|
| 160 | + */
|
|
| 161 | + #resolveRun = null;
|
|
| 162 | + /**
|
|
| 163 | + * Whether the `run` promise has been, or is about to be, resolved.
|
|
| 164 | + *
|
|
| 165 | + * @type {boolean}
|
|
| 166 | + */
|
|
| 167 | + #resolved = false;
|
|
| 168 | + /**
|
|
| 169 | + * Whether a cancel request has been started.
|
|
| 170 | + *
|
|
| 171 | + * @type {boolean}
|
|
| 172 | + */
|
|
| 282 | 173 | #cancelled = false;
|
| 283 | 174 | |
| 284 | - allowedTransitions = Object.freeze([
|
|
| 285 | - TorConnectState.Configuring,
|
|
| 286 | - TorConnectState.Bootstrapped,
|
|
| 287 | - TorConnectState.Error,
|
|
| 288 | - ]);
|
|
| 175 | + /**
|
|
| 176 | + * Run a bootstrap attempt.
|
|
| 177 | + *
|
|
| 178 | + * @param {ProgressCallback} progressCallback - The callback to invoke with
|
|
| 179 | + * the bootstrap progress.
|
|
| 180 | + * @param {BootstrapOptions} options - Options to apply to the bootstrap.
|
|
| 181 | + *
|
|
| 182 | + * @return {Promise<string, Error>} - The result of the bootstrap.
|
|
| 183 | + */
|
|
| 184 | + run(progressCallback, options) {
|
|
| 185 | + const { promise, resolve, reject } = Promise.withResolvers();
|
|
| 186 | + this.#resolveRun = arg => {
|
|
| 187 | + if (this.#resolved) {
|
|
| 188 | + // Already been called once.
|
|
| 189 | + if (arg.error) {
|
|
| 190 | + lazy.logger.error("Delayed bootstrap error", arg.error);
|
|
| 191 | + }
|
|
| 192 | + return;
|
|
| 193 | + }
|
|
| 194 | + this.#resolved = true;
|
|
| 195 | + try {
|
|
| 196 | + // Should be ok to call this twice in the case where we "cancel" the
|
|
| 197 | + // bootstrap.
|
|
| 198 | + this.#internetTest?.cancel();
|
|
| 199 | + } catch (error) {
|
|
| 200 | + lazy.logger.error("Unexpected error in bootstrap cleanup", error);
|
|
| 201 | + }
|
|
| 202 | + if (arg.error) {
|
|
| 203 | + reject(arg.error);
|
|
| 204 | + } else {
|
|
| 205 | + resolve(arg.result);
|
|
| 206 | + }
|
|
| 207 | + };
|
|
| 208 | + try {
|
|
| 209 | + this.#runInternal(progressCallback, options);
|
|
| 210 | + } catch (error) {
|
|
| 211 | + this.#resolveRun({ error });
|
|
| 212 | + }
|
|
| 289 | 213 | |
| 290 | - constructor() {
|
|
| 291 | - super(TorConnectState.Bootstrapping);
|
|
| 214 | + return promise;
|
|
| 292 | 215 | }
|
| 293 | 216 | |
| 294 | - async run() {
|
|
| 295 | - if (await this.#simulateCensorship()) {
|
|
| 296 | - return;
|
|
| 217 | + /**
|
|
| 218 | + * Run the attempt.
|
|
| 219 | + *
|
|
| 220 | + * @param {ProgressCallback} progressCallback - The callback to invoke with
|
|
| 221 | + * the bootstrap progress.
|
|
| 222 | + * @param {BootstrapOptions} options - Options to apply to the bootstrap.
|
|
| 223 | + */
|
|
| 224 | + #runInternal(progressCallback, options) {
|
|
| 225 | + if (options.simulateCensorship) {
|
|
| 226 | + // Create a fake request.
|
|
| 227 | + this.#bootstrap = {
|
|
| 228 | + _timeout: 0,
|
|
| 229 | + bootstrap() {
|
|
| 230 | + this._timeout = setTimeout(() => {
|
|
| 231 | + const err = new Error("Censorship simulation");
|
|
| 232 | + err.phase = "conn";
|
|
| 233 | + err.reason = "noroute";
|
|
| 234 | + this.onbootstraperror(err);
|
|
| 235 | + }, options.simulateDelay || 0);
|
|
| 236 | + },
|
|
| 237 | + cancel() {
|
|
| 238 | + clearTimeout(this._timeout);
|
|
| 239 | + },
|
|
| 240 | + };
|
|
| 241 | + } else {
|
|
| 242 | + this.#bootstrap = new lazy.TorBootstrapRequest();
|
|
| 297 | 243 | }
|
| 298 | 244 | |
| 299 | - this.#bootstrap = new lazy.TorBootstrapRequest();
|
|
| 300 | - this.#bootstrap.onbootstrapstatus = (progress, status) => {
|
|
| 301 | - TorConnect._updateBootstrapProgress(progress, status);
|
|
| 245 | + this.#bootstrap.onbootstrapstatus = (progress, _status) => {
|
|
| 246 | + if (!this.#resolved) {
|
|
| 247 | + progressCallback(progress);
|
|
| 248 | + }
|
|
| 302 | 249 | };
|
| 303 | 250 | this.#bootstrap.onbootstrapcomplete = () => {
|
| 304 | - this.#internetTest.cancel();
|
|
| 305 | - this.changeState(TorConnectState.Bootstrapped);
|
|
| 251 | + this.#resolveRun({ result: "complete" });
|
|
| 306 | 252 | };
|
| 307 | 253 | this.#bootstrap.onbootstraperror = error => {
|
| 308 | - if (this.#cancelled) {
|
|
| 309 | - // We ignore this error since it occurred after cancelling (by the
|
|
| 310 | - // user). We assume the error is just a side effect of the cancelling.
|
|
| 311 | - // E.g. If the cancelling is triggered late in the process, we get
|
|
| 312 | - // "Building circuits: Establishing a Tor circuit failed".
|
|
| 313 | - // TODO: Maybe move this logic deeper in the process to know when to
|
|
| 314 | - // filter out such errors triggered by cancelling.
|
|
| 315 | - lazy.logger.warn("Post-cancel error.", error);
|
|
| 254 | + if (this.#bootstrapError) {
|
|
| 255 | + lazy.logger.warn("Another bootstrap error", error);
|
|
| 316 | 256 | return;
|
| 317 | 257 | }
|
| 318 | 258 | // We have to wait for the Internet test to finish before sending the
|
| ... | ... | @@ -320,30 +260,40 @@ class BootstrappingState extends StateCallback { |
| 320 | 260 | this.#bootstrapError = error;
|
| 321 | 261 | this.#maybeTransitionToError();
|
| 322 | 262 | };
|
| 323 | - |
|
| 324 | - this.#internetTest = new InternetTest();
|
|
| 325 | - this.#internetTest.onResult = status => {
|
|
| 326 | - TorConnect._internetStatus = status;
|
|
| 327 | - this.#maybeTransitionToError();
|
|
| 328 | - };
|
|
| 329 | - this.#internetTest.onError = () => {
|
|
| 330 | - this.#maybeTransitionToError();
|
|
| 331 | - };
|
|
| 263 | + if (options.testInternet) {
|
|
| 264 | + this.#internetTest = new InternetTest(options.simulateOffline);
|
|
| 265 | + this.#internetTest.onResult = () => {
|
|
| 266 | + this.#maybeTransitionToError();
|
|
| 267 | + };
|
|
| 268 | + this.#internetTest.onError = () => {
|
|
| 269 | + this.#maybeTransitionToError();
|
|
| 270 | + };
|
|
| 271 | + }
|
|
| 332 | 272 | |
| 333 | 273 | this.#bootstrap.bootstrap();
|
| 334 | 274 | }
|
| 335 | 275 | |
| 336 | - async cleanup(nextState) {
|
|
| 337 | - if (nextState === TorConnectState.Configuring) {
|
|
| 338 | - // stop bootstrap process if user cancelled
|
|
| 339 | - this.#cancelled = true;
|
|
| 340 | - this.#internetTest?.cancel();
|
|
| 341 | - await this.#bootstrap?.cancel();
|
|
| 276 | + /**
|
|
| 277 | + * Callback for when we get a new bootstrap error or a change in the internet
|
|
| 278 | + * status.
|
|
| 279 | + */
|
|
| 280 | + #maybeTransitionToError() {
|
|
| 281 | + if (this.#resolved || this.#cancelled) {
|
|
| 282 | + if (this.#bootstrapError) {
|
|
| 283 | + // We ignore this error since it occurred after cancelling (by the
|
|
| 284 | + // user), or we have already resolved. We assume the error is just a
|
|
| 285 | + // side effect of the cancelling.
|
|
| 286 | + // E.g. If the cancelling is triggered late in the process, we get
|
|
| 287 | + // "Building circuits: Establishing a Tor circuit failed".
|
|
| 288 | + // TODO: Maybe move this logic deeper in the process to know when to
|
|
| 289 | + // filter out such errors triggered by cancelling.
|
|
| 290 | + lazy.logger.warn("Post-complete error.", this.#bootstrapError);
|
|
| 291 | + }
|
|
| 292 | + return;
|
|
| 342 | 293 | }
|
| 343 | - }
|
|
| 344 | 294 | |
| 345 | - #maybeTransitionToError() {
|
|
| 346 | 295 | if (
|
| 296 | + this.#internetTest &&
|
|
| 347 | 297 | this.#internetTest.status === InternetStatus.Unknown &&
|
| 348 | 298 | this.#internetTest.error === null &&
|
| 349 | 299 | this.#internetTest.enabled
|
| ... | ... | @@ -355,356 +305,394 @@ class BootstrappingState extends StateCallback { |
| 355 | 305 | // us again.
|
| 356 | 306 | return;
|
| 357 | 307 | }
|
| 358 | - // Do not transition to the offline error until we are sure that also the
|
|
| 359 | - // bootstrap failed, in case Moat is down but the bootstrap can proceed
|
|
| 360 | - // anyway.
|
|
| 308 | + // Do not transition to "offline" until we are sure that also the bootstrap
|
|
| 309 | + // failed, in case Moat is down but the bootstrap can proceed anyway.
|
|
| 361 | 310 | if (!this.#bootstrapError) {
|
| 362 | 311 | return;
|
| 363 | 312 | }
|
| 364 | - if (this.#internetTest.status === InternetStatus.Offline) {
|
|
| 365 | - this.changeState(
|
|
| 366 | - TorConnectState.Error,
|
|
| 367 | - new TorConnectError(TorConnectError.Offline)
|
|
| 368 | - );
|
|
| 369 | - } else {
|
|
| 370 | - // Give priority to the bootstrap error, in case the Internet test fails
|
|
| 371 | - TorConnect._hasBootstrapEverFailed = true;
|
|
| 372 | - this.changeState(
|
|
| 373 | - TorConnectState.Error,
|
|
| 374 | - new TorConnectError(
|
|
| 375 | - TorConnectError.BootstrapError,
|
|
| 313 | + if (this.#internetTest?.status === InternetStatus.Offline) {
|
|
| 314 | + if (this.#bootstrapError) {
|
|
| 315 | + lazy.logger.info(
|
|
| 316 | + "Ignoring bootstrap error since offline.",
|
|
| 376 | 317 | this.#bootstrapError
|
| 377 | - )
|
|
| 378 | - );
|
|
| 379 | - }
|
|
| 380 | - }
|
|
| 381 | - |
|
| 382 | - async #simulateCensorship() {
|
|
| 383 | - // debug hook to simulate censorship preventing bootstrapping
|
|
| 384 | - const censorshipLevel = Services.prefs.getIntPref(
|
|
| 385 | - TorConnectPrefs.censorship_level,
|
|
| 386 | - 0
|
|
| 387 | - );
|
|
| 388 | - if (censorshipLevel <= 0) {
|
|
| 389 | - return false;
|
|
| 390 | - }
|
|
| 391 | - |
|
| 392 | - await debugSleep(1500);
|
|
| 393 | - if (this.transitioning) {
|
|
| 394 | - // Already left this state.
|
|
| 395 | - return true;
|
|
| 318 | + );
|
|
| 319 | + }
|
|
| 320 | + this.#resolveRun({ result: "offline" });
|
|
| 321 | + return;
|
|
| 396 | 322 | }
|
| 397 | - TorConnect._hasBootstrapEverFailed = true;
|
|
| 398 | - if (censorshipLevel === 2) {
|
|
| 399 | - const codes = Object.keys(TorConnect._countryNames);
|
|
| 400 | - TorConnect._detectedLocation =
|
|
| 401 | - codes[Math.floor(Math.random() * codes.length)];
|
|
| 402 | - }
|
|
| 403 | - const err = new Error("Censorship simulation");
|
|
| 404 | - err.phase = "conn";
|
|
| 405 | - err.reason = "noroute";
|
|
| 406 | - this.changeState(
|
|
| 407 | - TorConnectState.Error,
|
|
| 408 | - new TorConnectError(TorConnectError.BootstrapError, err)
|
|
| 409 | - );
|
|
| 410 | - return true;
|
|
| 411 | - }
|
|
| 412 | -}
|
|
| 413 | - |
|
| 414 | -class AutoBootstrappingState extends StateCallback {
|
|
| 415 | - #moat;
|
|
| 416 | - #settings;
|
|
| 417 | - #changedSettings = false;
|
|
| 418 | - #transitionPromise;
|
|
| 419 | - #transitionResolve;
|
|
| 420 | - |
|
| 421 | - allowedTransitions = Object.freeze([
|
|
| 422 | - TorConnectState.Configuring,
|
|
| 423 | - TorConnectState.Bootstrapped,
|
|
| 424 | - TorConnectState.Error,
|
|
| 425 | - ]);
|
|
| 426 | - |
|
| 427 | - constructor() {
|
|
| 428 | - super(TorConnectState.AutoBootstrapping);
|
|
| 429 | - this.#transitionPromise = new Promise(resolve => {
|
|
| 430 | - this.#transitionResolve = resolve;
|
|
| 323 | + this.#resolveRun({
|
|
| 324 | + error: new TorConnectError(
|
|
| 325 | + TorConnectError.BootstrapError,
|
|
| 326 | + this.#bootstrapError
|
|
| 327 | + ),
|
|
| 431 | 328 | });
|
| 432 | 329 | }
|
| 433 | 330 | |
| 434 | - async run(countryCode) {
|
|
| 435 | - if (await this.#simulateCensorship(countryCode)) {
|
|
| 436 | - return;
|
|
| 437 | - }
|
|
| 438 | - await this.#initMoat();
|
|
| 439 | - if (this.transitioning) {
|
|
| 331 | + /**
|
|
| 332 | + * Cancel the bootstrap attempt.
|
|
| 333 | + */
|
|
| 334 | + async cancel() {
|
|
| 335 | + if (this.#cancelled) {
|
|
| 336 | + lazy.logger.warn(
|
|
| 337 | + "Cancelled bootstrap after it has already been cancelled"
|
|
| 338 | + );
|
|
| 440 | 339 | return;
|
| 441 | 340 | }
|
| 442 | - await this.#fetchSettings(countryCode);
|
|
| 443 | - if (this.transitioning) {
|
|
| 341 | + this.#cancelled = true;
|
|
| 342 | + if (this.#resolved) {
|
|
| 343 | + lazy.logger.warn("Cancelled bootstrap after it has already resolved");
|
|
| 444 | 344 | return;
|
| 445 | 345 | }
|
| 446 | - await this.#trySettings();
|
|
| 346 | + // Wait until after bootstrap.cancel returns before we resolve with
|
|
| 347 | + // cancelled. In particular, there is a small chance that the bootstrap
|
|
| 348 | + // completes, in which case we want to be able to resolve with a success
|
|
| 349 | + // instead.
|
|
| 350 | + this.#internetTest?.cancel();
|
|
| 351 | + await this.#bootstrap?.cancel();
|
|
| 352 | + this.#resolveRun({ result: "cancelled" });
|
|
| 447 | 353 | }
|
| 354 | +}
|
|
| 448 | 355 | |
| 356 | +/**
|
|
| 357 | + * Each instance can be used to attempt one auto-bootstrapping sequence.
|
|
| 358 | + */
|
|
| 359 | +class AutoBootstrapAttempt {
|
|
| 449 | 360 | /**
|
| 450 | - * Simulate a censorship event, if needed.
|
|
| 361 | + * The current bootstrap attempt, if any.
|
|
| 451 | 362 | *
|
| 452 | - * @param {string} countryCode The country code passed to the state
|
|
| 453 | - * @returns {Promise<boolean>} true if we are simulating the censorship and
|
|
| 454 | - * the bootstrap should stop immediately, or false if the bootstrap should
|
|
| 455 | - * continue normally.
|
|
| 363 | + * @type {?BootstrapAttempt}
|
|
| 456 | 364 | */
|
| 457 | - async #simulateCensorship(countryCode) {
|
|
| 458 | - const censorshipLevel = Services.prefs.getIntPref(
|
|
| 459 | - TorConnectPrefs.censorship_level,
|
|
| 460 | - 0
|
|
| 461 | - );
|
|
| 462 | - if (censorshipLevel <= 0) {
|
|
| 463 | - return false;
|
|
| 464 | - }
|
|
| 365 | + #bootstrapAttempt = null;
|
|
| 366 | + /**
|
|
| 367 | + * The method to call to complete the `run` promise.
|
|
| 368 | + *
|
|
| 369 | + * @type {?ResolveBootstrap}
|
|
| 370 | + */
|
|
| 371 | + #resolveRun = null;
|
|
| 372 | + /**
|
|
| 373 | + * Whether the `run` promise has been, or is about to be, resolved.
|
|
| 374 | + *
|
|
| 375 | + * @type {boolean}
|
|
| 376 | + */
|
|
| 377 | + #resolved = false;
|
|
| 378 | + /**
|
|
| 379 | + * Whether a cancel request has been started.
|
|
| 380 | + *
|
|
| 381 | + * @type {boolean}
|
|
| 382 | + */
|
|
| 383 | + #cancelled = false;
|
|
| 384 | + /**
|
|
| 385 | + * The method to call when the cancelled value is set to true.
|
|
| 386 | + *
|
|
| 387 | + * @type {?Function}
|
|
| 388 | + */
|
|
| 389 | + #resolveCancelled = null;
|
|
| 390 | + /**
|
|
| 391 | + * A promise that resolves when the cancelled value is set to true. We can use
|
|
| 392 | + * this with Promise.race to end early when the user cancels.
|
|
| 393 | + *
|
|
| 394 | + * @type {?Promise}
|
|
| 395 | + */
|
|
| 396 | + #cancelledPromise = null;
|
|
| 397 | + /**
|
|
| 398 | + * The found settings from Moat.
|
|
| 399 | + *
|
|
| 400 | + * @type {?object[]}
|
|
| 401 | + */
|
|
| 402 | + #settings = null;
|
|
| 403 | + /**
|
|
| 404 | + * The last settings that have been applied to the TorProvider, if any.
|
|
| 405 | + *
|
|
| 406 | + * @type {?object}
|
|
| 407 | + */
|
|
| 408 | + #changedSetting = null;
|
|
| 409 | + /**
|
|
| 410 | + * The detected region code returned by Moat, if any.
|
|
| 411 | + *
|
|
| 412 | + * @type {?string}
|
|
| 413 | + */
|
|
| 414 | + detectedRegion = null;
|
|
| 465 | 415 | |
| 466 | - // Very severe censorship: always fail even after manually selecting
|
|
| 467 | - // location specific settings.
|
|
| 468 | - if (censorshipLevel === 3) {
|
|
| 469 | - await debugSleep(2500);
|
|
| 470 | - if (!this.transitioning) {
|
|
| 471 | - this.changeState(
|
|
| 472 | - TorConnectState.Error,
|
|
| 473 | - new TorConnectError(TorConnectError.AllSettingsFailed)
|
|
| 474 | - );
|
|
| 416 | + /**
|
|
| 417 | + * Run an auto-bootstrap attempt.
|
|
| 418 | + *
|
|
| 419 | + * @param {ProgressCallback} progressCallback - The callback to invoke with
|
|
| 420 | + * the bootstrap progress.
|
|
| 421 | + * @param {BootstrapOptions} options - Options to apply to the bootstrap.
|
|
| 422 | + *
|
|
| 423 | + * @return {Promise<string, Error>} - The result of the bootstrap.
|
|
| 424 | + */
|
|
| 425 | + run(progressCallback, options) {
|
|
| 426 | + const { promise, resolve, reject } = Promise.withResolvers();
|
|
| 427 | + |
|
| 428 | + this.#resolveRun = async arg => {
|
|
| 429 | + if (this.#resolved) {
|
|
| 430 | + // Already been called once.
|
|
| 431 | + if (arg.error) {
|
|
| 432 | + lazy.logger.error("Delayed auto-bootstrap error", arg.error);
|
|
| 433 | + }
|
|
| 434 | + return;
|
|
| 475 | 435 | }
|
| 476 | - return true;
|
|
| 477 | - }
|
|
| 478 | - |
|
| 479 | - // Severe censorship: only fail after auto selecting, but succeed after
|
|
| 480 | - // manually selecting a country.
|
|
| 481 | - if (censorshipLevel === 2 && !countryCode) {
|
|
| 482 | - await debugSleep(2500);
|
|
| 483 | - if (!this.transitioning) {
|
|
| 484 | - this.changeState(
|
|
| 485 | - TorConnectState.Error,
|
|
| 486 | - new TorConnectError(TorConnectError.CannotDetermineCountry)
|
|
| 487 | - );
|
|
| 436 | + this.#resolved = true;
|
|
| 437 | + try {
|
|
| 438 | + // Run cleanup before we resolve the promise to ensure two instances
|
|
| 439 | + // of AutoBootstrapAttempt are not trying to change the settings at
|
|
| 440 | + // the same time.
|
|
| 441 | + if (this.#changedSetting) {
|
|
| 442 | + if (arg.result === "complete") {
|
|
| 443 | + // Persist the current settings to preferences.
|
|
| 444 | + lazy.TorSettings.setSettings(this.#changedSetting);
|
|
| 445 | + lazy.TorSettings.saveToPrefs();
|
|
| 446 | + } // else, applySettings will restore the current settings.
|
|
| 447 | + await lazy.TorSettings.applySettings();
|
|
| 448 | + }
|
|
| 449 | + } catch (error) {
|
|
| 450 | + lazy.logger.error("Unexpected error in auto-bootstrap cleanup", error);
|
|
| 488 | 451 | }
|
| 489 | - return true;
|
|
| 490 | - }
|
|
| 452 | + if (arg.error) {
|
|
| 453 | + reject(arg.error);
|
|
| 454 | + } else {
|
|
| 455 | + resolve(arg.result);
|
|
| 456 | + }
|
|
| 457 | + };
|
|
| 491 | 458 | |
| 492 | - return false;
|
|
| 493 | - }
|
|
| 459 | + ({ promise: this.#cancelledPromise, resolve: this.#resolveCancelled } =
|
|
| 460 | + Promise.withResolvers());
|
|
| 494 | 461 | |
| 495 | - /**
|
|
| 496 | - * Initialize the MoatRPC to communicate with the backend.
|
|
| 497 | - */
|
|
| 498 | - async #initMoat() {
|
|
| 499 | - this.#moat = new lazy.MoatRPC();
|
|
| 500 | - // We need to wait Moat's initialization even when we are requested to
|
|
| 501 | - // transition to another state to be sure its uninit will have its intended
|
|
| 502 | - // effect. So, do not use Promise.race here.
|
|
| 503 | - await this.#moat.init();
|
|
| 462 | + this.#runInternal(progressCallback, options).catch(error => {
|
|
| 463 | + this.#resolveRun({ error });
|
|
| 464 | + });
|
|
| 465 | + |
|
| 466 | + return promise;
|
|
| 504 | 467 | }
|
| 505 | 468 | |
| 506 | 469 | /**
|
| 507 | - * Lookup user's potential censorship circumvention settings from Moat
|
|
| 508 | - * service.
|
|
| 470 | + * Run the attempt.
|
|
| 471 | + *
|
|
| 472 | + * Note, this is an async method, but should *not* be awaited by the `run`
|
|
| 473 | + * method.
|
|
| 474 | + *
|
|
| 475 | + * @param {ProgressCallback} progressCallback - The callback to invoke with
|
|
| 476 | + * the bootstrap progress.
|
|
| 477 | + * @param {BootstrapOptions} options - Options to apply to the bootstrap.
|
|
| 509 | 478 | */
|
| 510 | - async #fetchSettings(countryCode) {
|
|
| 511 | - // For now, throw any errors we receive from the backend, except when it was
|
|
| 512 | - // unable to detect user's country/region.
|
|
| 513 | - // If we use specialized error objects, we could pass the original errors to
|
|
| 514 | - // them.
|
|
| 515 | - const maybeSettings = await Promise.race([
|
|
| 516 | - this.#moat.circumvention_settings(
|
|
| 517 | - [...lazy.TorSettings.builtinBridgeTypes, "vanilla"],
|
|
| 518 | - countryCode
|
|
| 519 | - ),
|
|
| 520 | - // This might set maybeSettings to undefined.
|
|
| 521 | - this.#transitionPromise,
|
|
| 522 | - ]);
|
|
| 523 | - if (maybeSettings?.country) {
|
|
| 524 | - TorConnect._detectedLocation = maybeSettings.country;
|
|
| 525 | - }
|
|
| 526 | - |
|
| 527 | - if (maybeSettings?.settings && maybeSettings.settings.length) {
|
|
| 528 | - this.#settings = maybeSettings.settings;
|
|
| 529 | - } else if (!this.transitioning) {
|
|
| 530 | - // Keep consistency with the other call.
|
|
| 531 | - this.#settings = await Promise.race([
|
|
| 532 | - this.#moat.circumvention_defaults([
|
|
| 533 | - ...lazy.TorSettings.builtinBridgeTypes,
|
|
| 534 | - "vanilla",
|
|
| 535 | - ]),
|
|
| 536 | - // This might set this.#settings to undefined.
|
|
| 537 | - this.#transitionPromise,
|
|
| 538 | - ]);
|
|
| 479 | + async #runInternal(progressCallback, options) {
|
|
| 480 | + await this.#fetchSettings(options);
|
|
| 481 | + if (this.#cancelled || this.#resolved) {
|
|
| 482 | + return;
|
|
| 539 | 483 | }
|
| 540 | 484 | |
| 541 | - if (!this.#settings?.length && !this.transitioning) {
|
|
| 542 | - if (!TorConnect._detectedLocation) {
|
|
| 543 | - // unable to determine country
|
|
| 544 | - throw new TorConnectError(TorConnectError.CannotDetermineCountry);
|
|
| 545 | - } else {
|
|
| 546 | - // no settings available for country
|
|
| 547 | - throw new TorConnectError(TorConnectError.NoSettingsForCountry);
|
|
| 548 | - }
|
|
| 485 | + if (!this.#settings?.length) {
|
|
| 486 | + this.#resolveRun({
|
|
| 487 | + error: new TorConnectError(
|
|
| 488 | + options.regionCode === "automatic" && !this.detectedRegion
|
|
| 489 | + ? TorConnectError.CannotDetermineCountry
|
|
| 490 | + : TorConnectError.NoSettingsForCountry
|
|
| 491 | + ),
|
|
| 492 | + });
|
|
| 549 | 493 | }
|
| 550 | - }
|
|
| 551 | 494 | |
| 552 | - /**
|
|
| 553 | - * Try to apply the settings we fetched.
|
|
| 554 | - */
|
|
| 555 | - async #trySettings() {
|
|
| 556 | - // Otherwise, apply each of our settings and try to bootstrap with each.
|
|
| 495 | + // Apply each of our settings and try to bootstrap with each.
|
|
| 557 | 496 | for (const [index, currentSetting] of this.#settings.entries()) {
|
| 558 | - if (this.transitioning) {
|
|
| 559 | - break;
|
|
| 560 | - }
|
|
| 561 | - |
|
| 562 | 497 | lazy.logger.info(
|
| 563 | 498 | `Attempting Bootstrap with configuration ${index + 1}/${
|
| 564 | 499 | this.#settings.length
|
| 565 | 500 | }`
|
| 566 | 501 | );
|
| 567 | 502 | |
| 568 | - // Send the new settings directly to the provider. We will save them only
|
|
| 569 | - // if the bootstrap succeeds.
|
|
| 570 | - // FIXME: We should somehow signal TorSettings users that we have set
|
|
| 571 | - // custom settings, and they should not apply theirs until we are done
|
|
| 572 | - // with trying ours.
|
|
| 573 | - // Otherwise, the new settings provided by the user while we were
|
|
| 574 | - // bootstrapping could be the ones that cause the bootstrap to succeed,
|
|
| 575 | - // but we overwrite them (unless we backup the original settings, and then
|
|
| 576 | - // save our new settings only if they have not changed).
|
|
| 577 | - // Another idea (maybe easier to implement) is to disable the settings
|
|
| 578 | - // UI while *any* bootstrap is going on.
|
|
| 579 | - // This is also documented in tor-browser#41921.
|
|
| 580 | - const provider = await lazy.TorProviderBuilder.build();
|
|
| 581 | - this.#changedSettings = true;
|
|
| 582 | - // We need to merge with old settings, in case the user is using a proxy
|
|
| 583 | - // or is behind a firewall.
|
|
| 584 | - await provider.writeSettings({
|
|
| 585 | - ...lazy.TorSettings.getSettings(),
|
|
| 586 | - ...currentSetting,
|
|
| 587 | - });
|
|
| 588 | - |
|
| 589 | - // Build out our bootstrap request.
|
|
| 590 | - const bootstrap = new lazy.TorBootstrapRequest();
|
|
| 591 | - bootstrap.onbootstrapstatus = (progress, status) => {
|
|
| 592 | - TorConnect._updateBootstrapProgress(progress, status);
|
|
| 593 | - };
|
|
| 594 | - bootstrap.onbootstraperror = error => {
|
|
| 595 | - lazy.logger.error("Auto-Bootstrap error", error);
|
|
| 596 | - };
|
|
| 503 | + await this.#trySetting(currentSetting, progressCallback, options);
|
|
| 597 | 504 | |
| 598 | - // Begin the bootstrap.
|
|
| 599 | - const success = await Promise.race([
|
|
| 600 | - bootstrap.bootstrap(),
|
|
| 601 | - this.#transitionPromise,
|
|
| 602 | - ]);
|
|
| 603 | - // Either the bootstrap request has finished, or a transition (caused by
|
|
| 604 | - // an error or by user's cancelation) started.
|
|
| 605 | - // However, we cannot be already transitioning in case of success, so if
|
|
| 606 | - // we are we should cancel the current bootstrap.
|
|
| 607 | - // With the current TorProvider, this will set DisableNetwork=1 again,
|
|
| 608 | - // which is what the user wanted if they canceled.
|
|
| 609 | - if (this.transitioning) {
|
|
| 610 | - if (success) {
|
|
| 611 | - lazy.logger.warn(
|
|
| 612 | - "We were already transitioning after a success, we were not expecting this."
|
|
| 613 | - );
|
|
| 614 | - }
|
|
| 615 | - bootstrap.cancel();
|
|
| 616 | - return;
|
|
| 617 | - }
|
|
| 618 | - if (success) {
|
|
| 619 | - // Persist the current settings to preferences.
|
|
| 620 | - lazy.TorSettings.setSettings(currentSetting);
|
|
| 621 | - lazy.TorSettings.saveToPrefs();
|
|
| 622 | - // Do not await `applySettings`. Otherwise this opens up a window of
|
|
| 623 | - // time where the user can still "Cancel" the bootstrap.
|
|
| 624 | - // We are calling `applySettings` just to be on the safe side, but the
|
|
| 625 | - // settings we are passing now should be exactly the same we already
|
|
| 626 | - // passed earlier.
|
|
| 627 | - lazy.TorSettings.applySettings().catch(e =>
|
|
| 628 | - lazy.logger.error("TorSettings.applySettings threw unexpectedly.", e)
|
|
| 629 | - );
|
|
| 630 | - this.changeState(TorConnectState.Bootstrapped);
|
|
| 505 | + if (this.#cancelled || this.#resolved) {
|
|
| 631 | 506 | return;
|
| 632 | 507 | }
|
| 633 | 508 | }
|
| 634 | 509 | |
| 635 | - // Only explicitly change state here if something else has not transitioned
|
|
| 636 | - // us.
|
|
| 637 | - if (!this.transitioning) {
|
|
| 638 | - throw new TorConnectError(TorConnectError.AllSettingsFailed);
|
|
| 639 | - }
|
|
| 640 | - }
|
|
| 641 | - |
|
| 642 | - transitionRequested() {
|
|
| 643 | - this.#transitionResolve();
|
|
| 510 | + this.#resolveRun({
|
|
| 511 | + error: new TorConnectError(TorConnectError.AllSettingsFailed),
|
|
| 512 | + });
|
|
| 644 | 513 | }
|
| 645 | 514 | |
| 646 | - async cleanup(nextState) {
|
|
| 647 | - // No need to await.
|
|
| 648 | - this.#moat?.uninit();
|
|
| 649 | - this.#moat = null;
|
|
| 515 | + /**
|
|
| 516 | + * Lookup user's potential censorship circumvention settings from Moat
|
|
| 517 | + * service.
|
|
| 518 | + *
|
|
| 519 | + * @param {BootstrapOptions} options - Options to apply to the bootstrap.
|
|
| 520 | + */
|
|
| 521 | + async #fetchSettings(options) {
|
|
| 522 | + if (options.simulateMoatResponse) {
|
|
| 523 | + await Promise.race([
|
|
| 524 | + new Promise(res => setTimeout(res, options.simulateDelay || 0)),
|
|
| 525 | + this.#cancelledPromise,
|
|
| 526 | + ]);
|
|
| 650 | 527 | |
| 651 | - if (this.#changedSettings && nextState !== TorConnectState.Bootstrapped) {
|
|
| 652 | - try {
|
|
| 653 | - await lazy.TorSettings.applySettings();
|
|
| 654 | - } catch (e) {
|
|
| 655 | - // We cannot do much if the original settings were bad or
|
|
| 656 | - // if the connection closed, so just report it in the
|
|
| 657 | - // console.
|
|
| 658 | - lazy.logger.warn("Failed to restore original settings.", e);
|
|
| 528 | + if (this.#cancelled || this.#resolved) {
|
|
| 529 | + return;
|
|
| 659 | 530 | }
|
| 531 | + |
|
| 532 | + this.detectedRegion = options.simulateMoatResponse.country || null;
|
|
| 533 | + this.#settings = options.simulateMoatResponse.settings ?? null;
|
|
| 534 | + |
|
| 535 | + return;
|
|
| 660 | 536 | }
|
| 661 | - }
|
|
| 662 | -}
|
|
| 663 | 537 | |
| 664 | -class BootstrappedState extends StateCallback {
|
|
| 665 | - // We may need to leave the bootstrapped state if the tor daemon
|
|
| 666 | - // exits (if it is restarted, we will have to bootstrap again).
|
|
| 667 | - allowedTransitions = Object.freeze([TorConnectState.Configuring]);
|
|
| 538 | + const moat = new lazy.MoatRPC();
|
|
| 539 | + try {
|
|
| 540 | + // We need to wait Moat's initialization even when we are requested to
|
|
| 541 | + // transition to another state to be sure its uninit will have its
|
|
| 542 | + // intended effect. So, do not use Promise.race here.
|
|
| 543 | + await moat.init();
|
|
| 668 | 544 | |
| 669 | - constructor() {
|
|
| 670 | - super(TorConnectState.Bootstrapped);
|
|
| 671 | - }
|
|
| 545 | + if (this.#cancelled || this.#resolved) {
|
|
| 546 | + return;
|
|
| 547 | + }
|
|
| 672 | 548 | |
| 673 | - run() {
|
|
| 674 | - // Notify observers of bootstrap completion.
|
|
| 675 | - Services.obs.notifyObservers(null, TorConnectTopics.BootstrapComplete);
|
|
| 549 | + // For now, throw any errors we receive from the backend, except when it
|
|
| 550 | + // was unable to detect user's country/region.
|
|
| 551 | + // If we use specialized error objects, we could pass the original errors
|
|
| 552 | + // to them.
|
|
| 553 | + const maybeSettings = await Promise.race([
|
|
| 554 | + moat.circumvention_settings(
|
|
| 555 | + [...lazy.TorSettings.builtinBridgeTypes, "vanilla"],
|
|
| 556 | + options.regionCode === "automatic" ? null : options.regionCode
|
|
| 557 | + ),
|
|
| 558 | + // This might set maybeSettings to undefined.
|
|
| 559 | + this.#cancelledPromise,
|
|
| 560 | + ]);
|
|
| 561 | + if (this.#cancelled || this.#resolved) {
|
|
| 562 | + return;
|
|
| 563 | + }
|
|
| 564 | + |
|
| 565 | + this.detectedRegion = maybeSettings?.country || null;
|
|
| 566 | + |
|
| 567 | + if (maybeSettings?.settings?.length) {
|
|
| 568 | + this.#settings = maybeSettings.settings;
|
|
| 569 | + } else {
|
|
| 570 | + // Keep consistency with the other call.
|
|
| 571 | + this.#settings = await Promise.race([
|
|
| 572 | + moat.circumvention_defaults([
|
|
| 573 | + ...lazy.TorSettings.builtinBridgeTypes,
|
|
| 574 | + "vanilla",
|
|
| 575 | + ]),
|
|
| 576 | + // This might set this.#settings to undefined.
|
|
| 577 | + this.#cancelledPromise,
|
|
| 578 | + ]);
|
|
| 579 | + }
|
|
| 580 | + } finally {
|
|
| 581 | + // Do not await the uninit.
|
|
| 582 | + moat.uninit();
|
|
| 583 | + }
|
|
| 676 | 584 | }
|
| 677 | -}
|
|
| 678 | 585 | |
| 679 | -class ErrorState extends StateCallback {
|
|
| 680 | - allowedTransitions = Object.freeze([TorConnectState.Configuring]);
|
|
| 586 | + /**
|
|
| 587 | + * Try to apply the settings we fetched.
|
|
| 588 | + *
|
|
| 589 | + * @param {object} setting - The setting to try.
|
|
| 590 | + * @param {ProgressCallback} progressCallback - The callback to invoke with
|
|
| 591 | + * the bootstrap progress.
|
|
| 592 | + * @param {BootstrapOptions} options - Options to apply to the bootstrap.
|
|
| 593 | + */
|
|
| 594 | + async #trySetting(setting, progressCallback, options) {
|
|
| 595 | + if (this.#cancelled || this.#resolved) {
|
|
| 596 | + return;
|
|
| 597 | + }
|
|
| 681 | 598 | |
| 682 | - static #hasEverHappened = false;
|
|
| 599 | + if (options.simulateMoatResponse && setting.simulateCensorship) {
|
|
| 600 | + // Move the simulateCensorship option to the options for the next
|
|
| 601 | + // BootstrapAttempt.
|
|
| 602 | + setting = structuredClone(setting);
|
|
| 603 | + delete setting.simulateCensorship;
|
|
| 604 | + options = { ...options, simulateCensorship: true };
|
|
| 605 | + }
|
|
| 683 | 606 | |
| 684 | - constructor() {
|
|
| 685 | - super(TorConnectState.Error);
|
|
| 686 | - ErrorState.#hasEverHappened = true;
|
|
| 687 | - }
|
|
| 607 | + // Send the new settings directly to the provider. We will save them only
|
|
| 608 | + // if the bootstrap succeeds.
|
|
| 609 | + // FIXME: We should somehow signal TorSettings users that we have set
|
|
| 610 | + // custom settings, and they should not apply theirs until we are done
|
|
| 611 | + // with trying ours.
|
|
| 612 | + // Otherwise, the new settings provided by the user while we were
|
|
| 613 | + // bootstrapping could be the ones that cause the bootstrap to succeed,
|
|
| 614 | + // but we overwrite them (unless we backup the original settings, and then
|
|
| 615 | + // save our new settings only if they have not changed).
|
|
| 616 | + // Another idea (maybe easier to implement) is to disable the settings
|
|
| 617 | + // UI while *any* bootstrap is going on.
|
|
| 618 | + // This is also documented in tor-browser#41921.
|
|
| 619 | + const provider = await lazy.TorProviderBuilder.build();
|
|
| 620 | + this.#changedSetting = setting;
|
|
| 621 | + // We need to merge with old settings, in case the user is using a proxy
|
|
| 622 | + // or is behind a firewall.
|
|
| 623 | + await provider.writeSettings({
|
|
| 624 | + ...lazy.TorSettings.getSettings(),
|
|
| 625 | + ...setting,
|
|
| 626 | + });
|
|
| 688 | 627 | |
| 689 | - run(_error) {
|
|
| 690 | - this.changeState(TorConnectState.Configuring);
|
|
| 691 | - }
|
|
| 628 | + if (this.#cancelled || this.#resolved) {
|
|
| 629 | + return;
|
|
| 630 | + }
|
|
| 692 | 631 | |
| 693 | - static get hasEverHappened() {
|
|
| 694 | - return ErrorState.#hasEverHappened;
|
|
| 695 | - }
|
|
| 696 | -}
|
|
| 632 | + let result;
|
|
| 633 | + try {
|
|
| 634 | + this.#bootstrapAttempt = new BootstrapAttempt();
|
|
| 635 | + // At this stage, cancelling AutoBootstrap will also cancel this
|
|
| 636 | + // bootstrapAttempt.
|
|
| 637 | + result = await this.#bootstrapAttempt.run(progressCallback, options);
|
|
| 638 | + } catch (error) {
|
|
| 639 | + // Only re-try with the next settings *if* we have a BootstrapError.
|
|
| 640 | + // Other errors will end this auto-bootstrap attempt entirely.
|
|
| 641 | + if (
|
|
| 642 | + error instanceof TorConnectError &&
|
|
| 643 | + error.code === TorConnectError.BootstrapError
|
|
| 644 | + ) {
|
|
| 645 | + lazy.logger.info("TorConnect setting failed", setting, error);
|
|
| 646 | + // Try with the next settings.
|
|
| 647 | + // NOTE: We do not restore the user settings in between these runs.
|
|
| 648 | + // Instead we wait for #resolveRun callback to do so.
|
|
| 649 | + // This means there is a window of time where the setting is applied, but
|
|
| 650 | + // no bootstrap is running.
|
|
| 651 | + return;
|
|
| 652 | + }
|
|
| 653 | + // Pass error up.
|
|
| 654 | + throw error;
|
|
| 655 | + } finally {
|
|
| 656 | + this.#bootstrapAttempt = null;
|
|
| 657 | + }
|
|
| 697 | 658 | |
| 698 | -class DisabledState extends StateCallback {
|
|
| 699 | - // Trap state: no way to leave the Disabled state.
|
|
| 700 | - allowedTransitions = Object.freeze([]);
|
|
| 659 | + if (this.#cancelled || this.#resolved) {
|
|
| 660 | + return;
|
|
| 661 | + }
|
|
| 701 | 662 | |
| 702 | - constructor() {
|
|
| 703 | - super(TorConnectState.Disabled);
|
|
| 663 | + // Pass the BootstrapAttempt result up.
|
|
| 664 | + this.#resolveRun({ result });
|
|
| 704 | 665 | }
|
| 705 | 666 | |
| 706 | - async run() {
|
|
| 707 | - lazy.logger.debug("Entered the disabled state.");
|
|
| 667 | + /**
|
|
| 668 | + * Cancel the bootstrap attempt.
|
|
| 669 | + */
|
|
| 670 | + async cancel() {
|
|
| 671 | + if (this.#cancelled) {
|
|
| 672 | + lazy.logger.warn(
|
|
| 673 | + "Cancelled auto-bootstrap after it has already been cancelled"
|
|
| 674 | + );
|
|
| 675 | + return;
|
|
| 676 | + }
|
|
| 677 | + this.#cancelled = true;
|
|
| 678 | + this.#resolveCancelled();
|
|
| 679 | + if (this.#resolved) {
|
|
| 680 | + lazy.logger.warn(
|
|
| 681 | + "Cancelled auto-bootstrap after it has already resolved"
|
|
| 682 | + );
|
|
| 683 | + return;
|
|
| 684 | + }
|
|
| 685 | + |
|
| 686 | + // Wait until after bootstrap.cancel returns before we resolve with
|
|
| 687 | + // cancelled. In particular, there is a small chance that the bootstrap
|
|
| 688 | + // completes, in which case we want to be able to resolve with a success
|
|
| 689 | + // instead.
|
|
| 690 | + if (this.#bootstrapAttempt) {
|
|
| 691 | + this.#bootstrapAttempt.cancel();
|
|
| 692 | + await this.#bootstrapAttempt;
|
|
| 693 | + }
|
|
| 694 | + // In case no bootstrap is running, we resolve with "cancelled".
|
|
| 695 | + this.#resolveRun({ result: "cancelled" });
|
|
| 708 | 696 | }
|
| 709 | 697 | }
|
| 710 | 698 | |
| ... | ... | @@ -721,8 +709,11 @@ class InternetTest { |
| 721 | 709 | #pending = false;
|
| 722 | 710 | #canceled = false;
|
| 723 | 711 | #timeout = 0;
|
| 712 | + #simulateOffline = false;
|
|
| 713 | + |
|
| 714 | + constructor(simulateOffline) {
|
|
| 715 | + this.#simulateOffline = simulateOffline;
|
|
| 724 | 716 | |
| 725 | - constructor() {
|
|
| 726 | 717 | this.#enabled = Services.prefs.getBoolPref(
|
| 727 | 718 | TorConnectPrefs.allow_internet_test,
|
| 728 | 719 | true
|
| ... | ... | @@ -752,6 +743,19 @@ class InternetTest { |
| 752 | 743 | this.#canceled = false;
|
| 753 | 744 | |
| 754 | 745 | lazy.logger.info("Starting the Internet test");
|
| 746 | + |
|
| 747 | + if (this.#simulateOffline) {
|
|
| 748 | + await new Promise(res => setTimeout(res, 500));
|
|
| 749 | + |
|
| 750 | + this.#status = InternetStatus.Offline;
|
|
| 751 | + |
|
| 752 | + if (this.#canceled) {
|
|
| 753 | + return;
|
|
| 754 | + }
|
|
| 755 | + this.onResult(this.#status);
|
|
| 756 | + return;
|
|
| 757 | + }
|
|
| 758 | + |
|
| 755 | 759 | const mrpc = new lazy.MoatRPC();
|
| 756 | 760 | try {
|
| 757 | 761 | await mrpc.init();
|
| ... | ... | @@ -792,27 +796,173 @@ class InternetTest { |
| 792 | 796 | return this.#status;
|
| 793 | 797 | }
|
| 794 | 798 | |
| 795 | - get error() {
|
|
| 796 | - return this.#error;
|
|
| 797 | - }
|
|
| 799 | + get error() {
|
|
| 800 | + return this.#error;
|
|
| 801 | + }
|
|
| 802 | + |
|
| 803 | + get enabled() {
|
|
| 804 | + return this.#enabled;
|
|
| 805 | + }
|
|
| 806 | + |
|
| 807 | + // We randomize the Internet test timeout to make fingerprinting it harder, at
|
|
| 808 | + // least a little bit...
|
|
| 809 | + #timeoutRand() {
|
|
| 810 | + const offset = 30000;
|
|
| 811 | + const randRange = 5000;
|
|
| 812 | + return offset + randRange * (Math.random() * 2 - 1);
|
|
| 813 | + }
|
|
| 814 | +}
|
|
| 815 | + |
|
| 816 | +export const TorConnectStage = Object.freeze({
|
|
| 817 | + Disabled: "Disabled",
|
|
| 818 | + Loading: "Loading",
|
|
| 819 | + Start: "Start",
|
|
| 820 | + Bootstrapping: "Bootstrapping",
|
|
| 821 | + Offline: "Offline",
|
|
| 822 | + ChooseRegion: "ChooseRegion",
|
|
| 823 | + RegionNotFound: "RegionNotFound",
|
|
| 824 | + ConfirmRegion: "ConfirmRegion",
|
|
| 825 | + FinalError: "FinalError",
|
|
| 826 | + Bootstrapped: "Bootstrapped",
|
|
| 827 | +});
|
|
| 828 | + |
|
| 829 | +/**
|
|
| 830 | + * @typedef {object} ConnectStage
|
|
| 831 | + *
|
|
| 832 | + * A summary of the user stage.
|
|
| 833 | + *
|
|
| 834 | + * @property {string} name - The name of the stage.
|
|
| 835 | + * @property {string} defaultRegion - The default region to show in the UI.
|
|
| 836 | + * @property {?string} bootstrapTrigger - The TorConnectStage prior to this
|
|
| 837 | + * bootstrap attempt. Only set during the "Bootstrapping" stage.
|
|
| 838 | + * @property {?BootstrapError} error - The last bootstrapping error.
|
|
| 839 | + * @property {boolean} tryAgain - Whether a bootstrap attempt has failed, so
|
|
| 840 | + * that a normal bootstrap should be shown as "Try Again" instead of
|
|
| 841 | + * "Connect". NOTE: to be removed when about:torconnect no longer uses
|
|
| 842 | + * breadcrumbs.
|
|
| 843 | + * @property {boolean} potentiallyBlocked - Whether bootstrapping has ever
|
|
| 844 | + * failed, not including being cancelled or being offline. I.e. whether we
|
|
| 845 | + * have reached an error stage at some point before being bootstrapped.
|
|
| 846 | + * @property {BootstrappingStatus} bootstrappingStatus - The current
|
|
| 847 | + * bootstrapping status.
|
|
| 848 | + */
|
|
| 849 | + |
|
| 850 | +/**
|
|
| 851 | + * @typedef {object} BootstrappingStatus
|
|
| 852 | + *
|
|
| 853 | + * The status of a bootstrap.
|
|
| 854 | + *
|
|
| 855 | + * @property {number} progress - The percent progress.
|
|
| 856 | + * @property {boolean} hasWarning - Whether this bootstrap has a warning in the
|
|
| 857 | + * Tor log.
|
|
| 858 | + */
|
|
| 859 | + |
|
| 860 | +/**
|
|
| 861 | + * @typedef {object} BootstrapError
|
|
| 862 | + *
|
|
| 863 | + * Details about the error that caused bootstrapping to fail.
|
|
| 864 | + *
|
|
| 865 | + * @property {string} code - The error code type.
|
|
| 866 | + * @property {string} message - The error message.
|
|
| 867 | + * @property {?string} phase - The bootstrapping phase that failed.
|
|
| 868 | + * @property {?string} reason - The bootstrapping failure reason.
|
|
| 869 | + */
|
|
| 870 | + |
|
| 871 | +export const TorConnect = {
|
|
| 872 | + /**
|
|
| 873 | + * Default bootstrap options for simulation.
|
|
| 874 | + *
|
|
| 875 | + * @type {BootstrapOptions}
|
|
| 876 | + */
|
|
| 877 | + simulateBootstrapOptions: {},
|
|
| 878 | + |
|
| 879 | + /**
|
|
| 880 | + * The name of the current stage the user is in.
|
|
| 881 | + *
|
|
| 882 | + * @type {string}
|
|
| 883 | + */
|
|
| 884 | + _stageName: TorConnectStage.Loading,
|
|
| 885 | + |
|
| 886 | + get stageName() {
|
|
| 887 | + return this._stageName;
|
|
| 888 | + },
|
|
| 889 | + |
|
| 890 | + /**
|
|
| 891 | + * The stage that triggered bootstrapping.
|
|
| 892 | + *
|
|
| 893 | + * @type {?string}
|
|
| 894 | + */
|
|
| 895 | + _bootstrapTrigger: null,
|
|
| 896 | + |
|
| 897 | + /**
|
|
| 898 | + * The alternative stage that we should move to after bootstrapping completes.
|
|
| 899 | + *
|
|
| 900 | + * @type {?string}
|
|
| 901 | + */
|
|
| 902 | + _requestedStage: null,
|
|
| 903 | + |
|
| 904 | + /**
|
|
| 905 | + * The default region to show in the UI for auto-bootstrapping.
|
|
| 906 | + *
|
|
| 907 | + * @type {string}
|
|
| 908 | + */
|
|
| 909 | + _defaultRegion: "automatic",
|
|
| 910 | + |
|
| 911 | + /**
|
|
| 912 | + * The current bootstrap attempt, if any.
|
|
| 913 | + *
|
|
| 914 | + * @type {?(BootstrapAttempt|AutoBootstrapAttempt)}
|
|
| 915 | + */
|
|
| 916 | + _bootstrapAttempt: null,
|
|
| 917 | + |
|
| 918 | + /**
|
|
| 919 | + * The bootstrap error that was last generated.
|
|
| 920 | + *
|
|
| 921 | + * @type {?TorConnectError}
|
|
| 922 | + */
|
|
| 923 | + _errorDetails: null,
|
|
| 924 | + |
|
| 925 | + /**
|
|
| 926 | + * Whether a bootstrap attempt has failed, so that a normal bootstrap should
|
|
| 927 | + * be shown as "Try Again" instead of "Connect".
|
|
| 928 | + *
|
|
| 929 | + * @type {boolean}
|
|
| 930 | + */
|
|
| 931 | + // TODO: Drop tryAgain when we remove breadcrumbs and use "Start again"
|
|
| 932 | + // instead.
|
|
| 933 | + _tryAgain: false,
|
|
| 798 | 934 | |
| 799 | - get enabled() {
|
|
| 800 | - return this.#enabled;
|
|
| 801 | - }
|
|
| 935 | + /**
|
|
| 936 | + * Whether bootstrapping has ever returned an error.
|
|
| 937 | + *
|
|
| 938 | + * @type {boolean}
|
|
| 939 | + */
|
|
| 940 | + _potentiallyBlocked: false,
|
|
| 802 | 941 | |
| 803 | - // We randomize the Internet test timeout to make fingerprinting it harder, at
|
|
| 804 | - // least a little bit...
|
|
| 805 | - #timeoutRand() {
|
|
| 806 | - const offset = 30000;
|
|
| 807 | - const randRange = 5000;
|
|
| 808 | - return offset + randRange * (Math.random() * 2 - 1);
|
|
| 809 | - }
|
|
| 810 | -}
|
|
| 942 | + /**
|
|
| 943 | + * Get a summary of the current user stage.
|
|
| 944 | + *
|
|
| 945 | + * @type {ConnectStage}
|
|
| 946 | + */
|
|
| 947 | + get stage() {
|
|
| 948 | + return {
|
|
| 949 | + name: this._stageName,
|
|
| 950 | + defaultRegion: this._defaultRegion,
|
|
| 951 | + bootstrapTrigger: this._bootstrapTrigger,
|
|
| 952 | + error: this._errorDetails
|
|
| 953 | + ? {
|
|
| 954 | + code: this._errorDetails.code,
|
|
| 955 | + message: String(this._errorDetails.message ?? ""),
|
|
| 956 | + phase: this._errorDetails.cause?.phase ?? null,
|
|
| 957 | + reason: this._errorDetails.cause?.reason ?? null,
|
|
| 958 | + }
|
|
| 959 | + : null,
|
|
| 960 | + tryAgain: this._tryAgain,
|
|
| 961 | + potentiallyBlocked: this._potentiallyBlocked,
|
|
| 962 | + bootstrappingStatus: structuredClone(this._bootstrappingStatus),
|
|
| 963 | + };
|
|
| 964 | + },
|
|
| 811 | 965 | |
| 812 | -export const TorConnect = {
|
|
| 813 | - _stateHandler: new InitialState(),
|
|
| 814 | - _bootstrapProgress: 0,
|
|
| 815 | - _internetStatus: InternetStatus.Unknown,
|
|
| 816 | 966 | // list of country codes Moat has settings for
|
| 817 | 967 | _countryCodes: [],
|
| 818 | 968 | _countryNames: Object.freeze(
|
| ... | ... | @@ -826,109 +976,28 @@ export const TorConnect = { |
| 826 | 976 | return codesNames;
|
| 827 | 977 | })()
|
| 828 | 978 | ),
|
| 829 | - _detectedLocation: "",
|
|
| 830 | - _errorCode: null,
|
|
| 831 | - _errorDetails: null,
|
|
| 832 | - _logHasWarningOrError: false,
|
|
| 833 | - _hasBootstrapEverFailed: false,
|
|
| 834 | - _transitionPromise: null,
|
|
| 835 | 979 | |
| 836 | 980 | // This is used as a helper to make the state of about:torconnect persistent
|
| 837 | 981 | // during a session, but TorConnect does not use this data at all.
|
| 838 | 982 | _uiState: {},
|
| 839 | 983 | |
| 840 | - _stateCallbacks: Object.freeze(
|
|
| 841 | - new Map([
|
|
| 842 | - // Initial is never transitioned to
|
|
| 843 | - [TorConnectState.Initial, InitialState],
|
|
| 844 | - [TorConnectState.Configuring, ConfiguringState],
|
|
| 845 | - [TorConnectState.Bootstrapping, BootstrappingState],
|
|
| 846 | - [TorConnectState.AutoBootstrapping, AutoBootstrappingState],
|
|
| 847 | - [TorConnectState.Bootstrapped, BootstrappedState],
|
|
| 848 | - [TorConnectState.Error, ErrorState],
|
|
| 849 | - [TorConnectState.Disabled, DisabledState],
|
|
| 850 | - ])
|
|
| 851 | - ),
|
|
| 852 | - |
|
| 853 | - _makeState(state) {
|
|
| 854 | - const klass = this._stateCallbacks.get(state);
|
|
| 855 | - if (!klass) {
|
|
| 856 | - throw new Error(`${state} is not a valid state.`);
|
|
| 857 | - }
|
|
| 858 | - return new klass();
|
|
| 859 | - },
|
|
| 860 | - |
|
| 861 | - async _changeState(newState, ...args) {
|
|
| 862 | - if (this._stateHandler.transitioning) {
|
|
| 863 | - // Avoid an exception to prevent it to be propagated to the original
|
|
| 864 | - // begin call.
|
|
| 865 | - lazy.logger.warn("Already transitioning");
|
|
| 866 | - return;
|
|
| 867 | - }
|
|
| 868 | - const prevState = this._stateHandler;
|
|
| 869 | - |
|
| 870 | - // ensure this is a valid state transition
|
|
| 871 | - if (!prevState.allowedTransitions.includes(newState)) {
|
|
| 872 | - throw Error(
|
|
| 873 | - `TorConnect: Attempted invalid state transition from ${prevState.state} to ${newState}`
|
|
| 874 | - );
|
|
| 875 | - }
|
|
| 876 | - |
|
| 877 | - lazy.logger.trace(
|
|
| 878 | - `Try transitioning from ${prevState.state} to ${newState}`,
|
|
| 879 | - args
|
|
| 880 | - );
|
|
| 881 | - try {
|
|
| 882 | - await prevState.end(newState);
|
|
| 883 | - } catch (e) {
|
|
| 884 | - // We take for granted that the begin of this state will call us again,
|
|
| 885 | - // to request the transition to the error state.
|
|
| 886 | - if (newState !== TorConnectState.Error) {
|
|
| 887 | - lazy.logger.debug(
|
|
| 888 | - `Refusing the transition from ${prevState.state} to ${newState} because the previous state threw.`
|
|
| 889 | - );
|
|
| 890 | - return;
|
|
| 891 | - }
|
|
| 892 | - }
|
|
| 893 | - |
|
| 894 | - // Set our new state first so that state transitions can themselves
|
|
| 895 | - // trigger a state transition.
|
|
| 896 | - this._stateHandler = this._makeState(newState);
|
|
| 897 | - |
|
| 898 | - // Error signal needs to be sent out before we enter the Error state.
|
|
| 899 | - // Expected on android `onBootstrapError` to set lastKnownError.
|
|
| 900 | - // Expected in about:torconnect to set the error codes and internet status
|
|
| 901 | - // *before* the StateChange signal.
|
|
| 902 | - if (newState === TorConnectState.Error) {
|
|
| 903 | - let error = args[0];
|
|
| 904 | - if (!(error instanceof TorConnectError)) {
|
|
| 905 | - error = new TorConnectError(TorConnectError.ExternalError, error);
|
|
| 906 | - }
|
|
| 907 | - TorConnect._errorCode = error.code;
|
|
| 908 | - TorConnect._errorDetails = error;
|
|
| 909 | - lazy.logger.error(`Entering error state (${error.code})`, error);
|
|
| 910 | - |
|
| 911 | - Services.obs.notifyObservers(error, TorConnectTopics.Error);
|
|
| 912 | - }
|
|
| 913 | - |
|
| 914 | - Services.obs.notifyObservers(
|
|
| 915 | - { state: newState },
|
|
| 916 | - TorConnectTopics.StateChange
|
|
| 917 | - );
|
|
| 918 | - this._stateHandler.begin(...args);
|
|
| 984 | + /**
|
|
| 985 | + * The status of the most recent bootstrap attempt.
|
|
| 986 | + *
|
|
| 987 | + * @type {BootstrappingStatus}
|
|
| 988 | + */
|
|
| 989 | + _bootstrappingStatus: {
|
|
| 990 | + progress: 0,
|
|
| 991 | + hasWarning: false,
|
|
| 919 | 992 | },
|
| 920 | 993 | |
| 921 | - _updateBootstrapProgress(progress, status) {
|
|
| 922 | - this._bootstrapProgress = progress;
|
|
| 923 | - |
|
| 924 | - lazy.logger.info(
|
|
| 925 | - `Bootstrapping ${this._bootstrapProgress}% complete (${status})`
|
|
| 926 | - );
|
|
| 994 | + /**
|
|
| 995 | + * Notify the bootstrap progress.
|
|
| 996 | + */
|
|
| 997 | + _notifyBootstrapProgress() {
|
|
| 998 | + lazy.logger.debug("BootstrappingStatus", this._bootstrappingStatus);
|
|
| 927 | 999 | Services.obs.notifyObservers(
|
| 928 | - {
|
|
| 929 | - progress: TorConnect._bootstrapProgress,
|
|
| 930 | - hasWarnings: TorConnect._logHasWarningOrError,
|
|
| 931 | - },
|
|
| 1000 | + this._bootstrappingStatus,
|
|
| 932 | 1001 | TorConnectTopics.BootstrapProgress
|
| 933 | 1002 | );
|
| 934 | 1003 | },
|
| ... | ... | @@ -936,62 +1005,54 @@ export const TorConnect = { |
| 936 | 1005 | // init should be called by TorStartupService
|
| 937 | 1006 | init() {
|
| 938 | 1007 | lazy.logger.debug("TorConnect.init()");
|
| 939 | - this._stateHandler.begin();
|
|
| 940 | 1008 | |
| 941 | 1009 | if (!this.enabled) {
|
| 942 | 1010 | // Disabled
|
| 943 | - this._changeState(TorConnectState.Disabled);
|
|
| 944 | - } else {
|
|
| 945 | - let observeTopic = addTopic => {
|
|
| 946 | - Services.obs.addObserver(this, addTopic);
|
|
| 947 | - lazy.logger.debug(`Observing topic '${addTopic}'`);
|
|
| 948 | - };
|
|
| 1011 | + this._setStage(TorConnectStage.Disabled);
|
|
| 1012 | + return;
|
|
| 1013 | + }
|
|
| 949 | 1014 | |
| 950 | - // Wait for TorSettings, as we will need it.
|
|
| 951 | - // We will wait for a TorProvider only after TorSettings is ready,
|
|
| 952 | - // because the TorProviderBuilder initialization might not have finished
|
|
| 953 | - // at this point, and TorSettings initialization is a prerequisite for
|
|
| 954 | - // having a provider.
|
|
| 955 | - // So, we prefer initializing TorConnect as soon as possible, so that
|
|
| 956 | - // the UI will be able to detect it is in the Initializing state and act
|
|
| 957 | - // consequently.
|
|
| 958 | - lazy.TorSettings.initializedPromise.then(() =>
|
|
| 959 | - this._settingsInitialized()
|
|
| 960 | - );
|
|
| 1015 | + let observeTopic = addTopic => {
|
|
| 1016 | + Services.obs.addObserver(this, addTopic);
|
|
| 1017 | + lazy.logger.debug(`Observing topic '${addTopic}'`);
|
|
| 1018 | + };
|
|
| 961 | 1019 | |
| 962 | - // register the Tor topics we always care about
|
|
| 963 | - observeTopic(lazy.TorProviderTopics.ProcessExited);
|
|
| 964 | - observeTopic(lazy.TorProviderTopics.HasWarnOrErr);
|
|
| 965 | - }
|
|
| 1020 | + // Wait for TorSettings, as we will need it.
|
|
| 1021 | + // We will wait for a TorProvider only after TorSettings is ready,
|
|
| 1022 | + // because the TorProviderBuilder initialization might not have finished
|
|
| 1023 | + // at this point, and TorSettings initialization is a prerequisite for
|
|
| 1024 | + // having a provider.
|
|
| 1025 | + // So, we prefer initializing TorConnect as soon as possible, so that
|
|
| 1026 | + // the UI will be able to detect it is in the Initializing state and act
|
|
| 1027 | + // consequently.
|
|
| 1028 | + lazy.TorSettings.initializedPromise.then(() => this._settingsInitialized());
|
|
| 1029 | + |
|
| 1030 | + // register the Tor topics we always care about
|
|
| 1031 | + observeTopic(lazy.TorProviderTopics.ProcessExited);
|
|
| 1032 | + observeTopic(lazy.TorProviderTopics.HasWarnOrErr);
|
|
| 966 | 1033 | },
|
| 967 | 1034 | |
| 968 | 1035 | async observe(subject, topic) {
|
| 969 | 1036 | lazy.logger.debug(`Observed ${topic}`);
|
| 970 | 1037 | |
| 971 | 1038 | switch (topic) {
|
| 972 | - case lazy.TorProviderTopics.HasWarnOrErr: {
|
|
| 973 | - this._logHasWarningOrError = true;
|
|
| 1039 | + case lazy.TorProviderTopics.HasWarnOrErr:
|
|
| 1040 | + if (this._bootstrappingStatus.hasWarning) {
|
|
| 1041 | + // No change.
|
|
| 1042 | + return;
|
|
| 1043 | + }
|
|
| 1044 | + if (this._stageName === "Bootstrapping") {
|
|
| 1045 | + this._bootstrappingStatus.hasWarning = true;
|
|
| 1046 | + this._notifyBootstrapProgress();
|
|
| 1047 | + }
|
|
| 974 | 1048 | break;
|
| 975 | - }
|
|
| 976 | - case lazy.TorProviderTopics.ProcessExited: {
|
|
| 1049 | + case lazy.TorProviderTopics.ProcessExited:
|
|
| 1050 | + lazy.logger.info("Starting again since the tor process exited");
|
|
| 977 | 1051 | // Treat a failure as a possibly broken configuration.
|
| 978 | 1052 | // So, prevent quickstart at the next start.
|
| 979 | 1053 | Services.prefs.setBoolPref(TorLauncherPrefs.prompt_at_startup, true);
|
| 980 | - switch (this.state) {
|
|
| 981 | - case TorConnectState.Bootstrapping:
|
|
| 982 | - case TorConnectState.AutoBootstrapping:
|
|
| 983 | - case TorConnectState.Bootstrapped:
|
|
| 984 | - // If we are in the bootstrap or auto bootstrap, we could go
|
|
| 985 | - // through the error phase (and eventually we might do it, if some
|
|
| 986 | - // transition calls fail). However, this would start the
|
|
| 987 | - // connection assist, so we go directly to configuring.
|
|
| 988 | - // FIXME: Find a better way to handle this.
|
|
| 989 | - this._changeState(TorConnectState.Configuring);
|
|
| 990 | - break;
|
|
| 991 | - // Other states naturally resolve in configuration.
|
|
| 992 | - }
|
|
| 1054 | + this._makeStageRequest(TorConnectStage.Start, true);
|
|
| 993 | 1055 | break;
|
| 994 | - }
|
|
| 995 | 1056 | default:
|
| 996 | 1057 | // ignore
|
| 997 | 1058 | break;
|
| ... | ... | @@ -1003,29 +1064,47 @@ export const TorConnect = { |
| 1003 | 1064 | // daemon when it exits (tor-browser#21053, tor-browser#41921).
|
| 1004 | 1065 | await lazy.TorProviderBuilder.build();
|
| 1005 | 1066 | |
| 1006 | - // tor-browser#41907: This is only a workaround to avoid users being
|
|
| 1007 | - // bounced back to the initial panel without any explanation.
|
|
| 1008 | - // Longer term we should disable the clickable elements, or find a UX
|
|
| 1009 | - // to prevent this from happening (e.g., allow buttons to be clicked,
|
|
| 1010 | - // but show an intermediate starting state, or a message that tor is
|
|
| 1011 | - // starting while the butons are disabled, etc...).
|
|
| 1012 | - // Notice that currently the initial state does not do anything.
|
|
| 1013 | - // Instead of just waiting, we could move this code in its callback.
|
|
| 1014 | - // See also tor-browser#41921.
|
|
| 1015 | - if (this.state !== TorConnectState.Initial) {
|
|
| 1016 | - lazy.logger.warn(
|
|
| 1017 | - "The TorProvider was built after the state had already changed."
|
|
| 1018 | - );
|
|
| 1019 | - return;
|
|
| 1020 | - }
|
|
| 1021 | 1067 | lazy.logger.debug("The TorProvider is ready, changing state.");
|
| 1068 | + // NOTE: If the tor process exits before this point, then
|
|
| 1069 | + // shouldQuickStart would be `false`.
|
|
| 1070 | + // NOTE: At this point, _requestedStage should still be `null`.
|
|
| 1071 | + this._setStage(TorConnectStage.Start);
|
|
| 1022 | 1072 | if (this.shouldQuickStart) {
|
| 1023 | 1073 | // Quickstart
|
| 1024 | - this._changeState(TorConnectState.Bootstrapping);
|
|
| 1025 | - } else {
|
|
| 1026 | - // Configuring
|
|
| 1027 | - this._changeState(TorConnectState.Configuring);
|
|
| 1074 | + this.beginBootstrapping();
|
|
| 1075 | + }
|
|
| 1076 | + },
|
|
| 1077 | + |
|
| 1078 | + /**
|
|
| 1079 | + * Set the user stage.
|
|
| 1080 | + *
|
|
| 1081 | + * @param {string} name - The name of the stage to move to.
|
|
| 1082 | + */
|
|
| 1083 | + _setStage(name) {
|
|
| 1084 | + if (this._bootstrapAttempt) {
|
|
| 1085 | + throw new Error(`Trying to set the stage to ${name} during a bootstrap`);
|
|
| 1086 | + }
|
|
| 1087 | + |
|
| 1088 | + lazy.logger.info(`Entering stage ${name}`);
|
|
| 1089 | + const prevState = this.state;
|
|
| 1090 | + this._stageName = name;
|
|
| 1091 | + this._bootstrappingStatus.hasWarning = false;
|
|
| 1092 | + this._bootstrappingStatus.progress =
|
|
| 1093 | + name === TorConnectStage.Bootstrapped ? 100 : 0;
|
|
| 1094 | + |
|
| 1095 | + Services.obs.notifyObservers(this.stage, TorConnectTopics.StageChange);
|
|
| 1096 | + |
|
| 1097 | + // TODO: Remove when all pages have switched to stage.
|
|
| 1098 | + const newState = this.state;
|
|
| 1099 | + if (prevState !== newState) {
|
|
| 1100 | + Services.obs.notifyObservers(
|
|
| 1101 | + { state: newState },
|
|
| 1102 | + TorConnectTopics.StateChange
|
|
| 1103 | + );
|
|
| 1028 | 1104 | }
|
| 1105 | + |
|
| 1106 | + // Update the progress after the stage has changed.
|
|
| 1107 | + this._notifyBootstrapProgress();
|
|
| 1029 | 1108 | },
|
| 1030 | 1109 | |
| 1031 | 1110 | /*
|
| ... | ... | @@ -1049,33 +1128,41 @@ export const TorConnect = { |
| 1049 | 1128 | return (
|
| 1050 | 1129 | this.enabled &&
|
| 1051 | 1130 | // if we have succesfully bootstraped, then no need to show TorConnect
|
| 1052 | - this.state !== TorConnectState.Bootstrapped
|
|
| 1131 | + this._stageName !== TorConnectStage.Bootstrapped
|
|
| 1053 | 1132 | );
|
| 1054 | 1133 | },
|
| 1055 | 1134 | |
| 1056 | 1135 | /**
|
| 1057 | - * Whether bootstrapping can currently begin.
|
|
| 1136 | + * Whether we are in a stage that can lead into the Bootstrapping stage. I.e.
|
|
| 1137 | + * whether we can make a "normal" or "auto" bootstrapping request.
|
|
| 1058 | 1138 | *
|
| 1059 | - * The value may change with TorConnectTopics.StateChanged.
|
|
| 1139 | + * The value may change with TorConnectTopics.StageChanged.
|
|
| 1060 | 1140 | *
|
| 1061 | 1141 | * @param {boolean}
|
| 1062 | 1142 | */
|
| 1063 | 1143 | get canBeginBootstrap() {
|
| 1064 | - return this._stateHandler.allowedTransitions.includes(
|
|
| 1065 | - TorConnectState.Bootstrapping
|
|
| 1144 | + return (
|
|
| 1145 | + this._stageName === TorConnectStage.Start ||
|
|
| 1146 | + this._stageName === TorConnectStage.Offline ||
|
|
| 1147 | + this._stageName === TorConnectStage.ChooseRegion ||
|
|
| 1148 | + this._stageName === TorConnectStage.RegionNotFound ||
|
|
| 1149 | + this._stageName === TorConnectStage.ConfirmRegion
|
|
| 1066 | 1150 | );
|
| 1067 | 1151 | },
|
| 1068 | 1152 | |
| 1069 | 1153 | /**
|
| 1070 | - * Whether auto-bootstrapping can currently begin.
|
|
| 1154 | + * Whether we are in an error stage that can lead into the Bootstrapping
|
|
| 1155 | + * stage. I.e. whether we can make an "auto" bootstrapping request.
|
|
| 1071 | 1156 | *
|
| 1072 | - * The value may change with TorConnectTopics.StateChanged.
|
|
| 1157 | + * The value may change with TorConnectTopics.StageChanged.
|
|
| 1073 | 1158 | *
|
| 1074 | 1159 | * @param {boolean}
|
| 1075 | 1160 | */
|
| 1076 | 1161 | get canBeginAutoBootstrap() {
|
| 1077 | - return this._stateHandler.allowedTransitions.includes(
|
|
| 1078 | - TorConnectState.AutoBootstrapping
|
|
| 1162 | + return (
|
|
| 1163 | + this._stageName === TorConnectStage.ChooseRegion ||
|
|
| 1164 | + this._stageName === TorConnectStage.RegionNotFound ||
|
|
| 1165 | + this._stageName === TorConnectStage.ConfirmRegion
|
|
| 1079 | 1166 | );
|
| 1080 | 1167 | },
|
| 1081 | 1168 | |
| ... | ... | @@ -1088,16 +1175,39 @@ export const TorConnect = { |
| 1088 | 1175 | );
|
| 1089 | 1176 | },
|
| 1090 | 1177 | |
| 1178 | + // TODO: Remove when all pages have switched to "stage".
|
|
| 1091 | 1179 | get state() {
|
| 1092 | - return this._stateHandler.state;
|
|
| 1093 | - },
|
|
| 1094 | - |
|
| 1095 | - get bootstrapProgress() {
|
|
| 1096 | - return this._bootstrapProgress;
|
|
| 1097 | - },
|
|
| 1098 | - |
|
| 1099 | - get internetStatus() {
|
|
| 1100 | - return this._internetStatus;
|
|
| 1180 | + // There is no "Error" stage, but about:torconnect relies on receiving the
|
|
| 1181 | + // Error state to update its display. So we temporarily set the stage for a
|
|
| 1182 | + // StateChange signal.
|
|
| 1183 | + if (this._isErrorState) {
|
|
| 1184 | + return TorConnectState.Error;
|
|
| 1185 | + }
|
|
| 1186 | + switch (this._stageName) {
|
|
| 1187 | + case TorConnectStage.Disabled:
|
|
| 1188 | + return TorConnectState.Disabled;
|
|
| 1189 | + case TorConnectStage.Loading:
|
|
| 1190 | + return TorConnectState.Initial;
|
|
| 1191 | + case TorConnectStage.Start:
|
|
| 1192 | + case TorConnectStage.Offline:
|
|
| 1193 | + case TorConnectStage.ChooseRegion:
|
|
| 1194 | + case TorConnectStage.RegionNotFound:
|
|
| 1195 | + case TorConnectStage.ConfirmRegion:
|
|
| 1196 | + case TorConnectStage.FinalError:
|
|
| 1197 | + return TorConnectState.Configuring;
|
|
| 1198 | + case TorConnectStage.Bootstrapping:
|
|
| 1199 | + if (
|
|
| 1200 | + this._bootstrapTrigger === TorConnectStage.Start ||
|
|
| 1201 | + this._bootstrapTrigger === TorConnectStage.Offline
|
|
| 1202 | + ) {
|
|
| 1203 | + return TorConnectState.Bootstrapping;
|
|
| 1204 | + }
|
|
| 1205 | + return TorConnectState.AutoBootstrapping;
|
|
| 1206 | + case TorConnectStage.Bootstrapped:
|
|
| 1207 | + return TorConnectState.Bootstrapped;
|
|
| 1208 | + }
|
|
| 1209 | + lazy.logger.error(`Unknown state at stage ${this._stageName}`);
|
|
| 1210 | + return null;
|
|
| 1101 | 1211 | },
|
| 1102 | 1212 | |
| 1103 | 1213 | get countryCodes() {
|
| ... | ... | @@ -1108,92 +1218,414 @@ export const TorConnect = { |
| 1108 | 1218 | return this._countryNames;
|
| 1109 | 1219 | },
|
| 1110 | 1220 | |
| 1111 | - get detectedLocation() {
|
|
| 1112 | - return this._detectedLocation;
|
|
| 1221 | + /**
|
|
| 1222 | + * Whether the Bootstrapping process has ever failed, not including being
|
|
| 1223 | + * cancelled or being offline.
|
|
| 1224 | + *
|
|
| 1225 | + * The value may change with TorConnectTopics.StageChanged.
|
|
| 1226 | + *
|
|
| 1227 | + * @type {boolean}
|
|
| 1228 | + */
|
|
| 1229 | + get potentiallyBlocked() {
|
|
| 1230 | + return this._potentiallyBlocked;
|
|
| 1113 | 1231 | },
|
| 1114 | 1232 | |
| 1115 | - get errorCode() {
|
|
| 1116 | - return this._errorCode;
|
|
| 1233 | + /**
|
|
| 1234 | + * Ensure that we are not disabled.
|
|
| 1235 | + */
|
|
| 1236 | + _ensureEnabled() {
|
|
| 1237 | + if (!this.enabled || this._stageName === TorConnectStage.Disabled) {
|
|
| 1238 | + throw new Error("Unexpected Disabled stage for user method");
|
|
| 1239 | + }
|
|
| 1117 | 1240 | },
|
| 1118 | 1241 | |
| 1119 | - get errorDetails() {
|
|
| 1120 | - return this._errorDetails;
|
|
| 1121 | - },
|
|
| 1242 | + /**
|
|
| 1243 | + * Signal an error to listeners.
|
|
| 1244 | + *
|
|
| 1245 | + * @param {Error} error - The error.
|
|
| 1246 | + */
|
|
| 1247 | + _signalError(error) {
|
|
| 1248 | + // TODO: Replace this method with _setError without any signalling when
|
|
| 1249 | + // pages have switched to stage.
|
|
| 1250 | + // Currently it simulates the old behaviour for about:torconnect.
|
|
| 1251 | + lazy.logger.debug("Signalling error", error);
|
|
| 1252 | + |
|
| 1253 | + if (!(error instanceof TorConnectError)) {
|
|
| 1254 | + error = new TorConnectError(TorConnectError.ExternalError, error);
|
|
| 1255 | + }
|
|
| 1256 | + this._errorDetails = error;
|
|
| 1122 | 1257 | |
| 1123 | - get logHasWarningOrError() {
|
|
| 1124 | - return this._logHasWarningOrError;
|
|
| 1258 | + // Temporarily set an error state for listeners.
|
|
| 1259 | + // We send the Error signal before the "StateChange" signal.
|
|
| 1260 | + // Expected on android `onBootstrapError` to set lastKnownError.
|
|
| 1261 | + // Expected in about:torconnect to set the error codes and internet status
|
|
| 1262 | + // *before* the StateChange signal.
|
|
| 1263 | + this._isErrorState = true;
|
|
| 1264 | + Services.obs.notifyObservers(error, TorConnectTopics.Error);
|
|
| 1265 | + Services.obs.notifyObservers(
|
|
| 1266 | + { state: this.state },
|
|
| 1267 | + TorConnectTopics.StateChange
|
|
| 1268 | + );
|
|
| 1269 | + this._isErrorState = false;
|
|
| 1125 | 1270 | },
|
| 1126 | 1271 | |
| 1127 | 1272 | /**
|
| 1128 | - * Whether we have ever entered the Error state.
|
|
| 1273 | + * Add simulation options to the bootstrap request.
|
|
| 1129 | 1274 | *
|
| 1130 | - * @type {boolean}
|
|
| 1275 | + * @param {BootstrapOptions} bootstrapOptions - The options to add to.
|
|
| 1276 | + * @param {string} [regionCode] - The region code being used.
|
|
| 1131 | 1277 | */
|
| 1132 | - get hasEverFailed() {
|
|
| 1133 | - return ErrorState.hasEverHappened;
|
|
| 1278 | + _addSimulateOptions(bootstrapOptions, regionCode) {
|
|
| 1279 | + if (this.simulateBootstrapOptions.simulateCensorship) {
|
|
| 1280 | + bootstrapOptions.simulateCensorship = true;
|
|
| 1281 | + }
|
|
| 1282 | + if (this.simulateBootstrapOptions.simulateDelay) {
|
|
| 1283 | + bootstrapOptions.simulateDelay =
|
|
| 1284 | + this.simulateBootstrapOptions.simulateDelay;
|
|
| 1285 | + }
|
|
| 1286 | + if (this.simulateBootstrapOptions.simulateOffline) {
|
|
| 1287 | + bootstrapOptions.simulateOffline = true;
|
|
| 1288 | + }
|
|
| 1289 | + if (this.simulateBootstrapOptions.simulateMoatResponse) {
|
|
| 1290 | + bootstrapOptions.simulateMoatResponse =
|
|
| 1291 | + this.simulateBootstrapOptions.simulateMoatResponse;
|
|
| 1292 | + }
|
|
| 1293 | + |
|
| 1294 | + const censorshipLevel = Services.prefs.getIntPref(
|
|
| 1295 | + TorConnectPrefs.censorship_level,
|
|
| 1296 | + 0
|
|
| 1297 | + );
|
|
| 1298 | + if (censorshipLevel > 0 && !bootstrapOptions.simulateDelay) {
|
|
| 1299 | + bootstrapOptions.simulateDelay = 1500;
|
|
| 1300 | + }
|
|
| 1301 | + if (censorshipLevel === 1) {
|
|
| 1302 | + // Bootstrap fails, but auto-bootstrap does not.
|
|
| 1303 | + if (!regionCode) {
|
|
| 1304 | + bootstrapOptions.simulateCensorship = true;
|
|
| 1305 | + }
|
|
| 1306 | + } else if (censorshipLevel === 2) {
|
|
| 1307 | + // Bootstrap fails. Auto-bootstrap fails with ConfirmRegion when using
|
|
| 1308 | + // auto-detect region, but succeeds otherwise.
|
|
| 1309 | + if (!regionCode) {
|
|
| 1310 | + bootstrapOptions.simulateCensorship = true;
|
|
| 1311 | + }
|
|
| 1312 | + if (regionCode === "automatic") {
|
|
| 1313 | + bootstrapOptions.simulateCensorship = true;
|
|
| 1314 | + bootstrapOptions.simulateMoatResponse = {
|
|
| 1315 | + country: "fi",
|
|
| 1316 | + settings: [{}, {}],
|
|
| 1317 | + };
|
|
| 1318 | + }
|
|
| 1319 | + } else if (censorshipLevel === 3) {
|
|
| 1320 | + // Bootstrap and auto-bootstrap fail.
|
|
| 1321 | + bootstrapOptions.simulateCensorship = true;
|
|
| 1322 | + bootstrapOptions.simulateMoatResponse = {
|
|
| 1323 | + country: null,
|
|
| 1324 | + settings: [],
|
|
| 1325 | + };
|
|
| 1326 | + }
|
|
| 1134 | 1327 | },
|
| 1135 | 1328 | |
| 1136 | 1329 | /**
|
| 1137 | - * Whether the Bootstrapping process has ever failed, not including when it
|
|
| 1138 | - * failed due to not being connected to the internet.
|
|
| 1330 | + * Confirm that a bootstrapping can take place, and whether the given values
|
|
| 1331 | + * are valid.
|
|
| 1139 | 1332 | *
|
| 1140 | - * This does not include a failure in AutoBootstrapping.
|
|
| 1333 | + * @param {string} [regionCode] - The region code passed in.
|
|
| 1141 | 1334 | *
|
| 1142 | - * @type {boolean}
|
|
| 1335 | + * @return {boolean} whether bootstrapping can proceed.
|
|
| 1143 | 1336 | */
|
| 1144 | - get potentiallyBlocked() {
|
|
| 1145 | - return this._hasBootstrapEverFailed;
|
|
| 1146 | - },
|
|
| 1337 | + _confirmBootstrapping(regionCode) {
|
|
| 1338 | + this._ensureEnabled();
|
|
| 1339 | + |
|
| 1340 | + if (this._bootstrapAttempt) {
|
|
| 1341 | + lazy.logger.warn(
|
|
| 1342 | + "Already have an ongoing bootstrap attempt." +
|
|
| 1343 | + ` Ignoring request with ${regionCode}.`
|
|
| 1344 | + );
|
|
| 1345 | + return false;
|
|
| 1346 | + }
|
|
| 1347 | + |
|
| 1348 | + const currentStage = this._stageName;
|
|
| 1349 | + |
|
| 1350 | + if (regionCode) {
|
|
| 1351 | + if (!this.canBeginAutoBootstrap) {
|
|
| 1352 | + lazy.logger.warn(
|
|
| 1353 | + `Cannot begin auto bootstrap in stage ${currentStage}`
|
|
| 1354 | + );
|
|
| 1355 | + return false;
|
|
| 1356 | + }
|
|
| 1357 | + if (
|
|
| 1358 | + regionCode === "automatic" &&
|
|
| 1359 | + currentStage !== TorConnectStage.ChooseRegion
|
|
| 1360 | + ) {
|
|
| 1361 | + lazy.logger.warn("Auto bootstrap is missing an explicit regionCode");
|
|
| 1362 | + return false;
|
|
| 1363 | + }
|
|
| 1364 | + return true;
|
|
| 1365 | + }
|
|
| 1366 | + |
|
| 1367 | + if (!this.canBeginBootstrap) {
|
|
| 1368 | + lazy.logger.warn(`Cannot begin bootstrap in stage ${currentStage}`);
|
|
| 1369 | + return false;
|
|
| 1370 | + }
|
|
| 1371 | + if (this.canBeginAutoBootstrap) {
|
|
| 1372 | + // Only expect "auto" bootstraps to be triggered when in an error stage.
|
|
| 1373 | + lazy.logger.warn(
|
|
| 1374 | + `Expected a regionCode to bootstrap in stage ${currentStage}`
|
|
| 1375 | + );
|
|
| 1376 | + return false;
|
|
| 1377 | + }
|
|
| 1147 | 1378 | |
| 1148 | - get uiState() {
|
|
| 1149 | - return this._uiState;
|
|
| 1379 | + return true;
|
|
| 1150 | 1380 | },
|
| 1151 | - set uiState(newState) {
|
|
| 1152 | - this._uiState = newState;
|
|
| 1381 | + |
|
| 1382 | + /**
|
|
| 1383 | + * Begin a bootstrap attempt.
|
|
| 1384 | + *
|
|
| 1385 | + * @param {string} [regionCode] - An optional region code string to use, or
|
|
| 1386 | + * "automatic" to automatically determine the region. If given, will start
|
|
| 1387 | + * an auto-bootstrap attempt.
|
|
| 1388 | + */
|
|
| 1389 | + async beginBootstrapping(regionCode) {
|
|
| 1390 | + lazy.logger.debug("TorConnect.beginBootstrapping()");
|
|
| 1391 | + |
|
| 1392 | + if (!this._confirmBootstrapping(regionCode)) {
|
|
| 1393 | + return;
|
|
| 1394 | + }
|
|
| 1395 | + |
|
| 1396 | + const beginStage = this._stageName;
|
|
| 1397 | + const bootstrapOptions = { regionCode };
|
|
| 1398 | + const bootstrapAttempt = regionCode
|
|
| 1399 | + ? new AutoBootstrapAttempt()
|
|
| 1400 | + : new BootstrapAttempt();
|
|
| 1401 | + |
|
| 1402 | + if (!regionCode) {
|
|
| 1403 | + // Only test internet for the first bootstrap attempt.
|
|
| 1404 | + // TODO: Remove this since we do not have user consent. tor-browser#42605.
|
|
| 1405 | + bootstrapOptions.testInternet = true;
|
|
| 1406 | + }
|
|
| 1407 | + |
|
| 1408 | + this._addSimulateOptions(bootstrapOptions, regionCode);
|
|
| 1409 | + |
|
| 1410 | + // NOTE: The only `await` in this method is for `bootstrapAttempt.run`.
|
|
| 1411 | + // Moreover, we returned early if `_bootstrapAttempt` was non-`null`.
|
|
| 1412 | + // Therefore, the method is effectively "locked" by `_bootstrapAttempt`, so
|
|
| 1413 | + // there should only ever be one caller at a time.
|
|
| 1414 | + |
|
| 1415 | + if (regionCode) {
|
|
| 1416 | + // Set the default to what the user chose.
|
|
| 1417 | + this._defaultRegion = regionCode;
|
|
| 1418 | + } else {
|
|
| 1419 | + // Reset the default region to show in the UI.
|
|
| 1420 | + this._defaultRegion = "automatic";
|
|
| 1421 | + }
|
|
| 1422 | + this._requestedStage = null;
|
|
| 1423 | + this._bootstrapTrigger = beginStage;
|
|
| 1424 | + this._setStage(TorConnectStage.Bootstrapping);
|
|
| 1425 | + this._bootstrapAttempt = bootstrapAttempt;
|
|
| 1426 | + |
|
| 1427 | + let error = null;
|
|
| 1428 | + let result = null;
|
|
| 1429 | + try {
|
|
| 1430 | + result = await bootstrapAttempt.run(progress => {
|
|
| 1431 | + this._bootstrappingStatus.progress = progress;
|
|
| 1432 | + lazy.logger.info(`Bootstrapping ${progress}% complete`);
|
|
| 1433 | + this._notifyBootstrapProgress();
|
|
| 1434 | + }, bootstrapOptions);
|
|
| 1435 | + } catch (err) {
|
|
| 1436 | + error = err;
|
|
| 1437 | + }
|
|
| 1438 | + |
|
| 1439 | + const requestedStage = this._requestedStage;
|
|
| 1440 | + this._requestedStage = null;
|
|
| 1441 | + this._bootstrapTrigger = null;
|
|
| 1442 | + this._bootstrapAttempt = null;
|
|
| 1443 | + |
|
| 1444 | + if (bootstrapAttempt.detectedRegion) {
|
|
| 1445 | + this._defaultRegion = bootstrapAttempt.detectedRegion;
|
|
| 1446 | + }
|
|
| 1447 | + |
|
| 1448 | + if (result === "complete") {
|
|
| 1449 | + // Reset tryAgain, potentiallyBlocked and errorDetails in case the tor
|
|
| 1450 | + // process exists later on.
|
|
| 1451 | + this._tryAgain = false;
|
|
| 1452 | + this._potentiallyBlocked = false;
|
|
| 1453 | + this._errorDetails = null;
|
|
| 1454 | + |
|
| 1455 | + if (requestedStage) {
|
|
| 1456 | + lazy.logger.warn(
|
|
| 1457 | + `Ignoring ${requestedStage} request since we are bootstrapped`
|
|
| 1458 | + );
|
|
| 1459 | + }
|
|
| 1460 | + this._setStage(TorConnectStage.Bootstrapped);
|
|
| 1461 | + Services.obs.notifyObservers(null, TorConnectTopics.BootstrapComplete);
|
|
| 1462 | + return;
|
|
| 1463 | + }
|
|
| 1464 | + |
|
| 1465 | + if (requestedStage) {
|
|
| 1466 | + lazy.logger.debug("Ignoring bootstrap result", result, error);
|
|
| 1467 | + this._setStage(requestedStage);
|
|
| 1468 | + return;
|
|
| 1469 | + }
|
|
| 1470 | + |
|
| 1471 | + if (
|
|
| 1472 | + result === "offline" &&
|
|
| 1473 | + (beginStage === TorConnectStage.Start ||
|
|
| 1474 | + beginStage === TorConnectStage.Offline)
|
|
| 1475 | + ) {
|
|
| 1476 | + this._tryAgain = true;
|
|
| 1477 | + this._signalError(new TorConnectError(TorConnectError.Offline));
|
|
| 1478 | + |
|
| 1479 | + this._setStage(TorConnectStage.Offline);
|
|
| 1480 | + return;
|
|
| 1481 | + }
|
|
| 1482 | + |
|
| 1483 | + if (error) {
|
|
| 1484 | + lazy.logger.info("Bootstrap attempt error", error);
|
|
| 1485 | + |
|
| 1486 | + this._tryAgain = true;
|
|
| 1487 | + this._potentiallyBlocked = true;
|
|
| 1488 | + |
|
| 1489 | + this._signalError(error);
|
|
| 1490 | + |
|
| 1491 | + switch (beginStage) {
|
|
| 1492 | + case TorConnectStage.Start:
|
|
| 1493 | + case TorConnectStage.Offline:
|
|
| 1494 | + this._setStage(TorConnectStage.ChooseRegion);
|
|
| 1495 | + return;
|
|
| 1496 | + case TorConnectStage.ChooseRegion:
|
|
| 1497 | + // TODO: Uncomment for behaviour in tor-browser#42550.
|
|
| 1498 | + /*
|
|
| 1499 | + if (regionCode !== "automatic") {
|
|
| 1500 | + // Not automatic. Go straight to the final error.
|
|
| 1501 | + this._setStage(TorConnectStage.FinalError);
|
|
| 1502 | + return;
|
|
| 1503 | + }
|
|
| 1504 | + */
|
|
| 1505 | + if (regionCode !== "automatic" || bootstrapAttempt.detectedRegion) {
|
|
| 1506 | + this._setStage(TorConnectStage.ConfirmRegion);
|
|
| 1507 | + return;
|
|
| 1508 | + }
|
|
| 1509 | + this._setStage(TorConnectStage.RegionNotFound);
|
|
| 1510 | + return;
|
|
| 1511 | + }
|
|
| 1512 | + this._setStage(TorConnectStage.FinalError);
|
|
| 1513 | + return;
|
|
| 1514 | + }
|
|
| 1515 | + |
|
| 1516 | + // Bootstrap was cancelled.
|
|
| 1517 | + if (result !== "cancelled") {
|
|
| 1518 | + lazy.logger.error(`Unexpected bootstrap result`, result);
|
|
| 1519 | + }
|
|
| 1520 | + |
|
| 1521 | + // TODO: Remove this Offline hack when pages use "stage".
|
|
| 1522 | + if (beginStage === TorConnectStage.Offline) {
|
|
| 1523 | + // Re-send the "Offline" error to push the pages back to "Offline".
|
|
| 1524 | + this._signalError(new TorConnectError(TorConnectError.Offline));
|
|
| 1525 | + }
|
|
| 1526 | + |
|
| 1527 | + // Return to the previous stage.
|
|
| 1528 | + this._setStage(beginStage);
|
|
| 1153 | 1529 | },
|
| 1154 | 1530 | |
| 1155 | - /*
|
|
| 1156 | - These functions allow external consumers to tell TorConnect to transition states
|
|
| 1531 | + /**
|
|
| 1532 | + * Cancel an ongoing bootstrap attempt.
|
|
| 1157 | 1533 | */
|
| 1534 | + cancelBootstrapping() {
|
|
| 1535 | + lazy.logger.debug("TorConnect.cancelBootstrapping()");
|
|
| 1536 | + |
|
| 1537 | + this._ensureEnabled();
|
|
| 1538 | + |
|
| 1539 | + if (!this._bootstrapAttempt) {
|
|
| 1540 | + lazy.logger.warn("No bootstrap attempt to cancel");
|
|
| 1541 | + return;
|
|
| 1542 | + }
|
|
| 1158 | 1543 | |
| 1159 | - beginBootstrap() {
|
|
| 1160 | - lazy.logger.debug("TorConnect.beginBootstrap()");
|
|
| 1161 | - this._changeState(TorConnectState.Bootstrapping);
|
|
| 1544 | + this._bootstrapAttempt.cancel();
|
|
| 1162 | 1545 | },
|
| 1163 | 1546 | |
| 1164 | - cancelBootstrap() {
|
|
| 1165 | - lazy.logger.debug("TorConnect.cancelBootstrap()");
|
|
| 1547 | + /**
|
|
| 1548 | + * Request the transition to the given stage.
|
|
| 1549 | + *
|
|
| 1550 | + * If we are bootstrapping, it will be cancelled and the stage will be
|
|
| 1551 | + * transitioned to when it resolves. Otherwise, we will switch to the stage
|
|
| 1552 | + * immediately.
|
|
| 1553 | + *
|
|
| 1554 | + * @param {string} stage - The stage to request.
|
|
| 1555 | + * @param {boolean} [overideBootstrapped=false] - Whether the request can
|
|
| 1556 | + * override the "Bootstrapped" stage.
|
|
| 1557 | + */
|
|
| 1558 | + _makeStageRequest(stage, overrideBootstrapped = false) {
|
|
| 1559 | + lazy.logger.debug(`Request for stage ${stage}`);
|
|
| 1560 | + |
|
| 1561 | + this._ensureEnabled();
|
|
| 1562 | + |
|
| 1563 | + if (stage === this._stageName) {
|
|
| 1564 | + lazy.logger.info(`Ignoring request for current stage ${stage}`);
|
|
| 1565 | + return;
|
|
| 1566 | + }
|
|
| 1166 | 1567 | if (
|
| 1167 | - this.state !== TorConnectState.AutoBootstrapping &&
|
|
| 1168 | - this.state !== TorConnectState.Bootstrapping
|
|
| 1568 | + !overrideBootstrapped &&
|
|
| 1569 | + this._stageName === TorConnectStage.Bootstrapped
|
|
| 1169 | 1570 | ) {
|
| 1571 | + lazy.logger.warn(`Cannot move to ${stage} when bootstrapped`);
|
|
| 1572 | + return;
|
|
| 1573 | + }
|
|
| 1574 | + if (this._stageName === TorConnectStage.Loading) {
|
|
| 1575 | + if (stage === TorConnectStage.Start) {
|
|
| 1576 | + // Will transition to "Start" stage when loading completes.
|
|
| 1577 | + lazy.logger.info("Still in the Loading stage");
|
|
| 1578 | + } else {
|
|
| 1579 | + lazy.logger.warn(`Cannot move to ${stage} when Loading`);
|
|
| 1580 | + }
|
|
| 1581 | + return;
|
|
| 1582 | + }
|
|
| 1583 | + |
|
| 1584 | + if (!this._bootstrapAttempt) {
|
|
| 1585 | + // Transition immediately.
|
|
| 1586 | + this._setStage(stage);
|
|
| 1587 | + return;
|
|
| 1588 | + }
|
|
| 1589 | + |
|
| 1590 | + if (this._requestedStage === stage) {
|
|
| 1591 | + lazy.logger.info(`Already requesting stage ${stage}`);
|
|
| 1592 | + return;
|
|
| 1593 | + }
|
|
| 1594 | + if (this._requestedStage) {
|
|
| 1170 | 1595 | lazy.logger.warn(
|
| 1171 | - `Cannot cancel bootstrapping in the ${this.state} state`
|
|
| 1596 | + `Overriding request for ${this._requestedStage} with ${stage}`
|
|
| 1172 | 1597 | );
|
| 1173 | - return;
|
|
| 1174 | 1598 | }
|
| 1175 | - this._changeState(TorConnectState.Configuring);
|
|
| 1599 | + // Move to stage *after* bootstrap completes.
|
|
| 1600 | + this._requestedStage = stage;
|
|
| 1601 | + this._bootstrapAttempt?.cancel();
|
|
| 1176 | 1602 | },
|
| 1177 | 1603 | |
| 1178 | - beginAutoBootstrap(countryCode) {
|
|
| 1179 | - lazy.logger.debug("TorConnect.beginAutoBootstrap()");
|
|
| 1180 | - this._changeState(TorConnectState.AutoBootstrapping, countryCode);
|
|
| 1604 | + /**
|
|
| 1605 | + * Restart the TorConnect stage to the start.
|
|
| 1606 | + */
|
|
| 1607 | + startAgain() {
|
|
| 1608 | + this._makeStageRequest(TorConnectStage.Start);
|
|
| 1181 | 1609 | },
|
| 1182 | 1610 | |
| 1183 | - /*
|
|
| 1184 | - Further external commands and helper methods
|
|
| 1611 | + /**
|
|
| 1612 | + * Set the stage to be "ChooseRegion".
|
|
| 1185 | 1613 | */
|
| 1186 | - openTorPreferences() {
|
|
| 1187 | - if (lazy.TorLauncherUtil.isAndroid) {
|
|
| 1188 | - lazy.EventDispatcher.instance.sendRequest({
|
|
| 1189 | - type: "GeckoView:Tor:OpenSettings",
|
|
| 1190 | - });
|
|
| 1614 | + chooseRegion() {
|
|
| 1615 | + if (!this._potentiallyBlocked) {
|
|
| 1616 | + lazy.logger.error("chooseRegion request before getting an error");
|
|
| 1191 | 1617 | return;
|
| 1192 | 1618 | }
|
| 1193 | - const win = lazy.BrowserWindowTracker.getTopWindow();
|
|
| 1194 | - win.switchToTabHavingURI("about:preferences#connection", true);
|
|
| 1619 | + // NOTE: The ChooseRegion stage needs _errorDetails to be displayed in
|
|
| 1620 | + // about:torconnect. The _potentiallyBlocked condition should be
|
|
| 1621 | + // sufficient to ensure this.
|
|
| 1622 | + this._makeStageRequest(TorConnectStage.ChooseRegion);
|
|
| 1195 | 1623 | },
|
| 1196 | 1624 | |
| 1625 | + /*
|
|
| 1626 | + Further external commands and helper methods
|
|
| 1627 | + */
|
|
| 1628 | + |
|
| 1197 | 1629 | /**
|
| 1198 | 1630 | * Open the "about:torconnect" tab.
|
| 1199 | 1631 | *
|
| ... | ... | @@ -1204,10 +1636,11 @@ export const TorConnect = { |
| 1204 | 1636 | * potentially blocked.
|
| 1205 | 1637 | *
|
| 1206 | 1638 | * @param {object} [options] - extra options.
|
| 1207 | - * @property {boolean} [options.beginBootstrap=false] - Whether to try and
|
|
| 1208 | - * begin Bootstrapping.
|
|
| 1209 | - * @property {string} [options.beginAutoBootstrap] - The location to use to
|
|
| 1210 | - * begin AutoBootstrapping, if possible.
|
|
| 1639 | + * @property {"soft"|"hard"} [options.beginBootstrapping] - Whether to try and
|
|
| 1640 | + * begin bootstrapping. "soft" will only trigger the bootstrap if we are not
|
|
| 1641 | + * `potentiallyBlocked`. "hard" will try begin the bootstrap regardless.
|
|
| 1642 | + * @property {string} [options.regionCode] - A region to pass in for
|
|
| 1643 | + * auto-bootstrapping.
|
|
| 1211 | 1644 | */
|
| 1212 | 1645 | openTorConnect(options) {
|
| 1213 | 1646 | // FIXME: Should we move this to the about:torconnect actor?
|
| ... | ... | @@ -1215,25 +1648,23 @@ export const TorConnect = { |
| 1215 | 1648 | win.switchToTabHavingURI("about:torconnect", true, {
|
| 1216 | 1649 | ignoreQueryString: true,
|
| 1217 | 1650 | });
|
| 1218 | - if (
|
|
| 1219 | - options?.beginBootstrap &&
|
|
| 1220 | - this.canBeginBootstrap &&
|
|
| 1221 | - !this.potentiallyBlocked
|
|
| 1222 | - ) {
|
|
| 1223 | - this.beginBootstrap();
|
|
| 1651 | + |
|
| 1652 | + if (!options?.beginBootstrapping || !this.canBeginBootstrap) {
|
|
| 1653 | + return;
|
|
| 1224 | 1654 | }
|
| 1225 | - // options.beginAutoBootstrap can be an empty string.
|
|
| 1226 | - if (
|
|
| 1227 | - options?.beginAutoBootstrap !== undefined &&
|
|
| 1228 | - this.canBeginAutoBootstrap
|
|
| 1229 | - ) {
|
|
| 1230 | - this.beginAutoBootstrap(options.beginAutoBootstrap);
|
|
| 1655 | + |
|
| 1656 | + if (options.beginBootstrapping === "hard") {
|
|
| 1657 | + if (this.canBeginAutoBootstrap && !options.regionCode) {
|
|
| 1658 | + // Treat as an addition startAgain request to first move back to the
|
|
| 1659 | + // "Start" stage before bootstrapping.
|
|
| 1660 | + this.startAgain();
|
|
| 1661 | + }
|
|
| 1662 | + } else if (this.potentiallyBlocked) {
|
|
| 1663 | + // Do not trigger the bootstrap if we have ever had an error.
|
|
| 1664 | + return;
|
|
| 1231 | 1665 | }
|
| 1232 | - },
|
|
| 1233 | 1666 | |
| 1234 | - viewTorLogs() {
|
|
| 1235 | - const win = lazy.BrowserWindowTracker.getTopWindow();
|
|
| 1236 | - win.switchToTabHavingURI("about:preferences#connection-viewlogs", true);
|
|
| 1667 | + this.beginBootstrapping(options.regionCode);
|
|
| 1237 | 1668 | },
|
| 1238 | 1669 | |
| 1239 | 1670 | async getCountryCodes() {
|