Dan Ballard pushed to branch tor-browser-140.1.0esr-15.0-1 at The Tor Project / Applications / Tor Browser
Commits:
- 
4fba4d54
by Dan Ballard at 2025-08-04T23:24:21-05:00
- 
a04605ca
by Dan Ballard at 2025-08-04T23:24:21-05:00
- 
c563c43e
by Dan Ballard at 2025-08-04T23:24:22-05:00
- 
e9ac372c
by Dan Ballard at 2025-08-04T23:30:59-05:00
- 
579ade4c
by Dan Ballard at 2025-08-04T23:31:02-05:00
13 changed files:
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/TorBrowserFeatures.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/TorBridgeConfigFragment.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorBootstrapProgressViewModel.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistViewModel.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorController.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerGV.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorLogsViewModel.kt
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorAndroidIntegration.java
- + mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorConnectError.java
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorConnectStage.java
- toolkit/modules/TorAndroidIntegration.sys.mjs
Changes:
| ... | ... | @@ -101,7 +101,6 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingMode | 
| 101 | 101 |  import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
 | 
| 102 | 102 |  import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager
 | 
| 103 | 103 |  import org.mozilla.fenix.components.appstate.AppAction
 | 
| 104 | -import org.mozilla.fenix.components.appstate.AppAction.ShareAction
 | |
| 105 | 104 |  import org.mozilla.fenix.components.appstate.OrientationMode
 | 
| 106 | 105 |  import org.mozilla.fenix.components.metrics.BreadcrumbsRecorder
 | 
| 107 | 106 |  import org.mozilla.fenix.components.metrics.GrowthDataWorker
 | 
| ... | ... | @@ -137,7 +136,6 @@ import org.mozilla.fenix.home.intent.OpenBrowserIntentProcessor | 
| 137 | 136 |  import org.mozilla.fenix.home.intent.OpenPasswordManagerIntentProcessor
 | 
| 138 | 137 |  import org.mozilla.fenix.home.intent.OpenRecentlyClosedIntentProcessor
 | 
| 139 | 138 |  import org.mozilla.fenix.home.intent.OpenSpecificTabIntentProcessor
 | 
| 140 | -import org.mozilla.fenix.home.intent.ReEngagementIntentProcessor
 | |
| 141 | 139 |  import org.mozilla.fenix.home.intent.SpeechProcessingIntentProcessor
 | 
| 142 | 140 |  import org.mozilla.fenix.home.intent.StartSearchIntentProcessor
 | 
| 143 | 141 |  import org.mozilla.fenix.library.bookmarks.DesktopFolders
 | 
| ... | ... | @@ -167,19 +165,18 @@ import org.mozilla.fenix.theme.DefaultThemeManager | 
| 167 | 165 |  import org.mozilla.fenix.theme.StatusBarColorManager
 | 
| 168 | 166 |  import org.mozilla.fenix.theme.ThemeManager
 | 
| 169 | 167 |  import org.mozilla.fenix.tor.TorConnectionAssistFragmentDirections
 | 
| 170 | -import org.mozilla.fenix.tor.TorEvents
 | |
| 171 | 168 |  import org.mozilla.fenix.utils.Settings
 | 
| 172 | 169 |  import org.mozilla.fenix.utils.changeAppLauncherIcon
 | 
| 173 | 170 |  import java.lang.ref.WeakReference
 | 
| 174 | 171 |  import java.util.Locale
 | 
| 175 | 172 | |
| 176 | -import androidx.compose.material.SnackbarDuration
 | |
| 177 | 173 |  import mozilla.components.browser.engine.gecko.GeckoEngine
 | 
| 178 | 174 |  import org.mozilla.fenix.compose.core.Action
 | 
| 179 | 175 |  import org.mozilla.fenix.compose.snackbar.SnackbarState
 | 
| 180 | 176 |  import org.mozilla.fenix.compose.snackbar.Snackbar
 | 
| 181 | 177 |  import org.mozilla.fenix.tor.UrlQuickLoadViewModel
 | 
| 182 | 178 |  import org.mozilla.geckoview.TorAndroidIntegration
 | 
| 179 | +import org.mozilla.geckoview.TorAndroidIntegration.BootstrapStateChangeListener
 | |
| 183 | 180 |  import org.mozilla.geckoview.TorConnectStage
 | 
| 184 | 181 |  import kotlin.system.exitProcess
 | 
| 185 | 182 | |
| ... | ... | @@ -915,19 +912,25 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, TorAn | 
| 915 | 912 |       */
 | 
| 916 | 913 |      @SuppressLint("MissingSuperCall") // super.onNewIntent is called in [onNewIntentInternal(intent)]
 | 
| 917 | 914 |      final override fun onNewIntent(intent: Intent) {
 | 
| 918 | -        if (intent.action == ACTION_MAIN || components.torController.isConnected) {
 | |
| 915 | +        if (intent.action == ACTION_MAIN || components.torController.isBootstrapped) {
 | |
| 919 | 916 |              onNewIntentInternal(intent)
 | 
| 920 | 917 |          } else {
 | 
| 921 | 918 |              // Wait until Tor is connected to handle intents from external apps for links, search, etc.
 | 
| 922 | -            components.torController.registerTorListener(object : TorEvents {
 | |
| 923 | -                override fun onTorConnected() {
 | |
| 924 | -                    components.torController.unregisterTorListener(this)
 | |
| 925 | -                    onNewIntentInternal(intent)
 | |
| 919 | +            val torIntegration = (components.core.engine as GeckoEngine).getTorIntegrationController()
 | |
| 920 | +            torIntegration.registerBootstrapStateChangeListener(
 | |
| 921 | +                object : BootstrapStateChangeListener {
 | |
| 922 | + | |
| 923 | +                    override fun onBootstrapStageChange(stage: TorConnectStage) {
 | |
| 924 | +                        if (stage.isBootstrapped) {
 | |
| 925 | +                            torIntegration.unregisterBootstrapStateChangeListener(this)
 | |
| 926 | +                            onNewIntentInternal(intent)
 | |
| 927 | +                        }
 | |
| 928 | +                    }
 | |
| 929 | + | |
| 930 | +                    override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) {}
 | |
| 926 | 931 |                  }
 | 
| 927 | -                override fun onTorConnecting() { /* no-op */ }
 | |
| 928 | -                override fun onTorStopped() { /* no-op */ }
 | |
| 929 | -                override fun onTorStatusUpdate(entry: String?, status: String?, progress: Double?) { /* no-op */ }
 | |
| 930 | -            })
 | |
| 932 | +            )
 | |
| 933 | + | |
| 931 | 934 |              return
 | 
| 932 | 935 |          }
 | 
| 933 | 936 |      }
 | 
| ... | ... | @@ -1516,7 +1519,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, TorAn | 
| 1516 | 1519 |              // we want to ignore other cases when the app gets open by users clicking on links,
 | 
| 1517 | 1520 |              // unless Tor is not yet connected.
 | 
| 1518 | 1521 |              getSettings().shouldStartOnHome() && (intent?.action == ACTION_MAIN ||
 | 
| 1519 | -                    !components.torController.isConnected)
 | |
| 1522 | +                    !components.torController.isBootstrapped)
 | |
| 1520 | 1523 |          }
 | 
| 1521 | 1524 |      }
 | 
| 1522 | 1525 | |
| ... | ... | @@ -1606,14 +1609,13 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, TorAn | 
| 1606 | 1609 |          exitProcess(0)
 | 
| 1607 | 1610 |      }
 | 
| 1608 | 1611 | |
| 1609 | -    override fun onBootstrapStateChange(state: String) = Unit
 | |
| 1610 | -    override fun onBootstrapStageChange(stage: TorConnectStage) = Unit
 | |
| 1611 | -    override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) = Unit
 | |
