
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 fixup! TB 42247: Android helpers for the TorProvider Bug 41188 pt2: strip out legacy TorController logic and shims - - - - - a04605ca by Dan Ballard at 2025-08-04T23:24:21-05:00 fixup! TB 41878: [android] Add standalone Tor Bootstrap Bug 41188 pt2: strip out legacy TorController logic and shims - - - - - c563c43e by Dan Ballard at 2025-08-04T23:24:22-05:00 fixup! TB 40041 [android]: Implement Tor Network Settings Bug 41188 pt2: strip out legacy TorController logic and shims - - - - - e9ac372c by Dan Ballard at 2025-08-04T23:30:59-05:00 fixup! [android] Implement Android-native Connection Assist UI Bug 41188 pt2: strip out legacy TorController logic and shims - - - - - 579ade4c by Dan Ballard at 2025-08-04T23:31:02-05:00 fixup! [android] Modify add-on support Bug 41188 pt2: strip out legacy TorController logic and shims - - - - - 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: ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt ===================================== @@ -101,7 +101,6 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager import org.mozilla.fenix.components.appstate.AppAction -import org.mozilla.fenix.components.appstate.AppAction.ShareAction import org.mozilla.fenix.components.appstate.OrientationMode import org.mozilla.fenix.components.metrics.BreadcrumbsRecorder import org.mozilla.fenix.components.metrics.GrowthDataWorker @@ -137,7 +136,6 @@ import org.mozilla.fenix.home.intent.OpenBrowserIntentProcessor import org.mozilla.fenix.home.intent.OpenPasswordManagerIntentProcessor import org.mozilla.fenix.home.intent.OpenRecentlyClosedIntentProcessor import org.mozilla.fenix.home.intent.OpenSpecificTabIntentProcessor -import org.mozilla.fenix.home.intent.ReEngagementIntentProcessor import org.mozilla.fenix.home.intent.SpeechProcessingIntentProcessor import org.mozilla.fenix.home.intent.StartSearchIntentProcessor import org.mozilla.fenix.library.bookmarks.DesktopFolders @@ -167,19 +165,18 @@ import org.mozilla.fenix.theme.DefaultThemeManager import org.mozilla.fenix.theme.StatusBarColorManager import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.tor.TorConnectionAssistFragmentDirections -import org.mozilla.fenix.tor.TorEvents import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.changeAppLauncherIcon import java.lang.ref.WeakReference import java.util.Locale -import androidx.compose.material.SnackbarDuration import mozilla.components.browser.engine.gecko.GeckoEngine import org.mozilla.fenix.compose.core.Action import org.mozilla.fenix.compose.snackbar.SnackbarState import org.mozilla.fenix.compose.snackbar.Snackbar import org.mozilla.fenix.tor.UrlQuickLoadViewModel import org.mozilla.geckoview.TorAndroidIntegration +import org.mozilla.geckoview.TorAndroidIntegration.BootstrapStateChangeListener import org.mozilla.geckoview.TorConnectStage import kotlin.system.exitProcess @@ -915,19 +912,25 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, TorAn */ @SuppressLint("MissingSuperCall") // super.onNewIntent is called in [onNewIntentInternal(intent)] final override fun onNewIntent(intent: Intent) { - if (intent.action == ACTION_MAIN || components.torController.isConnected) { + if (intent.action == ACTION_MAIN || components.torController.isBootstrapped) { onNewIntentInternal(intent) } else { // Wait until Tor is connected to handle intents from external apps for links, search, etc. - components.torController.registerTorListener(object : TorEvents { - override fun onTorConnected() { - components.torController.unregisterTorListener(this) - onNewIntentInternal(intent) + val torIntegration = (components.core.engine as GeckoEngine).getTorIntegrationController() + torIntegration.registerBootstrapStateChangeListener( + object : BootstrapStateChangeListener { + + override fun onBootstrapStageChange(stage: TorConnectStage) { + if (stage.isBootstrapped) { + torIntegration.unregisterBootstrapStateChangeListener(this) + onNewIntentInternal(intent) + } + } + + override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) {} } - override fun onTorConnecting() { /* no-op */ } - override fun onTorStopped() { /* no-op */ } - override fun onTorStatusUpdate(entry: String?, status: String?, progress: Double?) { /* no-op */ } - }) + ) + return } } @@ -1516,7 +1519,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, TorAn // we want to ignore other cases when the app gets open by users clicking on links, // unless Tor is not yet connected. getSettings().shouldStartOnHome() && (intent?.action == ACTION_MAIN || - !components.torController.isConnected) + !components.torController.isBootstrapped) } } @@ -1606,14 +1609,13 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, TorAn exitProcess(0) } - override fun onBootstrapStateChange(state: String) = Unit - override fun onBootstrapStageChange(stage: TorConnectStage) = Unit - override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) = Unit - override fun onBootstrapComplete() { - if (settings().useHtmlConnectionUi) { - components.useCases.tabsUseCases.removeAllTabs() - navHost.navController.navigate(NavGraphDirections.actionStartupHome()) + override fun onBootstrapStageChange(stage: TorConnectStage) { + if (stage.isBootstrapped) { + if (settings().useHtmlConnectionUi) { + components.useCases.tabsUseCases.removeAllTabs() + navHost.navController.navigate(NavGraphDirections.actionStartupHome()) + } } } - override fun onBootstrapError(code: String?, message: String?, phase: String?, reason: String?) = Unit + override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) = Unit } ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt ===================================== @@ -13,6 +13,7 @@ import android.os.Build import android.os.Bundle import android.os.StrictMode import androidx.annotation.VisibleForTesting +import mozilla.components.browser.engine.gecko.GeckoEngine import mozilla.components.feature.intent.ext.sanitize import mozilla.components.feature.intent.processing.IntentProcessor import mozilla.components.support.base.log.logger.Logger @@ -31,7 +32,8 @@ import org.mozilla.fenix.ext.settings import org.mozilla.fenix.perf.MarkersActivityLifecycleCallbacks import org.mozilla.fenix.perf.StartupTimeline import org.mozilla.fenix.shortcut.NewTabShortcutIntentProcessor -import org.mozilla.fenix.tor.TorEvents +import org.mozilla.geckoview.TorAndroidIntegration.BootstrapStateChangeListener +import org.mozilla.geckoview.TorConnectStage /** * Processes incoming intents and sends them to the corresponding activity. @@ -55,19 +57,24 @@ class IntentReceiverActivity : Activity() { // the HomeActivity. val intent = intent?.let { Intent(it) } ?: Intent() intent.sanitize().stripUnwantedFlags() - if (intent.action == ACTION_MAIN || components.torController.isConnected) { + if (intent.action == ACTION_MAIN || components.torController.isBootstrapped) { processIntent(intent) } else { // Wait until Tor is connected to handle intents from external apps for links, search, etc. - components.torController.registerTorListener(object : TorEvents { - override fun onTorConnected() { - components.torController.unregisterTorListener(this) - processIntent(intent) - } - override fun onTorConnecting() { /* no-op */ } - override fun onTorStopped() { /* no-op */ } - override fun onTorStatusUpdate(entry: String?, status: String?, progress: Double?) { /* no-op */ } - }) + val engine = components.core.engine as GeckoEngine + engine.getTorIntegrationController().registerBootstrapStateChangeListener( + object : BootstrapStateChangeListener { + + override fun onBootstrapStageChange(stage: TorConnectStage) { + if (stage.isBootstrapped) { + engine.getTorIntegrationController().unregisterBootstrapStateChangeListener(this) + processIntent(intent) + } + } + + override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) {} + }) + // In the meantime, open the HomeActivity so the user can get connected. processIntent(Intent()) ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/TorBrowserFeatures.kt ===================================== @@ -20,7 +20,7 @@ import mozilla.components.support.webextensions.WebExtensionSupport import mozilla.components.support.base.log.logger.Logger import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings -import org.mozilla.fenix.tor.TorEvents +import org.mozilla.fenix.tor.RunOnceBootstrapped object TorBrowserFeatures { private val logger = Logger("torbrowser-features") @@ -142,9 +142,8 @@ object TorBrowserFeatures { * causing automatic update checks failures (components.addonUpdater being a lazy prop). * The extension, from then on, should behave as if the user had installed it manually. */ - context.components.torController.registerTorListener(object : TorEvents { - override fun onTorConnected() { - context.components.torController.unregisterTorListener(this) + context.components.torController.registerRunOnceBootstrapped(object : RunOnceBootstrapped { + override fun onBootstrapped() { // Enable automatic updates. This must be done on every startup (tor-browser#42353) context.components.addonUpdater.registerForFutureUpdates(NOSCRIPT_ID) // Force a one-time immediate update check for older installations @@ -153,18 +152,6 @@ object TorBrowserFeatures { settings.noscriptUpdated = 2 } } - - @SuppressWarnings("EmptyFunctionBlock") - override fun onTorConnecting() { - } - - @SuppressWarnings("EmptyFunctionBlock") - override fun onTorStopped() { - } - - @SuppressWarnings("EmptyFunctionBlock") - override fun onTorStatusUpdate(entry: String?, status: String?, progress: Double?) { - } }) } ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/TorBridgeConfigFragment.kt ===================================== @@ -56,7 +56,6 @@ class TorBridgeConfigFragment : PreferenceFragmentCompat() { setOnPreferenceChangeListener<Boolean> { preference, enabled -> preference.context.components.torController.bridgesEnabled = enabled updateCurrentConfiguredBridgePref(preference) - preference.context.components.torController.restartTor() true } } @@ -71,7 +70,6 @@ class TorBridgeConfigFragment : PreferenceFragmentCompat() { preference.context.components.torController.bridgeTransport = TorBridgeTransportConfig.USER_PROVIDED preference.context.components.torController.userProvidedBridges = userProvidedBridge updateCurrentConfiguredBridgePref(preference) - preference.context.components.torController.restartTor() true } val userProvidedBridge: String? = context.components.torController.userProvidedBridges @@ -103,7 +101,6 @@ class TorBridgeConfigFragment : PreferenceFragmentCompat() { preference.context.components.torController.bridgeTransport = bridge previousTransportConfig = bridge updateCurrentConfiguredBridgePref(preference) - preference.context.components.torController.restartTor() } true } ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorBootstrapProgressViewModel.kt ===================================== @@ -27,20 +27,9 @@ class TorBootstrapProgressViewModel( super.onCleared() } - override fun onBootstrapStateChange(state: String?) {} override fun onBootstrapStageChange(stage: TorConnectStage) = Unit override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) { this.progress.value = progress.toInt() } - - override fun onBootstrapComplete() {} - - override fun onBootstrapError( - code: String?, - message: String?, - phase: String?, - reason: String?, - ) { - } } ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistViewModel.kt ===================================== @@ -154,23 +154,12 @@ class TorConnectionAssistViewModel( } } - override fun onBootstrapStateChange(state: String?) {} - - override fun onBootstrapStageChange(stage: TorConnectStage?) { + override fun onBootstrapStageChange(stage: TorConnectStage) { torConnectStage.value = stage } override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) {} - override fun onBootstrapComplete() {} - - override fun onBootstrapError( - code: String?, - message: String?, - phase: String?, - reason: String?, - ) {} - fun button1ShouldBeDisabled(screen: ConnectAssistUiState): Boolean { return selectedCountryCode.value == "automatic" && screen.regionDropDownDefaultItem == R.string.connection_assist_select_country_or_region } ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorController.kt ===================================== @@ -6,37 +6,14 @@ package org.mozilla.fenix.tor import androidx.lifecycle.LifecycleCoroutineScope -interface TorEvents { - fun onTorConnecting() - fun onTorConnected() - fun onTorStatusUpdate(entry: String?, status: String?, progress: Double? = 0.0) - fun onTorStopped() +// Callback for function to be run one time when the system is bootstrapped and then disregarded +interface RunOnceBootstrapped { + fun onBootstrapped() } -class TorError( - var message: String, - var details: String, - var phase: String, - var reason: String, -) { } -interface TorLogs { - fun onLog(type: String?, message: String?, timestamp: String?) -} - -internal enum class TorStatus(val status: String) { - OFF("OFF"), - STARTING("STARTING"), - ON("ON"), - STOPPING("STOPPING"), - UNKNOWN("UNKNOWN"); -} - -interface TorController: TorEvents { +interface TorController { val logEntries: MutableList<TorLog> - val isStarting: Boolean - val isRestarting: Boolean val isBootstrapped: Boolean - val isConnected: Boolean var bridgesEnabled: Boolean var bridgeTransport: TorBridgeTransportConfig var userProvidedBridges: String? @@ -44,21 +21,21 @@ interface TorController: TorEvents { fun start() fun stop() - override fun onTorConnecting() - override fun onTorConnected() - override fun onTorStatusUpdate(entry: String?, status: String?, progress: Double?) - override fun onTorStopped() - - fun getLastErrorState() : TorError? - - fun registerTorListener(l: TorEvents) - fun unregisterTorListener(l: TorEvents) - - fun registerTorLogListener(l: TorLogs) - fun unregisterTorLogListener(l: TorLogs) + // TorBrowserFeatures.install wants to register a callback for when tor bootstraps the first time + // so it can then check for noscript updates. + // Currently it needs to register it before TorAndroidIntegration is fully loaded, so this way + // they can register with TorController which will start streaming events from TAS when available + // and call them one time when the system is bootstrapped + // TODO: rewire the noscript update call in TorBrowserFeatures.install + // a) call TorBrowserFeatures.install from somewhere else (ex: move from Core.GeckoEngine.also + // to maybe FenixApplication.setupInMainProcessOnly + // dan: had trouble with this first time: + // https://gitlab.torproject.org/tpo/applications/tor-browser/-/merge_requests/... + // b) just move the call to `context.components.addonUpdater.update(NOSCRIPT_ID)` somewhere else + // that can use TorAndroidIntegration.BootstrapListener + fun registerRunOnceBootstrapped(rob: RunOnceBootstrapped) + fun unregisterRunOnceBootstrapped(rob: RunOnceBootstrapped) fun initiateTorBootstrap(lifecycleScope: LifecycleCoroutineScope? = null, withDebugLogging: Boolean = false) fun stopTor() - fun setTorStopped() - fun restartTor() } ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerGV.kt ===================================== @@ -4,75 +4,30 @@ package org.mozilla.fenix.tor import android.content.Context import android.util.Log import androidx.lifecycle.LifecycleCoroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import mozilla.components.browser.engine.gecko.GeckoEngine import org.mozilla.fenix.ext.components import org.mozilla.geckoview.TorAndroidIntegration import org.mozilla.geckoview.TorAndroidIntegration.BootstrapStateChangeListener import org.mozilla.geckoview.TorAndroidIntegration.TorLogListener import org.mozilla.geckoview.TorConnectStage +import org.mozilla.geckoview.TorConnectStageName import org.mozilla.geckoview.TorSettings import org.mozilla.geckoview.TorSettings.BridgeBuiltinType import org.mozilla.geckoview.TorSettings.BridgeSource -// Enum matching TorConnectState from TorConnect.sys.mjs that we get from onBootstrapStateChange -internal enum class TorConnectState(val state: String) { - Initial("Initial"), - Configuring("Configuring"), - AutoBootstrapping("AutoBootstrapping"), - Bootstrapping("Bootstrapping"), - Error("Error"), - Bootstrapped("Bootstrapped"), - Disabled("Disabled"); - - fun isStarting() = this == Bootstrapping || this == AutoBootstrapping - fun isError() = this == Error - - fun isStarted() = this == Bootstrapped - - fun isOff() = this == Initial || this == Configuring || this == Disabled || this == Error - - - // Convert to TorStatus that firefox-android uses based on tor-android-service - fun toTorStatus(): TorStatus { - return when (this) { - Initial -> TorStatus.OFF - Configuring -> TorStatus.OFF - AutoBootstrapping -> TorStatus.STARTING - Bootstrapping -> TorStatus.STARTING - Error -> TorStatus.UNKNOWN - Bootstrapped -> TorStatus.ON - Disabled -> TorStatus.OFF - } - } -} - class TorControllerGV( private val context: Context, -) : TorController, TorEvents, BootstrapStateChangeListener, TorLogListener { +) : TorController, BootstrapStateChangeListener, TorLogListener { private val TAG = "TorControllerGV" - private var torListeners = mutableListOf<TorEvents>() - private var torLogListeners = mutableListOf<TorLogs>() - - private val _lastKnownStatus = MutableStateFlow(TorConnectState.Initial) - internal val lastKnownStatus: StateFlow<TorConnectState> = _lastKnownStatus + private var runOnceBootstrappedHandlers = mutableListOf<RunOnceBootstrapped>() - internal var lastKnownError: TorError? = null - private var wasTorBootstrapped = false - private var isTorRestarting = false - - private var isTorBootstrapped = false - get() = ((_lastKnownStatus.value.isStarted()) && wasTorBootstrapped) + override val isBootstrapped get() = + getTorIntegration().lastKnowStage.value?.name?.isBootstrapped ?: false private val entries = mutableListOf<TorLog>() override val logEntries get() = entries - override val isStarting get() = _lastKnownStatus.value.isStarting() - override val isRestarting get() = isTorRestarting - override val isBootstrapped get() = isTorBootstrapped - override val isConnected get() = (_lastKnownStatus.value.isStarted() && !isTorRestarting) private fun getTorIntegration(): TorAndroidIntegration { return (context.components.core.engine as GeckoEngine).getTorIntegrationController() @@ -82,8 +37,7 @@ class TorControllerGV( return getTorIntegration().getSettings() } - - // On a fresh install bridgeEnagled can be set to true without a valid bridgeSource + // On a fresh install bridgeEnabled can be set to true without a valid bridgeSource // having been selected. After first use this will not happen because last selected bridge // will be remembered and reused. // However, on first use, submitting this to TorSettings is an invalid state. @@ -105,7 +59,6 @@ class TorControllerGV( } } - override var bridgeTransport: TorBridgeTransportConfig get() { return when (getTorSettings()?.bridgesSource) { @@ -144,7 +97,6 @@ class TorControllerGV( } } - // Currently the UI takes a user provided string and sets this in one step so there is where we // actually set it.bridgesSource = BridgeSource.UserProvided, not above, // as TorSettings.sys.mjs #cleanupSettings could reject BridgeSource.UserProvided @@ -179,73 +131,37 @@ class TorControllerGV( getTorIntegration().unregisterLogListener(this) } - // TorEvents - override fun onTorConnecting() { - synchronized(torListeners) { - torListeners.toList().forEach { it.onTorConnecting() } - } - } - - // TorEvents - override fun onTorConnected() { - synchronized(torListeners) { - torListeners.toList().forEach { it.onTorConnected() } - } - } - - // TorEvents - override fun onTorStatusUpdate(entry: String?, status: String?, progress: Double?) { - synchronized(torListeners) { - torListeners.toList().forEach { it.onTorStatusUpdate(entry, status, progress) } - } - } - - // TorEvents - override fun onTorStopped() { - synchronized(torListeners) { - torListeners.toList().forEach { it.onTorStopped() } - } - } - override fun onLog(type: String?, message: String?, timestamp: String?) { - synchronized(torLogListeners) { + synchronized(entries) { entries.add(TorLog(type ?: "null", message ?: "null", timestamp ?: "null")) - torLogListeners.toList().forEach { it.onLog(type ?: "null", message ?: "null", timestamp) } - } - } - - override fun registerTorListener(l: TorEvents) { - synchronized(torListeners) { - if (torListeners.contains(l)) { - return - } - torListeners.add(l) } } - override fun unregisterTorListener(l: TorEvents) { - synchronized(torListeners) { - if (!torListeners.contains(l)) { + override fun registerRunOnceBootstrapped(rob: RunOnceBootstrapped) { + // TODO Remove need for this with tb-44002 + // it would be nice to have a short circuit run and don't add if already bootstrapped + // however this calls context.components.core.engine which tries to lazy load engine + // which causes a recursive loop. instead we should do the work in tb-44002 + // this is currently fine as there is a single use case for this called in + // TorBrowserFeatures that is at startup + //if (isBootstrapped) { + // rob.onBootstrapped() + // return + //} + synchronized(runOnceBootstrappedHandlers) { + if (runOnceBootstrappedHandlers.contains(rob)) { return } - torListeners.remove(l) + runOnceBootstrappedHandlers.add(rob) } } - override fun registerTorLogListener(l: TorLogs) { - synchronized(torLogListeners) { - if (torLogListeners.contains(l)) { - return - } - torLogListeners.add(l) - } - } - override fun unregisterTorLogListener(l: TorLogs) { - synchronized(torLogListeners) { - if (!torLogListeners.contains(l)) { + override fun unregisterRunOnceBootstrapped(rob: RunOnceBootstrapped) { + synchronized(runOnceBootstrappedHandlers) { + if (!runOnceBootstrappedHandlers.contains(rob)) { return } - torLogListeners.remove(l) + runOnceBootstrappedHandlers.remove(rob) } } @@ -260,82 +176,22 @@ class TorControllerGV( getTorIntegration().cancelBootstrap() } - override fun setTorStopped() { - _lastKnownStatus.value = TorConnectState.Configuring - onTorStatusUpdate(null, lastKnownStatus.toString(), 0.0) - onTorStopped() - } - - override fun restartTor() { - if (!_lastKnownStatus.value.isStarted() && wasTorBootstrapped) { - // If we aren't started, but we were previously bootstrapped, - // then we handle a "restart" request as a "start" restart - initiateTorBootstrap() - } else { - // |isTorRestarting| tracks the state of restart. When we receive an |OFF| state - // from TorService in persistentBroadcastReceiver::onReceive we restart the Tor - // service. - isTorRestarting = true - stopTor() - } - } - - override fun getLastErrorState() : TorError? { - return lastKnownError - } - - // TorEventsBootstrapStateChangeListener -> (lastKnowStatus, TorEvents) - // Handle events from GeckoView TorAndroidIntegration and map to TorEvents based events - // and state for firefox-android (designed for tor-android-service) - // fun onTorConnecting() - // fun onTorConnected() - // fun onTorStatusUpdate(entry: String?, status: String?, progress: Double?) - // fun onTorStopped() - // TorEventsBootstrapStateChangeListener - override fun onBootstrapStateChange(newStateVal: String?) { - Log.d(TAG, "onBootstrapStateChange(newStateVal = $newStateVal)") - val newState: TorConnectState = TorConnectState.valueOf(newStateVal ?: "Error") - - if (newState.isError() && wasTorBootstrapped) { - stopTor() - } - - if (newState.isStarted()) { - wasTorBootstrapped = true - onTorConnected() - } - - if (wasTorBootstrapped && newState == TorConnectState.Configuring) { - wasTorBootstrapped = false - if (isTorRestarting) { - initiateTorBootstrap() - } else { - setTorStopped() + override fun onBootstrapStageChange(stage: TorConnectStage) { + Log.d(TAG, "onBootstrapStageChange(stage = $stage)") + + if (stage.name == TorConnectStageName.Bootstrapped) { + synchronized(runOnceBootstrappedHandlers) { + runOnceBootstrappedHandlers.toList().forEach { + it.onBootstrapped() + runOnceBootstrappedHandlers.remove(it) + } } } - - if (_lastKnownStatus.value.isOff() && newState.isStarting()) { - isTorRestarting = false - } - - _lastKnownStatus.value = newState - onTorStatusUpdate(null, newStateVal, null) } - override fun onBootstrapStageChange(stage: TorConnectStage) = Unit - // TorEventsBootstrapStateChangeListener override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) { Log.d(TAG, "onBootstrapProgress(progress = $progress, hasWarnings = $hasWarnings)") - onTorStatusUpdate("", _lastKnownStatus.value.toTorStatus().status, progress) - } - - // TorEventsBootstrapStateChangeListener - override fun onBootstrapComplete() = Unit - - // TorEventsBootstrapStateChangeListener - override fun onBootstrapError(code: String?, message: String?, phase: String?, reason: String?) { - lastKnownError = TorError(code ?: "", message ?: "", phase ?: "", reason ?: "") } } ===================================== mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorLogsViewModel.kt ===================================== @@ -13,11 +13,15 @@ import android.widget.Toast import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import mozilla.components.browser.engine.gecko.GeckoEngine import org.mozilla.fenix.R import org.mozilla.fenix.ext.components +import org.mozilla.geckoview.TorAndroidIntegration.TorLogListener -class TorLogsViewModel(application: Application) : AndroidViewModel(application), TorLogs { +class TorLogsViewModel(application: Application) : AndroidViewModel(application), TorLogListener { private val torController = application.components.torController + private val engine = application.components.core.engine as GeckoEngine + private val torAndroidIntegration = engine.getTorIntegrationController() private val clipboardManager = application.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager @@ -33,7 +37,7 @@ class TorLogsViewModel(application: Application) : AndroidViewModel(application) init { setupClipboardListener() - torController.registerTorLogListener(this) + torAndroidIntegration.registerLogListener(this) val currentEntries = torController.logEntries for (log in currentEntries) { addLog(log) @@ -46,7 +50,7 @@ class TorLogsViewModel(application: Application) : AndroidViewModel(application) override fun onCleared() { super.onCleared() - torController.unregisterTorLogListener(this) + torAndroidIntegration.unregisterLogListener(this) } private fun setupClipboardListener() { ===================================== mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorAndroidIntegration.java ===================================== @@ -39,11 +39,8 @@ public class TorAndroidIntegration implements BundleEventListener { private static final String EVENT_TOR_STOP = "GeckoView:Tor:StopTor"; private static final String EVENT_MEEK_START = "GeckoView:Tor:StartMeek"; private static final String EVENT_MEEK_STOP = "GeckoView:Tor:StopMeek"; - private static final String EVENT_CONNECT_STATE_CHANGED = "GeckoView:Tor:ConnectStateChanged"; // deprecation path - private static final String EVENT_CONNECT_STAGE_CHANGED = "GeckoView:Tor:ConnectStageChanged"; // replacement path - private static final String EVENT_CONNECT_ERROR = "GeckoView:Tor:ConnectError"; + private static final String EVENT_CONNECT_STAGE_CHANGED = "GeckoView:Tor:ConnectStageChanged"; private static final String EVENT_BOOTSTRAP_PROGRESS = "GeckoView:Tor:BootstrapProgress"; - private static final String EVENT_BOOTSTRAP_COMPLETE = "GeckoView:Tor:BootstrapComplete"; private static final String EVENT_TOR_LOGS = "GeckoView:Tor:Logs"; private static final String EVENT_SETTINGS_READY = "GeckoView:Tor:SettingsReady"; private static final String EVENT_SETTINGS_CHANGED = "GeckoView:Tor:SettingsChanged"; @@ -62,6 +59,7 @@ public class TorAndroidIntegration implements BundleEventListener { private static final String EVENT_QUICKSTART_GET = "GeckoView:Tor:QuickstartGet"; private static final String EVENT_QUICKSTART_SET = "GeckoView:Tor:QuickstartSet"; private static final String EVENT_REGION_NAMES_GET = "GeckoView:Tor:RegionNamesGet"; + private static final String EVENT_SHOULD_SHOW_TOR_CONNECT = "GeckoView:Tor:ShouldShowTorConnect"; private static final String CONTROL_PORT_FILE = "/control-ipc"; private static final String SOCKS_FILE = "/socks-ipc"; @@ -124,11 +122,8 @@ public class TorAndroidIntegration implements BundleEventListener { EVENT_MEEK_STOP, EVENT_SETTINGS_READY, EVENT_SETTINGS_CHANGED, - EVENT_CONNECT_STATE_CHANGED, EVENT_CONNECT_STAGE_CHANGED, - EVENT_CONNECT_ERROR, EVENT_BOOTSTRAP_PROGRESS, - EVENT_BOOTSTRAP_COMPLETE, EVENT_TOR_LOGS); } @@ -157,35 +152,18 @@ public class TorAndroidIntegration implements BundleEventListener { } else { Log.w(TAG, "Ignoring a settings changed event that did not have the new settings."); } - } else if (EVENT_CONNECT_STATE_CHANGED.equals(event)) { - String state = message.getString("state"); - for (BootstrapStateChangeListener listener : mBootstrapStateListeners) { - listener.onBootstrapStateChange(state); - } } else if (EVENT_CONNECT_STAGE_CHANGED.equals(event)) { TorConnectStage stage = new TorConnectStage(message.getBundle("stage")); _lastKnownStage.setValue(stage); for (BootstrapStateChangeListener listener : mBootstrapStateListeners) { listener.onBootstrapStageChange(stage); } - } else if (EVENT_CONNECT_ERROR.equals(event)) { - String code = message.getString("code"); - String msg = message.getString("message"); - String phase = message.getString("phase"); - String reason = message.getString("reason"); - for (BootstrapStateChangeListener listener : mBootstrapStateListeners) { - listener.onBootstrapError(code, msg, phase, reason); - } } else if (EVENT_BOOTSTRAP_PROGRESS.equals(event)) { double progress = message.getDouble("progress"); boolean hasWarnings = message.getBoolean("hasWarnings"); for (BootstrapStateChangeListener listener : mBootstrapStateListeners) { listener.onBootstrapProgress(progress, hasWarnings); } - } else if (EVENT_BOOTSTRAP_COMPLETE.equals(event)) { - for (BootstrapStateChangeListener listener : mBootstrapStateListeners) { - listener.onBootstrapComplete(); - } } else if (EVENT_TOR_LOGS.equals(event)) { String msg = message.getString("message"); String type = message.getString("logType"); @@ -647,15 +625,9 @@ public class TorAndroidIntegration implements BundleEventListener { } public interface BootstrapStateChangeListener { - void onBootstrapStateChange(String state); // depreaction path - - void onBootstrapStageChange(TorConnectStage stage); // new upgrade + void onBootstrapStageChange(@NonNull TorConnectStage stage); // new upgrade void onBootstrapProgress(double progress, boolean hasWarnings); - - void onBootstrapComplete(); - - void onBootstrapError(String code, String message, String phase, String reason); } public interface TorLogListener { @@ -736,6 +708,17 @@ public class TorAndroidIntegration implements BundleEventListener { }); } + public interface ShouldShowTorConnectGetter { + void onValue(Boolean shouldShowTorConnect); + } + + public void shouldShowTorConnectGet(ShouldShowTorConnectGetter shouldShowTorConnectGetter) { + EventDispatcher.getInstance().queryBoolean(EVENT_SHOULD_SHOW_TOR_CONNECT).then(shouldShowTorConnect -> { + shouldShowTorConnectGetter.onValue(shouldShowTorConnect); + return new GeckoResult<Void>(); + }); + } + public @NonNull GeckoResult<Void> beginBootstrap() { return EventDispatcher.getInstance().queryVoid(EVENT_BOOTSTRAP_BEGIN); } @@ -754,21 +737,21 @@ public class TorAndroidIntegration implements BundleEventListener { return EventDispatcher.getInstance().queryVoid(EVENT_BOOTSTRAP_CANCEL); } - public void registerBootstrapStateChangeListener(BootstrapStateChangeListener listener) { + public synchronized void registerBootstrapStateChangeListener(BootstrapStateChangeListener listener) { mBootstrapStateListeners.add(listener); } - public void unregisterBootstrapStateChangeListener(BootstrapStateChangeListener listener) { + public synchronized void unregisterBootstrapStateChangeListener(BootstrapStateChangeListener listener) { mBootstrapStateListeners.remove(listener); } private final HashSet<BootstrapStateChangeListener> mBootstrapStateListeners = new HashSet<>(); - public void registerLogListener(TorLogListener listener) { + public synchronized void registerLogListener(TorLogListener listener) { mLogListeners.add(listener); } - public void unregisterLogListener(TorLogListener listener) { + public synchronized void unregisterLogListener(TorLogListener listener) { mLogListeners.remove(listener); } ===================================== mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorConnectError.java ===================================== @@ -0,0 +1,24 @@ +package org.mozilla.geckoview; + +import org.mozilla.gecko.util.GeckoBundle; + +public class TorConnectError { + public String code; + public String message; + public String phase; + public String reason; + + public TorConnectError(GeckoBundle bundle) { + code = bundle.getString("code"); + message = bundle.getString("message"); + phase = bundle.getString("phase"); + reason = bundle.getString("reason"); + } + + public TorConnectError(String code, String message, String phase, String reason) { + this.code = code; + this.message = message; + this.phase = phase; + this.reason = reason; + } +} ===================================== mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorConnectStage.java ===================================== @@ -5,24 +5,10 @@ import org.mozilla.gecko.util.GeckoBundle; // Class to receive ConnectStage object from TorConnect.sys.mjs ~ln677 public class TorConnectStage { - public class Error { - public String code; - public String message; - public String phase; - public String reason; - - public Error(GeckoBundle bundle) { - code = bundle.getString("code"); - message = bundle.getString("message"); - phase = bundle.getString("phase"); - reason = bundle.getString("reason"); - } - } - public TorConnectStageName name; // The TorConnectStage prior to this bootstrap attempt. Only set during the "Bootstrapping" stage. public TorConnectStageName bootstrapTrigger; - public Error error; + public TorConnectError error; public String defaultRegion; public Boolean potentiallyBlocked; public Boolean tryAgain; @@ -37,7 +23,7 @@ public class TorConnectStage { potentiallyBlocked = bundle.getBoolean("potentiallyBlocked"); tryAgain = bundle.getBoolean("tryAgain"); if (bundle.getBundle("error") != null) { - error = new Error(bundle.getBundle("error")); + error = new TorConnectError(bundle.getBundle("error")); } bootstrappingStatus = new TorBootstrappingStatus(bundle.getBundle("bootstrappingStatus")); } ===================================== toolkit/modules/TorAndroidIntegration.sys.mjs ===================================== @@ -28,7 +28,6 @@ const EmittedEvents = Object.freeze({ settingsChanged: "GeckoView:Tor:SettingsChanged", connectStateChanged: "GeckoView:Tor:ConnectStateChanged", // deprecation path connectStageChanged: "GeckoView:Tor:ConnectStageChanged", // new replacement path - connectError: "GeckoView:Tor:ConnectError", bootstrapProgress: "GeckoView:Tor:BootstrapProgress", bootstrapComplete: "GeckoView:Tor:BootstrapComplete", torLogs: "GeckoView:Tor:Logs", @@ -49,6 +48,7 @@ const ListenedEvents = Object.freeze({ quickstartGet: "GeckoView:Tor:QuickstartGet", quickstartSet: "GeckoView:Tor:QuickstartSet", regionNamesGet: "GeckoView:Tor:RegionNamesGet", + shouldShowTorConnectGet: "GeckoView:Tor:ShouldShowTorConnect", }); class TorAndroidIntegrationImpl { @@ -134,16 +134,6 @@ class TorAndroidIntegrationImpl { type: EmittedEvents.bootstrapComplete, }); break; - // TODO: Replace with StageChange stage.error. - case lazy.TorConnectTopics.Error: - lazy.EventDispatcher.instance.sendRequest({ - type: EmittedEvents.connectError, - code: subj.wrappedJSObject.code ?? "", - message: subj.wrappedJSObject.message ?? "", - phase: subj.wrappedJSObject.cause?.phase ?? "", - reason: subj.wrappedJSObject.cause?.reason ?? "", - }); - break; case lazy.TorProviderTopics.TorLog: lazy.EventDispatcher.instance.sendRequest({ type: EmittedEvents.torLogs, @@ -225,6 +215,9 @@ class TorAndroidIntegrationImpl { case ListenedEvents.regionNamesGet: callback?.onSuccess(lazy.TorConnect.getRegionNames()); return; + case ListenedEvents.shouldShowTorConnectGet: + callback?.onSuccess(lazy.TorConnect.shouldShowTorConnect()); + return; } callback?.onSuccess(); } catch (e) { View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/bf58fe3... -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/bf58fe3... You're receiving this email because of your account on gitlab.torproject.org.