tbb-commits
Threads by month
- ----- 2025 -----
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- 1 participants
- 18612 discussions

[Git][tpo/applications/tor-browser][tor-browser-115.7.0esr-13.5-1] 2 commits: fixup! Bug 40597: Implement TorSettings module
by richard (@richard) 26 Jan '24
by richard (@richard) 26 Jan '24
26 Jan '24
richard pushed to branch tor-browser-115.7.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits:
bb028644 by Pier Angelo Vendrame at 2024-01-26T11:31:15+01:00
fixup! Bug 40597: Implement TorSettings module
Bug 42384: Use TorSettings's initialization promise to handle race
conditions
We now have a race condition for which the notification about
TorSettings being ready is sent before TorConnect can observe this
topic.
A while ago, we introduced an initialization promise to TorSettings,
therefore we can use it instead of Service.obs and avoid this race
condition.
Also, fixed quicksettings ignored in TorSettings.setSettings.
- - - - -
a48e4389 by Pier Angelo Vendrame at 2024-01-26T11:35:10+01:00
fixup! Bug 42247: Android helpers for the TorProvider
Update the cached mSettings whenever someone one the GeckoView side
changes any TorSettings settings.
- - - - -
4 changed files:
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorIntegrationAndroid.java
- toolkit/modules/TorAndroidIntegration.sys.mjs
- toolkit/modules/TorConnect.sys.mjs
- toolkit/modules/TorSettings.sys.mjs
Changes:
=====================================
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorIntegrationAndroid.java
=====================================
@@ -47,6 +47,8 @@ public class TorIntegrationAndroid implements BundleEventListener {
private static final String EVENT_BOOTSTRAP_COMPLETE = "GeckoView:Tor:BootstrapComplete";
private static final String EVENT_BOOTSTRAP_ERROR = "GeckoView:Tor:BootstrapError";
private static final String EVENT_SETTINGS_OPEN = "GeckoView:Tor:OpenSettings";
+ private static final String EVENT_SETTINGS_READY = "GeckoView:Tor:SettingsReady";
+ private static final String EVENT_SETTINGS_CHANGED = "GeckoView:Tor:SettingsChanged";
// Events we emit
private static final String EVENT_SETTINGS_GET = "GeckoView:Tor:SettingsGet";
@@ -57,7 +59,6 @@ public class TorIntegrationAndroid implements BundleEventListener {
private static final String EVENT_BOOTSTRAP_BEGIN_AUTO = "GeckoView:Tor:BootstrapBeginAuto";
private static final String EVENT_BOOTSTRAP_CANCEL = "GeckoView:Tor:BootstrapCancel";
private static final String EVENT_BOOTSTRAP_GET_STATE = "GeckoView:Tor:BootstrapGetState";
- private static final String EVENT_SETTINGS_READY = "GeckoView:Tor:SettingsReady";
private static final String CONTROL_PORT_FILE = "/control-ipc";
private static final String SOCKS_FILE = "/socks-ipc";
@@ -112,6 +113,7 @@ public class TorIntegrationAndroid implements BundleEventListener {
EVENT_MEEK_START,
EVENT_MEEK_STOP,
EVENT_SETTINGS_READY,
+ EVENT_SETTINGS_CHANGED,
EVENT_BOOTSTRAP_STATE_CHANGED,
EVENT_BOOTSTRAP_PROGRESS,
EVENT_BOOTSTRAP_COMPLETE,
@@ -136,6 +138,14 @@ public class TorIntegrationAndroid implements BundleEventListener {
} catch(Exception e) {
Log.e(TAG, "SettingsLoader error: "+ e.toString());
}
+ } else if (EVENT_SETTINGS_CHANGED.equals(event)) {
+ GeckoBundle newSettings = message.getBundle("settings");
+ if (newSettings != null) {
+ // TODO: Should we notify listeners?
+ mSettings = new TorSettings(newSettings);
+ } else {
+ Log.w(TAG, "Ignoring a settings changed event that did not have the new settings.");
+ }
} else if (EVENT_BOOTSTRAP_STATE_CHANGED.equals(event)) {
String state = message.getString("state");
for (BootstrapStateChangeListener listener: mBootstrapStateListeners) {
=====================================
toolkit/modules/TorAndroidIntegration.sys.mjs
=====================================
@@ -124,6 +124,15 @@ class TorAndroidIntegrationImpl {
settings: lazy.TorSettings.getSettings(),
});
break;
+ case lazy.TorSettingsTopics.SettingsChanged:
+ // For Android we push also the settings object to avoid a round trip on
+ // the event dispatcher.
+ lazy.EventDispatcher.instance.sendRequest({
+ type: EmittedEvents.settingsChanged,
+ changes: subj.wrappedJSObject.changes ?? [],
+ settings: lazy.TorSettings.getSettings(),
+ });
+ break;
}
}
=====================================
toolkit/modules/TorConnect.sys.mjs
=====================================
@@ -878,10 +878,11 @@ export const TorConnect = (() => {
console.log(`TorConnect: Observing topic '${addTopic}'`);
};
+ TorSettings.initializedPromise.then(() => this._settingsInitialized());
+
// register the Tor topics we always care about
observeTopic(TorTopics.ProcessExited);
observeTopic(TorTopics.LogHasWarnOrErr);
- observeTopic(TorSettingsTopics.Ready);
}
},
@@ -889,29 +890,6 @@ export const TorConnect = (() => {
console.log(`TorConnect: Observed ${topic}`);
switch (topic) {
- /* We need to wait until TorSettings have been loaded and applied before we can Quickstart */
- case TorSettingsTopics.Ready: {
- // tor-browser#41907: This is only a workaround to avoid users being
- // bounced back to the initial panel without any explanation.
- // Longer term we should disable the clickable elements, or find a UX
- // to prevent this from happening (e.g., allow buttons to be clicked,
- // but show an intermediate starting state, or a message that tor is
- // starting while the butons are disabled, etc...).
- if (this.state !== TorConnectState.Initial) {
- console.warn(
- "TorConnect: Seen the torsettings:ready after the state has already changed, ignoring the notification."
- );
- break;
- }
- if (this.shouldQuickStart) {
- // Quickstart
- this._changeState(TorConnectState.Bootstrapping);
- } else {
- // Configuring
- this._changeState(TorConnectState.Configuring);
- }
- break;
- }
case TorTopics.LogHasWarnOrErr: {
this._logHasWarningOrError = true;
break;
@@ -941,6 +919,28 @@ export const TorConnect = (() => {
}
},
+ _settingsInitialized() {
+ // tor-browser#41907: This is only a workaround to avoid users being
+ // bounced back to the initial panel without any explanation.
+ // Longer term we should disable the clickable elements, or find a UX
+ // to prevent this from happening (e.g., allow buttons to be clicked,
+ // but show an intermediate starting state, or a message that tor is
+ // starting while the butons are disabled, etc...).
+ if (this.state !== TorConnectState.Initial) {
+ console.warn(
+ "TorConnect: Seen the torsettings:ready after the state has already changed, ignoring the notification."
+ );
+ return;
+ }
+ if (this.shouldQuickStart) {
+ // Quickstart
+ this._changeState(TorConnectState.Bootstrapping);
+ } else {
+ // Configuring
+ this._changeState(TorConnectState.Configuring);
+ }
+ },
+
/*
Various getters
*/
=====================================
toolkit/modules/TorSettings.sys.mjs
=====================================
@@ -992,6 +992,10 @@ class TorSettingsImpl {
// Hold off on lots of notifications until all settings are changed.
this.freezeNotifications();
try {
+ if ("quickstart" in settings) {
+ this.quickstart.enabled = !!settings.quickstart.enabled;
+ }
+
if ("bridges" in settings) {
this.bridges.enabled = !!settings.bridges.enabled;
// Currently, disabling bridges in the UI does not remove the lines,
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/db59cb…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/db59cb…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/firefox-android][firefox-android-115.2.1-13.5-1] fixup! Add Tor integration and UI
by Dan Ballard (@dan) 26 Jan '24
by Dan Ballard (@dan) 26 Jan '24
26 Jan '24
Dan Ballard pushed to branch firefox-android-115.2.1-13.5-1 at The Tor Project / Applications / firefox-android
Commits:
6d0a6453 by Dan Ballard at 2024-01-25T16:15:45-08:00
fixup! Add Tor integration and UI
Bug 42252: Make TorController and interface and add a Geckoview implementation
- - - - -
4 changed files:
- fenix/app/src/main/java/org/mozilla/fenix/components/Components.kt
- fenix/app/src/main/java/org/mozilla/fenix/tor/TorController.kt
- + fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerGV.kt
- + fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerTAS.kt
Changes:
=====================================
fenix/app/src/main/java/org/mozilla/fenix/components/Components.kt
=====================================
@@ -43,7 +43,8 @@ import org.mozilla.fenix.perf.StartupActivityLog
import org.mozilla.fenix.perf.StartupStateProvider
import org.mozilla.fenix.perf.StrictModeManager
import org.mozilla.fenix.perf.lazyMonitored
-import org.mozilla.fenix.tor.TorController
+import org.mozilla.fenix.tor.TorControllerGV
+import org.mozilla.fenix.tor.TorControllerTAS
import org.mozilla.fenix.utils.ClipboardHandler
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.wifi.WifiConnectionMonitor
@@ -201,7 +202,7 @@ class Components(private val context: Context) {
),
)
}
- val torController by lazyMonitored { TorController(context) }
+ val torController by lazyMonitored { if (settings.useNewBootstrap) TorControllerGV(context) else TorControllerTAS(context) }
}
/**
=====================================
fenix/app/src/main/java/org/mozilla/fenix/tor/TorController.kt
=====================================
@@ -4,22 +4,7 @@
package org.mozilla.fenix.tor
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
import androidx.lifecycle.LifecycleCoroutineScope
-import androidx.localbroadcastmanager.content.LocalBroadcastManager
-
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withTimeoutOrNull
-
-import org.mozilla.fenix.BuildConfig
-
-import org.torproject.android.service.TorService
-import org.torproject.android.service.TorServiceConstants
-import org.torproject.android.service.util.Prefs
interface TorEvents {
fun onTorConnecting()
@@ -28,347 +13,59 @@ interface TorEvents {
fun onTorStopped()
}
-private enum class TorStatus {
- OFF,
- STARTING,
- ON,
- STOPPING,
- UNKNOWN;
+internal enum class TorStatus(val status: String) {
+ OFF("OFF"),
+ STARTING("STARTING"),
+ ON("ON"),
+ STOPPING("STOPPING"),
+ UNKNOWN("UNKNOWN");
- fun getStateFromString(status: String): TorStatus {
- return when (status) {
- TorServiceConstants.STATUS_ON -> ON
- TorServiceConstants.STATUS_STARTING -> STARTING
- TorServiceConstants.STATUS_STOPPING -> STOPPING
- TorServiceConstants.STATUS_OFF -> OFF
- else -> UNKNOWN
+ companion object {
+ fun fromString(status: String): TorStatus {
+ return when (status) {
+ "ON" -> ON
+ "STARTING" -> STARTING
+ "STOPPING" -> STOPPING
+ "OFF" -> OFF
+ else -> UNKNOWN
+ }
}
}
fun isOff() = this == OFF
fun isOn() = this == ON
fun isStarting() = this == STARTING
- fun isStarted() = ((this == TorStatus.STARTING) || (this == TorStatus.ON))
+ fun isStarted() = ((this == STARTING) || (this == ON))
fun isStopping() = this == STOPPING
fun isUnknown() = this == UNKNOWN
}
-@SuppressWarnings("TooManyFunctions")
-class TorController(
- private val context: Context
-) : TorEvents {
-
- private val lbm: LocalBroadcastManager = LocalBroadcastManager.getInstance(context)
- private val entries = mutableListOf<Pair<String?, String?>>()
- val logEntries get() = entries
-
- private var torListeners = mutableListOf<TorEvents>()
-
- private var pendingRegisterChangeList = mutableListOf<Pair<TorEvents, Boolean>>()
- private var lockTorListenersMutation = false
-
- private var lastKnownStatus = TorStatus.OFF
- private var wasTorBootstrapped = false
- private var isTorRestarting = false
-
- // This may be a lie
- private var isTorBootstrapped = false
- get() = ((lastKnownStatus == TorStatus.ON) && wasTorBootstrapped)
-
- val isDebugLoggingEnabled get() =
- context
- .getSharedPreferences("org.torproject.android_preferences", Context.MODE_PRIVATE)
- .getBoolean("pref_enable_logging", false)
-
- val isStarting get() = lastKnownStatus.isStarting()
- val isRestarting get() = isTorRestarting
- val isBootstrapped get() = isTorBootstrapped
- val isConnected get() = (lastKnownStatus.isStarted() && !isTorRestarting)
-
+interface TorController: TorEvents {
+ val logEntries: MutableList<Pair<String?, String?>>
+ val isStarting: Boolean
+ val isRestarting: Boolean
+ val isBootstrapped: Boolean
+ val isConnected: Boolean
var bridgesEnabled: Boolean
- get() = Prefs.bridgesEnabled()
- set(value) { Prefs.putBridgesEnabled(value) }
-
var bridgeTransport: TorBridgeTransportConfig
- get() {
- return TorBridgeTransportConfigUtil.getStringToBridgeTransport(
- Prefs.getBridgesList()
- )
- }
- set(value) {
- if (value == TorBridgeTransportConfig.USER_PROVIDED) {
- // Don't set the pref when the value is USER_PROVIDED because
- // "user_provided" is not a valid bridge or transport type.
- // This call should be followed by setting userProvidedBridges.
- return
- }
- Prefs.setBridgesList(value.transportName)
- }
-
var userProvidedBridges: String?
- get() {
- val bridges = Prefs.getBridgesList()
- val bridgeType =
- TorBridgeTransportConfigUtil.getStringToBridgeTransport(bridges)
- return when (bridgeType) {
- TorBridgeTransportConfig.USER_PROVIDED -> bridges
- else -> null
- }
- }
- set(value) {
- Prefs.setBridgesList(value)
- }
-
- fun start() {
- // Register receiver
- lbm.registerReceiver(
- persistentBroadcastReceiver,
- IntentFilter(TorServiceConstants.ACTION_STATUS)
- )
- lbm.registerReceiver(
- persistentBroadcastReceiver,
- IntentFilter(TorServiceConstants.LOCAL_ACTION_LOG)
- )
- }
- fun stop() {
- lbm.unregisterReceiver(persistentBroadcastReceiver)
- }
+ fun start()
+ fun stop()
- private val persistentBroadcastReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- if (intent.action == null ||
- (intent.action != TorServiceConstants.ACTION_STATUS &&
- intent.action != TorServiceConstants.LOCAL_ACTION_LOG)
- ) {
- return
- }
- val action = intent.action
+ override fun onTorConnecting()
+ override fun onTorConnected()
+ override fun onTorStatusUpdate(entry: String?, status: String?)
+ override fun onTorStopped()
- val logentry: String?
- val status: String?
- if (action == TorServiceConstants.LOCAL_ACTION_LOG) {
- logentry = intent.getExtras()
- ?.getCharSequence(TorServiceConstants.LOCAL_EXTRA_LOG) as? String?
- } else {
- logentry = null
- }
-
- status = intent.getExtras()
- ?.getCharSequence(TorServiceConstants.EXTRA_STATUS) as? String?
-
- if (logentry == null && status == null) {
- return
- }
-
- onTorStatusUpdate(logentry, status)
-
- if (status == null) {
- return
- }
-
- val newStatus = lastKnownStatus.getStateFromString(status)
-
- if (newStatus.isUnknown() && wasTorBootstrapped) {
- stopTor()
- }
-
- entries.add(Pair(logentry, status))
-
- if (logentry != null && logentry.contains(TorServiceConstants.TOR_CONTROL_PORT_MSG_BOOTSTRAP_DONE)) {
- wasTorBootstrapped = true
- onTorConnected()
- }
-
- if (lastKnownStatus.isStopping() && newStatus.isOff()) {
- if (isTorRestarting) {
- initiateTorBootstrap()
- } else {
- onTorStopped()
- }
- }
-
- if (lastKnownStatus.isOff() && newStatus.isStarting()) {
- isTorRestarting = false
- }
-
- lastKnownStatus = newStatus
- }
- }
-
- override fun onTorConnecting() {
- lockTorListenersMutation = true
- torListeners.forEach { it.onTorConnecting() }
- lockTorListenersMutation = false
-
- handlePendingRegistrationChanges()
- }
-
- override fun onTorConnected() {
- lockTorListenersMutation = true
- torListeners.forEach { it.onTorConnected() }
- lockTorListenersMutation = false
-
- handlePendingRegistrationChanges()
- }
+ fun registerTorListener(l: TorEvents)
+ fun unregisterTorListener(l: TorEvents)
- override fun onTorStatusUpdate(entry: String?, status: String?) {
- lockTorListenersMutation = true
- torListeners.forEach { it.onTorStatusUpdate(entry, status) }
- lockTorListenersMutation = false
-
- handlePendingRegistrationChanges()
- }
-
- override fun onTorStopped() {
- lockTorListenersMutation = true
- torListeners.forEach { it.onTorStopped() }
- lockTorListenersMutation = false
-
- handlePendingRegistrationChanges()
- }
-
- fun registerTorListener(l: TorEvents) {
- if (torListeners.contains(l)) {
- return
- }
-
- if (lockTorListenersMutation) {
- pendingRegisterChangeList.add(Pair(l, true))
- } else {
- torListeners.add(l)
- }
- }
-
- fun unregisterTorListener(l: TorEvents) {
- if (!torListeners.contains(l)) {
- return
- }
-
- if (lockTorListenersMutation) {
- pendingRegisterChangeList.add(Pair(l, false))
- } else {
- torListeners.remove(l)
- }
- }
-
- private fun handlePendingRegistrationChanges() {
- pendingRegisterChangeList.forEach {
- if (it.second) {
- registerTorListener(it.first)
- } else {
- unregisterTorListener(it.first)
- }
- }
-
- pendingRegisterChangeList.clear()
- }
-
- /**
- * Receive the current Tor status.
- *
- * Send a request for the current status and receive the response.
- * Returns true if Tor is running, false otherwise.
- *
- */
- private suspend fun checkTorIsStarted(): Boolean {
- val channel = Channel<Boolean>()
-
- // Register receiver
- val lbm: LocalBroadcastManager = LocalBroadcastManager.getInstance(context)
- val localBroadcastReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- val action = intent.action ?: return
- // We only want ACTION_STATUS messages
- if (action != TorServiceConstants.ACTION_STATUS) {
- return
- }
- // The current status has the EXTRA_STATUS key
- val currentStatus =
- intent.getStringExtra(TorServiceConstants.EXTRA_STATUS)
- channel.trySend(currentStatus === TorServiceConstants.STATUS_ON)
- }
- }
- lbm.registerReceiver(
- localBroadcastReceiver,
- IntentFilter(TorServiceConstants.ACTION_STATUS)
- )
-
- // Request service status
- sendServiceAction(TorServiceConstants.ACTION_STATUS)
-
- // Wait for response and unregister receiver
- var torIsStarted = false
- withTimeoutOrNull(torServiceResponseTimeout) {
- torIsStarted = channel.receive()
- }
- lbm.unregisterReceiver(localBroadcastReceiver)
- return torIsStarted
- }
-
- fun initiateTorBootstrap(lifecycleScope: LifecycleCoroutineScope? = null, withDebugLogging: Boolean = false) {
- if (BuildConfig.DISABLE_TOR) {
- return
- }
-
- context.getSharedPreferences("org.torproject.android_preferences", Context.MODE_PRIVATE)
- .edit().putBoolean("pref_enable_logging", withDebugLogging).apply()
-
- if (lifecycleScope == null) {
- sendServiceAction(TorServiceConstants.ACTION_START)
- } else {
- lifecycleScope.launch {
- val torNeedsStart = !checkTorIsStarted()
- if (torNeedsStart) {
- sendServiceAction(TorServiceConstants.ACTION_START)
- }
- }
- }
- }
-
- fun stopTor() {
- if (BuildConfig.DISABLE_TOR) {
- return
- }
-
- val torService = Intent(context, TorService::class.java)
- context.stopService(torService)
- }
-
- fun setTorStopped() {
- lastKnownStatus = TorStatus.OFF
- onTorStopped()
- }
-
- fun restartTor() {
- // tor-android-service doesn't dynamically update the torrc file,
- // and it doesn't use SETCONF, so we completely restart the service.
- // However, don't restart if we aren't started and we weren't
- // previously started.
- if (!lastKnownStatus.isStarted() && !wasTorBootstrapped) {
- return
- }
+ fun initiateTorBootstrap(lifecycleScope: LifecycleCoroutineScope? = null, withDebugLogging: Boolean = false)
+ fun stopTor()
+ fun setTorStopped()
+ fun restartTor()
+}
- if (!lastKnownStatus.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()
- }
- }
- private fun sendServiceAction(action: String) {
- val torServiceStatus = Intent(context, TorService::class.java)
- torServiceStatus.action = action
- context.startService(torServiceStatus)
- }
- companion object {
- const val torServiceResponseTimeout = 5000L
- }
-}
=====================================
fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerGV.kt
=====================================
@@ -0,0 +1,289 @@
+package org.mozilla.fenix.tor
+
+
+import android.content.Context
+import android.util.Log
+import androidx.lifecycle.LifecycleCoroutineScope
+import mozilla.components.browser.engine.gecko.GeckoEngine
+import org.mozilla.fenix.ext.components
+import org.mozilla.geckoview.TorIntegrationAndroid
+import org.mozilla.geckoview.TorIntegrationAndroid.BootstrapStateChangeListener
+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 {
+
+ private val TAG = "TorControllerGV"
+
+ private var torListeners = mutableListOf<TorEvents>()
+
+ private var lastKnownStatus = TorConnectState.Initial
+ private var wasTorBootstrapped = false
+ private var isTorRestarting = false
+
+ private var isTorBootstrapped = false
+ get() = ((lastKnownStatus.isStarted()) && wasTorBootstrapped)
+
+ private val entries = mutableListOf<Pair<String?, String?>>()
+ override val logEntries get() = entries
+ override val isStarting get() = lastKnownStatus.isStarting()
+ override val isRestarting get() = isTorRestarting
+ override val isBootstrapped get() = isTorBootstrapped
+ override val isConnected get() = (lastKnownStatus.isStarted() && !isTorRestarting)
+
+ private fun getTorIntegration(): TorIntegrationAndroid {
+ return (context.components.core.engine as GeckoEngine).getTorIntegrationController()
+ }
+
+ private fun getTorSettings(): TorSettings? {
+ return getTorIntegration().getSettings()
+ }
+
+ override var bridgesEnabled: Boolean
+ get() {
+ return getTorSettings()?.bridgesEnabled ?: false
+ }
+ set(value) {
+ getTorSettings()?.let {
+ it.bridgesEnabled = value
+ getTorIntegration().setSettings(it, true, true)
+ }
+ }
+
+
+ override var bridgeTransport: TorBridgeTransportConfig
+ get() {
+ return when (getTorSettings()?.bridgesSource) {
+ BridgeSource.BuiltIn -> {
+ when (getTorSettings()?.bridgesBuiltinType) {
+ BridgeBuiltinType.Obfs4 -> TorBridgeTransportConfig.BUILTIN_OBFS4
+ BridgeBuiltinType.MeekAzure -> TorBridgeTransportConfig.BUILTIN_MEEK_AZURE
+ BridgeBuiltinType.Snowflake -> TorBridgeTransportConfig.BUILTIN_SNOWFLAKE
+ else -> TorBridgeTransportConfig.USER_PROVIDED
+ }
+
+ }
+
+ BridgeSource.UserProvided -> TorBridgeTransportConfig.USER_PROVIDED
+ else -> TorBridgeTransportConfig.USER_PROVIDED
+ }
+ }
+ set(value) {
+ getTorSettings()?.let {
+ if (value == TorBridgeTransportConfig.USER_PROVIDED) {
+ it.bridgesSource = BridgeSource.BuiltIn
+ } else {
+ val bbt: BridgeBuiltinType = when (value) {
+ TorBridgeTransportConfig.BUILTIN_OBFS4 -> BridgeBuiltinType.Obfs4
+ TorBridgeTransportConfig.BUILTIN_MEEK_AZURE -> BridgeBuiltinType.MeekAzure
+ TorBridgeTransportConfig.BUILTIN_SNOWFLAKE -> BridgeBuiltinType.Snowflake
+ else -> BridgeBuiltinType.Invalid
+ }
+ it.bridgesBuiltinType = bbt
+ }
+ getTorIntegration().setSettings(it, true, true)
+ }
+ }
+
+
+ override var userProvidedBridges: String?
+ get() {
+ return getTorSettings()?.bridgeBridgeStrings?.joinToString("\r\n")
+ }
+ set(value) {
+ getTorSettings()?.let {
+ it.bridgeBridgeStrings = value?.split("\r\n")?.toTypedArray() ?: arrayOf<String>()
+ getTorIntegration().setSettings(it, true, true)
+ }
+ }
+
+ override fun start() {
+ getTorIntegration().registerBootstrapStateChangeListener(this)
+ }
+
+ override fun stop() {
+ getTorIntegration().unregisterBootstrapStateChangeListener(this)
+ }
+
+ // TorEvents
+ override fun onTorConnecting() {
+ synchronized(torListeners) {
+ torListeners.forEach { it.onTorConnecting() }
+ }
+ }
+
+ // TorEvents
+ override fun onTorConnected() {
+ synchronized(torListeners) {
+ torListeners.forEach { it.onTorConnected() }
+ }
+ }
+
+ // TorEvents
+ override fun onTorStatusUpdate(entry: String?, status: String?) {
+ synchronized(torListeners) {
+ torListeners.forEach { it.onTorStatusUpdate(entry, status) }
+ }
+ }
+
+ // TorEvents
+ override fun onTorStopped() {
+ synchronized(torListeners) {
+ torListeners.forEach { it.onTorStopped() }
+ }
+ }
+
+ 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)) {
+ return
+ }
+ torListeners.remove(l)
+ }
+ }
+
+ override fun initiateTorBootstrap(
+ lifecycleScope: LifecycleCoroutineScope?,
+ withDebugLogging: Boolean,
+ ) {
+ getTorIntegration().beginBootstrap()
+ }
+
+ override fun stopTor() {
+ getTorIntegration().cancelBootstrap()
+ }
+
+ override fun setTorStopped() {
+ lastKnownStatus = TorConnectState.Disabled
+ onTorStopped()
+ }
+
+ override fun restartTor() {
+ if (!lastKnownStatus.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()
+ }
+ }
+
+ // 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?)
+ // fun onTorStopped()
+
+ // TorEventsBootstrapStateChangeListener
+ override fun onBootstrapStateChange(newStateVal: String?) {
+ Log.d(TAG, "onBootstrapStateChange($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 {
+ onTorStopped()
+ }
+ }
+
+ if (lastKnownStatus.isOff() && newState.isStarting()) {
+ isTorRestarting = false
+ }
+
+ lastKnownStatus = newState
+
+ }
+
+ // TorEventsBootstrapStateChangeListener
+ override fun onBootstrapProgress(progress: Double, status: String?, hasWarnings: Boolean) {
+ Log.d(TAG, "onBootstrapProgress($progress, $status, $hasWarnings)")
+ if (progress == 100.0) {
+ lastKnownStatus = TorConnectState.Bootstrapped
+ wasTorBootstrapped = true
+ onTorConnected()
+ } else {
+ lastKnownStatus = TorConnectState.Bootstrapping
+ onTorConnecting()
+
+ }
+ entries.add(Pair(status, lastKnownStatus.toTorStatus().status))
+ onTorStatusUpdate(status, lastKnownStatus.toTorStatus().status)
+ }
+
+ // TorEventsBootstrapStateChangeListener
+ override fun onBootstrapComplete() {
+ lastKnownStatus = TorConnectState.Bootstrapped
+ this.onTorConnected()
+ }
+
+ // TorEventsBootstrapStateChangeListener
+ override fun onBootstrapError(message: String?, details: String?) {
+ lastKnownStatus = TorConnectState.Error
+ }
+
+ // TorEventsBootstrapStateChangeListener
+ override fun onSettingsRequested() {
+ // noop
+ }
+}
=====================================
fenix/app/src/main/java/org/mozilla/fenix/tor/TorControllerTAS.kt
=====================================
@@ -0,0 +1,332 @@
+package org.mozilla.fenix.tor
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import androidx.lifecycle.LifecycleCoroutineScope
+import androidx.localbroadcastmanager.content.LocalBroadcastManager
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withTimeoutOrNull
+import org.mozilla.fenix.BuildConfig
+import org.torproject.android.service.TorService
+import org.torproject.android.service.TorServiceConstants
+import org.torproject.android.service.util.Prefs
+
+@SuppressWarnings("TooManyFunctions")
+class TorControllerTAS (private val context: Context): TorController {
+ private val lbm: LocalBroadcastManager = LocalBroadcastManager.getInstance(context)
+ private val entries = mutableListOf<Pair<String?, String?>>()
+ override val logEntries get() = entries
+
+ private var torListeners = mutableListOf<TorEvents>()
+
+ private var pendingRegisterChangeList = mutableListOf<Pair<TorEvents, Boolean>>()
+ private var lockTorListenersMutation = false
+
+ private var lastKnownStatus = TorStatus.OFF
+ private var wasTorBootstrapped = false
+ private var isTorRestarting = false
+
+ // This may be a lie
+ private var isTorBootstrapped = false
+ get() = ((lastKnownStatus == TorStatus.ON) && wasTorBootstrapped)
+
+ val isDebugLoggingEnabled get() =
+ context
+ .getSharedPreferences("org.torproject.android_preferences", Context.MODE_PRIVATE)
+ .getBoolean("pref_enable_logging", false)
+
+ override val isStarting get() = lastKnownStatus.isStarting()
+ override val isRestarting get() = isTorRestarting
+ override val isBootstrapped get() = isTorBootstrapped
+ override val isConnected get() = (lastKnownStatus.isStarted() && !isTorRestarting)
+
+ override var bridgesEnabled: Boolean
+ get() = Prefs.bridgesEnabled()
+ set(value) { Prefs.putBridgesEnabled(value) }
+
+ override var bridgeTransport: TorBridgeTransportConfig
+ get() {
+ return TorBridgeTransportConfigUtil.getStringToBridgeTransport(
+ Prefs.getBridgesList()
+ )
+ }
+ set(value) {
+ if (value == TorBridgeTransportConfig.USER_PROVIDED) {
+ // Don't set the pref when the value is USER_PROVIDED because
+ // "user_provided" is not a valid bridge or transport type.
+ // This call should be followed by setting userProvidedBridges.
+ return
+ }
+ Prefs.setBridgesList(value.transportName)
+ }
+
+ override var userProvidedBridges: String?
+ get() {
+ val bridges = Prefs.getBridgesList()
+ val bridgeType =
+ TorBridgeTransportConfigUtil.getStringToBridgeTransport(bridges)
+ return when (bridgeType) {
+ TorBridgeTransportConfig.USER_PROVIDED -> bridges
+ else -> null
+ }
+ }
+ set(value) {
+ Prefs.setBridgesList(value)
+ }
+
+ override fun start() {
+ // Register receiver
+ lbm.registerReceiver(
+ persistentBroadcastReceiver,
+ IntentFilter(TorServiceConstants.ACTION_STATUS)
+ )
+ lbm.registerReceiver(
+ persistentBroadcastReceiver,
+ IntentFilter(TorServiceConstants.LOCAL_ACTION_LOG)
+ )
+ }
+
+ override fun stop() {
+ lbm.unregisterReceiver(persistentBroadcastReceiver)
+ }
+
+ private val persistentBroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action == null ||
+ (intent.action != TorServiceConstants.ACTION_STATUS &&
+ intent.action != TorServiceConstants.LOCAL_ACTION_LOG)
+ ) {
+ return
+ }
+ val action = intent.action
+
+ val logentry: String?
+ val status: String?
+ if (action == TorServiceConstants.LOCAL_ACTION_LOG) {
+ logentry = intent.getExtras()
+ ?.getCharSequence(TorServiceConstants.LOCAL_EXTRA_LOG) as? String?
+ } else {
+ logentry = null
+ }
+
+ status = intent.getExtras()
+ ?.getCharSequence(TorServiceConstants.EXTRA_STATUS) as? String?
+
+ if (logentry == null && status == null) {
+ return
+ }
+
+ onTorStatusUpdate(logentry, status)
+
+ if (status == null) {
+ return
+ }
+
+ val newStatus = TorStatus.fromString(status)
+
+ if (newStatus.isUnknown() && wasTorBootstrapped) {
+ stopTor()
+ }
+
+ entries.add(Pair(logentry, status))
+
+ if (logentry != null && logentry.contains(TorServiceConstants.TOR_CONTROL_PORT_MSG_BOOTSTRAP_DONE)) {
+ wasTorBootstrapped = true
+ onTorConnected()
+ }
+
+ if (lastKnownStatus.isStopping() && newStatus.isOff()) {
+ if (isTorRestarting) {
+ initiateTorBootstrap()
+ } else {
+ onTorStopped()
+ }
+ }
+
+ if (lastKnownStatus.isOff() && newStatus.isStarting()) {
+ isTorRestarting = false
+ }
+
+ lastKnownStatus = newStatus
+ }
+ }
+
+ override fun onTorConnecting() {
+ lockTorListenersMutation = true
+ torListeners.forEach { it.onTorConnecting() }
+ lockTorListenersMutation = false
+
+ handlePendingRegistrationChanges()
+ }
+
+ override fun onTorConnected() {
+ lockTorListenersMutation = true
+ torListeners.forEach { it.onTorConnected() }
+ lockTorListenersMutation = false
+
+ handlePendingRegistrationChanges()
+ }
+
+ override fun onTorStatusUpdate(entry: String?, status: String?) {
+ lockTorListenersMutation = true
+ torListeners.forEach { it.onTorStatusUpdate(entry, status) }
+ lockTorListenersMutation = false
+
+ handlePendingRegistrationChanges()
+ }
+
+ override fun onTorStopped() {
+ lockTorListenersMutation = true
+ torListeners.forEach { it.onTorStopped() }
+ lockTorListenersMutation = false
+
+ handlePendingRegistrationChanges()
+ }
+
+ override fun registerTorListener(l: TorEvents) {
+ if (torListeners.contains(l)) {
+ return
+ }
+
+ if (lockTorListenersMutation) {
+ pendingRegisterChangeList.add(Pair(l, true))
+ } else {
+ torListeners.add(l)
+ }
+ }
+
+ override fun unregisterTorListener(l: TorEvents) {
+ if (!torListeners.contains(l)) {
+ return
+ }
+
+ if (lockTorListenersMutation) {
+ pendingRegisterChangeList.add(Pair(l, false))
+ } else {
+ torListeners.remove(l)
+ }
+ }
+
+ private fun handlePendingRegistrationChanges() {
+ pendingRegisterChangeList.forEach {
+ if (it.second) {
+ registerTorListener(it.first)
+ } else {
+ unregisterTorListener(it.first)
+ }
+ }
+
+ pendingRegisterChangeList.clear()
+ }
+
+ /**
+ * Receive the current Tor status.
+ *
+ * Send a request for the current status and receive the response.
+ * Returns true if Tor is running, false otherwise.
+ *
+ */
+ private suspend fun checkTorIsStarted(): Boolean {
+ val channel = Channel<Boolean>()
+
+ // Register receiver
+ val lbm: LocalBroadcastManager = LocalBroadcastManager.getInstance(context)
+ val localBroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val action = intent.action ?: return
+ // We only want ACTION_STATUS messages
+ if (action != TorServiceConstants.ACTION_STATUS) {
+ return
+ }
+ // The current status has the EXTRA_STATUS key
+ val currentStatus =
+ intent.getStringExtra(TorServiceConstants.EXTRA_STATUS)
+ channel.trySend(currentStatus === TorServiceConstants.STATUS_ON)
+ }
+ }
+ lbm.registerReceiver(
+ localBroadcastReceiver,
+ IntentFilter(TorServiceConstants.ACTION_STATUS)
+ )
+
+ // Request service status
+ sendServiceAction(TorServiceConstants.ACTION_STATUS)
+
+ // Wait for response and unregister receiver
+ var torIsStarted = false
+ withTimeoutOrNull(torServiceResponseTimeout) {
+ torIsStarted = channel.receive()
+ }
+ lbm.unregisterReceiver(localBroadcastReceiver)
+ return torIsStarted
+ }
+
+ override fun initiateTorBootstrap(lifecycleScope: LifecycleCoroutineScope?, withDebugLogging: Boolean) {
+ if (BuildConfig.DISABLE_TOR) {
+ return
+ }
+
+ context.getSharedPreferences("org.torproject.android_preferences", Context.MODE_PRIVATE)
+ .edit().putBoolean("pref_enable_logging", withDebugLogging).apply()
+
+ if (lifecycleScope == null) {
+ sendServiceAction(TorServiceConstants.ACTION_START)
+ } else {
+ lifecycleScope.launch {
+ val torNeedsStart = !checkTorIsStarted()
+ if (torNeedsStart) {
+ sendServiceAction(TorServiceConstants.ACTION_START)
+ }
+ }
+ }
+ }
+
+ override fun stopTor() {
+ if (BuildConfig.DISABLE_TOR) {
+ return
+ }
+
+ val torService = Intent(context, TorService::class.java)
+ context.stopService(torService)
+ }
+
+ override fun setTorStopped() {
+ lastKnownStatus = TorStatus.OFF
+ onTorStopped()
+ }
+
+ override fun restartTor() {
+ // tor-android-service doesn't dynamically update the torrc file,
+ // and it doesn't use SETCONF, so we completely restart the service.
+ // However, don't restart if we aren't started and we weren't
+ // previously started.
+ if (!lastKnownStatus.isStarted() && !wasTorBootstrapped) {
+ return
+ }
+
+ if (!lastKnownStatus.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()
+ }
+ }
+
+ private fun sendServiceAction(action: String) {
+ val torServiceStatus = Intent(context, TorService::class.java)
+ torServiceStatus.action = action
+ context.startService(torServiceStatus)
+ }
+
+ companion object {
+ const val torServiceResponseTimeout = 5000L
+ }
+}
View it on GitLab: https://gitlab.torproject.org/tpo/applications/firefox-android/-/commit/6d0…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/firefox-android/-/commit/6d0…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-115.7.0esr-13.5-1] 2 commits: fixup! Bug 40597: Implement TorSettings module
by Pier Angelo Vendrame (@pierov) 25 Jan '24
by Pier Angelo Vendrame (@pierov) 25 Jan '24
25 Jan '24
Pier Angelo Vendrame pushed to branch tor-browser-115.7.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits:
f49834e2 by Dan Ballard at 2024-01-25T17:05:18+00:00
fixup! Bug 40597: Implement TorSettings module
Bug 42252: Fix typo in documents
- - - - -
db59cb25 by Dan Ballard at 2024-01-25T17:05:18+00:00
fixup! Bug 42247: Android helpers for the TorProvider
Bug 42252: Add support for TorController in firefox-android
- - - - -
4 changed files:
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorIntegrationAndroid.java
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorSettings.java
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/androidlegacysettings/TorLegacyAndroidSettings.java
- toolkit/modules/TorSettings.sys.mjs
Changes:
=====================================
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorIntegrationAndroid.java
=====================================
@@ -82,6 +82,10 @@ public class TorIntegrationAndroid implements BundleEventListener {
private final HashMap<Integer, MeekTransport> mMeeks = new HashMap<>();
private int mMeekCounter;
+ // mSettings is a java side copy of the authoritative settings in the JS code.
+ // it's useful to maintain as the ui may be fetching these options often and
+ // we don't watch each fetch to be a passthrough to JS with JSON serialization and
+ // deserialization each time
private TorSettings mSettings = null;
/* package */ TorIntegrationAndroid(Context context) {
@@ -557,11 +561,38 @@ public class TorIntegrationAndroid implements BundleEventListener {
void onSettingsRequested();
}
- public @NonNull GeckoResult<GeckoBundle> getSettings() {
- return EventDispatcher.getInstance().queryBundle(EVENT_SETTINGS_GET);
+ private @NonNull void reloadSettings() {
+ EventDispatcher.getInstance().queryBundle(EVENT_SETTINGS_GET).then( new GeckoResult.OnValueListener<GeckoBundle, Void>() {
+ public GeckoResult<Void> onValue(final GeckoBundle bundle) {
+ mSettings = new TorSettings(bundle);
+ return new GeckoResult<Void>();
+ }
+ });
+ }
+
+ public TorSettings getSettings() {
+ return mSettings;
+ }
+
+ public void setSettings(final TorSettings settings, boolean save, boolean apply) {
+ mSettings = settings;
+
+ emitSetSettings(settings, save, apply).then(
+ new GeckoResult.OnValueListener<Void, Void>() {
+ public GeckoResult<Void> onValue(Void v) {
+ return new GeckoResult<Void>();
+ }
+ },
+ new GeckoResult.OnExceptionListener<Void>() {
+ public GeckoResult<Void> onException(final Throwable e) {
+ Log.e(TAG, "Failed to set settings", e);
+ reloadSettings();
+ return new GeckoResult<Void>();
+ }
+ });
}
- public @NonNull GeckoResult<Void> setSettings(final TorSettings settings, boolean save, boolean apply) {
+ private @NonNull GeckoResult<Void> emitSetSettings(final TorSettings settings, boolean save, boolean apply) {
GeckoBundle bundle = new GeckoBundle(3);
bundle.putBoolean("save", save);
bundle.putBoolean("apply", apply);
=====================================
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorSettings.java
=====================================
@@ -2,24 +2,8 @@ package org.mozilla.geckoview;
import android.util.Log;
-import org.json.JSONArray;
-import org.json.JSONObject;
import org.mozilla.gecko.util.GeckoBundle;
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.PrintStream;
-import java.io.SequenceInputStream;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-import java.util.stream.Collectors;
-
public class TorSettings {
public enum BridgeSource {
@@ -76,6 +60,35 @@ public class TorSettings {
}
}
+ public enum BridgeBuiltinType {
+ /* TorSettings.sys.mjs ~ln43: string: obfs4|meek-azure|snowflake|etc */
+ Invalid("invalid"),
+ Obfs4("obfs4"),
+ MeekAzure("meek-azure"),
+ Snowflake("snowflake");
+
+
+ private String type;
+
+ BridgeBuiltinType(String type) {
+ this.type = type;
+ }
+
+ public String toString() {
+ return type;
+ }
+
+ public static BridgeBuiltinType fromString(String s) {
+ switch (s) {
+ case "obfs4": return Obfs4;
+ case "meek-azure": return MeekAzure;
+ case "snowflake": return Snowflake;
+ }
+ return Invalid;
+ }
+
+ }
+
private boolean loaded = false;
public boolean enabled = true;
@@ -85,7 +98,7 @@ public class TorSettings {
// bridges section
public boolean bridgesEnabled = false;
public BridgeSource bridgesSource = BridgeSource.Invalid;
- public String bridgesBuiltinType = "";
+ public BridgeBuiltinType bridgesBuiltinType = BridgeBuiltinType.Invalid;
public String[] bridgeBridgeStrings;
// proxy section
@@ -112,7 +125,7 @@ public class TorSettings {
bridgesEnabled = bridges.getBoolean("enabled");
bridgesSource = BridgeSource.fromInt(bridges.getInt("source"));
- bridgesBuiltinType = bridges.getString("builtin_type");
+ bridgesBuiltinType = BridgeBuiltinType.fromString(bridges.getString("builtin_type"));
bridgeBridgeStrings = bridges.getStringArray("bridge_strings");
quickstart = qs.getBoolean("enabled");
@@ -143,7 +156,7 @@ public class TorSettings {
bridges.putBoolean("enabled", bridgesEnabled);
bridges.putInt("source", bridgesSource.toInt());
- bridges.putString("builtin_type", bridgesBuiltinType);
+ bridges.putString("builtin_type", bridgesBuiltinType.toString());
bridges.putStringArray("bridge_strings", bridgeBridgeStrings);
qs.putBoolean("enabled", quickstart);
=====================================
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/androidlegacysettings/TorLegacyAndroidSettings.java
=====================================
@@ -1,11 +1,5 @@
package org.mozilla.geckoview.androidlegacysettings;
-import java.io.IOException;
-
-import android.content.SharedPreferences;
-
-import org.mozilla.gecko.GeckoAppShell;
-
import org.mozilla.geckoview.TorSettings;
public class TorLegacyAndroidSettings {
@@ -54,10 +48,10 @@ public class TorLegacyAndroidSettings {
switch (userDefinedBridgeList) {
case "obfs4":
case "snowflake":
- settings.bridgesBuiltinType = userDefinedBridgeList;
+ settings.bridgesBuiltinType = TorSettings.BridgeBuiltinType.fromString(userDefinedBridgeList);
break;
case "meek":
- settings.bridgesBuiltinType = "meek-azure";
+ settings.bridgesBuiltinType = TorSettings.BridgeBuiltinType.MeekAzure;
break;
default:
settings.bridgesSource = TorSettings.BridgeSource.Invalid;
=====================================
toolkit/modules/TorSettings.sys.mjs
=====================================
@@ -40,7 +40,7 @@ const TorSettingsPrefs = Object.freeze({
enabled: "torbrowser.settings.bridges.enabled",
/* int: See TorBridgeSource */
source: "torbrowser.settings.bridges.source",
- /* string: obfs4|meek_azure|snowflake|etc */
+ /* string: obfs4|meek-azure|snowflake|etc */
builtin_type: "torbrowser.settings.bridges.builtin_type",
/* preference branch: each child branch should be a bridge string */
bridge_strings: "torbrowser.settings.bridges.bridge_strings",
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/308d6a…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/308d6a…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-115.7.0esr-13.5-1] fixup! Bug 42247: Android helpers for the TorProvider
by Pier Angelo Vendrame (@pierov) 25 Jan '24
by Pier Angelo Vendrame (@pierov) 25 Jan '24
25 Jan '24
Pier Angelo Vendrame pushed to branch tor-browser-115.7.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits:
308d6a64 by Pier Angelo Vendrame at 2024-01-25T17:44:22+01:00
fixup! Bug 42247: Android helpers for the TorProvider
Bug 42368: Do not use API26+ java.nio features
- - - - -
1 changed file:
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorIntegrationAndroid.java
Changes:
=====================================
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorIntegrationAndroid.java
=====================================
@@ -16,15 +16,10 @@ import androidx.annotation.Nullable;
import java.io.BufferedReader;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.attribute.PosixFilePermission;
-import java.nio.file.attribute.PosixFilePermissions;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -69,9 +64,9 @@ public class TorIntegrationAndroid implements BundleEventListener {
private static final String COOKIE_AUTH_FILE = "/auth-file";
private final String mLibraryDir;
- private final Path mCacheDir;
+ private final String mCacheDir;
private final String mIpcDirectory;
- private final String mDataDir;
+ private final File mDataDir;
private TorProcess mTorProcess = null;
/**
@@ -91,9 +86,9 @@ public class TorIntegrationAndroid implements BundleEventListener {
/* package */ TorIntegrationAndroid(Context context) {
mLibraryDir = context.getApplicationInfo().nativeLibraryDir;
- mCacheDir = context.getCacheDir().toPath();
+ mCacheDir = context.getCacheDir().getAbsolutePath();
mIpcDirectory = mCacheDir + "/tor-private";
- mDataDir = context.getDataDir().getAbsolutePath() + "/tor";
+ mDataDir = new File(context.getFilesDir(), "tor");
registerListener();
}
@@ -260,7 +255,7 @@ public class TorIntegrationAndroid implements BundleEventListener {
args.add("CookieAuthFile");
args.add(ipcDir + COOKIE_AUTH_FILE);
args.add("DataDirectory");
- args.add(mDataDir);
+ args.add(mDataDir.getAbsolutePath());
boolean copied = true;
try {
copyAndUseConfigFile("--defaults-torrc", "torrc-defaults", args);
@@ -322,15 +317,19 @@ public class TorIntegrationAndroid implements BundleEventListener {
private void cleanIpcDirectory() {
File directory = new File(TorIntegrationAndroid.this.mIpcDirectory);
- if (!Files.isDirectory(directory.toPath())) {
+ if (!directory.isDirectory()) {
if (!directory.mkdirs()) {
Log.e(TAG, "Failed to create the IPC directory.");
return;
}
try {
- Set<PosixFilePermission> chmod = PosixFilePermissions.fromString("rwx------");
- Files.setPosixFilePermissions(directory.toPath(), chmod);
- } catch (IOException e) {
+ directory.setReadable(false, false);
+ directory.setReadable(true, true);
+ directory.setWritable(false, false);
+ directory.setWritable(true, true);
+ directory.setExecutable(false, false);
+ directory.setExecutable(true, true);
+ } catch (SecurityException e) {
Log.e(TAG, "Could not set the permissions to the IPC directory.", e);
}
return;
@@ -347,15 +346,46 @@ public class TorIntegrationAndroid implements BundleEventListener {
}
private void copyAndUseConfigFile(String option, String name, ArrayList<String> args) throws IOException {
- final Path path = Paths.get(mCacheDir.toFile().getAbsolutePath(), name);
- if (!mCopiedConfigFiles || !path.toFile().exists()) {
- final Context context = GeckoAppShell.getApplicationContext();
- final InputStream in = context.getAssets().open("common/" + name);
- Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING);
+ File file = copyConfigFile(name);
+ args.add(option);
+ args.add(file.getAbsolutePath());
+ }
+
+ private File copyConfigFile(String name) throws IOException {
+ final File file = new File(mCacheDir, name);
+ if (mCopiedConfigFiles && file.exists()) {
+ return file;
+ }
+
+ final Context context = GeckoAppShell.getApplicationContext();
+ final InputStream in = context.getAssets().open("common/" + name);
+ // Files.copy is API 26+, so use java.io and a loop for now.
+ FileOutputStream out = null;
+ try {
+ out = new FileOutputStream(file);
+ } catch (IOException e) {
in.close();
+ throw e;
}
- args.add(option);
- args.add(path.toString());
+ try {
+ byte buffer[] = new byte[4096];
+ int read;
+ while ((read = in.read(buffer)) >= 0) {
+ out.write(buffer, 0, read);
+ }
+ } finally {
+ try {
+ in.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Cannot close the input stream for " + name);
+ }
+ try {
+ out.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Cannot close the output stream for " + name);
+ }
+ }
+ return file;
}
public void shutdown() {
@@ -406,9 +436,10 @@ public class TorIntegrationAndroid implements BundleEventListener {
setName("meek-" + id);
final ProcessBuilder builder = new ProcessBuilder(mLibraryDir + "/libObfs4proxy.so");
{
+ File ptStateDir = new File(mDataDir, "pt_state");
final Map<String, String> env = builder.environment();
env.put("TOR_PT_MANAGED_TRANSPORT_VER", "1");
- env.put("TOR_PT_STATE_LOCATION", mDataDir + "/pt_state");
+ env.put("TOR_PT_STATE_LOCATION", ptStateDir.getAbsolutePath());
env.put("TOR_PT_EXIT_ON_STDIN_CLOSE", "1");
env.put("TOR_PT_CLIENT_TRANSPORTS", TRANSPORT);
}
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/308d6a6…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/308d6a6…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/rbm][main] Bug 40068: Use Capture::Tiny instead of IO::CaptureOutput
by richard (@richard) 25 Jan '24
by richard (@richard) 25 Jan '24
25 Jan '24
richard pushed to branch main at The Tor Project / Applications / RBM
Commits:
9336d42f by Nicolas Vigier at 2024-01-25T11:42:58+01:00
Bug 40068: Use Capture::Tiny instead of IO::CaptureOutput
The IO::CaptureOutput perl module is deprecated, so we switch to
Capture::Tiny.
The Capture::Tiny module does not provide a capture_exec function
similar to the one from IO::CaptureOutput, so we implement one using the
same name, which avoids changing all the places where we were using it.
- - - - -
2 changed files:
- lib/RBM.pm
- lib/RBM/DefaultConfig.pm
Changes:
=====================================
lib/RBM.pm
=====================================
@@ -10,7 +10,7 @@ use YAML::XS qw(LoadFile);
use Template;
use File::Basename;
use IO::Handle;
-use IO::CaptureOutput qw(capture_exec);
+use Capture::Tiny qw(capture);
use File::Temp;
use File::Copy;
use File::Copy::Recursive qw(fcopy);
@@ -29,7 +29,7 @@ use feature "state";
BEGIN {
require Exporter;
our @ISA = qw(Exporter);
- our @EXPORT = qw(exit_error);
+ our @EXPORT = qw(exit_error capture_exec);
}
our $config;
@@ -308,6 +308,15 @@ sub exit_error {
exit (exists $_[1] ? $_[1] : 1);
}
+sub capture_exec {
+ my @cmd = @_;
+ my ($stdout, $stderr, $exit) = capture {
+ system(@cmd);
+ };
+ return ($stdout, $stderr, $exit == 0, $exit) if wantarray();
+ return $stdout;
+}
+
sub set_git_gpg_wrapper {
my ($project) = @_;
my $w = project_config($project, 'gpg_wrapper');
=====================================
lib/RBM/DefaultConfig.pm
=====================================
@@ -10,9 +10,8 @@ BEGIN {
}
use File::Basename;
-use RBM;
+use RBM qw(capture_exec);
use Cwd qw(getcwd);
-use IO::CaptureOutput qw(capture_exec);
use File::Temp;
use File::Path qw(make_path);
View it on GitLab: https://gitlab.torproject.org/tpo/applications/rbm/-/commit/9336d42fdd669af…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/rbm/-/commit/9336d42fdd669af…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][maint-13.0] Update rbm for rbm#40067
by boklm (@boklm) 25 Jan '24
by boklm (@boklm) 25 Jan '24
25 Jan '24
boklm pushed to branch maint-13.0 at The Tor Project / Applications / tor-browser-build
Commits:
af1c78b5 by Nicolas Vigier at 2024-01-25T11:19:16+01:00
Update rbm for rbm#40067
- - - - -
1 changed file:
- rbm
Changes:
=====================================
rbm
=====================================
@@ -1 +1 @@
-Subproject commit 40acf540fe75055df2eb78454f070f57d0804729
+Subproject commit b5e5b04aaf677c4bacfb5ace45598313286bfdf6
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/a…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/a…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][main] Update rbm for rbm#40067
by boklm (@boklm) 25 Jan '24
by boklm (@boklm) 25 Jan '24
25 Jan '24
boklm pushed to branch main at The Tor Project / Applications / tor-browser-build
Commits:
83a1a381 by Nicolas Vigier at 2024-01-25T11:17:52+01:00
Update rbm for rbm#40067
- - - - -
1 changed file:
- rbm
Changes:
=====================================
rbm
=====================================
@@ -1 +1 @@
-Subproject commit 40acf540fe75055df2eb78454f070f57d0804729
+Subproject commit b5e5b04aaf677c4bacfb5ace45598313286bfdf6
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/8…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/8…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-115.7.0esr-13.0-1] fixup! Bug 41668: Tweaks to the Base Browser updater for Tor Browser
by boklm (@boklm) 25 Jan '24
by boklm (@boklm) 25 Jan '24
25 Jan '24
boklm pushed to branch tor-browser-115.7.0esr-13.0-1 at The Tor Project / Applications / Tor Browser
Commits:
6292bbc9 by Nicolas Vigier at 2024-01-25T10:26:11+01:00
fixup! Bug 41668: Tweaks to the Base Browser updater for Tor Browser
Bug 42293: Don't disable updater when run by torbrowser-launcher flatpak
When the TORBROWSER_LAUNCHER environment variable is set (which
is set by torbrowser-launcher since version 0.3.7) we assume that Tor
Browser is not installed by a Flatkpak. Although torbrowser-launcher
itself can be installed by a Flatpak, this should not prevent the
updater from working.
- - - - -
1 changed file:
- widget/gtk/WidgetUtilsGtk.cpp
Changes:
=====================================
widget/gtk/WidgetUtilsGtk.cpp
=====================================
@@ -138,6 +138,11 @@ void SetLastMousePressEvent(GdkEvent* aEvent) {
bool IsRunningUnderSnap() { return !!GetSnapInstanceName(); }
bool IsRunningUnderFlatpak() {
+ // tor-browser#42293: Don't disable updater when run by torbrowser-launcher flatpak
+ const char* torbrowserLauncher = g_getenv("TORBROWSER_LAUNCHER");
+ if (torbrowserLauncher) {
+ return false;
+ }
// https://gitlab.gnome.org/GNOME/gtk/-/blob/4300a5c609306ce77cbc8a3580c19201d…
static bool sRunning = [] {
return g_file_test("/.flatpak-info", G_FILE_TEST_EXISTS);
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/6292bbc…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/6292bbc…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-115.7.0esr-13.5-1] fixup! Bug 41668: Tweaks to the Base Browser updater for Tor Browser
by boklm (@boklm) 25 Jan '24
by boklm (@boklm) 25 Jan '24
25 Jan '24
boklm pushed to branch tor-browser-115.7.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits:
5898a9ab by Nicolas Vigier at 2024-01-25T10:21:48+01:00
fixup! Bug 41668: Tweaks to the Base Browser updater for Tor Browser
Bug 42293: Don't disable updater when run by torbrowser-launcher flatpak
When the TORBROWSER_LAUNCHER environment variable is set (which
is set by torbrowser-launcher since version 0.3.7) we assume that Tor
Browser is not installed by a Flatkpak. Although torbrowser-launcher
itself can be installed by a Flatpak, this should not prevent the
updater from working.
- - - - -
1 changed file:
- widget/gtk/WidgetUtilsGtk.cpp
Changes:
=====================================
widget/gtk/WidgetUtilsGtk.cpp
=====================================
@@ -138,6 +138,11 @@ void SetLastMousePressEvent(GdkEvent* aEvent) {
bool IsRunningUnderSnap() { return !!GetSnapInstanceName(); }
bool IsRunningUnderFlatpak() {
+ // tor-browser#42293: Don't disable updater when run by torbrowser-launcher flatpak
+ const char* torbrowserLauncher = g_getenv("TORBROWSER_LAUNCHER");
+ if (torbrowserLauncher) {
+ return false;
+ }
// https://gitlab.gnome.org/GNOME/gtk/-/blob/4300a5c609306ce77cbc8a3580c19201d…
static bool sRunning = [] {
return g_file_test("/.flatpak-info", G_FILE_TEST_EXISTS);
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/5898a9a…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/5898a9a…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-115.7.0esr-13.5-1] 3 commits: fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in...
by richard (@richard) 24 Jan '24
by richard (@richard) 24 Jan '24
24 Jan '24
richard pushed to branch tor-browser-115.7.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits:
47afbf56 by Henry Wilkes at 2024-01-23T20:27:35+00:00
fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
Bug 42036: Replace replace/add bridges section with new design, ready
for Lox.
- - - - -
0021e3f3 by Henry Wilkes at 2024-01-23T20:27:36+00:00
fixup! Tor Browser strings
Bug 42036: Add strings for adding/replacing bridges.
- - - - -
c1bcea9d by Henry Wilkes at 2024-01-23T20:27:36+00:00
fixup! Add TorStrings module for localization
Bug 42036: Remove old strings for replacing bridges.
- - - - -
11 changed files:
- + browser/components/torpreferences/content/bridge-bot.svg
- browser/components/torpreferences/content/connectionPane.js
- browser/components/torpreferences/content/connectionPane.xhtml
- browser/components/torpreferences/content/provideBridgeDialog.js
- browser/components/torpreferences/content/provideBridgeDialog.xhtml
- + browser/components/torpreferences/content/telegram-logo.svg
- browser/components/torpreferences/content/torPreferences.css
- browser/components/torpreferences/jar.mn
- browser/locales/en-US/browser/tor-browser.ftl
- toolkit/modules/TorStrings.sys.mjs
- toolkit/torbutton/chrome/locale/en-US/settings.properties
Changes:
=====================================
browser/components/torpreferences/content/bridge-bot.svg
=====================================
@@ -0,0 +1,13 @@
+<svg width="40" height="44" viewBox="0 0 40 44" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M37.1877 22.7584C38.7409 25.1504 38.9178 27.7923 37.5828 28.6591C36.2478 29.5259 33.9065 28.2895 32.3533 25.8975C30.8001 23.5055 30.6232 20.8637 31.9582 19.9969C33.2932 19.13 35.6345 20.3664 37.1877 22.7584Z" fill="#C069FF"/>
+<path d="M2.81234 22.7584C1.25915 25.1504 1.08224 27.7923 2.41721 28.6591C3.75217 29.5259 6.09349 28.2895 7.64668 25.8975C9.19987 23.5055 9.37678 20.8637 8.04181 19.9969C6.70685 19.13 4.36553 20.3664 2.81234 22.7584Z" fill="#C069FF"/>
+<path d="M32.2002 19.4754C33.9149 19.4754 35.3181 20.8678 35.1823 22.5772C34.7579 27.9186 33.2458 32.9181 30.8541 36.7668C28.0043 41.3527 24.1391 43.9291 20.1088 43.9291C16.0785 43.9291 12.2133 41.3527 9.36344 36.7668C6.97177 32.9181 5.45965 27.9186 5.03525 22.5772C4.89944 20.8678 6.30265 19.4754 8.0174 19.4754L32.2002 19.4754Z" fill="#C069FF"/>
+<path d="M28.4375 32.1121C28.4375 27.4522 24.6599 23.6746 20 23.6746C15.3401 23.6746 11.5625 27.4522 11.5625 32.1121V33.5139H12.8809V32.1121C12.8809 28.1803 16.0682 24.9929 20 24.9929C23.9318 24.9929 27.1191 28.1803 27.1191 32.1121V33.5139H28.4375V32.1121Z" fill="#15141A"/>
+<path d="M25.9062 32.1121C25.9062 28.8501 23.2619 26.2058 20 26.2058C16.7381 26.2058 14.0937 28.8501 14.0937 32.1121L14.0936 33.5139H15.412L15.4121 32.1121C15.4121 29.5782 17.4662 27.5242 20 27.5242C22.5338 27.5242 24.5879 29.5782 24.5879 32.1121L24.588 33.5139H25.9064L25.9062 32.1121Z" fill="#15141A"/>
+<path d="M20 28.7371C21.864 28.7371 23.375 30.2481 23.375 32.1121L23.3753 33.5139H22.0569L22.0566 32.1121C22.0566 30.9762 21.1359 30.0554 20 30.0554C18.8642 30.0554 17.9434 30.9762 17.9434 32.1121L17.9431 33.5139H16.6247V32.1121C16.6247 30.2481 18.136 28.7371 20 28.7371Z" fill="#15141A"/>
+<path d="M8.9145 17.8162C7.19975 17.8162 5.78665 16.4193 6.02668 14.7215C6.53221 11.1456 7.9061 7.82078 9.99195 5.21826C12.6698 1.87706 16.3018 -1.07451e-07 20.0889 0C23.8759 1.07451e-07 27.5079 1.87706 30.1858 5.21826C32.2716 7.82078 33.6455 11.1456 34.151 14.7215C34.3911 16.4193 32.978 17.8162 31.2632 17.8162H8.9145Z" fill="#C069FF"/>
+<path d="M13.1064 15.1091C11.3916 15.1091 9.96814 13.7048 10.3139 12.0252C10.7578 9.86855 11.6634 7.87853 12.956 6.27814C14.8477 3.93602 17.4134 2.62024 20.0887 2.62024C22.7639 2.62024 25.3296 3.93602 27.2213 6.27814C28.514 7.87853 29.4195 9.86855 29.8635 12.0252C30.2092 13.7048 28.7857 15.1091 27.071 15.1091H13.1064Z" fill="#EBD0FF"/>
+<path d="M17.5125 6.81215C17.5125 7.58388 16.9065 8.2095 16.1589 8.2095C15.4112 8.2095 14.8052 7.58388 14.8052 6.81215C14.8052 6.04041 15.4112 5.41479 16.1589 5.41479C16.9065 5.41479 17.5125 6.04041 17.5125 6.81215Z" fill="#15141A"/>
+<path d="M25.1981 6.81215C25.1981 7.58388 24.592 8.2095 23.8444 8.2095C23.0968 8.2095 22.4907 7.58388 22.4907 6.81215C22.4907 6.04041 23.0968 5.41479 23.8444 5.41479C24.592 5.41479 25.1981 6.04041 25.1981 6.81215Z" fill="#15141A"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M22.4395 9.01993L22.4044 9.11353C21.5971 11.2673 18.526 11.1951 17.8208 9.0058L18.427 8.81052C18.9472 10.4254 21.2125 10.4787 21.808 8.88998L21.8431 8.79639L22.4395 9.01993Z" fill="#15141A"/>
+</svg>
=====================================
browser/components/torpreferences/content/connectionPane.js
=====================================
@@ -1316,6 +1316,18 @@ const gBridgeSettings = {
* @type {Element?}
*/
_noBridgesEl: null,
+ /**
+ * The heading element for changing bridges.
+ *
+ * @type {Element?}
+ */
+ _changeHeadingEl: null,
+ /**
+ * The button for user to provide a bridge address or share code.
+ *
+ * @type {Element?}
+ */
+ _userProvideButton: null,
/**
* Initialize the bridge settings.
@@ -1345,6 +1357,47 @@ const gBridgeSettings = {
});
});
+ this._changeHeadingEl = document.getElementById(
+ "tor-bridges-change-heading"
+ );
+ this._userProvideButton = document.getElementById(
+ "tor-bridges-open-user-provide-dialog-button"
+ );
+
+ document.l10n.setAttributes(
+ document.getElementById("tor-bridges-user-provide-description"),
+ // TODO: Set a different string if we have Lox enabled.
+ "tor-bridges-add-addresses-description"
+ );
+
+ // TODO: Change to GetLoxBridges if Lox enabled, and the account is set up.
+ const telegramUserName = "GetBridgesBot";
+ const telegramInstruction = document.getElementById(
+ "tor-bridges-provider-instruction-telegram"
+ );
+ telegramInstruction.querySelector(
+ "a"
+ ).href = `https://t.me/${telegramUserName}`;
+ document.l10n.setAttributes(
+ telegramInstruction,
+ "tor-bridges-provider-telegram-instruction",
+ { telegramUserName }
+ );
+
+ document
+ .getElementById("tor-bridges-open-built-in-dialog-button")
+ .addEventListener("click", () => {
+ this._openBuiltinDialog();
+ });
+ this._userProvideButton.addEventListener("click", () => {
+ this._openUserProvideDialog(this._haveBridges ? "replace" : "add");
+ });
+ document
+ .getElementById("tor-bridges-open-request-dialog-button")
+ .addEventListener("click", () => {
+ this._openRequestDialog();
+ });
+
Services.obs.addObserver(this, TorSettingsTopics.SettingsChanged);
gBridgeGrid.init();
@@ -1488,6 +1541,17 @@ const gBridgeSettings = {
// and hidden.
this._groupEl.classList.toggle("no-bridges", !haveBridges);
this._groupEl.classList.toggle("have-bridges", haveBridges);
+
+ document.l10n.setAttributes(
+ this._changeHeadingEl,
+ haveBridges
+ ? "tor-bridges-replace-bridges-heading"
+ : "tor-bridges-add-bridges-heading"
+ );
+ document.l10n.setAttributes(
+ this._userProvideButton,
+ haveBridges ? "tor-bridges-replace-button" : "tor-bridges-add-new-button"
+ );
},
/**
@@ -1615,9 +1679,7 @@ const gBridgeSettings = {
"tor-bridges-options-edit-all-menu-item"
);
editItem.addEventListener("click", () => {
- // TODO: move to gBridgeSettings.
- // TODO: Change dialog title. Do not allow Lox invite.
- gConnectionPane.onAddBridgeManually();
+ this._openUserProvideDialog("edit");
});
// TODO: Do we want a different item for built-in bridges, rather than
@@ -1687,6 +1749,138 @@ const gBridgeSettings = {
_forceCloseBridgesMenu() {
this._bridgesMenu.hide(null, { force: true });
},
+
+ /**
+ * Open a bridge dialog that will change the users bridges.
+ *
+ * @param {string} url - The url of the dialog to open.
+ * @param {object?} inputData - The input data to send to the dialog window.
+ * @param {Function} onAccept - The method to call if the bridge dialog was
+ * accepted by the user. This will be passed a "result" object containing
+ * data set by the dialog. This should return a promise that resolves once
+ * the bridge settings have been set, or null if the settings have not
+ * been applied.
+ */
+ _openDialog(url, inputData, onAccept) {
+ const result = { accepted: false, connect: false };
+ let savedSettings = null;
+ gSubDialog.open(
+ url,
+ {
+ features: "resizable=yes",
+ closingCallback: () => {
+ if (!result.accepted) {
+ return;
+ }
+ savedSettings = onAccept(result);
+ if (!savedSettings) {
+ // No change in settings.
+ return;
+ }
+ if (!result.connect) {
+ // Do not open about:torconnect.
+ return;
+ }
+
+ // Wait until the settings are applied before bootstrapping.
+ savedSettings.then(() => {
+ // The bridge dialog button is "connect" when Tor is not
+ // bootstrapped, so do the connect.
+
+ // Start Bootstrapping, which should use the configured bridges.
+ // NOTE: We do this regardless of any previous TorConnect Error.
+ if (TorConnect.canBeginBootstrap) {
+ TorConnect.beginBootstrap();
+ }
+ // Open "about:torconnect".
+ // FIXME: If there has been a previous bootstrapping error then
+ // "about:torconnect" will be trying to get the user to use
+ // AutoBootstrapping. It is not set up to handle a forced direct
+ // entry to plain Bootstrapping from this dialog so the UI will
+ // not be aligned. In particular the
+ // AboutTorConnect.uiState.bootstrapCause will be aligned to
+ // whatever was shown previously in "about:torconnect" instead.
+ TorConnect.openTorConnect();
+ });
+ },
+ // closedCallback should be called after gSubDialog has already
+ // re-assigned focus back to the document.
+ closedCallback: () => {
+ if (!savedSettings) {
+ return;
+ }
+ // Wait until the settings have changed, so that the UI could
+ // respond, then move focus.
+ savedSettings.then(() => gBridgeSettings.takeFocus());
+ },
+ },
+ result,
+ inputData
+ );
+ },
+
+ /**
+ * Open the built-in bridge dialog.
+ */
+ _openBuiltinDialog() {
+ this._openDialog(
+ "chrome://browser/content/torpreferences/builtinBridgeDialog.xhtml",
+ null,
+ result => {
+ if (!result.type) {
+ return null;
+ }
+ return setTorSettings(() => {
+ TorSettings.bridges.enabled = true;
+ TorSettings.bridges.source = TorBridgeSource.BuiltIn;
+ TorSettings.bridges.builtin_type = result.type;
+ });
+ }
+ );
+ },
+
+ /*
+ * Open the request bridge dialog.
+ */
+ _openRequestDialog() {
+ this._openDialog(
+ "chrome://browser/content/torpreferences/requestBridgeDialog.xhtml",
+ null,
+ result => {
+ if (!result.bridges?.length) {
+ return null;
+ }
+ return setTorSettings(() => {
+ TorSettings.bridges.enabled = true;
+ TorSettings.bridges.source = TorBridgeSource.BridgeDB;
+ TorSettings.bridges.bridge_strings = result.bridges.join("\n");
+ });
+ }
+ );
+ },
+
+ /**
+ * Open the user provide dialog.
+ *
+ * @param {string} mode - The mode to open the dialog in: "add", "replace" or
+ * "edit".
+ */
+ _openUserProvideDialog(mode) {
+ this._openDialog(
+ "chrome://browser/content/torpreferences/provideBridgeDialog.xhtml",
+ { mode },
+ result => {
+ if (!result.bridgeStrings) {
+ return null;
+ }
+ return setTorSettings(() => {
+ TorSettings.bridges.enabled = true;
+ TorSettings.bridges.source = TorBridgeSource.UserProvided;
+ TorSettings.bridges.bridge_strings = result.bridgeStrings;
+ });
+ }
+ );
+ },
};
/*
@@ -1719,13 +1913,6 @@ const gConnectionPane = (function () {
location: "#torPreferences-bridges-location",
locationEntries: "#torPreferences-bridges-locationEntries",
chooseForMe: "#torPreferences-bridges-buttonChooseBridgeForMe",
- addHeader: "#torPreferences-addBridge-header",
- addBuiltinLabel: "#torPreferences-addBridge-labelBuiltinBridge",
- addBuiltinButton: "#torPreferences-addBridge-buttonBuiltinBridge",
- requestLabel: "#torPreferences-addBridge-labelRequestBridge",
- requestButton: "#torPreferences-addBridge-buttonRequestBridge",
- enterLabel: "#torPreferences-addBridge-labelEnterBridge",
- enterButton: "#torPreferences-addBridge-buttonEnterBridge",
},
advanced: {
header: "h1#torPreferences-advanced-header",
@@ -1985,39 +2172,6 @@ const gConnectionPane = (function () {
this._showAutoconfiguration();
}
- // Add a new bridge
- prefpane.querySelector(selectors.bridges.addHeader).textContent =
- TorStrings.settings.bridgeAdd;
- prefpane.querySelector(selectors.bridges.addBuiltinLabel).textContent =
- TorStrings.settings.bridgeSelectBrowserBuiltin;
- {
- const button = prefpane.querySelector(
- selectors.bridges.addBuiltinButton
- );
- button.setAttribute("label", TorStrings.settings.bridgeSelectBuiltin);
- button.addEventListener("command", e => {
- this.onAddBuiltinBridge();
- });
- }
- prefpane.querySelector(selectors.bridges.requestLabel).textContent =
- TorStrings.settings.bridgeRequestFromTorProject;
- {
- const button = prefpane.querySelector(selectors.bridges.requestButton);
- button.setAttribute("label", TorStrings.settings.bridgeRequest);
- button.addEventListener("command", e => {
- this.onRequestBridge();
- });
- }
- prefpane.querySelector(selectors.bridges.enterLabel).textContent =
- TorStrings.settings.bridgeEnterKnown;
- {
- const button = prefpane.querySelector(selectors.bridges.enterButton);
- button.setAttribute("label", TorStrings.settings.bridgeAddManually);
- button.addEventListener("command", e => {
- this.onAddBridgeManually();
- });
- }
-
// Advanced setup
prefpane.querySelector(selectors.advanced.header).innerText =
TorStrings.settings.advancedHeading;
@@ -2122,122 +2276,6 @@ const gConnectionPane = (function () {
this._showAutoconfiguration();
},
- /**
- * Open a bridge dialog that will change the users bridges.
- *
- * @param {string} url - The url of the dialog to open.
- * @param {Function} onAccept - The method to call if the bridge dialog was
- * accepted by the user. This will be passed a "result" object containing
- * data set by the dialog. This should return a promise that resolves once
- * the bridge settings have been set, or null if the settings have not
- * been applied.
- */
- openBridgeDialog(url, onAccept) {
- const result = { accepted: false, connect: false };
- let savedSettings = null;
- gSubDialog.open(
- url,
- {
- features: "resizable=yes",
- closingCallback: () => {
- if (!result.accepted) {
- return;
- }
- savedSettings = onAccept(result);
- if (!savedSettings) {
- // No change in settings.
- return;
- }
- if (!result.connect) {
- // Do not open about:torconnect.
- return;
- }
-
- // Wait until the settings are applied before bootstrapping.
- savedSettings.then(() => {
- // The bridge dialog button is "connect" when Tor is not
- // bootstrapped, so do the connect.
-
- // Start Bootstrapping, which should use the configured bridges.
- // NOTE: We do this regardless of any previous TorConnect Error.
- if (TorConnect.canBeginBootstrap) {
- TorConnect.beginBootstrap();
- }
- // Open "about:torconnect".
- // FIXME: If there has been a previous bootstrapping error then
- // "about:torconnect" will be trying to get the user to use
- // AutoBootstrapping. It is not set up to handle a forced direct
- // entry to plain Bootstrapping from this dialog so the UI will
- // not be aligned. In particular the
- // AboutTorConnect.uiState.bootstrapCause will be aligned to
- // whatever was shown previously in "about:torconnect" instead.
- TorConnect.openTorConnect();
- });
- },
- // closedCallback should be called after gSubDialog has already
- // re-assigned focus back to the document.
- closedCallback: () => {
- if (!savedSettings) {
- return;
- }
- // Wait until the settings have changed, so that the UI could
- // respond, then move focus.
- savedSettings.then(() => gCurrentBridgesArea.takeFocus());
- },
- },
- result
- );
- },
-
- onAddBuiltinBridge() {
- this.openBridgeDialog(
- "chrome://browser/content/torpreferences/builtinBridgeDialog.xhtml",
- result => {
- if (!result.type) {
- return null;
- }
- return setTorSettings(() => {
- TorSettings.bridges.enabled = true;
- TorSettings.bridges.source = TorBridgeSource.BuiltIn;
- TorSettings.bridges.builtin_type = result.type;
- });
- }
- );
- },
-
- // called when the request bridge button is activated
- onRequestBridge() {
- this.openBridgeDialog(
- "chrome://browser/content/torpreferences/requestBridgeDialog.xhtml",
- result => {
- if (!result.bridges?.length) {
- return null;
- }
- return setTorSettings(() => {
- TorSettings.bridges.enabled = true;
- TorSettings.bridges.source = TorBridgeSource.BridgeDB;
- TorSettings.bridges.bridge_strings = result.bridges.join("\n");
- });
- }
- );
- },
-
- onAddBridgeManually() {
- this.openBridgeDialog(
- "chrome://browser/content/torpreferences/provideBridgeDialog.xhtml",
- result => {
- if (!result.bridgeStrings) {
- return null;
- }
- return setTorSettings(() => {
- TorSettings.bridges.enabled = true;
- TorSettings.bridges.source = TorBridgeSource.UserProvided;
- TorSettings.bridges.bridge_strings = result.bridgeStrings;
- });
- }
- );
- },
-
onAdvancedSettings() {
gSubDialog.open(
"chrome://browser/content/torpreferences/connectionSettingsDialog.xhtml",
=====================================
browser/components/torpreferences/content/connectionPane.xhtml
=====================================
@@ -293,28 +293,102 @@
></html:button>
</html:div>
</html:div>
- <html:h2 id="torPreferences-addBridge-header"></html:h2>
+ <html:h2 id="tor-bridges-change-heading"></html:h2>
<hbox align="center">
- <label id="torPreferences-addBridge-labelBuiltinBridge" flex="1" />
- <button
- id="torPreferences-addBridge-buttonBuiltinBridge"
- class="accessory-button"
+ <description
+ flex="1"
+ data-l10n-id="tor-bridges-select-built-in-description"
/>
- </hbox>
- <hbox align="center">
- <label id="torPreferences-addBridge-labelRequestBridge" flex="1" />
- <button
- id="torPreferences-addBridge-buttonRequestBridge"
+ <html:button
+ id="tor-bridges-open-built-in-dialog-button"
class="accessory-button"
- />
+ data-l10n-id="tor-bridges-select-built-in-button"
+ ></html:button>
</hbox>
<hbox align="center">
- <label id="torPreferences-addBridge-labelEnterBridge" flex="1" />
- <button
- id="torPreferences-addBridge-buttonEnterBridge"
+ <description id="tor-bridges-user-provide-description" flex="1" />
+ <html:button
+ id="tor-bridges-open-user-provide-dialog-button"
class="accessory-button"
- />
+ ></html:button>
</hbox>
+ <html:h3
+ id="tor-bridges-provider-heading"
+ data-l10n-id="tor-bridges-find-more-heading"
+ ></html:h3>
+ <description data-l10n-id="tor-bridges-find-more-description" />
+ <html:div id="tor-bridges-provider-area">
+ <html:ul id="tor-bridges-provider-list">
+ <html:li class="tor-bridges-provider-item">
+ <html:img
+ id="tor-bridges-provider-icon-telegram"
+ class="tor-bridges-provider-icon"
+ alt=""
+ />
+ <html:div
+ class="tor-bridges-provider-name"
+ data-l10n-id="tor-bridges-provider-telegram-name"
+ ></html:div>
+ <html:div
+ id="tor-bridges-provider-instruction-telegram"
+ class="tor-bridges-provider-instruction"
+ >
+ <html:a data-l10n-name="user"></html:a>
+ </html:div>
+ </html:li>
+ <html:li class="tor-bridges-provider-item">
+ <html:img
+ id="tor-bridges-provider-icon-web"
+ class="tor-bridges-provider-icon"
+ alt=""
+ />
+ <html:div
+ class="tor-bridges-provider-name"
+ data-l10n-id="tor-bridges-provider-web-name"
+ ></html:div>
+ <html:div
+ class="tor-bridges-provider-instruction"
+ data-l10n-id="tor-bridges-provider-web-instruction"
+ data-l10n-args='{ "url": "bridges.torproject.org" }'
+ >
+ <html:a
+ href="https://bridges.torproject.org"
+ data-l10n-name="url"
+ ></html:a>
+ </html:div>
+ </html:li>
+ <html:li class="tor-bridges-provider-item">
+ <html:img
+ id="tor-bridges-provider-icon-email"
+ class="tor-bridges-provider-icon"
+ alt=""
+ />
+ <html:div
+ class="tor-bridges-provider-name"
+ data-l10n-id="tor-bridges-provider-email-name"
+ ></html:div>
+ <html:div
+ class="tor-bridges-provider-instruction"
+ data-l10n-id="tor-bridges-provider-email-instruction"
+ data-l10n-args='{ "address": "bridges(a)torproject.org" }'
+ ></html:div>
+ </html:li>
+ </html:ul>
+ <html:div id="tor-bridges-request-box">
+ <html:img
+ alt=""
+ src="chrome://browser/content/torpreferences/bridge-bot.svg"
+ ></html:img>
+ <html:div
+ id="tor-bridges-request-description"
+ data-l10n-id="tor-bridges-request-from-browser"
+ ></html:div>
+ <html:button
+ id="tor-bridges-open-request-dialog-button"
+ data-l10n-id="tor-bridges-request-button"
+ ></html:button>
+ </html:div>
+ </html:div>
</groupbox>
<!-- Advanced -->
=====================================
browser/components/torpreferences/content/provideBridgeDialog.js
=====================================
@@ -15,11 +15,24 @@ const { TorConnect, TorConnectTopics } = ChromeUtils.importESModule(
const gProvideBridgeDialog = {
init() {
this._result = window.arguments[0];
+ const mode = window.arguments[1].mode;
+
+ let titleId;
+ switch (mode) {
+ case "edit":
+ titleId = "user-provide-bridge-dialog-edit-title";
+ break;
+ case "add":
+ titleId = "user-provide-bridge-dialog-add-title";
+ break;
+ case "replace":
+ default:
+ titleId = "user-provide-bridge-dialog-replace-title";
+ break;
+ }
+
+ document.l10n.setAttributes(document.documentElement, titleId);
- document.documentElement.setAttribute(
- "title",
- TorStrings.settings.provideBridgeTitleAdd
- );
const learnMore = document.createXULElement("label");
learnMore.className = "learnMore text-link";
learnMore.setAttribute("is", "text-link");
=====================================
browser/components/torpreferences/content/provideBridgeDialog.xhtml
=====================================
@@ -9,6 +9,10 @@
xmlns:html="http://www.w3.org/1999/xhtml"
>
<dialog id="torPreferences-provideBridge-dialog" buttons="accept,cancel">
+ <linkset>
+ <html:link rel="localization" href="browser/tor-browser.ftl" />
+ </linkset>
+
<script src="chrome://browser/content/torpreferences/provideBridgeDialog.js" />
<description>
=====================================
browser/components/torpreferences/content/telegram-logo.svg
=====================================
@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="context-fill" xmlns="http://www.w3.org/2000/svg">
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M13.5527 3.74072C13.6057 3.44502 13.3069 3.21007 13.0321 3.33143L2.46222 7.99865L5.04818 8.60316L10.1001 5.29184C10.8373 4.80861 11.6593 5.7727 11.0656 6.42426L7.88895 9.91029L11.9093 12.8968L13.5527 3.74072ZM12.5272 2.18794C13.7181 1.66208 15.013 2.68016 14.783 3.96155L13.104 13.3162C12.9564 14.1382 11.9962 14.5186 11.3258 14.0205L7.03263 10.8313C6.49819 10.4343 6.42353 9.66259 6.87195 9.17049L7.47872 8.50462L5.68862 9.67797C5.4311 9.84676 5.11564 9.90263 4.81582 9.83254L1.81371 9.13075C0.762034 8.8849 0.627375 7.4424 1.61537 7.00614L12.5272 2.18794Z"/>
+</svg>
=====================================
browser/components/torpreferences/content/torPreferences.css
=====================================
@@ -447,6 +447,91 @@
fill: currentColor;
}
+#tor-bridges-provider-heading {
+ font-size: 1.14em;
+ margin-block: 48px 8px;
+}
+
+#tor-bridges-provider-area {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 16px;
+ align-items: start;
+ line-height: 1.8;
+ margin-block-start: 24px;
+}
+
+#tor-bridges-provider-list {
+ display: grid;
+ grid-template-columns: max-content max-content;
+ /* 16px gap between items. */
+ gap: 16px 12px;
+ margin-block: 16px;
+}
+
+.tor-bridges-provider-item {
+ grid-column: 1 / -1;
+ display: grid;
+ grid-template-columns: subgrid;
+ align-items: center;
+ justify-items: start;
+ /* No gap between the name and instruction. */
+ gap: 0 12px;
+}
+
+.tor-bridges-provider-icon {
+ width: 16px;
+ height: 16px;
+ -moz-context-properties: fill;
+ fill: var(--in-content-icon-color);
+}
+
+#tor-bridges-provider-icon-telegram {
+ content: url("chrome://browser/content/torpreferences/telegram-logo.svg");
+}
+
+#tor-bridges-provider-icon-web {
+ content: url("chrome://browser/content/torpreferences/network.svg");
+}
+
+#tor-bridges-provider-icon-email {
+ content: url("chrome://browser/skin/mail.svg");
+}
+
+.tor-bridges-provider-name {
+ font-weight: 600;
+ font-size: 0.85em;
+}
+
+.tor-bridges-provider-instruction {
+ grid-column: 2 / 3;
+}
+
+#tor-bridges-request-box {
+ /* Take up the full height in the container. */
+ align-self: stretch;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ padding: 16px;
+ background: var(--in-content-box-info-background);
+ border-radius: 4px;
+}
+
+#tor-bridges-request-box > * {
+ flex: 0 0 auto;
+}
+
+#tor-bridges-request-description {
+ margin-block: 12px 16px;
+}
+
+#tor-bridges-open-request-dialog-button {
+ margin: 0;
+ line-height: 1;
+}
+
#torPreferences-bridges-location {
width: 280px;
}
=====================================
browser/components/torpreferences/jar.mn
=====================================
@@ -1,6 +1,8 @@
browser.jar:
content/browser/torpreferences/bridge.svg (content/bridge.svg)
content/browser/torpreferences/bridge-qr.svg (content/bridge-qr.svg)
+ content/browser/torpreferences/telegram-logo.svg (content/telegram-logo.svg)
+ content/browser/torpreferences/bridge-bot.svg (content/bridge-bot.svg)
content/browser/torpreferences/bridgeQrDialog.xhtml (content/bridgeQrDialog.xhtml)
content/browser/torpreferences/bridgeQrDialog.js (content/bridgeQrDialog.js)
content/browser/torpreferences/builtinBridgeDialog.xhtml (content/builtinBridgeDialog.xhtml)
=====================================
browser/locales/en-US/browser/tor-browser.ftl
=====================================
@@ -121,3 +121,58 @@ tor-bridges-share-description = Share your bridges with trusted contacts.
tor-bridges-copy-addresses-button = Copy addresses
tor-bridges-qr-addresses-button =
.title = Show QR code
+
+# Shown as a heading when the user has no current bridges.
+tor-bridges-add-bridges-heading = Add bridges
+# Shown as a heading when the user has existing bridges that can be replaced.
+tor-bridges-replace-bridges-heading = Replace your bridges
+
+tor-bridges-select-built-in-description = Choose from one of { -brand-short-name }’s built-in bridges
+tor-bridges-select-built-in-button = Select a built-in bridge…
+
+tor-bridges-add-addresses-description = Enter bridge addresses you already know
+# Shown when the user has no current bridges.
+# Opens a dialog where the user can provide a new bridge address or share code.
+tor-bridges-add-new-button = Add new bridges…
+# Shown when the user has existing bridges.
+# Opens a dialog where the user can provide a new bridge address or share code to replace their current bridges.
+tor-bridges-replace-button = Replace bridges…
+
+tor-bridges-find-more-heading = Find more bridges
+# "Tor Project" is the organisation name.
+tor-bridges-find-more-description = Since many bridge addresses aren’t public, you may need to request some from the Tor Project.
+
+# "Telegram" is the common brand name of the Telegram Messenger application
+tor-bridges-provider-telegram-name = Telegram
+# Here "Message" is a verb, short for "Send a message to". This is an instruction to send a message to the given Telegram Messenger user to receive a new bridge.
+# $telegramUserName (String) - The Telegram Messenger user name that should receive messages. Should be wrapped in '<a data-l10n-name="user">' and '</a>'.
+# E.g. in English, "Message GetBridgesBot".
+tor-bridges-provider-telegram-instruction = Message <a data-l10n-name="user">{ $telegramUserName }</a>
+
+# "Web" is the proper noun for the "World Wide Web".
+tor-bridges-provider-web-name = Web
+# Instructions to visit the given website.
+# $url (String) - The URL for Tor Project bridges. Should be wrapped in '<a data-l10n-name"url">' and '</a>'.
+tor-bridges-provider-web-instruction = Visit <a data-l10n-name="url">{ $url }</a>
+
+# "Gmail" is the Google brand name. "Riseup" refers to the Riseup organisation at riseup.net.
+tor-bridges-provider-email-name = Gmail or Riseup
+# Here "Email" is a verb, short for "Send an email to". This is an instruction to send an email to the given address to receive a new bridge.
+# $address (String) - The email address that should receive the email.
+# E.g. in English, "Email bridges(a)torproject.org".
+tor-bridges-provider-email-instruction = Email { $address }
+
+tor-bridges-request-from-browser = You can also get bridges from the bridge bot without leaving { -brand-short-name }.
+tor-bridges-request-button = Request bridges…
+
+## User provided bridge dialog.
+
+# Used when the user is editing their existing bridge addresses.
+user-provide-bridge-dialog-edit-title =
+ .title = Edit your bridges
+# Used when the user has no existing bridges.
+user-provide-bridge-dialog-add-title =
+ .title = Add new bridges
+# Used when the user is replacing their existing bridges with new ones.
+user-provide-bridge-dialog-replace-title =
+ .title = Replace your bridges
=====================================
toolkit/modules/TorStrings.sys.mjs
=====================================
@@ -105,14 +105,6 @@ const Loader = {
bridgeRemoveAllDialogTitle: "Remove all bridges?",
bridgeRemoveAllDialogDescription:
"If these bridges were received from torproject.org or added manually, this action cannot be undone",
- bridgeAdd: "Add a New Bridge",
- bridgeSelectBrowserBuiltin:
- "Choose from one of Tor Browser’s built-in bridges",
- bridgeSelectBuiltin: "Select a Built-In Bridge…",
- bridgeRequestFromTorProject: "Request a bridge from torproject.org",
- bridgeRequest: "Request a Bridge…",
- bridgeEnterKnown: "Enter a bridge address you already know",
- bridgeAddManually: "Add a Bridge Manually…",
// Advanced settings
advancedHeading: "Advanced",
advancedLabel: "Configure how Tor Browser connects to the internet",
@@ -148,7 +140,6 @@ const Loader = {
captchaTextboxPlaceholder: "Enter the characters from the image",
incorrectCaptcha: "The solution is not correct. Please try again.",
// Provide bridge dialog
- provideBridgeTitleAdd: "Add a Bridge Manually",
provideBridgeDescription:
"Add a bridge provided by a trusted organization or someone you know. If you don’t have a bridge, you can request one from the Tor Project. %S",
provideBridgePlaceholder: "type address:port (one per line)",
=====================================
toolkit/torbutton/chrome/locale/en-US/settings.properties
=====================================
@@ -39,13 +39,6 @@ settings.bridgeDisableBuiltIn=Disable built-in bridges
settings.copied=Copied!
settings.bridgeRemoveAllDialogTitle=Remove all bridges?
settings.bridgeRemoveAllDialogDescription=If these bridges were received from torproject.org or added manually, this action cannot be undone
-settings.bridgeAdd=Add a New Bridge
-settings.bridgeSelectBrowserBuiltin=Choose from one of Tor Browser’s built-in bridges
-settings.bridgeSelectBuiltin=Select a Built-In Bridge…
-settings.bridgeRequestFromTorProject=Request a bridge from torproject.org
-settings.bridgeRequest=Request a Bridge…
-settings.bridgeEnterKnown=Enter a bridge address you already know
-settings.bridgeAddManually=Add a Bridge Manually…
# Advanced settings
settings.advancedHeading=Advanced
@@ -82,8 +75,6 @@ settings.solveTheCaptcha=Solve the CAPTCHA to request a bridge.
settings.captchaTextboxPlaceholder=Enter the characters from the image
settings.incorrectCaptcha=The solution is not correct. Please try again.
-# Provide bridge dialog
-settings.provideBridgeTitleAdd=Add a Bridge Manually
# Translation note: %S is a Learn more link.
settings.provideBridgeDescription=Add a bridge provided by a trusted organization or someone you know. If you don’t have a bridge, you can request one from the Tor Project. %S
settings.provideBridgePlaceholder=type address:port (one per line)
@@ -125,3 +116,13 @@ settings.bridgeShowAll=Show All Bridges
settings.bridgeShowFewer=Show Fewer Bridges
settings.allBridgesEnabled=Use current bridges
settings.bridgeRemoveAll=Remove All Bridges
+settings.bridgeAdd=Add a New Bridge
+settings.bridgeSelectBrowserBuiltin=Choose from one of Tor Browser’s built-in bridges
+settings.bridgeSelectBuiltin=Select a Built-In Bridge…
+settings.bridgeRequestFromTorProject=Request a bridge from torproject.org
+settings.bridgeRequest=Request a Bridge…
+settings.bridgeEnterKnown=Enter a bridge address you already know
+settings.bridgeAddManually=Add a Bridge Manually…
+
+# Provide bridge dialog
+settings.provideBridgeTitleAdd=Add a Bridge Manually
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/6211ed…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/6211ed…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][main] Bug 41066: Compress the APKs more
by Pier Angelo Vendrame (@pierov) 24 Jan '24
by Pier Angelo Vendrame (@pierov) 24 Jan '24
24 Jan '24
Pier Angelo Vendrame pushed to branch main at The Tor Project / Applications / tor-browser-build
Commits:
23474930 by Pier Angelo Vendrame at 2024-01-24T17:08:30+01:00
Bug 41066: Compress the APKs more
Our APK was refused by the Play Store as too big.
As a workaround, for this release we can try to compress the APK more,
by extracting it and repacking it with 7-zip.
The preferred and long-term solution would be to switch to Android App
Bundles, but this might require some changes to our signing scripts.
- - - - -
2 changed files:
- projects/browser/build.android
- projects/browser/config
Changes:
=====================================
projects/browser/build.android
=====================================
@@ -46,13 +46,19 @@ mv $rootdir/[% c('input_files_by_name/noscript') %] "$noscript_path"
mv $rootdir/allowed_addons.json $assets_dir/allowed_addons.json
-[% c('zip', {
- zip_src => [ '$assets_dir' ],
- zip_args => '$apk',
- }) %]
+mkdir apk
+pushd apk
+7zz x "$apk"
+cp -R ../assets ./
+find -type f -exec touch -m -t '[% USE date; date.format(pc("firefox-android", "timestamp"), format = "%Y%m%d%H%M") %]' {} \;
+find -type f ! -name resources.arsc -printf '%P\n' | sort > ../files.txt
+7zz a -tzip -mx9 -mtc- -spf ../repacked.apk @../files.txt
+# resources.arsc must not be compressed as per the APK specifications
+7zz a -tzip -mm=Copy -mtc- ../repacked.apk resources.arsc
+popd
aligned_apk=$(basename $apk .apk)_aligned.apk
-zipalign -vp 4 $apk $aligned_apk
+zipalign -vp 4 repacked.apk $aligned_apk
# Sign a QA build. This .apk is not a debug version and doesn't contain a debug
# flag in the manifest.
=====================================
projects/browser/config
=====================================
@@ -46,7 +46,13 @@ targets:
var:
verify_allowed_addons: 1
arch_deps:
- - openjdk-11-jdk-headless
+ - 7zip
+ - openjdk-17-jdk-headless
+ container:
+ # 7zip is in backports in bullseye, and we can already use Java 17 for
+ # apksigner.
+ suite: bookworm
+ arch: amd64
torbrowser:
var:
prefs_file: 000-tor-browser.js
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/2…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/2…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/rbm][main] Bug 40067: Use --no-verbose wget option when not running in a terminal
by richard (@richard) 24 Jan '24
by richard (@richard) 24 Jan '24
24 Jan '24
richard pushed to branch main at The Tor Project / Applications / RBM
Commits:
b5e5b04a by Nicolas Vigier at 2024-01-24T10:24:19+01:00
Bug 40067: Use --no-verbose wget option when not running in a terminal
Use the `--no-verbose` option when stdout is not a tty. We avoid setting
the option when `getting_id` is set, in order to have constant
`input_file_id` when `urlget` is being used inside the `exec` script in
`input_files`.
- - - - -
2 changed files:
- doc/options_misc.asc
- lib/RBM/DefaultConfig.pm
Changes:
=====================================
doc/options_misc.asc
=====================================
@@ -21,6 +21,9 @@ abbrev_length::
This option sets the length of the abbreviated commits, when
using the +abbrev+ option.
+isatty::
+ This option is true when stdout is connected to a tty.
+
tar::
Use this options instead of 'tar' in build scripts when you want
to create deterministic tar files. This options set tar arguments
=====================================
lib/RBM/DefaultConfig.pm
=====================================
@@ -151,6 +151,7 @@ our %default_config = (
debug => 0,
compress_tar => 'gz',
version => "[%- exit_error('No version specified for ' _ project); -%]",
+ isatty => sub { -t STDOUT },
####
####
####
@@ -577,7 +578,7 @@ set -e
END;
-%]
tmpfile="\$(mktemp -p [% shell_quote(rbm_tmp_dir) %])"
-wget -O"\$tmpfile" [% shell_quote(c("URL")) %]
+wget[% IF !c("getting_id") && !c("isatty") %] --no-verbose[% END %] -O"\$tmpfile" [% shell_quote(c("URL")) %]
mv -f "\$tmpfile" [% shell_quote(dest_dir _ "/" _ c("filename")) %]
URLGET
sig_ext => [ qw(gpg asc sig) ],
View it on GitLab: https://gitlab.torproject.org/tpo/applications/rbm/-/commit/b5e5b04aaf677c4…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/rbm/-/commit/b5e5b04aaf677c4…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][main] Update release-prep template with startpage contact to notify on major ESR transition
by richard (@richard) 24 Jan '24
by richard (@richard) 24 Jan '24
24 Jan '24
richard pushed to branch main at The Tor Project / Applications / tor-browser-build
Commits:
ef63c83c by Richard Pospesel at 2024-01-24T11:30:38+00:00
Update release-prep template with startpage contact to notify on major ESR transition
- - - - -
1 changed file:
- .gitlab/issue_templates/Release Prep - Tor Browser Alpha.md
Changes:
=====================================
.gitlab/issue_templates/Release Prep - Tor Browser Alpha.md
=====================================
@@ -169,6 +169,8 @@
- [ ] Email external partners:
- ***(Optional, after ESR migration)*** Cloudflare: ask-research(a)cloudflare.com
- **NOTE** : We need to provide them with updated user agent string so they can update their internal machinery to prevent Tor Browser users from getting so many CAPTCHAs
+ - ***(Optional, after ESR migration)*** Startpage: admin(a)startpage.com
+ - **NOTE** : Startpage also needs the updated user-agent string for better experience on their onion service sites.
</details>
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/e…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/e…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-115.7.0esr-13.5-1] fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in...
by richard (@richard) 23 Jan '24
by richard (@richard) 23 Jan '24
23 Jan '24
richard pushed to branch tor-browser-115.7.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits:
6211ed8f by Henry Wilkes at 2024-01-23T18:27:26+00:00
fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
Bug 42036: Refactor tor setting dialogs to follow firefox style.
Also, move the user focus to their new bridges when the bridge dialogs
succeed.
- - - - -
16 changed files:
- + browser/components/torpreferences/content/bridgeQrDialog.js
- − browser/components/torpreferences/content/bridgeQrDialog.mjs
- browser/components/torpreferences/content/bridgeQrDialog.xhtml
- browser/components/torpreferences/content/builtinBridgeDialog.mjs → browser/components/torpreferences/content/builtinBridgeDialog.js
- browser/components/torpreferences/content/builtinBridgeDialog.xhtml
- browser/components/torpreferences/content/connectionPane.js
- browser/components/torpreferences/content/connectionSettingsDialog.mjs → browser/components/torpreferences/content/connectionSettingsDialog.js
- browser/components/torpreferences/content/connectionSettingsDialog.xhtml
- browser/components/torpreferences/content/provideBridgeDialog.mjs → browser/components/torpreferences/content/provideBridgeDialog.js
- browser/components/torpreferences/content/provideBridgeDialog.xhtml
- browser/components/torpreferences/content/requestBridgeDialog.mjs → browser/components/torpreferences/content/requestBridgeDialog.js
- browser/components/torpreferences/content/requestBridgeDialog.xhtml
- + browser/components/torpreferences/content/torLogDialog.js
- − browser/components/torpreferences/content/torLogDialog.mjs
- browser/components/torpreferences/content/torLogDialog.xhtml
- browser/components/torpreferences/jar.mn
Changes:
=====================================
browser/components/torpreferences/content/bridgeQrDialog.js
=====================================
@@ -0,0 +1,34 @@
+"use strict";
+
+const { QRCode } = ChromeUtils.importESModule(
+ "resource://gre/modules/QRCode.sys.mjs"
+);
+
+const { TorStrings } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorStrings.sys.mjs"
+);
+
+window.addEventListener(
+ "DOMContentLoaded",
+ () => {
+ const bridgeString = window.arguments[0];
+
+ document.documentElement.setAttribute(
+ "title",
+ TorStrings.settings.scanQrTitle
+ );
+ const target = document.getElementById("bridgeQr-target");
+ const style = window.getComputedStyle(target);
+ const width = style.width.substr(0, style.width.length - 2);
+ const height = style.height.substr(0, style.height.length - 2);
+ new QRCode(target, {
+ text: bridgeString,
+ width,
+ height,
+ colorDark: style.color,
+ colorLight: style.backgroundColor,
+ document,
+ });
+ },
+ { once: true }
+);
=====================================
browser/components/torpreferences/content/bridgeQrDialog.mjs deleted
=====================================
@@ -1,44 +0,0 @@
-import { QRCode } from "resource://gre/modules/QRCode.sys.mjs";
-
-import { TorStrings } from "resource://gre/modules/TorStrings.sys.mjs";
-
-export class BridgeQrDialog {
- constructor() {
- this._bridgeString = "";
- }
-
- static get selectors() {
- return {
- target: "#bridgeQr-target",
- };
- }
-
- _populateXUL(window, dialog) {
- dialog.parentElement.setAttribute("title", TorStrings.settings.scanQrTitle);
- const target = dialog.querySelector(BridgeQrDialog.selectors.target);
- const style = window.getComputedStyle(target);
- const width = style.width.substr(0, style.width.length - 2);
- const height = style.height.substr(0, style.height.length - 2);
- new QRCode(target, {
- text: this._bridgeString,
- width,
- height,
- colorDark: style.color,
- colorLight: style.backgroundColor,
- document: window.document,
- });
- }
-
- init(window, dialog) {
- this._populateXUL(window, dialog);
- }
-
- openDialog(gSubDialog, bridgeString) {
- this._bridgeString = bridgeString;
- gSubDialog.open(
- "chrome://browser/content/torpreferences/bridgeQrDialog.xhtml",
- { features: "resizable=yes" },
- this
- );
- }
-}
=====================================
browser/components/torpreferences/content/bridgeQrDialog.xhtml
=====================================
@@ -9,6 +9,8 @@
xmlns:html="http://www.w3.org/1999/xhtml"
>
<dialog id="bridgeQr-dialog" buttons="accept">
+ <script src="chrome://browser/content/torpreferences/bridgeQrDialog.js" />
+
<html:div>
<html:div id="bridgeQr">
<html:div id="bridgeQr-target" />
@@ -16,16 +18,5 @@
<html:div id="bridgeQr-onion" />
</html:div>
</html:div>
- <script type="application/javascript">
- <![CDATA[
- "use strict";
-
- let dialogObject = window.arguments[0];
- document.addEventListener("DOMContentLoaded", () => {
- let dialogElement = document.getElementById("bridgeQr-dialog");
- dialogObject.init(window, dialogElement);
- });
- ]]>
- </script>
</dialog>
</window>
=====================================
browser/components/torpreferences/content/builtinBridgeDialog.mjs → browser/components/torpreferences/content/builtinBridgeDialog.js
=====================================
@@ -1,38 +1,32 @@
-import { TorStrings } from "resource://gre/modules/TorStrings.sys.mjs";
-
-import {
- TorSettings,
- TorBridgeSource,
-} from "resource://gre/modules/TorSettings.sys.mjs";
-
-import {
- TorConnect,
- TorConnectTopics,
-} from "resource://gre/modules/TorConnect.sys.mjs";
-
-export class BuiltinBridgeDialog {
- /**
- * Create a new instance.
- *
- * @param {Function} onSubmit - A callback for when the user accepts the
- * dialog selection.
- */
- constructor(onSubmit) {
- this.onSubmit = onSubmit;
- this._acceptButton = null;
- this._radioGroup = null;
- }
-
- _populateXUL(window, dialog) {
- const dialogWin = dialog.parentElement;
- dialogWin.setAttribute("title", TorStrings.settings.builtinBridgeHeader);
-
- dialog.querySelector(
- "#torPreferences-builtinBridge-description"
+"use strict";
+
+const { TorStrings } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorStrings.sys.mjs"
+);
+
+const { TorSettings, TorBridgeSource } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorSettings.sys.mjs"
+);
+
+const { TorConnect, TorConnectTopics } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorConnect.sys.mjs"
+);
+
+const gBuiltinBridgeDialog = {
+ init() {
+ this._result = window.arguments[0];
+
+ document.documentElement.setAttribute(
+ "title",
+ TorStrings.settings.builtinBridgeHeader
+ );
+
+ document.getElementById(
+ "torPreferences-builtinBridge-description"
).textContent = TorStrings.settings.builtinBridgeDescription2;
- this._radioGroup = dialog.querySelector(
- "#torPreferences-builtinBridge-typeSelection"
+ this._radioGroup = document.getElementById(
+ "torPreferences-builtinBridge-typeSelection"
);
const typeStrings = {
@@ -84,8 +78,12 @@ export class BuiltinBridgeDialog {
}
this._radioGroup.addEventListener("select", () => this.onSelectChange());
+
+ const dialog = document.getElementById(
+ "torPreferences-builtinBridge-dialog"
+ );
dialog.addEventListener("dialogaccept", () => {
- this.onSubmit(this._radioGroup.value, TorConnect.canBeginBootstrap);
+ this._result.accepted = true;
});
this._acceptButton = dialog.getButton("accept");
@@ -94,20 +92,28 @@ export class BuiltinBridgeDialog {
this.onSelectChange();
this.onAcceptStateChange();
- }
+ },
+
+ uninit() {
+ Services.obs.removeObserver(this, TorConnectTopics.StateChange);
+ },
onSelectChange() {
- this._acceptButton.disabled = !this._radioGroup.value;
- }
+ const value = this._radioGroup.value;
+ this._acceptButton.disabled = !value;
+ this._result.type = value;
+ },
onAcceptStateChange() {
+ const connect = TorConnect.canBeginBootstrap;
+ this._result.connect = connect;
this._acceptButton.setAttribute(
"label",
- TorConnect.canBeginBootstrap
+ connect
? TorStrings.settings.bridgeButtonConnect
: TorStrings.settings.bridgeButtonAccept
);
- }
+ },
observe(subject, topic, data) {
switch (topic) {
@@ -115,27 +121,20 @@ export class BuiltinBridgeDialog {
this.onAcceptStateChange();
break;
}
- }
-
- init(window, aDialog) {
- this._populateXUL(window, aDialog);
- }
-
- close() {
- // Unregister our observer topics.
- Services.obs.removeObserver(this, TorConnectTopics.StateChange);
- }
-
- openDialog(gSubDialog) {
- gSubDialog.open(
- "chrome://browser/content/torpreferences/builtinBridgeDialog.xhtml",
- {
- features: "resizable=yes",
- closingCallback: () => {
- this.close();
- },
+ },
+};
+
+window.addEventListener(
+ "DOMContentLoaded",
+ () => {
+ gBuiltinBridgeDialog.init();
+ window.addEventListener(
+ "unload",
+ () => {
+ gBuiltinBridgeDialog.uninit();
},
- this
+ { once: true }
);
- }
-}
+ },
+ { once: true }
+);
=====================================
browser/components/torpreferences/content/builtinBridgeDialog.xhtml
=====================================
@@ -9,6 +9,8 @@
xmlns:html="http://www.w3.org/1999/xhtml"
>
<dialog id="torPreferences-builtinBridge-dialog" buttons="accept,cancel">
+ <script src="chrome://browser/content/torpreferences/builtinBridgeDialog.js" />
+
<description id="torPreferences-builtinBridge-description"> </description>
<radiogroup id="torPreferences-builtinBridge-typeSelection">
<vbox class="builtin-bridges-option">
@@ -78,16 +80,5 @@
</html:div>
</vbox>
</radiogroup>
- <script type="application/javascript">
- <![CDATA[
- "use strict";
-
- let builtinBridgeDialog = window.arguments[0];
- document.addEventListener("DOMContentLoaded", () => {
- let dialog = document.getElementById("torPreferences-builtinBridge-dialog");
- builtinBridgeDialog.init(window, dialog);
- });
- ]]>
- </script>
</dialog>
</window>
=====================================
browser/components/torpreferences/content/connectionPane.js
=====================================
@@ -24,30 +24,6 @@ const { TorProviderBuilder, TorProviderTopics } = ChromeUtils.importESModule(
const { TorConnect, TorConnectTopics, TorConnectState, TorCensorshipLevel } =
ChromeUtils.importESModule("resource://gre/modules/TorConnect.sys.mjs");
-const { TorLogDialog } = ChromeUtils.importESModule(
- "chrome://browser/content/torpreferences/torLogDialog.mjs"
-);
-
-const { ConnectionSettingsDialog } = ChromeUtils.importESModule(
- "chrome://browser/content/torpreferences/connectionSettingsDialog.mjs"
-);
-
-const { BridgeQrDialog } = ChromeUtils.importESModule(
- "chrome://browser/content/torpreferences/bridgeQrDialog.mjs"
-);
-
-const { BuiltinBridgeDialog } = ChromeUtils.importESModule(
- "chrome://browser/content/torpreferences/builtinBridgeDialog.mjs"
-);
-
-const { RequestBridgeDialog } = ChromeUtils.importESModule(
- "chrome://browser/content/torpreferences/requestBridgeDialog.mjs"
-);
-
-const { ProvideBridgeDialog } = ChromeUtils.importESModule(
- "chrome://browser/content/torpreferences/provideBridgeDialog.mjs"
-);
-
const { MoatRPC } = ChromeUtils.importESModule(
"resource://gre/modules/Moat.sys.mjs"
);
@@ -114,6 +90,19 @@ async function getConnectedBridgeId() {
return bridge?.fingerprint ?? null;
}
+/**
+ * Show the bridge QR to the user.
+ *
+ * @param {string} bridgeString - The string to use in the QR.
+ */
+function showBridgeQr(bridgeString) {
+ gSubDialog.open(
+ "chrome://browser/content/torpreferences/bridgeQrDialog.xhtml",
+ { features: "resizable=yes" },
+ bridgeString
+ );
+}
+
// TODO: Instead of aria-live in the DOM, use the proposed ariaNotify
// API if it gets accepted into firefox and works with screen readers.
// See https://github.com/WICG/proposals/issues/112
@@ -803,8 +792,7 @@ const gBridgeGrid = {
if (!bridgeLine) {
return;
}
- const dialog = new BridgeQrDialog();
- dialog.openDialog(gSubDialog, bridgeLine);
+ showBridgeQr(bridgeLine);
});
row.menu
.querySelector(".tor-bridges-options-copy-one-menu-item")
@@ -1571,8 +1559,7 @@ const gBridgeSettings = {
if (!this._canQRBridges) {
return;
}
- const dialog = new BridgeQrDialog();
- dialog.openDialog(gSubDialog, this._bridgeStrings);
+ showBridgeQr(this._bridgeStrings);
},
/**
@@ -2136,96 +2123,133 @@ const gConnectionPane = (function () {
},
/**
- * Save and apply settings, then optionally open about:torconnect and start
- * bootstrapping.
+ * Open a bridge dialog that will change the users bridges.
*
- * @param {fucntion} changes - The changes to make.
- * @param {boolean} connect - Whether to open about:torconnect and start
- * bootstrapping if possible.
+ * @param {string} url - The url of the dialog to open.
+ * @param {Function} onAccept - The method to call if the bridge dialog was
+ * accepted by the user. This will be passed a "result" object containing
+ * data set by the dialog. This should return a promise that resolves once
+ * the bridge settings have been set, or null if the settings have not
+ * been applied.
*/
- async saveBridgeSettings(changes, connect) {
- // TODO: Move focus into the bridge area.
- // dialog.ownerGlobal.addEventListener("unload", () => gCurrentBridgesArea.takeFocus(), { once: true });
- // or use closedCallback in gSubDialog.open()
- setTorSettings(changes);
-
- if (!connect) {
- return;
- }
-
- // The bridge dialog button is "connect" when Tor is not bootstrapped,
- // so do the connect.
+ openBridgeDialog(url, onAccept) {
+ const result = { accepted: false, connect: false };
+ let savedSettings = null;
+ gSubDialog.open(
+ url,
+ {
+ features: "resizable=yes",
+ closingCallback: () => {
+ if (!result.accepted) {
+ return;
+ }
+ savedSettings = onAccept(result);
+ if (!savedSettings) {
+ // No change in settings.
+ return;
+ }
+ if (!result.connect) {
+ // Do not open about:torconnect.
+ return;
+ }
- // Start Bootstrapping, which should use the configured bridges.
- // NOTE: We do this regardless of any previous TorConnect Error.
- if (TorConnect.canBeginBootstrap) {
- TorConnect.beginBootstrap();
- }
- // Open "about:torconnect".
- // FIXME: If there has been a previous bootstrapping error then
- // "about:torconnect" will be trying to get the user to use
- // AutoBootstrapping. It is not set up to handle a forced direct
- // entry to plain Bootstrapping from this dialog so the UI will not
- // be aligned. In particular the
- // AboutTorConnect.uiState.bootstrapCause will be aligned to
- // whatever was shown previously in "about:torconnect" instead.
- TorConnect.openTorConnect();
+ // Wait until the settings are applied before bootstrapping.
+ savedSettings.then(() => {
+ // The bridge dialog button is "connect" when Tor is not
+ // bootstrapped, so do the connect.
+
+ // Start Bootstrapping, which should use the configured bridges.
+ // NOTE: We do this regardless of any previous TorConnect Error.
+ if (TorConnect.canBeginBootstrap) {
+ TorConnect.beginBootstrap();
+ }
+ // Open "about:torconnect".
+ // FIXME: If there has been a previous bootstrapping error then
+ // "about:torconnect" will be trying to get the user to use
+ // AutoBootstrapping. It is not set up to handle a forced direct
+ // entry to plain Bootstrapping from this dialog so the UI will
+ // not be aligned. In particular the
+ // AboutTorConnect.uiState.bootstrapCause will be aligned to
+ // whatever was shown previously in "about:torconnect" instead.
+ TorConnect.openTorConnect();
+ });
+ },
+ // closedCallback should be called after gSubDialog has already
+ // re-assigned focus back to the document.
+ closedCallback: () => {
+ if (!savedSettings) {
+ return;
+ }
+ // Wait until the settings have changed, so that the UI could
+ // respond, then move focus.
+ savedSettings.then(() => gCurrentBridgesArea.takeFocus());
+ },
+ },
+ result
+ );
},
onAddBuiltinBridge() {
- const builtinBridgeDialog = new BuiltinBridgeDialog(
- (bridgeType, connect) => {
- this.saveBridgeSettings(() => {
+ this.openBridgeDialog(
+ "chrome://browser/content/torpreferences/builtinBridgeDialog.xhtml",
+ result => {
+ if (!result.type) {
+ return null;
+ }
+ return setTorSettings(() => {
TorSettings.bridges.enabled = true;
TorSettings.bridges.source = TorBridgeSource.BuiltIn;
- TorSettings.bridges.builtin_type = bridgeType;
- }, connect);
+ TorSettings.bridges.builtin_type = result.type;
+ });
}
);
- builtinBridgeDialog.openDialog(gSubDialog);
},
// called when the request bridge button is activated
onRequestBridge() {
- const requestBridgeDialog = new RequestBridgeDialog(
- (aBridges, connect) => {
- if (!aBridges.length) {
- return;
+ this.openBridgeDialog(
+ "chrome://browser/content/torpreferences/requestBridgeDialog.xhtml",
+ result => {
+ if (!result.bridges?.length) {
+ return null;
}
-
- const bridgeStrings = aBridges.join("\n");
-
- this.saveBridgeSettings(() => {
+ return setTorSettings(() => {
TorSettings.bridges.enabled = true;
TorSettings.bridges.source = TorBridgeSource.BridgeDB;
- TorSettings.bridges.bridge_strings = bridgeStrings;
- }, connect);
+ TorSettings.bridges.bridge_strings = result.bridges.join("\n");
+ });
}
);
- requestBridgeDialog.openDialog(gSubDialog);
},
onAddBridgeManually() {
- const provideBridgeDialog = new ProvideBridgeDialog(
- (aBridgeString, connect) => {
- this.saveBridgeSettings(() => {
+ this.openBridgeDialog(
+ "chrome://browser/content/torpreferences/provideBridgeDialog.xhtml",
+ result => {
+ if (!result.bridgeStrings) {
+ return null;
+ }
+ return setTorSettings(() => {
TorSettings.bridges.enabled = true;
TorSettings.bridges.source = TorBridgeSource.UserProvided;
- TorSettings.bridges.bridge_strings = aBridgeString;
- }, connect);
+ TorSettings.bridges.bridge_strings = result.bridgeStrings;
+ });
}
);
- provideBridgeDialog.openDialog(gSubDialog);
},
onAdvancedSettings() {
- const connectionSettingsDialog = new ConnectionSettingsDialog();
- connectionSettingsDialog.openDialog(gSubDialog);
+ gSubDialog.open(
+ "chrome://browser/content/torpreferences/connectionSettingsDialog.xhtml",
+ { features: "resizable=yes" }
+ );
},
onViewTorLogs() {
- const torLogDialog = new TorLogDialog();
- torLogDialog.openDialog(gSubDialog);
+ gSubDialog.open(
+ "chrome://browser/content/torpreferences/torLogDialog.xhtml",
+ { features: "resizable=yes" }
+ );
},
};
return retval;
=====================================
browser/components/torpreferences/content/connectionSettingsDialog.mjs → browser/components/torpreferences/content/connectionSettingsDialog.js
=====================================
@@ -1,73 +1,67 @@
-import {
- TorSettings,
- TorProxyType,
-} from "resource://gre/modules/TorSettings.sys.mjs";
+"use strict";
-import { TorStrings } from "resource://gre/modules/TorStrings.sys.mjs";
+const { TorSettings, TorProxyType } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorSettings.sys.mjs"
+);
-export class ConnectionSettingsDialog {
- constructor() {
- this._dialog = null;
- this._useProxyCheckbox = null;
- this._proxyTypeLabel = null;
- this._proxyTypeMenulist = null;
- this._proxyAddressLabel = null;
- this._proxyAddressTextbox = null;
- this._proxyPortLabel = null;
- this._proxyPortTextbox = null;
- this._proxyUsernameLabel = null;
- this._proxyUsernameTextbox = null;
- this._proxyPasswordLabel = null;
- this._proxyPasswordTextbox = null;
- this._useFirewallCheckbox = null;
- this._allowedPortsLabel = null;
- this._allowedPortsTextbox = null;
- }
+const { TorStrings } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorStrings.sys.mjs"
+);
- static get selectors() {
- return {
- header: "#torPreferences-connection-header",
- useProxyCheckbox: "checkbox#torPreferences-connection-toggleProxy",
- proxyTypeLabel: "label#torPreferences-localProxy-type",
- proxyTypeList: "menulist#torPreferences-localProxy-builtinList",
- proxyAddressLabel: "label#torPreferences-localProxy-address",
- proxyAddressTextbox: "input#torPreferences-localProxy-textboxAddress",
- proxyPortLabel: "label#torPreferences-localProxy-port",
- proxyPortTextbox: "input#torPreferences-localProxy-textboxPort",
- proxyUsernameLabel: "label#torPreferences-localProxy-username",
- proxyUsernameTextbox: "input#torPreferences-localProxy-textboxUsername",
- proxyPasswordLabel: "label#torPreferences-localProxy-password",
- proxyPasswordTextbox: "input#torPreferences-localProxy-textboxPassword",
- useFirewallCheckbox: "checkbox#torPreferences-connection-toggleFirewall",
- firewallAllowedPortsLabel: "label#torPreferences-connection-allowedPorts",
- firewallAllowedPortsTextbox:
- "input#torPreferences-connection-textboxAllowedPorts",
- };
- }
+const gConnectionSettingsDialog = {
+ _useProxyCheckbox: null,
+ _proxyTypeLabel: null,
+ _proxyTypeMenulist: null,
+ _proxyAddressLabel: null,
+ _proxyAddressTextbox: null,
+ _proxyPortLabel: null,
+ _proxyPortTextbox: null,
+ _proxyUsernameLabel: null,
+ _proxyUsernameTextbox: null,
+ _proxyPasswordLabel: null,
+ _proxyPasswordTextbox: null,
+ _useFirewallCheckbox: null,
+ _allowedPortsLabel: null,
+ _allowedPortsTextbox: null,
+
+ selectors: {
+ header: "#torPreferences-connection-header",
+ useProxyCheckbox: "checkbox#torPreferences-connection-toggleProxy",
+ proxyTypeLabel: "label#torPreferences-localProxy-type",
+ proxyTypeList: "menulist#torPreferences-localProxy-builtinList",
+ proxyAddressLabel: "label#torPreferences-localProxy-address",
+ proxyAddressTextbox: "input#torPreferences-localProxy-textboxAddress",
+ proxyPortLabel: "label#torPreferences-localProxy-port",
+ proxyPortTextbox: "input#torPreferences-localProxy-textboxPort",
+ proxyUsernameLabel: "label#torPreferences-localProxy-username",
+ proxyUsernameTextbox: "input#torPreferences-localProxy-textboxUsername",
+ proxyPasswordLabel: "label#torPreferences-localProxy-password",
+ proxyPasswordTextbox: "input#torPreferences-localProxy-textboxPassword",
+ useFirewallCheckbox: "checkbox#torPreferences-connection-toggleFirewall",
+ firewallAllowedPortsLabel: "label#torPreferences-connection-allowedPorts",
+ firewallAllowedPortsTextbox:
+ "input#torPreferences-connection-textboxAllowedPorts",
+ },
// disables the provided list of elements
_setElementsDisabled(elements, disabled) {
for (let currentElement of elements) {
currentElement.disabled = disabled;
}
- }
+ },
- _populateXUL(window, aDialog) {
- const selectors = ConnectionSettingsDialog.selectors;
+ init() {
+ const selectors = this.selectors;
- this._dialog = aDialog;
- const dialogWin = this._dialog.parentElement;
- dialogWin.setAttribute(
+ document.documentElement.setAttribute(
"title",
TorStrings.settings.connectionSettingsDialogTitle
);
- this._dialog.querySelector(selectors.header).textContent =
+ document.querySelector(selectors.header).textContent =
TorStrings.settings.connectionSettingsDialogHeader;
// Local Proxy
- this._useProxyCheckbox = this._dialog.querySelector(
- selectors.useProxyCheckbox
- );
+ this._useProxyCheckbox = document.querySelector(selectors.useProxyCheckbox);
this._useProxyCheckbox.setAttribute(
"label",
TorStrings.settings.useLocalProxy
@@ -76,7 +70,7 @@ export class ConnectionSettingsDialog {
const checked = this._useProxyCheckbox.checked;
this.onToggleProxy(checked);
});
- this._proxyTypeLabel = this._dialog.querySelector(selectors.proxyTypeLabel);
+ this._proxyTypeLabel = document.querySelector(selectors.proxyTypeLabel);
this._proxyTypeLabel.setAttribute("value", TorStrings.settings.proxyType);
let mockProxies = [
@@ -90,9 +84,7 @@ export class ConnectionSettingsDialog {
},
{ value: TorProxyType.HTTPS, label: TorStrings.settings.proxyTypeHTTP },
];
- this._proxyTypeMenulist = this._dialog.querySelector(
- selectors.proxyTypeList
- );
+ this._proxyTypeMenulist = document.querySelector(selectors.proxyTypeList);
this._proxyTypeMenulist.addEventListener("command", e => {
const value = this._proxyTypeMenulist.value;
this.onSelectProxyType(value);
@@ -104,14 +96,14 @@ export class ConnectionSettingsDialog {
this._proxyTypeMenulist.querySelector("menupopup").appendChild(menuEntry);
}
- this._proxyAddressLabel = this._dialog.querySelector(
+ this._proxyAddressLabel = document.querySelector(
selectors.proxyAddressLabel
);
this._proxyAddressLabel.setAttribute(
"value",
TorStrings.settings.proxyAddress
);
- this._proxyAddressTextbox = this._dialog.querySelector(
+ this._proxyAddressTextbox = document.querySelector(
selectors.proxyAddressTextbox
);
this._proxyAddressTextbox.setAttribute(
@@ -129,33 +121,31 @@ export class ConnectionSettingsDialog {
}
}
});
- this._proxyPortLabel = this._dialog.querySelector(selectors.proxyPortLabel);
+ this._proxyPortLabel = document.querySelector(selectors.proxyPortLabel);
this._proxyPortLabel.setAttribute("value", TorStrings.settings.proxyPort);
- this._proxyPortTextbox = this._dialog.querySelector(
- selectors.proxyPortTextbox
- );
- this._proxyUsernameLabel = this._dialog.querySelector(
+ this._proxyPortTextbox = document.querySelector(selectors.proxyPortTextbox);
+ this._proxyUsernameLabel = document.querySelector(
selectors.proxyUsernameLabel
);
this._proxyUsernameLabel.setAttribute(
"value",
TorStrings.settings.proxyUsername
);
- this._proxyUsernameTextbox = this._dialog.querySelector(
+ this._proxyUsernameTextbox = document.querySelector(
selectors.proxyUsernameTextbox
);
this._proxyUsernameTextbox.setAttribute(
"placeholder",
TorStrings.settings.proxyUsernamePasswordPlaceholder
);
- this._proxyPasswordLabel = this._dialog.querySelector(
+ this._proxyPasswordLabel = document.querySelector(
selectors.proxyPasswordLabel
);
this._proxyPasswordLabel.setAttribute(
"value",
TorStrings.settings.proxyPassword
);
- this._proxyPasswordTextbox = this._dialog.querySelector(
+ this._proxyPasswordTextbox = document.querySelector(
selectors.proxyPasswordTextbox
);
this._proxyPasswordTextbox.setAttribute(
@@ -174,7 +164,7 @@ export class ConnectionSettingsDialog {
}
// Local firewall
- this._useFirewallCheckbox = this._dialog.querySelector(
+ this._useFirewallCheckbox = document.querySelector(
selectors.useFirewallCheckbox
);
this._useFirewallCheckbox.setAttribute(
@@ -185,14 +175,14 @@ export class ConnectionSettingsDialog {
const checked = this._useFirewallCheckbox.checked;
this.onToggleFirewall(checked);
});
- this._allowedPortsLabel = this._dialog.querySelector(
+ this._allowedPortsLabel = document.querySelector(
selectors.firewallAllowedPortsLabel
);
this._allowedPortsLabel.setAttribute(
"value",
TorStrings.settings.allowedPorts
);
- this._allowedPortsTextbox = this._dialog.querySelector(
+ this._allowedPortsTextbox = document.querySelector(
selectors.firewallAllowedPortsTextbox
);
this._allowedPortsTextbox.setAttribute(
@@ -207,10 +197,11 @@ export class ConnectionSettingsDialog {
TorSettings.firewall.allowed_ports.join(", ");
}
- this._dialog.addEventListener("dialogaccept", e => {
+ const dialog = document.getElementById("torPreferences-connection-dialog");
+ dialog.addEventListener("dialogaccept", e => {
this._applySettings();
});
- }
+ },
// callback when proxy is toggled
onToggleProxy(enabled) {
@@ -235,7 +226,7 @@ export class ConnectionSettingsDialog {
if (enabled) {
this.onSelectProxyType(this._proxyTypeMenulist.value);
}
- }
+ },
// callback when proxy type is changed
onSelectProxyType(value) {
@@ -308,7 +299,7 @@ export class ConnectionSettingsDialog {
break;
}
}
- }
+ },
// callback when firewall proxy is toggled
onToggleFirewall(enabled) {
@@ -319,7 +310,7 @@ export class ConnectionSettingsDialog {
[this._allowedPortsLabel, this._allowedPortsTextbox],
disabled
);
- }
+ },
// pushes settings from UI to tor
_applySettings() {
@@ -372,17 +363,13 @@ export class ConnectionSettingsDialog {
TorSettings.saveToPrefs();
TorSettings.applySettings();
- }
-
- init(window, aDialog) {
- this._populateXUL(window, aDialog);
- }
+ },
+};
- openDialog(gSubDialog) {
- gSubDialog.open(
- "chrome://browser/content/torpreferences/connectionSettingsDialog.xhtml",
- { features: "resizable=yes" },
- this
- );
- }
-}
+window.addEventListener(
+ "DOMContentLoaded",
+ () => {
+ gConnectionSettingsDialog.init();
+ },
+ { once: true }
+);
=====================================
browser/components/torpreferences/content/connectionSettingsDialog.xhtml
=====================================
@@ -9,6 +9,8 @@
xmlns:html="http://www.w3.org/1999/xhtml"
>
<dialog id="torPreferences-connection-dialog" buttons="accept,cancel">
+ <script src="chrome://browser/content/torpreferences/connectionSettingsDialog.js" />
+
<html:h3 id="torPreferences-connection-header">​</html:h3>
<!-- Local Proxy -->
<checkbox id="torPreferences-connection-toggleProxy" label="​" />
@@ -78,16 +80,5 @@
/>
</hbox>
</box>
- <script type="application/javascript">
- <![CDATA[
- "use strict";
-
- let connectionSettingsDialog = window.arguments[0];
- document.addEventListener("DOMContentLoaded", () => {
- let dialog = document.getElementById("torPreferences-connection-dialog");
- connectionSettingsDialog.init(window, dialog);
- });
- ]]>
- </script>
</dialog>
</window>
=====================================
browser/components/torpreferences/content/provideBridgeDialog.mjs → browser/components/torpreferences/content/provideBridgeDialog.js
=====================================
@@ -1,53 +1,44 @@
-import { TorStrings } from "resource://gre/modules/TorStrings.sys.mjs";
-
-import {
- TorSettings,
- TorBridgeSource,
-} from "resource://gre/modules/TorSettings.sys.mjs";
-
-import {
- TorConnect,
- TorConnectTopics,
-} from "resource://gre/modules/TorConnect.sys.mjs";
-
-export class ProvideBridgeDialog {
- constructor(onSubmit) {
- this.onSubmit = onSubmit;
- this._dialog = null;
- this._textarea = null;
- this._acceptButton = null;
- }
-
- static get selectors() {
- return {
- description: "#torPreferences-provideBridge-description",
- textarea: "#torPreferences-provideBridge-textarea",
- };
- }
-
- _populateXUL(window, aDialog) {
- const selectors = ProvideBridgeDialog.selectors;
-
- const openHelp = () => {
+"use strict";
+
+const { TorStrings } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorStrings.sys.mjs"
+);
+
+const { TorSettings, TorBridgeSource } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorSettings.sys.mjs"
+);
+
+const { TorConnect, TorConnectTopics } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorConnect.sys.mjs"
+);
+
+const gProvideBridgeDialog = {
+ init() {
+ this._result = window.arguments[0];
+
+ document.documentElement.setAttribute(
+ "title",
+ TorStrings.settings.provideBridgeTitleAdd
+ );
+ const learnMore = document.createXULElement("label");
+ learnMore.className = "learnMore text-link";
+ learnMore.setAttribute("is", "text-link");
+ learnMore.setAttribute("value", TorStrings.settings.learnMore);
+ learnMore.addEventListener("click", () => {
window.top.openTrustedLinkIn(
TorStrings.settings.learnMoreBridgesURL,
"tab"
);
- };
+ });
- this._dialog = aDialog;
- const dialogWin = this._dialog.parentElement;
- dialogWin.setAttribute("title", TorStrings.settings.provideBridgeTitleAdd);
- const learnMore = window.document.createXULElement("label");
- learnMore.className = "learnMore text-link";
- learnMore.setAttribute("is", "text-link");
- learnMore.setAttribute("value", TorStrings.settings.learnMore);
- learnMore.addEventListener("click", openHelp);
- const descr = this._dialog.querySelector(selectors.description);
- descr.textContent = "";
const pieces = TorStrings.settings.provideBridgeDescription.split("%S");
- descr.append(pieces[0], learnMore, pieces[1] || "");
- this._textarea = this._dialog.querySelector(selectors.textarea);
+ document
+ .getElementById("torPreferences-provideBridge-description")
+ .replaceChildren(pieces[0], learnMore, pieces[1] || "");
+
+ this._textarea = document.getElementById(
+ "torPreferences-provideBridge-textarea"
+ );
this._textarea.setAttribute(
"placeholder",
TorStrings.settings.provideBridgePlaceholder
@@ -58,32 +49,44 @@ export class ProvideBridgeDialog {
this._textarea.value = TorSettings.bridges.bridge_strings.join("\n");
}
- this._dialog.addEventListener("dialogaccept", e => {
- this.onSubmit(this._textarea.value, TorConnect.canBeginBootstrap);
+ const dialog = document.getElementById(
+ "torPreferences-provideBridge-dialog"
+ );
+ dialog.addEventListener("dialogaccept", e => {
+ this._result.accepted = true;
});
- this._acceptButton = this._dialog.getButton("accept");
+ this._acceptButton = dialog.getButton("accept");
Services.obs.addObserver(this, TorConnectTopics.StateChange);
this.onValueChange();
this.onAcceptStateChange();
- }
+ },
+
+ uninit() {
+ Services.obs.removeObserver(this, TorConnectTopics.StateChange);
+ },
onValueChange() {
// TODO: Do some proper value parsing and error reporting. See
// tor-browser#40552.
- this._acceptButton.disabled = !this._textarea.value.trim();
- }
+ const value = this._textarea.value.trim();
+ this._acceptButton.disabled = !value;
+ this._result.bridgeStrings = value;
+ },
onAcceptStateChange() {
+ const connect = TorConnect.canBeginBootstrap;
+ this._result.connect = connect;
+
this._acceptButton.setAttribute(
"label",
- TorConnect.canBeginBootstrap
+ connect
? TorStrings.settings.bridgeButtonConnect
: TorStrings.settings.bridgeButtonAccept
);
- }
+ },
observe(subject, topic, data) {
switch (topic) {
@@ -91,27 +94,20 @@ export class ProvideBridgeDialog {
this.onAcceptStateChange();
break;
}
- }
-
- init(window, aDialog) {
- this._populateXUL(window, aDialog);
- }
-
- close() {
- // Unregister our observer topics.
- Services.obs.removeObserver(this, TorConnectTopics.StateChange);
- }
-
- openDialog(gSubDialog) {
- gSubDialog.open(
- "chrome://browser/content/torpreferences/provideBridgeDialog.xhtml",
- {
- features: "resizable=yes",
- closingCallback: () => {
- this.close();
- },
+ },
+};
+
+window.addEventListener(
+ "DOMContentLoaded",
+ () => {
+ gProvideBridgeDialog.init();
+ window.addEventListener(
+ "unload",
+ () => {
+ gProvideBridgeDialog.uninit();
},
- this
+ { once: true }
);
- }
-}
+ },
+ { once: true }
+);
=====================================
browser/components/torpreferences/content/provideBridgeDialog.xhtml
=====================================
@@ -9,6 +9,8 @@
xmlns:html="http://www.w3.org/1999/xhtml"
>
<dialog id="torPreferences-provideBridge-dialog" buttons="accept,cancel">
+ <script src="chrome://browser/content/torpreferences/provideBridgeDialog.js" />
+
<description>
<html:div id="torPreferences-provideBridge-description"
>​<br />​</html:div
@@ -19,16 +21,5 @@
multiline="true"
rows="3"
/>
- <script type="application/javascript">
- <![CDATA[
- "use strict";
-
- let provideBridgeDialog = window.arguments[0];
- document.addEventListener("DOMContentLoaded", () => {
- let dialog = document.getElementById("torPreferences-provideBridge-dialog");
- provideBridgeDialog.init(window, dialog);
- });
- ]]>
- </script>
</dialog>
</window>
=====================================
browser/components/torpreferences/content/requestBridgeDialog.mjs → browser/components/torpreferences/content/requestBridgeDialog.js
=====================================
@@ -1,44 +1,39 @@
-import { BridgeDB } from "resource://gre/modules/BridgeDB.sys.mjs";
-import { TorStrings } from "resource://gre/modules/TorStrings.sys.mjs";
-
-import {
- TorConnect,
- TorConnectTopics,
-} from "resource://gre/modules/TorConnect.sys.mjs";
-
-export class RequestBridgeDialog {
- constructor(onSubmit) {
- this.onSubmit = onSubmit;
- this._dialog = null;
- this._submitButton = null;
- this._dialogHeader = null;
- this._captchaImage = null;
- this._captchaEntryTextbox = null;
- this._captchaRefreshButton = null;
- this._incorrectCaptchaHbox = null;
- this._incorrectCaptchaLabel = null;
- }
-
- static get selectors() {
- return {
- dialogHeader: "h3#torPreferences-requestBridge-header",
- captchaImage: "image#torPreferences-requestBridge-captchaImage",
- captchaEntryTextbox: "input#torPreferences-requestBridge-captchaTextbox",
- refreshCaptchaButton:
- "button#torPreferences-requestBridge-refreshCaptchaButton",
- incorrectCaptchaHbox:
- "hbox#torPreferences-requestBridge-incorrectCaptchaHbox",
- incorrectCaptchaLabel:
- "label#torPreferences-requestBridge-incorrectCaptchaError",
- };
- }
-
- _populateXUL(window, dialog) {
- const selectors = RequestBridgeDialog.selectors;
+"use strict";
+
+const { BridgeDB } = ChromeUtils.importESModule(
+ "resource://gre/modules/BridgeDB.sys.mjs"
+);
+const { TorStrings } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorStrings.sys.mjs"
+);
+
+const { TorConnect, TorConnectTopics } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorConnect.sys.mjs"
+);
+
+const gRequestBridgeDialog = {
+ selectors: {
+ dialogHeader: "h3#torPreferences-requestBridge-header",
+ captchaImage: "image#torPreferences-requestBridge-captchaImage",
+ captchaEntryTextbox: "input#torPreferences-requestBridge-captchaTextbox",
+ refreshCaptchaButton:
+ "button#torPreferences-requestBridge-refreshCaptchaButton",
+ incorrectCaptchaHbox:
+ "hbox#torPreferences-requestBridge-incorrectCaptchaHbox",
+ incorrectCaptchaLabel:
+ "label#torPreferences-requestBridge-incorrectCaptchaError",
+ },
+
+ init() {
+ this._result = window.arguments[0];
+
+ const selectors = this.selectors;
+
+ this._dialog = document.getElementById(
+ "torPreferences-requestBridge-dialog"
+ );
- this._dialog = dialog;
- const dialogWin = dialog.parentElement;
- dialogWin.setAttribute(
+ document.documentElement.setAttribute(
"title",
TorStrings.settings.requestBridgeDialogTitle
);
@@ -104,16 +99,24 @@ export class RequestBridgeDialog {
Services.obs.addObserver(this, TorConnectTopics.StateChange);
this.onAcceptStateChange();
- }
+ },
+
+ uninit() {
+ BridgeDB.close();
+ // Unregister our observer topics.
+ Services.obs.removeObserver(this, TorConnectTopics.StateChange);
+ },
onAcceptStateChange() {
+ const connect = TorConnect.canBeginBootstrap;
+ this._result.connect = connect;
this._submitButton.setAttribute(
"label",
- TorConnect.canBeginBootstrap
+ connect
? TorStrings.settings.bridgeButtonConnect
: TorStrings.settings.submitCaptcha
);
- }
+ },
observe(subject, topic, data) {
switch (topic) {
@@ -121,7 +124,7 @@ export class RequestBridgeDialog {
this.onAcceptStateChange();
break;
}
- }
+ },
_setcaptchaImage(uri) {
if (uri != this._captchaImage.src) {
@@ -131,27 +134,17 @@ export class RequestBridgeDialog {
this._captchaEntryTextbox.focus();
this._captchaEntryTextbox.select();
}
- }
+ },
_setUIDisabled(disabled) {
this._submitButton.disabled = this._captchaGuessIsEmpty() || disabled;
this._captchaEntryTextbox.disabled = disabled;
this._captchaRefreshButton.disabled = disabled;
- }
+ },
_captchaGuessIsEmpty() {
return this._captchaEntryTextbox.value == "";
- }
-
- init(window, dialog) {
- this._populateXUL(window, dialog);
- }
-
- close() {
- BridgeDB.close();
- // Unregister our observer topics.
- Services.obs.removeObserver(this, TorConnectTopics.StateChange);
- }
+ },
/*
Event Handlers
@@ -169,8 +162,9 @@ export class RequestBridgeDialog {
BridgeDB.submitCaptchaGuess(captchaText)
.then(aBridges => {
- if (aBridges) {
- this.onSubmit(aBridges, TorConnect.canBeginBootstrap);
+ if (aBridges && aBridges.length) {
+ this._result.accepted = true;
+ this._result.bridges = aBridges;
this._submitButton.disabled = false;
// This was successful, but use cancelDialog() to close, since
// we intercept the `dialogaccept` event.
@@ -186,7 +180,7 @@ export class RequestBridgeDialog {
this._incorrectCaptchaHbox.style.visibility = "visible";
console.log(aError);
});
- }
+ },
onRefreshCaptcha() {
this._setUIDisabled(true);
@@ -198,18 +192,20 @@ export class RequestBridgeDialog {
BridgeDB.requestNewCaptchaImage().then(uri => {
this._setcaptchaImage(uri);
});
- }
-
- openDialog(gSubDialog) {
- gSubDialog.open(
- "chrome://browser/content/torpreferences/requestBridgeDialog.xhtml",
- {
- features: "resizable=yes",
- closingCallback: () => {
- this.close();
- },
+ },
+};
+
+window.addEventListener(
+ "DOMContentLoaded",
+ () => {
+ gRequestBridgeDialog.init();
+ window.addEventListener(
+ "unload",
+ () => {
+ gRequestBridgeDialog.uninit();
},
- this
+ { once: true }
);
- }
-}
+ },
+ { once: true }
+);
=====================================
browser/components/torpreferences/content/requestBridgeDialog.xhtml
=====================================
@@ -9,6 +9,8 @@
xmlns:html="http://www.w3.org/1999/xhtml"
>
<dialog id="torPreferences-requestBridge-dialog" buttons="accept,cancel">
+ <script src="chrome://browser/content/torpreferences/requestBridgeDialog.js" />
+
<!-- ok, so ​ is a zero-width space. We need to have *something* in the innerText so that XUL knows how tall the
title node is so that it can determine how large to make the dialog element's inner draw area. If we have nothing
in the innerText, then it collapse to 0 height, and the contents of the dialog ends up partially hidden >:( -->
@@ -30,16 +32,5 @@
<image id="torPreferences-requestBridge-errorIcon" />
<label id="torPreferences-requestBridge-incorrectCaptchaError" flex="1" />
</hbox>
- <script type="application/javascript">
- <![CDATA[
- "use strict";
-
- let requestBridgeDialog = window.arguments[0];
- document.addEventListener("DOMContentLoaded", () => {
- let dialog = document.getElementById("torPreferences-requestBridge-dialog");
- requestBridgeDialog.init(window, dialog);
- });
- ]]>
- </script>
</dialog>
</window>
=====================================
browser/components/torpreferences/content/torLogDialog.js
=====================================
@@ -0,0 +1,62 @@
+"use strict";
+
+const { setTimeout, clearTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+const { TorProviderBuilder } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorProviderBuilder.sys.mjs"
+);
+const { TorStrings } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorStrings.sys.mjs"
+);
+
+window.addEventListener(
+ "DOMContentLoaded",
+ () => {
+ document.documentElement.setAttribute(
+ "title",
+ TorStrings.settings.torLogDialogTitle
+ );
+
+ const dialog = document.getElementById("torPreferences-torLog-dialog");
+ const copyLogButton = dialog.getButton("extra1");
+ copyLogButton.setAttribute("label", TorStrings.settings.copyLog);
+
+ const logText = document.getElementById(
+ "torPreferences-torDialog-textarea"
+ );
+
+ let restoreButtonTimeout = null;
+ copyLogButton.addEventListener("command", () => {
+ // Copy tor log messages to the system clipboard.
+ let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
+ Ci.nsIClipboardHelper
+ );
+ clipboard.copyString(logText.value);
+
+ const label = copyLogButton.querySelector("label");
+ label.setAttribute("value", TorStrings.settings.copied);
+ copyLogButton.classList.add("primary");
+
+ const RESTORE_TIME = 1200;
+ if (restoreButtonTimeout !== null) {
+ clearTimeout(restoreButtonTimeout);
+ }
+ restoreButtonTimeout = setTimeout(() => {
+ label.setAttribute("value", TorStrings.settings.copyLog);
+ copyLogButton.classList.remove("primary");
+ restoreButtonTimeout = null;
+ }, RESTORE_TIME);
+ });
+
+ // A waiting state should not be needed at this point.
+ // Also, we probably cannot even arrive here if the provider failed to
+ // initialize, otherwise we could use a try/catch, and write the exception
+ // text in the logs, instead.
+ TorProviderBuilder.build().then(
+ provider => (logText.value = provider.getLog())
+ );
+ },
+ { once: true }
+);
=====================================
browser/components/torpreferences/content/torLogDialog.mjs deleted
=====================================
@@ -1,78 +0,0 @@
-import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";
-
-import { TorProviderBuilder } from "resource://gre/modules/TorProviderBuilder.sys.mjs";
-import { TorStrings } from "resource://gre/modules/TorStrings.sys.mjs";
-
-export class TorLogDialog {
- constructor() {
- this._dialog = null;
- this._logTextarea = null;
- this._copyLogButton = null;
- this._restoreButtonTimeout = null;
- }
-
- static get selectors() {
- return {
- copyLogButton: "extra1",
- logTextarea: "textarea#torPreferences-torDialog-textarea",
- };
- }
-
- async _populateXUL(aDialog) {
- this._dialog = aDialog;
- const dialogWin = this._dialog.parentElement;
- dialogWin.setAttribute("title", TorStrings.settings.torLogDialogTitle);
-
- this._logTextarea = this._dialog.querySelector(
- TorLogDialog.selectors.logTextarea
- );
-
- this._copyLogButton = this._dialog.getButton(
- TorLogDialog.selectors.copyLogButton
- );
- this._copyLogButton.setAttribute("label", TorStrings.settings.copyLog);
- this._copyLogButton.addEventListener("command", () => {
- this.copyTorLog();
- const label = this._copyLogButton.querySelector("label");
- label.setAttribute("value", TorStrings.settings.copied);
- this._copyLogButton.classList.add("primary");
-
- const RESTORE_TIME = 1200;
- if (this._restoreButtonTimeout !== null) {
- clearTimeout(this._restoreButtonTimeout);
- }
- this._restoreButtonTimeout = setTimeout(() => {
- label.setAttribute("value", TorStrings.settings.copyLog);
- this._copyLogButton.classList.remove("primary");
- this._restoreButtonTimeout = null;
- }, RESTORE_TIME);
- });
-
- // A waiting state should not be needed at this point.
- // Also, we probably cannot even arrive here if the provider failed to
- // initialize, otherwise we could use a try/catch, and write the exception
- // text in the logs, instead.
- const provider = await TorProviderBuilder.build();
- this._logTextarea.value = provider.getLog();
- }
-
- init(window, aDialog) {
- this._populateXUL(aDialog);
- }
-
- copyTorLog() {
- // Copy tor log messages to the system clipboard.
- let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
- Ci.nsIClipboardHelper
- );
- clipboard.copyString(this._logTextarea.value);
- }
-
- openDialog(gSubDialog) {
- gSubDialog.open(
- "chrome://browser/content/torpreferences/torLogDialog.xhtml",
- { features: "resizable=yes" },
- this
- );
- }
-}
=====================================
browser/components/torpreferences/content/torLogDialog.xhtml
=====================================
@@ -9,21 +9,12 @@
xmlns:html="http://www.w3.org/1999/xhtml"
>
<dialog id="torPreferences-torLog-dialog" buttons="accept,extra1">
+ <script src="chrome://browser/content/torpreferences/torLogDialog.js" />
+
<html:textarea
id="torPreferences-torDialog-textarea"
multiline="true"
readonly="true"
/>
- <script type="application/javascript">
- <![CDATA[
- "use strict";
-
- let torLogDialog = window.arguments[0];
- document.addEventListener("DOMContentLoaded", () => {
- let dialog = document.getElementById("torPreferences-torLog-dialog");
- torLogDialog.init(window, dialog);
- });
- ]]>
- </script>
</dialog>
</window>
=====================================
browser/components/torpreferences/jar.mn
=====================================
@@ -2,19 +2,19 @@ browser.jar:
content/browser/torpreferences/bridge.svg (content/bridge.svg)
content/browser/torpreferences/bridge-qr.svg (content/bridge-qr.svg)
content/browser/torpreferences/bridgeQrDialog.xhtml (content/bridgeQrDialog.xhtml)
- content/browser/torpreferences/bridgeQrDialog.mjs (content/bridgeQrDialog.mjs)
+ content/browser/torpreferences/bridgeQrDialog.js (content/bridgeQrDialog.js)
content/browser/torpreferences/builtinBridgeDialog.xhtml (content/builtinBridgeDialog.xhtml)
- content/browser/torpreferences/builtinBridgeDialog.mjs (content/builtinBridgeDialog.mjs)
+ content/browser/torpreferences/builtinBridgeDialog.js (content/builtinBridgeDialog.js)
content/browser/torpreferences/connectionSettingsDialog.xhtml (content/connectionSettingsDialog.xhtml)
- content/browser/torpreferences/connectionSettingsDialog.mjs (content/connectionSettingsDialog.mjs)
+ content/browser/torpreferences/connectionSettingsDialog.js (content/connectionSettingsDialog.js)
content/browser/torpreferences/network.svg (content/network.svg)
content/browser/torpreferences/network-broken.svg (content/network-broken.svg)
content/browser/torpreferences/provideBridgeDialog.xhtml (content/provideBridgeDialog.xhtml)
- content/browser/torpreferences/provideBridgeDialog.mjs (content/provideBridgeDialog.mjs)
+ content/browser/torpreferences/provideBridgeDialog.js (content/provideBridgeDialog.js)
content/browser/torpreferences/requestBridgeDialog.xhtml (content/requestBridgeDialog.xhtml)
- content/browser/torpreferences/requestBridgeDialog.mjs (content/requestBridgeDialog.mjs)
+ content/browser/torpreferences/requestBridgeDialog.js (content/requestBridgeDialog.js)
content/browser/torpreferences/connectionCategory.inc.xhtml (content/connectionCategory.inc.xhtml)
- content/browser/torpreferences/torLogDialog.mjs (content/torLogDialog.mjs)
+ content/browser/torpreferences/torLogDialog.js (content/torLogDialog.js)
content/browser/torpreferences/torLogDialog.xhtml (content/torLogDialog.xhtml)
content/browser/torpreferences/connectionPane.js (content/connectionPane.js)
content/browser/torpreferences/connectionPane.xhtml (content/connectionPane.xhtml)
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/6211ed8…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/6211ed8…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-115.7.0esr-13.5-1] 5 commits: fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in...
by richard (@richard) 23 Jan '24
by richard (@richard) 23 Jan '24
23 Jan '24
richard pushed to branch tor-browser-115.7.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits:
39bde3a7 by Henry Wilkes at 2024-01-23T17:30:24+00:00
fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
Bug 42036: Remove old bridge cards.
- - - - -
8fee6e00 by Henry Wilkes at 2024-01-23T17:30:25+00:00
fixup! Add TorStrings module for localization
Bug 42036: Remove old bridge cards.
- - - - -
dc354cb3 by Henry Wilkes at 2024-01-23T17:30:25+00:00
fixup! Bug 40597: Implement TorSettings module
Bug 42036: Add syncSettings to TorSettings and improve error reporting.
- - - - -
0b412130 by Henry Wilkes at 2024-01-23T17:30:26+00:00
fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
Bug 42036: Implement new bridge settings UI, ready for Lox.
- - - - -
02cf2743 by Henry Wilkes at 2024-01-23T17:30:26+00:00
fixup! Tor Browser strings
Bug 42036: New bridge UI strings.
- - - - -
12 changed files:
- + browser/components/torpreferences/content/bridge-qr.svg
- + browser/components/torpreferences/content/bridge.svg
- browser/components/torpreferences/content/builtinBridgeDialog.mjs
- browser/components/torpreferences/content/builtinBridgeDialog.xhtml
- browser/components/torpreferences/content/connectionPane.js
- browser/components/torpreferences/content/connectionPane.xhtml
- browser/components/torpreferences/content/torPreferences.css
- browser/components/torpreferences/jar.mn
- browser/locales/en-US/browser/tor-browser.ftl
- toolkit/modules/TorSettings.sys.mjs
- toolkit/modules/TorStrings.sys.mjs
- toolkit/torbutton/chrome/locale/en-US/settings.properties
Changes:
=====================================
browser/components/torpreferences/content/bridge-qr.svg
=====================================
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="context-fill" xmlns="http://www.w3.org/2000/svg">
+ <path d="M5.5 3C4.83696 3 4.20107 3.26339 3.73223 3.73223C3.26339 4.20107 3 4.83696 3 5.5V9H4.75V5.5C4.75 5.30109 4.82902 5.11032 4.96967 4.96967C5.11032 4.82902 5.30109 4.75 5.5 4.75H9V3H5.5ZM11 10.25C11 10.4489 10.921 10.6397 10.7803 10.7803C10.6397 10.921 10.4489 11 10.25 11H7.75C7.55109 11 7.36032 10.921 7.21967 10.7803C7.07902 10.6397 7 10.4489 7 10.25V7.75C7 7.55109 7.07902 7.36032 7.21967 7.21967C7.36032 7.07902 7.55109 7 7.75 7H10.25C10.4489 7 10.6397 7.07902 10.7803 7.21967C10.921 7.36032 11 7.55109 11 7.75V10.25ZM17 15H15V13H17V11H15V9H17V7H15V9H13V11H15V13H13V15H11V13H9V15H7V17H9V15H11V17H13V15H15V17H17V15ZM3 18.5V15H4.75V18.5C4.75 18.914 5.086 19.25 5.5 19.25H9V21H5.5C4.83696 21 4.20107 20.7366 3.73223 20.2678C3.26339 19.7989 3 19.163 3 18.5ZM15 3H18.5C19.163 3 19.7989 3.26339 20.2678 3.73223C20.7366 4.20107 21 4.83696 21 5.5V9H19.25V5.5C19.25 5.30109 19.171 5.11032 19.0303 4.96967C18.8897 4.82902 18.6989 4.75 18.5 4.75H15V3ZM21 18.5V15H19.25V18.5C19.25 18.6989 19.171 18.8897 19.0303 19.0303C18.8897 19.171 18.6989 19.25 18.5 19.25H15V21H18.5C19.163 21 19.7989 20.7366 20.2678 20.2678C20.7366 19.7989 21 19.163 21 18.5Z"/>
+</svg>
=====================================
browser/components/torpreferences/content/bridge.svg
=====================================
@@ -0,0 +1,5 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="context-fill" xmlns="http://www.w3.org/2000/svg">
+ <path d="M15.5 11.5C15.5 7.35786 12.1421 4 8 4C3.85786 4 0.5 7.35786 0.5 11.5V12.7461H1.67188V11.5C1.67188 8.00507 4.50507 5.17188 8 5.17188C11.4949 5.17188 14.3281 8.00507 14.3281 11.5V12.7461H15.5V11.5Z"/>
+ <path d="M13.25 11.5C13.25 8.6005 10.8995 6.24999 7.99999 6.24999C5.1005 6.24999 2.74999 8.6005 2.74999 11.5L2.74989 12.7461H3.92177L3.92187 11.5C3.92187 9.24771 5.74771 7.42187 7.99999 7.42187C10.2523 7.42187 12.0781 9.24771 12.0781 11.5L12.0782 12.7461H13.2501L13.25 11.5Z"/>
+ <path d="M8 8.5C9.65686 8.5 11 9.84315 11 11.5L11.0002 12.7461H9.82836L9.82813 11.5C9.82813 10.4904 9.00965 9.67188 8 9.67188C6.99036 9.67188 6.17188 10.4904 6.17188 11.5L6.17164 12.7461H4.99977V11.5C4.99977 9.84315 6.34315 8.5 8 8.5Z"/>
+</svg>
=====================================
browser/components/torpreferences/content/builtinBridgeDialog.mjs
=====================================
@@ -69,10 +69,12 @@ export class BuiltinBridgeDialog {
optionEl.querySelector(
".torPreferences-current-bridge-label"
).textContent = TorStrings.settings.currentBridge;
- optionEl.classList.toggle(
- "current-builtin-bridge-type",
- type === currentBuiltinType
- );
+ optionEl
+ .querySelector(".bridge-status-badge")
+ .classList.toggle(
+ "bridge-status-current-built-in",
+ type === currentBuiltinType
+ );
}
if (currentBuiltinType) {
=====================================
browser/components/torpreferences/content/builtinBridgeDialog.xhtml
=====================================
@@ -20,8 +20,8 @@
aria-describedby="obfs-bridges-current obfs-bridges-description"
value="obfs4"
/>
- <html:span class="torPreferences-current-bridge-badge">
- <image class="torPreferences-current-bridge-icon" />
+ <html:span class="bridge-status-badge">
+ <html:div class="bridge-status-icon"></html:div>
<html:span
id="obfs-bridges-current"
class="torPreferences-current-bridge-label"
@@ -41,8 +41,8 @@
aria-describedby="snowflake-bridges-current snowflake-bridges-description"
value="snowflake"
/>
- <html:span class="torPreferences-current-bridge-badge">
- <image class="torPreferences-current-bridge-icon" />
+ <html:span class="bridge-status-badge">
+ <html:div class="bridge-status-icon"></html:div>
<html:span
id="snowflake-bridges-current"
class="torPreferences-current-bridge-label"
@@ -62,8 +62,8 @@
aria-describedby="meek-bridges-current meek-bridges-description"
value="meek-azure"
/>
- <html:span class="torPreferences-current-bridge-badge">
- <image class="torPreferences-current-bridge-icon" />
+ <html:span class="bridge-status-badge">
+ <html:div class="bridge-status-icon"></html:div>
<html:span
id="meek-bridges-current"
class="torPreferences-current-bridge-label"
=====================================
browser/components/torpreferences/content/connectionPane.js
=====================================
@@ -66,6 +66,1642 @@ const InternetStatus = Object.freeze({
Offline: -1,
});
+/**
+ * Make changes to TorSettings and save them.
+ *
+ * Bulk changes will be frozen together.
+ *
+ * @param {Function} changes - Method to apply changes to TorSettings.
+ */
+async function setTorSettings(changes) {
+ if (!TorSettings.initialized) {
+ console.warning("Ignoring changes to uninitialized TorSettings");
+ return;
+ }
+ TorSettings.freezeNotifications();
+ try {
+ changes();
+ // This will trigger TorSettings.#cleanupSettings()
+ TorSettings.saveToPrefs();
+ try {
+ // May throw.
+ await TorSettings.applySettings();
+ } catch (e) {
+ console.error("Failed to save Tor settings", e);
+ }
+ } finally {
+ TorSettings.thawNotifications();
+ }
+}
+
+/**
+ * Get the ID/fingerprint of the bridge used in the most recent Tor circuit.
+ *
+ * @returns {string?} - The bridge ID or null if a bridge with an id was not
+ * used in the last circuit.
+ */
+async function getConnectedBridgeId() {
+ // TODO: PieroV: We could make sure TorSettings is in sync by monitoring also
+ // changes of settings. At that point, we could query it, instead of doing a
+ // query over the control port.
+ let bridge = null;
+ try {
+ const provider = await TorProviderBuilder.build();
+ bridge = provider.currentBridge;
+ } catch (e) {
+ console.warn("Could not get current bridge", e);
+ }
+ return bridge?.fingerprint ?? null;
+}
+
+// TODO: Instead of aria-live in the DOM, use the proposed ariaNotify
+// API if it gets accepted into firefox and works with screen readers.
+// See https://github.com/WICG/proposals/issues/112
+/**
+ * Notification for screen reader users.
+ */
+const gBridgesNotification = {
+ /**
+ * The screen reader area that shows updates.
+ *
+ * @type {Element?}
+ */
+ _updateArea: null,
+ /**
+ * The text for the screen reader update.
+ *
+ * @type {Element?}
+ */
+ _textEl: null,
+ /**
+ * A timeout for hiding the update.
+ *
+ * @type {integer?}
+ */
+ _hideUpdateTimeout: null,
+
+ /**
+ * Initialize the area for notifications.
+ */
+ init() {
+ this._updateArea = document.getElementById("tor-bridges-update-area");
+ this._textEl = document.getElementById("tor-bridges-update-area-text");
+ },
+
+ /**
+ * Post a new notification, replacing any existing one.
+ *
+ * @param {string} type - The notification type.
+ */
+ post(type) {
+ this._updateArea.hidden = false;
+ // First we clear the update area to reset the text to be empty.
+ this._textEl.removeAttribute("data-l10n-id");
+ this._textEl.textContent = "";
+ if (this._hideUpdateTimeout !== null) {
+ clearTimeout(this._hideUpdateTimeout);
+ this._hideUpdateTimeout = null;
+ }
+
+ let updateId;
+ switch (type) {
+ case "removed-one":
+ updateId = "tor-bridges-update-removed-one-bridge";
+ break;
+ case "removed-all":
+ updateId = "tor-bridges-update-removed-all-bridges";
+ break;
+ case "changed":
+ default:
+ // Generic message for when bridges change.
+ updateId = "tor-bridges-update-changed-bridges";
+ break;
+ }
+
+ // Hide the area after 5 minutes, when the update is not "recent" any
+ // more.
+ this._hideUpdateTimeout = setTimeout(() => {
+ this._updateArea.hidden = true;
+ }, 300000);
+
+ // Wait a small amount of time to actually set the textContent. Otherwise
+ // the screen reader (tested with Orca) may not pick up on the change in
+ // text.
+ setTimeout(() => {
+ document.l10n.setAttributes(this._textEl, updateId);
+ }, 500);
+ },
+};
+
+/**
+ * Controls the bridge grid.
+ */
+const gBridgeGrid = {
+ /**
+ * The grid element.
+ *
+ * @type {Element?}
+ */
+ _grid: null,
+ /**
+ * The template for creating new rows.
+ *
+ * @type {HTMLTemplateElement?}
+ */
+ _rowTemplate: null,
+
+ /**
+ * @typedef {object} EmojiCell
+ *
+ * @property {Element} cell - The grid cell element.
+ * @property {Element} img - The grid cell icon.
+ * @property {Element} index - The emoji index.
+ */
+ /**
+ * @typedef {object} BridgeGridRow
+ *
+ * @property {Element} element - The row element.
+ * @property {Element} optionsButton - The options button.
+ * @property {EmojiCell[]} emojis - The emoji cells.
+ * @property {Element} menu - The options menupopup.
+ * @property {Element} statusEl - The bridge status element.
+ * @property {Element} statusText - The status text.
+ * @property {string} bridgeLine - The identifying bridge string for this row.
+ * @property {string?} bridgeId - The ID/fingerprint for the bridge, or null
+ * if it doesn't have one.
+ * @property {integer} index - The index of the row in the grid.
+ * @property {boolean} connected - Whether we are connected to the bridge
+ * (recently in use for a Tor circuit).
+ * @property {BridgeGridCell[]} cells - The cells that belong to the row,
+ * ordered by their column.
+ */
+ /**
+ * @typedef {object} BridgeGridCell
+ *
+ * @property {Element} element - The cell element.
+ * @property {Element} focusEl - The element belonging to the cell that should
+ * receive focus. Should be the cell element itself, or an interactive
+ * focusable child.
+ * @property {integer} columnIndex - The index of the column this cell belongs
+ * to.
+ * @property {BridgeGridRow} row - The row this cell belongs to.
+ */
+ /**
+ * The current rows in the grid.
+ *
+ * @type {BridgeGridRow[]}
+ */
+ _rows: [],
+ /**
+ * The cell that should be the focus target when the user moves focus into the
+ * grid, or null if the grid itself should be the target.
+ *
+ * @type {BridgeGridCell?}
+ */
+ _focusCell: null,
+
+ /**
+ * Initialize the bridge grid.
+ */
+ init() {
+ this._grid = document.getElementById("tor-bridges-grid-display");
+ // Initially, make only the grid itself part of the keyboard tab cycle.
+ // matches _focusCell = null.
+ this._grid.tabIndex = 0;
+
+ this._rowTemplate = document.getElementById(
+ "tor-bridges-grid-row-template"
+ );
+
+ this._grid.addEventListener("keydown", this);
+ this._grid.addEventListener("mousedown", this);
+ this._grid.addEventListener("focusin", this);
+
+ Services.obs.addObserver(this, TorSettingsTopics.SettingsChanged);
+
+ // NOTE: Before initializedPromise completes, this area is hidden.
+ TorSettings.initializedPromise.then(() => {
+ this._updateRows(true);
+ });
+ },
+
+ /**
+ * Uninitialize the bridge grid.
+ */
+ uninit() {
+ Services.obs.removeObserver(this, TorSettingsTopics.SettingsChanged);
+ this.deactivate();
+ },
+
+ /**
+ * Whether the grid is visible and responsive.
+ *
+ * @type {boolean}
+ */
+ _active: false,
+
+ /**
+ * Activate and show the bridge grid.
+ */
+ activate() {
+ if (this._active) {
+ return;
+ }
+
+ this._active = true;
+
+ Services.obs.addObserver(this, "intl:app-locales-changed");
+ Services.obs.addObserver(this, TorProviderTopics.BridgeChanged);
+
+ this._grid.classList.add("grid-active");
+
+ this._updateEmojiLangCode();
+ this._updateConnectedBridge();
+ },
+
+ /**
+ * Deactivate and hide the bridge grid.
+ */
+ deactivate() {
+ if (!this._active) {
+ return;
+ }
+
+ this._active = false;
+
+ this._forceCloseRowMenus();
+
+ this._grid.classList.remove("grid-active");
+
+ Services.obs.removeObserver(this, "intl:app-locales-changed");
+ Services.obs.removeObserver(this, TorProviderTopics.BridgeChanged);
+ },
+
+ observe(subject, topic, data) {
+ switch (topic) {
+ case TorSettingsTopics.SettingsChanged:
+ const { changes } = subject.wrappedJSObject;
+ if (
+ changes.includes("bridges.source") ||
+ changes.includes("bridges.bridge_strings")
+ ) {
+ this._updateRows();
+ }
+ break;
+ case "intl:app-locales-changed":
+ this._updateEmojiLangCode();
+ break;
+ case TorProviderTopics.BridgeChanged:
+ this._updateConnectedBridge();
+ break;
+ }
+ },
+
+ handleEvent(event) {
+ if (event.type === "keydown") {
+ if (event.altKey || event.shiftKey || event.metaKey || event.ctrlKey) {
+ // Don't interfere with these events.
+ return;
+ }
+
+ if (this._rows.some(row => row.menu.open)) {
+ // Have an open menu, let the menu handle the event instead.
+ return;
+ }
+
+ let numRows = this._rows.length;
+ if (!numRows) {
+ // Nowhere for focus to go.
+ return;
+ }
+
+ let moveRow = 0;
+ let moveColumn = 0;
+ const isLTR = this._grid.matches(":dir(ltr)");
+ switch (event.key) {
+ case "ArrowDown":
+ moveRow = 1;
+ break;
+ case "ArrowUp":
+ moveRow = -1;
+ break;
+ case "ArrowRight":
+ moveColumn = isLTR ? 1 : -1;
+ break;
+ case "ArrowLeft":
+ moveColumn = isLTR ? -1 : 1;
+ break;
+ default:
+ return;
+ }
+
+ // Prevent scrolling the nearest scroll container.
+ event.preventDefault();
+
+ const curCell = this._focusCell;
+ let row = curCell ? curCell.row.index + moveRow : 0;
+ let column = curCell ? curCell.columnIndex + moveColumn : 0;
+
+ // Clamp in bounds.
+ if (row < 0) {
+ row = 0;
+ } else if (row >= numRows) {
+ row = numRows - 1;
+ }
+
+ const numCells = this._rows[row].cells.length;
+ if (column < 0) {
+ column = 0;
+ } else if (column >= numCells) {
+ column = numCells - 1;
+ }
+
+ const newCell = this._rows[row].cells[column];
+
+ if (newCell !== curCell) {
+ this._setFocus(newCell);
+ }
+ } else if (event.type === "mousedown") {
+ if (event.button !== 0) {
+ return;
+ }
+ // Move focus index to the clicked target.
+ // NOTE: Since the cells and the grid have "tabindex=-1", they are still
+ // click-focusable. Therefore, the default mousedown handler will try to
+ // move focus to it.
+ // Rather than block this default handler, we instead re-direct the focus
+ // to the correct cell in the "focusin" listener.
+ const newCell = this._getCellFromTarget(event.target);
+ // NOTE: If newCell is null, then we do nothing here, but instead wait for
+ // the focusin handler to trigger.
+ if (newCell && newCell !== this._focusCell) {
+ this._setFocus(newCell);
+ }
+ } else if (event.type === "focusin") {
+ const focusCell = this._getCellFromTarget(event.target);
+ if (focusCell !== this._focusCell) {
+ // Focus is not where it is expected.
+ // E.g. the user has clicked the edge of the grid.
+ // Restore focus immediately back to the cell we expect.
+ this._setFocus(this._focusCell);
+ }
+ }
+ },
+
+ /**
+ * Return the cell that was the target of an event.
+ *
+ * @param {Element} element - The target of an event.
+ *
+ * @returns {BridgeGridCell?} - The cell that the element belongs to, or null
+ * if it doesn't belong to any cell.
+ */
+ _getCellFromTarget(element) {
+ for (const row of this._rows) {
+ for (const cell of row.cells) {
+ if (cell.element.contains(element)) {
+ return cell;
+ }
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Determine whether the document's active element (focus) is within the grid
+ * or not.
+ *
+ * @returns {boolean} - Whether focus is within this grid or not.
+ */
+ _focusWithin() {
+ return this._grid.contains(document.activeElement);
+ },
+
+ /**
+ * Set the cell that should be the focus target of the grid, possibly moving
+ * the document's focus as well.
+ *
+ * @param {BridgeGridCell?} cell - The cell to make the focus target, or null
+ * if the grid itself should be the target.
+ * @param {boolean} [focusWithin] - Whether focus should be moved within the
+ * grid. If undefined, this will move focus if the grid currently contains
+ * the document's focus.
+ */
+ _setFocus(cell, focusWithin) {
+ if (focusWithin === undefined) {
+ focusWithin = this._focusWithin();
+ }
+ const prevFocusElement = this._focusCell
+ ? this._focusCell.focusEl
+ : this._grid;
+ const newFocusElement = cell ? cell.focusEl : this._grid;
+
+ if (prevFocusElement !== newFocusElement) {
+ prevFocusElement.tabIndex = -1;
+ newFocusElement.tabIndex = 0;
+ }
+ // Set _focusCell now, before we potentially call "focus", which can trigger
+ // the "focusin" handler.
+ this._focusCell = cell;
+
+ if (focusWithin) {
+ // Focus was within the grid, so we need to actively move it to the new
+ // element.
+ newFocusElement.focus({ preventScroll: true });
+ // Scroll to the whole cell into view, rather than just the focus element.
+ (cell?.element ?? newFocusElement).scrollIntoView({
+ block: "nearest",
+ inline: "nearest",
+ });
+ }
+ },
+
+ /**
+ * Reset the grids focus to be the first row's first cell, if any.
+ *
+ * @param {boolean} [focusWithin] - Whether focus should be moved within the
+ * grid. If undefined, this will move focus if the grid currently contains
+ * the document's focus.
+ */
+ _resetFocus(focusWithin) {
+ this._setFocus(
+ this._rows.length ? this._rows[0].cells[0] : null,
+ focusWithin
+ );
+ },
+
+ /**
+ * The bridge ID/fingerprint of the most recently used bridge (appearing in
+ * the latest Tor circuit). Roughly corresponds to the bridge we are currently
+ * connected to.
+ *
+ * null if there are no such bridges.
+ *
+ * @type {string?}
+ */
+ _connectedBridgeId: null,
+ /**
+ * Update _connectedBridgeId.
+ */
+ async _updateConnectedBridge() {
+ const bridgeId = await getConnectedBridgeId();
+ if (bridgeId === this._connectedBridgeId) {
+ return;
+ }
+ this._connectedBridgeId = bridgeId;
+ for (const row of this._rows) {
+ this._updateRowStatus(row);
+ }
+ },
+
+ /**
+ * Update the status of a row.
+ *
+ * @param {BridgeGridRow} row - The row to update.
+ */
+ _updateRowStatus(row) {
+ const connected = row.bridgeId && this._connectedBridgeId === row.bridgeId;
+ // NOTE: row.connected is initially undefined, so won't match `connected`.
+ if (connected === row.connected) {
+ return;
+ }
+
+ row.connected = connected;
+
+ const noStatus = !connected;
+
+ row.element.classList.toggle("hide-status", noStatus);
+ row.statusEl.classList.toggle("bridge-status-none", noStatus);
+ row.statusEl.classList.toggle("bridge-status-connected", connected);
+
+ if (connected) {
+ document.l10n.setAttributes(
+ row.statusText,
+ "tor-bridges-status-connected"
+ );
+ } else {
+ document.l10n.setAttributes(row.statusText, "tor-bridges-status-none");
+ }
+ },
+
+ /**
+ * The language code for emoji annotations.
+ *
+ * null if unset.
+ *
+ * @type {string?}
+ */
+ _emojiLangCode: null,
+ /**
+ * A promise that resolves to two JSON structures for bridge-emojis.json and
+ * annotations.json, respectively.
+ *
+ * @type {Promise}
+ */
+ _emojiPromise: Promise.all([
+ fetch(
+ "chrome://browser/content/torpreferences/bridgemoji/bridge-emojis.json"
+ ).then(response => response.json()),
+ fetch(
+ "chrome://browser/content/torpreferences/bridgemoji/annotations.json"
+ ).then(response => response.json()),
+ ]),
+
+ /**
+ * Update _emojiLangCode.
+ */
+ async _updateEmojiLangCode() {
+ let langCode;
+ const emojiAnnotations = (await this._emojiPromise)[1];
+ // Find the first desired locale we have annotations for.
+ // Add "en" as a fallback.
+ for (const bcp47 of [...Services.locale.appLocalesAsBCP47, "en"]) {
+ langCode = bcp47;
+ if (langCode in emojiAnnotations) {
+ break;
+ }
+ // Remove everything after the dash, if there is one.
+ langCode = bcp47.replace(/-.*/, "");
+ if (langCode in emojiAnnotations) {
+ break;
+ }
+ }
+ if (langCode !== this._emojiLangCode) {
+ this._emojiLangCode = langCode;
+ for (const row of this._rows) {
+ this._updateRowEmojis(row);
+ }
+ }
+ },
+
+ /**
+ * Update the bridge emojis to show their corresponding emoji with an
+ * annotation that matches the current locale.
+ *
+ * @param {BridgeGridRow} row - The row to update the emojis of.
+ */
+ async _updateRowEmojis(row) {
+ if (!this._emojiLangCode) {
+ // No lang code yet, wait until it is updated.
+ return;
+ }
+
+ const [emojiList, emojiAnnotations] = await this._emojiPromise;
+ const unknownString = await document.l10n.formatValue(
+ "tor-bridges-emoji-unknown"
+ );
+
+ for (const { cell, img, index } of row.emojis) {
+ const emoji = emojiList[index];
+ let emojiName;
+ if (!emoji) {
+ // Unexpected.
+ img.removeAttribute("src");
+ } else {
+ const cp = emoji.codePointAt(0).toString(16);
+ img.setAttribute(
+ "src",
+ `chrome://browser/content/torpreferences/bridgemoji/svgs/${cp}.svg`
+ );
+ emojiName = emojiAnnotations[this._emojiLangCode][cp];
+ }
+ if (!emojiName) {
+ console.error(`No emoji for index ${index}`);
+ emojiName = unknownString;
+ }
+ document.l10n.setAttributes(cell, "tor-bridges-emoji-cell", {
+ emojiName,
+ });
+ }
+ },
+
+ /**
+ * Create a new row for the grid.
+ *
+ * @param {string} bridgeLine - The bridge line for this row, which also acts
+ * as its ID.
+ *
+ * @returns {BridgeGridRow} - A new row, with then "index" unset and the
+ * "element" without a parent.
+ */
+ _createRow(bridgeLine) {
+ let details;
+ try {
+ details = TorParsers.parseBridgeLine(bridgeLine);
+ } catch (e) {
+ console.error(`Detected invalid bridge line: ${bridgeLine}`, e);
+ }
+ const row = {
+ element: this._rowTemplate.content.children[0].cloneNode(true),
+ bridgeLine,
+ bridgeId: details?.id ?? null,
+ cells: [],
+ };
+
+ const emojiBlock = row.element.querySelector(".tor-bridges-emojis-block");
+ row.emojis = makeBridgeId(bridgeLine).map(index => {
+ const cell = document.createElement("span");
+ // Each emoji is its own cell, we rely on the fact that makeBridgeId
+ // always returns four indices.
+ cell.setAttribute("role", "gridcell");
+ cell.classList.add("tor-bridges-grid-cell", "tor-bridges-emoji-cell");
+
+ const img = document.createElement("img");
+ img.classList.add("tor-bridges-emoji-icon");
+ // Accessible name will be set on the cell itself.
+ img.setAttribute("alt", "");
+
+ cell.appendChild(img);
+ emojiBlock.appendChild(cell);
+ // Image and text is set in _updateRowEmojis.
+ return { cell, img, index };
+ });
+
+ for (const [columnIndex, element] of row.element
+ .querySelectorAll(".tor-bridges-grid-cell")
+ .entries()) {
+ const focusEl =
+ element.querySelector(".tor-bridges-grid-focus") ?? element;
+ // Set a negative tabIndex, this makes the element click-focusable but not
+ // part of the tab navigation sequence.
+ focusEl.tabIndex = -1;
+ row.cells.push({ element, focusEl, columnIndex, row });
+ }
+
+ // TODO: properly handle "vanilla" bridges?
+ document.l10n.setAttributes(
+ row.element.querySelector(".tor-bridges-type-cell"),
+ "tor-bridges-type-prefix",
+ { type: details?.transport ?? "vanilla" }
+ );
+
+ row.element.querySelector(".tor-bridges-address-cell").textContent =
+ bridgeLine;
+
+ row.statusEl = row.element.querySelector(
+ ".tor-bridges-status-cell .bridge-status-badge"
+ );
+ row.statusText = row.element.querySelector(".tor-bridges-status-cell-text");
+
+ this._initRowMenu(row);
+
+ this._updateRowStatus(row);
+ this._updateRowEmojis(row);
+ return row;
+ },
+
+ /**
+ * The row menu index used for generating new ids.
+ *
+ * @type {integer}
+ */
+ _rowMenuIndex: 0,
+ /**
+ * Generate a new id for the options menu.
+ *
+ * @returns {string} - The new id.
+ */
+ _generateRowMenuId() {
+ const id = `tor-bridges-individual-options-menu-${this._rowMenuIndex}`;
+ // Assume we won't run out of ids.
+ this._rowMenuIndex++;
+ return id;
+ },
+
+ /**
+ * Initialize the shared menu for a row.
+ *
+ * @param {BridgeGridRow} row - The row to initialize the menu of.
+ */
+ _initRowMenu(row) {
+ row.menu = row.element.querySelector(
+ ".tor-bridges-individual-options-menu"
+ );
+ row.optionsButton = row.element.querySelector(
+ ".tor-bridges-options-cell-button"
+ );
+
+ row.menu.id = this._generateRowMenuId();
+ row.optionsButton.setAttribute("aria-controls", row.menu.id);
+
+ row.optionsButton.addEventListener("click", event => {
+ row.menu.toggle(event);
+ });
+
+ row.menu.addEventListener("hidden", () => {
+ // Make sure the button receives focus again when the menu is hidden.
+ // Currently, panel-list.js only does this when the menu is opened with a
+ // keyboard, but this causes focus to be lost from the page if the user
+ // uses a mixture of keyboard and mouse.
+ row.optionsButton.focus();
+ });
+
+ row.menu
+ .querySelector(".tor-bridges-options-qr-one-menu-item")
+ .addEventListener("click", () => {
+ const bridgeLine = row.bridgeLine;
+ if (!bridgeLine) {
+ return;
+ }
+ const dialog = new BridgeQrDialog();
+ dialog.openDialog(gSubDialog, bridgeLine);
+ });
+ row.menu
+ .querySelector(".tor-bridges-options-copy-one-menu-item")
+ .addEventListener("click", () => {
+ const clipboard = Cc[
+ "@mozilla.org/widget/clipboardhelper;1"
+ ].getService(Ci.nsIClipboardHelper);
+ clipboard.copyString(row.bridgeLine);
+ });
+ row.menu
+ .querySelector(".tor-bridges-options-remove-one-menu-item")
+ .addEventListener("click", () => {
+ const bridgeLine = row.bridgeLine;
+ const strings = TorSettings.bridges.bridge_strings;
+ const index = strings.indexOf(bridgeLine);
+ if (index === -1) {
+ return;
+ }
+ strings.splice(index, 1);
+
+ setTorSettings(() => {
+ TorSettings.bridges.bridge_strings = strings;
+ });
+ });
+ },
+
+ /**
+ * Force the row menu to close.
+ */
+ _forceCloseRowMenus() {
+ for (const row of this._rows) {
+ row.menu.hide(null, { force: true });
+ }
+ },
+
+ /**
+ * The known bridge source.
+ *
+ * Initially null to indicate that it is unset.
+ *
+ * @type {integer?}
+ */
+ _bridgeSource: null,
+ /**
+ * The bridge sources this is shown for.
+ *
+ * @type {string[]}
+ */
+ _supportedSources: [TorBridgeSource.BridgeDB, TorBridgeSource.UserProvided],
+
+ /**
+ * Update the grid to show the latest bridge strings.
+ *
+ * @param {boolean} [initializing=false] - Whether this is being called as
+ * part of initialization.
+ */
+ _updateRows(initializing = false) {
+ // Store whether we have focus within the grid, before removing or hiding
+ // DOM elements.
+ const focusWithin = this._focusWithin();
+
+ let lostAllBridges = false;
+ let newSource = false;
+ const bridgeSource = TorSettings.bridges.source;
+ if (bridgeSource !== this._bridgeSource) {
+ newSource = true;
+
+ this._bridgeSource = bridgeSource;
+
+ if (this._supportedSources.includes(bridgeSource)) {
+ this.activate();
+ } else {
+ if (this._active && bridgeSource === TorBridgeSource.Invalid) {
+ lostAllBridges = true;
+ }
+ this.deactivate();
+ }
+ }
+
+ const ordered = this._active
+ ? TorSettings.bridges.bridge_strings.map(bridgeLine => {
+ const row = this._rows.find(r => r.bridgeLine === bridgeLine);
+ if (row) {
+ return row;
+ }
+ return this._createRow(bridgeLine);
+ })
+ : [];
+
+ // Whether we should reset the grid's focus.
+ // We always reset when we have a new bridge source.
+ // We reset the focus if no current Cell has focus. I.e. when adding a row
+ // to an empty grid, we want the focus to move to the first item.
+ // We also reset the focus if the current Cell is in a row that will be
+ // removed (including if all rows are removed).
+ // NOTE: In principle, if a row is removed, we could move the focus to the
+ // next or previous row (in the same cell column). However, most likely if
+ // the grid has the user focus, they are removing a single row using its
+ // options button. In this case, returning the user to some other row's
+ // options button might be more disorienting since it would not be simple
+ // for them to know *which* bridge they have landed on.
+ // NOTE: We do not reset the focus in other cases because we do not want the
+ // user to loose their place in the grid unnecessarily.
+ let resetFocus =
+ newSource || !this._focusCell || !ordered.includes(this._focusCell.row);
+
+ // Remove rows no longer needed from the DOM.
+ let numRowsRemoved = 0;
+ let rowAddedOrMoved = false;
+
+ for (const row of this._rows) {
+ if (!ordered.includes(row)) {
+ numRowsRemoved++;
+ // If the row menu was open, it will also be deleted.
+ // NOTE: Since the row menu is part of the row, focusWithin will be true
+ // if the menu had focus, so focus should be re-assigned.
+ row.element.remove();
+ }
+ }
+
+ // Go through all the rows to set their ".index" property and to ensure they
+ // are in the correct position in the DOM.
+ // NOTE: We could use replaceChildren to get the correct DOM structure, but
+ // we want to avoid rebuilding the entire tree when a single row is added or
+ // removed.
+ for (const [index, row] of ordered.entries()) {
+ row.index = index;
+ const element = row.element;
+ // Get the expected previous element, that should already be in the DOM
+ // from the previous loop.
+ const prevEl = index ? ordered[index - 1].element : null;
+
+ if (
+ element.parentElement === this._grid &&
+ prevEl === element.previousElementSibling
+ ) {
+ // Already in the correct position in the DOM.
+ continue;
+ }
+
+ rowAddedOrMoved = true;
+ // NOTE: Any elements already in the DOM, but not in the correct position
+ // will be removed and re-added by the below command.
+ // NOTE: if the row has document focus, then it should remain there.
+ if (prevEl) {
+ prevEl.after(element);
+ } else {
+ this._grid.prepend(element);
+ }
+ }
+ this._rows = ordered;
+
+ // Restore any lost focus.
+ if (resetFocus) {
+ // If we are not active (and therefore hidden), we will not try and move
+ // focus (activeElement), but may still change the *focusable* element for
+ // when we are shown again.
+ this._resetFocus(this._active && focusWithin);
+ }
+ if (!this._active && focusWithin) {
+ // Move focus out of this element, which has been hidden.
+ gBridgeSettings.takeFocus();
+ }
+
+ // Notify the user if there was some change to the DOM.
+ // If we are initializing, we generate no notification since there has been
+ // no change in the setting.
+ if (!initializing) {
+ let notificationType;
+ if (lostAllBridges) {
+ // Just lost all bridges, and became de-active.
+ notificationType = "removed-all";
+ } else if (this._rows.length) {
+ // Otherwise, only generate a notification if we are still active, with
+ // at least one bridge.
+ // I.e. do not generate a message if the new source is "builtin".
+ if (newSource) {
+ // A change in source.
+ notificationType = "changed";
+ } else if (numRowsRemoved === 1 && !rowAddedOrMoved) {
+ // Only one bridge was removed. This is most likely in response to them
+ // manually removing a single bridge or using the bridge row's options
+ // menu.
+ notificationType = "removed-one";
+ } else if (numRowsRemoved || rowAddedOrMoved) {
+ // Some other change. This is most likely in response to a manual edit
+ // of the existing bridges.
+ notificationType = "changed";
+ }
+ // Else, there was no change.
+ }
+
+ if (notificationType) {
+ gBridgesNotification.post(notificationType);
+ }
+ }
+ },
+};
+
+/**
+ * Controls the built-in bridges area.
+ */
+const gBuiltinBridgesArea = {
+ /**
+ * The display area.
+ *
+ * @type {Element?}
+ */
+ _area: null,
+ /**
+ * The type name element.
+ *
+ * @type {Element?}
+ */
+ _nameEl: null,
+ /**
+ * The bridge type description element.
+ *
+ * @type {Element?}
+ */
+ _descriptionEl: null,
+ /**
+ * The connection status.
+ *
+ * @type {Element?}
+ */
+ _connectionStatusEl: null,
+
+ /**
+ * Initialize the built-in bridges area.
+ */
+ init() {
+ this._area = document.getElementById("tor-bridges-built-in-display");
+ this._nameEl = document.getElementById("tor-bridges-built-in-type-name");
+ this._descriptionEl = document.getElementById(
+ "tor-bridges-built-in-description"
+ );
+ this._connectionStatusEl = document.getElementById(
+ "tor-bridges-built-in-connected"
+ );
+
+ Services.obs.addObserver(this, TorSettingsTopics.SettingsChanged);
+
+ // NOTE: Before initializedPromise completes, this area is hidden.
+ TorSettings.initializedPromise.then(() => {
+ this._updateBridgeType(true);
+ });
+ },
+
+ /**
+ * Uninitialize the built-in bridges area.
+ */
+ uninit() {
+ Services.obs.removeObserver(this, TorSettingsTopics.SettingsChanged);
+ this.deactivate();
+ },
+
+ /**
+ * Whether the built-in area is visible and responsive.
+ *
+ * @type {boolean}
+ */
+ _active: false,
+
+ /**
+ * Activate and show the built-in bridge area.
+ */
+ activate() {
+ if (this._active) {
+ return;
+ }
+ this._active = true;
+
+ Services.obs.addObserver(this, TorProviderTopics.BridgeChanged);
+
+ this._area.classList.add("built-in-active");
+
+ this._updateBridgeIds();
+ this._updateConnectedBridge();
+ },
+
+ /**
+ * Deactivate and hide built-in bridge area.
+ */
+ deactivate() {
+ if (!this._active) {
+ return;
+ }
+ this._active = false;
+
+ this._area.classList.remove("built-in-active");
+
+ Services.obs.removeObserver(this, TorProviderTopics.BridgeChanged);
+ },
+
+ observe(subject, topic, data) {
+ switch (topic) {
+ case TorSettingsTopics.SettingsChanged:
+ const { changes } = subject.wrappedJSObject;
+ if (
+ changes.includes("bridges.source") ||
+ changes.includes("bridges.builtin_type")
+ ) {
+ this._updateBridgeType();
+ }
+ if (changes.includes("bridges.bridge_strings")) {
+ this._updateBridgeIds();
+ }
+ break;
+ case TorProviderTopics.BridgeChanged:
+ this._updateConnectedBridge();
+ break;
+ }
+ },
+
+ /**
+ * Updates the shown connected state.
+ */
+ _updateConnectedState() {
+ this._connectionStatusEl.classList.toggle(
+ "bridge-status-connected",
+ this._bridgeType &&
+ this._connectedBridgeId &&
+ this._bridgeIds.includes(this._connectedBridgeId)
+ );
+ },
+
+ /**
+ * The currently shown bridge type. Empty if deactivated, and null if
+ * uninitialized.
+ *
+ * @type {string?}
+ */
+ _bridgeType: null,
+ /**
+ * The strings for each known bridge type.
+ *
+ * @type {Object<string,object>}
+ */
+ _bridgeTypeStrings: {
+ // TODO: Change to Fluent ids.
+ obfs4: {
+ name: TorStrings.settings.builtinBridgeObfs4Title,
+ description: TorStrings.settings.builtinBridgeObfs4Description2,
+ },
+ snowflake: {
+ name: TorStrings.settings.builtinBridgeSnowflake,
+ description: TorStrings.settings.builtinBridgeSnowflakeDescription2,
+ },
+ "meek-azure": {
+ name: TorStrings.settings.builtinBridgeMeekAzure,
+ description: TorStrings.settings.builtinBridgeMeekAzureDescription2,
+ },
+ },
+
+ /**
+ * The known bridge source.
+ *
+ * Initially null to indicate that it is unset.
+ *
+ * @type {integer?}
+ */
+ _bridgeSource: null,
+
+ /**
+ * Update the shown bridge type.
+ *
+ * @param {boolean} [initializing=false] - Whether this is being called as
+ * part of initialization.
+ */
+ async _updateBridgeType(initializing = false) {
+ let lostAllBridges = false;
+ let newSource = false;
+ const bridgeSource = TorSettings.bridges.source;
+ if (bridgeSource !== this._bridgeSource) {
+ newSource = true;
+
+ this._bridgeSource = bridgeSource;
+
+ if (bridgeSource === TorBridgeSource.BuiltIn) {
+ this.activate();
+ } else {
+ if (this._active && bridgeSource === TorBridgeSource.Invalid) {
+ lostAllBridges = true;
+ }
+ const hadFocus = this._area.contains(document.activeElement);
+ this.deactivate();
+ if (hadFocus) {
+ gBridgeSettings.takeFocus();
+ }
+ }
+ }
+
+ const bridgeType = this._active ? TorSettings.bridges.builtin_type : "";
+
+ let newType = false;
+ if (bridgeType !== this._bridgeType) {
+ newType = true;
+
+ this._bridgeType = bridgeType;
+
+ const bridgeStrings = this._bridgeTypeStrings[bridgeType];
+ if (bridgeStrings) {
+ /*
+ document.l10n.setAttributes(this._nameEl, bridgeStrings.name);
+ document.l10n.setAttributes(
+ this._descriptionEl,
+ bridgeStrings.description
+ );
+ */
+ this._nameEl.textContent = bridgeStrings.name;
+ this._descriptionEl.textContent = bridgeStrings.description;
+ } else {
+ // Unknown type, or no type.
+ this._nameEl.removeAttribute("data-l10n-id");
+ this._nameEl.textContent = bridgeType;
+ this._descriptionEl.removeAttribute("data-l10n-id");
+ this._descriptionEl.textContent = "";
+ }
+
+ this._updateConnectedState();
+ }
+
+ // Notify the user if there was some change to the type.
+ // If we are initializing, we generate no notification since there has been
+ // no change in the setting.
+ if (!initializing) {
+ let notificationType;
+ if (lostAllBridges) {
+ // Just lost all bridges, and became de-active.
+ notificationType = "removed-all";
+ } else if (this._active && (newSource || newType)) {
+ // Otherwise, only generate a notification if we are still active, with
+ // a bridge type.
+ // I.e. do not generate a message if the new source is not "builtin".
+ notificationType = "changed";
+ }
+
+ if (notificationType) {
+ gBridgesNotification.post(notificationType);
+ }
+ }
+ },
+
+ /**
+ * The bridge IDs/fingerprints for the built-in bridges.
+ *
+ * @type {Array<string>}
+ */
+ _bridgeIds: [],
+ /**
+ * Update _bridgeIds
+ */
+ _updateBridgeIds() {
+ this._bridgeIds = [];
+ for (const bridgeLine of TorSettings.bridges.bridge_strings) {
+ try {
+ this._bridgeIds.push(TorParsers.parseBridgeLine(bridgeLine).id);
+ } catch (e) {
+ console.error(`Detected invalid bridge line: ${bridgeLine}`, e);
+ }
+ }
+
+ this._updateConnectedState();
+ },
+
+ /**
+ * The bridge ID/fingerprint of the most recently used bridge (appearing in
+ * the latest Tor circuit). Roughly corresponds to the bridge we are currently
+ * connected to.
+ *
+ * @type {string?}
+ */
+ _connectedBridgeId: null,
+ /**
+ * Update _connectedBridgeId.
+ */
+ async _updateConnectedBridge() {
+ this._connectedBridgeId = await getConnectedBridgeId();
+ this._updateConnectedState();
+ },
+};
+
+/**
+ * Controls the bridge settings.
+ */
+const gBridgeSettings = {
+ /**
+ * The preferences <groupbox> for bridges
+ *
+ * @type {Element?}
+ */
+ _groupEl: null,
+ /**
+ * The button for controlling whether bridges are enabled.
+ *
+ * @type {Element?}
+ */
+ _toggleButton: null,
+ /**
+ * The area for showing current bridges.
+ *
+ * @type {Element?}
+ */
+ _bridgesEl: null,
+ /**
+ * The heading for the bridge settings.
+ *
+ * @type {Element?}
+ */
+ _bridgesSettingsHeading: null,
+ /**
+ * The current bridges heading, at the start of the area.
+ *
+ * @type {Element?}
+ */
+ _currentBridgesHeading: null,
+ /**
+ * The area for showing no bridges.
+ *
+ * @type {Element?}
+ */
+ _noBridgesEl: null,
+
+ /**
+ * Initialize the bridge settings.
+ */
+ init() {
+ gBridgesNotification.init();
+
+ this._bridgesSettingsHeading = document.getElementById(
+ "torPreferences-bridges-header"
+ );
+ this._currentBridgesHeading = document.getElementById(
+ "tor-bridges-current-heading"
+ );
+ this._bridgesEl = document.getElementById("tor-bridges-current");
+ this._noBridgesEl = document.getElementById("tor-bridges-none");
+ this._groupEl = document.getElementById("torPreferences-bridges-group");
+ this._toggleButton = document.getElementById("tor-bridges-enabled-toggle");
+ // Initially disabled whilst TorSettings may not be initialized.
+ this._toggleButton.disabled = true;
+
+ this._toggleButton.addEventListener("toggle", () => {
+ if (!this._haveBridges) {
+ return;
+ }
+ setTorSettings(() => {
+ TorSettings.bridges.enabled = this._toggleButton.pressed;
+ });
+ });
+
+ Services.obs.addObserver(this, TorSettingsTopics.SettingsChanged);
+
+ gBridgeGrid.init();
+ gBuiltinBridgesArea.init();
+
+ this._initBridgesMenu();
+ this._initShareArea();
+
+ // NOTE: Before initializedPromise completes, the current bridges sections
+ // should be hidden.
+ // And gBridgeGrid and gBuiltinBridgesArea are not active.
+ TorSettings.initializedPromise.then(() => {
+ this._updateEnabled();
+ this._updateBridgeStrings();
+ this._updateSource();
+ });
+ },
+
+ /**
+ * Un-initialize the bridge settings.
+ */
+ uninit() {
+ gBridgeGrid.uninit();
+ gBuiltinBridgesArea.uninit();
+
+ Services.obs.removeObserver(this, TorSettingsTopics.SettingsChanged);
+ },
+
+ observe(subject, topic, data) {
+ switch (topic) {
+ case TorSettingsTopics.SettingsChanged:
+ const { changes } = subject.wrappedJSObject;
+ if (changes.includes("bridges.enabled")) {
+ this._updateEnabled();
+ }
+ if (changes.includes("bridges.source")) {
+ this._updateSource();
+ }
+ if (changes.includes("bridges.bridge_strings")) {
+ this._updateBridgeStrings();
+ }
+ break;
+ }
+ },
+
+ /**
+ * Update whether the bridges should be shown as enabled.
+ */
+ _updateEnabled() {
+ // Changing the pressed property on moz-toggle should not trigger its
+ // "toggle" event.
+ this._toggleButton.pressed = TorSettings.bridges.enabled;
+ },
+
+ /**
+ * The shown bridge source.
+ *
+ * Initially null to indicate that it is unset for the first call to
+ * _updateSource.
+ *
+ * @type {integer?}
+ */
+ _bridgeSource: null,
+
+ /**
+ * Update _bridgeSource.
+ */
+ _updateSource() {
+ // NOTE: This should only ever be called after TorSettings is already
+ // initialized.
+ const bridgeSource = TorSettings.bridges.source;
+ if (bridgeSource === this._bridgeSource) {
+ // Avoid re-activating an area if the source has not changed.
+ return;
+ }
+
+ this._bridgeSource = bridgeSource;
+
+ // Before hiding elements, we determine whether our region contained the
+ // user focus.
+ const hadFocus =
+ this._bridgesEl.contains(document.activeElement) ||
+ this._noBridgesEl.contains(document.activeElement);
+
+ this._bridgesEl.classList.toggle(
+ "source-built-in",
+ bridgeSource === TorBridgeSource.BuiltIn
+ );
+ this._bridgesEl.classList.toggle(
+ "source-user",
+ bridgeSource === TorBridgeSource.UserProvided
+ );
+ this._bridgesEl.classList.toggle(
+ "source-requested",
+ bridgeSource === TorBridgeSource.BridgeDB
+ );
+
+ // Force the menu to close whenever the source changes.
+ // NOTE: If the menu had focus then hadFocus will be true, and focus will be
+ // re-assigned.
+ this._forceCloseBridgesMenu();
+
+ // Update whether we have bridges.
+ this._updateHaveBridges();
+
+ if (hadFocus) {
+ // Always reset the focus to the start of the area whenever the source
+ // changes.
+ // NOTE: gBuiltinBridges._updateBridgeType and gBridgeGrid._updateRows
+ // may have already called takeFocus in response to them being
+ // de-activated. The re-call should be safe.
+ this.takeFocus();
+ }
+ },
+
+ /**
+ * Whether we have bridges or not, or null if it is unknown.
+ *
+ * @type {boolean?}
+ */
+ _haveBridges: null,
+
+ /**
+ * Update the _haveBridges value.
+ */
+ _updateHaveBridges() {
+ // NOTE: We use the TorSettings.bridges.source value, rather than
+ // this._bridgeSource because _updateHaveBridges can be called just before
+ // _updateSource (via takeFocus).
+ const haveBridges = TorSettings.bridges.source !== TorBridgeSource.Invalid;
+
+ if (haveBridges === this._haveBridges) {
+ return;
+ }
+
+ this._haveBridges = haveBridges;
+
+ this._toggleButton.disabled = !haveBridges;
+ // Add classes to show or hide the "no bridges" and "Your bridges" sections.
+ // NOTE: Before haveBridges is set, neither class is added, so both sections
+ // and hidden.
+ this._groupEl.classList.toggle("no-bridges", !haveBridges);
+ this._groupEl.classList.toggle("have-bridges", haveBridges);
+ },
+
+ /**
+ * Force the focus to move to the bridge area.
+ */
+ takeFocus() {
+ if (this._haveBridges === null) {
+ // The bridges area has not been initialized yet, which means that
+ // TorSettings may not be initialized.
+ // Unexpected to receive a call before then, so just return early.
+ return;
+ }
+
+ // Make sure we have the latest value for _haveBridges.
+ // We also ensure that the _currentBridgesHeading element is visible before
+ // we focus it.
+ this._updateHaveBridges();
+ if (this._haveBridges) {
+ // Move focus to the start of the area, which is the heading.
+ // It has tabindex="-1" so should be focusable, even though it is not part
+ // of the usual tab navigation.
+ this._currentBridgesHeading.focus();
+ } else {
+ // Move focus to the top of the bridge settings.
+ this._bridgesSettingsHeading.focus();
+ }
+ },
+
+ /**
+ * The bridge strings in a copy-able form.
+ *
+ * @type {string}
+ */
+ _bridgeStrings: "",
+ /**
+ * Whether the bridge strings should be shown as a QR code.
+ *
+ * @type {boolean}
+ */
+ _canQRBridges: false,
+
+ /**
+ * Update the stored bridge strings.
+ */
+ _updateBridgeStrings() {
+ const bridges = TorSettings.bridges.bridge_strings;
+
+ this._bridgeStrings = bridges.join("\n");
+ // TODO: Determine what logic we want.
+ this._canQRBridges = bridges.length <= 3;
+
+ this._qrButton.disabled = !this._canQRBridges;
+ },
+
+ /**
+ * Copy all the bridge addresses to the clipboard.
+ */
+ _copyBridges() {
+ const clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
+ Ci.nsIClipboardHelper
+ );
+ clipboard.copyString(this._bridgeStrings);
+ },
+
+ /**
+ * Open the QR code dialog encoding all the bridge addresses.
+ */
+ _openQR() {
+ if (!this._canQRBridges) {
+ return;
+ }
+ const dialog = new BridgeQrDialog();
+ dialog.openDialog(gSubDialog, this._bridgeStrings);
+ },
+
+ /**
+ * The QR button for copying all QR codes.
+ *
+ * @type {Element?}
+ */
+ _qrButton: null,
+
+ _initShareArea() {
+ document
+ .getElementById("tor-bridges-copy-addresses-button")
+ .addEventListener("click", () => {
+ this._copyBridges();
+ });
+
+ this._qrButton = document.getElementById("tor-bridges-qr-addresses-button");
+ this._qrButton.addEventListener("click", () => {
+ this._openQR();
+ });
+ },
+
+ /**
+ * The menu for all bridges.
+ *
+ * @type {Element?}
+ */
+ _bridgesMenu: null,
+
+ /**
+ * Initialize the menu for all bridges.
+ */
+ _initBridgesMenu() {
+ this._bridgesMenu = document.getElementById("tor-bridges-all-options-menu");
+
+ // NOTE: We generally assume that once the bridge menu is opened the
+ // this._bridgeStrings value will not change.
+ const qrItem = document.getElementById(
+ "tor-bridges-options-qr-all-menu-item"
+ );
+ qrItem.addEventListener("click", () => {
+ this._openQR();
+ });
+
+ const copyItem = document.getElementById(
+ "tor-bridges-options-copy-all-menu-item"
+ );
+ copyItem.addEventListener("click", () => {
+ this._copyBridges();
+ });
+
+ const editItem = document.getElementById(
+ "tor-bridges-options-edit-all-menu-item"
+ );
+ editItem.addEventListener("click", () => {
+ // TODO: move to gBridgeSettings.
+ // TODO: Change dialog title. Do not allow Lox invite.
+ gConnectionPane.onAddBridgeManually();
+ });
+
+ // TODO: Do we want a different item for built-in bridges, rather than
+ // "Remove all bridges"?
+ document
+ .getElementById("tor-bridges-options-remove-all-menu-item")
+ .addEventListener("click", () => {
+ // TODO: Should we only have a warning when not built-in?
+ const parentWindow =
+ Services.wm.getMostRecentWindow("navigator:browser");
+ const flags =
+ Services.prompt.BUTTON_POS_0 *
+ Services.prompt.BUTTON_TITLE_IS_STRING +
+ Services.prompt.BUTTON_POS_0_DEFAULT +
+ Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL;
+
+ // TODO: Update the text, and remove old strings.
+ const buttonIndex = Services.prompt.confirmEx(
+ parentWindow,
+ TorStrings.settings.bridgeRemoveAllDialogTitle,
+ TorStrings.settings.bridgeRemoveAllDialogDescription,
+ flags,
+ TorStrings.settings.remove,
+ null,
+ null,
+ null,
+ {}
+ );
+
+ if (buttonIndex !== 0) {
+ return;
+ }
+
+ setTorSettings(() => {
+ // This should always have the side effect of disabling bridges as
+ // well.
+ TorSettings.bridges.bridge_strings = [];
+ });
+ });
+
+ this._bridgesMenu.addEventListener("showing", () => {
+ const canCopy = this._bridgeSource !== TorBridgeSource.BuiltIn;
+ qrItem.hidden = !this._canQRBridges || !canCopy;
+ copyItem.hidden = !canCopy;
+ editItem.hidden = this._bridgeSource !== TorBridgeSource.UserProvided;
+ });
+
+ const bridgesMenuButton = document.getElementById(
+ "tor-bridges-all-options-button"
+ );
+ bridgesMenuButton.addEventListener("click", event => {
+ this._bridgesMenu.toggle(event, bridgesMenuButton);
+ });
+
+ this._bridgesMenu.addEventListener("hidden", () => {
+ // Make sure the button receives focus again when the menu is hidden.
+ // Currently, panel-list.js only does this when the menu is opened with a
+ // keyboard, but this causes focus to be lost from the page if the user
+ // uses a mixture of keyboard and mouse.
+ bridgesMenuButton.focus();
+ });
+ },
+
+ /**
+ * Force the bridges menu to close.
+ */
+ _forceCloseBridgesMenu() {
+ this._bridgesMenu.hide(null, { force: true });
+ },
+};
+
/*
Connection Pane
@@ -96,29 +1732,6 @@ const gConnectionPane = (function () {
location: "#torPreferences-bridges-location",
locationEntries: "#torPreferences-bridges-locationEntries",
chooseForMe: "#torPreferences-bridges-buttonChooseBridgeForMe",
- currentHeader: "#torPreferences-currentBridges-header",
- currentDescription: "#torPreferences-currentBridges-description",
- currentDescriptionText: "#torPreferences-currentBridges-descriptionText",
- controls: "#torPreferences-currentBridges-controls",
- switch: "#torPreferences-currentBridges-switch",
- cards: "#torPreferences-currentBridges-cards",
- cardTemplate: "#torPreferences-bridgeCard-template",
- card: ".torPreferences-bridgeCard",
- cardId: ".torPreferences-bridgeCard-id",
- cardHeadingManualLink: ".torPreferences-bridgeCard-manualLink",
- cardHeadingAddr: ".torPreferences-bridgeCard-headingAddr",
- cardConnectedLabel: ".torPreferences-current-bridge-label",
- cardOptions: ".torPreferences-bridgeCard-options",
- cardMenu: "#torPreferences-bridgeCard-menu",
- cardQrGrid: ".torPreferences-bridgeCard-grid",
- cardQrContainer: ".torPreferences-bridgeCard-qr",
- cardQr: ".torPreferences-bridgeCard-qrCode",
- cardShare: ".torPreferences-bridgeCard-share",
- cardAddr: ".torPreferences-bridgeCard-addr",
- cardLearnMore: ".torPreferences-bridgeCard-learnMore",
- cardCopy: ".torPreferences-bridgeCard-copyButton",
- showAll: "#torPreferences-currentBridges-showAll",
- removeAll: "#torPreferences-currentBridges-removeAll",
addHeader: "#torPreferences-addBridge-header",
addBuiltinLabel: "#torPreferences-addBridge-labelBuiltinBridge",
addBuiltinButton: "#torPreferences-addBridge-buttonBuiltinBridge",
@@ -142,8 +1755,6 @@ const gConnectionPane = (function () {
_internetStatus: InternetStatus.Unknown,
- _currentBridgeId: null,
-
// populate xul with strings and cache the relevant elements
_populateXUL() {
// saves tor settings to disk when navigate away from about:preferences
@@ -387,390 +1998,6 @@ const gConnectionPane = (function () {
this._showAutoconfiguration();
}
- // Bridge cards
- const bridgeHeader = prefpane.querySelector(
- selectors.bridges.currentHeader
- );
- bridgeHeader.textContent = TorStrings.settings.bridgeCurrent;
- const bridgeControls = prefpane.querySelector(selectors.bridges.controls);
- const bridgeSwitch = prefpane.querySelector(selectors.bridges.switch);
- bridgeSwitch.setAttribute("label", TorStrings.settings.allBridgesEnabled);
- bridgeSwitch.addEventListener("toggle", () => {
- TorSettings.bridges.enabled = bridgeSwitch.pressed;
- TorSettings.saveToPrefs();
- TorSettings.applySettings().finally(() => {
- this._populateBridgeCards();
- });
- });
- const bridgeDescription = prefpane.querySelector(
- selectors.bridges.currentDescription
- );
- bridgeDescription.querySelector(
- selectors.bridges.currentDescriptionText
- ).textContent = TorStrings.settings.bridgeCurrentDescription;
- const bridgeTemplate = prefpane.querySelector(
- selectors.bridges.cardTemplate
- );
- {
- const learnMore = bridgeTemplate.querySelector(
- selectors.bridges.cardLearnMore
- );
- learnMore.setAttribute("value", TorStrings.settings.learnMore);
- learnMore.setAttribute(
- "href",
- TorStrings.settings.learnMoreBridgesCardURL
- );
- if (TorStrings.settings.learnMoreBridgesCardURL.startsWith("about:")) {
- learnMore.setAttribute("useoriginprincipal", "true");
- }
- }
- {
- const manualLink = bridgeTemplate.querySelector(
- selectors.bridges.cardHeadingManualLink
- );
- manualLink.setAttribute("value", TorStrings.settings.whatAreThese);
- manualLink.setAttribute(
- "href",
- TorStrings.settings.learnMoreBridgesCardURL
- );
- if (TorStrings.settings.learnMoreBridgesCardURL.startsWith("about:")) {
- manualLink.setAttribute("useoriginprincipal", "true");
- }
- }
- bridgeTemplate.querySelector(
- selectors.bridges.cardConnectedLabel
- ).textContent = TorStrings.settings.connectedBridge;
- bridgeTemplate
- .querySelector(selectors.bridges.cardCopy)
- .setAttribute("label", TorStrings.settings.bridgeCopy);
- bridgeTemplate.querySelector(selectors.bridges.cardShare).textContent =
- TorStrings.settings.bridgeShare;
- const bridgeCards = prefpane.querySelector(selectors.bridges.cards);
- const bridgeMenu = prefpane.querySelector(selectors.bridges.cardMenu);
-
- this._addBridgeCard = bridgeString => {
- const card = bridgeTemplate.cloneNode(true);
- card.removeAttribute("id");
- const grid = card.querySelector(selectors.bridges.cardQrGrid);
- card.addEventListener("click", e => {
- if (
- card.classList.contains("currently-connected") ||
- bridgeCards.classList.contains("single-card")
- ) {
- return;
- }
- let target = e.target;
- let apply = true;
- while (target !== null && target !== card && apply) {
- // Deal with mixture of "command" and "click" events
- apply = !target.classList?.contains("stop-click");
- target = target.parentElement;
- }
- if (apply) {
- if (card.classList.toggle("expanded")) {
- grid.classList.add("to-animate");
- grid.style.height = `${grid.scrollHeight}px`;
- } else {
- // Be sure we still have the to-animate class
- grid.classList.add("to-animate");
- grid.style.height = "";
- }
- }
- });
- const emojis = makeBridgeId(bridgeString).map(emojiIndex => {
- const img = document.createElement("img");
- img.classList.add("emoji");
- // Image is set in _updateBridgeEmojis.
- img.dataset.emojiIndex = emojiIndex;
- return img;
- });
- const idString = TorStrings.settings.bridgeId;
- const id = card.querySelector(selectors.bridges.cardId);
- let details;
- try {
- details = TorParsers.parseBridgeLine(bridgeString);
- } catch (e) {
- console.error(`Detected invalid bridge line: ${bridgeString}`, e);
- }
- if (details && details.id !== undefined) {
- card.setAttribute("data-bridge-id", details.id);
- }
- // TODO: properly handle "vanilla" bridges?
- const type =
- details && details.transport !== undefined
- ? details.transport
- : "vanilla";
- for (const piece of idString.split(/(%[12]\$S)/)) {
- if (piece == "%1$S") {
- id.append(type);
- } else if (piece == "%2$S") {
- id.append(...emojis);
- } else {
- id.append(piece);
- }
- }
- card.querySelector(selectors.bridges.cardHeadingAddr).textContent =
- bridgeString;
- const optionsButton = card.querySelector(selectors.bridges.cardOptions);
- if (TorSettings.bridges.source === TorBridgeSource.BuiltIn) {
- optionsButton.setAttribute("hidden", "true");
- } else {
- // Cloning the menupopup element does not work as expected.
- // Therefore, we use only one, and just before opening it, we remove
- // its previous items, and add the ones relative to the bridge whose
- // button has been pressed.
- optionsButton.addEventListener("click", () => {
- const menuItem = document.createXULElement("menuitem");
- menuItem.setAttribute("label", TorStrings.settings.remove);
- menuItem.classList.add("menuitem-iconic");
- menuItem.image = "chrome://global/skin/icons/delete.svg";
- menuItem.addEventListener("command", e => {
- const strings = TorSettings.bridges.bridge_strings;
- const index = strings.indexOf(bridgeString);
- if (index !== -1) {
- strings.splice(index, 1);
- }
- TorSettings.bridges.enabled =
- bridgeSwitch.pressed && !!strings.length;
- TorSettings.bridges.bridge_strings = strings.join("\n");
- TorSettings.saveToPrefs();
- TorSettings.applySettings().finally(() => {
- this._populateBridgeCards();
- });
- });
- if (bridgeMenu.firstChild) {
- bridgeMenu.firstChild.remove();
- }
- bridgeMenu.append(menuItem);
- bridgeMenu.openPopup(optionsButton, {
- position: "bottomleft topleft",
- });
- });
- }
- const bridgeAddr = card.querySelector(selectors.bridges.cardAddr);
- bridgeAddr.setAttribute("value", bridgeString);
- const bridgeCopy = card.querySelector(selectors.bridges.cardCopy);
- let restoreTimeout = null;
- bridgeCopy.addEventListener("command", e => {
- this.onCopyBridgeAddress(bridgeAddr);
- const label = bridgeCopy.querySelector("label");
- label.setAttribute("value", TorStrings.settings.copied);
- bridgeCopy.classList.add("primary");
-
- const RESTORE_TIME = 1200;
- if (restoreTimeout !== null) {
- clearTimeout(restoreTimeout);
- }
- restoreTimeout = setTimeout(() => {
- label.setAttribute("value", TorStrings.settings.bridgeCopy);
- bridgeCopy.classList.remove("primary");
- restoreTimeout = null;
- }, RESTORE_TIME);
- });
- if (details?.id && details.id === this._currentBridgeId) {
- card.classList.add("currently-connected");
- bridgeCards.prepend(card);
- } else {
- bridgeCards.append(card);
- }
- // Add the QR only after appending the card, to have the computed style
- try {
- const container = card.querySelector(selectors.bridges.cardQr);
- const style = getComputedStyle(container);
- const width = style.width.substring(0, style.width.length - 2);
- const height = style.height.substring(0, style.height.length - 2);
- new QRCode(container, {
- text: bridgeString,
- width,
- height,
- colorDark: style.color,
- colorLight: style.backgroundColor,
- document,
- });
- container.parentElement.addEventListener("click", () => {
- this.onShowQr(bridgeString);
- });
- } catch (err) {
- // TODO: Add a generic image in case of errors such as code overflow.
- // It should never happen with correct codes, but after all this
- // content can be generated by users...
- console.error("Could not generate the QR code for the bridge:", err);
- }
- };
- this._checkBridgeCardsHeight = () => {
- for (const card of bridgeCards.children) {
- // Expanded cards have the height set manually to their details for
- // the CSS animation. However, when resizing the window, we may need
- // to adjust their height.
- if (
- card.classList.contains("expanded") ||
- card.classList.contains("currently-connected")
- ) {
- const grid = card.querySelector(selectors.bridges.cardQrGrid);
- // Reset it first, to avoid having a height that is higher than
- // strictly needed. Also, remove the to-animate class, because the
- // animation interferes with this process!
- grid.classList.remove("to-animate");
- grid.style.height = "";
- grid.style.height = `${grid.scrollHeight}px`;
- }
- }
- };
- this._currentBridgesExpanded = false;
- const showAll = prefpane.querySelector(selectors.bridges.showAll);
- showAll.setAttribute("label", TorStrings.settings.bridgeShowAll);
- showAll.addEventListener("command", () => {
- this._currentBridgesExpanded = !this._currentBridgesExpanded;
- this._populateBridgeCards();
- if (!this._currentBridgesExpanded) {
- bridgeSwitch.scrollIntoView({ behavior: "smooth" });
- }
- });
- const removeAll = prefpane.querySelector(selectors.bridges.removeAll);
- removeAll.setAttribute("label", TorStrings.settings.bridgeRemoveAll);
- removeAll.addEventListener("command", () => {
- this._confirmBridgeRemoval();
- });
- this._populateBridgeCards = () => {
- const collapseThreshold = 4;
-
- const newStrings = new Set(TorSettings.bridges.bridge_strings);
- const numBridges = newStrings.size;
- const noBridges = !numBridges;
- bridgeHeader.hidden = noBridges;
- bridgeDescription.hidden = noBridges;
- bridgeControls.hidden = noBridges;
- bridgeCards.hidden = noBridges;
- if (noBridges) {
- showAll.hidden = true;
- removeAll.hidden = true;
- bridgeCards.textContent = "";
- return;
- }
- // Changing the pressed property on moz-toggle should not trigger its
- // "toggle" event.
- bridgeSwitch.pressed = TorSettings.bridges.enabled;
- bridgeCards.classList.toggle("disabled", !TorSettings.bridges.enabled);
- bridgeCards.classList.toggle("single-card", numBridges === 1);
-
- let shownCards = 0;
- const toShow = this._currentBridgesExpanded
- ? numBridges
- : collapseThreshold;
-
- // Do not remove all the old cards, because it makes scrollbar "jump"
- const currentCards = bridgeCards.querySelectorAll(
- selectors.bridges.card
- );
- for (const card of currentCards) {
- const string = card.querySelector(selectors.bridges.cardAddr).value;
- const hadString = newStrings.delete(string);
- if (!hadString || shownCards == toShow) {
- card.remove();
- } else {
- shownCards++;
- }
- }
-
- // Add only the new strings that remained in the set
- for (const bridge of newStrings) {
- if (shownCards >= toShow) {
- if (!this._currentBridgeId) {
- break;
- } else if (!bridge.includes(this._currentBridgeId)) {
- continue;
- }
- }
- this._addBridgeCard(bridge);
- shownCards++;
- }
-
- // If we know the connected bridge, we may have added more than the ones
- // we should actually show (but the connected ones have been prepended,
- // if needed). So, remove any exceeding ones.
- while (shownCards > toShow) {
- bridgeCards.lastElementChild.remove();
- shownCards--;
- }
-
- // Newly added emojis.
- this._updateBridgeEmojis();
-
- // And finally update the buttons
- removeAll.hidden = false;
- showAll.classList.toggle("primary", TorSettings.bridges.enabled);
- if (numBridges > collapseThreshold) {
- showAll.hidden = false;
- showAll.setAttribute(
- "aria-expanded",
- // Boolean value gets converted to string "true" or "false".
- this._currentBridgesExpanded
- );
- showAll.setAttribute(
- "label",
- this._currentBridgesExpanded
- ? TorStrings.settings.bridgeShowFewer
- : TorStrings.settings.bridgeShowAll
- );
- // We do not want both collapsed and disabled at the same time,
- // because we use collapsed only to display a gradient on the list.
- bridgeCards.classList.toggle(
- "list-collapsed",
- !this._currentBridgesExpanded && TorSettings.bridges.enabled
- );
- } else {
- // NOTE: We do not expect the showAll button to have focus when we
- // hide it since we do not expect `numBridges` to decrease whilst
- // this button is focused.
- showAll.hidden = true;
- bridgeCards.classList.remove("list-collapsed");
- }
- };
- this._populateBridgeCards();
- this._updateConnectedBridges = () => {
- for (const card of bridgeCards.querySelectorAll(
- ".currently-connected"
- )) {
- card.classList.remove("currently-connected");
- card.querySelector(selectors.bridges.cardQrGrid).style.height = "";
- }
- if (!this._currentBridgeId) {
- return;
- }
- // Make sure we have the connected bridge in the list
- this._populateBridgeCards();
- // At the moment, IDs do not have to be unique (and it is a concrete
- // case also with built-in bridges!). E.g., one line for the IPv4
- // address and one for the IPv6 address, so use querySelectorAll
- const cards = bridgeCards.querySelectorAll(
- `[data-bridge-id="${this._currentBridgeId}"]`
- );
- for (const card of cards) {
- card.classList.add("currently-connected");
- }
- const placeholder = document.createElement("span");
- bridgeCards.prepend(placeholder);
- placeholder.replaceWith(...cards);
- this._checkBridgeCardsHeight();
- };
- this._checkConnectedBridge = async () => {
- // TODO: We could make sure TorSettings is in sync by monitoring also
- // changes of settings. At that point, we could query it, instead of
- // doing a query over the control port.
- let bridge = null;
- try {
- const provider = await TorProviderBuilder.build();
- bridge = provider.currentBridge;
- } catch (e) {
- console.warn("Could not get current bridge", e);
- }
- if (bridge?.fingerprint !== this._currentBridgeId) {
- this._currentBridgeId = bridge?.fingerprint ?? null;
- this._updateConnectedBridges();
- }
- };
- this._checkConnectedBridge();
-
// Add a new bridge
prefpane.querySelector(selectors.bridges.addHeader).textContent =
TorStrings.settings.bridgeAdd;
@@ -804,34 +2031,6 @@ const gConnectionPane = (function () {
});
}
- this._confirmBridgeRemoval = () => {
- const aParentWindow =
- Services.wm.getMostRecentWindow("navigator:browser");
-
- const ps = Services.prompt;
- const btnFlags =
- ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING +
- ps.BUTTON_POS_0_DEFAULT +
- ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL;
-
- const notUsed = { value: false };
- const btnIndex = ps.confirmEx(
- aParentWindow,
- TorStrings.settings.bridgeRemoveAllDialogTitle,
- TorStrings.settings.bridgeRemoveAllDialogDescription,
- btnFlags,
- TorStrings.settings.remove,
- null,
- null,
- null,
- notUsed
- );
-
- if (btnIndex === 0) {
- this.onRemoveAllBridges();
- }
- };
-
// Advanced setup
prefpane.querySelector(selectors.advanced.header).innerText =
TorStrings.settings.advancedHeading;
@@ -862,11 +2061,11 @@ const gConnectionPane = (function () {
});
Services.obs.addObserver(this, TorConnectTopics.StateChange);
- Services.obs.addObserver(this, TorProviderTopics.BridgeChanged);
- Services.obs.addObserver(this, "intl:app-locales-changed");
},
init() {
+ gBridgeSettings.init();
+
TorSettings.initializedPromise.then(() => this._populateXUL());
const onUnload = () => {
@@ -874,21 +2073,14 @@ const gConnectionPane = (function () {
gConnectionPane.uninit();
};
window.addEventListener("unload", onUnload);
-
- window.addEventListener("resize", () => {
- this._checkBridgeCardsHeight();
- });
- window.addEventListener("hashchange", () => {
- this._checkBridgeCardsHeight();
- });
},
uninit() {
+ gBridgeSettings.uninit();
+
// unregister our observer topics
Services.obs.removeObserver(this, TorSettingsTopics.SettingsChanged);
Services.obs.removeObserver(this, TorConnectTopics.StateChange);
- Services.obs.removeObserver(this, TorProviderTopics.BridgeChanged);
- Services.obs.removeObserver(this, "intl:app-locales-changed");
},
// whether the page should be present in about:preferences
@@ -916,64 +2108,6 @@ const gConnectionPane = (function () {
this.onStateChange();
break;
}
- case TorProviderTopics.BridgeChanged: {
- this._checkConnectedBridge();
- break;
- }
- case "intl:app-locales-changed": {
- this._updateBridgeEmojis();
- break;
- }
- }
- },
-
- /**
- * Update the bridge emojis to show their corresponding emoji with an
- * annotation that matches the current locale.
- */
- async _updateBridgeEmojis() {
- if (!this._emojiPromise) {
- this._emojiPromise = Promise.all([
- fetch(
- "chrome://browser/content/torpreferences/bridgemoji/bridge-emojis.json"
- ).then(response => response.json()),
- fetch(
- "chrome://browser/content/torpreferences/bridgemoji/annotations.json"
- ).then(response => response.json()),
- ]);
- }
- const [emojiList, emojiAnnotations] = await this._emojiPromise;
- let langCode;
- // Find the first desired locale we have annotations for.
- // Add "en" as a fallback.
- for (const bcp47 of [...Services.locale.appLocalesAsBCP47, "en"]) {
- langCode = bcp47;
- if (langCode in emojiAnnotations) {
- break;
- }
- // Remove everything after the dash, if there is one.
- langCode = bcp47.replace(/-.*/, "");
- if (langCode in emojiAnnotations) {
- break;
- }
- }
- for (const img of document.querySelectorAll(".emoji[data-emoji-index]")) {
- const emoji = emojiList[img.dataset.emojiIndex];
- if (!emoji) {
- // Unexpected.
- console.error(`No emoji for index ${img.dataset.emojiIndex}`);
- img.removeAttribute("src");
- img.removeAttribute("alt");
- img.removeAttribute("title");
- continue;
- }
- const cp = emoji.codePointAt(0).toString(16);
- img.setAttribute(
- "src",
- `chrome://browser/content/torpreferences/bridgemoji/svgs/${cp}.svg`
- );
- img.setAttribute("alt", emoji);
- img.setAttribute("title", emojiAnnotations[langCode][cp]);
}
},
@@ -999,51 +2133,21 @@ const gConnectionPane = (function () {
onStateChange() {
this._populateStatus();
this._showAutoconfiguration();
- this._populateBridgeCards();
- },
-
- onShowQr(bridgeString) {
- const dialog = new BridgeQrDialog();
- dialog.openDialog(gSubDialog, bridgeString);
- },
-
- onCopyBridgeAddress(addressElem) {
- const clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
- Ci.nsIClipboardHelper
- );
- clipboard.copyString(addressElem.value);
- },
-
- onRemoveAllBridges() {
- TorSettings.bridges.enabled = false;
- TorSettings.bridges.bridge_strings = "";
- if (TorSettings.bridges.source === TorBridgeSource.BuiltIn) {
- TorSettings.bridges.builtin_type = "";
- }
- TorSettings.saveToPrefs();
- TorSettings.applySettings().finally(() => {
- this._populateBridgeCards();
- });
},
/**
* Save and apply settings, then optionally open about:torconnect and start
* bootstrapping.
*
+ * @param {fucntion} changes - The changes to make.
* @param {boolean} connect - Whether to open about:torconnect and start
* bootstrapping if possible.
*/
- async saveBridgeSettings(connect) {
- TorSettings.saveToPrefs();
- // FIXME: This can throw if the user adds a bridge manually with invalid
- // content. Should be addressed by tor-browser#41913.
- try {
- await TorSettings.applySettings();
- } catch (e) {
- console.error("Applying settings failed", e);
- }
-
- this._populateBridgeCards();
+ async saveBridgeSettings(changes, connect) {
+ // TODO: Move focus into the bridge area.
+ // dialog.ownerGlobal.addEventListener("unload", () => gCurrentBridgesArea.takeFocus(), { once: true });
+ // or use closedCallback in gSubDialog.open()
+ setTorSettings(changes);
if (!connect) {
return;
@@ -1071,11 +2175,11 @@ const gConnectionPane = (function () {
onAddBuiltinBridge() {
const builtinBridgeDialog = new BuiltinBridgeDialog(
(bridgeType, connect) => {
- TorSettings.bridges.enabled = true;
- TorSettings.bridges.source = TorBridgeSource.BuiltIn;
- TorSettings.bridges.builtin_type = bridgeType;
-
- this.saveBridgeSettings(connect);
+ this.saveBridgeSettings(() => {
+ TorSettings.bridges.enabled = true;
+ TorSettings.bridges.source = TorBridgeSource.BuiltIn;
+ TorSettings.bridges.builtin_type = bridgeType;
+ }, connect);
}
);
builtinBridgeDialog.openDialog(gSubDialog);
@@ -1088,12 +2192,14 @@ const gConnectionPane = (function () {
if (!aBridges.length) {
return;
}
+
const bridgeStrings = aBridges.join("\n");
- TorSettings.bridges.enabled = true;
- TorSettings.bridges.source = TorBridgeSource.BridgeDB;
- TorSettings.bridges.bridge_strings = bridgeStrings;
- this.saveBridgeSettings(connect);
+ this.saveBridgeSettings(() => {
+ TorSettings.bridges.enabled = true;
+ TorSettings.bridges.source = TorBridgeSource.BridgeDB;
+ TorSettings.bridges.bridge_strings = bridgeStrings;
+ }, connect);
}
);
requestBridgeDialog.openDialog(gSubDialog);
@@ -1102,11 +2208,11 @@ const gConnectionPane = (function () {
onAddBridgeManually() {
const provideBridgeDialog = new ProvideBridgeDialog(
(aBridgeString, connect) => {
- TorSettings.bridges.enabled = true;
- TorSettings.bridges.source = TorBridgeSource.UserProvided;
- TorSettings.bridges.bridge_strings = aBridgeString;
-
- this.saveBridgeSettings(connect);
+ this.saveBridgeSettings(() => {
+ TorSettings.bridges.enabled = true;
+ TorSettings.bridges.source = TorBridgeSource.UserProvided;
+ TorSettings.bridges.bridge_strings = aBridgeString;
+ }, connect);
}
);
provideBridgeDialog.openDialog(gSubDialog);
=====================================
browser/components/torpreferences/content/connectionPane.xhtml
=====================================
@@ -67,7 +67,7 @@
<!-- Bridges -->
<hbox class="subcategory" data-category="paneConnection" hidden="true">
- <html:h1 id="torPreferences-bridges-header" />
+ <html:h1 id="torPreferences-bridges-header" tabindex="-1" />
</hbox>
<groupbox
id="torPreferences-bridges-group"
@@ -103,73 +103,196 @@
class="primary"
/>
</hbox>
- <html:h2 id="torPreferences-currentBridges-header"> </html:h2>
- <description flex="1" id="torPreferences-currentBridges-description">
- <html:span id="torPreferences-currentBridges-descriptionText" />
- </description>
- <hbox align="center" id="torPreferences-currentBridges-controls">
- <html:moz-toggle
- id="torPreferences-currentBridges-switch"
- label-align-after=""
- />
- <spacer flex="1" />
- <button id="torPreferences-currentBridges-removeAll" />
- </hbox>
- <menupopup id="torPreferences-bridgeCard-menu" />
- <vbox
- id="torPreferences-bridgeCard-template"
- class="torPreferences-bridgeCard"
- >
- <hbox class="torPreferences-bridgeCard-heading">
- <html:div class="torPreferences-bridgeCard-id" />
- <label
- class="torPreferences-bridgeCard-manualLink learnMore text-link stop-click"
- is="text-link"
- />
- <html:div class="torPreferences-bridgeCard-headingAddr" />
- <html:div class="torPreferences-bridgeCard-buttons">
- <html:span class="torPreferences-current-bridge-badge">
- <image class="torPreferences-current-bridge-icon" />
- <html:span class="torPreferences-current-bridge-label"></html:span>
+ <html:moz-toggle
+ id="tor-bridges-enabled-toggle"
+ label-align-after=""
+ data-l10n-id="tor-bridges-use-bridges"
+ data-l10n-attrs="label"
+ />
+ <!-- Add an aria-live area where we can post notifications to screen
+ - reader users about changes to their list of bridges. This is to give
+ - these users some feedback for when the remove a bridge or change
+ - their bridges in other ways. I.e. whenever tor-bridges-grid-display
+ - changes its rows.
+ -
+ - If we change the text in #tor-bridges-update-area-text, a screen
+ - reader should speak out the text to the user, even when this area
+ - does not have focus.
+ -
+ - In fact, we don't really want the user to navigate to this element
+ - directly. But currently using an aria-live region in the DOM is the
+ - only way to effectively pass a notification to a screen reader user.
+ - Since it must be somewhere in the DOM, we logically place it just
+ - before the grid, where it is hopefully least confusing to stumble
+ - across.
+ -
+ - TODO: Instead of aria-live in the DOM, use the proposed ariaNotify
+ - API if it gets accepted into firefox and works with screen readers.
+ - See https://github.com/WICG/proposals/issues/112
+ -->
+ <!-- NOTE: This area is hidden by default, and is only shown temporarily
+ - when a notification is added. -->
+ <html:div id="tor-bridges-update-area" hidden="hidden">
+ <!-- NOTE: This first span's text content will *not* be read out as part
+ - of the notification because it does not have an aria-live
+ - attribute. Instead it is just here to give context to the following
+ - text in #tor-bridges-update-area-text if the user navigates to
+ - #tor-bridges-update-area manually whilst it is not hidden.
+ - I.e. it is just here to make it less confusing if a screen reader
+ - user stumbles across this.
+ -->
+ <html:span data-l10n-id="tor-bridges-update-area-intro"></html:span>
+ <!-- Whitespace between spans. -->
+ <!-- This second span is the area to place notification text in. -->
+ <html:span
+ id="tor-bridges-update-area-text"
+ aria-live="polite"
+ ></html:span>
+ </html:div>
+ <html:div id="tor-bridges-none">
+ <html:img id="tor-bridges-none-icon" alt="" />
+ <html:div data-l10n-id="tor-bridges-none-added"></html:div>
+ </html:div>
+ <html:div id="tor-bridges-current">
+ <html:div id="tor-bridges-current-header-bar">
+ <html:h2
+ id="tor-bridges-current-heading"
+ tabindex="-1"
+ data-l10n-id="tor-bridges-your-bridges"
+ ></html:h2>
+ <html:span
+ id="tor-bridges-user-label"
+ data-l10n-id="tor-bridges-source-user"
+ ></html:span>
+ <html:span
+ id="tor-bridges-built-in-label"
+ data-l10n-id="tor-bridges-source-built-in"
+ ></html:span>
+ <html:span
+ id="tor-bridges-requested-label"
+ data-l10n-id="tor-bridges-source-requested"
+ ></html:span>
+ <html:button
+ id="tor-bridges-all-options-button"
+ class="tor-bridges-options-button"
+ aria-haspopup="menu"
+ aria-expanded="false"
+ aria-controls="tor-bridges-all-options-menu"
+ data-l10n-id="tor-bridges-options-button"
+ ></html:button>
+ <html:panel-list id="tor-bridges-all-options-menu">
+ <html:panel-item
+ id="tor-bridges-options-qr-all-menu-item"
+ data-l10n-attrs="accesskey"
+ data-l10n-id="tor-bridges-menu-item-qr-all-bridge-addresses"
+ ></html:panel-item>
+ <html:panel-item
+ id="tor-bridges-options-copy-all-menu-item"
+ data-l10n-attrs="accesskey"
+ data-l10n-id="tor-bridges-menu-item-copy-all-bridge-addresses"
+ ></html:panel-item>
+ <html:panel-item
+ id="tor-bridges-options-edit-all-menu-item"
+ data-l10n-attrs="accesskey"
+ data-l10n-id="tor-bridges-menu-item-edit-all-bridges"
+ ></html:panel-item>
+ <html:panel-item
+ id="tor-bridges-options-remove-all-menu-item"
+ data-l10n-attrs="accesskey"
+ data-l10n-id="tor-bridges-menu-item-remove-all-bridges"
+ ></html:panel-item>
+ </html:panel-list>
+ </html:div>
+ <html:div id="tor-bridges-built-in-display">
+ <html:div id="tor-bridges-built-in-type-name"></html:div>
+ <html:div
+ id="tor-bridges-built-in-connected"
+ class="bridge-status-badge"
+ >
+ <html:div class="bridge-status-icon"></html:div>
+ <html:span
+ data-l10n-id="tor-bridges-built-in-status-connected"
+ ></html:span>
+ </html:div>
+ <html:div id="tor-bridges-built-in-description"></html:div>
+ </html:div>
+ <html:div
+ id="tor-bridges-grid-display"
+ role="grid"
+ aria-labelledby="tor-bridges-current-heading"
+ ></html:div>
+ <html:template id="tor-bridges-grid-row-template">
+ <html:div class="tor-bridges-grid-row" role="row">
+ <!-- TODO: lox status cell for new bridges? -->
+ <html:span
+ class="tor-bridges-type-cell tor-bridges-grid-cell"
+ role="gridcell"
+ ></html:span>
+ <html:span class="tor-bridges-emojis-block" role="none"></html:span>
+ <html:span class="tor-bridges-grid-end-block" role="none">
+ <html:span
+ class="tor-bridges-address-cell tor-bridges-grid-cell"
+ role="gridcell"
+ ></html:span>
+ <html:span
+ class="tor-bridges-status-cell tor-bridges-grid-cell"
+ role="gridcell"
+ >
+ <html:div class="bridge-status-badge">
+ <html:div class="bridge-status-icon"></html:div>
+ <html:span class="tor-bridges-status-cell-text"></html:span>
+ </html:div>
+ </html:span>
+ <html:span
+ class="tor-bridges-options-cell tor-bridges-grid-cell"
+ role="gridcell"
+ >
+ <html:button
+ class="tor-bridges-options-cell-button tor-bridges-options-button tor-bridges-grid-focus"
+ aria-haspopup="menu"
+ aria-expanded="false"
+ data-l10n-id="tor-bridges-individual-bridge-options-button"
+ ></html:button>
+ <html:panel-list class="tor-bridges-individual-options-menu">
+ <html:panel-item
+ class="tor-bridges-options-qr-one-menu-item"
+ data-l10n-attrs="accesskey"
+ data-l10n-id="tor-bridges-menu-item-qr-address"
+ ></html:panel-item>
+ <html:panel-item
+ class="tor-bridges-options-copy-one-menu-item"
+ data-l10n-attrs="accesskey"
+ data-l10n-id="tor-bridges-menu-item-copy-address"
+ ></html:panel-item>
+ <html:panel-item
+ class="tor-bridges-options-remove-one-menu-item"
+ data-l10n-attrs="accesskey"
+ data-l10n-id="tor-bridges-menu-item-remove-bridge"
+ ></html:panel-item>
+ </html:panel-list>
+ </html:span>
</html:span>
- <html:button class="torPreferences-bridgeCard-options stop-click" />
</html:div>
- </hbox>
- <box class="torPreferences-bridgeCard-grid">
- <box class="torPreferences-bridgeCard-qrWrapper">
- <html:div class="torPreferences-bridgeCard-qr stop-click">
- <html:div class="torPreferences-bridgeCard-qrCode" />
- <html:div class="torPreferences-bridgeCard-qrOnionBox" />
- <html:div class="torPreferences-bridgeCard-qrOnion" />
- </html:div>
- </box>
- <description class="torPreferences-bridgeCard-share"></description>
- <hbox class="torPreferences-bridgeCard-addrBox">
- <html:input
- class="torPreferences-bridgeCard-addr stop-click"
- type="text"
- readonly="readonly"
- />
- </hbox>
- <!-- tor-browser#41977 disable this learn more link until we have an alternate manual entry -->
- <hbox class="torPreferences-bridgeCard-learnMoreBox" align="center" hidden="true">
- <label
- class="torPreferences-bridgeCard-learnMore learnMore text-link stop-click"
- is="text-link"
- />
- </hbox>
- <hbox class="torPreferences-bridgeCard-copy" align="center">
- <button class="torPreferences-bridgeCard-copyButton stop-click" />
- </hbox>
- </box>
- </vbox>
- <vbox id="torPreferences-currentBridges-cards"></vbox>
- <vbox align="center">
- <button
- id="torPreferences-currentBridges-showAll"
- aria-controls="torPreferences-currentBridges-cards"
- />
- </vbox>
+ </html:template>
+ <html:div id="tor-bridges-share">
+ <html:h3
+ id="tor-bridges-share-heading"
+ data-l10n-id="tor-bridges-share-heading"
+ ></html:h3>
+ <html:span
+ id="tor-bridges-share-description"
+ data-l10n-id="tor-bridges-share-description"
+ ></html:span>
+ <html:button
+ id="tor-bridges-copy-addresses-button"
+ data-l10n-id="tor-bridges-copy-addresses-button"
+ ></html:button>
+ <html:button
+ id="tor-bridges-qr-addresses-button"
+ data-l10n-id="tor-bridges-qr-addresses-button"
+ ></html:button>
+ </html:div>
+ </html:div>
<html:h2 id="torPreferences-addBridge-header"></html:h2>
<hbox align="center">
<label id="torPreferences-addBridge-labelBuiltinBridge" flex="1" />
=====================================
browser/components/torpreferences/content/torPreferences.css
=====================================
@@ -69,282 +69,391 @@
}
/* Bridge settings */
-#torPreferences-bridges-location {
- width: 280px;
-}
-#torPreferences-bridges-location menuitem[disabled="true"] {
- color: var(--in-content-button-text-color, inherit);
- font-weight: 700;
+.bridge-status-badge {
+ display: flex;
+ min-width: max-content;
+ align-items: center;
+ gap: 0.5em;
+ font-size: 0.85em;
}
-/* Bridge cards */
-:root {
- --bridgeCard-animation-time: 0.25s;
+.bridge-status-badge:not(
+ .bridge-status-connected,
+ .bridge-status-none,
+ .bridge-status-current-built-in
+) {
+ display: none;
}
-#torPreferences-currentBridges-cards {
- /* The padding is needed because the mask-image creates an unexpected result
- otherwise... */
- padding: 24px 4px;
+.bridge-status-badge.bridge-status-connected {
+ color: var(--purple-60);
}
-#torPreferences-currentBridges-cards.list-collapsed {
- mask-image: linear-gradient(rgb(0, 0, 0) 0% 75%, rgba(0, 0, 0, 0.1));
+@media (prefers-color-scheme: dark) {
+ .bridge-status-badge.bridge-status-connected {
+ color: var(--purple-30);
+ }
}
-#torPreferences-currentBridges-cards.disabled {
- opacity: 0.4;
+.bridge-status-badge.bridge-status-current-built-in {
+ color: var(--in-content-accent-color);
}
-.torPreferences-bridgeCard {
- padding: 16px 12px;
- /* define border-radius here because of the transition */
- border-radius: 4px;
- transition: margin var(--bridgeCard-animation-time), box-shadow 150ms;
- cursor: pointer;
+.bridge-status-badge > * {
+ flex: 0 0 auto;
}
-.torPreferences-bridgeCard.expanded,
-.torPreferences-bridgeCard.currently-connected,
-.single-card .torPreferences-bridgeCard {
- margin: 12px 0;
- background: var(--in-content-box-background);
- box-shadow: var(--card-shadow);
+.bridge-status-icon {
+ width: 16px;
+ height: 16px;
+ background-repeat: no-repeat;
+ background-position: center center;
+ -moz-context-properties: fill;
+ fill: currentColor;
}
-.torPreferences-bridgeCard:hover {
- background: var(--in-content-box-background);
- box-shadow: var(--card-shadow-hover);
+.bridge-status-badge:is(
+ .bridge-status-connected,
+ .bridge-status-current-built-in
+) .bridge-status-icon {
+ background-image: url("chrome://global/skin/icons/check.svg");
}
-.single-card .torPreferences-bridgeCard,
-.torPreferences-bridgeCard.currently-connected {
- cursor: default;
+.bridge-status-badge.bridge-status-none .bridge-status-icon {
+ /* Hide the icon. */
+ display: none;
}
-.torPreferences-bridgeCard-heading {
- display: flex;
- align-items: center;
+#tor-bridges-enabled-toggle {
+ margin-block: 16px;
+ width: max-content;
}
-.torPreferences-bridgeCard-id {
- display: flex;
- align-items: center;
- font-weight: 700;
+#tor-bridges-update-area {
+ /* Still accessible to screen reader, but not visual. */
+ position: absolute;
+ clip-path: inset(50%);
}
-.torPreferences-bridgeCard-id .emoji {
- width: 20px;
- height: 20px;
- margin-inline-start: 4px;
- padding: 4px;
- font-size: 20px;
- border-radius: 4px;
- background: var(--in-content-box-background-odd);
+#torPreferences-bridges-group:not(.have-bridges, .no-bridges) {
+ /* Hide bridge settings whilst not initialized. */
+ display: none;
}
-#torPreferences-currentBridges-cards:not(
- .single-card
-) .torPreferences-bridgeCard:not(
- .expanded,
- .currently-connected
-) .torPreferences-bridgeCard-manualLink {
+#torPreferences-bridges-group:not(.have-bridges) #tor-bridges-current {
display: none;
}
-.torPreferences-bridgeCard-manualLink {
- margin: 0 8px;
+#torPreferences-bridges-group:not(.no-bridges) #tor-bridges-none {
+ display: none;
}
-.torPreferences-bridgeCard-headingAddr {
- /* flex extends the element when needed, but without setting a width (any) the
- overflow + ellipses does not work. */
- width: 20px;
- flex: 1;
- margin: 0 8px;
- overflow: hidden;
- color: var(--text-color-deemphasized);
- white-space: nowrap;
- text-overflow: ellipsis;
+#tor-bridges-current:not(.source-built-in) #tor-bridges-built-in-label {
+ display: none;
}
-.expanded .torPreferences-bridgeCard-headingAddr,
-.currently-connected .torPreferences-bridgeCard-headingAddr,
-.single-card .torPreferences-bridgeCard-headingAddr {
+#tor-bridges-current:not(.source-user) #tor-bridges-user-label {
display: none;
}
-.torPreferences-bridgeCard-buttons {
- display: flex;
- align-items: center;
- margin-inline-start: auto;
- align-self: center;
+#tor-bridges-current:not(.source-requested) #tor-bridges-requested-label {
+ display: none;
}
-.torPreferences-current-bridge-badge {
- /* Hidden by default, otherwise display is "flex". */
+#tor-bridges-current:not(
+ .source-user,
+ .source-requested
+) #tor-bridges-share {
display: none;
- align-items: center;
- font-size: 0.85em;
}
-:is(
- .builtin-bridges-option.current-builtin-bridge-type,
- .torPreferences-bridgeCard.currently-connected
-) .torPreferences-current-bridge-badge {
- display: flex;
+#tor-bridges-none,
+#tor-bridges-current {
+ margin-inline: 0;
+ margin-block: 32px;
+ line-height: 1.8;
}
-.torPreferences-current-bridge-icon {
- margin-inline-start: 1px;
- margin-inline-end: 7px;
- list-style-image: url("chrome://global/skin/icons/check.svg");
+#tor-bridges-none {
+ display: grid;
+ justify-items: center;
+ text-align: center;
+ padding-block: 64px;
+ padding-inline: 32px;
+ gap: 16px;
+ border-radius: 4px;
+ color: var(--text-color-deemphasized);
+ border: 2px dashed color-mix(in srgb, currentColor 20%, transparent);
+}
+
+#tor-bridges-none-icon {
+ width: 20px;
+ height: 20px;
+ content: url("chrome://browser/content/torpreferences/bridge.svg");
-moz-context-properties: fill;
fill: currentColor;
- flex: 0 0 auto;
}
-.torPreferences-bridgeCard .torPreferences-current-bridge-badge {
- color: var(--purple-60);
- margin-inline-end: 12px;
+#tor-bridges-current {
+ padding: 16px;
+ border-radius: 4px;
+ background: var(--in-content-box-info-background);
}
-@media (prefers-color-scheme: dark) {
- .torPreferences-bridgeCard .torPreferences-current-bridge-badge {
- color: var(--purple-30);
- }
+#tor-bridges-current-header-bar {
+ display: flex;
+ min-width: max-content;
+ align-items: center;
+ border-block-end: 1px solid var(--in-content-border-color);
+ padding-block-end: 16px;
+ margin-block-end: 16px;
}
-.torPreferences-bridgeCard-options {
- width: 24px;
- min-width: 0;
- height: 24px;
- min-height: 0;
- margin-inline-start: 8px;
- padding: 1px;
+#tor-bridges-current-header-bar > * {
+ flex: 0 0 auto;
+}
+
+#tor-bridges-current-heading {
+ margin: 0;
+ margin-inline-end: 2em;
+ font-size: inherit;
+ flex: 1 0 auto;
+}
+
+.tor-bridges-options-button {
+ padding: 3px;
+ margin: 0;
+ min-height: auto;
+ min-width: auto;
+ box-sizing: content-box;
+ width: 16px;
+ height: 16px;
background-image: url("chrome://global/skin/icons/more.svg");
background-repeat: no-repeat;
background-position: center center;
- fill: currentColor;
+ background-origin: content-box;
+ background-size: contain;
-moz-context-properties: fill;
+ fill: currentColor;
}
-#torPreferences-bridgeCard-menu menuitem {
- fill: currentColor;
- -moz-context-properties: fill;
+#tor-bridges-all-options-button {
+ margin-inline-start: 8px;
}
-.torPreferences-bridgeCard-qrWrapper {
- grid-area: bridge-qr;
- display: block; /* So it doesn't stretch the child vertically. */
- margin-inline-end: 14px;
+#tor-bridges-built-in-display {
+ display: grid;
+ grid-template:
+ "type status" min-content
+ "description description" auto
+ / max-content 1fr;
+ gap: 4px 1.5em;
+ margin-block-end: 16px;
}
-.torPreferences-bridgeCard-qr {
- --qr-one: black;
- --qr-zero: white;
- background: var(--qr-zero);
- position: relative;
- padding: 4px;
- border-radius: 2px;
+#tor-bridges-built-in-display:not(.built-in-active) {
+ display: none;
}
-.torPreferences-bridgeCard-qrCode {
- width: 112px;
- height: 112px;
- /* Define these colors, as they will be passed to the QR code library */
- background: var(--qr-zero);
- color: var(--qr-one);
+#tor-bridges-built-in-type-name {
+ font-weight: 700;
+ grid-area: type;
}
-.torPreferences-bridgeCard-qrOnionBox {
- width: 28px;
- height: 28px;
- position: absolute;
- top: calc(50% - 14px);
- inset-inline-start: calc(50% - 14px);
- background: var(--qr-zero);
+#tor-bridges-built-in-connected {
+ grid-area: status;
+ justify-self: end;
}
-.torPreferences-bridgeCard-qrOnion {
- width: 16px;
- height: 16px;
- position: absolute;
- top: calc(50% - 8px);
- inset-inline-start: calc(50% - 8px);
+#tor-bridges-built-in-description {
+ grid-area: description;
+}
- mask: url("chrome://browser/content/torpreferences/bridge-qr-onion-mask.svg");
- mask-repeat: no-repeat;
- mask-size: 16px;
- background: var(--qr-one);
+#tor-bridges-grid-display {
+ display: grid;
+ grid-template-columns: max-content repeat(4, max-content) 1fr;
+ --tor-bridges-grid-column-gap: 8px;
+ --tor-bridges-grid-column-short-gap: 4px;
}
-.torPreferences-bridgeCard-qr:hover .torPreferences-bridgeCard-qrOnionBox {
- background: var(--qr-one);
+#tor-bridges-grid-display:not(.grid-active) {
+ display: none;
}
-.torPreferences-bridgeCard-qr:hover .torPreferences-bridgeCard-qrOnion {
- mask: url("chrome://global/skin/icons/search-glass.svg");
- background: var(--qr-zero);
+.tor-bridges-grid-row {
+ /* We want each row to act as a row of three items in the
+ * #tor-bridges-grid-display grid layout.
+ * We also want a 16px spacing between rows, and 8px spacing between columns,
+ * which are outside the .tor-bridges-grid-cell's border area. So that
+ * clicking these gaps will not focus any item, and their focus outlines do
+ * not overlap.
+ * Moreover, we also want each row to show its .tor-bridges-options-cell when
+ * the .tor-bridges-grid-row has :hover.
+ *
+ * We could use "display: contents" on the row and set a "gap: 16px 8px" on
+ * the parent so that its items fall into the parent layout. However, the gap
+ * between the items would mean there are places where no row has :hover. So
+ * if the user glided across the grid, the options button would visibly
+ * disappear any time the pointer entered a gap, causing the display to feel
+ * "jumpy".
+ *
+ * Instead, we use a "subgrid" layout for each .tor-bridges-grid-row, and
+ * using padding, rather than a gap, for the vertical spacing. Therefore,
+ * every part of the grid is covered by a row, so moving the pointer over the
+ * grid will always have one row with :hover, so one of the options cell will
+ * always be visible.
+ */
+ display: grid;
+ grid-column: 1 / -1;
+ grid-template-columns: subgrid;
+ /* Add 16px gap between rows, plus 8px at the start and end of the grid. */
+ padding-block: 8px;
}
-.torPreferences-bridgeCard-grid {
- height: 0; /* We will set it in JS when expanding it! */
+.tor-bridges-grid-cell:focus-visible {
+ outline: var(--in-content-focus-outline);
+ outline-offset: var(--in-content-focus-outline-offset);
+}
+
+.tor-bridges-grid-cell {
+ /* The cell is stretched to the height of the row, so that each focus outline
+ * shares the same height, but we want to center-align the content within,
+ * which is either a single Element or a TextNode. */
display: grid;
- grid-template-rows: auto 1fr;
- grid-template-columns: auto 1fr auto;
- grid-template-areas:
- 'bridge-qr bridge-share bridge-share'
- 'bridge-qr bridge-address bridge-address'
- 'bridge-qr bridge-learn-more bridge-copy';
- visibility: hidden;
+ align-content: center;
}
-.expanded .torPreferences-bridgeCard-grid,
-.currently-connected .torPreferences-bridgeCard-grid,
-.single-card .torPreferences-bridgeCard-grid {
- padding-top: 12px;
- visibility: visible;
+.tor-bridges-type-cell {
+ margin-inline-end: var(--tor-bridges-grid-column-gap);
}
-.currently-connected .torPreferences-bridgeCard-grid,
-.single-card .torPreferences-bridgeCard-grid {
- height: auto;
+.tor-bridges-emojis-block {
+ /* Emoji block occupies four columns, but with a smaller gap. */
+ display: contents;
}
-.torPreferences-bridgeCard-grid.to-animate {
- transition: height var(--bridgeCard-animation-time) ease-out, visibility var(--bridgeCard-animation-time);
- overflow: hidden;
+.tor-bridges-emoji-cell:not(:last-child) {
+ margin-inline-end: var(--tor-bridges-grid-column-short-gap);
}
-.torPreferences-bridgeCard-share {
- grid-area: bridge-share;
+.tor-bridges-emoji-icon {
+ display: block;
+ box-sizing: content-box;
+ width: 16px;
+ height: 16px;
+ background: var(--in-content-button-background);
+ border-radius: 4px;
+ padding: 8px;
}
-.torPreferences-bridgeCard-addrBox {
- grid-area: bridge-address;
+.tor-bridges-grid-end-block {
+ /* The last three cells all share a single grid item slot in the
+ * #tor-bridges-grid-display layout.
+ * This is because we do not want to align its cells between rows. */
+ min-width: max-content;
display: flex;
- align-items: center;
- justify-content: center;
- margin: 8px 0;
+ /* Choose "stretch" instead of "center" so that focus outline is a consistent
+ * height between cells. */
+ align-items: stretch;
+ margin-inline-start: var(--tor-bridges-grid-column-gap);
+ gap: var(--tor-bridges-grid-column-gap);
}
-input.torPreferences-bridgeCard-addr {
- width: 100%;
+.tor-bridges-address-cell {
+ /* base size */
+ width: 10em;
+ flex: 1 0 auto;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
color: var(--text-color-deemphasized);
}
-.torPreferences-bridgeCard-leranMoreBox {
- grid-area: bridge-learn-more;
+.tor-bridges-status-cell,
+.tor-bridges-options-cell {
+ flex: 0 0 auto;
}
-.torPreferences-bridgeCard-copy {
- grid-area: bridge-copy;
+/* Hide the options button if the row does not have hover or focus. */
+.tor-bridges-grid-row:not(
+ :hover,
+ :focus-within
+) .tor-bridges-options-cell,
+/* Hide the status cell when it shows "No status" if the cell does not have
+ * focus. */
+.tor-bridges-grid-row.hide-status .tor-bridges-status-cell:not(:focus) {
+ /* Still accessible to screen reader, but not visual and does not contribute
+ * to the parent flex layout. */
+ /* NOTE: We assume that the height of these cell's content is equal to or less
+ * than the other cells, so there won't be a jump in row height when they
+ * become visual again and contribute to the layout. */
+ position: absolute;
+ clip-path: inset(50%);
}
-#torPreferences-bridgeCard-template {
- display: none;
+#tor-bridges-share {
+ margin-block-start: 24px;
+ border-radius: 4px;
+ border: 1px solid var(--in-content-border-color);
+ padding: 16px;
+ display: grid;
+ grid-template:
+ "heading heading heading" min-content
+ /* If the description spans one line, it will be center-aligned with the
+ * buttons, otherwise it will start to expand upwards. */
+ "description . ." 1fr
+ "description copy qr" min-content
+ / 1fr max-content max-content;
+ gap: 0 8px;
+ align-items: center;
+}
+
+#tor-bridges-share-heading {
+ grid-area: heading;
+ font-size: inherit;
+ margin: 0;
+ font-weight: 700;
+}
+
+#tor-bridges-share-description {
+ grid-area: description;
+}
+
+#tor-bridges-copy-addresses-button {
+ grid-area: copy;
+ margin: 0;
+ /* Match the QR height if it is higher than ours. */
+ min-height: auto;
+ line-height: 1;
+ align-self: stretch;
+}
+
+#tor-bridges-qr-addresses-button {
+ grid-area: qr;
+ padding: 5px;
+ margin: 0;
+ min-height: auto;
+ min-width: auto;
+ box-sizing: content-box;
+ width: 24px;
+ height: 24px;
+ background-image: url("chrome://browser/content/torpreferences/bridge-qr.svg");
+ background-repeat: no-repeat;
+ background-position: center center;
+ background-origin: content-box;
+ background-size: contain;
+ -moz-context-properties: fill;
+ fill: currentColor;
+}
+
+#torPreferences-bridges-location {
+ width: 280px;
+}
+
+#torPreferences-bridges-location menuitem[disabled="true"] {
+ color: var(--in-content-button-text-color, inherit);
+ font-weight: 700;
}
/* Advanced Settings */
@@ -446,10 +555,6 @@ dialog#torPreferences-requestBridge-dialog > hbox {
font-weight: 700;
}
-.builtin-bridges-option .torPreferences-current-bridge-badge {
- color: var(--in-content-accent-color);
-}
-
/* Request bridge dialog */
/*
This hbox is hidden by css here by default so that the
=====================================
browser/components/torpreferences/jar.mn
=====================================
@@ -1,4 +1,6 @@
browser.jar:
+ content/browser/torpreferences/bridge.svg (content/bridge.svg)
+ content/browser/torpreferences/bridge-qr.svg (content/bridge-qr.svg)
content/browser/torpreferences/bridgeQrDialog.xhtml (content/bridgeQrDialog.xhtml)
content/browser/torpreferences/bridgeQrDialog.mjs (content/bridgeQrDialog.mjs)
content/browser/torpreferences/builtinBridgeDialog.xhtml (content/builtinBridgeDialog.xhtml)
=====================================
browser/locales/en-US/browser/tor-browser.ftl
=====================================
@@ -44,3 +44,80 @@ tor-browser-home-message-testing = This is an unstable version of Tor Browser fo
# Shown in Home settings, corresponds to the default about:tor home page.
home-mode-choice-tor =
.label = Tor Browser Home
+
+## Tor Bridges Settings
+
+# Toggle button for enabling and disabling the use of bridges.
+tor-bridges-use-bridges =
+ .label = Use bridges
+
+tor-bridges-none-added = No bridges added
+tor-bridges-your-bridges = Your bridges
+tor-bridges-source-user = Added by you
+tor-bridges-source-built-in = Built-in
+tor-bridges-source-requested = Requested from Tor
+# The "..." menu button for all current bridges.
+tor-bridges-options-button =
+ .title = All bridges
+# Shown in the "..." menu for all bridges when the user can generate a QR code for all of their bridges.
+tor-bridges-menu-item-qr-all-bridge-addresses = Show QR code
+ .accesskey = Q
+# Shown in the "..." menu for all bridges when the user can copy all of their bridges.
+tor-bridges-menu-item-copy-all-bridge-addresses = Copy bridge addresses
+ .accesskey = C
+# Only shown in the "..." menu for bridges added by the user.
+tor-bridges-menu-item-edit-all-bridges = Edit bridges
+ .accesskey = E
+# Shown in the "..." menu for all current bridges.
+tor-bridges-menu-item-remove-all-bridges = Remove all bridges
+ .accesskey = R
+
+# Shown when one of the built-in bridges is in use.
+tor-bridges-built-in-status-connected = Connected
+
+# Shown at the start of a Tor bridge line.
+# $type (String) - The Tor bridge type ("snowflake", "obfs4", "meek-azure").
+tor-bridges-type-prefix = { $type } bridge:
+# The name and accessible description for a bridge emoji cell. Each bridge address can be hashed into four emojis shown to the user (bridgemoji feature). This cell corresponds to a *single* such emoji. The "title" should just be emojiName. The "aria-description" should give screen reader users enough of a hint that the cell contains a single emoji.
+# $emojiName (String) - The name of the emoji, already localized.
+# E.g. with Orca screen reader in en-US this would read "unicorn. Row 2 Column 2. Emoji".
+tor-bridges-emoji-cell =
+ .title = { $emojiName }
+ .aria-description = Emoji
+# The emoji name to show on hover when a bridge emoji's name is unknown.
+tor-bridges-emoji-unknown = Unknown
+# Shown when the bridge has been used for the most recent Tor circuit, i.e. the most recent bridge we have connected to.
+tor-bridges-status-connected = Connected
+# Used when the bridge has no status, i.e. the *absence* of a status to report to the user. This is only visibly shown when the status cell has keyboard focus.
+tor-bridges-status-none = No status
+# The "..." menu button for an individual bridge row.
+tor-bridges-individual-bridge-options-button =
+ .title = Bridge options
+# Shown in the "..." menu for an individual bridge. Shows the QR code for this one bridge.
+tor-bridges-menu-item-qr-address = Show QR code
+ .accesskey = Q
+# Shown in the "..." menu for an individual bridge. Copies the single bridge address to clipboard.
+tor-bridges-menu-item-copy-address = Copy bridge address
+ .accesskey = C
+# Shown in the "..." menu for an individual bridge. Removes this one bridge.
+tor-bridges-menu-item-remove-bridge = Remove bridge
+ .accesskey = R
+
+# Text shown just before a description of the most recent change to the list of user's bridges. Some white space will separate this text from the change description.
+# This text is not visible, but is instead used for screen reader users.
+# E.g. in English this could be "Recent update: One of your Tor bridges has been removed."
+tor-bridges-update-area-intro = Recent update:
+# Update text for screen reader users when only one of their bridges has been removed.
+tor-bridges-update-removed-one-bridge = One of your Tor bridges has been removed.
+# Update text for screen reader users when all of their bridges have been removed.
+tor-bridges-update-removed-all-bridges = All of your Tor bridges have been removed.
+# Update text for screen reader users when their bridges have changed in some arbitrary way.
+tor-bridges-update-changed-bridges = Your Tor bridges have changed.
+
+# Shown for requested bridges and bridges added by the user.
+tor-bridges-share-heading = Help others connect
+#
+tor-bridges-share-description = Share your bridges with trusted contacts.
+tor-bridges-copy-addresses-button = Copy addresses
+tor-bridges-qr-addresses-button =
+ .title = Show QR code
=====================================
toolkit/modules/TorSettings.sys.mjs
=====================================
@@ -175,6 +175,14 @@ class TorSettingsImpl {
allowed_ports: [],
},
};
+ /**
+ * Accumulated errors from trying to set settings.
+ *
+ * Only added to if not null.
+ *
+ * @type {Array<Error>?}
+ */
+ #settingErrors = null;
/**
* The recommended pluggable transport.
@@ -224,16 +232,33 @@ class TorSettingsImpl {
enabled: {},
});
this.#addProperties("bridges", {
+ /**
+ * Whether the bridges are enabled or not.
+ *
+ * @type {boolean}
+ */
enabled: {},
+ /**
+ * The current bridge source.
+ *
+ * @type {integer}
+ */
source: {
- transform: val => {
+ transform: (val, addError) => {
if (Object.values(TorBridgeSource).includes(val)) {
return val;
}
- lazy.logger.error(`Not a valid bridge source: "${val}"`);
+ addError(`Not a valid bridge source: "${val}"`);
return TorBridgeSource.Invalid;
},
},
+ /**
+ * The current bridge strings.
+ *
+ * Can only be non-empty if the "source" is not Invalid.
+ *
+ * @type {Array<string>}
+ */
bridge_strings: {
transform: val => {
if (Array.isArray(val)) {
@@ -244,13 +269,15 @@ class TorSettingsImpl {
copy: val => [...val],
equal: (val1, val2) => this.#arrayEqual(val1, val2),
},
+ /**
+ * The built-in type to use when using the BuiltIn "source", or empty when
+ * using any other source.
+ *
+ * @type {string}
+ */
builtin_type: {
- callback: val => {
+ callback: (val, addError) => {
if (!val) {
- // Make sure that the source is not BuiltIn
- if (this.bridges.source === TorBridgeSource.BuiltIn) {
- this.bridges.source = TorBridgeSource.Invalid;
- }
return;
}
const bridgeStrings = this.#getBuiltinBridges(val);
@@ -258,39 +285,28 @@ class TorSettingsImpl {
this.bridges.bridge_strings = bridgeStrings;
return;
}
- lazy.logger.error(`No built-in ${val} bridges found`);
- // Change to be empty, this will trigger this callback again,
- // but with val as "".
- this.bridges.builtin_type == "";
+
+ addError(`No built-in ${val} bridges found`);
+ // Set as invalid, which will make the builtin_type "" and set the
+ // bridge_strings to be empty at the next #cleanupSettings.
+ this.bridges.source = TorBridgeSource.Invalid;
},
},
});
this.#addProperties("proxy", {
- enabled: {
- callback: val => {
- if (val) {
- return;
- }
- // Reset proxy settings.
- this.proxy.type = TorProxyType.Invalid;
- this.proxy.address = "";
- this.proxy.port = 0;
- this.proxy.username = "";
- this.proxy.password = "";
- },
- },
+ enabled: {},
type: {
- transform: val => {
+ transform: (val, addError) => {
if (Object.values(TorProxyType).includes(val)) {
return val;
}
- lazy.logger.error(`Not a valid proxy type: "${val}"`);
+ addError(`Not a valid proxy type: "${val}"`);
return TorProxyType.Invalid;
},
},
address: {},
port: {
- transform: val => {
+ transform: (val, addError) => {
if (val === 0) {
// This is a valid value that "unsets" the port.
// Keep this value without giving a warning.
@@ -298,15 +314,11 @@ class TorSettingsImpl {
return 0;
}
// Unset to 0 if invalid null is returned.
- return this.#parsePort(val, false) ?? 0;
+ return this.#parsePort(val, false, addError) ?? 0;
},
},
- username: {
- transform: val => val ?? "",
- },
- password: {
- transform: val => val ?? "",
- },
+ username: {},
+ password: {},
uri: {
getter: () => {
const { type, address, port, username, password } = this.proxy;
@@ -329,20 +341,16 @@ class TorSettingsImpl {
},
});
this.#addProperties("firewall", {
- enabled: {
- callback: val => {
- if (!val) {
- this.firewall.allowed_ports = "";
- }
- },
- },
+ enabled: {},
allowed_ports: {
- transform: val => {
+ transform: (val, addError) => {
if (!Array.isArray(val)) {
val = val === "" ? [] : val.split(",");
}
// parse and remove duplicates
- const portSet = new Set(val.map(p => this.#parsePort(p, true)));
+ const portSet = new Set(
+ val.map(p => this.#parsePort(p, true, addError))
+ );
// parsePort returns null for failed parses, so remove it.
portSet.delete(null);
return [...portSet];
@@ -353,6 +361,39 @@ class TorSettingsImpl {
});
}
+ /**
+ * Clean the setting values after making some changes, so that the values do
+ * not contradict each other.
+ */
+ #cleanupSettings() {
+ this.freezeNotifications();
+ try {
+ if (this.bridges.source === TorBridgeSource.Invalid) {
+ this.bridges.enabled = false;
+ this.bridges.bridge_strings = [];
+ }
+ if (!this.bridges.bridge_strings.length) {
+ this.bridges.enabled = false;
+ this.bridges.source = TorBridgeSource.Invalid;
+ }
+ if (this.bridges.source !== TorBridgeSource.BuiltIn) {
+ this.bridges.builtin_type = "";
+ }
+ if (!this.proxy.enabled) {
+ this.proxy.type = TorProxyType.Invalid;
+ this.proxy.address = "";
+ this.proxy.port = 0;
+ this.proxy.username = "";
+ this.proxy.password = "";
+ }
+ if (!this.firewall.enabled) {
+ this.firewall.allowed_ports = [];
+ }
+ } finally {
+ this.thawNotifications();
+ }
+ }
+
/**
* The current number of freezes applied to the notifications.
*
@@ -435,6 +476,13 @@ class TorSettingsImpl {
const group = {};
for (const name in propParams) {
const { getter, transform, callback, copy, equal } = propParams[name];
+ // Method for adding setting errors.
+ const addError = message => {
+ message = `TorSettings.${groupname}.${name}: ${message}`;
+ lazy.logger.error(message);
+ // Only add to #settingErrors if it is not null.
+ this.#settingErrors?.push(message);
+ };
Object.defineProperty(group, name, {
get: getter
? () => {
@@ -467,16 +515,20 @@ class TorSettingsImpl {
this.freezeNotifications();
try {
if (transform) {
- val = transform(val);
+ val = transform(val, addError);
}
const isEqual = equal ? equal(val, prevVal) : val === prevVal;
if (!isEqual) {
- if (callback) {
- callback(val);
- }
+ // Set before the callback.
this.#settings[groupname][name] = val;
this.#notificationQueue.add(`${groupname}.${name}`);
+
+ if (callback) {
+ callback(val, addError);
+ }
}
+ } catch (e) {
+ addError(e.message);
} finally {
this.thawNotifications();
}
@@ -503,11 +555,12 @@ class TorSettingsImpl {
* @param {string|integer} val - The value to parse.
* @param {boolean} trim - Whether a string value can be stripped of
* whitespace before parsing.
+ * @param {function} addError - Callback to add error messages to.
*
* @return {integer?} - The port number, or null if the given value was not
* valid.
*/
- #parsePort(val, trim) {
+ #parsePort(val, trim, addError) {
if (typeof val === "string") {
if (trim) {
val = val.trim();
@@ -516,12 +569,12 @@ class TorSettingsImpl {
if (this.#portRegex.test(val)) {
val = Number.parseInt(val, 10);
} else {
- lazy.logger.error(`Invalid port string "${val}"`);
+ addError(`Invalid port string "${val}"`);
return null;
}
}
if (!Number.isInteger(val) || val < 1 || val > 65535) {
- lazy.logger.error(`Port out of range: ${val}`);
+ addError(`Port out of range: ${val}`);
return null;
}
return val;
@@ -739,6 +792,8 @@ class TorSettingsImpl {
""
);
}
+
+ this.#cleanupSettings();
}
/**
@@ -748,6 +803,7 @@ class TorSettingsImpl {
lazy.logger.debug("saveToPrefs()");
this.#checkIfInitialized();
+ this.#cleanupSettings();
/* Quickstart */
Services.prefs.setBoolPref(
@@ -847,6 +903,8 @@ class TorSettingsImpl {
async #applySettings(allowUninitialized) {
lazy.logger.debug("#applySettings()");
+ this.#cleanupSettings();
+
const settingsMap = new Map();
// #applySettings can be called only when #allowUninitialized is false
@@ -928,6 +986,8 @@ class TorSettingsImpl {
const backup = this.getSettings();
const backupNotifications = [...this.#notificationQueue];
+ // Start collecting errors.
+ this.#settingErrors = [];
// Hold off on lots of notifications until all settings are changed.
this.freezeNotifications();
@@ -946,25 +1006,11 @@ class TorSettingsImpl {
case TorBridgeSource.UserProvided:
this.bridges.bridge_strings = settings.bridges.bridge_strings;
break;
- case TorBridgeSource.BuiltIn: {
+ case TorBridgeSource.BuiltIn:
this.bridges.builtin_type = settings.bridges.builtin_type;
- if (!this.bridges.bridge_strings.length) {
- // No bridges were found when setting the builtin_type.
- throw new Error(
- `No available builtin bridges of type ${settings.bridges.builtin_type}`
- );
- }
break;
- }
case TorBridgeSource.Invalid:
break;
- default:
- if (settings.bridges.enabled) {
- throw new Error(
- `Bridge source '${settings.source}' is not a valid source`
- );
- }
- break;
}
}
@@ -985,6 +1031,12 @@ class TorSettingsImpl {
this.firewall.allowed_ports = settings.firewall.allowed_ports;
}
}
+
+ this.#cleanupSettings();
+
+ if (this.#settingErrors.length) {
+ throw Error(this.#settingErrors.join("; "));
+ }
} catch (ex) {
// Restore the old settings without any new notifications generated from
// the above code.
@@ -1001,6 +1053,8 @@ class TorSettingsImpl {
lazy.logger.error("setSettings failed", ex);
} finally {
this.thawNotifications();
+ // Stop collecting errors.
+ this.#settingErrors = null;
}
lazy.logger.debug("setSettings result", this.#settings);
=====================================
toolkit/modules/TorStrings.sys.mjs
=====================================
@@ -3,8 +3,6 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-"use strict";
-
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
@@ -86,7 +84,6 @@ const Loader = {
statusTorNotConnected: "Not Connected",
statusTorBlocked: "Potentially Blocked",
learnMore: "Learn more",
- whatAreThese: "What are these?",
// Quickstart
quickstartHeading: "Quickstart",
quickstartDescription:
@@ -101,22 +98,10 @@ const Loader = {
bridgeLocationFrequent: "Frequently selected locations",
bridgeLocationOther: "Other locations",
bridgeChooseForMe: "Choose a Bridge For Me…",
- bridgeCurrent: "Your Current Bridges",
- bridgeCurrentDescription:
- "You can keep one or more bridges saved, and Tor will choose which one to use when you connect. Tor will automatically switch to use another bridge when needed.",
- bridgeId: "%1$S bridge: %2$S",
currentBridge: "Current bridge",
- connectedBridge: "Connected",
remove: "Remove",
bridgeDisableBuiltIn: "Disable built-in bridges",
- bridgeShare:
- "Share this bridge using the QR code or by copying its address:",
- bridgeCopy: "Copy Bridge Address",
copied: "Copied!",
- bridgeShowAll: "Show All Bridges",
- bridgeShowFewer: "Show Fewer Bridges",
- allBridgesEnabled: "Use current bridges",
- bridgeRemoveAll: "Remove All Bridges",
bridgeRemoveAllDialogTitle: "Remove all bridges?",
bridgeRemoveAllDialogDescription:
"If these bridges were received from torproject.org or added manually, this action cannot be undone",
@@ -199,8 +184,6 @@ const Loader = {
...tsb.getStrings(strings),
learnMoreTorBrowserURL: "about:manual#about",
learnMoreBridgesURL: "about:manual#bridges",
- learnMoreBridgesCardURL: "about:manual#bridges_bridge-moji",
- learnMoreCircumventionURL: "about:manual#circumvention",
};
} /* Tor Network Settings Strings */,
=====================================
toolkit/torbutton/chrome/locale/en-US/settings.properties
=====================================
@@ -32,23 +32,11 @@ settings.bridgeLocationAutomatic=Automatic
settings.bridgeLocationFrequent=Frequently selected locations
settings.bridgeLocationOther=Other locations
settings.bridgeChooseForMe=Choose a Bridge For Me…
-settings.bridgeCurrent=Your Current Bridges
-settings.bridgeCurrentDescription=You can keep one or more bridges saved, and Tor will choose which one to use when you connect. Tor will automatically switch to use another bridge when needed.
-# Translation note: %1$S = bridge type; %2$S = bridge emoji id
-settings.bridgeId=%1$S bridge: %2$S
-settings.connectedBridge=Connected
settings.currentBridge=Current bridge
settings.remove=Remove
settings.bridgeDisableBuiltIn=Disable built-in bridges
-settings.bridgeShare=Share this bridge using the QR code or by copying its address:
-settings.whatAreThese=What are these?
-settings.bridgeCopy=Copy Bridge Address
settings.copied=Copied!
-settings.bridgeShowAll=Show All Bridges
-settings.bridgeShowFewer=Show Fewer Bridges
-settings.allBridgesEnabled=Use current bridges
-settings.bridgeRemoveAll=Remove All Bridges
settings.bridgeRemoveAllDialogTitle=Remove all bridges?
settings.bridgeRemoveAllDialogDescription=If these bridges were received from torproject.org or added manually, this action cannot be undone
settings.bridgeAdd=Add a New Bridge
@@ -121,3 +109,19 @@ settings.allowedPortsPlaceholder=Comma-separated values
# Log dialog
settings.torLogDialogTitle=Tor Logs
settings.copyLog=Copy Tor Log to Clipboard
+
+
+# TODO: Remove
+
+settings.bridgeCurrent=Your Current Bridges
+settings.bridgeCurrentDescription=You can keep one or more bridges saved, and Tor will choose which one to use when you connect. Tor will automatically switch to use another bridge when needed.
+# Translation note: %1$S = bridge type; %2$S = bridge emoji id
+settings.bridgeId=%1$S bridge: %2$S
+settings.connectedBridge=Connected
+settings.bridgeShare=Share this bridge using the QR code or by copying its address:
+settings.whatAreThese=What are these?
+settings.bridgeCopy=Copy Bridge Address
+settings.bridgeShowAll=Show All Bridges
+settings.bridgeShowFewer=Show Fewer Bridges
+settings.allBridgesEnabled=Use current bridges
+settings.bridgeRemoveAll=Remove All Bridges
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/cbeecf…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/cbeecf…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-update-responses][main] release: new version, 13.0.9
by richard (@richard) 23 Jan '24
by richard (@richard) 23 Jan '24
23 Jan '24
richard pushed to branch main at The Tor Project / Applications / Tor Browser update responses
Commits:
870a5f0b by Richard Pospesel at 2024-01-23T13:22:31+00:00
release: new version, 13.0.9
- - - - -
20 changed files:
- update_3/release/13.0.6-13.0.9-linux-i686-ALL.xml
- update_3/release/13.0.6-13.0.9-linux-x86_64-ALL.xml
- update_3/release/13.0.6-13.0.9-macos-ALL.xml
- update_3/release/13.0.6-13.0.9-windows-i686-ALL.xml
- update_3/release/13.0.6-13.0.9-windows-x86_64-ALL.xml
- update_3/release/13.0.7-13.0.9-linux-i686-ALL.xml
- update_3/release/13.0.7-13.0.9-linux-x86_64-ALL.xml
- update_3/release/13.0.7-13.0.9-macos-ALL.xml
- update_3/release/13.0.7-13.0.9-windows-i686-ALL.xml
- update_3/release/13.0.7-13.0.9-windows-x86_64-ALL.xml
- update_3/release/13.0.8-13.0.9-linux-i686-ALL.xml
- update_3/release/13.0.8-13.0.9-linux-x86_64-ALL.xml
- update_3/release/13.0.8-13.0.9-macos-ALL.xml
- update_3/release/13.0.8-13.0.9-windows-i686-ALL.xml
- update_3/release/13.0.8-13.0.9-windows-x86_64-ALL.xml
- update_3/release/13.0.9-linux-i686-ALL.xml
- update_3/release/13.0.9-linux-x86_64-ALL.xml
- update_3/release/13.0.9-macos-ALL.xml
- update_3/release/13.0.9-windows-i686-ALL.xml
- update_3/release/13.0.9-windows-x86_64-ALL.xml
Changes:
=====================================
update_3/release/13.0.6-13.0.9-linux-i686-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686-13…" hashFunction="SHA512" hashValue="d36e7dd3a39de83fb9a71fd5a8b7262aca98bb97953287fe9a3ab90c0a95ad97c40d240834d702b57182b599efc21f92aa15b6b62f5e180f0617d8fb678e035e" size="121912046" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686--1…" hashFunction="SHA512" hashValue="e347bbb01cab053e900bece0cc37f4b80170c5ccdfd41504d88ad3c9b1cfd9eda836257db99f8311ad7f2974c52293c38280be9cc2c28392aa491f1b883c25c4" size="14969289" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686-13…" hashFunction="SHA512" hashValue="09f66723dade529f77267546a8cab70bb71c8621bc7c67e7975ce67139c0f8b305125b4cf38b4a77cea4182c382a215013ac718dc6ee3561b3046266f2d234c8" size="121912074" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686--1…" hashFunction="SHA512" hashValue="b539153addf108f35781a2564a84f659a42fc0c01fdc5f05b50747abc4eafefa4a5630918d82e502421fb56c24d9307c3d50f00dfb8ba5d22dbb7c6d8dc0ef58" size="14969329" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.6-13.0.9-linux-x86_64-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="4c26112f794839450194bac302923e4fe8f4f9d15568b638b876760ed064e21f1c1eae0262f8b68d628e4ea78ad0466b4e841b859dc6183da1caf9d622590597" size="121131338" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="4a4d0aa0fe3ff7ba342286a08af1dff0b024fb6db37b43f31b481f4148364ce833d7c48de8eb4a1831244968225a8473077d4cea4cd14a306b3dac2fc67bc397" size="14359671" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="d4f4a4a2a403dc597e475cc335a83e42774ff571baac4e1385e19a8d199a6fa9cae1ac42ee8d082b0ec01fb5e34f02c5482cd2723203c8d282c67349e43f5be8" size="121131366" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="f4a2e180f439e1f94c2f8da7b1e46d6b35e2c77919ae0e65fc3fcfbc2d89ab565e28e23c57a87980100ad9459863b7eaf7e6258443ff61a444691ffb4af51291" size="14359711" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.6-13.0.9-macos-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="16.0.0"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos-13.0.9_…" hashFunction="SHA512" hashValue="86aaf0b4deb8eab82620ceb4829783e652735174e1106e92981aeb2ad7c5eeb576fbef9114d75f038ff26ae67bdc3e74c042677d28e18174d5ac1447b9cff0fe" size="169201961" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos--13.0.6…" hashFunction="SHA512" hashValue="bafe6b8d4b68915b41e16a31b44ad1237841d234c5f39546d1a09f7f251f4e2d79c1be5eaccbb7216887603931b850a151000869ca302b6bb7a2b3f5b8d58db3" size="46429595" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="16.0.0"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos-13.0.9_…" hashFunction="SHA512" hashValue="b407a03608c5b816ab80cce5866d8126eabc68796cb022dafcb9b80f654aafe56eceb0f30477007f930ee8392c06817c5ce0873124cf325238ed6246eee6d870" size="169203413" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos--13.0.6…" hashFunction="SHA512" hashValue="d6b0de2b7d506ecde5f393a10d3392ceb9dd28bd7e9d75242b9a2a069257518c86d776602af374eae9bbf8d49f9cbb3a379561f8691f46ad36904d94ef2b07e2" size="46430283" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.6-13.0.9-windows-i686-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="475cd3c2d31d174fbd30ac592da6d4b9257c61baf09832903bc07a5dac55089131738e3edbc2def7561c6477a2d3db78857249337496473be57c966b7ef809a7" size="109175033" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="82084ddf48c29805bfdf7d7c56282018915cbbaebcfa3710428bcd9e023f17a1b9c7013f1c211b4144142face81edc19214f2c1a9de5bcd02a7c7307ae53331e" size="21150270" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="a220ac388f2974ee8c8f4162f6d3258ba3412875d1fd5bb0e7c52cf01fcbbcb548e2875753807c3b8a7feef4b01788940d7dd506343c3b817fbc7b419848db8b" size="109175061" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="08db2cfaadd37427cae650fcd1a2cd884b3b5124a2004bfeea8633a810b31fca142160e2495d06ec73fe6370e6edeb1df7d6c9c781531fa55d91908a66d8ddda" size="21150310" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.6-13.0.9-windows-x86_64-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="d196e644f3a75bd877219c1e1e2fc070affdb312826696dc928e2fdec3d6bc513dfb7aac005b115192446a56ab188649accf3c89ec729855176e87af82c6915e" size="109340435" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="8f48e537a44c0a506c7d5004b8cc4bac4f9ba503a1303935b7ee250396bca84ad34d739c8fcacf7800098c9776d8eec845900145ae86093e7da590d384f1be6b" size="20153176" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="071dc63319396dfe4a835d57646a4dcae73942d10a1b11f3967494d3a92e16664c9bc77127a5ffee357b7b32bc73ba3bf7665a40208f90e94093238e2a0e4bb2" size="109340463" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="1b78269c4d8c9a8522ab29eda37ba4efed7f472320411703247f5b9019f3c558ac2026b4eaab8c8b239599d6d4df5944aa48a0de5757d4784dc0c0b021810793" size="20153216" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.7-13.0.9-linux-i686-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686-13…" hashFunction="SHA512" hashValue="d36e7dd3a39de83fb9a71fd5a8b7262aca98bb97953287fe9a3ab90c0a95ad97c40d240834d702b57182b599efc21f92aa15b6b62f5e180f0617d8fb678e035e" size="121912046" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686--1…" hashFunction="SHA512" hashValue="7a2865c0f36bfde2084de0e9f7a35ecfa0c5850ef3e79b3d3b2a4fe13bb79681059c515722ae46dd46d3ee904a9d33309f15c57654ffcfbe374b14ffd4064258" size="11560810" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686-13…" hashFunction="SHA512" hashValue="09f66723dade529f77267546a8cab70bb71c8621bc7c67e7975ce67139c0f8b305125b4cf38b4a77cea4182c382a215013ac718dc6ee3561b3046266f2d234c8" size="121912074" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686--1…" hashFunction="SHA512" hashValue="26ab2df3f8c18c92a7b5c8af181fbfd697d97e9164d9f820056a5c3ce97c63dcb0c6dd76708232fcc37387324fd0c0f0d57b708f91aae3d82b5e958fb072b127" size="11560838" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.7-13.0.9-linux-x86_64-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="4c26112f794839450194bac302923e4fe8f4f9d15568b638b876760ed064e21f1c1eae0262f8b68d628e4ea78ad0466b4e841b859dc6183da1caf9d622590597" size="121131338" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="84c5ae313b6df6d901cdd08ec852deaaf8a89beeda1b3b53076e045fc06fdee36e48932a3c3321e6445b3c382ca489f8f1883959ed7c6a4603bd594a39312ecc" size="11429422" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="d4f4a4a2a403dc597e475cc335a83e42774ff571baac4e1385e19a8d199a6fa9cae1ac42ee8d082b0ec01fb5e34f02c5482cd2723203c8d282c67349e43f5be8" size="121131366" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="611af646854a54d98ede8da1d9a72e445c91d68b70b5bd697dede7bc399c5cf6c32b1b7c518e8312a27caf283a7283df0677f9d140e01c4e3eb2e80e9dd8de8c" size="11429450" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.7-13.0.9-macos-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="16.0.0"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos-13.0.9_…" hashFunction="SHA512" hashValue="86aaf0b4deb8eab82620ceb4829783e652735174e1106e92981aeb2ad7c5eeb576fbef9114d75f038ff26ae67bdc3e74c042677d28e18174d5ac1447b9cff0fe" size="169201961" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos--13.0.7…" hashFunction="SHA512" hashValue="0869d892ac0f2014534f46456668f8ce8595391afa8b3c28c5983cd71f08214b9a164f1c7d7367378f5b5f3b4e4e1f4d2f4a417487f29165f71f5c15bde28b54" size="42821574" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="16.0.0"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos-13.0.9_…" hashFunction="SHA512" hashValue="b407a03608c5b816ab80cce5866d8126eabc68796cb022dafcb9b80f654aafe56eceb0f30477007f930ee8392c06817c5ce0873124cf325238ed6246eee6d870" size="169203413" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos--13.0.7…" hashFunction="SHA512" hashValue="324eb7c8ed16e5eb6c455447e3bcd7bcb1387d27764901b99ddce24ab5e653dc9cd6db7f2164c4e295b71d772edf574c16e81a9235ba1cecc1b7f89fd4482d32" size="42827222" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.7-13.0.9-windows-i686-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="475cd3c2d31d174fbd30ac592da6d4b9257c61baf09832903bc07a5dac55089131738e3edbc2def7561c6477a2d3db78857249337496473be57c966b7ef809a7" size="109175033" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="25cf3385153b06ab684a4a2454cc44d57518caa7cabd3dede389dad686932dbc477d40165de8b2d954d43973e5065046aa380f9cc0542e48ce3e7b926037642b" size="18349134" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="a220ac388f2974ee8c8f4162f6d3258ba3412875d1fd5bb0e7c52cf01fcbbcb548e2875753807c3b8a7feef4b01788940d7dd506343c3b817fbc7b419848db8b" size="109175061" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="529d6c756a630f146534ffef87ab538c0ac2534c8e4b44b7fac2e96c0032ccb31b24953c8443626e028d07cc005d8d31a86adefd9a0ad8a6dbfd93f7066697b1" size="18349162" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.7-13.0.9-windows-x86_64-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="d196e644f3a75bd877219c1e1e2fc070affdb312826696dc928e2fdec3d6bc513dfb7aac005b115192446a56ab188649accf3c89ec729855176e87af82c6915e" size="109340435" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="0cb6d22edfeb0af7830a1ea86ea83314e620b04dc74e4ae3216011c72e31121fd130b4ec3ccb6af89e4269ca048d60efdb4a885c7d53c35eace2a0ee010d0c04" size="17160296" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="071dc63319396dfe4a835d57646a4dcae73942d10a1b11f3967494d3a92e16664c9bc77127a5ffee357b7b32bc73ba3bf7665a40208f90e94093238e2a0e4bb2" size="109340463" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="2fe6e0d4b0618d7a41a4d5604b1934d303531229475d8f7f62dc3914e4e0846a5abbf4e0238685ce9ddd013a805ce431b6d362c512ae4f102cc30b666eb63a72" size="17160324" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.8-13.0.9-linux-i686-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686-13…" hashFunction="SHA512" hashValue="d36e7dd3a39de83fb9a71fd5a8b7262aca98bb97953287fe9a3ab90c0a95ad97c40d240834d702b57182b599efc21f92aa15b6b62f5e180f0617d8fb678e035e" size="121912046" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686--1…" hashFunction="SHA512" hashValue="6ca7d353bf303a2302ef050cadf092920248a2601c3d9322bd59403679305319e502b4c916ac5f9d52733dba07883b9d0a086087e5a9ab40be35a81044503060" size="11561086" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686-13…" hashFunction="SHA512" hashValue="09f66723dade529f77267546a8cab70bb71c8621bc7c67e7975ce67139c0f8b305125b4cf38b4a77cea4182c382a215013ac718dc6ee3561b3046266f2d234c8" size="121912074" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686--1…" hashFunction="SHA512" hashValue="45d8f7cc409b94494e3bc8c5c205df499207bd768ad3a73fe2750316e886fca9a13f519c1d06de149e2b346b7049c4d651b2403bc670a2659241ac5e0e305a52" size="11561126" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.8-13.0.9-linux-x86_64-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="4c26112f794839450194bac302923e4fe8f4f9d15568b638b876760ed064e21f1c1eae0262f8b68d628e4ea78ad0466b4e841b859dc6183da1caf9d622590597" size="121131338" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="a487a662396760fb53c492fea9b5420866b6be2f2aa7a02fa79d1069caab63d484174f05c0917d48c64e55dcc36f7e0c38e34c12bb6b9f90652d82d25d57b4b4" size="11431798" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="d4f4a4a2a403dc597e475cc335a83e42774ff571baac4e1385e19a8d199a6fa9cae1ac42ee8d082b0ec01fb5e34f02c5482cd2723203c8d282c67349e43f5be8" size="121131366" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="f472d50fbb16f21feb5f4a941af2fe22ac002b562b599a6c42f3e497a142bbf19388a7577475fddabfaf7eb05a02b65693b9a08857cda839d108f2cdbd568372" size="11431838" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.8-13.0.9-macos-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="16.0.0"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos-13.0.9_…" hashFunction="SHA512" hashValue="86aaf0b4deb8eab82620ceb4829783e652735174e1106e92981aeb2ad7c5eeb576fbef9114d75f038ff26ae67bdc3e74c042677d28e18174d5ac1447b9cff0fe" size="169201961" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos--13.0.8…" hashFunction="SHA512" hashValue="b31ee3228b074853fa531d53d09994aacb2d25e5e64e2da69befb06f7dfc191f43268c60cec6cbdf84b6b3624f75483f8f7e5d36d6b0532314bd84dd50ee0088" size="42830202" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="16.0.0"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos-13.0.9_…" hashFunction="SHA512" hashValue="b407a03608c5b816ab80cce5866d8126eabc68796cb022dafcb9b80f654aafe56eceb0f30477007f930ee8392c06817c5ce0873124cf325238ed6246eee6d870" size="169203413" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos--13.0.8…" hashFunction="SHA512" hashValue="0305411dfdc93affa9d052bc8772acf66b63f5d48b5a76511eebc2e8ad9e27baa273d711c7e2eb7822b1fcdba3308d8a7fccf7934a8d3f48555757c7f6afeea0" size="42822506" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.8-13.0.9-windows-i686-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="475cd3c2d31d174fbd30ac592da6d4b9257c61baf09832903bc07a5dac55089131738e3edbc2def7561c6477a2d3db78857249337496473be57c966b7ef809a7" size="109175033" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="4067411ab90cb78f4045874d7b90ad69637fe351feba220e00cd56025408f8d43fa1a682e8b7ecf32fe0b8313b82516dffb231065a3b94f8b4fc6f1806c75e89" size="13209426" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="a220ac388f2974ee8c8f4162f6d3258ba3412875d1fd5bb0e7c52cf01fcbbcb548e2875753807c3b8a7feef4b01788940d7dd506343c3b817fbc7b419848db8b" size="109175061" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="541a6cf4ddcbed4877e93b7721fffa05caeba48a12619fcdb784a1ea19c989efa0dfce25547500330bf54bb4a182df6dd5a5c3e48f8238ee16eb79d2475066ed" size="13209466" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.8-13.0.9-windows-x86_64-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="d196e644f3a75bd877219c1e1e2fc070affdb312826696dc928e2fdec3d6bc513dfb7aac005b115192446a56ab188649accf3c89ec729855176e87af82c6915e" size="109340435" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="df61d91b79ca5a77df1144be8dbb874d8d60cd30b87ea46ec4583294d5bc5e0694c049f6de6a740db5093fa98e6720f77b4918f49c5f8e69d0bbc13f5fb7260e" size="11908612" type="partial"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="071dc63319396dfe4a835d57646a4dcae73942d10a1b11f3967494d3a92e16664c9bc77127a5ffee357b7b32bc73ba3bf7665a40208f90e94093238e2a0e4bb2" size="109340463" type="complete"></patch><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="824c8d9a0d6d4b5741c6725ff15914c79c17d378c941d44e4e27e11557b148a1c62cd49376581ca74a22eb044f64978734974d1456a6a5cd34c0f1241707a9bd" size="11908652" type="partial"></patch></update></updates>
=====================================
update_3/release/13.0.9-linux-i686-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686-13…" hashFunction="SHA512" hashValue="d36e7dd3a39de83fb9a71fd5a8b7262aca98bb97953287fe9a3ab90c0a95ad97c40d240834d702b57182b599efc21f92aa15b6b62f5e180f0617d8fb678e035e" size="121912046" type="complete"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-i686-13…" hashFunction="SHA512" hashValue="09f66723dade529f77267546a8cab70bb71c8621bc7c67e7975ce67139c0f8b305125b4cf38b4a77cea4182c382a215013ac718dc6ee3561b3046266f2d234c8" size="121912074" type="complete"></patch></update></updates>
=====================================
update_3/release/13.0.9-linux-x86_64-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="4c26112f794839450194bac302923e4fe8f4f9d15568b638b876760ed064e21f1c1eae0262f8b68d628e4ea78ad0466b4e841b859dc6183da1caf9d622590597" size="121131338" type="complete"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-linux-x86_64-…" hashFunction="SHA512" hashValue="d4f4a4a2a403dc597e475cc335a83e42774ff571baac4e1385e19a8d199a6fa9cae1ac42ee8d082b0ec01fb5e34f02c5482cd2723203c8d282c67349e43f5be8" size="121131366" type="complete"></patch></update></updates>
=====================================
update_3/release/13.0.9-macos-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="16.0.0"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos-13.0.9_…" hashFunction="SHA512" hashValue="86aaf0b4deb8eab82620ceb4829783e652735174e1106e92981aeb2ad7c5eeb576fbef9114d75f038ff26ae67bdc3e74c042677d28e18174d5ac1447b9cff0fe" size="169201961" type="complete"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="16.0.0"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-macos-13.0.9_…" hashFunction="SHA512" hashValue="b407a03608c5b816ab80cce5866d8126eabc68796cb022dafcb9b80f654aafe56eceb0f30477007f930ee8392c06817c5ce0873124cf325238ed6246eee6d870" size="169203413" type="complete"></patch></update></updates>
=====================================
update_3/release/13.0.9-windows-i686-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="475cd3c2d31d174fbd30ac592da6d4b9257c61baf09832903bc07a5dac55089131738e3edbc2def7561c6477a2d3db78857249337496473be57c966b7ef809a7" size="109175033" type="complete"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-i686-…" hashFunction="SHA512" hashValue="a220ac388f2974ee8c8f4162f6d3258ba3412875d1fd5bb0e7c52cf01fcbbcb548e2875753807c3b8a7feef4b01788940d7dd506343c3b817fbc7b419848db8b" size="109175061" type="complete"></patch></update></updates>
=====================================
update_3/release/13.0.9-windows-x86_64-ALL.xml
=====================================
@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
-<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="d196e644f3a75bd877219c1e1e2fc070affdb312826696dc928e2fdec3d6bc513dfb7aac005b115192446a56ab188649accf3c89ec729855176e87af82c6915e" size="109340435" type="complete"></patch></update></updates>
+<updates><update type="minor" displayVersion="13.0.9" appVersion="13.0.9" platformVersion="115.7.0" buildID="20240115174022" detailsURL="https://blog.torproject.org/new-release-tor-browser-1309" actions="showURL" openURL="https://blog.torproject.org/new-release-tor-browser-1309" minSupportedOSVersion="6.1" minSupportedInstructionSet="SSE2"><patch URL="https://cdn.torproject.org/aus1/torbrowser/13.0.9/tor-browser-windows-x86_6…" hashFunction="SHA512" hashValue="071dc63319396dfe4a835d57646a4dcae73942d10a1b11f3967494d3a92e16664c9bc77127a5ffee357b7b32bc73ba3bf7665a40208f90e94093238e2a0e4bb2" size="109340463" type="complete"></patch></update></updates>
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-update-responses…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-update-responses…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build] Pushed new tag tbb-13.0.9-build2
by richard (@richard) 23 Jan '24
by richard (@richard) 23 Jan '24
23 Jan '24
richard pushed new tag tbb-13.0.9-build2 at The Tor Project / Applications / tor-browser-build
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/tree/tbb…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][maint-13.0] Bug 41055: Prepare Tor Browser Stable 13.0.9
by richard (@richard) 23 Jan '24
by richard (@richard) 23 Jan '24
23 Jan '24
richard pushed to branch maint-13.0 at The Tor Project / Applications / tor-browser-build
Commits:
e5f6056c by Richard Pospesel at 2024-01-23T10:58:37+00:00
Bug 41055: Prepare Tor Browser Stable 13.0.9
- add tor-browser-build#41066 to changelog
- - - - -
2 changed files:
- projects/browser/Bundle-Data/Docs-TBB/ChangeLog.txt
- rbm.conf
Changes:
=====================================
projects/browser/Bundle-Data/Docs-TBB/ChangeLog.txt
=====================================
@@ -28,6 +28,8 @@ Tor Browser 13.0.9 - January 23 2024
* Bug 41016: Switch from bullseye to bookworm for desktop platforms [tor-browser-build]
* Windows
* Bug 41015: Enable std::filesystem on libc++ on Windows [tor-browser-build]
+ * Android
+ * Bug 41066: The Android x86 APK for 13.0.9 is too big [tor-browser-build]
Tor Browser 13.5a3 - December 22 2023
* All Platforms
=====================================
rbm.conf
=====================================
@@ -82,7 +82,7 @@ buildconf:
var:
torbrowser_version: '13.0.9'
- torbrowser_build: 'build1'
+ torbrowser_build: 'build2'
torbrowser_incremental_from:
- '[% IF c("var/tor-browser") %]13.0.8[% END %]'
- '13.0.7'
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/e…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/e…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][maint-13.0] Bug 41066: Compress the APKs more
by richard (@richard) 23 Jan '24
by richard (@richard) 23 Jan '24
23 Jan '24
richard pushed to branch maint-13.0 at The Tor Project / Applications / tor-browser-build
Commits:
4ae6c26c by Pier Angelo Vendrame at 2024-01-23T10:48:57+01:00
Bug 41066: Compress the APKs more
Our APK was refused by the Play Store as too big.
As a workaround, for this release we can try to compress the APK more,
by extracting it and repacking it with 7-zip.
The preferred and long-term solution would be to switch to Android App
Bundles, but this might require some changes to our signing scripts.
- - - - -
2 changed files:
- projects/browser/build.android
- projects/browser/config
Changes:
=====================================
projects/browser/build.android
=====================================
@@ -31,13 +31,19 @@ mv $rootdir/[% c('input_files_by_name/noscript') %] "$noscript_path"
mv $rootdir/allowed_addons.json $assets_dir/allowed_addons.json
-[% c('zip', {
- zip_src => [ '$assets_dir' ],
- zip_args => '$apk',
- }) %]
+mkdir apk
+pushd apk
+7zz x "$apk"
+cp -R ../assets ./
+find -type f -exec touch -m -t '[% USE date; date.format(pc("firefox-android", "timestamp"), format = "%Y%m%d%H%M") %]' {} \;
+find -type f ! -name resources.arsc -printf '%P\n' | sort > ../files.txt
+7zz a -tzip -mx9 -mtc- -spf ../repacked.apk @../files.txt
+# resources.arsc must not be compressed as per the APK specifications
+7zz a -tzip -mm=Copy -mtc- ../repacked.apk resources.arsc
+popd
aligned_apk=$(basename $apk .apk)_aligned.apk
-zipalign -vp 4 $apk $aligned_apk
+zipalign -vp 4 repacked.apk $aligned_apk
# Sign a QA build. This .apk is not a debug version and doesn't contain a debug
# flag in the manifest.
=====================================
projects/browser/config
=====================================
@@ -46,7 +46,13 @@ targets:
var:
verify_allowed_addons: 1
arch_deps:
- - openjdk-11-jdk-headless
+ - 7zip
+ - openjdk-17-jdk-headless
+ container:
+ # 7zip is in backports in bullseye, and we can already use Java 17 for
+ # apksigner.
+ suite: bookworm
+ arch: amd64
torbrowser:
var:
prefs_file: 000-tor-browser.js
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/4…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/4…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/firefox-android][firefox-android-115.2.1-13.5-1] fixup! Enable the connect assist experiments on alpha
by Pier Angelo Vendrame (@pierov) 23 Jan '24
by Pier Angelo Vendrame (@pierov) 23 Jan '24
23 Jan '24
Pier Angelo Vendrame pushed to branch firefox-android-115.2.1-13.5-1 at The Tor Project / Applications / firefox-android
Commits:
47c80363 by clairehurst at 2024-01-22T12:30:48-07:00
fixup! Enable the connect assist experiments on alpha
- - - - -
1 changed file:
- fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
Changes:
=====================================
fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
=====================================
@@ -1213,19 +1213,22 @@ abstract class BaseBrowserFragment :
}
private fun handleBetaHtmlTorConnect() {
- if (getCurrentTab()?.content?.url == "about:torconnect") {
+ val currentTab = getCurrentTab() ?: return
+ if (currentTab.content.url == "about:torconnect") {
if (!requireActivity().settings().useNewBootstrap) {
- requireContext().components.useCases.tabsUseCases.removeAllTabs()
+ requireContext().components.useCases.tabsUseCases.removeTab(currentTab.id)
(requireActivity() as HomeActivity).navHost.navController.navigate(
NavGraphDirections.actionStartupTorbootstrap(),
)
} else if (!requireActivity().settings().useNewBootstrapHtmlUi) {
- requireContext().components.useCases.tabsUseCases.removeAllTabs()
+ requireContext().components.useCases.tabsUseCases.removeTab(currentTab.id)
(requireActivity() as HomeActivity).navigateToHome()
} else {
// This just makes it not flash (be visible for a split second) before handleTabSelected() hides it again
browserToolbarView.view.visibility = View.GONE
}
+ } else if (currentTab.content.url == "about:tor") {
+ requireContext().components.useCases.tabsUseCases.removeTab(currentTab.id)
}
}
View it on GitLab: https://gitlab.torproject.org/tpo/applications/firefox-android/-/commit/47c…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/firefox-android/-/commit/47c…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/mullvad-browser][mullvad-browser-115.7.0esr-13.5-1] Bug 1867408 - NSS backport
by ma1 (@ma1) 22 Jan '24
by ma1 (@ma1) 22 Jan '24
22 Jan '24
ma1 pushed to branch mullvad-browser-115.7.0esr-13.5-1 at The Tor Project / Applications / Mullvad Browser
Commits:
00f5c6d8 by hackademix at 2024-01-22T17:00:26+01:00
Bug 1867408 - NSS backport
- - - - -
1 changed file:
- security/nss/lib/ssl/sslsecur.c
Changes:
=====================================
security/nss/lib/ssl/sslsecur.c
=====================================
@@ -488,7 +488,12 @@ ssl_SendSavedWriteData(sslSocket *ss)
if (rv < 0) {
return rv;
}
- ss->pendingBuf.len -= rv;
+ if (rv > ss->pendingBuf.len) {
+ PORT_Assert(0); /* This shouldn't happen */
+ ss->pendingBuf.len = 0;
+ } else {
+ ss->pendingBuf.len -= rv;
+ }
if (ss->pendingBuf.len > 0 && rv > 0) {
/* UGH !! This shifts the whole buffer down by copying it */
PORT_Memmove(ss->pendingBuf.buf, ss->pendingBuf.buf + rv,
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/00f…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/00f…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/mullvad-browser][mullvad-browser-115.7.0esr-13.0-1] Bug 1867408 - NSS backport
by ma1 (@ma1) 22 Jan '24
by ma1 (@ma1) 22 Jan '24
22 Jan '24
ma1 pushed to branch mullvad-browser-115.7.0esr-13.0-1 at The Tor Project / Applications / Mullvad Browser
Commits:
e0af4349 by hackademix at 2024-01-22T16:57:26+01:00
Bug 1867408 - NSS backport
- - - - -
1 changed file:
- security/nss/lib/ssl/sslsecur.c
Changes:
=====================================
security/nss/lib/ssl/sslsecur.c
=====================================
@@ -488,7 +488,12 @@ ssl_SendSavedWriteData(sslSocket *ss)
if (rv < 0) {
return rv;
}
- ss->pendingBuf.len -= rv;
+ if (rv > ss->pendingBuf.len) {
+ PORT_Assert(0); /* This shouldn't happen */
+ ss->pendingBuf.len = 0;
+ } else {
+ ss->pendingBuf.len -= rv;
+ }
if (ss->pendingBuf.len > 0 && rv > 0) {
/* UGH !! This shifts the whole buffer down by copying it */
PORT_Memmove(ss->pendingBuf.buf, ss->pendingBuf.buf + rv,
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/e0a…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/e0a…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][base-browser-115.7.0esr-13.5-1] Bug 1867408 - NSS backport
by ma1 (@ma1) 22 Jan '24
by ma1 (@ma1) 22 Jan '24
22 Jan '24
ma1 pushed to branch base-browser-115.7.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits:
eed2de42 by hackademix at 2024-01-22T16:56:41+01:00
Bug 1867408 - NSS backport
- - - - -
1 changed file:
- security/nss/lib/ssl/sslsecur.c
Changes:
=====================================
security/nss/lib/ssl/sslsecur.c
=====================================
@@ -488,7 +488,12 @@ ssl_SendSavedWriteData(sslSocket *ss)
if (rv < 0) {
return rv;
}
- ss->pendingBuf.len -= rv;
+ if (rv > ss->pendingBuf.len) {
+ PORT_Assert(0); /* This shouldn't happen */
+ ss->pendingBuf.len = 0;
+ } else {
+ ss->pendingBuf.len -= rv;
+ }
if (ss->pendingBuf.len > 0 && rv > 0) {
/* UGH !! This shifts the whole buffer down by copying it */
PORT_Memmove(ss->pendingBuf.buf, ss->pendingBuf.buf + rv,
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/eed2de4…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/eed2de4…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][base-browser-115.7.0esr-13.0-1] Bug 1867408 - NSS backport
by ma1 (@ma1) 22 Jan '24
by ma1 (@ma1) 22 Jan '24
22 Jan '24
ma1 pushed to branch base-browser-115.7.0esr-13.0-1 at The Tor Project / Applications / Tor Browser
Commits:
6c906dba by hackademix at 2024-01-22T16:46:05+01:00
Bug 1867408 - NSS backport
- - - - -
1 changed file:
- security/nss/lib/ssl/sslsecur.c
Changes:
=====================================
security/nss/lib/ssl/sslsecur.c
=====================================
@@ -488,7 +488,12 @@ ssl_SendSavedWriteData(sslSocket *ss)
if (rv < 0) {
return rv;
}
- ss->pendingBuf.len -= rv;
+ if (rv > ss->pendingBuf.len) {
+ PORT_Assert(0); /* This shouldn't happen */
+ ss->pendingBuf.len = 0;
+ } else {
+ ss->pendingBuf.len -= rv;
+ }
if (ss->pendingBuf.len > 0 && rv > 0) {
/* UGH !! This shifts the whole buffer down by copying it */
PORT_Memmove(ss->pendingBuf.buf, ss->pendingBuf.buf + rv,
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/6c906db…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/6c906db…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-115.7.0esr-13.5-1] Bug 1867408 - NSS backport
by ma1 (@ma1) 22 Jan '24
by ma1 (@ma1) 22 Jan '24
22 Jan '24
ma1 pushed to branch tor-browser-115.7.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits:
cbeecf99 by hackademix at 2024-01-22T16:29:11+01:00
Bug 1867408 - NSS backport
- - - - -
1 changed file:
- security/nss/lib/ssl/sslsecur.c
Changes:
=====================================
security/nss/lib/ssl/sslsecur.c
=====================================
@@ -488,7 +488,12 @@ ssl_SendSavedWriteData(sslSocket *ss)
if (rv < 0) {
return rv;
}
- ss->pendingBuf.len -= rv;
+ if (rv > ss->pendingBuf.len) {
+ PORT_Assert(0); /* This shouldn't happen */
+ ss->pendingBuf.len = 0;
+ } else {
+ ss->pendingBuf.len -= rv;
+ }
if (ss->pendingBuf.len > 0 && rv > 0) {
/* UGH !! This shifts the whole buffer down by copying it */
PORT_Memmove(ss->pendingBuf.buf, ss->pendingBuf.buf + rv,
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/cbeecf9…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/cbeecf9…
You're receiving this email because of your account on gitlab.torproject.org.
1
0