| 1612 | -    override fun onBootstrapComplete() {
 | |
| 1613 | -        if (settings().useHtmlConnectionUi) {
 | |
| 1614 | -            components.useCases.tabsUseCases.removeAllTabs()
 | |
| 1615 | -            navHost.navController.navigate(NavGraphDirections.actionStartupHome())
 | |
| 1612 | +    override fun onBootstrapStageChange(stage: TorConnectStage)  {
 | |
| 1613 | +        if (stage.isBootstrapped) {
 | |
| 1614 | +            if (settings().useHtmlConnectionUi) {
 | |
| 1615 | +                components.useCases.tabsUseCases.removeAllTabs()
 | |
| 1616 | +                navHost.navController.navigate(NavGraphDirections.actionStartupHome())
 | |
| 1617 | +            }
 | |
| 1616 | 1618 |          }
 | 
| 1617 | 1619 |      }
 | 
| 1618 | -    override fun onBootstrapError(code: String?, message: String?, phase: String?, reason: String?) = Unit
 | |
| 1620 | +    override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) = Unit
 | |
| 1619 | 1621 |  } | 
| ... | ... | @@ -13,6 +13,7 @@ import android.os.Build | 
| 13 | 13 |  import android.os.Bundle
 | 
| 14 | 14 |  import android.os.StrictMode
 | 
| 15 | 15 |  import androidx.annotation.VisibleForTesting
 | 
| 16 | +import mozilla.components.browser.engine.gecko.GeckoEngine
 | |
| 16 | 17 |  import mozilla.components.feature.intent.ext.sanitize
 | 
| 17 | 18 |  import mozilla.components.feature.intent.processing.IntentProcessor
 | 
| 18 | 19 |  import mozilla.components.support.base.log.logger.Logger
 | 
| ... | ... | @@ -31,7 +32,8 @@ import org.mozilla.fenix.ext.settings | 
| 31 | 32 |  import org.mozilla.fenix.perf.MarkersActivityLifecycleCallbacks
 | 
| 32 | 33 |  import org.mozilla.fenix.perf.StartupTimeline
 | 
| 33 | 34 |  import org.mozilla.fenix.shortcut.NewTabShortcutIntentProcessor
 | 
| 34 | -import org.mozilla.fenix.tor.TorEvents
 | |
| 35 | +import org.mozilla.geckoview.TorAndroidIntegration.BootstrapStateChangeListener
 | |
| 36 | +import org.mozilla.geckoview.TorConnectStage
 | |
| 35 | 37 | |
| 36 | 38 |  /**
 | 
| 37 | 39 |   * Processes incoming intents and sends them to the corresponding activity.
 | 
| ... | ... | @@ -55,19 +57,24 @@ class IntentReceiverActivity : Activity() { | 
| 55 | 57 |          // the HomeActivity.
 | 
| 56 | 58 |          val intent = intent?.let { Intent(it) } ?: Intent()
 | 
| 57 | 59 |          intent.sanitize().stripUnwantedFlags()
 | 
| 58 | -        if (intent.action == ACTION_MAIN || components.torController.isConnected) {
 | |
| 60 | +        if (intent.action == ACTION_MAIN || components.torController.isBootstrapped) {
 | |
| 59 | 61 |              processIntent(intent)
 | 
| 60 | 62 |          } else {
 | 
| 61 | 63 |              // Wait until Tor is connected to handle intents from external apps for links, search, etc.
 | 
| 62 | -            components.torController.registerTorListener(object : TorEvents {
 | |
| 63 | -                override fun onTorConnected() {
 | |
| 64 | -                    components.torController.unregisterTorListener(this)
 | |
| 65 | -                    processIntent(intent)
 | |
| 66 | -                }
 | |
| 67 | -                override fun onTorConnecting() { /* no-op */ }
 | |
| 68 | -                override fun onTorStopped() { /* no-op */ }
 | |
| 69 | -                override fun onTorStatusUpdate(entry: String?, status: String?, progress: Double?) { /* no-op */ }
 | |
| 70 | -            })
 | |
| 64 | +            val engine = components.core.engine as GeckoEngine
 | |
| 65 | +            engine.getTorIntegrationController().registerBootstrapStateChangeListener(
 | |
| 66 | +                object : BootstrapStateChangeListener {
 | |
| 67 | + | |
| 68 | +                    override fun onBootstrapStageChange(stage: TorConnectStage) {
 | |
| 69 | +                        if (stage.isBootstrapped) {
 | |
| 70 | +                            engine.getTorIntegrationController().unregisterBootstrapStateChangeListener(this)
 | |
| 71 | +                            processIntent(intent)
 | |
| 72 | +                        }
 | |
| 73 | +                    }
 | |
| 74 | + | |
| 75 | +                    override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) {}
 | |
| 76 | +                })
 | |
| 77 | + | |
| 71 | 78 | |
| 72 | 79 |              // In the meantime, open the HomeActivity so the user can get connected.
 | 
| 73 | 80 |              processIntent(Intent())
 | 
| ... | ... | @@ -20,7 +20,7 @@ import mozilla.components.support.webextensions.WebExtensionSupport | 
| 20 | 20 |  import mozilla.components.support.base.log.logger.Logger
 | 
| 21 | 21 |  import org.mozilla.fenix.ext.components
 | 
| 22 | 22 |  import org.mozilla.fenix.ext.settings
 | 
| 23 | -import org.mozilla.fenix.tor.TorEvents
 | |
| 23 | +import org.mozilla.fenix.tor.RunOnceBootstrapped
 | |
