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() {
|