| 24 | 24 | |
| 25 | 25 |  object TorBrowserFeatures {
 | 
| 26 | 26 |      private val logger = Logger("torbrowser-features")
 | 
| ... | ... | @@ -142,9 +142,8 @@ object TorBrowserFeatures { | 
| 142 | 142 |           *  causing automatic update checks failures (components.addonUpdater being a lazy prop).
 | 
| 143 | 143 |           *  The extension, from then on, should behave as if the user had installed it manually.
 | 
| 144 | 144 |           */
 | 
| 145 | -        context.components.torController.registerTorListener(object : TorEvents {
 | |
| 146 | -            override fun onTorConnected() {
 | |
| 147 | -                context.components.torController.unregisterTorListener(this)
 | |
| 145 | +        context.components.torController.registerRunOnceBootstrapped(object : RunOnceBootstrapped {
 | |
| 146 | +            override fun onBootstrapped() {
 | |
| 148 | 147 |                  // Enable automatic updates. This must be done on every startup (tor-browser#42353)
 | 
| 149 | 148 |                  context.components.addonUpdater.registerForFutureUpdates(NOSCRIPT_ID)
 | 
| 150 | 149 |                  // Force a one-time immediate update check for older installations
 | 
| ... | ... | @@ -153,18 +152,6 @@ object TorBrowserFeatures { | 
| 153 | 152 |                      settings.noscriptUpdated = 2
 | 
| 154 | 153 |                  }
 | 
| 155 | 154 |              }
 | 
| 156 | - | |
| 157 | -            @SuppressWarnings("EmptyFunctionBlock")
 | |
| 158 | -            override fun onTorConnecting() {
 | |
| 159 | -            }
 | |
| 160 | - | |
| 161 | -            @SuppressWarnings("EmptyFunctionBlock")
 | |
| 162 | -            override fun onTorStopped() {
 | |
| 163 | -            }
 | |
| 164 | - | |
| 165 | -            @SuppressWarnings("EmptyFunctionBlock")
 | |
| 166 | -            override fun onTorStatusUpdate(entry: String?, status: String?, progress: Double?) {
 | |
| 167 | -            }
 | |
| 168 | 155 |          })
 | 
| 169 | 156 |      }
 | 
| 170 | 157 | 
| ... | ... | @@ -56,7 +56,6 @@ class TorBridgeConfigFragment : PreferenceFragmentCompat() { | 
| 56 | 56 |              setOnPreferenceChangeListener<Boolean> { preference, enabled ->
 | 
| 57 | 57 |                  preference.context.components.torController.bridgesEnabled = enabled
 | 
| 58 | 58 |                  updateCurrentConfiguredBridgePref(preference)
 | 
| 59 | -                preference.context.components.torController.restartTor()
 | |
| 60 | 59 |                  true
 | 
| 61 | 60 |              }
 | 
| 62 | 61 |          }
 | 
| ... | ... | @@ -71,7 +70,6 @@ class TorBridgeConfigFragment : PreferenceFragmentCompat() { | 
| 71 | 70 |                  preference.context.components.torController.bridgeTransport = TorBridgeTransportConfig.USER_PROVIDED
 | 
| 72 | 71 |                  preference.context.components.torController.userProvidedBridges = userProvidedBridge
 | 
| 73 | 72 |                  updateCurrentConfiguredBridgePref(preference)
 | 
| 74 | -                preference.context.components.torController.restartTor()
 | |
| 75 | 73 |                  true
 | 
| 76 | 74 |              }
 | 
| 77 | 75 |              val userProvidedBridge: String? = context.components.torController.userProvidedBridges
 | 
| ... | ... | @@ -103,7 +101,6 @@ class TorBridgeConfigFragment : PreferenceFragmentCompat() { | 
| 103 | 101 |                      preference.context.components.torController.bridgeTransport = bridge
 | 
| 104 | 102 |                      previousTransportConfig = bridge
 | 
| 105 | 103 |                      updateCurrentConfiguredBridgePref(preference)
 | 
| 106 | -                    preference.context.components.torController.restartTor()
 | |
| 107 | 104 |                  }
 | 
| 108 | 105 |                  true
 | 
| 109 | 106 |              }
 | 
| ... | ... | @@ -27,20 +27,9 @@ class TorBootstrapProgressViewModel( | 
| 27 | 27 |          super.onCleared()
 | 
| 28 | 28 |      }
 | 
| 29 | 29 | |
| 30 | -    override fun onBootstrapStateChange(state: String?) {}
 | |
| 31 | 30 |      override fun onBootstrapStageChange(stage: TorConnectStage) = Unit
 | 
| 32 | 31 | |
| 33 | 32 |      override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) {
 | 
| 34 | 33 |          this.progress.value = progress.toInt()
 | 
| 35 | 34 |      }
 | 
| 36 | - | |
| 37 | -    override fun onBootstrapComplete() {}
 | |
| 38 | - | |
| 39 | -    override fun onBootstrapError(
 | |
| 40 | -        code: String?,
 | |
| 41 | -        message: String?,
 | |
| 42 | -        phase: String?,
 | |
| 43 | -        reason: String?,
 | |
| 44 | -    ) {
 | |
| 45 | -    }
 | |
| 46 | 35 |  } | 
| ... | ... | @@ -154,23 +154,12 @@ class TorConnectionAssistViewModel( | 
| 154 | 154 |          }
 | 
| 155 | 155 |      }
 | 
| 156 | 156 | |
| 157 | -    override fun onBootstrapStateChange(state: String?) {}
 | |
| 158 | - | |
| 159 | -    override fun onBootstrapStageChange(stage: TorConnectStage?) {
 | |
| 157 | +    override fun onBootstrapStageChange(stage: TorConnectStage) {
 | |
| 160 | 158 |          torConnectStage.value = stage
 | 
| 161 | 159 |      }
 | 
| 162 | 160 | |
| 163 | 161 |      override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) {}
 | 
| 164 | 162 | |
| 165 | -    override fun onBootstrapComplete() {}
 | |
| 166 | - | |
| 167 | -    override fun onBootstrapError(
 | |
| 168 | -        code: String?,
 | |
| 169 | -        message: String?,
 | |
| 170 | -        phase: String?,
 | |
| 171 | -        reason: String?,
 | |
| 172 | -    ) {}
 | |
| 173 | - | |
| 174 | 163 |      fun button1ShouldBeDisabled(screen: ConnectAssistUiState): Boolean {
 | 
| 175 | 164 |          return selectedCountryCode.value == "automatic" && screen.regionDropDownDefaultItem == R.string.connection_assist_select_country_or_region
 | 
| 176 | 165 |      }
 | 
| ... | ... | @@ -6,37 +6,14 @@ package org.mozilla.fenix.tor | 
| 6 | 6 | |
| 7 | 7 |  import androidx.lifecycle.LifecycleCoroutineScope
 | 
| 8 | 8 | |
| 9 | -interface TorEvents {
 | |
| 10 | -    fun onTorConnecting()
 | |
| 11 | -    fun onTorConnected()
 | |
| 12 | -    fun onTorStatusUpdate(entry: String?, status: String?, progress: Double? = 0.0)
 | |
| 13 | -    fun onTorStopped()
 | |
| 9 | +// Callback for function to be run one time when the system is bootstrapped and then disregarded
 | |
| 10 | +interface RunOnceBootstrapped {
 | |
| 11 | +    fun onBootstrapped()
 | |
| 14 | 12 |  }
 | 
| 15 | -class TorError(
 | |
| 16 | -    var message: String,
 | |
| 17 | -    var details: String,
 | |
| 18 | -    var phase: String,
 | |
| 19 | -    var reason: String,
 | |
| 20 | -) { }
 | |
| 21 | 13 | |
| 22 | -interface TorLogs {
 | |
| 23 | -    fun onLog(type: String?, message: String?, timestamp: String?)
 | |
| 24 | -}
 | |
| 25 | - | |
| 26 | -internal enum class TorStatus(val status: String) {
 | |
| 27 | -    OFF("OFF"),
 | |
| 28 | -    STARTING("STARTING"),
 | |
| 29 | -    ON("ON"),
 | |
| 30 | -    STOPPING("STOPPING"),
 | |
| 31 | -    UNKNOWN("UNKNOWN");
 | |
| 32 | -}
 | |
| 33 | - | |
| 34 | -interface TorController: TorEvents {
 | |
| 14 | +interface TorController {
 | |
| 35 | 15 |      val logEntries: MutableList<TorLog>
 | 
| 36 | -    val isStarting: Boolean
 | |
| 37 | -    val isRestarting: Boolean
 | |
| 38 | 16 |      val isBootstrapped: Boolean
 | 
| 39 | -    val isConnected: Boolean
 | |
| 40 | 17 |      var bridgesEnabled: Boolean
 | 
| 41 | 18 |      var bridgeTransport: TorBridgeTransportConfig
 | 
| 42 | 19 |      var userProvidedBridges: String?
 | 
| ... | ... | @@ -44,21 +21,21 @@ interface TorController: TorEvents { | 
| 44 | 21 |      fun start()
 | 
| 45 | 22 |      fun stop()
 | 
| 46 | 23 | |
| 47 | -    override fun onTorConnecting()
 | |
| 48 | -    override fun onTorConnected()
 | |
| 49 | -    override fun onTorStatusUpdate(entry: String?, status: String?, progress: Double?)
 | |
| 50 | -    override fun onTorStopped()
 | |
| 51 | - | |
| 52 | -    fun getLastErrorState() : TorError?
 | |
| 53 | - | |
| 54 | -    fun registerTorListener(l: TorEvents)
 | |
| 55 | -    fun unregisterTorListener(l: TorEvents)
 | |
| 56 | - | |
| 57 | -    fun registerTorLogListener(l: TorLogs)
 | |
| 58 | -    fun unregisterTorLogListener(l: TorLogs)
 | |
| 24 | +    // TorBrowserFeatures.install wants to register a callback for when tor bootstraps the first time
 | |
| 25 | +    // so it can then check for noscript updates.
 | |
| 26 | +    // Currently it needs to register it before TorAndroidIntegration is fully loaded, so this way
 | |
| 27 | +    // they can register with TorController which will start streaming events from TAS when available
 | |
| 28 | +    // and call them one time when the system is bootstrapped
 | |
| 29 | +    // TODO: rewire the noscript update call in TorBrowserFeatures.install
 | |
| 30 | +    //   a) call TorBrowserFeatures.install from somewhere else (ex: move from Core.GeckoEngine.also
 | |
| 31 | +    //      to maybe FenixApplication.setupInMainProcessOnly
 | |
| 32 | +    //      dan: had trouble with this first time:
 | |
| 33 | +    //      https://gitlab.torproject.org/tpo/applications/tor-browser/-/merge_requests/1423#note_3191590
 | |
| 34 | +    //   b) just move the call to `context.components.addonUpdater.update(NOSCRIPT_ID)` somewhere else
 | |
| 35 | +    //      that can use TorAndroidIntegration.BootstrapListener
 | |
| 36 | +    fun registerRunOnceBootstrapped(rob: RunOnceBootstrapped)
 | |
| 37 | +    fun unregisterRunOnceBootstrapped(rob: RunOnceBootstrapped)
 | |
| 59 | 38 | |
| 60 | 39 |      fun initiateTorBootstrap(lifecycleScope: LifecycleCoroutineScope? = null, withDebugLogging: Boolean = false)
 | 
| 61 | 40 |      fun stopTor()
 | 
| 62 | -    fun setTorStopped()
 | |
| 63 | -    fun restartTor()
 | |
| 64 | 41 |  } | 
| ... | ... | @@ -4,75 +4,30 @@ package org.mozilla.fenix.tor | 
| 4 | 4 |  import android.content.Context
 | 
| 5 | 5 |  import android.util.Log
 | 
| 6 | 6 |  import androidx.lifecycle.LifecycleCoroutineScope
 | 
| 7 | -import kotlinx.coroutines.flow.MutableStateFlow
 | |
| 8 | -import kotlinx.coroutines.flow.StateFlow
 | |
| 9 | 7 |  import mozilla.components.browser.engine.gecko.GeckoEngine
 | 
| 10 | 8 |  import org.mozilla.fenix.ext.components
 | 
| 11 | 9 |  import org.mozilla.geckoview.TorAndroidIntegration
 | 
| 12 | 10 |  import org.mozilla.geckoview.TorAndroidIntegration.BootstrapStateChangeListener
 | 
| 13 | 11 |  import org.mozilla.geckoview.TorAndroidIntegration.TorLogListener
 | 
| 14 | 12 |  import org.mozilla.geckoview.TorConnectStage
 | 
| 13 | +import org.mozilla.geckoview.TorConnectStageName
 | |
| 15 | 14 |  import org.mozilla.geckoview.TorSettings
 | 
| 16 | 15 |  import org.mozilla.geckoview.TorSettings.BridgeBuiltinType
 | 
| 17 | 16 |  import org.mozilla.geckoview.TorSettings.BridgeSource
 | 
| 18 | 17 | |
| 19 | -// Enum matching TorConnectState from TorConnect.sys.mjs that we get from onBootstrapStateChange
 | |
| 20 | -internal enum class TorConnectState(val state: String) {
 | |
| 21 | -    Initial("Initial"),
 | |
| 22 | -    Configuring("Configuring"),
 | |
| 23 | -    AutoBootstrapping("AutoBootstrapping"),
 | |
| 24 | -    Bootstrapping("Bootstrapping"),
 | |
| 25 | -    Error("Error"),
 | |
| 26 | -    Bootstrapped("Bootstrapped"),
 | |
| 27 | -    Disabled("Disabled");
 | |
| 28 | - | |
| 29 | -    fun isStarting() = this == Bootstrapping || this == AutoBootstrapping
 | |
| 30 | -    fun isError() = this == Error
 | |
| 31 | - | |
| 32 | -    fun isStarted() = this == Bootstrapped
 | |
| 33 | - | |
| 34 | -    fun isOff() = this == Initial || this == Configuring || this == Disabled || this == Error
 | |
| 35 | - | |
| 36 | - | |
| 37 | -    // Convert to TorStatus that firefox-android uses based on tor-android-service
 | |
| 38 | -    fun toTorStatus(): TorStatus {
 | |
| 39 | -        return when (this) {
 | |
| 40 | -            Initial -> TorStatus.OFF
 | |
| 41 | -            Configuring -> TorStatus.OFF
 | |
| 42 | -            AutoBootstrapping -> TorStatus.STARTING
 | |
| 43 | -            Bootstrapping -> TorStatus.STARTING
 | |
| 44 | -            Error -> TorStatus.UNKNOWN
 | |
| 45 | -            Bootstrapped -> TorStatus.ON
 | |
| 46 | -            Disabled -> TorStatus.OFF
 | |
| 47 | -        }
 | |
| 48 | -    }
 | |
| 49 | -}
 | |
| 50 | - | |
| 51 | 18 |  class TorControllerGV(
 | 
| 52 | 19 |      private val context: Context,
 | 
| 53 | -) : TorController, TorEvents, BootstrapStateChangeListener, TorLogListener {
 | |
| 20 | +) : TorController, BootstrapStateChangeListener, TorLogListener {
 | |
| 54 | 21 | |
| 55 | 22 |      private val TAG = "TorControllerGV"
 | 
| 56 | 23 | |
| 57 | -    private var torListeners = mutableListOf<TorEvents>()
 | |
| 58 | -    private var torLogListeners = mutableListOf<TorLogs>()
 | |
| 59 | - | |
| 60 | -    private val _lastKnownStatus = MutableStateFlow(TorConnectState.Initial)
 | |
| 61 | -    internal val lastKnownStatus: StateFlow<TorConnectState> = _lastKnownStatus
 | |
| 24 | +    private var runOnceBootstrappedHandlers = mutableListOf<RunOnceBootstrapped>()
 | |
| 62 | 25 | |
| 63 | -    internal var lastKnownError: TorError? = null
 | |
| 64 | -    private var wasTorBootstrapped = false
 | |
| 65 | -    private var isTorRestarting = false
 | |
| 66 | - | |
| 67 | -    private var isTorBootstrapped = false
 | |
| 68 | -        get() = ((_lastKnownStatus.value.isStarted()) && wasTorBootstrapped)
 | |
| 26 | +    override val isBootstrapped get() =
 | |
| 27 | +        getTorIntegration().lastKnowStage.value?.name?.isBootstrapped ?: false
 | |
| 69 | 28 | |
| 70 | 29 |      private val entries = mutableListOf<TorLog>()
 | 
| 71 | 30 |      override val logEntries get() = entries
 | 
| 72 | -    override val isStarting get() = _lastKnownStatus.value.isStarting()
 | |
| 73 | -    override val isRestarting get() = isTorRestarting
 | |
| 74 | -    override val isBootstrapped get() = isTorBootstrapped
 | |
| 75 | -    override val isConnected get() = (_lastKnownStatus.value.isStarted() && !isTorRestarting)
 | |
| 76 | 31 | |
| 77 | 32 |      private fun getTorIntegration(): TorAndroidIntegration {
 | 
| 78 | 33 |          return (context.components.core.engine as GeckoEngine).getTorIntegrationController()
 | 
| ... | ... | @@ -82,8 +37,7 @@ class TorControllerGV( | 
| 82 | 37 |          return getTorIntegration().getSettings()
 | 
| 83 | 38 |      }
 | 
| 84 | 39 | |
| 85 | - | |
| 86 | -    // On a fresh install bridgeEnagled can be set to true without a valid bridgeSource
 | |
| 40 | +    // On a fresh install bridgeEnabled can be set to true without a valid bridgeSource
 | |
| 87 | 41 |      // having been selected. After first use this will not happen because last selected bridge
 | 
| 88 | 42 |      // will be remembered and reused.
 | 
| 89 | 43 |      // However, on first use, submitting this to TorSettings is an invalid state.
 | 
| ... | ... | @@ -105,7 +59,6 @@ class TorControllerGV( | 
| 105 | 59 |              }
 | 
| 106 | 60 |          }
 | 
| 107 | 61 | |
| 108 | - | |
| 109 | 62 |      override var bridgeTransport: TorBridgeTransportConfig
 | 
| 110 | 63 |          get() {
 | 
| 111 | 64 |              return when (getTorSettings()?.bridgesSource) {
 | 
| ... | ... | @@ -144,7 +97,6 @@ class TorControllerGV( | 
| 144 | 97 |              }
 | 
| 145 | 98 |          }
 | 
| 146 | 99 | |
| 147 | - | |
| 148 | 100 |      // Currently the UI takes a user provided string and sets this in one step so there is where we
 | 
| 149 | 101 |      // actually set it.bridgesSource = BridgeSource.UserProvided, not above,
 | 
| 150 | 102 |      // as TorSettings.sys.mjs #cleanupSettings could reject BridgeSource.UserProvided
 | 
| ... | ... | @@ -179,73 +131,37 @@ class TorControllerGV( | 
| 179 | 131 |          getTorIntegration().unregisterLogListener(this)
 | 
| 180 | 132 |      }
 | 
| 181 | 133 | |
| 182 | -    // TorEvents
 | |
| 183 | -    override fun onTorConnecting() {
 | |
| 184 | -        synchronized(torListeners) {
 | |
| 185 | -            torListeners.toList().forEach { it.onTorConnecting() }
 | |
| 186 | -        }
 | |
| 187 | -    }
 | |
| 188 | - | |
| 189 | -    // TorEvents
 | |
| 190 | -    override fun onTorConnected() {
 | |
| 191 | -        synchronized(torListeners) {
 | |
| 192 | -            torListeners.toList().forEach { it.onTorConnected() }
 | |
| 193 | -        }
 | |
| 194 | -    }
 | |
| 195 | - | |
| 196 | -    // TorEvents
 | |
| 197 | -    override fun onTorStatusUpdate(entry: String?, status: String?, progress: Double?) {
 | |
| 198 | -        synchronized(torListeners) {
 | |
| 199 | -            torListeners.toList().forEach { it.onTorStatusUpdate(entry, status, progress) }
 | |
| 200 | -        }
 | |
| 201 | -    }
 | |
| 202 | - | |
| 203 | -    // TorEvents
 | |
| 204 | -    override fun onTorStopped() {
 | |
| 205 | -        synchronized(torListeners) {
 | |
| 206 | -            torListeners.toList().forEach { it.onTorStopped() }
 | |
| 207 | -        }
 | |
| 208 | -    }
 | |
| 209 | - | |
| 210 | 134 |      override fun onLog(type: String?, message: String?, timestamp: String?) {
 | 
| 211 | -        synchronized(torLogListeners) {
 | |
| 135 | +        synchronized(entries) {
 | |
| 212 | 136 |              entries.add(TorLog(type ?: "null", message ?: "null", timestamp ?: "null"))
 | 
| 213 | -            torLogListeners.toList().forEach { it.onLog(type ?: "null", message ?: "null", timestamp) }
 | |
| 214 | -        }
 | |
| 215 | -    }
 | |
| 216 | - | |
| 217 | -    override fun registerTorListener(l: TorEvents) {
 | |
| 218 | -        synchronized(torListeners) {
 | |
| 219 | -            if (torListeners.contains(l)) {
 | |
| 220 | -                return
 | |
| 221 | -            }
 | |
| 222 | -            torListeners.add(l)
 | |
| 223 | 137 |          }
 | 
| 224 | 138 |      }
 | 
| 225 | 139 | |
| 226 | -    override fun unregisterTorListener(l: TorEvents) {
 | |
| 227 | -        synchronized(torListeners) {
 | |
| 228 | -            if (!torListeners.contains(l)) {
 | |
| 140 | +    override fun registerRunOnceBootstrapped(rob: RunOnceBootstrapped) {
 | |
| 141 | +        // TODO Remove need for this with tb-44002
 | |
| 142 | +        // it would be nice to have a short circuit run and don't add if already bootstrapped
 | |
| 143 | +        // however this calls context.components.core.engine which tries to lazy load engine
 | |
| 144 | +        // which causes a recursive loop. instead we should do the work in tb-44002
 | |
| 145 | +        // this is currently fine as there is a single use case for this called in
 | |
| 146 | +        // TorBrowserFeatures that is at startup
 | |
| 147 | +        //if (isBootstrapped) {
 | |
| 148 | +        //    rob.onBootstrapped()
 | |
| 149 | +        //    return
 | |
| 150 | +        //}
 | |
| 151 | +        synchronized(runOnceBootstrappedHandlers) {
 | |
| 152 | +            if (runOnceBootstrappedHandlers.contains(rob)) {
 | |
| 229 | 153 |                  return
 | 
| 230 | 154 |              }
 | 
| 231 | -            torListeners.remove(l)
 | |
| 155 | +            runOnceBootstrappedHandlers.add(rob)
 | |
| 232 | 156 |          }
 | 
| 233 | 157 |      }
 | 
| 234 | 158 | |
| 235 | -    override fun registerTorLogListener(l: TorLogs) {
 | |
| 236 | -        synchronized(torLogListeners) {
 | |
| 237 | -            if (torLogListeners.contains(l)) {
 | |
| 238 | -                return
 | |
| 239 | -            }
 | |
| 240 | -            torLogListeners.add(l)
 | |
| 241 | -        }
 | |
| 242 | -    }
 | |
| 243 | -    override fun unregisterTorLogListener(l: TorLogs) {
 | |
| 244 | -        synchronized(torLogListeners) {
 | |
| 245 | -            if (!torLogListeners.contains(l)) {
 | |
| 159 | +    override fun unregisterRunOnceBootstrapped(rob: RunOnceBootstrapped) {
 | |
| 160 | +        synchronized(runOnceBootstrappedHandlers) {
 | |
| 161 | +            if (!runOnceBootstrappedHandlers.contains(rob)) {
 | |
| 246 | 162 |                  return
 | 
| 247 | 163 |              }
 | 
| 248 | -            torLogListeners.remove(l)
 | |
| 164 | +            runOnceBootstrappedHandlers.remove(rob)
 | |
| 249 | 165 |          }
 | 
| 250 | 166 |      }
 | 
| 251 | 167 | |
| ... | ... | @@ -260,82 +176,22 @@ class TorControllerGV( | 
| 260 | 176 |          getTorIntegration().cancelBootstrap()
 | 
| 261 | 177 |      }
 | 
| 262 | 178 | |
| 263 | -    override fun setTorStopped() {
 | |
| 264 | -        _lastKnownStatus.value = TorConnectState.Configuring
 | |
| 265 | -        onTorStatusUpdate(null, lastKnownStatus.toString(), 0.0)
 | |
| 266 | -        onTorStopped()
 | |
| 267 | -    }
 | |
| 268 | - | |
| 269 | -    override fun restartTor() {
 | |
| 270 | -        if (!_lastKnownStatus.value.isStarted() && wasTorBootstrapped) {
 | |
| 271 | -            // If we aren't started, but we were previously bootstrapped,
 | |
| 272 | -            // then we handle a "restart" request as a "start" restart
 | |
| 273 | -            initiateTorBootstrap()
 | |
| 274 | -        } else {
 | |
| 275 | -            // |isTorRestarting| tracks the state of restart. When we receive an |OFF| state
 | |
| 276 | -            // from TorService in persistentBroadcastReceiver::onReceive we restart the Tor
 | |
| 277 | -            // service.
 | |
| 278 | -            isTorRestarting = true
 | |
| 279 | -            stopTor()
 | |
| 280 | -        }
 | |
| 281 | -    }
 | |
| 282 | - | |
| 283 | -    override fun getLastErrorState() : TorError? {
 | |
| 284 | -        return lastKnownError
 | |
| 285 | -    }
 | |
| 286 | - | |
| 287 | -    // TorEventsBootstrapStateChangeListener -> (lastKnowStatus, TorEvents)
 | |
| 288 | -    // Handle events from GeckoView TorAndroidIntegration and map to TorEvents based events
 | |
| 289 | -    // and state for firefox-android (designed for tor-android-service)
 | |
| 290 | -    //   fun onTorConnecting()
 | |
| 291 | -    //   fun onTorConnected()
 | |
| 292 | -    //   fun onTorStatusUpdate(entry: String?, status: String?, progress: Double?)
 | |
| 293 | -    //   fun onTorStopped()
 | |
| 294 | - | |
| 295 | 179 |      // TorEventsBootstrapStateChangeListener
 | 
| 296 | -    override fun onBootstrapStateChange(newStateVal: String?) {
 | |
| 297 | -        Log.d(TAG, "onBootstrapStateChange(newStateVal = $newStateVal)")
 | |
| 298 | -        val newState: TorConnectState = TorConnectState.valueOf(newStateVal ?: "Error")
 | |
| 299 | - | |
| 300 | -        if (newState.isError() && wasTorBootstrapped) {
 | |
| 301 | -            stopTor()
 | |
| 302 | -        }
 | |
| 303 | - | |
| 304 | -        if (newState.isStarted()) {
 | |
| 305 | -            wasTorBootstrapped = true
 | |
| 306 | -            onTorConnected()
 | |
| 307 | -        }
 | |
| 308 | - | |
| 309 | -        if (wasTorBootstrapped && newState == TorConnectState.Configuring) {
 | |
| 310 | -            wasTorBootstrapped = false
 | |
| 311 | -            if (isTorRestarting) {
 | |
| 312 | -                initiateTorBootstrap()
 | |
| 313 | -            } else {
 | |
| 314 | -                setTorStopped()
 | |
| 180 | +    override fun onBootstrapStageChange(stage: TorConnectStage) {
 | |
| 181 | +        Log.d(TAG, "onBootstrapStageChange(stage = $stage)")
 | |
| 182 | + | |
| 183 | +        if (stage.name == TorConnectStageName.Bootstrapped) {
 | |
| 184 | +            synchronized(runOnceBootstrappedHandlers) {
 | |
| 185 | +                runOnceBootstrappedHandlers.toList().forEach {
 | |
| 186 | +                    it.onBootstrapped()
 | |
| 187 | +                    runOnceBootstrappedHandlers.remove(it)
 | |
| 188 | +                }
 | |
| 315 | 189 |              }
 | 
| 316 | 190 |          }
 | 
| 317 | - | |
| 318 | -        if (_lastKnownStatus.value.isOff() && newState.isStarting()) {
 | |
| 319 | -            isTorRestarting = false
 | |
| 320 | -        }
 | |
| 321 | - | |
| 322 | -        _lastKnownStatus.value = newState
 | |
| 323 | -        onTorStatusUpdate(null, newStateVal, null)
 | |
| 324 | 191 |      }
 | 
| 325 | 192 | |
| 326 | -    override fun onBootstrapStageChange(stage: TorConnectStage) = Unit
 | |
| 327 | - | |
| 328 | 193 |      // TorEventsBootstrapStateChangeListener
 | 
| 329 | 194 |      override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) {
 | 
| 330 | 195 |          Log.d(TAG, "onBootstrapProgress(progress = $progress, hasWarnings = $hasWarnings)")
 | 
| 331 | -        onTorStatusUpdate("", _lastKnownStatus.value.toTorStatus().status, progress)
 | |
| 332 | -    }
 | |
| 333 | - | |
| 334 | -    // TorEventsBootstrapStateChangeListener
 | |
| 335 | -    override fun onBootstrapComplete() = Unit
 | |
| 336 | - | |
| 337 | -    // TorEventsBootstrapStateChangeListener
 | |
| 338 | -    override fun onBootstrapError(code: String?, message: String?, phase: String?, reason: String?) {
 | |
| 339 | -        lastKnownError = TorError(code ?: "", message ?: "", phase ?: "", reason ?: "")
 | |
| 340 | 196 |      }
 | 
| 341 | 197 |  } | 
| ... | ... | @@ -13,11 +13,15 @@ import android.widget.Toast | 
| 13 | 13 |  import androidx.lifecycle.AndroidViewModel
 | 
| 14 | 14 |  import androidx.lifecycle.LiveData
 | 
| 15 | 15 |  import androidx.lifecycle.MutableLiveData
 | 
| 16 | +import mozilla.components.browser.engine.gecko.GeckoEngine
 | |
| 16 | 17 |  import org.mozilla.fenix.R
 | 
| 17 | 18 |  import org.mozilla.fenix.ext.components
 | 
| 19 | +import org.mozilla.geckoview.TorAndroidIntegration.TorLogListener
 | |
| 18 | 20 | |
| 19 | -class TorLogsViewModel(application: Application) : AndroidViewModel(application), TorLogs {
 | |
| 21 | +class TorLogsViewModel(application: Application) : AndroidViewModel(application), TorLogListener {
 | |
| 20 | 22 |      private val torController = application.components.torController
 | 
| 23 | +    private val engine = application.components.core.engine as GeckoEngine
 | |
| 24 | +    private val torAndroidIntegration = engine.getTorIntegrationController()
 | |
| 21 | 25 |      private val clipboardManager =
 | 
| 22 | 26 |          application.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
 | 
| 23 | 27 | |
| ... | ... | @@ -33,7 +37,7 @@ class TorLogsViewModel(application: Application) : AndroidViewModel(application) | 
| 33 | 37 | |
| 34 | 38 |      init {
 | 
| 35 | 39 |          setupClipboardListener()
 | 
| 36 | -        torController.registerTorLogListener(this)
 | |
| 40 | +        torAndroidIntegration.registerLogListener(this)
 | |
| 37 | 41 |          val currentEntries = torController.logEntries
 | 
| 38 | 42 |          for (log in currentEntries) {
 | 
| 39 | 43 |              addLog(log)
 | 
| ... | ... | @@ -46,7 +50,7 @@ class TorLogsViewModel(application: Application) : AndroidViewModel(application) | 
| 46 | 50 | |
| 47 | 51 |      override fun onCleared() {
 | 
| 48 | 52 |          super.onCleared()
 | 
| 49 | -        torController.unregisterTorLogListener(this)
 | |
| 53 | +        torAndroidIntegration.unregisterLogListener(this)
 | |
| 50 | 54 |      }
 | 
| 51 | 55 | |
| 52 | 56 |      private fun setupClipboardListener() {
 | 
| ... | ... | @@ -39,11 +39,8 @@ public class TorAndroidIntegration implements BundleEventListener { | 
| 39 | 39 |    private static final String EVENT_TOR_STOP = "GeckoView:Tor:StopTor";
 | 
| 40 | 40 |    private static final String EVENT_MEEK_START = "GeckoView:Tor:StartMeek";
 | 
| 41 | 41 |    private static final String EVENT_MEEK_STOP = "GeckoView:Tor:StopMeek";
 | 
| 42 | -  private static final String EVENT_CONNECT_STATE_CHANGED = "GeckoView:Tor:ConnectStateChanged"; // deprecation path
 | |
| 43 | -  private static final String EVENT_CONNECT_STAGE_CHANGED = "GeckoView:Tor:ConnectStageChanged"; // replacement path
 | |
| 44 | -  private static final String EVENT_CONNECT_ERROR = "GeckoView:Tor:ConnectError";
 | |
| 42 | +  private static final String EVENT_CONNECT_STAGE_CHANGED = "GeckoView:Tor:ConnectStageChanged";
 | |
| 45 | 43 |    private static final String EVENT_BOOTSTRAP_PROGRESS = "GeckoView:Tor:BootstrapProgress";
 | 
| 46 | -  private static final String EVENT_BOOTSTRAP_COMPLETE = "GeckoView:Tor:BootstrapComplete";
 | |
| 47 | 44 |    private static final String EVENT_TOR_LOGS = "GeckoView:Tor:Logs";
 | 
| 48 | 45 |    private static final String EVENT_SETTINGS_READY = "GeckoView:Tor:SettingsReady";
 | 
| 49 | 46 |    private static final String EVENT_SETTINGS_CHANGED = "GeckoView:Tor:SettingsChanged";
 | 
| ... | ... | @@ -62,6 +59,7 @@ public class TorAndroidIntegration implements BundleEventListener { | 
| 62 | 59 |    private static final String EVENT_QUICKSTART_GET = "GeckoView:Tor:QuickstartGet";
 | 
| 63 | 60 |    private static final String EVENT_QUICKSTART_SET = "GeckoView:Tor:QuickstartSet";
 | 
| 64 | 61 |    private static final String EVENT_REGION_NAMES_GET = "GeckoView:Tor:RegionNamesGet";
 | 
| 62 | +  private static final String EVENT_SHOULD_SHOW_TOR_CONNECT = "GeckoView:Tor:ShouldShowTorConnect";
 | |
| 65 | 63 | |
| 66 | 64 |    private static final String CONTROL_PORT_FILE = "/control-ipc";
 | 
| 67 | 65 |    private static final String SOCKS_FILE = "/socks-ipc";
 | 
| ... | ... | @@ -124,11 +122,8 @@ public class TorAndroidIntegration implements BundleEventListener { | 
| 124 | 122 |              EVENT_MEEK_STOP,
 | 
| 125 | 123 |              EVENT_SETTINGS_READY,
 | 
| 126 | 124 |              EVENT_SETTINGS_CHANGED,
 | 
| 127 | -            EVENT_CONNECT_STATE_CHANGED,
 | |
| 128 | 125 |              EVENT_CONNECT_STAGE_CHANGED,
 | 
| 129 | -            EVENT_CONNECT_ERROR,
 | |
| 130 | 126 |              EVENT_BOOTSTRAP_PROGRESS,
 | 
| 131 | -            EVENT_BOOTSTRAP_COMPLETE,
 | |
| 132 | 127 |              EVENT_TOR_LOGS);
 | 
| 133 | 128 |    }
 | 
| 134 | 129 | |
| ... | ... | @@ -157,35 +152,18 @@ public class TorAndroidIntegration implements BundleEventListener { | 
| 157 | 152 |        } else {
 | 
| 158 | 153 |          Log.w(TAG, "Ignoring a settings changed event that did not have the new settings.");
 | 
| 159 | 154 |        }
 | 
| 160 | -    } else if (EVENT_CONNECT_STATE_CHANGED.equals(event)) {
 | |
| 161 | -      String state = message.getString("state");
 | |
| 162 | -      for (BootstrapStateChangeListener listener : mBootstrapStateListeners) {
 | |
| 163 | -        listener.onBootstrapStateChange(state);
 | |
| 164 | -      }
 | |
| 165 | 155 |      } else if (EVENT_CONNECT_STAGE_CHANGED.equals(event)) {
 | 
| 166 | 156 |        TorConnectStage stage = new TorConnectStage(message.getBundle("stage"));
 | 
| 167 | 157 |        _lastKnownStage.setValue(stage);
 | 
| 168 | 158 |        for (BootstrapStateChangeListener listener : mBootstrapStateListeners) {
 | 
| 169 | 159 |          listener.onBootstrapStageChange(stage);
 | 
| 170 | 160 |        }
 | 
| 171 | -    } else if (EVENT_CONNECT_ERROR.equals(event)) {
 | |
| 172 | -      String code = message.getString("code");
 | |
| 173 | -      String msg = message.getString("message");
 | |
| 174 | -      String phase = message.getString("phase");
 | |
| 175 | -      String reason = message.getString("reason");
 | |
| 176 | -      for (BootstrapStateChangeListener listener : mBootstrapStateListeners) {
 | |
| 177 | -        listener.onBootstrapError(code, msg, phase, reason);
 | |
| 178 | -      }
 | |
| 179 | 161 |      } else if (EVENT_BOOTSTRAP_PROGRESS.equals(event)) {
 | 
| 180 | 162 |        double progress = message.getDouble("progress");
 | 
| 181 | 163 |        boolean hasWarnings = message.getBoolean("hasWarnings");
 | 
| 182 | 164 |        for (BootstrapStateChangeListener listener : mBootstrapStateListeners) {
 | 
| 183 | 165 |          listener.onBootstrapProgress(progress, hasWarnings);
 | 
| 184 | 166 |        }
 | 
| 185 | -    } else if (EVENT_BOOTSTRAP_COMPLETE.equals(event)) {
 | |
| 186 | -      for (BootstrapStateChangeListener listener : mBootstrapStateListeners) {
 | |
| 187 | -        listener.onBootstrapComplete();
 | |
| 188 | -      }
 | |
| 189 | 167 |      } else if (EVENT_TOR_LOGS.equals(event)) {
 | 
| 190 | 168 |        String msg = message.getString("message");
 | 
| 191 | 169 |        String type = message.getString("logType");
 | 
| ... | ... | @@ -647,15 +625,9 @@ public class TorAndroidIntegration implements BundleEventListener { | 
| 647 | 625 |    }
 | 
| 648 | 626 | |
| 649 | 627 |    public interface BootstrapStateChangeListener {
 | 
| 650 | -    void onBootstrapStateChange(String state); // depreaction path
 | |
| 651 | - | |
| 652 | -    void onBootstrapStageChange(TorConnectStage stage); // new upgrade
 | |
| 628 | +    void onBootstrapStageChange(@NonNull TorConnectStage stage); // new upgrade
 | |
| 653 | 629 | |
| 654 | 630 |      void onBootstrapProgress(double progress, boolean hasWarnings);
 | 
| 655 | - | |
| 656 | -    void onBootstrapComplete();
 | |
| 657 | - | |
| 658 | -    void onBootstrapError(String code, String message, String phase, String reason);
 | |
| 659 | 631 |    }
 | 
| 660 | 632 | |
| 661 | 633 |    public interface TorLogListener {
 | 
| ... | ... | @@ -736,6 +708,17 @@ public class TorAndroidIntegration implements BundleEventListener { | 
| 736 | 708 |      });
 | 
| 737 | 709 |    }
 | 
| 738 | 710 | |
| 711 | +  public interface ShouldShowTorConnectGetter {
 | |
| 712 | +    void onValue(Boolean shouldShowTorConnect);
 | |
| 713 | +  }
 | |
| 714 | + | |
| 715 | +  public void shouldShowTorConnectGet(ShouldShowTorConnectGetter shouldShowTorConnectGetter) {
 | |
| 716 | +    EventDispatcher.getInstance().queryBoolean(EVENT_SHOULD_SHOW_TOR_CONNECT).then(shouldShowTorConnect -> {
 | |
| 717 | +      shouldShowTorConnectGetter.onValue(shouldShowTorConnect);
 | |
| 718 | +      return new GeckoResult<Void>();
 | |
| 719 | +    });
 | |
| 720 | +  }
 | |
| 721 | + | |
| 739 | 722 |    public @NonNull GeckoResult<Void> beginBootstrap() {
 | 
| 740 | 723 |      return EventDispatcher.getInstance().queryVoid(EVENT_BOOTSTRAP_BEGIN);
 | 
| 741 | 724 |    }
 | 
| ... | ... | @@ -754,21 +737,21 @@ public class TorAndroidIntegration implements BundleEventListener { | 
| 754 | 737 |      return EventDispatcher.getInstance().queryVoid(EVENT_BOOTSTRAP_CANCEL);
 | 
| 755 | 738 |    }
 | 
| 756 | 739 | |
| 757 | -  public void registerBootstrapStateChangeListener(BootstrapStateChangeListener listener) {
 | |
| 740 | +  public synchronized void registerBootstrapStateChangeListener(BootstrapStateChangeListener listener) {
 | |
| 758 | 741 |      mBootstrapStateListeners.add(listener);
 | 
| 759 | 742 |    }
 | 
| 760 | 743 | |
| 761 | -  public void unregisterBootstrapStateChangeListener(BootstrapStateChangeListener listener) {
 | |
| 744 | +  public synchronized void unregisterBootstrapStateChangeListener(BootstrapStateChangeListener listener) {
 | |
| 762 | 745 |      mBootstrapStateListeners.remove(listener);
 | 
| 763 | 746 |    }
 | 
| 764 | 747 | |
| 765 | 748 |    private final HashSet<BootstrapStateChangeListener> mBootstrapStateListeners = new HashSet<>();
 | 
| 766 | 749 | |
| 767 | -  public void registerLogListener(TorLogListener listener) {
 | |
| 750 | +  public synchronized void registerLogListener(TorLogListener listener) {
 | |
| 768 | 751 |      mLogListeners.add(listener);
 | 
| 769 | 752 |    }
 | 
| 770 | 753 | |
| 771 | -  public void unregisterLogListener(TorLogListener listener) {
 | |
| 754 | +  public synchronized void unregisterLogListener(TorLogListener listener) {
 | |
| 772 | 755 |      mLogListeners.remove(listener);
 | 
| 773 | 756 |    }
 | 
| 774 | 757 | 
| 1 | +package org.mozilla.geckoview;
 | |
| 2 | + | |
| 3 | +import org.mozilla.gecko.util.GeckoBundle;
 | |
| 4 | + | |
| 5 | +public class TorConnectError {
 | |
| 6 | +    public String code;
 | |
| 7 | +    public String message;
 | |
| 8 | +    public String phase;
 | |
| 9 | +    public String reason;
 | |
| 10 | + | |
| 11 | +    public TorConnectError(GeckoBundle bundle) {
 | |
| 12 | +        code = bundle.getString("code");
 | |
| 13 | +        message = bundle.getString("message");
 | |
| 14 | +        phase = bundle.getString("phase");
 | |
| 15 | +        reason = bundle.getString("reason");
 | |
| 16 | +    }
 | |
| 17 | + | |
| 18 | +    public TorConnectError(String code, String message, String phase, String reason) {
 | |
| 19 | +        this.code = code;
 | |
| 20 | +        this.message = message;
 | |
| 21 | +        this.phase = phase;
 | |
| 22 | +        this.reason = reason;
 | |
| 23 | +    }
 | |
| 24 | +} | 
| ... | ... | @@ -5,24 +5,10 @@ import org.mozilla.gecko.util.GeckoBundle; | 
| 5 | 5 |  // Class to receive ConnectStage object from TorConnect.sys.mjs ~ln677
 | 
| 6 | 6 |  public class TorConnectStage {
 | 
| 7 | 7 | |
| 8 | -    public class Error {
 | |
| 9 | -        public String code;
 | |
| 10 | -        public String message;
 | |
| 11 | -        public String phase;
 | |
| 12 | -        public String reason;
 | |
| 13 | - | |
| 14 | -        public Error(GeckoBundle bundle) {
 | |
| 15 | -            code = bundle.getString("code");
 | |
| 16 | -            message = bundle.getString("message");
 | |
| 17 | -            phase = bundle.getString("phase");
 | |
| 18 | -            reason = bundle.getString("reason");
 | |
| 19 | -        }
 | |
| 20 | -    }
 | |
| 21 | - | |
| 22 | 8 |      public TorConnectStageName name;
 | 
| 23 | 9 |      // The TorConnectStage prior to this bootstrap attempt. Only set during the "Bootstrapping" stage.
 | 
| 24 | 10 |      public TorConnectStageName bootstrapTrigger;
 | 
| 25 | -    public Error error;
 | |
| 11 | +    public TorConnectError error;
 | |
| 26 | 12 |      public String defaultRegion;
 | 
| 27 | 13 |      public Boolean potentiallyBlocked;
 | 
| 28 | 14 |      public Boolean tryAgain;
 | 
| ... | ... | @@ -37,7 +23,7 @@ public class TorConnectStage { | 
| 37 | 23 |          potentiallyBlocked = bundle.getBoolean("potentiallyBlocked");
 | 
| 38 | 24 |          tryAgain = bundle.getBoolean("tryAgain");
 | 
| 39 | 25 |          if (bundle.getBundle("error") != null) {
 | 
| 40 | -            error = new Error(bundle.getBundle("error"));
 | |
| 26 | +            error = new TorConnectError(bundle.getBundle("error"));
 | |
| 41 | 27 |          }
 | 
| 42 | 28 |          bootstrappingStatus = new TorBootstrappingStatus(bundle.getBundle("bootstrappingStatus"));
 | 
| 43 | 29 |      }
 | 
| ... | ... | @@ -28,7 +28,6 @@ const EmittedEvents = Object.freeze({ | 
| 28 | 28 |    settingsChanged: "GeckoView:Tor:SettingsChanged",
 | 
| 29 | 29 |    connectStateChanged: "GeckoView:Tor:ConnectStateChanged", // deprecation path
 | 
| 30 | 30 |    connectStageChanged: "GeckoView:Tor:ConnectStageChanged", // new replacement path
 | 
| 31 | -  connectError: "GeckoView:Tor:ConnectError",
 | |
| 32 | 31 |    bootstrapProgress: "GeckoView:Tor:BootstrapProgress",
 | 
| 33 | 32 |    bootstrapComplete: "GeckoView:Tor:BootstrapComplete",
 | 
| 34 | 33 |    torLogs: "GeckoView:Tor:Logs",
 | 
| ... | ... | @@ -49,6 +48,7 @@ const ListenedEvents = Object.freeze({ | 
| 49 | 48 |    quickstartGet: "GeckoView:Tor:QuickstartGet",
 | 
| 50 | 49 |    quickstartSet: "GeckoView:Tor:QuickstartSet",
 | 
| 51 | 50 |    regionNamesGet: "GeckoView:Tor:RegionNamesGet",
 | 
| 51 | +  shouldShowTorConnectGet: "GeckoView:Tor:ShouldShowTorConnect",
 | |
| 52 | 52 |  });
 | 
| 53 | 53 | |
| 54 | 54 |  class TorAndroidIntegrationImpl {
 | 
| ... | ... | @@ -134,16 +134,6 @@ class TorAndroidIntegrationImpl { | 
| 134 | 134 |            type: EmittedEvents.bootstrapComplete,
 | 
| 135 | 135 |          });
 | 
| 136 | 136 |          break;
 | 
| 137 | -      // TODO: Replace with StageChange stage.error.
 | |
| 138 | -      case lazy.TorConnectTopics.Error:
 | |
| 139 | -        lazy.EventDispatcher.instance.sendRequest({
 | |
| 140 | -          type: EmittedEvents.connectError,
 | |
| 141 | -          code: subj.wrappedJSObject.code ?? "",
 | |
| 142 | -          message: subj.wrappedJSObject.message ?? "",
 | |
| 143 | -          phase: subj.wrappedJSObject.cause?.phase ?? "",
 | |
| 144 | -          reason: subj.wrappedJSObject.cause?.reason ?? "",
 | |
| 145 | -        });
 | |
| 146 | -        break;
 | |
| 147 | 137 |        case lazy.TorProviderTopics.TorLog:
 | 
| 148 | 138 |          lazy.EventDispatcher.instance.sendRequest({
 | 
| 149 | 139 |            type: EmittedEvents.torLogs,
 | 
| ... | ... | @@ -225,6 +215,9 @@ class TorAndroidIntegrationImpl { | 
| 225 | 215 |          case ListenedEvents.regionNamesGet:
 | 
| 226 | 216 |            callback?.onSuccess(lazy.TorConnect.getRegionNames());
 | 
| 227 | 217 |            return;
 | 
| 218 | +        case ListenedEvents.shouldShowTorConnectGet:
 | |
| 219 | +          callback?.onSuccess(lazy.TorConnect.shouldShowTorConnect());
 | |
| 220 | +          return;
 | |
| 228 | 221 |        }
 | 
| 229 | 222 |        callback?.onSuccess();
 | 
| 230 | 223 |      } catch (e) {
 |