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
- 18609 discussions

[Git][tpo/applications/torbrowser-launcher] Pushed new branch asciiwolf-rdns-desktop-files
by asciiwolf (@asciiwolf) 20 Mar '25
by asciiwolf (@asciiwolf) 20 Mar '25
20 Mar '25
asciiwolf pushed new branch asciiwolf-rdns-desktop-files at The Tor Project / Applications / torbrowser-launcher
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/torbrowser-launcher/-/tree/a…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][base-browser-128.8.0esr-14.5-1] fixup! BB 18905: Hide unwanted items from help menu
by morgan (@morgan) 20 Mar '25
by morgan (@morgan) 20 Mar '25
20 Mar '25
morgan pushed to branch base-browser-128.8.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits:
e0147508 by Pier Angelo Vendrame at 2025-03-20T13:22:29+00:00
fixup! BB 18905: Hide unwanted items from help menu
BB 41755: Show the link to about:support in the help menu.
- - - - -
2 changed files:
- browser/base/content/browser-menubar.inc
- toolkit/content/aboutSupport.js
Changes:
=====================================
browser/base/content/browser-menubar.inc
=====================================
@@ -481,7 +481,6 @@
appmenu-data-l10n-id="appmenu-help-enter-troubleshoot-mode2"/>
<menuitem id="troubleShooting"
oncommand="openTroubleshootingPage()"
- hidden="true"
data-l10n-id="menu-help-more-troubleshooting-info"
appmenu-data-l10n-id="appmenu-help-more-troubleshooting-info"/>
<menuitem id="help_reportSiteIssue"
=====================================
toolkit/content/aboutSupport.js
=====================================
@@ -1931,13 +1931,14 @@ function populateActionBox() {
if (ResetProfile.resetSupported()) {
$("reset-box").style.display = "block";
}
- if (!Services.appinfo.inSafeMode && AppConstants.platform !== "android") {
+ // tor-browser#41755: Do not show safe mode, as it would disable NoScript.
+ /*if (!Services.appinfo.inSafeMode && AppConstants.platform !== "android") {
$("safe-mode-box").style.display = "block";
if (Services.policies && !Services.policies.isAllowed("safeMode")) {
$("restart-in-safe-mode-button").setAttribute("disabled", "true");
}
- }
+ }*/
}
// Prompt user to restart the browser in safe mode
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/e014750…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/e014750…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-128.8.0esr-14.5-1] fixup! BB 18905: Hide unwanted items from help menu
by morgan (@morgan) 20 Mar '25
by morgan (@morgan) 20 Mar '25
20 Mar '25
morgan pushed to branch tor-browser-128.8.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits:
dca03874 by Pier Angelo Vendrame at 2025-03-20T11:48:58+01:00
fixup! BB 18905: Hide unwanted items from help menu
BB 41755: Show the link to about:support in the help menu.
- - - - -
2 changed files:
- browser/base/content/browser-menubar.inc
- toolkit/content/aboutSupport.js
Changes:
=====================================
browser/base/content/browser-menubar.inc
=====================================
@@ -489,7 +489,6 @@
appmenu-data-l10n-id="appmenu-help-enter-troubleshoot-mode2"/>
<menuitem id="troubleShooting"
oncommand="openTroubleshootingPage()"
- hidden="true"
data-l10n-id="menu-help-more-troubleshooting-info"
appmenu-data-l10n-id="appmenu-help-more-troubleshooting-info"/>
<menuitem id="help_reportSiteIssue"
=====================================
toolkit/content/aboutSupport.js
=====================================
@@ -1931,13 +1931,14 @@ function populateActionBox() {
if (ResetProfile.resetSupported()) {
$("reset-box").style.display = "block";
}
- if (!Services.appinfo.inSafeMode && AppConstants.platform !== "android") {
+ // tor-browser#41755: Do not show safe mode, as it would disable NoScript.
+ /*if (!Services.appinfo.inSafeMode && AppConstants.platform !== "android") {
$("safe-mode-box").style.display = "block";
if (Services.policies && !Services.policies.isAllowed("safeMode")) {
$("restart-in-safe-mode-button").setAttribute("disabled", "true");
}
- }
+ }*/
}
// Prompt user to restart the browser in safe mode
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/dca0387…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/dca0387…
You're receiving this email because of your account on gitlab.torproject.org.
1
0
boklm pushed to branch main at The Tor Project / Applications / RBM
Commits:
e8f3ffa4 by Pier Angelo Vendrame at 2025-03-20T12:00:08+01:00
Fix a typo in README.md.
- - - - -
1 changed file:
- README.md
Changes:
=====================================
README.md
=====================================
@@ -3,7 +3,7 @@ rbm: Reproducible Build Manager
rbm is a packaging tool that helps you create and build packages for
multiple Linux distributions, and automate the parts that can be
-automated. It includes options to run the build in a defined environement
+automated. It includes options to run the build in a defined environment
to allow reproducing the build.
For more information, visit the website :
View it on GitLab: https://gitlab.torproject.org/tpo/applications/rbm/-/commit/e8f3ffa48716b91…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/rbm/-/commit/e8f3ffa48716b91…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-128.8.0esr-14.5-1] 5 commits: fixup! [android] Implement Android-native Connection Assist UI
by Dan Ballard (@dan) 20 Mar '25
by Dan Ballard (@dan) 20 Mar '25
20 Mar '25
Dan Ballard pushed to branch tor-browser-128.8.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits:
52589797 by clairehurst at 2025-03-19T18:33:18-06:00
fixup! [android] Implement Android-native Connection Assist UI
Bug 41188: Wire up stages to UI
- - - - -
f0a7fed9 by clairehurst at 2025-03-19T18:33:18-06:00
fixup! TB 42247: Android helpers for the TorProvider
Bug 41188: Wire up stages to UI
- - - - -
9fe16689 by clairehurst at 2025-03-19T18:33:19-06:00
add to fixup! TB 42247: Android helpers for the TorProvider
- - - - -
c1ec88a7 by clairehurst at 2025-03-19T18:33:19-06:00
add to fixup! [android] Implement Android-native Connection Assist UI
- - - - -
293696bb by Dan Ballard at 2025-03-19T17:58:54-07:00
fixup! fixup! TB 42247: Android helpers for the TorProvider
- - - - -
12 changed files:
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/ConnectAssistUiState.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/QuickstartViewModel.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorBootstrapProgressViewModel.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistFragment.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistViewModel.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/UrlQuickLoadViewModel.kt
- mobile/android/fenix/app/src/main/res/layout/fragment_tor_connection_assist.xml
- mobile/android/fenix/app/src/main/res/values/colors.xml
- mobile/android/fenix/app/src/main/res/values/torbrowser_strings.xml
- mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorAndroidIntegration.java
- toolkit/modules/TorAndroidIntegration.sys.mjs
Changes:
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
=====================================
@@ -1111,6 +1111,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity, TorAn
.setAllCapsForActionButton(false)
.setAction(getString(R.string.connection_assist_connect_to_tor_before_opening_links_confirmation)) {
urlQuickLoadViewModel.urlToLoadAfterConnecting.value = searchTermOrURL
+ urlQuickLoadViewModel.maybeBeginBootstrap()
if (navHost.navController.previousBackStackEntry?.destination?.id == R.id.torConnectionAssistFragment) {
supportFragmentManager.popBackStack()
} else {
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/ConnectAssistUiState.kt
=====================================
@@ -2,14 +2,12 @@ package org.mozilla.fenix.tor
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
-import androidx.annotation.IntRange
import androidx.annotation.StringRes
import org.mozilla.fenix.R
enum class ConnectAssistUiState(
val progressBarVisible: Boolean,
- @IntRange(0, 100) var progress: Int = 0,
- @ColorRes val progressTintColorResource: Int? = null,
+ @ColorRes val progressBackgroundTintColorResource: Int = R.color.progress_background_tint,
val backButtonVisible: Boolean,
val settingsButtonVisible: Boolean,
val torConnectImageVisible: Boolean,
@@ -23,18 +21,19 @@ enum class ConnectAssistUiState(
@StringRes val internetErrorDescription2: Int? = null,
@StringRes val titleDescriptionTextStringResource: Int? = R.string.preferences_tor_network_settings_explanation,
val quickstartSwitchVisible: Boolean,
- val unblockTheInternetInCountryDescriptionVisible: Boolean,
val countryDropDownVisible: Boolean,
+ @StringRes val countryDropDownDefaultItem: Int = R.string.connection_assist_automatic_country_detection,
val torBootstrapButton1Visible: Boolean,
@StringRes val torBootstrapButton1TextStringResource: Int = R.string.tor_bootstrap_connect,
- val torBootstrapButton1ShouldShowTryingABridge: Boolean = false,
+ val torBootstrapButton1ShouldTryABridge: Boolean = false,
+ val torBootstrapButton1ShouldOpenSettings: Boolean = false,
val torBootstrapButton2Visible: Boolean,
@StringRes val torBootstrapButton2TextStringResource: Int? = R.string.connection_assist_configure_connection_button,
val torBootstrapButton2ShouldOpenSettings: Boolean = true,
val wordmarkLogoVisible: Boolean = false,
val torBootstrapButton2ShouldRestartApp: Boolean = false,
) {
- Splash(
+ Loading(
progressBarVisible = false,
backButtonVisible = false,
settingsButtonVisible = false,
@@ -42,15 +41,13 @@ enum class ConnectAssistUiState(
titleLargeTextViewVisible = false,
titleDescriptionVisible = false,
quickstartSwitchVisible = false,
- unblockTheInternetInCountryDescriptionVisible = false,
countryDropDownVisible = false,
torBootstrapButton1Visible = false,
torBootstrapButton2Visible = false,
wordmarkLogoVisible = true,
),
- Configuring(
+ Start(
progressBarVisible = false,
- progress = 0,
backButtonVisible = false,
settingsButtonVisible = true,
torConnectImageVisible = true,
@@ -60,16 +57,14 @@ enum class ConnectAssistUiState(
titleDescriptionVisible = true,
titleDescriptionTextStringResource = R.string.preferences_tor_network_settings_explanation,
quickstartSwitchVisible = true,
- unblockTheInternetInCountryDescriptionVisible = false,
countryDropDownVisible = false,
torBootstrapButton1Visible = true,
torBootstrapButton2Visible = true,
torBootstrapButton2TextStringResource = R.string.connection_assist_configure_connection_button,
torBootstrapButton2ShouldOpenSettings = true,
),
- Connecting(
+ Bootstrapping(
progressBarVisible = true,
- progress = 0,
backButtonVisible = false,
settingsButtonVisible = true,
torConnectImageVisible = true,
@@ -79,17 +74,15 @@ enum class ConnectAssistUiState(
titleDescriptionVisible = true,
titleDescriptionTextStringResource = R.string.preferences_tor_network_settings_explanation,
quickstartSwitchVisible = true,
- unblockTheInternetInCountryDescriptionVisible = false,
countryDropDownVisible = false,
torBootstrapButton1Visible = false,
torBootstrapButton2Visible = true,
torBootstrapButton2TextStringResource = R.string.btn_cancel,
torBootstrapButton2ShouldOpenSettings = false,
),
- InternetError(
+ Offline(
progressBarVisible = true,
- progress = 100,
- progressTintColorResource = R.color.warning_yellow,
+ progressBackgroundTintColorResource = R.color.warning_yellow,
backButtonVisible = true,
settingsButtonVisible = true,
torConnectImageVisible = true,
@@ -101,7 +94,6 @@ enum class ConnectAssistUiState(
internetErrorDescription = R.string.connection_assist_internet_error_description,
titleDescriptionTextStringResource = null,
quickstartSwitchVisible = false,
- unblockTheInternetInCountryDescriptionVisible = false,
countryDropDownVisible = false,
torBootstrapButton1Visible = true,
torBootstrapButton1TextStringResource = R.string.connection_assist_internet_error_try_again,
@@ -111,8 +103,6 @@ enum class ConnectAssistUiState(
),
TryingAgain(
progressBarVisible = true,
- progress = 0,
- progressTintColorResource = null,
backButtonVisible = true,
settingsButtonVisible = true,
torConnectImageVisible = true,
@@ -124,17 +114,15 @@ enum class ConnectAssistUiState(
internetErrorDescription = R.string.connection_assist_internet_error_description,
titleDescriptionTextStringResource = null,
quickstartSwitchVisible = false,
- unblockTheInternetInCountryDescriptionVisible = false,
countryDropDownVisible = false,
torBootstrapButton1Visible = false,
torBootstrapButton2Visible = true,
torBootstrapButton2TextStringResource = R.string.btn_cancel,
torBootstrapButton2ShouldOpenSettings = false,
),
- ConnectionAssist(
+ ChooseRegion(
progressBarVisible = true,
- progress = 100,
- progressTintColorResource = R.color.warning_yellow,
+ progressBackgroundTintColorResource = R.color.warning_yellow,
backButtonVisible = true,
settingsButtonVisible = true,
torConnectImageVisible = true,
@@ -146,19 +134,16 @@ enum class ConnectAssistUiState(
internetErrorDescription = R.string.connection_assist_try_a_bridge_description,
titleDescriptionTextStringResource = null,
quickstartSwitchVisible = false,
- unblockTheInternetInCountryDescriptionVisible = true,
countryDropDownVisible = true,
torBootstrapButton1Visible = true,
torBootstrapButton1TextStringResource = R.string.connection_assist_try_a_bridge_button,
- torBootstrapButton1ShouldShowTryingABridge = true,
+ torBootstrapButton1ShouldTryABridge = true,
torBootstrapButton2Visible = false,
torBootstrapButton2TextStringResource = null,
torBootstrapButton2ShouldOpenSettings = true,
),
TryingABridge(
progressBarVisible = true,
- progress = 0,
- progressTintColorResource = null,
backButtonVisible = true,
settingsButtonVisible = true,
torConnectImageVisible = true,
@@ -167,20 +152,18 @@ enum class ConnectAssistUiState(
titleLargeTextViewTextStringResource = R.string.connection_assist_trying_a_bridge_title,
titleDescriptionVisible = true,
learnMoreStringResource = R.string.connection_assist_internet_error_learn_more,
- internetErrorDescription = R.string.connection_assist_try_a_bridge_description,
+ internetErrorDescription = ChooseRegion.internetErrorDescription,
titleDescriptionTextStringResource = null,
quickstartSwitchVisible = true,
- unblockTheInternetInCountryDescriptionVisible = false,
countryDropDownVisible = false,
torBootstrapButton1Visible = false,
torBootstrapButton2Visible = true,
torBootstrapButton2TextStringResource = R.string.btn_cancel,
torBootstrapButton2ShouldOpenSettings = false,
),
- LocationError(
+ RegionNotFound(
progressBarVisible = true,
- progress = 100,
- progressTintColorResource = R.color.warning_yellow,
+ progressBackgroundTintColorResource = R.color.warning_yellow,
backButtonVisible = true,
settingsButtonVisible = true,
torConnectImageVisible = true,
@@ -194,19 +177,39 @@ enum class ConnectAssistUiState(
internetErrorDescription2 = R.string.connection_assist_select_country_try_again,
titleDescriptionTextStringResource = null,
quickstartSwitchVisible = false,
- unblockTheInternetInCountryDescriptionVisible = true,
countryDropDownVisible = true,
+ countryDropDownDefaultItem = R.string.connection_assist_select_country_or_region,
torBootstrapButton1Visible = true,
torBootstrapButton1TextStringResource = R.string.connection_assist_try_a_bridge_button,
- torBootstrapButton1ShouldShowTryingABridge = true,
+ torBootstrapButton1ShouldTryABridge = true,
torBootstrapButton2Visible = false,
torBootstrapButton2TextStringResource = null,
torBootstrapButton2ShouldOpenSettings = true,
),
- LocationCheck(
+ TryingABridgeRegionNotFound(
+ progressBarVisible = true,
+ backButtonVisible = true,
+ settingsButtonVisible = true,
+ torConnectImageVisible = true,
+ torConnectImageResource = R.drawable.connect,
+ titleLargeTextViewVisible = true,
+ titleLargeTextViewTextStringResource = R.string.connection_assist_trying_a_bridge_title,
+ titleDescriptionVisible = true,
+ learnMoreStringResource = R.string.connection_assist_internet_error_learn_more,
+ internetErrorDescription = RegionNotFound.internetErrorDescription,
+ internetErrorDescription1 = RegionNotFound.internetErrorDescription1,
+ internetErrorDescription2 = RegionNotFound.internetErrorDescription2,
+ titleDescriptionTextStringResource = null,
+ quickstartSwitchVisible = true,
+ countryDropDownVisible = false,
+ torBootstrapButton1Visible = false,
+ torBootstrapButton2Visible = true,
+ torBootstrapButton2TextStringResource = R.string.btn_cancel,
+ torBootstrapButton2ShouldOpenSettings = false,
+ ),
+ ConfirmRegion(
progressBarVisible = true,
- progress = 100,
- progressTintColorResource = R.color.warning_yellow,
+ progressBackgroundTintColorResource = R.color.warning_yellow,
backButtonVisible = true,
settingsButtonVisible = true,
torConnectImageVisible = true,
@@ -220,18 +223,38 @@ enum class ConnectAssistUiState(
internetErrorDescription2 = R.string.connection_assist_select_country_try_again,
titleDescriptionTextStringResource = null,
quickstartSwitchVisible = false,
- unblockTheInternetInCountryDescriptionVisible = true,
countryDropDownVisible = true,
+ countryDropDownDefaultItem = R.string.connection_assist_select_country_or_region,
torBootstrapButton1Visible = true,
torBootstrapButton1TextStringResource = R.string.connection_assist_try_a_bridge_button,
- torBootstrapButton1ShouldShowTryingABridge = true,
+ torBootstrapButton1ShouldTryABridge = true,
torBootstrapButton2Visible = false,
torBootstrapButton2TextStringResource = null,
torBootstrapButton2ShouldOpenSettings = true,
),
+ TryingABridgeConfirmRegion(
+ progressBarVisible = true,
+ backButtonVisible = true,
+ settingsButtonVisible = true,
+ torConnectImageVisible = true,
+ torConnectImageResource = R.drawable.connect,
+ titleLargeTextViewVisible = true,
+ titleLargeTextViewTextStringResource = R.string.connection_assist_trying_a_bridge_title,
+ titleDescriptionVisible = true,
+ learnMoreStringResource = R.string.connection_assist_internet_error_learn_more,
+ internetErrorDescription = ConfirmRegion.internetErrorDescription,
+ internetErrorDescription1 = ConfirmRegion.internetErrorDescription1,
+ internetErrorDescription2 = ConfirmRegion.internetErrorDescription2,
+ titleDescriptionTextStringResource = null,
+ quickstartSwitchVisible = true,
+ countryDropDownVisible = false,
+ torBootstrapButton1Visible = false,
+ torBootstrapButton2Visible = true,
+ torBootstrapButton2TextStringResource = R.string.btn_cancel,
+ torBootstrapButton2ShouldOpenSettings = false,
+ ),
LastTry(
progressBarVisible = true,
- progress = 0,
backButtonVisible = true,
settingsButtonVisible = true,
torConnectImageVisible = true,
@@ -245,7 +268,6 @@ enum class ConnectAssistUiState(
internetErrorDescription2 = R.string.connection_assist_select_country_try_again,
titleDescriptionTextStringResource = null,
quickstartSwitchVisible = true,
- unblockTheInternetInCountryDescriptionVisible = false,
countryDropDownVisible = false,
torBootstrapButton1Visible = false,
torBootstrapButton2Visible = true,
@@ -254,8 +276,7 @@ enum class ConnectAssistUiState(
),
FinalError(
progressBarVisible = true,
- progress = 100,
- progressTintColorResource = R.color.warning_yellow,
+ progressBackgroundTintColorResource = R.color.warning_yellow,
backButtonVisible = true,
settingsButtonVisible = true,
torConnectImageVisible = true,
@@ -268,10 +289,10 @@ enum class ConnectAssistUiState(
internetErrorDescription1 = R.string.connection_assist_final_error_troubleshoot_connection_link,
titleDescriptionTextStringResource = null,
quickstartSwitchVisible = false,
- unblockTheInternetInCountryDescriptionVisible = false,
countryDropDownVisible = false,
torBootstrapButton1Visible = true,
torBootstrapButton1TextStringResource = R.string.connection_assist_configure_connection_button,
+ torBootstrapButton1ShouldOpenSettings = true,
torBootstrapButton2Visible = true,
torBootstrapButton2TextStringResource = R.string.mozac_lib_crash_dialog_button_restart,
torBootstrapButton2ShouldOpenSettings = false,
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/QuickstartViewModel.kt
=====================================
@@ -12,19 +12,19 @@ class QuickstartViewModel(
) : AndroidViewModel(application) {
private val components = getApplication<Application>().components
- private val torIntegrationAndroid =
+ private val torAndroidIntegration =
(components.core.engine as GeckoEngine).getTorIntegrationController()
/**
* NOTE: Whilst the initial value for _quickstart is fetched from
- * TorIntegrationAndroid.quickstartGet (which is surfaced from TorConnect.quickstart), and we
+ * TorAndroidIntegration.quickstartGet (which is surfaced from TorConnect.quickstart), and we
* pass on any changes in value up to TorConnect.quickstart (via quickstartSet()), we do not
* listen for any changes to the TorConnect.quickstart value via "QuickstartChange" because we
* do not expect anything outside of TorConnectViewModel to change its value, so we expect its
* value to remain in sync with our local value.
*/
init {
- torIntegrationAndroid.quickstartGet {
+ torAndroidIntegration.quickstartGet {
_quickstart.value = it
components.settings.quickStart = it
}
@@ -36,7 +36,7 @@ class QuickstartViewModel(
}
fun quickstartSet(value: Boolean) {
- torIntegrationAndroid.quickstartSet(value)
+ torAndroidIntegration.quickstartSet(value)
_quickstart.value = value
components.settings.quickStart = value
}
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorBootstrapProgressViewModel.kt
=====================================
@@ -11,7 +11,7 @@ class TorBootstrapProgressViewModel(
application: Application,
) : AndroidViewModel(application), BootstrapStateChangeListener {
- private val torIntegrationAndroid =
+ private val torAndroidIntegration =
application.components.core.geckoRuntime.torIntegrationController
val progress: MutableLiveData<Int> by lazy {
@@ -19,11 +19,11 @@ class TorBootstrapProgressViewModel(
}
init {
- torIntegrationAndroid.registerBootstrapStateChangeListener(this)
+ torAndroidIntegration.registerBootstrapStateChangeListener(this)
}
override fun onCleared() {
- torIntegrationAndroid.unregisterBootstrapStateChangeListener(this)
+ torAndroidIntegration.unregisterBootstrapStateChangeListener(this)
super.onCleared()
}
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistFragment.kt
=====================================
@@ -4,6 +4,8 @@
package org.mozilla.fenix.tor
+import android.content.BroadcastReceiver
+import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Build
@@ -17,7 +19,10 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
import androidx.appcompat.content.res.AppCompatResources
+import androidx.core.view.isEmpty
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
@@ -38,7 +43,6 @@ class TorConnectionAssistFragment : Fragment(), UserInteractionHandler {
private val progressViewModel: TorBootstrapProgressViewModel by viewModels()
private val quickstartViewModel: QuickstartViewModel by activityViewModels()
private val torConnectionAssistViewModel : TorConnectionAssistViewModel by viewModels()
- private val urlQuickLoadViewModel : UrlQuickLoadViewModel by activityViewModels()
private var _binding: FragmentTorConnectionAssistBinding? = null
private val binding get() = _binding!!
@@ -52,9 +56,18 @@ class TorConnectionAssistFragment : Fragment(), UserInteractionHandler {
inflater, container, false,
)
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action === Intent.ACTION_LOCALE_CHANGED) {
+ Log.v("LocaleReceiver", "received ACTION_LOCALE_CHANGED")
+ torConnectionAssistViewModel.fetchCountryNamesGet()
+ }
+ }
+ }
+
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
- torConnectionAssistViewModel.collectLastKnownStatus()
+ torConnectionAssistViewModel.collectTorConnectStage()
}
}
@@ -65,12 +78,6 @@ class TorConnectionAssistFragment : Fragment(), UserInteractionHandler {
}
}
- urlQuickLoadViewModel.urlToLoadAfterConnecting.observe(viewLifecycleOwner) { url ->
- if (!url.isNullOrBlank()) {
- torConnectionAssistViewModel.handleConnect()
- }
- }
-
return binding.root
}
@@ -129,14 +136,10 @@ class TorConnectionAssistFragment : Fragment(), UserInteractionHandler {
private fun setProgressBar(screen: ConnectAssistUiState) {
binding.torBootstrapProgressBar.visibility =
if (screen.progressBarVisible) View.VISIBLE else View.GONE
- binding.torBootstrapProgressBar.progress = screen.progress
- binding.torBootstrapProgressBar.progressTintList =
- screen.progressTintColorResource?.let {
- AppCompatResources.getColorStateList(
- requireContext(),
- it,
- )
- }
+ binding.torBootstrapProgressBar.progressBackgroundTintList = AppCompatResources.getColorStateList(
+ requireContext(),
+ screen.progressBackgroundTintColorResource,
+ )
}
private fun setSettingsButton(screen: ConnectAssistUiState) {
@@ -201,17 +204,126 @@ class TorConnectionAssistFragment : Fragment(), UserInteractionHandler {
}
private fun setCountryDropDown(screen: ConnectAssistUiState) {
- binding.unblockTheInternetInCountryDescription.visibility =
- if (screen.unblockTheInternetInCountryDescriptionVisible) View.VISIBLE else View.GONE
- binding.countryDropDown.visibility = if (screen.countryDropDownVisible) View.VISIBLE else View.GONE
+ if (screen.countryDropDownVisible) {
+ val spinnerAdapter: ArrayAdapter<String> = initializeSpinner()
+ if (binding.countryDropDown.isEmpty()) {
+ populateCountryDropDown(spinnerAdapter)
+ setOnItemSelectedListener()
+ }
+
+ setFirstItemInCountryDropDown(spinnerAdapter, getString(screen.countryDropDownDefaultItem))
+
+ if (screen == ConnectAssistUiState.ChooseRegion || screen == ConnectAssistUiState.ConfirmRegion || screen == ConnectAssistUiState.RegionNotFound) {
+ torConnectionAssistViewModel.selectDefaultRegion()
+ binding.countryDropDown.setSelection(spinnerAdapter.getPosition(torConnectionAssistViewModel.selectedCountryCode.value))
+ }
+
+ binding.unblockTheInternetInCountryDescription.visibility = View.VISIBLE
+ binding.countryDropDown.visibility = View.VISIBLE
+ } else {
+ binding.unblockTheInternetInCountryDescription.visibility = View.GONE
+ binding.countryDropDown.visibility = View.GONE
+ }
+ }
+
+ private fun setFirstItemInCountryDropDown(
+ spinnerAdapter: ArrayAdapter<String>,
+ item: String,
+ ) {
+ if (!spinnerAdapter.isEmpty) {
+ spinnerAdapter.remove(spinnerAdapter.getItem(0))
+ }
+ spinnerAdapter.insert(item, 0)
+ }
+
+ private fun initializeSpinner(): ArrayAdapter<String> {
+ val spinnerAdapter: ArrayAdapter<String> =
+ ArrayAdapter<String>(
+ requireContext(),
+ android.R.layout.simple_spinner_item,
+ android.R.id.text1,
+ )
+ spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ binding.countryDropDown.adapter = spinnerAdapter
+ return spinnerAdapter
+ }
+
+ private fun populateCountryDropDown(spinnerAdapter: ArrayAdapter<String>) {
+ torConnectionAssistViewModel.fetchCountryNamesGet()
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ torConnectionAssistViewModel.countryCodeNameMap.collect {
+ Log.d(TAG, "countryCodeNameMap: $it")
+ if (it != null) {
+ spinnerAdapter.clear()
+ spinnerAdapter.add(getString(torConnectionAssistViewModel.torConnectScreen.value.countryDropDownDefaultItem))
+ spinnerAdapter.addAll(it.values)
+ }
+ }
+ }
+ }
+ }
+
+ private fun setOnItemSelectedListener() {
+ binding.countryDropDown.onItemSelectedListener =
+ object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(
+ parent: AdapterView<*>?,
+ view: View?,
+ position: Int,
+ id: Long,
+ ) {
+ torConnectionAssistViewModel.setCountryCodeToSelectedItem(position)
+ updateButton1(torConnectionAssistViewModel.torConnectScreen.value)
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>?) {}
+ }
}
private fun setButton1(screen: ConnectAssistUiState) {
- binding.torBootstrapButton1.visibility =
- if (screen.torBootstrapButton1Visible) View.VISIBLE else View.GONE
- binding.torBootstrapButton1.text = getString(screen.torBootstrapButton1TextStringResource)
- binding.torBootstrapButton1.setOnClickListener {
- torConnectionAssistViewModel.handleConnect()
+ binding.torBootstrapButton1.apply {
+ visibility =
+ if (screen.torBootstrapButton1Visible) View.VISIBLE else View.GONE
+ text = getString(screen.torBootstrapButton1TextStringResource)
+ setOnClickListener {
+ if (screen.torBootstrapButton1ShouldOpenSettings) {
+ openTorConnectionSettings()
+ } else {
+ torConnectionAssistViewModel.handleConnect()
+ }
+ }
+ updateButton1(screen)
+ }
+ }
+
+ private fun updateButton1(screen: ConnectAssistUiState) {
+ binding.torBootstrapButton1.apply {
+ if (!torConnectionAssistViewModel.button1ShouldBeDisabled(screen)) {
+ isEnabled = true
+ backgroundTintList = AppCompatResources.getColorStateList(
+ requireContext(),
+ R.color.connect_button_purple,
+ )
+ setTextColor(
+ AppCompatResources.getColorStateList(
+ requireContext(),
+ R.color.photonLightGrey05,
+ ),
+ )
+ } else {
+ isEnabled = false
+ backgroundTintList = AppCompatResources.getColorStateList(
+ requireContext(),
+ R.color.disabled_connect_button_purple,
+ )
+ setTextColor(
+ AppCompatResources.getColorStateList(
+ requireContext(),
+ R.color.disabled_text_gray_purple,
+ ),
+ )
+ }
}
}
@@ -235,13 +347,12 @@ class TorConnectionAssistFragment : Fragment(), UserInteractionHandler {
}
}
binding.torBootstrapButton2.setOnClickListener {
- torConnectionAssistViewModel.cancelTorBootstrap()
if (screen.torBootstrapButton2ShouldOpenSettings) {
openTorConnectionSettings()
} else if (screen.torBootstrapButton2ShouldRestartApp) {
restartApplication()
} else {
- showScreen(ConnectAssistUiState.Configuring)
+ torConnectionAssistViewModel.cancelTorBootstrap()
}
}
}
@@ -297,11 +408,7 @@ class TorConnectionAssistFragment : Fragment(), UserInteractionHandler {
}
private fun openTorConnectionSettings() {
- findNavController().navigate(
- TorConnectionAssistFragmentDirections.actionTorConnectionAssistFragmentToSettingsFragment(
- requireContext().getString(R.string.pref_key_connection)
- ),
- )
+ openSettings(requireContext().getString(R.string.pref_key_connection))
}
private fun restartApplication() {
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorConnectionAssistViewModel.kt
=====================================
@@ -11,186 +11,148 @@ import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.mozilla.fenix.HomeActivity
+import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
+import org.mozilla.gecko.util.GeckoBundle
+import org.mozilla.geckoview.TorAndroidIntegration.BootstrapStateChangeListener
+import org.mozilla.geckoview.TorConnectStage
+import org.mozilla.geckoview.TorConnectStageName
class TorConnectionAssistViewModel(
application: Application,
-) : AndroidViewModel(application) {
+) : AndroidViewModel(application), BootstrapStateChangeListener {
private val TAG = "torConnectionAssistVM"
- private val torIntegrationAndroid =
+ private val torAndroidIntegration =
application.components.core.geckoRuntime.torIntegrationController
- private val _torController: TorControllerGV = application.components.torController
- private val _torConnectScreen = MutableStateFlow(ConnectAssistUiState.Splash)
- internal val torConnectScreen: StateFlow<ConnectAssistUiState> = _torConnectScreen
-
- val shouldOpenHome: MutableLiveData<Boolean> by lazy {
- MutableLiveData(false)
+ init {
+ torAndroidIntegration.registerBootstrapStateChangeListener(this)
}
- fun handleConnect() {
- if (_torConnectScreen.value.torBootstrapButton1ShouldShowTryingABridge) {
- tryABridge()
- } else {
- if (_torController.lastKnownStatus.value.isOff()) {
- torIntegrationAndroid.beginBootstrap()
+ fun fetchCountryNamesGet() {
+ torAndroidIntegration.countryNamesGet { countryNames : GeckoBundle? ->
+ if (countryNames != null) {
+ val codes: Array<String> = countryNames.keys()
+ val regions = mutableMapOf<String, String>()
+ for (code in codes) {
+ regions[code] = countryNames.getString(code)
+ }
+ countryCodeNameMap.value = regions
}
}
}
- fun cancelTorBootstrap() {
- torIntegrationAndroid.cancelBootstrap()
- _torController.setTorStopped()
- }
-
- suspend fun collectLastKnownStatus() {
- _torController.lastKnownStatus.collect {
- when (it) {
- TorConnectState.Initial -> _torConnectScreen.value = ConnectAssistUiState.Splash
- TorConnectState.Configuring -> handleConfiguring()
- TorConnectState.AutoBootstrapping -> handleBootstrap()
- TorConnectState.Bootstrapping -> handleBootstrap()
- TorConnectState.Bootstrapped -> shouldOpenHome.value = true
- TorConnectState.Disabled -> shouldOpenHome.value = true
- TorConnectState.Error -> handleError()
- }
- }
+ override fun onCleared() {
+ torAndroidIntegration.unregisterBootstrapStateChangeListener(this)
+ super.onCleared()
}
- private fun handleConfiguring() {
- if (_torController.lastKnownError == null) {
- _torConnectScreen.value = ConnectAssistUiState.Configuring
- } else {
- handleError()
- }
+ private val torConnectStage: MutableStateFlow<TorConnectStage?> by lazy {
+ MutableStateFlow(torAndroidIntegration.lastKnowStage.value)
}
- private fun handleBootstrap() {
- when (_torConnectScreen.value) {
- ConnectAssistUiState.InternetError -> {
- _torConnectScreen.value = ConnectAssistUiState.TryingAgain
- }
-
- ConnectAssistUiState.TryingAgain -> {
- /** stay here */
- }
-
- ConnectAssistUiState.ConnectionAssist -> {
- _torConnectScreen.value = ConnectAssistUiState.TryingABridge
- }
-
- ConnectAssistUiState.LocationError -> {
- _torConnectScreen.value = ConnectAssistUiState.TryingABridge
- }
+ private val _torConnectScreen = MutableStateFlow(ConnectAssistUiState.Loading)
+ internal val torConnectScreen: StateFlow<ConnectAssistUiState> = _torConnectScreen
- ConnectAssistUiState.TryingABridge -> {
- /** stay here */
- }
+ val countryCodeNameMap: MutableStateFlow<Map<String, String>?> by lazy {
+ MutableStateFlow(null)
+ }
- ConnectAssistUiState.LocationCheck -> {
- _torConnectScreen.value = ConnectAssistUiState.LastTry
- }
+ val selectedCountryCode: MutableStateFlow<String> by lazy {
+ MutableStateFlow("automatic")
+ }
- ConnectAssistUiState.LastTry -> {
- /** stay here */
- }
+ fun selectDefaultRegion() {
+ selectedCountryCode.value = torConnectStage.value?.defaultRegion ?: "automatic"
+ }
- else -> _torConnectScreen.value =
- ConnectAssistUiState.Connecting
- }
+ fun setCountryCodeToSelectedItem(position: Int) {
+ selectedCountryCode.value =
+ countryCodeNameMap.value?.keys?.toList()
+ ?.getOrNull(position - 1) ?: "automatic"
+ // position - 1 since we have the default/first value of automatic
+ Log.d(TAG, "selectedCountryCode = ${selectedCountryCode.value}")
}
- private fun handleError() {
- _torController.lastKnownError?.apply {
- Log.d(
- TAG,
- "TorError(message = $message, details = $details, phase = $phase, reason = $reason",
- )
- // TODO better error handling
- when (reason) {
-// "noroute" -> handleNoRoute() TODO re-add when working better
- else -> handleUnknownError()
- }
- }
+ val shouldOpenHome: MutableLiveData<Boolean> by lazy {
+ MutableLiveData(false)
}
- private fun handleNoRoute() {
- Log.d(TAG, "handleNoRoute(), _torConnectScreen.value = ${_torConnectScreen.value}")
- when (_torConnectScreen.value) {
- ConnectAssistUiState.Connecting -> _torConnectScreen.value = ConnectAssistUiState.ConnectionAssist
- ConnectAssistUiState.ConnectionAssist -> {/** no op, likely a duplicate error */}
- ConnectAssistUiState.TryingABridge -> _torConnectScreen.value = ConnectAssistUiState.LocationCheck
- ConnectAssistUiState.LocationCheck -> {/** no op, likely a duplicate error */}
- ConnectAssistUiState.LastTry -> _torConnectScreen.value = ConnectAssistUiState.FinalError
- ConnectAssistUiState.FinalError -> {/** no op, likely a duplicate error */}
- else -> _torConnectScreen.value = ConnectAssistUiState.InternetError
+ fun handleConnect() {
+ val screen = _torConnectScreen.value
+ if (screen.torBootstrapButton1ShouldTryABridge && !button1ShouldBeDisabled(screen)) {
+ Log.d(TAG, "beginAutoBootstrap with countryCode: ${selectedCountryCode.value}")
+ torAndroidIntegration.beginAutoBootstrap(selectedCountryCode.value)
+ } else {
+ torAndroidIntegration.beginBootstrap()
}
}
- private fun handleUnknownError() {
- // TODO should we have a dedicated screen for unknown errors?
- _torConnectScreen.value = ConnectAssistUiState.InternetError
+ fun cancelTorBootstrap() {
+ torAndroidIntegration.cancelBootstrap()
}
- private fun tryABridge() {
- if (!locationFound()) {
- _torConnectScreen.value = ConnectAssistUiState.LocationError
- return
- }
- if (!_torController.bridgesEnabled) {
- _torController.bridgesEnabled = true
- _torController.bridgeTransport =
- TorBridgeTransportConfig.BUILTIN_SNOWFLAKE // TODO select based on country
+ suspend fun collectTorConnectStage() {
+ torConnectStage.collect {
+ Log.d(TAG, "torConnectStageName: ${it?.name}")
+ when (it?.name) {
+ TorConnectStageName.Disabled -> shouldOpenHome.value = true // TODO use TorConnect.enabled instead to determine this
+ TorConnectStageName.Loading -> _torConnectScreen.value = ConnectAssistUiState.Loading
+ TorConnectStageName.Start -> _torConnectScreen.value = ConnectAssistUiState.Start
+ TorConnectStageName.Bootstrapping -> _torConnectScreen.value = handleBootstrapTrigger(it.bootstrapTrigger)
+ TorConnectStageName.Offline -> _torConnectScreen.value = ConnectAssistUiState.Offline
+ TorConnectStageName.ChooseRegion -> _torConnectScreen.value = ConnectAssistUiState.ChooseRegion
+ TorConnectStageName.RegionNotFound -> _torConnectScreen.value = ConnectAssistUiState.RegionNotFound
+ TorConnectStageName.ConfirmRegion -> _torConnectScreen.value = ConnectAssistUiState.ConfirmRegion
+ TorConnectStageName.FinalError -> _torConnectScreen.value = ConnectAssistUiState.FinalError
+ TorConnectStageName.Bootstrapped -> shouldOpenHome.value = true
+ null -> {}
+ }
}
- torIntegrationAndroid.beginBootstrap()
}
- private fun locationFound(): Boolean {
- // TODO try to find location
- return true
+ private fun handleBootstrapTrigger(bootstrapTrigger: TorConnectStageName) : ConnectAssistUiState {
+ Log.d(TAG, "bootstrapTrigger: $bootstrapTrigger")
+ return when (bootstrapTrigger) {
+ TorConnectStageName.Start -> ConnectAssistUiState.Bootstrapping
+ TorConnectStageName.Offline -> ConnectAssistUiState.TryingAgain
+ TorConnectStageName.ChooseRegion -> ConnectAssistUiState.TryingABridge
+ TorConnectStageName.RegionNotFound -> ConnectAssistUiState.TryingABridgeRegionNotFound
+ TorConnectStageName.ConfirmRegion -> ConnectAssistUiState.TryingABridgeConfirmRegion
+ else -> {
+ Log.e(TAG, "Unexpected bootstrapTrigger of $bootstrapTrigger")
+ ConnectAssistUiState.TryingAgain
+ }
+ }
}
fun handleBackButtonPressed(homeActivity: HomeActivity) {
when (torConnectScreen.value) {
- ConnectAssistUiState.Splash -> homeActivity.shutDown()
- ConnectAssistUiState.Configuring -> homeActivity.shutDown()
- ConnectAssistUiState.Connecting -> cancelTorBootstrap()
- ConnectAssistUiState.InternetError -> {
- _torController.lastKnownError = null
- _torConnectScreen.value = ConnectAssistUiState.Configuring
- }
-
- ConnectAssistUiState.TryingAgain -> {
- cancelTorBootstrap()
- }
+ ConnectAssistUiState.Loading -> homeActivity.shutDown()
+ ConnectAssistUiState.Start -> homeActivity.shutDown()
+ else -> torAndroidIntegration.startAgain()
+ }
+ }
- ConnectAssistUiState.ConnectionAssist -> {
- _torController.lastKnownError = null
- _torConnectScreen.value = ConnectAssistUiState.Configuring
- }
+ override fun onBootstrapStateChange(state: String?) {}
- ConnectAssistUiState.TryingABridge -> {
- _torController.stopTor()
- _torConnectScreen.value = ConnectAssistUiState.ConnectionAssist
- }
+ override fun onBootstrapStageChange(stage: TorConnectStage?) {
+ torConnectStage.value = stage
+ }
- ConnectAssistUiState.LocationError -> {
- _torConnectScreen.value = ConnectAssistUiState.ConnectionAssist
- }
+ override fun onBootstrapProgress(progress: Double, hasWarnings: Boolean) {}
- ConnectAssistUiState.LocationCheck -> {
- _torConnectScreen.value = ConnectAssistUiState.LocationError
- }
+ override fun onBootstrapComplete() {}
- ConnectAssistUiState.LastTry -> {
- _torController.stopTor()
- _torConnectScreen.value = ConnectAssistUiState.LocationCheck
- }
+ override fun onBootstrapError(
+ code: String?,
+ message: String?,
+ phase: String?,
+ reason: String?,
+ ) {}
- ConnectAssistUiState.FinalError -> {
- _torConnectScreen.value = ConnectAssistUiState.LocationCheck
- }
- }
+ fun button1ShouldBeDisabled(screen: ConnectAssistUiState): Boolean {
+ return selectedCountryCode.value == "automatic" && screen.countryDropDownDefaultItem == R.string.connection_assist_select_country_or_region
}
}
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/UrlQuickLoadViewModel.kt
=====================================
@@ -1,10 +1,26 @@
package org.mozilla.fenix.tor
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
+import org.mozilla.fenix.ext.components
+import org.mozilla.geckoview.TorConnectStageName
+
+class UrlQuickLoadViewModel(application: Application) : AndroidViewModel(application) {
+
+ private val torAndroidIntegration =
+ application.components.core.geckoRuntime.torIntegrationController
-class UrlQuickLoadViewModel : ViewModel() {
val urlToLoadAfterConnecting: MutableLiveData<String?> by lazy {
MutableLiveData<String?>(null)
}
+
+ fun maybeBeginBootstrap() {
+ when (torAndroidIntegration.lastKnowStage.value?.name) {
+ TorConnectStageName.Offline -> torAndroidIntegration.beginBootstrap()
+ TorConnectStageName.Start -> torAndroidIntegration.beginBootstrap()
+ else -> {}
+ }
+ }
+
}
=====================================
mobile/android/fenix/app/src/main/res/layout/fragment_tor_connection_assist.xml
=====================================
@@ -45,6 +45,7 @@
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:visibility="invisible"
+ android:contentDescription="@string/connection_assist_back_button_content_description_start_again"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
=====================================
mobile/android/fenix/app/src/main/res/values/colors.xml
=====================================
@@ -380,6 +380,8 @@
<!-- Connection Assist -->
<color name="connect_button_purple">#9059FF</color>
+ <color name="disabled_connect_button_purple">#5C42A9</color>
+ <color name="disabled_text_gray_purple">#8782A9</color>
<color name="configure_connection_button_white">#E1E0E7</color>
<color name="warning_yellow">#FFA436</color>
<color name="progress_background_tint">#55148C</color>
=====================================
mobile/android/fenix/app/src/main/res/values/torbrowser_strings.xml
=====================================
@@ -78,7 +78,9 @@
<!-- Connection assist. -->
<string name="connection_assist_unblock_the_internet_in_country_or_region">Unblock the internet in:</string>
<!-- Connection assist. -->
- <string name="connection_assist_share_my_location_country_or_region">Share my location</string>
+ <string name="connection_assist_automatic_country_detection">Automatic</string>
+ <!-- Connection assist, -->
+ <string name="connection_assist_select_country_or_region">Select country or region</string>
<!-- Connection assist. -->
<string name="connection_assist_try_a_bridge_button">Try a bridge</string>
@@ -122,5 +124,7 @@
<string name="connection_assist_connect_to_tor_before_opening_links">Connect to Tor before opening links</string>
<!-- Connection assist. Confirmation button for a shown "Snackbar" (special popup notification). -->
<string name="connection_assist_connect_to_tor_before_opening_links_confirmation">CONNECT</string>
+ <!-- Connection assist. Content Description for back button. Button will start the connection assist process again -->
+ <string name="connection_assist_back_button_content_description_start_again">Start again</string>
</resources>
=====================================
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorAndroidIntegration.java
=====================================
@@ -10,6 +10,8 @@ import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
@@ -53,8 +55,10 @@ public class TorAndroidIntegration 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_START_AGAIN = "GeckoView:Tor:StartAgain";
private static final String EVENT_QUICKSTART_GET = "GeckoView:Tor:QuickstartGet";
private static final String EVENT_QUICKSTART_SET = "GeckoView:Tor:QuickstartSet";
+ private static final String EVENT_COUNTRY_NAMES_GET = "GeckoView:Tor:CountryNamesGet";
private static final String CONTROL_PORT_FILE = "/control-ipc";
private static final String SOCKS_FILE = "/socks-ipc";
@@ -82,6 +86,9 @@ public class TorAndroidIntegration implements BundleEventListener {
private int mMeekCounter;
+ private final MutableLiveData<TorConnectStage> _lastKnownStage = new MutableLiveData<>(null);
+ public LiveData<TorConnectStage> lastKnowStage = _lastKnownStage;
+
/**
* 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
@@ -154,6 +161,7 @@ public class TorAndroidIntegration implements BundleEventListener {
}
} else if (EVENT_CONNECT_STAGE_CHANGED.equals(event)) {
TorConnectStage stage = new TorConnectStage(message.getBundle("stage"));
+ _lastKnownStage.setValue(stage);
for (BootstrapStateChangeListener listener : mBootstrapStateListeners) {
listener.onBootstrapStageChange(stage);
}
@@ -693,6 +701,10 @@ public class TorAndroidIntegration implements BundleEventListener {
return EventDispatcher.getInstance().queryVoid(EVENT_SETTINGS_SET, bundle);
}
+ public @NonNull GeckoResult<Void> startAgain() {
+ return EventDispatcher.getInstance().queryVoid(EVENT_START_AGAIN);
+ }
+
public interface QuickstartGetter {
void onValue(boolean enabled);
}
@@ -710,6 +722,17 @@ public class TorAndroidIntegration implements BundleEventListener {
return EventDispatcher.getInstance().queryVoid(EVENT_QUICKSTART_SET, bundle);
}
+ public interface CountryNamesGetter {
+ void onValue(GeckoBundle regions);
+ }
+
+ public void countryNamesGet(CountryNamesGetter countryNamesGetter) {
+ EventDispatcher.getInstance().queryBundle(EVENT_COUNTRY_NAMES_GET).then(countryNames -> {
+ countryNamesGetter.onValue(countryNames);
+ return new GeckoResult<Void>();
+ });
+ }
+
public @NonNull GeckoResult<Void> beginBootstrap() {
return EventDispatcher.getInstance().queryVoid(EVENT_BOOTSTRAP_BEGIN);
}
=====================================
toolkit/modules/TorAndroidIntegration.sys.mjs
=====================================
@@ -42,8 +42,10 @@ const ListenedEvents = Object.freeze({
bootstrapBeginAuto: "GeckoView:Tor:BootstrapBeginAuto",
bootstrapCancel: "GeckoView:Tor:BootstrapCancel",
bootstrapGetState: "GeckoView:Tor:BootstrapGetState",
+ startAgain: "GeckoView:Tor:StartAgain",
quickstartGet: "GeckoView:Tor:QuickstartGet",
quickstartSet: "GeckoView:Tor:QuickstartSet",
+ countryNamesGet: "GeckoView:Tor:CountryNamesGet",
});
class TorAndroidIntegrationImpl {
@@ -190,14 +192,18 @@ class TorAndroidIntegrationImpl {
case ListenedEvents.bootstrapGetState:
callback?.onSuccess(lazy.TorConnect.state);
return;
- // TODO: Expose TorConnect.startAgain() to allow users to begin
- // from the start again.
+ case ListenedEvents.startAgain:
+ lazy.TorConnect.startAgain();
+ break;
case ListenedEvents.quickstartGet:
callback?.onSuccess(lazy.TorConnect.quickstart);
return;
case ListenedEvents.quickstartSet:
lazy.TorConnect.quickstart = data.enabled;
break;
+ case ListenedEvents.countryNamesGet:
+ callback?.onSuccess(lazy.TorConnect.countryNames);
+ return;
}
callback?.onSuccess();
} catch (e) {
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/797d5a…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/797d5a…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-128.8.0esr-14.5-1] fixup! TB 2176: Rebrand Firefox to TorBrowser
by morgan (@morgan) 19 Mar '25
by morgan (@morgan) 19 Mar '25
19 Mar '25
morgan pushed to branch tor-browser-128.8.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits:
797d5a05 by Pier Angelo Vendrame at 2025-03-19T18:58:58+01:00
fixup! TB 2176: Rebrand Firefox to TorBrowser
TB 43575: Clean up more channel-related prefs.
app.update.promptWaitTime is already in 001-base-browser.js.
- - - - -
3 changed files:
- browser/branding/tb-alpha/pref/firefox-branding.js
- browser/branding/tb-nightly/pref/firefox-branding.js
- browser/branding/tb-release/pref/firefox-branding.js
Changes:
=====================================
browser/branding/tb-alpha/pref/firefox-branding.js
=====================================
@@ -17,8 +17,6 @@ pref("app.releaseNotesURL.aboutDialog", "https://blog.torproject.org/new-alpha-r
// The time interval between checks for a new version (in seconds)
pref("app.update.interval", 43200); // 12 hours
-// Give the user x seconds to react before showing the big UI. default=12 hours
-pref("app.update.promptWaitTime", 43200);
// The number of days a binary is permitted to be old
// without checking for an update. This assumes that
// app.update.checkInstallTime is true.
=====================================
browser/branding/tb-nightly/pref/firefox-branding.js
=====================================
@@ -17,8 +17,6 @@ pref("app.releaseNotesURL.aboutDialog", "about:blank");
// The time interval between checks for a new version (in seconds)
pref("app.update.interval", 14400); // 4 hours
-// Give the user x seconds to react before showing the big UI. default=12 hours
-pref("app.update.promptWaitTime", 43200);
// The number of days a binary is permitted to be old
// without checking for an update. This assumes that
// app.update.checkInstallTime is true.
=====================================
browser/branding/tb-release/pref/firefox-branding.js
=====================================
@@ -17,8 +17,6 @@ pref("app.releaseNotesURL.aboutDialog", "https://blog.torproject.org/new-release
// Interval: Time between checks for a new version (in seconds)
pref("app.update.interval", 43200); // 12 hours
-// Give the user x seconds to react before showing the big UI. default=192 hours
-pref("app.update.promptWaitTime", 691200);
// The number of days a binary is permitted to be old
// without checking for an update. This assumes that
// app.update.checkInstallTime is true.
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/797d5a0…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/797d5a0…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/mullvad-browser][mullvad-browser-128.8.0esr-14.5-1] 3 commits: fixup! MB 1: Mullvad Browser branding
by Pier Angelo Vendrame (@pierov) 19 Mar '25
by Pier Angelo Vendrame (@pierov) 19 Mar '25
19 Mar '25
Pier Angelo Vendrame pushed to branch mullvad-browser-128.8.0esr-14.5-1 at The Tor Project / Applications / Mullvad Browser
Commits:
b086d25d by Pier Angelo Vendrame at 2025-03-19T18:39:52+01:00
fixup! MB 1: Mullvad Browser branding
MB 412: Link the release page in the about dialog and clean brand-related preferences.
- - - - -
4ac981eb by Pier Angelo Vendrame at 2025-03-19T18:41:32+01:00
fixup! MB 112: Updater customization for Mullvad Browser
MB 412: Link the release page in the about dialog and clean brand-related preferences.
- - - - -
979f4ea4 by Pier Angelo Vendrame at 2025-03-19T18:43:08+01:00
fixup! MB 38: Mullvad Browser configuration
MB 412: Link the release page in the about dialog and clean brand-related preferences.
- - - - -
4 changed files:
- browser/app/profile/000-mullvad-browser.js
- browser/branding/mb-alpha/pref/firefox-branding.js
- browser/branding/mb-nightly/pref/firefox-branding.js
- browser/branding/mb-release/pref/firefox-branding.js
Changes:
=====================================
browser/app/profile/000-mullvad-browser.js
=====================================
@@ -15,9 +15,6 @@ pref("doh-rollout.provider-list", "[{\"UIName\":\"Mullvad\",\"autoDefault\":true
// mullvad-browser#122: Audit DoH heuristics
pref("doh-rollout.disable-heuristics", true);
-// mullvad-browser#37: Customization for the about dialog
-pref("app.releaseNotesURL.aboutDialog", "about:blank");
-
// mullvad-browser#87: Windows and Linux need additional work to make the
// default browser choice working.
// We are shipping only the portable versions for the initial release anyway, so
@@ -40,9 +37,5 @@ pref("app.update.notifyDuringDownload", true);
pref("app.update.url.manual", "https://mullvad.net/download/browser");
pref("app.update.url.details", "https://mullvad.net/download/browser");
pref("app.update.badgeWaitTime", 0);
-pref("app.releaseNotesURL", "https://github.com/mullvad/mullvad-browser/releases");
-// disables the 'What's New?' link in the about dialog, otherwise we need to
-// duplicate logic for generating the url to the github releases page
-pref("app.releaseNotesURL.aboutDialog", "about:blank");
// point to our feedback url rather than Mozilla's
pref("app.feedback.baseURL", "https://mullvad.net/help/tag/browser/");
=====================================
browser/branding/mb-alpha/pref/firefox-branding.js
=====================================
@@ -5,31 +5,23 @@
// This file contains branding-specific prefs.
pref("startup.homepage_override_url", "");
-pref("startup.homepage_welcome_url", "about:welcome");
-pref("startup.homepage_welcome_url.additional", "");
-// Interval: Time between checks for a new version (in seconds)
-pref("app.update.interval", 43200); // 12 hours
-// Give the user x seconds to react before showing the big UI. default=192 hours
-pref("app.update.promptWaitTime", 691200);
// app.update.url.manual: URL user can browse to manually if for some reason
// all update installation attempts fail.
// app.update.url.details: a default value for the "More information about this
// update" link supplied in the "An update is available" page of the update
// wizard.
-pref("app.update.url.manual", "https://www.mozilla.org/%LOCALE%/firefox/new?reason=manual-update");
-pref("app.update.url.details", "https://www.mozilla.org/%LOCALE%/firefox/notes");
-pref("app.releaseNotesURL", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/releasenotes/?utm_source…");
-pref("app.releaseNotesURL.aboutDialog", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/releasenotes/?utm_source…");
+pref("app.update.url.manual", "https://mullvad.net/download/browser#alpha");
+pref("app.update.url.details", "https://mullvad.net/download/browser#alpha");
+pref("app.releaseNotesURL", "https://github.com/mullvad/mullvad-browser/releases/tag/%BB_VERSION%");
+pref("app.releaseNotesURL.aboutDialog", "https://github.com/mullvad/mullvad-browser/releases/tag/%BB_VERSION%");
+// Interval: Time between checks for a new version (in seconds)
+pref("app.update.interval", 43200); // 12 hours
// The number of days a binary is permitted to be old
// without checking for an update. This assumes that
// app.update.checkInstallTime is true.
pref("app.update.checkInstallTime.days", 63);
-// Give the user x seconds to reboot before showing a badge on the hamburger
-// button. default=4 days
-pref("app.update.badgeWaitTime", 345600);
-
// Number of usages of the web console.
// If this is less than 5, then pasting code into the web console is disabled
pref("devtools.selfxss.count", 0);
=====================================
browser/branding/mb-nightly/pref/firefox-branding.js
=====================================
@@ -5,31 +5,23 @@
// This file contains branding-specific prefs.
pref("startup.homepage_override_url", "");
-pref("startup.homepage_welcome_url", "about:welcome");
-pref("startup.homepage_welcome_url.additional", "");
-// Interval: Time between checks for a new version (in seconds)
-pref("app.update.interval", 43200); // 12 hours
-// Give the user x seconds to react before showing the big UI. default=192 hours
-pref("app.update.promptWaitTime", 691200);
// app.update.url.manual: URL user can browse to manually if for some reason
// all update installation attempts fail.
// app.update.url.details: a default value for the "More information about this
// update" link supplied in the "An update is available" page of the update
// wizard.
-pref("app.update.url.manual", "https://www.mozilla.org/%LOCALE%/firefox/new?reason=manual-update");
-pref("app.update.url.details", "https://www.mozilla.org/%LOCALE%/firefox/notes");
-pref("app.releaseNotesURL", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/releasenotes/?utm_source…");
-pref("app.releaseNotesURL.aboutDialog", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/releasenotes/?utm_source…");
+pref("app.update.url.manual", "https://nightlies.tbb.torproject.org/");
+pref("app.update.url.details", "https://nightlies.tbb.torproject.org/");
+pref("app.releaseNotesURL", "about:blank");
+pref("app.releaseNotesURL.aboutDialog", "about:blank");
+// Interval: Time between checks for a new version (in seconds)
+pref("app.update.interval", 14400); // 4 hours
// The number of days a binary is permitted to be old
// without checking for an update. This assumes that
// app.update.checkInstallTime is true.
-pref("app.update.checkInstallTime.days", 63);
-
-// Give the user x seconds to reboot before showing a badge on the hamburger
-// button. default=4 days
-pref("app.update.badgeWaitTime", 345600);
+pref("app.update.checkInstallTime.days", 2);
// Number of usages of the web console.
// If this is less than 5, then pasting code into the web console is disabled
-pref("devtools.selfxss.count", 0);
+pref("devtools.selfxss.count", 5);
=====================================
browser/branding/mb-release/pref/firefox-branding.js
=====================================
@@ -5,31 +5,23 @@
// This file contains branding-specific prefs.
pref("startup.homepage_override_url", "");
-pref("startup.homepage_welcome_url", "about:welcome");
-pref("startup.homepage_welcome_url.additional", "");
-// Interval: Time between checks for a new version (in seconds)
-pref("app.update.interval", 43200); // 12 hours
-// Give the user x seconds to react before showing the big UI. default=192 hours
-pref("app.update.promptWaitTime", 691200);
// app.update.url.manual: URL user can browse to manually if for some reason
// all update installation attempts fail.
// app.update.url.details: a default value for the "More information about this
// update" link supplied in the "An update is available" page of the update
// wizard.
-pref("app.update.url.manual", "https://www.mozilla.org/%LOCALE%/firefox/new?reason=manual-update");
-pref("app.update.url.details", "https://www.mozilla.org/%LOCALE%/firefox/notes");
-pref("app.releaseNotesURL", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/releasenotes/?utm_source…");
-pref("app.releaseNotesURL.aboutDialog", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/releasenotes/?utm_source…");
+pref("app.update.url.manual", "https://mullvad.net/download/browser");
+pref("app.update.url.details", "https://mullvad.net/download/browser");
+pref("app.releaseNotesURL", "https://github.com/mullvad/mullvad-browser/releases/tag/%BB_VERSION%");
+pref("app.releaseNotesURL.aboutDialog", "https://github.com/mullvad/mullvad-browser/releases/tag/%BB_VERSION%");
+// Interval: Time between checks for a new version (in seconds)
+pref("app.update.interval", 43200); // 12 hours
// The number of days a binary is permitted to be old
// without checking for an update. This assumes that
// app.update.checkInstallTime is true.
pref("app.update.checkInstallTime.days", 63);
-// Give the user x seconds to reboot before showing a badge on the hamburger
-// button. default=4 days
-pref("app.update.badgeWaitTime", 345600);
-
// Number of usages of the web console.
// If this is less than 5, then pasting code into the web console is disabled
pref("devtools.selfxss.count", 0);
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/a4…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/a4…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/mullvad-browser][mullvad-browser-128.8.0esr-14.5-1] fixup! BB 4234: Use the Firefox Update Process for Base Browser.
by Pier Angelo Vendrame (@pierov) 19 Mar '25
by Pier Angelo Vendrame (@pierov) 19 Mar '25
19 Mar '25
Pier Angelo Vendrame pushed to branch mullvad-browser-128.8.0esr-14.5-1 at The Tor Project / Applications / Mullvad Browser
Commits:
a4f6cb3b by Pier Angelo Vendrame at 2025-03-19T17:23:18+01:00
fixup! BB 4234: Use the Firefox Update Process for Base Browser.
TB 42720: Show what's new in the about:dialog.
Add a BB_VERSION replacement to the URLFormatter service.
- - - - -
1 changed file:
- toolkit/components/urlformatter/URLFormatter.sys.mjs
Changes:
=====================================
toolkit/components/urlformatter/URLFormatter.sys.mjs
=====================================
@@ -134,6 +134,9 @@ nsURLFormatterService.prototype = {
DISTRIBUTION_VERSION() {
return this.distribution.version;
},
+ BB_VERSION() {
+ return AppConstants.BASE_BROWSER_VERSION;
+ },
},
formatURL: function uf_formatURL(aFormat) {
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/a4f…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/a4f…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][base-browser-128.8.0esr-14.5-1] fixup! BB 4234: Use the Firefox Update Process for Base Browser.
by Pier Angelo Vendrame (@pierov) 19 Mar '25
by Pier Angelo Vendrame (@pierov) 19 Mar '25
19 Mar '25
Pier Angelo Vendrame pushed to branch base-browser-128.8.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits:
23c0b249 by Pier Angelo Vendrame at 2025-03-19T17:22:52+01:00
fixup! BB 4234: Use the Firefox Update Process for Base Browser.
TB 42720: Show what's new in the about:dialog.
Add a BB_VERSION replacement to the URLFormatter service.
- - - - -
1 changed file:
- toolkit/components/urlformatter/URLFormatter.sys.mjs
Changes:
=====================================
toolkit/components/urlformatter/URLFormatter.sys.mjs
=====================================
@@ -134,6 +134,9 @@ nsURLFormatterService.prototype = {
DISTRIBUTION_VERSION() {
return this.distribution.version;
},
+ BB_VERSION() {
+ return AppConstants.BASE_BROWSER_VERSION;
+ },
},
formatURL: function uf_formatURL(aFormat) {
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/23c0b24…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/23c0b24…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-128.8.0esr-14.5-1] fixup! TB 2176: Rebrand Firefox to TorBrowser
by Pier Angelo Vendrame (@pierov) 19 Mar '25
by Pier Angelo Vendrame (@pierov) 19 Mar '25
19 Mar '25
Pier Angelo Vendrame pushed to branch tor-browser-128.8.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits:
cf350084 by Pier Angelo Vendrame at 2025-03-19T17:09:21+01:00
fixup! TB 2176: Rebrand Firefox to TorBrowser
TB 42720: Show what's new in the about:dialog.
Improve some comments.
- - - - -
1 changed file:
- browser/base/content/aboutDialog.xhtml
Changes:
=====================================
browser/base/content/aboutDialog.xhtml
=====================================
@@ -117,6 +117,9 @@
<label id="version" class="update"/>
</hbox>
<description class="text-blurb">
+ <!-- Place release notes on the same line as the help and feedback links.
+ - We do not want the release notes on the same line as the version because it may overflow.
+ - See tor-browerr#42720. -->
<label id="releasenotes" is="text-link" hidden="true" data-l10n-id="releaseNotes-link"/>
<label is="text-link" onclick="openHelpLink('firefox-help')" data-l10n-id="aboutdialog-help-user"/>
<label id="submit-feedback" is="text-link" onclick="openFeedbackPage()" data-l10n-id="aboutdialog-submit-feedback"/>
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/cf35008…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/cf35008…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-128.8.0esr-14.5-1] 4 commits: fixup! TB 40562: Added Tor Browser preferences to 000-tor-browser.js
by Pier Angelo Vendrame (@pierov) 19 Mar '25
by Pier Angelo Vendrame (@pierov) 19 Mar '25
19 Mar '25
Pier Angelo Vendrame pushed to branch tor-browser-128.8.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits:
231850ac by Pier Angelo Vendrame at 2025-03-19T16:59:00+01:00
fixup! TB 40562: Added Tor Browser preferences to 000-tor-browser.js
TB 42720: Show what's new in the about:dialog.
Update our comment about app.releaseNotesURL.aboutDialog.
- - - - -
de58bd66 by Pier Angelo Vendrame at 2025-03-19T16:59:03+01:00
fixup! BB 4234: Use the Firefox Update Process for Base Browser.
TB 42720: Show what's new in the about:dialog.
Add a BB_VERSION replacement to the URLFormatter service.
- - - - -
8f35f42f by Pier Angelo Vendrame at 2025-03-19T16:59:04+01:00
fixup! TB 2176: Rebrand Firefox to TorBrowser
TB 42720: Show what's new in the about:dialog.
Customize the "What's new" link and cleanup other channel-specific
prefs.
- - - - -
8d7ba10b by Pier Angelo Vendrame at 2025-03-19T16:59:04+01:00
fixup! TB 41668: Tweaks to the Base Browser updater for Tor Browser
TB 42720: Show what's new in the about:dialog.
Add a variable for the base browser version without dot, as we use it
in blog post URLs.
- - - - -
7 changed files:
- browser/app/profile/000-tor-browser.js
- browser/base/content/aboutDialog.xhtml
- browser/base/content/aboutDialogTor.css
- browser/branding/tb-alpha/pref/firefox-branding.js
- browser/branding/tb-nightly/pref/firefox-branding.js
- browser/branding/tb-release/pref/firefox-branding.js
- toolkit/components/urlformatter/URLFormatter.sys.mjs
Changes:
=====================================
browser/app/profile/000-tor-browser.js
=====================================
@@ -1,14 +1,7 @@
#include 001-base-profile.js
pref("app.update.notifyDuringDownload", true);
-pref("app.update.url.manual", "https://www.torproject.org/download/languages/");
-pref("app.update.url.details", "https://www.torproject.org/download/");
pref("app.update.badgeWaitTime", 0);
-pref("app.releaseNotesURL", "about:blank");
-// disables the 'What's New?' link in the about dialog, otherwise we need to
-// duplicate logic for generating the url to the blog post that is already more
-// easily found in about:tor
-pref("app.releaseNotesURL.aboutDialog", "about:blank");
// point to our feedback url rather than Mozilla's
pref("app.feedback.baseURL", "https://support.torproject.org/%LOCALE%/misc/bug-or-feedback/");
=====================================
browser/base/content/aboutDialog.xhtml
=====================================
@@ -115,9 +115,9 @@
<!-- This HBOX is duplicated above without class="update" -->
<hbox align="baseline">
<label id="version" class="update"/>
- <label id="releasenotes" is="text-link" hidden="true" data-l10n-id="releaseNotes-link"/>
</hbox>
<description class="text-blurb">
+ <label id="releasenotes" is="text-link" hidden="true" data-l10n-id="releaseNotes-link"/>
<label is="text-link" onclick="openHelpLink('firefox-help')" data-l10n-id="aboutdialog-help-user"/>
<label id="submit-feedback" is="text-link" onclick="openFeedbackPage()" data-l10n-id="aboutdialog-submit-feedback"/>
</description>
=====================================
browser/base/content/aboutDialogTor.css
=====================================
@@ -26,6 +26,10 @@
margin-block: 10px;
}
+#releasenotes {
+ margin-inline-end: .9em; /* Same as #submit-feedback */
+}
+
#contributeDesc {
display: none;
}
=====================================
browser/branding/tb-alpha/pref/firefox-branding.js
=====================================
@@ -10,30 +10,19 @@
// actions="showURL"
// openURL="https://blog.torproject.org/tor-browser-55a2-released"
pref("startup.homepage_override_url", "https://blog.torproject.org/category/applications");
-pref("startup.homepage_welcome_url", "about:welcome");
-pref("startup.homepage_welcome_url.additional", "");
+pref("app.update.url.details", "https://www.torproject.org/download/alpha/");
+pref("app.update.url.manual", "https://www.torproject.org/download/alpha/");
+pref("app.releaseNotesURL", "https://blog.torproject.org/new-alpha-release-tor-browser-%BB_VERSION_FOR_U…");
+pref("app.releaseNotesURL.aboutDialog", "https://blog.torproject.org/new-alpha-release-tor-browser-%BB_VERSION_FOR_U…");
+
// The time interval between checks for a new version (in seconds)
pref("app.update.interval", 43200); // 12 hours
// Give the user x seconds to react before showing the big UI. default=12 hours
pref("app.update.promptWaitTime", 43200);
-// URL user can browse to manually if for some reason all update installation
-// attempts fail.
-pref("app.update.url.manual", "https://www.mozilla.org/%LOCALE%/firefox/nightly/");
-// A default value for the "More information about this update" link
-// supplied in the "An update is available" page of the update wizard.
-pref("app.update.url.details", "https://www.mozilla.org/%LOCALE%/firefox/nightly/notes/");
-
-pref("app.releaseNotesURL", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/releasenotes/?utm_source…");
-
// The number of days a binary is permitted to be old
// without checking for an update. This assumes that
// app.update.checkInstallTime is true.
pref("app.update.checkInstallTime.days", 2);
-
-// Give the user x seconds to reboot before showing a badge on the hamburger
-// button. default=immediately
-pref("app.update.badgeWaitTime", 0);
-
// Number of usages of the web console.
// If this is less than 5, then pasting code into the web console is disabled
pref("devtools.selfxss.count", 5);
=====================================
browser/branding/tb-nightly/pref/firefox-branding.js
=====================================
@@ -9,32 +9,20 @@
// each update manifest should contain attributes similar to:
// actions="showURL"
// openURL="https://blog.torproject.org/tor-browser-55a2-released"
-pref("startup.homepage_override_url", "https://blog.torproject.org/category/applications");
-pref("startup.homepage_welcome_url", "about:welcome");
-pref("startup.homepage_welcome_url.additional", "");
+pref("startup.homepage_override_url", "https://nightlies.tbb.torproject.org/");
+pref("app.update.url.details", "https://nightlies.tbb.torproject.org/nightly-builds/tor-browser-builds/");
+pref("app.update.url.manual", "https://nightlies.tbb.torproject.org/nightly-builds/tor-browser-builds/");
+pref("app.releaseNotesURL", "about:blank");
+pref("app.releaseNotesURL.aboutDialog", "about:blank");
+
// The time interval between checks for a new version (in seconds)
pref("app.update.interval", 14400); // 4 hours
// Give the user x seconds to react before showing the big UI. default=12 hours
pref("app.update.promptWaitTime", 43200);
-// URL user can browse to manually if for some reason all update installation
-// attempts fail.
-pref("app.update.url.manual", "https://www.mozilla.org/%LOCALE%/firefox/nightly/?reason=manual-update");
-// A default value for the "More information about this update" link
-// supplied in the "An update is available" page of the update wizard.
-pref("app.update.url.details", "https://www.mozilla.org/%LOCALE%/firefox/nightly/notes/");
-
-pref("app.releaseNotesURL", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/releasenotes/?utm_source…");
-pref("app.releaseNotesURL.aboutDialog", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/releasenotes/?utm_source…");
-
// The number of days a binary is permitted to be old
// without checking for an update. This assumes that
// app.update.checkInstallTime is true.
pref("app.update.checkInstallTime.days", 2);
-
-// Give the user x seconds to reboot before showing a badge on the hamburger
-// button. default=immediately
-pref("app.update.badgeWaitTime", 0);
-
// Number of usages of the web console.
// If this is less than 5, then pasting code into the web console is disabled
pref("devtools.selfxss.count", 5);
=====================================
browser/branding/tb-release/pref/firefox-branding.js
=====================================
@@ -10,43 +10,19 @@
// actions="showURL"
// openURL="https://blog.torproject.org/tor-browser-55a2-released"
pref("startup.homepage_override_url", "https://blog.torproject.org/category/applications");
-pref("startup.homepage_welcome_url", "about:welcome");
-pref("startup.homepage_welcome_url.additional", "");
+pref("app.update.url.details", "https://www.torproject.org/download/");
+pref("app.update.url.manual", "https://www.torproject.org/download/");
+pref("app.releaseNotesURL", "https://blog.torproject.org/new-release-tor-browser-%BB_VERSION_FOR_URLS%/");
+pref("app.releaseNotesURL.aboutDialog", "https://blog.torproject.org/new-release-tor-browser-%BB_VERSION_FOR_URLS%/");
+
// Interval: Time between checks for a new version (in seconds)
pref("app.update.interval", 43200); // 12 hours
// Give the user x seconds to react before showing the big UI. default=192 hours
pref("app.update.promptWaitTime", 691200);
-// app.update.url.manual: URL user can browse to manually if for some reason
-// all update installation attempts fail.
-// app.update.url.details: a default value for the "More information about this
-// update" link supplied in the "An update is available" page of the update
-// wizard.
-#if MOZ_UPDATE_CHANNEL == beta
- pref("app.update.url.manual", "https://www.mozilla.org/%LOCALE%/firefox/beta?reason=manual-update");
- pref("app.update.url.details", "https://www.mozilla.org/%LOCALE%/firefox/beta/notes");
- pref("app.releaseNotesURL", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%beta/releasenotes/?utm_so…");
- pref("app.releaseNotesURL.aboutDialog", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%beta/releasenotes/?utm_so…");
-#elifdef MOZ_ESR
- pref("app.update.url.manual", "https://www.mozilla.org/%LOCALE%/firefox/enterprise?reason=manual-update");
- pref("app.update.url.details", "https://www.mozilla.org/%LOCALE%/firefox/organizations/notes");
- pref("app.releaseNotesURL", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/releasenotes/?utm_source…");
- pref("app.releaseNotesURL.aboutDialog", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/releasenotes/?utm_source…");
-#else
- pref("app.update.url.manual", "https://www.mozilla.org/%LOCALE%/firefox/new?reason=manual-update");
- pref("app.update.url.details", "https://www.mozilla.org/%LOCALE%/firefox/notes");
- pref("app.releaseNotesURL", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/releasenotes/?utm_source…");
- pref("app.releaseNotesURL.aboutDialog", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/releasenotes/?utm_source…");
-#endif
-
// The number of days a binary is permitted to be old
// without checking for an update. This assumes that
// app.update.checkInstallTime is true.
pref("app.update.checkInstallTime.days", 63);
-
-// Give the user x seconds to reboot before showing a badge on the hamburger
-// button. default=4 days
-pref("app.update.badgeWaitTime", 345600);
-
// Number of usages of the web console.
// If this is less than 5, then pasting code into the web console is disabled
pref("devtools.selfxss.count", 0);
=====================================
toolkit/components/urlformatter/URLFormatter.sys.mjs
=====================================
@@ -134,6 +134,16 @@ nsURLFormatterService.prototype = {
DISTRIBUTION_VERSION() {
return this.distribution.version;
},
+ BB_VERSION() {
+ return AppConstants.BASE_BROWSER_VERSION;
+ },
+ BB_VERSION_FOR_URLS() {
+ let version = AppConstants.BASE_BROWSER_VERSION;
+ if (/^[0-9a\.]+$/.test(version)) {
+ version = version.replaceAll(".", "");
+ }
+ return version;
+ },
},
formatURL: function uf_formatURL(aFormat) {
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/b0a250…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/b0a250…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-128.8.0esr-14.5-1] fixup! TB 27476: Implement about:torconnect captive portal within Tor Browser
by morgan (@morgan) 19 Mar '25
by morgan (@morgan) 19 Mar '25
19 Mar '25
morgan pushed to branch tor-browser-128.8.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits:
b0a250cd by Henry Wilkes at 2025-03-19T11:15:37+00:00
fixup! TB 27476: Implement about:torconnect captive portal within Tor Browser
TB 42656: Drop maybeUpdateOpenLocationForTorConnect.
- - - - -
1 changed file:
- browser/components/urlbar/UrlbarInput.sys.mjs
Changes:
=====================================
browser/components/urlbar/UrlbarInput.sys.mjs
=====================================
@@ -20,7 +20,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
ReaderMode: "resource://gre/modules/ReaderMode.sys.mjs",
SearchUIUtils: "resource:///modules/SearchUIUtils.sys.mjs",
SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs",
- TorConnect: "resource://gre/modules/TorConnect.sys.mjs",
UrlbarController: "resource:///modules/UrlbarController.sys.mjs",
UrlbarEventBufferer: "resource:///modules/UrlbarEventBufferer.sys.mjs",
UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
@@ -276,36 +275,6 @@ export class UrlbarInput {
);
}
- // in certain scenarios we want user input uris to open in a new tab if they do so from the
- // about:torconnect tab
- #maybeUpdateOpenLocationForTorConnect(
- openUILinkWhere,
- currentURI,
- destinationURI
- ) {
- try {
- // only open in new tab if:
- if (
- // user is navigating away from about:torconnect
- currentURI === "about:torconnect" &&
- // we are trying to open in same tab
- openUILinkWhere === "current" &&
- // only if user still has not bootstrapped
- lazy.TorConnect.shouldShowTorConnect &&
- // and user is not just navigating to about:torconnect
- destinationURI !== "about:torconnect"
- ) {
- return "tab";
- }
- } catch (e) {
- // swallow exception and fall through returning original so we don't accidentally break
- // anything if an exception is thrown
- this.logger.error(e?.message ? e.message : e);
- }
-
- return openUILinkWhere;
- }
-
/**
* Applies styling to the text in the urlbar input, depending on the text.
*/
@@ -3017,11 +2986,6 @@ export class UrlbarInput {
this.inputField.setSelectionRange(0, 0);
}
- openUILinkWhere = this.#maybeUpdateOpenLocationForTorConnect(
- openUILinkWhere,
- this.window.gBrowser.currentURI.asciiSpec,
- url
- );
if (openUILinkWhere != "current") {
this.handleRevert();
}
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/b0a250c…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/b0a250c…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-128.8.0esr-14.5-1] 2 commits: fixup! [android] TBA strings
by Dan Ballard (@dan) 19 Mar '25
by Dan Ballard (@dan) 19 Mar '25
19 Mar '25
Dan Ballard pushed to branch tor-browser-128.8.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits:
9078db0b by Dan Ballard at 2025-03-19T00:06:35+00:00
fixup! [android] TBA strings
Bug 43505 [android]: Add 2025 UX Survey Campaign
- - - - -
359eed56 by Dan Ballard at 2025-03-18T17:10:05-07:00
Bug 43505 [android]: Add 2025 UX Survey Campaign
- - - - -
6 changed files:
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
- + mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/CampaignStrings.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
- + mobile/android/fenix/app/src/main/res/drawable/campaign_hand.xml
- mobile/android/fenix/app/src/main/res/layout/fragment_home.xml
- mobile/android/fenix/app/src/main/res/values/preference_keys.xml
Changes:
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
=====================================
@@ -12,33 +12,65 @@ import android.content.res.Configuration
import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.Bundle
+import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.ImageView
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.material.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.draw.scale
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.ViewCompositionStrategy
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.semantics.testTagsAsResourceId
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getColor
-import androidx.core.view.children
-import androidx.core.view.doOnLayout
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
@@ -108,6 +140,7 @@ import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.PrivateShortcutCreateManager
import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.components.appstate.AppAction
+import org.mozilla.fenix.components.components
import org.mozilla.fenix.components.menu.MenuAccessPoint
import org.mozilla.fenix.components.toolbar.IncompleteRedesignToolbarFeature
import org.mozilla.fenix.components.toolbar.ToolbarPosition
@@ -151,13 +184,13 @@ import org.mozilla.fenix.messaging.DefaultMessageController
import org.mozilla.fenix.messaging.FenixMessageSurfaceId
import org.mozilla.fenix.messaging.MessagingFeature
import org.mozilla.fenix.microsurvey.ui.MicrosurveyRequestPrompt
-import org.mozilla.fenix.nimbus.FxNimbus
import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks
import org.mozilla.fenix.search.toolbar.DefaultSearchSelectorController
import org.mozilla.fenix.search.toolbar.SearchSelectorMenu
import org.mozilla.fenix.tabstray.Page
import org.mozilla.fenix.tabstray.TabsTrayAccessPoint
import org.mozilla.fenix.theme.FirefoxTheme
+import org.mozilla.fenix.tor.CampaignStrings
import org.mozilla.fenix.utils.Settings.Companion.TOP_SITES_PROVIDER_MAX_THRESHOLD
import org.mozilla.fenix.utils.allowUndo
import org.mozilla.fenix.wallpapers.Wallpaper
@@ -165,6 +198,9 @@ import java.lang.ref.WeakReference
import org.mozilla.fenix.GleanMetrics.TabStrip as TabStripMetrics
import org.mozilla.fenix.tor.UrlQuickLoadViewModel
+import java.text.SimpleDateFormat
+import java.time.LocalDate
+import java.util.Date
@Suppress("TooManyFunctions", "LargeClass")
class HomeFragment : Fragment(), UserInteractionHandler {
@@ -507,6 +543,8 @@ class HomeFragment : Fragment(), UserInteractionHandler {
activity.themeManager.applyStatusBarTheme(activity)
+ tryShowUX2025Survey()
+
// FxNimbus.features.homescreen.recordExposure()
// DO NOT MOVE ANYTHING BELOW THIS addMarker CALL!
@@ -518,6 +556,31 @@ class HomeFragment : Fragment(), UserInteractionHandler {
return binding.root
}
+ private fun tryShowUX2025Survey() {
+ val allowedLocales = arrayListOf("en", "es", "ru", "fr", "pt")
+ val locale = CampaignStrings.getLocale()
+
+ val dateFormat = SimpleDateFormat("yyyy-MM-dd-hh-zzz")
+ val startDate = dateFormat.parse("2025-04-14-12-UTC")
+
+ val endDate = dateFormat.parse("2025-04-28-00-UTC")
+ val currentDate = Date()
+
+ if (currentDate.before(startDate) || currentDate.after(endDate)) {
+ return // comment out to test
+ }
+
+ if (allowedLocales.contains(locale) && !requireContext().settings().hideCampaign) {
+ binding.onionPatternImage.visibility = View.GONE
+ binding.campaignBox.apply {
+ setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
+ setContent {
+ CampaignBox()
+ }
+ }
+ }
+ }
+
private fun reinitializeNavBar() {
initializeNavBar(activity = requireActivity() as HomeActivity)
}
@@ -1396,4 +1459,213 @@ class HomeFragment : Fragment(), UserInteractionHandler {
override fun onBackPressed(): Boolean {
(requireActivity() as HomeActivity).shutDown()
}
+
+ @Composable
+ fun CampaignBox() {
+ BoxWithConstraints(
+ contentAlignment = Alignment.Center,
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentHeight()
+ ) {
+ val alternateLayout = this.maxWidth >= 500.dp
+
+ CampaignLayout(
+ alternateLayout,
+ maxWidth = this.maxWidth,
+ modifier = Modifier
+ .padding(top = if (alternateLayout) 65.dp else 55.dp, bottom = 56.dp),
+ )
+ }
+ }
+
+ @Composable
+ private fun CampaignLayout(
+ alternateLayout: Boolean,
+ maxWidth: Dp,
+ modifier: Modifier
+ ) {
+ Column(
+ modifier = modifier
+ .padding(horizontal = 22.dp)
+ .verticalScroll(rememberScrollState())
+ .fillMaxWidth(getVariableWidth(maxWidth)),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ PurpleBox(alternateLayout)
+ }
+ }
+
+ private fun getVariableWidth(width: Dp): Float = (500.dp / width).coerceIn(0.75f, 1.0f)
+
+ @Composable
+ private fun PurpleBox(
+ alternateLayout: Boolean,
+ ) {
+ Box(
+ modifier = Modifier.background(PhotonColors.Violet90, shape = RoundedCornerShape(8.dp))
+ .padding(16.dp),
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ Emoji()
+ Spacer(Modifier.weight(1f))
+ ExitIcon()
+ }
+ DynamicCampaignContent(alternateLayout)
+ }
+ }
+
+ @Composable
+ private fun Emoji() {
+ val alpha38Violet40 = Color(PhotonColors.Violet40.red, PhotonColors.Violet40.green, PhotonColors.Violet40.blue, 0.38f)
+ Image(
+ painter = painterResource(id = R.drawable.campaign_hand),
+ contentDescription = null,
+ modifier = Modifier
+ .size(64.dp)
+ .padding(16.dp)
+ .drawBehind {
+ drawCircle(
+ color = alpha38Violet40,
+ radius = this.size.maxDimension
+ )
+ }
+ )
+ }
+
+ @Composable
+ private fun ExitIcon() {
+ IconButton(
+ onClick = {
+ binding.campaignBox.visibility = View.GONE
+ binding.onionPatternImage.visibility = View.VISIBLE
+ context?.components?.settings?.hideCampaign = true
+ },
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_close),
+ tint = Color(
+ getColor(
+ requireContext(),
+ R.color.photonWhite,
+ ),
+ ),
+ contentDescription = CampaignStrings.get(CampaignStrings.CloseKey),
+ modifier = Modifier
+ .size(48.dp)
+ .padding(8.dp)
+ )
+ }
+ }
+
+
+ @Composable
+ private fun DynamicCampaignContent(
+ alternateLayout: Boolean
+ ) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Column(
+ modifier = Modifier.fillMaxWidth()
+ .padding( top = 88.dp),
+ horizontalAlignment = Alignment.Start,
+ ) {
+ TitleText()
+ MainText()
+
+ if (alternateLayout) {
+ Row(modifier = Modifier.fillMaxWidth()) {
+ Button1(alternateLayout)
+ Button2()
+ }
+ } else {
+ Button1(alternateLayout)
+ Button2()
+ }
+
+ }
+ }
+ }
+
+ @Composable
+ private fun TitleText() {
+
+ Text(text = CampaignStrings.get(CampaignStrings.HeaderKey),
+ color = PhotonColors.LightGrey05,
+ textAlign = TextAlign.Left,
+ fontWeight = FontWeight.Bold,
+ fontSize = 24.sp,
+ lineHeight = 34.sp,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+
+ @Composable
+ private fun MainText() {
+
+ Text(text = CampaignStrings.get(CampaignStrings.BodyKey),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(
+ start = 0.dp,
+ end = 0.dp,
+ bottom = 18.dp,
+ ),
+ color = PhotonColors.LightGrey05,
+ fontSize = 18.sp,
+ textAlign = TextAlign.Left,
+ )
+ }
+
+ @Composable
+ private fun Button1(alternateLayout: Boolean) {
+ Button(
+ onClick = {
+ var locale = CampaignStrings.getLocale()
+ if (locale == "pt") {
+ locale = "pt-BR"
+ }
+ (activity as HomeActivity).openToBrowserAndLoad(
+ searchTermOrURL = "https://survey.torproject.org/index.php/923269?lang=${locale}",
+ newTab = true,
+ from = BrowserDirection.FromHome,
+ )
+ },
+ colors = ButtonDefaults.buttonColors(
+ backgroundColor = PhotonColors.Violet60),
+ shape = RoundedCornerShape(4.dp),
+ modifier = Modifier.padding(0.dp)
+ .fillMaxWidth(fraction = if (alternateLayout) 0.5f else 1f),
+
+ ) {
+ Text(text = CampaignStrings.get(CampaignStrings.CTAKey),
+ color = PhotonColors.LightGrey05,
+ textAlign = TextAlign.Center,
+ fontSize = 18.sp,
+ modifier = Modifier.padding(8.dp))
+ }
+ }
+
+ @Composable
+ private fun Button2() {
+ Button(
+ onClick = {
+ binding.campaignBox.visibility = View.GONE
+ binding.onionPatternImage.visibility = View.VISIBLE
+ context?.components?.settings?.hideCampaign = true
+ },
+ colors = ButtonDefaults.buttonColors(
+ backgroundColor = PhotonColors.Violet90),
+ shape = RoundedCornerShape(4.dp),
+ modifier = Modifier.padding(0.dp)
+ .fillMaxWidth()
+ ) {
+ Text(text = CampaignStrings.get(CampaignStrings.DismissKey),
+ color = PhotonColors.Violet20,
+ textAlign = TextAlign.Center,
+ fontSize = 18.sp,
+ modifier = Modifier.padding(8.dp))
+ }
+ }
}
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/CampaignStrings.kt
=====================================
@@ -0,0 +1,64 @@
+package org.mozilla.fenix.tor
+
+import java.util.Locale
+
+object CampaignStrings {
+
+ val HeaderKey = "key_header"
+ val BodyKey = "key_body"
+ val CTAKey = "key_cta"
+ val DismissKey = "key_dismiss"
+ val CloseKey = "key_close"
+
+ private val translations: HashMap<String, HashMap<String, String>> = hashMapOf(
+ "en" to hashMapOf(
+ HeaderKey to "We’d love your feedback",
+ BodyKey to "Help us improve Tor Browser by completing this 10-minute survey.",
+ CTAKey to "Launch the survey",
+ DismissKey to "Dismiss",
+ CloseKey to "Close",
+ ),
+ "es" to hashMapOf(
+ HeaderKey to "Danos tu opinión",
+ BodyKey to "Ayúdanos a mejorar el Navegador Tor completando esta encuesta de 10 minutos.",
+ CTAKey to "Iniciar la encuesta",
+ DismissKey to "Cerrar",
+ CloseKey to "Cerrar",
+ ),
+ "ru" to hashMapOf(
+ HeaderKey to "Мы будем рады вашим отзывам",
+ BodyKey to "Помогите нам улучшить браузер Tor, пройдя 10-минутный опрос.",
+ CTAKey to "Начать опрос",
+ DismissKey to "Закрыть",
+ CloseKey to "Закрыть",
+ ),
+ "fr" to hashMapOf(
+ HeaderKey to "Nous serions ravis d’avoir votre avis !",
+ BodyKey to "Aidez-nous à améliorer le navigateur Tor en répondant à cette enquête de 10 minutes.",
+ CTAKey to "Lancer l'enquête",
+ DismissKey to "Fermer",
+ CloseKey to "Fermer",
+ ),
+ "pt" to hashMapOf(
+ HeaderKey to "Adoraríamos ouvir sua opinião",
+ BodyKey to "Ajude-nos a melhorar o Navegador Tor respondendo a esta pesquisa de 10 minutos.",
+ CTAKey to "Iniciar a pesquisa",
+ DismissKey to "Dispensar",
+ CloseKey to "Fechar"
+ ),
+ )
+
+ fun getLocale(): String {
+ // TODO: do we care about spoofEnglish setting?
+ return Locale.getDefault().getLanguage();
+ }
+
+
+ fun get(key: String): String {
+ val localeStrings = translations.get(getLocale())
+ if (localeStrings == null) {
+ return ""
+ }
+ return localeStrings.get(key) ?: ""
+ }
+}
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
=====================================
@@ -43,15 +43,7 @@ import org.mozilla.fenix.components.settings.lazyFeatureFlagPreference
import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getPreferenceKey
-import org.mozilla.fenix.nimbus.CookieBannersSection
import org.mozilla.fenix.nimbus.FxNimbus
-import org.mozilla.fenix.nimbus.HomeScreenSection
-import org.mozilla.fenix.nimbus.Mr2022Section
-import org.mozilla.fenix.nimbus.QueryParameterStrippingSection
-import org.mozilla.fenix.nimbus.QueryParameterStrippingSection.QUERY_PARAMETER_STRIPPING
-import org.mozilla.fenix.nimbus.QueryParameterStrippingSection.QUERY_PARAMETER_STRIPPING_ALLOW_LIST
-import org.mozilla.fenix.nimbus.QueryParameterStrippingSection.QUERY_PARAMETER_STRIPPING_PMB
-import org.mozilla.fenix.nimbus.QueryParameterStrippingSection.QUERY_PARAMETER_STRIPPING_STRIP_LIST
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataOnQuitType
import org.mozilla.fenix.settings.logins.SavedLoginsSortingStrategyMenu
@@ -2116,4 +2108,9 @@ class Settings(private val appContext: Context) : PreferencesHolder {
appContext.getPreferenceKey(R.string.pref_key_quick_start),
default = false,
)
+
+ var hideCampaign by booleanPreference(
+ appContext.getPreferenceKey(R.string.pref_key_hide_campaign_2025_ux_survey),
+ default = false,
+ )
}
=====================================
mobile/android/fenix/app/src/main/res/drawable/campaign_hand.xml
=====================================
@@ -0,0 +1,21 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
+ android:viewportWidth="36"
+ android:viewportHeight="36"
+ android:width="36dp"
+ android:height="36dp">
+ <path
+ android:pathData="M4.861 9.147c0.94 -0.657 2.357 -0.531 3.201 0.166l-0.968 -1.407c-0.779 -1.111 -0.5 -2.313 0.612 -3.093 1.112 -0.777 4.263 1.312 4.263 1.312 -0.786 -1.122 -0.639 -2.544 0.483 -3.331 1.122 -0.784 2.67 -0.513 3.456 0.611l10.42 14.72L25 31l-11.083 -4.042L4.25 12.625c-0.793 -1.129 -0.519 -2.686 0.611 -3.478z"
+ android:fillColor="#EF9645" />
+ <path
+ android:pathData="M2.695 17.336s-1.132 -1.65 0.519 -2.781c1.649 -1.131 2.78 0.518 2.78 0.518l5.251 7.658c0.181 -0.302 0.379 -0.6 0.6 -0.894L4.557 11.21s-1.131 -1.649 0.519 -2.78c1.649 -1.131 2.78 0.518 2.78 0.518l6.855 9.997c0.255 -0.208 0.516 -0.417 0.785 -0.622L7.549 6.732s-1.131 -1.649 0.519 -2.78c1.649 -1.131 2.78 0.518 2.78 0.518l7.947 11.589c0.292 -0.179 0.581 -0.334 0.871 -0.498L12.238 4.729s-1.131 -1.649 0.518 -2.78c1.649 -1.131 2.78 0.518 2.78 0.518l7.854 11.454 1.194 1.742c-4.948 3.394 -5.419 9.779 -2.592 13.902 0.565 0.825 1.39 0.26 1.39 0.26 -3.393 -4.949 -2.357 -10.51 2.592 -13.903L24.515 8.62s-0.545 -1.924 1.378 -2.47c1.924 -0.545 2.47 1.379 2.47 1.379l1.685 5.004c0.668 1.984 1.379 3.961 2.32 5.831 2.657 5.28 1.07 11.842 -3.94 15.279 -5.465 3.747 -12.936 2.354 -16.684 -3.11L2.695 17.336z"
+ android:fillColor="#FFDC5D" />
+ <path
+ android:pathData="M12 32.042C8 32.042 3.958 28 3.958 24c0 -0.553 -0.405 -1 -0.958 -1s-1.042 0.447 -1.042 1C1.958 30 6 34.042 12 34.042c0.553 0 1 -0.489 1 -1.042s-0.447 -0.958 -1 -0.958z"
+ android:fillColor="#5DADEC" />
+ <path
+ android:pathData="M7 34c-3 0 -5 -2 -5 -5 0 -0.553 -0.447 -1 -1 -1s-1 0.447 -1 1c0 4 3 7 7 7 0.553 0 1 -0.447 1 -1s-0.447 -1 -1 -1zM24 2c-0.552 0 -1 0.448 -1 1s0.448 1 1 1c4 0 8 3.589 8 8 0 0.552 0.448 1 1 1s1 -0.448 1 -1c0 -5.514 -4 -10 -10 -10z"
+ android:fillColor="#5DADEC" />
+ <path
+ android:pathData="M29 0.042c-0.552 0 -1 0.406 -1 0.958s0.448 1.042 1 1.042c3 0 4.958 2.225 4.958 4.958 0 0.552 0.489 1 1.042 1s0.958 -0.448 0.958 -1C35.958 3.163 33 0.042 29 0.042z"
+ android:fillColor="#5DADEC" />
+</vector>
\ No newline at end of file
=====================================
mobile/android/fenix/app/src/main/res/layout/fragment_home.xml
=====================================
@@ -134,6 +134,12 @@
</com.google.android.material.appbar.AppBarLayout>
+ <androidx.compose.ui.platform.ComposeView
+ android:id="@+id/campaignBox"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/sessionControlRecyclerView"
android:layout_width="match_parent"
=====================================
mobile/android/fenix/app/src/main/res/values/preference_keys.xml
=====================================
@@ -430,4 +430,5 @@
<string name="pref_key_tor_network_settings_bridges_enabled">pref_key_tor_network_settings_bridges_enabled</string>
<string name="pref_key_spoof_english" translatable="false">pref_key_spoof_english</string>
+ <string name="pref_key_hide_campaign_2025_ux_survey" translatable="false">pref_key_hide_campaign_2025_ux_survey_test</string>
</resources>
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/b23a82…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/b23a82…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/torbrowser-launcher] Deleted branch AppArmor-updates-for-current-Debian
by intrigeri (@intrigeri) 18 Mar '25
by intrigeri (@intrigeri) 18 Mar '25
18 Mar '25
intrigeri deleted branch AppArmor-updates-for-current-Debian at The Tor Project / Applications / torbrowser-launcher
--
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/torbrowser-launcher][main] 5 commits: AppArmor: allow unprivileged user namespaces
by intrigeri (@intrigeri) 18 Mar '25
by intrigeri (@intrigeri) 18 Mar '25
18 Mar '25
intrigeri pushed to branch main at The Tor Project / Applications / torbrowser-launcher
Commits:
692e28a1 by intrigeri at 2025-03-18T13:49:53+00:00
AppArmor: allow unprivileged user namespaces
Firefox uses userns to set up its own sandboxing.
On Debian, AppArmor was already allowing this by default, until a recent
upload (that is now in Trixie) updated the features pinning to a version that
now mediates usage of userns, so this functionality is now blocked by profiles
that don't explicitly allow it. Let's repair this.
Also reported as Debian#1098845.
- - - - -
91db109a by intrigeri at 2025-03-18T14:02:01+00:00
AppArmor: allow reading cgroups-v2 CPU bandwidth quota information
Firefox uses this info to determine how many CPUs the current thread actually
has access to, which seems like a reasonable thing to do for an app like Firefox
which manages a bunch of child processes. The call chain is: get_num_cpus →
cgroups_num_cpus → init_cgroups → load_cgroups → cpu_quota → max → "cpu.max".
- - - - -
7772a1ea by intrigeri at 2025-03-18T14:15:17+00:00
AppArmor: allow executing Firefox' own VA-API probe utility
This is necessary for Tor Browser to determine if VA-API is supported by the
host system, which in turn is needed to enable video hardware decoding.
- - - - -
9eb8686d by intrigeri at 2025-03-18T14:18:41+00:00
AppArmor: allow reading intel-media-driver feature files
Firefox reads these files when it runs the vaapitest tool and the VAAPI driver
for the Intel GEN8+ Graphics family is installed.
- - - - -
479b8f53 by intrigeri at 2025-03-18T17:00:32+00:00
Merge branch 'AppArmor-updates-for-current-Debian' into 'main'
AppArmor: various updates including 1 important fix for Debian Trixie
See merge request tpo/applications/torbrowser-launcher!24
- - - - -
1 changed file:
- apparmor/torbrowser.Browser.firefox
Changes:
=====================================
apparmor/torbrowser.Browser.firefox
=====================================
@@ -13,6 +13,8 @@ profile torbrowser_firefox @{torbrowser_firefox_executable} {
#include if exists <abstractions/vulkan>
#include if exists <abstractions/dbus-session-strict>
+ userns,
+
deny capability sys_ptrace,
# Uncomment the following lines if you want to give the Tor Browser read-write
@@ -94,6 +96,10 @@ profile torbrowser_firefox @{torbrowser_firefox_executable} {
owner @{torbrowser_home_dir}/TorBrowser/Tor/*.so.* mr,
owner @{torbrowser_home_dir}/TorBrowser/Tor/libstdc++/*.so mr,
owner @{torbrowser_home_dir}/TorBrowser/Tor/libstdc++/*.so.* mr,
+ owner @{torbrowser_home_dir}/vaapitest ix,
+
+ # intel-media-driver
+ /etc/igfx_user_feature*.txt r,
# parent Firefox process when restarting after upgrade, Web Content processes
owner @{torbrowser_firefox_executable} pxmr -> torbrowser_firefox,
@@ -121,6 +127,7 @@ profile torbrowser_firefox @{torbrowser_firefox_executable} {
/sys/devices/system/node/ r,
/sys/devices/system/node/node[0-9]*/meminfo r,
/sys/fs/cgroup/cpu,cpuacct/{,user.slice/}cpu.cfs_quota_us r,
+ /sys/fs/cgroup/user.slice/user-[0-9]*.slice/user@[0-9]*.service/app.slice/app-gnome-torbrowser-[0-9]*.scope/cpu.max r,
deny /sys/class/input/ r,
deny /sys/devices/virtual/block/*/uevent r,
View it on GitLab: https://gitlab.torproject.org/tpo/applications/torbrowser-launcher/-/compar…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/torbrowser-launcher/-/compar…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/torbrowser-launcher][main] fix: window icon under wayland
by boklm (@boklm) 18 Mar '25
by boklm (@boklm) 18 Mar '25
18 Mar '25
boklm pushed to branch main at The Tor Project / Applications / torbrowser-launcher
Commits:
a734238b by Integral at 2025-03-18T17:55:44+01:00
fix: window icon under wayland
Currently, when running the launcher on Wayland, the window icon
will fallback to the generic Wayland icon. Set desktop filename to
solve this problem.
- - - - -
1 changed file:
- torbrowser_launcher/__init__.py
Changes:
=====================================
torbrowser_launcher/__init__.py
=====================================
@@ -81,6 +81,9 @@ def main():
common = Common(tor_browser_launcher_version)
app = Application()
+ if "WAYLAND_DISPLAY" in os.environ:
+ app.setDesktopFileName("torbrowser")
+
# Open the window
gui = None
View it on GitLab: https://gitlab.torproject.org/tpo/applications/torbrowser-launcher/-/commit…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/torbrowser-launcher/-/commit…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-128.8.0esr-14.5-1] fixup! TB 40041 [android]: Implement Tor Network Settings
by Dan Ballard (@dan) 18 Mar '25
by Dan Ballard (@dan) 18 Mar '25
18 Mar '25
Dan Ballard pushed to branch tor-browser-128.8.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits:
b23a826c by clairehurst at 2025-03-18T16:30:23+00:00
fixup! TB 40041 [android]: Implement Tor Network Settings
- - - - -
3 changed files:
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt
- mobile/android/fenix/app/src/main/res/values/preference_keys.xml
- mobile/android/fenix/app/src/main/res/xml/preferences.xml
Changes:
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt
=====================================
@@ -796,6 +796,17 @@ class SettingsFragment : PreferenceFragmentCompat(), UserInteractionHandler {
true
}
}
+ requirePreference<Preference>(R.string.pref_key_about_config_shortcut).apply {
+ isVisible = requireContext().settings().showSecretDebugMenuThisSession || Config.channel == ReleaseChannel.Debug
+ setOnPreferenceClickListener {
+ (requireActivity() as HomeActivity).openToBrowserAndLoad(
+ searchTermOrURL = "about:config",
+ from = BrowserDirection.FromSettings,
+ newTab = true,
+ )
+ true
+ }
+ }
}
@VisibleForTesting
=====================================
mobile/android/fenix/app/src/main/res/values/preference_keys.xml
=====================================
@@ -415,6 +415,7 @@
<string name="pref_key_tor_network_settings_bridge_config" translatable="false">pref_key_tor_network_settings_bridge_config</string>
<string name="pref_key_tor_logs" translatable="false">pref_key_tor_logs</string>
<string name="pref_key_use_html_connection_ui" translatable="false">pref_key_use_html_connection_ui</string> <!-- Changing the pref_key should reset it to off for users that had it enabled -->
+ <string name="pref_key_about_config_shortcut" translatable="false">pref_key_about_config_shortcut</string>
<string name="pref_key_tor_network_settings_bridge_config_explanation" translatable="false">pref_key_tor_network_settings_bridge_config_explanation</string>
<string name="pref_key_tor_network_settings_bridge_config_toggle" translatable="false">pref_key_tor_network_settings_bridge_config_toggle</string>
<string name="pref_key_tor_network_settings_bridge_config_builtin_bridge_obfs4" translatable="false">pref_key_tor_network_settings_bridge_config_builtin_bridge_obfs4</string>
=====================================
mobile/android/fenix/app/src/main/res/xml/preferences.xml
=====================================
@@ -194,6 +194,12 @@
android:title="@string/preferences_tor_logs"
android:summary="@string/preferences_tor_logs_description" />
+ <Preference
+ android:key="@string/pref_key_about_config_shortcut"
+ app:iconSpaceReserved="false"
+ android:title="about:config"/>
+
+
<!-- Auto connect -->
<!-- Tor Logs -->
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/b23a826…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/b23a826…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-128.8.0esr-14.5-1] 6 commits: fixup! TB 40933: Add tor-launcher functionality
by henry (@henry) 18 Mar '25
by henry (@henry) 18 Mar '25
18 Mar '25
henry pushed to branch tor-browser-128.8.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits:
202a08d2 by Henry Wilkes at 2025-03-18T15:25:24+00:00
fixup! TB 40933: Add tor-launcher functionality
TB 43405: Split TorProvider writeSettings into separate methods for the
proxy, firewall and bridges settings.
We also call TorSettings.setTorProvider instead of
TorProvider.writeSettings so that TorSettings can handle the application
errors.
- - - - -
a88b1fd3 by Henry Wilkes at 2025-03-18T15:25:25+00:00
fixup! TB 40597: Implement TorSettings module
TB 43405: Do not allow string values for proxy and firewall ports. And
do not allow a proxy username without a password or vis versa.
- - - - -
ebfa2591 by Henry Wilkes at 2025-03-18T15:25:26+00:00
fixup! TB 40597: Implement TorSettings module
TB 43405: TorSettings handles failures to apply Tor settings.
We update TorSettings.#applySettings to catch TorProvider write errors
and signal this error with "ApplyError".
We also keep track of which group of settings have failed so that we can
restore them on the user's request.
- - - - -
cd596922 by Henry Wilkes at 2025-03-18T15:25:27+00:00
fixup! TB 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
TB 43405: Add some validation to the Advanced connection settings
dialog.
- - - - -
58ad673d by Henry Wilkes at 2025-03-18T15:25:27+00:00
TB 43405: Show a prompt whenever we fail to apply Tor settings.
- - - - -
eb9525d5 by Henry Wilkes at 2025-03-18T15:25:28+00:00
fixup! Tor Browser strings
TB 43405: Add strings for tor settings error notification.
- - - - -
8 changed files:
- browser/components/BrowserGlue.sys.mjs
- browser/components/torpreferences/content/connectionSettingsDialog.js
- browser/components/torpreferences/content/connectionSettingsDialog.xhtml
- + browser/modules/TorSettingsNotification.sys.mjs
- browser/modules/moz.build
- toolkit/components/tor-launcher/TorProvider.sys.mjs
- toolkit/locales/en-US/toolkit/global/tor-browser.ftl
- toolkit/modules/TorSettings.sys.mjs
Changes:
=====================================
browser/components/BrowserGlue.sys.mjs
=====================================
@@ -96,6 +96,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
TorConnect: "resource://gre/modules/TorConnect.sys.mjs",
TorConnectTopics: "resource://gre/modules/TorConnect.sys.mjs",
TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
+ TorSettingsNotification:
+ "resource:///modules/TorSettingsNotification.sys.mjs",
UIState: "resource://services-sync/UIState.sys.mjs",
UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
WebChannel: "resource://gre/modules/WebChannel.sys.mjs",
@@ -2030,6 +2032,8 @@ BrowserGlue.prototype = {
lazy.TorProviderBuilder.firstWindowLoaded();
+ lazy.TorSettingsNotification.ready();
+
ClipboardPrivacy.startup();
this._firstWindowTelemetry(aWindow);
=====================================
browser/components/torpreferences/content/connectionSettingsDialog.js
=====================================
@@ -5,6 +5,7 @@ const { TorSettings, TorProxyType } = ChromeUtils.importESModule(
);
const gConnectionSettingsDialog = {
+ _acceptButton: null,
_useProxyCheckbox: null,
_proxyTypeLabel: null,
_proxyTypeMenulist: null,
@@ -38,25 +39,43 @@ const gConnectionSettingsDialog = {
"input#torPreferences-connection-textboxAllowedPorts",
},
- // disables the provided list of elements
- _setElementsDisabled(elements, disabled) {
- for (let currentElement of elements) {
- currentElement.disabled = disabled;
- }
- },
+ /**
+ * The "proxy" and "firewall" settings to pass on to TorSettings.
+ *
+ * Each group is `null` whilst the inputs are invalid.
+ *
+ * @type {{proxy: ?object, firewall: ?object}}
+ */
+ _settings: { proxy: null, firewall: null },
init() {
+ const currentSettings = TorSettings.getSettings();
+
+ const dialog = document.getElementById("torPreferences-connection-dialog");
+ dialog.addEventListener("dialogaccept", event => {
+ if (!this._settings.proxy || !this._settings.firewall) {
+ // Do not close yet.
+ event.preventDefault();
+ return;
+ }
+ // TODO: Maybe wait for the method to resolve before closing. Although
+ // this can take a few seconds. See tor-browser#43467.
+ TorSettings.changeSettings(this._settings);
+ });
+ this._acceptButton = dialog.getButton("accept");
+
const selectors = this.selectors;
// Local Proxy
this._useProxyCheckbox = document.querySelector(selectors.useProxyCheckbox);
+ this._useProxyCheckbox.checked = currentSettings.proxy.enabled;
this._useProxyCheckbox.addEventListener("command", () => {
- const checked = this._useProxyCheckbox.checked;
- this.onToggleProxy(checked);
+ this.updateProxyType();
});
+
this._proxyTypeLabel = document.querySelector(selectors.proxyTypeLabel);
- let mockProxies = [
+ const mockProxies = [
{
value: TorProxyType.Socks4,
l10nId: "tor-advanced-dialog-proxy-socks4-menuitem",
@@ -72,15 +91,17 @@ const gConnectionSettingsDialog = {
];
this._proxyTypeMenulist = document.querySelector(selectors.proxyTypeList);
this._proxyTypeMenulist.addEventListener("command", () => {
- const value = this._proxyTypeMenulist.value;
- this.onSelectProxyType(value);
+ this.updateProxyType();
});
- for (let currentProxy of mockProxies) {
+ for (const currentProxy of mockProxies) {
let menuEntry = window.document.createXULElement("menuitem");
menuEntry.setAttribute("value", currentProxy.value);
menuEntry.setAttribute("data-l10n-id", currentProxy.l10nId);
this._proxyTypeMenulist.querySelector("menupopup").appendChild(menuEntry);
}
+ this._proxyTypeMenulist.value = currentSettings.proxy.enabled
+ ? currentSettings.proxy.type
+ : "";
this._proxyAddressLabel = document.querySelector(
selectors.proxyAddressLabel
@@ -89,13 +110,15 @@ const gConnectionSettingsDialog = {
selectors.proxyAddressTextbox
);
this._proxyAddressTextbox.addEventListener("blur", () => {
+ // If the address includes a port move it to the port input instead.
let value = this._proxyAddressTextbox.value.trim();
let colon = value.lastIndexOf(":");
if (colon != -1) {
- let maybePort = parseInt(value.substr(colon + 1));
- if (!isNaN(maybePort) && maybePort > 0 && maybePort < 65536) {
+ let maybePort = this.parsePort(value.substr(colon + 1));
+ if (maybePort !== null) {
this._proxyAddressTextbox.value = value.substr(0, colon);
this._proxyPortTextbox.value = maybePort;
+ this.updateProxy();
}
}
});
@@ -114,23 +137,36 @@ const gConnectionSettingsDialog = {
selectors.proxyPasswordTextbox
);
- this.onToggleProxy(false);
- if (TorSettings.proxy.enabled) {
- this.onToggleProxy(true);
- this.onSelectProxyType(TorSettings.proxy.type);
- this._proxyAddressTextbox.value = TorSettings.proxy.address;
- this._proxyPortTextbox.value = TorSettings.proxy.port;
- this._proxyUsernameTextbox.value = TorSettings.proxy.username;
- this._proxyPasswordTextbox.value = TorSettings.proxy.password;
+ if (currentSettings.proxy.enabled) {
+ this._proxyAddressTextbox.value = currentSettings.proxy.address;
+ this._proxyPortTextbox.value = currentSettings.proxy.port;
+ this._proxyUsernameTextbox.value = currentSettings.proxy.username;
+ this._proxyPasswordTextbox.value = currentSettings.proxy.password;
+ } else {
+ this._proxyAddressTextbox.value = "";
+ this._proxyPortTextbox.value = "";
+ this._proxyUsernameTextbox.value = "";
+ this._proxyPasswordTextbox.value = "";
+ }
+
+ for (const el of [
+ this._proxyAddressTextbox,
+ this._proxyPortTextbox,
+ this._proxyUsernameTextbox,
+ this._proxyPasswordTextbox,
+ ]) {
+ el.addEventListener("input", () => {
+ this.updateProxy();
+ });
}
// Local firewall
this._useFirewallCheckbox = document.querySelector(
selectors.useFirewallCheckbox
);
+ this._useFirewallCheckbox.checked = currentSettings.firewall.enabled;
this._useFirewallCheckbox.addEventListener("command", () => {
- const checked = this._useFirewallCheckbox.checked;
- this.onToggleFirewall(checked);
+ this.updateFirewallEnabled();
});
this._allowedPortsLabel = document.querySelector(
selectors.firewallAllowedPortsLabel
@@ -138,182 +174,161 @@ const gConnectionSettingsDialog = {
this._allowedPortsTextbox = document.querySelector(
selectors.firewallAllowedPortsTextbox
);
+ this._allowedPortsTextbox.value = currentSettings.firewall.enabled
+ ? currentSettings.firewall.allowed_ports.join(",")
+ : "80,443";
- this.onToggleFirewall(false);
- if (TorSettings.firewall.enabled) {
- this.onToggleFirewall(true);
- this._allowedPortsTextbox.value =
- TorSettings.firewall.allowed_ports.join(", ");
- }
-
- const dialog = document.getElementById("torPreferences-connection-dialog");
- dialog.addEventListener("dialogaccept", () => {
- this._applySettings();
+ this._allowedPortsTextbox.addEventListener("input", () => {
+ this.updateFirewall();
});
- },
- // callback when proxy is toggled
- onToggleProxy(enabled) {
- this._useProxyCheckbox.checked = enabled;
- let disabled = !enabled;
+ this.updateProxyType();
+ this.updateFirewallEnabled();
+ },
- this._setElementsDisabled(
- [
- this._proxyTypeLabel,
- this._proxyTypeMenulist,
- this._proxyAddressLabel,
- this._proxyAddressTextbox,
- this._proxyPortLabel,
- this._proxyPortTextbox,
- this._proxyUsernameLabel,
- this._proxyUsernameTextbox,
- this._proxyPasswordLabel,
- this._proxyPasswordTextbox,
- ],
- disabled
- );
- if (enabled) {
- this.onSelectProxyType(this._proxyTypeMenulist.value);
+ /**
+ * Convert a string into a port number.
+ *
+ * @param {string} portStr - The string to convert.
+ * @returns {?integer} - The port number, or `null` if the given string could
+ * not be converted.
+ */
+ parsePort(portStr) {
+ const portRegex = /^[1-9][0-9]*$/; // Strictly-positive decimal integer.
+ if (!portRegex.test(portStr)) {
+ return null;
}
+ const port = parseInt(portStr, 10);
+ if (TorSettings.validPort(port)) {
+ return port;
+ }
+ return null;
},
- // callback when proxy type is changed
- onSelectProxyType(value) {
- if (typeof value === "string") {
- value = parseInt(value);
- }
+ /**
+ * Update the disabled state of the accept button.
+ */
+ updateAcceptButton() {
+ this._acceptButton.disabled =
+ !this._settings.proxy || !this._settings.firewall;
+ },
- this._proxyTypeMenulist.value = value;
- switch (value) {
- case TorProxyType.Invalid: {
- this._setElementsDisabled(
- [
- this._proxyAddressLabel,
- this._proxyAddressTextbox,
- this._proxyPortLabel,
- this._proxyPortTextbox,
- this._proxyUsernameLabel,
- this._proxyUsernameTextbox,
- this._proxyPasswordLabel,
- this._proxyPasswordTextbox,
- ],
- true
- ); // DISABLE
+ /**
+ * Update the UI when the proxy setting is enabled or disabled, or the proxy
+ * type changes.
+ */
+ updateProxyType() {
+ const enabled = this._useProxyCheckbox.checked;
+ const haveType = enabled && Boolean(this._proxyTypeMenulist.value);
+ const type = parseInt(this._proxyTypeMenulist.value, 10);
- this._proxyAddressTextbox.value = "";
- this._proxyPortTextbox.value = "";
- this._proxyUsernameTextbox.value = "";
- this._proxyPasswordTextbox.value = "";
- break;
- }
- case TorProxyType.Socks4: {
- this._setElementsDisabled(
- [
- this._proxyAddressLabel,
- this._proxyAddressTextbox,
- this._proxyPortLabel,
- this._proxyPortTextbox,
- ],
- false
- ); // ENABLE
- this._setElementsDisabled(
- [
- this._proxyUsernameLabel,
- this._proxyUsernameTextbox,
- this._proxyPasswordLabel,
- this._proxyPasswordTextbox,
- ],
- true
- ); // DISABLE
+ this._proxyTypeLabel.disabled = !enabled;
+ this._proxyTypeMenulist.disabled = !enabled;
+ this._proxyAddressLabel.disabled = !haveType;
+ this._proxyAddressTextbox.disabled = !haveType;
+ this._proxyPortLabel.disabled = !haveType;
+ this._proxyPortTextbox.disabled = !haveType;
+ this._proxyUsernameTextbox.disabled =
+ !haveType || type === TorProxyType.Socks4;
+ this._proxyPasswordTextbox.disabled =
+ !haveType || type === TorProxyType.Socks4;
+ if (type === TorProxyType.Socks4) {
+ // Clear unused value.
+ this._proxyUsernameTextbox.value = "";
+ this._proxyPasswordTextbox.value = "";
+ }
- this._proxyUsernameTextbox.value = "";
- this._proxyPasswordTextbox.value = "";
- break;
- }
- case TorProxyType.Socks5:
- case TorProxyType.HTTPS: {
- this._setElementsDisabled(
- [
- this._proxyAddressLabel,
- this._proxyAddressTextbox,
- this._proxyPortLabel,
- this._proxyPortTextbox,
- this._proxyUsernameLabel,
- this._proxyUsernameTextbox,
- this._proxyPasswordLabel,
- this._proxyPasswordTextbox,
- ],
- false
- ); // ENABLE
- break;
+ this.updateProxy();
+ },
+
+ /**
+ * Update the dialog's stored proxy values.
+ */
+ updateProxy() {
+ if (this._useProxyCheckbox.checked) {
+ const typeStr = this._proxyTypeMenulist.value;
+ const type = parseInt(typeStr, 10);
+ // TODO: Validate the address. See tor-browser#43467.
+ const address = this._proxyAddressTextbox.value;
+ const portStr = this._proxyPortTextbox.value;
+ const username =
+ type === TorProxyType.Socks4 ? "" : this._proxyUsernameTextbox.value;
+ const password =
+ type === TorProxyType.Socks4 ? "" : this._proxyPasswordTextbox.value;
+ const port = parseInt(portStr, 10);
+ if (
+ !typeStr ||
+ !address ||
+ !portStr ||
+ !TorSettings.validPort(port) ||
+ // SOCKS5 needs either both username and password, or neither.
+ (type === TorProxyType.Socks5 &&
+ !TorSettings.validSocks5Credentials(username, password))
+ ) {
+ // Invalid.
+ this._settings.proxy = null;
+ } else {
+ this._settings.proxy = {
+ enabled: true,
+ type,
+ address,
+ port,
+ username,
+ password,
+ };
}
+ } else {
+ this._settings.proxy = { enabled: false };
}
+
+ this.updateAcceptButton();
},
- // callback when firewall proxy is toggled
- onToggleFirewall(enabled) {
- this._useFirewallCheckbox.checked = enabled;
- let disabled = !enabled;
+ /**
+ * Update the UI when the firewall setting is enabled or disabled.
+ */
+ updateFirewallEnabled() {
+ const enabled = this._useFirewallCheckbox.checked;
+ this._allowedPortsLabel.disabled = !enabled;
+ this._allowedPortsTextbox.disabled = !enabled;
- this._setElementsDisabled(
- [this._allowedPortsLabel, this._allowedPortsTextbox],
- disabled
- );
+ this.updateFirewall();
},
- // pushes settings from UI to tor
- _applySettings() {
- const type = this._useProxyCheckbox.checked
- ? parseInt(this._proxyTypeMenulist.value)
- : TorProxyType.Invalid;
- const address = this._proxyAddressTextbox.value;
- const port = this._proxyPortTextbox.value;
- const username = this._proxyUsernameTextbox.value;
- const password = this._proxyPasswordTextbox.value;
- const settings = { proxy: {}, firewall: {} };
- switch (type) {
- case TorProxyType.Invalid:
- settings.proxy.enabled = false;
- break;
- case TorProxyType.Socks4:
- settings.proxy.enabled = true;
- settings.proxy.type = type;
- settings.proxy.address = address;
- settings.proxy.port = port;
- settings.proxy.username = "";
- settings.proxy.password = "";
- break;
- case TorProxyType.Socks5:
- settings.proxy.enabled = true;
- settings.proxy.type = type;
- settings.proxy.address = address;
- settings.proxy.port = port;
- settings.proxy.username = username;
- settings.proxy.password = password;
- break;
- case TorProxyType.HTTPS:
- settings.proxy.enabled = true;
- settings.proxy.type = type;
- settings.proxy.address = address;
- settings.proxy.port = port;
- settings.proxy.username = username;
- settings.proxy.password = password;
- break;
- }
-
- let portListString = this._useFirewallCheckbox.checked
- ? this._allowedPortsTextbox.value
- : "";
- if (portListString) {
- settings.firewall.enabled = true;
- settings.firewall.allowed_ports = portListString;
+ /**
+ * Update the dialog's stored firewall values.
+ */
+ updateFirewall() {
+ if (this._useFirewallCheckbox.checked) {
+ const portList = [];
+ let listInvalid = false;
+ for (const portStr of this._allowedPortsTextbox.value.split(
+ /(?:\s*,\s*)+/g
+ )) {
+ if (!portStr) {
+ // Trailing or leading comma.
+ continue;
+ }
+ const port = this.parsePort(portStr);
+ if (port === null) {
+ listInvalid = true;
+ break;
+ }
+ portList.push(port);
+ }
+ if (!listInvalid && portList.length) {
+ this._settings.firewall = {
+ enabled: true,
+ allowed_ports: portList,
+ };
+ } else {
+ this._settings.firewall = null;
+ }
} else {
- settings.firewall.enabled = false;
+ this._settings.firewall = { enabled: false };
}
- // FIXME: What if this fails? Should we prevent the dialog to close and show
- // an error?
- TorSettings.changeSettings(settings);
+ this.updateAcceptButton();
},
};
=====================================
browser/components/torpreferences/content/connectionSettingsDialog.xhtml
=====================================
@@ -61,6 +61,7 @@
<html:input
id="torPreferences-localProxy-textboxAddress"
type="text"
+ required="required"
class="torMarginFix"
data-l10n-id="tor-advanced-dialog-proxy-address-input"
/>
@@ -75,7 +76,8 @@
class="proxy-port-input torMarginFix"
hidespinbuttons="true"
type="number"
- min="0"
+ required="required"
+ min="1"
max="65535"
maxlength="5"
/>
@@ -121,11 +123,14 @@
/>
</hbox>
<hbox id="torPreferences-connection-hboxAllowedPorts" align="center">
+ <!-- NOTE: The pattern allows comma-separated strictly positive
+ - integers. In particular "0" is not allowed. -->
<html:input
id="torPreferences-connection-textboxAllowedPorts"
type="text"
+ required="required"
+ pattern="^(\s*,\s*)*[1-9][0-9]*((\s*,\s*)|([1-9][0-9]*))*$"
class="torMarginFix"
- value="80,443"
data-l10n-id="tor-advanced-dialog-firewall-ports-input"
/>
</hbox>
=====================================
browser/modules/TorSettingsNotification.sys.mjs
=====================================
@@ -0,0 +1,167 @@
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
+ TorSettings: "resource://gre/modules/TorSettings.sys.mjs",
+ TorSettingsTopics: "resource://gre/modules/TorSettings.sys.mjs",
+});
+
+ChromeUtils.defineLazyGetter(lazy, "NotificationStrings", function () {
+ return new Localization(["toolkit/global/tor-browser.ftl"]);
+});
+
+/**
+ * Shows a notification whenever we get an ApplyError.
+ */
+export const TorSettingsNotification = {
+ /**
+ * Whether we have already been initialised.
+ *
+ * @type {boolean}
+ */
+ _initialized: false,
+
+ /**
+ * Called when the UI is ready to show a notification.
+ */
+ ready() {
+ if (this._initialized) {
+ return;
+ }
+ this._initialized = true;
+ Services.obs.addObserver(this, lazy.TorSettingsTopics.ApplyError);
+
+ // Show the notification for each group of settings if they have an error
+ // that was triggered prior to `ready` being called.
+ this.showNotification("bridges");
+ this.showNotification("proxy");
+ this.showNotification("firewall");
+ },
+
+ observe(subject, topic) {
+ if (topic === lazy.TorSettingsTopics.ApplyError) {
+ this.showNotification(subject.wrappedJSObject.group);
+ }
+ },
+
+ /**
+ * A promise for the `showNotification` method to ensure we only show one
+ * notification at a time.
+ *
+ * @type {?Promise}
+ */
+ _notificationPromise: null,
+
+ /**
+ * Show a notification for the given group of settings if `TorSettings` has an
+ * error for them.
+ *
+ * @param {string} group - The settings group to show the notification for.
+ */
+ async showNotification(group) {
+ const prevNotificationPromise = this._notificationPromise;
+ let notificationComplete;
+ ({ promise: this._notificationPromise, resolve: notificationComplete } =
+ Promise.withResolvers());
+ // Only want to show one notification at a time, so queue behind the
+ // previous one.
+ await prevNotificationPromise;
+
+ // NOTE: We only show the notification for a single `group` at a time, even
+ // when TorSettings has errors for multiple groups. This keeps the strings
+ // simple and means we can show different buttons depending on `canUndo` for
+ // each group individually.
+ // If we do have multiple errors the notification for each group will simply
+ // queue behind each other.
+ try {
+ // Grab the latest error value, which may have changed since
+ // showNotification was first called.
+ const error = lazy.TorSettings.getApplyError(group);
+ if (!error) {
+ // No current error for this group.
+ return;
+ }
+
+ const { canUndo } = error;
+
+ let titleId;
+ let introId;
+ switch (group) {
+ case "bridges":
+ titleId = "tor-settings-failed-notification-title-bridges";
+ introId = "tor-settings-failed-notification-cause-bridges";
+ break;
+ case "proxy":
+ titleId = "tor-settings-failed-notification-title-proxy";
+ introId = "tor-settings-failed-notification-cause-proxy";
+ break;
+ case "firewall":
+ titleId = "tor-settings-failed-notification-title-firewall";
+ introId = "tor-settings-failed-notification-cause-firewall";
+ break;
+ }
+
+ const [
+ titleText,
+ introText,
+ bodyText,
+ primaryButtonText,
+ secondaryButtonText,
+ ] = await lazy.NotificationStrings.formatValues([
+ { id: titleId },
+ { id: introId },
+ {
+ id: canUndo
+ ? "tor-settings-failed-notification-body-undo"
+ : "tor-settings-failed-notification-body-default",
+ },
+ {
+ id: canUndo
+ ? "tor-settings-failed-notification-button-undo"
+ : "tor-settings-failed-notification-button-clear",
+ },
+ { id: "tor-settings-failed-notification-button-fix-myself" },
+ ]);
+
+ const propBag = await Services.prompt.asyncConfirmEx(
+ lazy.BrowserWindowTracker.getTopWindow()?.browsingContext ?? null,
+ Services.prompt.MODAL_TYPE_INTERNAL_WINDOW,
+ titleText,
+ // Concatenate the intro text and the body text. Really these should be
+ // separate paragraph elements, but the prompt service does not support
+ // this. We split them with a double newline, which will hopefully avoid
+ // the usual problems with concatenating localised strings.
+ `${introText}\n\n${bodyText}`,
+ Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
+ Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1,
+ primaryButtonText,
+ secondaryButtonText,
+ null,
+ null,
+ null,
+ {}
+ );
+
+ const buttonNum = propBag.get("buttonNumClicked");
+
+ if (buttonNum === 0) {
+ if (canUndo) {
+ // Wait for these methods in case they resolve the error for a pending
+ // showNotification call.
+ await lazy.TorSettings.undoFailedSettings(group);
+ } else {
+ await lazy.TorSettings.clearFailedSettings(group);
+ }
+ } else if (buttonNum === 1) {
+ let win = lazy.BrowserWindowTracker.getTopWindow();
+ if (!win) {
+ win = await lazy.BrowserWindowTracker.promiseOpenWindow();
+ }
+ // Open the preferences or switch to its tab and highlight the Tor log.
+ win.openPreferences("connection-viewlogs");
+ }
+ } finally {
+ notificationComplete();
+ }
+ },
+};
=====================================
browser/modules/moz.build
=====================================
@@ -129,6 +129,7 @@ EXTRA_JS_MODULES += [
"SelectionChangedMenulist.sys.mjs",
"SiteDataManager.sys.mjs",
"SitePermissions.sys.mjs",
+ "TorSettingsNotification.sys.mjs",
"TorUIUtils.sys.mjs",
"TransientPrefs.sys.mjs",
"URILoadingHelper.sys.mjs",
=====================================
toolkit/components/tor-launcher/TorProvider.sys.mjs
=====================================
@@ -210,7 +210,7 @@ export class TorProvider {
if (this.ownsTorDaemon) {
try {
await lazy.TorSettings.initializedPromise;
- await this.writeSettings();
+ await lazy.TorSettings.setTorProvider(this);
} catch (e) {
logger.warn(
"Failed to initialize TorSettings or to write our initial settings. Continuing the initialization anyway.",
@@ -252,44 +252,65 @@ export class TorProvider {
/**
* Send settings to the tor daemon.
*
- * This should only be called internally or by the TorSettings module.
+ * @param {Map<string, ?string>} torSettings - The key value pairs to pass in.
*/
- async writeSettings() {
- // Fetch the current settings.
- // We set the useTemporary parameter since we want to apply temporary
- // bridges if they are available.
- const settings = lazy.TorSettings.getSettings(true);
- logger.debug("TorProvider.writeSettings", settings);
+ async #writeSettings(torSettings) {
+ logger.debug("Mapped settings object", torSettings);
+
+ // NOTE: The order in which TorProvider.#writeSettings should match the
+ // order in which the configuration is passed onto setConf. In turn,
+ // TorControlPort.setConf should similarly ensure that the configuration
+ // reaches the tor process in the same order.
+ // In particular, we do not want a race where an earlier call to
+ // TorProvider.#writeSettings for overlapping settings can be delayed and
+ // override a later call.
+ await this.#controller.setConf(Array.from(torSettings));
+ }
+
+ /**
+ * Send bridge settings to the tor daemon.
+ *
+ * This should only be called by the `TorSettings` module.
+ *
+ * @param {TorBridgeSettings} bridges - The bridge settings to apply.
+ */
+ async writeBridgeSettings(bridges) {
+ logger.debug("TorProvider.writeBridgeSettings", bridges);
const torSettings = new Map();
// Bridges
- const haveBridges =
- settings.bridges?.enabled && !!settings.bridges.bridge_strings.length;
+ const haveBridges = bridges?.enabled && !!bridges.bridge_strings.length;
torSettings.set(TorConfigKeys.useBridges, haveBridges);
- if (haveBridges) {
- torSettings.set(
- TorConfigKeys.bridgeList,
- settings.bridges.bridge_strings
- );
- } else {
- torSettings.set(TorConfigKeys.bridgeList, null);
- }
+ torSettings.set(
+ TorConfigKeys.bridgeList,
+ haveBridges ? bridges.bridge_strings : null
+ );
+
+ await this.#writeSettings(torSettings);
+ }
+
+ /**
+ * Send proxy settings to the tor daemon.
+ *
+ * This should only be called by the `TorSettings` module.
+ *
+ * @param {TorProxySettings} proxy - The proxy settings to apply.
+ */
+ async writeProxySettings(proxy) {
+ logger.debug("TorProvider.writeProxySettings", proxy);
+ const torSettings = new Map();
- // Proxy
torSettings.set(TorConfigKeys.socks4Proxy, null);
torSettings.set(TorConfigKeys.socks5Proxy, null);
torSettings.set(TorConfigKeys.socks5ProxyUsername, null);
torSettings.set(TorConfigKeys.socks5ProxyPassword, null);
torSettings.set(TorConfigKeys.httpsProxy, null);
torSettings.set(TorConfigKeys.httpsProxyAuthenticator, null);
- if (settings.proxy && !settings.proxy.enabled) {
- settings.proxy.type = null;
- }
- const address = settings.proxy?.address;
- const port = settings.proxy?.port;
- const username = settings.proxy?.username;
- const password = settings.proxy?.password;
- switch (settings.proxy?.type) {
+
+ const type = proxy.enabled ? proxy.type : null;
+ const { address, port, username, password } = proxy;
+
+ switch (type) {
case lazy.TorProxyType.Socks4:
torSettings.set(TorConfigKeys.socks4Proxy, `${address}:${port}`);
break;
@@ -307,27 +328,28 @@ export class TorProvider {
break;
}
- // Firewall
- if (settings.firewall?.enabled) {
- const reachableAddresses = settings.firewall.allowed_ports
- .map(port => `*:${port}`)
- .join(",");
- torSettings.set(TorConfigKeys.reachableAddresses, reachableAddresses);
- } else {
- torSettings.set(TorConfigKeys.reachableAddresses, null);
- }
+ await this.#writeSettings(torSettings);
+ }
- logger.debug("Mapped settings object", settings, torSettings);
+ /**
+ * Send firewall settings to the tor daemon.
+ *
+ * This should only be called by the `TorSettings` module.
+ *
+ * @param {TorFirewallSettings} firewall - The firewall settings to apply.
+ */
+ async writeFirewallSettings(firewall) {
+ logger.debug("TorProvider.writeFirewallSettings", firewall);
+ const torSettings = new Map();
- // Send settings to the tor process.
- // NOTE: Since everything up to this point has been non-async, the order in
- // which TorProvider.writeSettings is called should match the order in which
- // the configuration is passed onto setConf. In turn, TorControlPort.setConf
- // should similarly ensure that the configuration reaches the tor process in
- // the same order.
- // In particular, we do not want a race where an earlier call to
- // TorProvider.writeSettings can be delayed and override a later call.
- await this.#controller.setConf(Array.from(torSettings));
+ torSettings.set(
+ TorConfigKeys.reachableAddresses,
+ firewall.enabled
+ ? firewall.allowed_ports.map(port => `*:${port}`).join(",")
+ : null
+ );
+
+ await this.#writeSettings(torSettings);
}
async flushSettings() {
=====================================
toolkit/locales/en-US/toolkit/global/tor-browser.ftl
=====================================
@@ -467,6 +467,29 @@ tor-advanced-dialog-firewall-ports-input-label = Allowed ports
tor-advanced-dialog-firewall-ports-input =
.placeholder = Comma-separated values
+## Tor settings error notification.
+
+# Shown when the user's Tor bridge settings could not be passed on to the Tor daemon.
+tor-settings-failed-notification-title-bridges = Your Tor bridge settings could not be applied
+# Shown when the user's Tor bridge settings could not be passed on to the Tor daemon.
+tor-settings-failed-notification-cause-bridges = This could be due to an invalid bridge address.
+# Shown when the user's Tor proxy settings could not be passed on to the Tor daemon.
+tor-settings-failed-notification-title-proxy = Your Tor proxy settings could not be applied
+# Shown when the user's Tor proxy settings could not be passed on to the Tor daemon.
+tor-settings-failed-notification-cause-proxy = This could be due to invalid proxy information.
+# Shown when the user's Tor firewall settings could not be passed on to the Tor daemon.
+tor-settings-failed-notification-title-firewall = Your Tor firewall settings could not be applied
+# Shown when the user's Tor firewall settings could not be passed on to the Tor daemon.
+tor-settings-failed-notification-cause-firewall = This could be due to invalid firewall information.
+tor-settings-failed-notification-body-undo = Until fixed, your Tor connection will continue to use your previous settings. You can either undo the latest changes to restore the previous working settings or check the Tor log to find and fix the issue yourself.
+tor-settings-failed-notification-body-default = Until fixed, your Tor connection will continue to use default settings. You can either clear the problematic settings to restore them to default or check the Tor log to find and fix the issue yourself.
+# Button to revert the latest user settings.
+tor-settings-failed-notification-button-undo = Undo changes
+# Button to clear the user settings.
+tor-settings-failed-notification-button-clear = Clear
+# Button for the user to declare that they will fix the problematic settings by themself.
+tor-settings-failed-notification-button-fix-myself = Fix myself
+
## About Tor Browser dialog.
# '<label data-l10n-name="project-link">' and '</label>' should wrap the link text for the Tor Project, and will link to the Tor Project web page.
=====================================
toolkit/modules/TorSettings.sys.mjs
=====================================
@@ -9,7 +9,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
Lox: "resource://gre/modules/Lox.sys.mjs",
LoxTopics: "resource://gre/modules/Lox.sys.mjs",
TorParsers: "resource://gre/modules/TorParsers.sys.mjs",
- TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "logger", () => {
@@ -23,6 +22,7 @@ ChromeUtils.defineLazyGetter(lazy, "logger", () => {
export const TorSettingsTopics = Object.freeze({
Ready: "torsettings:ready",
SettingsChanged: "torsettings:settings-changed",
+ ApplyError: "torsettings:apply-error",
});
/* Prefs used to store settings in TorBrowser prefs */
@@ -162,44 +162,74 @@ function arrayShuffle(array) {
return array;
}
+/**
+ * @typedef {Object} TorBridgeSettings
+ *
+ * Represents the Tor bridge settings.
+ *
+ * @property {boolean} enabled - Whether bridges are enabled.
+ * @property {integer} source - The source of bridges. One of the values in
+ * `TorBridgeSource`.
+ * @property {string} lox_id - The ID of the lox credentials to get bridges for.
+ * Or "" if not using a `Lox` `source`.
+ * @property {string} builtin_type - The name of the built-in bridge type. Or ""
+ * if not using a `BuiltIn` `source`.
+ * @property {string[]} bridge_strings - The bridge lines to be passed to the
+ * provider. Should be empty if and only if the `source` is `Invalid`.
+ */
+
+/**
+ * @typedef {Object} TorProxySettings
+ *
+ * Represents the Tor proxy settings.
+ *
+ * @property {boolean} enabled - Whether the proxy should be enabled.
+ * @property {integer} type - The proxy type. One of the values in
+ * `TorProxyType`.
+ * @property {string} address - The proxy address, or "" if the proxy is not
+ * being used.
+ * @property {integer} port - The proxy port, or 0 if the proxy is not being
+ * used.
+ * @property {string} username - The proxy user name, or "" if this is not
+ * needed.
+ * @property {string} password - The proxy password, or "" if this is not
+ * needed.
+ */
+
+/**
+ * @typedef {Object} TorFirewallSettings
+ *
+ * Represents the Tor firewall settings.
+ *
+ * @property {boolean} enabled - Whether the firewall settings should be
+ * enabled.
+ * @property {integer[]} allowed_ports - The list of ports that are allowed.
+ */
+
+/**
+ * @typedef {Object} TorCombinedSettings
+ *
+ * A combination of Tor settings.
+ *
+ * @property {TorBridgeSettings} bridges - The bridge settings.
+ * @property {TorProxySettings} proxy - The proxy settings.
+ * @property {TorFirewallSettings} firewall - The firewall settings.
+ */
+
/* TorSettings module */
class TorSettingsImpl {
/**
- * The underlying settings values.
+ * The default settings to use.
*
- * @type {object}
+ * @type {TorCombinedSettings}
*/
- #settings = {
+ #defaultSettings = {
bridges: {
- /**
- * Whether the bridges are enabled or not.
- *
- * @type {boolean}
- */
enabled: false,
source: TorBridgeSource.Invalid,
- /**
- * The lox id is used with the Lox "source", and remains set with the
- * stored value when other sources are used.
- *
- * @type {string}
- */
lox_id: "",
- /**
- * The built-in type to use when using the BuiltIn "source", or empty when
- * using any other source.
- *
- * @type {string}
- */
builtin_type: "",
- /**
- * The current bridge strings.
- *
- * Can only be non-empty if the "source" is not Invalid.
- *
- * @type {Array<string>}
- */
bridge_strings: [],
},
proxy: {
@@ -216,10 +246,71 @@ class TorSettingsImpl {
},
};
+ /**
+ * The underlying settings values.
+ *
+ * @type {TorCombinedSettings}
+ */
+ #settings = structuredClone(this.#defaultSettings);
+
+ /**
+ * The last successfully applied settings for the current `TorProvider`, if
+ * any.
+ *
+ * NOTE: Should only be written to within `#applySettings`.
+ *
+ * @type {{
+ * bridges: ?TorBridgeSettings,
+ * proxy: ?TorProxySettings,
+ * firewall: ?TorFirewallSettings
+ * }}
+ */
+ #successfulSettings = { bridges: null, proxy: null, firewall: null };
+
+ /**
+ * Whether temporary bridge settings have been applied to the current
+ * `TorProvider`.
+ *
+ * @type {boolean}
+ */
+ #temporaryBridgesApplied = false;
+
+ /**
+ * @typedef {TorSettingsApplyError}
+ *
+ * @property {boolean} canUndo - Whether the latest error can be "undone".
+ * When this is `false`, the TorProvider will be using its default values
+ * instead.
+ */
+
+ /**
+ * A summary of the latest failures to apply our settings, if any.
+ *
+ * NOTE: Should only be written to within `#applySettings`.
+ *
+ * @type {{
+ * bridges: ?TorSettingsApplyError,
+ * proxy: ?TorSettingsApplyError,
+ * firewall: ?TorSettingsApplyError,
+ * }}
+ */
+ #applyErrors = { bridges: null, proxy: null, firewall: null };
+
+ /**
+ * Get the latest failure for the given setting, if any.
+ *
+ * @param {string} group - The settings to get the error details for.
+ *
+ * @returns {?TorSettingsApplyError} - The error details, if any.
+ */
+ getApplyError(group) {
+ return structuredClone(this.#applyErrors[group]);
+ }
+
/**
* Temporary bridge settings to apply instead of #settings.bridges.
*
- * @type {?Object}
+ * @type {?TorBridgeSettings}
*/
#temporaryBridgeSettings = null;
@@ -342,38 +433,39 @@ class TorSettingsImpl {
}
/**
- * Regular expression for a decimal non-negative integer.
+ * Verify a port number is within bounds.
*
- * @type {RegExp}
+ * @param {integer} val - The value to verify.
+ * @return {boolean} - Whether the port is within range.
*/
- #portRegex = /^[0-9]+$/;
+ validPort(val) {
+ return Number.isInteger(val) && val >= 1 && val <= 65535;
+ }
+
/**
- * Parse a string as a port number.
+ * Verify that some SOCKS5 credentials are valid.
*
- * @param {string|integer} val - The value to parse.
- * @param {boolean} trim - Whether a string value can be stripped of
- * whitespace before parsing.
- *
- * @return {integer?} - The port number, or null if the given value was not
- * valid.
+ * @param {string} username - The SOCKS5 username.
+ * @param {string} password - The SOCKS5 password.
+ * @return {boolean} - Whether the credentials are valid.
*/
- #parsePort(val, trim) {
- if (typeof val === "string") {
- if (trim) {
- val = val.trim();
+ validSocks5Credentials(username, password) {
+ if (!username && !password) {
+ // Both empty is valid.
+ return true;
+ }
+ for (const val of [username, password]) {
+ if (typeof val !== "string") {
+ return false;
}
- // ensure port string is a valid positive integer
- if (this.#portRegex.test(val)) {
- val = Number.parseInt(val, 10);
- } else {
- throw new Error(`Invalid port string "${val}"`);
+ const byteLen = new TextEncoder().encode(val).length;
+ if (byteLen < 1 || byteLen > 255) {
+ return false;
}
}
- if (!Number.isInteger(val) || val < 1 || val > 65535) {
- throw new Error(`Port out of range: ${val}`);
- }
- return val;
+ return true;
}
+
/**
* Test whether two arrays have equal members and order.
*
@@ -659,10 +751,11 @@ class TorSettingsImpl {
false
);
if (firewall.enabled) {
- firewall.allowed_ports = Services.prefs.getStringPref(
- TorSettingsPrefs.firewall.allowed_ports,
- ""
- );
+ firewall.allowed_ports = Services.prefs
+ .getStringPref(TorSettingsPrefs.firewall.allowed_ports, "")
+ .split(",")
+ .filter(p => p.trim())
+ .map(p => parseInt(p, 10));
}
try {
this.#fixupFirewallSettings(firewall);
@@ -767,19 +860,221 @@ class TorSettingsImpl {
}
}
+ /**
+ * A blocker promise for the #applySettings method.
+ *
+ * Ensures only one active caller to protect the #applyErrors and
+ * #successfulSettings properties.
+ *
+ * @type {?Promise}
+ */
+ #applySettingsTask = null;
+
/**
* Push our settings down to the tor provider.
*
* Even though this introduces a circular depdency, it makes the API nicer for
* frontend consumers.
*
- * @param {boolean} flush - Whether to also flush the settings to disk.
+ * @param {Object} apply - The list of settings to apply.
+ * @param {boolean} [apply.bridges] - Whether to apply our bridge settings.
+ * @param {boolean} [apply.proxy] - Whether to apply our proxy settings.
+ * @param {boolean} [apply.firewall] - Whether to apply our firewall settings.
+ * @param {boolean} [details] - Optional details.
+ * @param {boolean} [details.useTemporaryBridges] - Whether the caller wants
+ * to apply temporary bridges.
+ * @param {boolean} [details.newProvider] - Whether the caller is initialising
+ * a new `TorProvider`.
*/
- async #applySettings(flush) {
- const provider = await lazy.TorProviderBuilder.build();
- await provider.writeSettings();
- if (flush) {
- provider.flushSettings();
+ async #applySettings(apply, details) {
+ // Grab this provider before awaiting.
+ // In particular, if the provider is changed we do not want to switch to
+ // writing to the new instance.
+ const providerRef = this.#providerRef;
+ const provider = providerRef?.deref();
+ if (!provider) {
+ // Wait until setTorProvider is called.
+ lazy.logger.info("No TorProvider yet");
+ return;
+ }
+
+ // We only want one instance of #applySettings to be running at any given
+ // time, so we await the previous call first.
+ // Read and replace #applySettingsTask before we do any async operations.
+ // I.e. this is effectively an atomic read and replace.
+ const prevTask = this.#applySettingsTask;
+ let taskComplete;
+ ({ promise: this.#applySettingsTask, resolve: taskComplete } =
+ Promise.withResolvers());
+ await prevTask;
+
+ try {
+ let flush = false;
+ const errors = [];
+
+ // Test whether the provider is no longer running or has been replaced.
+ const providerRunning = () => {
+ return providerRef === this.#providerRef && provider.isRunning;
+ };
+
+ lazy.logger.debug("Passing on settings to the provider", apply, details);
+
+ if (details?.newProvider) {
+ // If we have a new provider we clear our successful settings.
+ // In particular, the user may have changed their settings several times
+ // whilst the tor process was not running. In the event of an
+ // "ApplyError", we want to correctly show to the user that they are now
+ // using default settings and we do not want to allow them to "undo"
+ // since the previous successful settings may be out of date.
+ // NOTE: We do not do this within `setTorProvider` since some other
+ // caller's `#applySettingsTask` may still be running and writing to
+ // these values when `setTorProvider` is called.
+ this.#successfulSettings.bridges = null;
+ this.#successfulSettings.proxy = null;
+ this.#successfulSettings.firewall = null;
+ this.#applyErrors.bridges = null;
+ this.#applyErrors.proxy = null;
+ this.#applyErrors.firewall = null;
+ // Temporary bridges are not applied to the new provider.
+ this.#temporaryBridgesApplied = false;
+ }
+
+ for (const group of ["bridges", "proxy", "firewall"]) {
+ if (!apply[group]) {
+ continue;
+ }
+
+ if (!providerRunning()) {
+ lazy.logger.info("The TorProvider is no longer running");
+ // Bail on this task since the old provider should not accept
+ // settings. setTorProvider will be called for the new provider and
+ // will already handle applying the same settings.
+ return;
+ }
+
+ let usingSettings = true;
+ if (group === "bridges") {
+ // Only record successes or failures when using the user settings.
+ if (this.#temporaryBridgeSettings && !details?.useTemporaryBridges) {
+ // #temporaryBridgeSettings were written whilst awaiting the
+ // previous task. Do nothing and allow applyTemporarySettings to
+ // apply the temporary bridges instead.
+ lazy.logger.info(
+ "Not apply bridges since temporary bridges were applied"
+ );
+ continue;
+ }
+ if (!this.#temporaryBridgeSettings && details?.useTemporaryBridges) {
+ // #temporaryBridgeSettings were cleared whilst awaiting the
+ // previous task. Do nothing and allow changeSettings or
+ // clearTemporaryBridges to apply the non-temporary bridges
+ // instead.
+ lazy.logger.info(
+ "Not apply temporary bridges since they were cleared"
+ );
+ continue;
+ }
+ usingSettings = !details?.useTemporaryBridges;
+ }
+
+ try {
+ switch (group) {
+ case "bridges": {
+ const bridges = structuredClone(
+ usingSettings
+ ? this.#settings.bridges
+ : this.#temporaryBridgeSettings
+ );
+
+ try {
+ await provider.writeBridgeSettings(bridges);
+ } catch (e) {
+ if (
+ usingSettings &&
+ this.#temporaryBridgesApplied &&
+ providerRunning()
+ ) {
+ lazy.logger.warn(
+ "Recovering to clear temporary bridges from the provider"
+ );
+ // The TorProvider is still using the temporary bridges. As a
+ // priority we want to try and restore the TorProvider to the
+ // state it was in prior to the temporary bridges being
+ // applied.
+ const prevBridges = structuredClone(
+ this.#successfulSettings.bridges ??
+ this.#defaultSettings.bridges
+ );
+ try {
+ await provider.writeBridgeSettings(prevBridges);
+ this.#temporaryBridgesApplied = false;
+ } catch (e) {
+ lazy.logger.error(
+ "Failed to clear the temporary bridges from the provider",
+ e
+ );
+ }
+ }
+ throw e;
+ }
+
+ if (usingSettings) {
+ this.#successfulSettings.bridges = bridges;
+ this.#temporaryBridgesApplied = false;
+ this.#applyErrors.bridges = null;
+ flush = true;
+ } else {
+ this.#temporaryBridgesApplied = true;
+ // Do not flush the temporary bridge settings until they are
+ // saved.
+ }
+ break;
+ }
+ case "proxy": {
+ const proxy = structuredClone(this.#settings.proxy);
+ await provider.writeProxySettings(proxy);
+ this.#successfulSettings.proxy = proxy;
+ this.#applyErrors.proxy = null;
+ flush = true;
+ break;
+ }
+ case "firewall": {
+ const firewall = structuredClone(this.#settings.firewall);
+ await provider.writeFirewallSettings(firewall);
+ this.#successfulSettings.firewall = firewall;
+ this.#applyErrors.firewall = null;
+ flush = true;
+ break;
+ }
+ }
+ } catch (e) {
+ // Store the error and throw later.
+ errors.push(e);
+ if (usingSettings && providerRunning()) {
+ // Record and signal the error.
+ // NOTE: We do not signal ApplyError when we fail to apply temporary
+ // bridges.
+ this.#applyErrors[group] = {
+ canUndo: Boolean(this.#successfulSettings[group]),
+ };
+ lazy.logger.debug(`Signalling new ApplyError for ${group}`);
+ Services.obs.notifyObservers(
+ { group },
+ TorSettingsTopics.ApplyError
+ );
+ }
+ }
+ }
+ if (flush && providerRunning()) {
+ provider.flushSettings();
+ }
+ if (errors.length) {
+ lazy.logger.error("Failed to apply settings", errors);
+ throw new Error(`Failed to apply settings. ${errors.join(". ")}.`);
+ }
+ } finally {
+ // Allow the next caller to proceed.
+ taskComplete();
}
}
@@ -869,7 +1164,30 @@ class TorSettingsImpl {
if (!Object.values(TorProxyType).includes(proxy.type)) {
throw new Error(`Invalid proxy type: ${proxy.type}`);
}
- proxy.port = this.#parsePort(proxy.port, false);
+ // Do not allow port value to be 0.
+ // Whilst Socks4Proxy, Socks5Proxy and HTTPSProxyPort allow you to pass in
+ // `<address>:0` this will select a default port. Our UI does not indicate
+ // that `0` maps to a different value, so we disallow it.
+ if (!this.validPort(proxy.port)) {
+ throw new Error(`Invalid proxy port: ${proxy.port}`);
+ }
+
+ switch (proxy.type) {
+ case TorProxyType.Socks4:
+ // Never use the username or password.
+ proxy.username = "";
+ proxy.password = "";
+ break;
+ case TorProxyType.Socks5:
+ if (!this.validSocks5Credentials(proxy.username, proxy.password)) {
+ throw new Error("Invalid SOCKS5 credentials");
+ }
+ break;
+ case TorProxyType.HTTPS:
+ // username and password are both optional.
+ break;
+ }
+
proxy.address = String(proxy.address);
proxy.username = String(proxy.username);
proxy.password = String(proxy.password);
@@ -890,22 +1208,82 @@ class TorSettingsImpl {
return;
}
- let allowed_ports = firewall.allowed_ports;
- if (!Array.isArray(allowed_ports)) {
- allowed_ports = allowed_ports === "" ? [] : allowed_ports.split(",");
+ if (!Array.isArray(firewall.allowed_ports)) {
+ throw new Error("allowed_ports should be an array of ports");
}
- // parse and remove duplicates
- const portSet = new Set();
-
- for (const port of allowed_ports) {
- try {
- portSet.add(this.#parsePort(port, true));
- } catch (e) {
- // Do not throw for individual ports.
- lazy.logger.error(`Failed to parse the port ${port}. Ignoring.`, e);
+ for (const port of firewall.allowed_ports) {
+ if (!this.validPort(port)) {
+ throw new Error(`Invalid firewall port: ${port}`);
}
}
- firewall.allowed_ports = [...portSet];
+ // Remove duplicates
+ firewall.allowed_ports = [...new Set(firewall.allowed_ports)];
+ }
+
+ /**
+ * The current `TorProvider` instance we are using, if any.
+ *
+ * @type {?WeakRef<TorProvider>}
+ */
+ #providerRef = null;
+
+ /**
+ * Called whenever we have a new provider to send settings to.
+ *
+ * @param {TorProvider} provider - The provider to apply our settings to.
+ */
+ async setTorProvider(provider) {
+ lazy.logger.debug("Applying settings to new provider");
+ this.#checkIfInitialized();
+
+ // Use a WeakRef to not keep the TorProvider instance alive.
+ this.#providerRef = new WeakRef(provider);
+ // NOTE: We need the caller to pass in the TorProvider instance because
+ // TorProvider's initialisation waits for this method. In particular, we
+ // cannot await TorProviderBuilder.build since it would hang!
+ await this.#applySettings(
+ { bridges: true, proxy: true, firewall: true },
+ { newProvider: true }
+ );
+ }
+
+ /**
+ * Undo settings that have failed to be applied by restoring the last
+ * successfully applied settings instead.
+ *
+ * @param {string} group - The group to undo the settings for.
+ */
+ async undoFailedSettings(group) {
+ if (!this.#applyErrors[group]) {
+ lazy.logger.warn(
+ `${group} settings have already been successfully replaced.`
+ );
+ return;
+ }
+ if (!this.#successfulSettings[group]) {
+ // Unexpected.
+ lazy.logger.warn(
+ `Cannot undo ${group} settings since we have no successful settings.`
+ );
+ return;
+ }
+ await this.changeSettings({ [group]: this.#successfulSettings[group] });
+ }
+
+ /**
+ * Clear settings that have failed to be applied by using the default settings
+ * instead.
+ *
+ * @param {string} group - The group to clear the settings for.
+ */
+ async clearFailedSettings(group) {
+ if (!this.#applyErrors[group]) {
+ lazy.logger.warn(
+ `${group} settings have already been successfully replaced.`
+ );
+ return;
+ }
+ await this.changeSettings({ [group]: this.#defaultSettings[group] });
}
/**
@@ -919,8 +1297,8 @@ class TorSettingsImpl {
* + proxy settings can be set as a group.
* + firewall settings can be set a group.
*
- * @param {object} newValues - The new setting values, a subset of the
- * complete settings that should be changed.
+ * @param {object} newValues - The new setting values that should be changed.
+ * A subset of the `TorCombinedSettings` object.
*/
async changeSettings(newValues) {
lazy.logger.debug("changeSettings()", newValues);
@@ -932,6 +1310,7 @@ class TorSettingsImpl {
const completeSettings = structuredClone(this.#settings);
const changes = [];
+ const apply = {};
/**
* Change the given setting to a new value. Does nothing if the new value
@@ -950,10 +1329,11 @@ class TorSettingsImpl {
}
completeSettings[group][prop] = value;
changes.push(`${group}.${prop}`);
+ // Apply these settings.
+ apply[group] = true;
};
if ("bridges" in newValues) {
- const changesLength = changes.length;
if ("source" in newValues.bridges) {
this.#fixupBridgeSettings(newValues.bridges);
changeSetting("bridges", "source", newValues.bridges.source);
@@ -984,7 +1364,7 @@ class TorSettingsImpl {
changeSetting("bridges", "enabled", newValues.bridges.enabled);
}
- if (this.#temporaryBridgeSettings && changes.length !== changesLength) {
+ if (this.#temporaryBridgeSettings && apply.bridges) {
// A change in the bridges settings.
// We want to clear the temporary bridge settings to ensure that they
// cannot be used to overwrite these user-provided settings.
@@ -1034,34 +1414,22 @@ class TorSettingsImpl {
lazy.logger.debug("setSettings result", this.#settings, changes);
- // After we have sent out the notifications for the changed settings and
- // saved the preferences we send the new settings to TorProvider.
- // Some properties are unread by TorProvider. So if only these values change
- // there is no need to re-apply the settings.
- const unreadProps = ["bridges.builtin_type", "bridges.lox_id"];
- const shouldApply = changes.some(prop => !unreadProps.includes(prop));
- if (shouldApply) {
- await this.#applySettings(true);
+ if (apply.bridges || apply.proxy || apply.firewall) {
+ // After we have sent out the notifications for the changed settings and
+ // saved the preferences we send the new settings to TorProvider.
+ await this.#applySettings(apply);
}
}
/**
* Get a copy of all our settings.
*
- * @param {boolean} [useTemporary=false] - Whether the returned settings
- * should use the temporary bridge settings, if any, instead.
- *
- * @returns {object} A copy of the settings object
+ * @returns {TorCombinedSettings} A copy of the current settings.
*/
- getSettings(useTemporary = false) {
+ getSettings() {
lazy.logger.debug("getSettings()");
this.#checkIfInitialized();
- const settings = structuredClone(this.#settings);
- if (useTemporary && this.#temporaryBridgeSettings) {
- // Override the bridge settings with our temporary ones.
- settings.bridges = structuredClone(this.#temporaryBridgeSettings);
- }
- return settings;
+ return structuredClone(this.#settings);
}
/**
@@ -1109,8 +1477,8 @@ class TorSettingsImpl {
// After checks are complete, we commit them.
this.#temporaryBridgeSettings = bridgeSettings;
- // Do not flush the temporary bridge settings until they are saved.
- await this.#applySettings(false);
+
+ await this.#applySettings({ bridges: true }, { useTemporaryBridges: true });
}
/**
@@ -1137,7 +1505,7 @@ class TorSettingsImpl {
return;
}
this.#temporaryBridgeSettings = null;
- await this.#applySettings();
+ await this.#applySettings({ bridges: true });
}
}
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/0f529e…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/0f529e…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-128.8.0esr-14.5-1] 2 commits: fixup! TB 40933: Add tor-launcher functionality
by henry (@henry) 18 Mar '25
by henry (@henry) 18 Mar '25
18 Mar '25
henry pushed to branch tor-browser-128.8.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits:
480957e5 by Henry Wilkes at 2025-03-18T15:21:16+00:00
fixup! TB 40933: Add tor-launcher functionality
TB 42300: Store TorProvider log messages in TorProviderBuilder to be
used between instances and to be available whilst a provider is not
available.
- - - - -
0f529e6f by Henry Wilkes at 2025-03-18T15:21:16+00:00
fixup! TB 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
TB 42300: Fetch tor logs from TorProviderBuilder.
- - - - -
3 changed files:
- browser/components/torpreferences/content/torLogDialog.js
- toolkit/components/tor-launcher/TorProvider.sys.mjs
- toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs
Changes:
=====================================
browser/components/torpreferences/content/torLogDialog.js
=====================================
@@ -66,28 +66,22 @@ const gTorLogDialog = {
);
});
- // 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 => {
- Services.obs.addObserver(this, TorProviderTopics.TorLog);
- window.addEventListener(
- "unload",
- () => {
- Services.obs.removeObserver(this, TorProviderTopics.TorLog);
- },
- { once: true }
- );
-
- for (const logEntry of provider.getLog()) {
- this.addLogEntry(logEntry, true);
- }
- // Set the initial scroll to the bottom.
- this._logTable.scrollTo({
- top: this._logTable.scrollTopMax,
- behaviour: "instant",
- });
+ Services.obs.addObserver(this, TorProviderTopics.TorLog);
+ window.addEventListener(
+ "unload",
+ () => {
+ Services.obs.removeObserver(this, TorProviderTopics.TorLog);
+ },
+ { once: true }
+ );
+
+ for (const logEntry of TorProviderBuilder.getLog()) {
+ this.addLogEntry(logEntry, true);
+ }
+ // Set the initial scroll to the bottom.
+ this._logTable.scrollTo({
+ top: this._logTable.scrollTopMax,
+ behaviour: "instant",
});
},
=====================================
toolkit/components/tor-launcher/TorProvider.sys.mjs
=====================================
@@ -45,12 +45,6 @@ const logger = console.createInstance({
* @property {string} [host] The host to connect for a TCP proxy
* @property {number} [port] The port number to use for a TCP proxy
*/
-/**
- * @typedef {object} LogEntry An object with a log message
- * @property {string} timestamp The local date-time stamp at which we received the message
- * @property {string} type The message level
- * @property {string} msg The message
- */
/**
* Stores the data associated with a circuit node.
*
@@ -69,7 +63,6 @@ const Preferences = Object.freeze({
ControlUseIpc: "extensions.torlauncher.control_port_use_ipc",
ControlHost: "extensions.torlauncher.control_host",
ControlPort: "extensions.torlauncher.control_port",
- MaxLogEntries: "extensions.torlauncher.max_tor_log_entries",
});
/* Config Keys used to configure tor daemon */
@@ -141,15 +134,6 @@ export class TorProvider {
*/
#socksSettings = null;
- /**
- * The logs we received over the control port.
- * We store a finite number of log entries which can be configured with
- * extensions.torlauncher.max_tor_log_entries.
- *
- * @type {LogEntry[]}
- */
- #logs = [];
-
#isBootstrapDone = false;
/**
* Keep the last warning to avoid broadcasting an async warning if it is the
@@ -511,15 +495,6 @@ export class TorProvider {
return this.#controller.onionAuthViewKeys();
}
- /**
- * Returns captured log messages.
- *
- * @returns {LogEntry[]} The logs we collected from the tor daemon so far.
- */
- getLog() {
- return structuredClone(this.#logs);
- }
-
/**
* @returns {boolean} true if we launched and control tor, false if we are
* using system tor.
@@ -1033,15 +1008,6 @@ export class TorProvider {
TorProviderTopics.TorLog
);
- const maxEntries = Services.prefs.getIntPref(
- Preferences.MaxLogEntries,
- 1000
- );
- if (maxEntries > 0 && this.#logs.length >= maxEntries) {
- this.#logs.splice(0, 1);
- }
-
- this.#logs.push({ type, msg, timestamp });
switch (type) {
case "ERR":
logger.error(`[Tor error] ${msg}`);
=====================================
toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs
=====================================
@@ -23,6 +23,13 @@ export const TorProviders = Object.freeze({
tor: 1,
});
+/**
+ * @typedef {object} LogEntry An object with a log message
+ * @property {string} timestamp The local date-time stamp at which we received the message
+ * @property {string} type The message level
+ * @property {string} msg The message
+ */
+
/**
* The factory to get a Tor provider.
* Currently we support only TorProvider, i.e., the one that interacts with
@@ -36,6 +43,48 @@ export class TorProviderBuilder {
*/
static #provider = null;
+ /**
+ * A record of the log messages from all TorProvider instances.
+ *
+ * @type {LogEntry[]}
+ */
+ static #log = [];
+
+ /**
+ * Get a record of historic log entries.
+ *
+ * @returns {LogEntry[]} - The record of entries.
+ */
+ static getLog() {
+ return structuredClone(this.#log);
+ }
+
+ /**
+ * The limit on the number of log entries we should store.
+ *
+ * @type {integer}
+ */
+ static #logLimit;
+
+ /**
+ * The observer that checks for new TorLog messages.
+ *
+ * @type {Function}
+ */
+ static #logObserver;
+
+ /**
+ * Add a new log message.
+ *
+ * @param {LogEntry} logEntry - The log entry to add.
+ */
+ static #addLogEntry(logEntry) {
+ if (this.#logLimit > 0 && this.#log.length >= this.#logLimit) {
+ this.#log.splice(0, 1);
+ }
+ this.#log.push(logEntry);
+ }
+
/**
* The observer that checks when the tor process exits, and reinitializes the
* provider.
@@ -56,6 +105,15 @@ export class TorProviderBuilder {
* Initialize the provider of choice.
*/
static init() {
+ this.#logLimit = Services.prefs.getIntPref(
+ "extensions.torlauncher.max_tor_log_entries",
+ 1000
+ );
+ this.#logObserver = subject => {
+ this.#addLogEntry(subject.wrappedJSObject);
+ };
+ Services.obs.addObserver(this.#logObserver, TorProviderTopics.TorLog);
+
switch (this.providerType) {
case TorProviders.tor:
// Even though initialization of the initial TorProvider is
@@ -136,6 +194,7 @@ export class TorProviderBuilder {
);
this.#exitObserver = null;
}
+ Services.obs.removeObserver(this.#logObserver, TorProviderTopics.TorLog);
}
/**
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/ad21bd…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/ad21bd…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/torbrowser-launcher] Pushed new branch AppArmor-updates-for-current-Debian
by intrigeri (@intrigeri) 18 Mar '25
by intrigeri (@intrigeri) 18 Mar '25
18 Mar '25
intrigeri pushed new branch AppArmor-updates-for-current-Debian at The Tor Project / Applications / torbrowser-launcher
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/torbrowser-launcher/-/tree/A…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][main] Tweak Tails notification in Tor Browser Stable release-prep template
by morgan (@morgan) 18 Mar '25
by morgan (@morgan) 18 Mar '25
18 Mar '25
morgan pushed to branch main at The Tor Project / Applications / tor-browser-build
Commits:
db399f9f by Morgan at 2025-03-18T13:05:10+00:00
Tweak Tails notification in Tor Browser Stable release-prep template
- - - - -
1 changed file:
- .gitlab/issue_templates/Release Prep - Tor Browser Stable.md
Changes:
=====================================
.gitlab/issue_templates/Release Prep - Tor Browser Stable.md
=====================================
@@ -147,28 +147,6 @@ Tor Browser Stable is on the `maint-${TOR_BROWSER_MAJOR}.${TOR_BROWSER_MINOR}` b
```bash
make torbrowser-kick-devmole-build
```
-- [ ] Notify Tails of pending builds
- - **Recipients**
- ```
- tails-dev(a)boum.org
- ```
- - **Subject**
- ```
- Tor Browser ${TOR_BROWSER_VERSION} Building
- ```
- - **Body**
- ```
- Hello,
-
- Tor Browser Stable is being built and should be ready for Tails in the coming hours. Build artifacts and hashes will be available here:
- - ${BUILD_ARTIFACTS_URL}
- - ${BUIDL_HASHES_URL}
-
- ⚠️ WARNING: We have not yet verified the builds match, so make sure you do so before prepping the Tails release!
-
- Changelog:
- # paste changelog as quote here
- ```
</details>
@@ -213,6 +191,25 @@ Tor Browser Stable is on the `maint-${TOR_BROWSER_MAJOR}.${TOR_BROWSER_MINOR}` b
- morgan
- pierov
- [ ] Ensure all builders have matching builds
+- [ ] Notify Tails
+ - **Recipients**
+ ```
+ tails-dev(a)boum.org
+ ```
+ - **Subject**
+ ```
+ Tor Browser ${TOR_BROWSER_VERSION} Ready
+ ```
+ - **Body**
+ ```
+ Hello,
+
+ Tor Browser Stable is ready for Tails. Build artifacts can be found here:
+ - ${BUILD_ARTIFACTS_URL}
+
+ Changelog:
+ # paste changelog as quote here
+ ```
- [ ] Verify the associated legacy `maint-13.5` release has been signed and deployed
- **⚠️ WARNING**: Do not continue if the legacy channel has not been fully signed and published yet; it is needed for update-response generation!
- **NOTE** Stable releases without a corresponding legacy release may ignore this
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/d…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/d…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][tor-browser-128.8.0esr-14.5-1] 2 commits: fixup! TB 27476: Implement about:torconnect captive portal within Tor Browser
by morgan (@morgan) 17 Mar '25
by morgan (@morgan) 17 Mar '25
17 Mar '25
morgan pushed to branch tor-browser-128.8.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits:
36916036 by Henry Wilkes at 2025-03-17T21:14:23+00:00
fixup! TB 27476: Implement about:torconnect captive portal within Tor Browser
TB 43321: Only focus the about:torconnect buttons under certain
circumstances.
By default, when switching stages we move the focus back to the stage
heading. This is because we want to lead the user back to the top of the
page to show them the new context. This should help improve the
experience when using a screen reader.
If we are in the bootstrapping stage we instead move the focus to the
"Cancel" button since it is likely that the user wants to use this
control.
If the user presses the "Cancel" button we return the focus to the
"Connect" or "Try a bridge" button. I.e. we restore the prior focus.
This allows to user to easily re-try without having to re-read the page
they just saw.
We do a similar thing when the user cancels the automatic startup
bootstrapping.
Finally, on page load we will focus the "Connect" button if the user has
previously interacted with it. We record this interaction in a
preference that persists between sessions.
We also separate out the "Loading" stage from the "Start" stage. It is
unexpected for `about:torconnect` to be opened whilst in the "Loading"
stage, but if it does happen it would be safer to keep the page blank.
The way this is implemented also ensures that the initial page is blank
prior to "get-init-args" resolving.
- - - - -
ad21bdd6 by Henry Wilkes at 2025-03-17T21:14:23+00:00
fixup! TB 40597: Implement TorSettings module
TB 43321: Add a isQuickstart property to the TorConnect.stage.
This is used by `about:torconnect` for determining focus behaviour.
- - - - -
5 changed files:
- toolkit/components/torconnect/TorConnectParent.sys.mjs
- toolkit/components/torconnect/content/aboutTorConnect.css
- toolkit/components/torconnect/content/aboutTorConnect.html
- toolkit/components/torconnect/content/aboutTorConnect.js
- toolkit/modules/TorConnect.sys.mjs
Changes:
=====================================
toolkit/components/torconnect/TorConnectParent.sys.mjs
=====================================
@@ -13,6 +13,9 @@ ChromeUtils.defineESModuleGetters(lazy, {
HomePage: "resource:///modules/HomePage.sys.mjs",
});
+const userHasEverClickedConnectPref =
+ "torbrowser.about_torconnect.user_has_ever_clicked_connect";
+
/*
This object is basically a marshalling interface between the TorConnect module
and a particular about:torconnect page
@@ -117,6 +120,9 @@ export class TorConnectParent extends JSWindowActorParent {
TorConnect.chooseRegion();
break;
case "torconnect:begin-bootstrapping":
+ if (message.data.userClickedConnect) {
+ Services.prefs.setBoolPref(userHasEverClickedConnectPref, true);
+ }
TorConnect.beginBootstrapping(message.data.regionCode);
break;
case "torconnect:cancel-bootstrapping":
@@ -130,6 +136,10 @@ export class TorConnectParent extends JSWindowActorParent {
Direction: Services.locale.isAppLocaleRTL ? "rtl" : "ltr",
CountryNames: TorConnect.countryNames,
stage: TorConnect.stage,
+ userHasEverClickedConnect: Services.prefs.getBoolPref(
+ userHasEverClickedConnectPref,
+ false
+ ),
quickstartEnabled: TorConnect.quickstart,
};
case "torconnect:get-frequent-regions":
=====================================
toolkit/components/torconnect/content/aboutTorConnect.css
=====================================
@@ -15,6 +15,11 @@ html {
height: 100%;
}
+body:not(.loaded) {
+ /* Keep blank whilst loading. */
+ display: none;
+}
+
#breadcrumbs {
display: flex;
align-items: center;
@@ -93,6 +98,11 @@ html {
display: none;
}
+#tor-connect-heading {
+ /* Do not show the focus outline. */
+ outline: none;
+}
+
#connect-to-tor {
margin-inline-start: 0;
}
=====================================
toolkit/components/torconnect/content/aboutTorConnect.html
=====================================
@@ -50,7 +50,7 @@
</div>
<div id="text-container">
<div class="title">
- <h1 class="title-text"></h1>
+ <h1 id="tor-connect-heading" class="title-text" tabindex="-1"></h1>
</div>
<div id="connectLongContent">
<p id="connectLongContentText"></p>
=====================================
toolkit/components/torconnect/content/aboutTorConnect.js
=====================================
@@ -32,7 +32,6 @@ class AboutTorConnect {
selectors = Object.freeze({
textContainer: {
title: "div.title",
- titleText: "h1.title-text",
longContentText: "#connectLongContentText",
},
progress: {
@@ -77,7 +76,7 @@ class AboutTorConnect {
elements = Object.freeze({
title: document.querySelector(this.selectors.textContainer.title),
- titleText: document.querySelector(this.selectors.textContainer.titleText),
+ heading: document.getElementById("tor-connect-heading"),
longContentText: document.querySelector(
this.selectors.textContainer.longContentText
),
@@ -138,18 +137,44 @@ class AboutTorConnect {
locations = {};
- beginBootstrapping() {
- RPMSendAsyncMessage("torconnect:begin-bootstrapping", {});
+ /**
+ * Whether the user requested a cancellation of the bootstrap from *this*
+ * page.
+ *
+ * @type {boolean}
+ */
+ userCancelled = false;
+
+ /**
+ * Start a normal bootstrap attempt.
+ *
+ * @param {boolean} userClickedConnect - Whether this request was triggered by
+ * the user clicking the "Connect" button on the "Start" page.
+ */
+ beginBootstrapping(userClickedConnect) {
+ RPMSendAsyncMessage("torconnect:begin-bootstrapping", {
+ userClickedConnect,
+ });
}
+ /**
+ * Start an auto bootstrap attempt.
+ *
+ * @param {string} regionCode - The region code to use for the bootstrap, or
+ * "automatic".
+ */
beginAutoBootstrapping(regionCode) {
RPMSendAsyncMessage("torconnect:begin-bootstrapping", {
regionCode,
});
}
+ /**
+ * Try and cancel the current bootstrap attempt.
+ */
cancelBootstrapping() {
RPMSendAsyncMessage("torconnect:cancel-bootstrapping");
+ this.userCancelled = true;
}
/*
@@ -260,7 +285,7 @@ class AboutTorConnect {
}
setTitle(title, className) {
- this.elements.titleText.textContent = title;
+ this.elements.heading.textContent = title;
this.elements.title.className = "title";
if (className) {
this.elements.title.classList.add(className);
@@ -349,18 +374,88 @@ class AboutTorConnect {
}
}
+ /**
+ * The connect button that was focused just prior to a bootstrap attempt, if
+ * any.
+ *
+ * @type {?Element}
+ */
+ preBootstrappingFocus = null;
+
+ /**
+ * The stage that was shown on this page just prior to a bootstrap attempt.
+ *
+ * @type {?string}
+ */
+ preBootstrappingStage = null;
+
/*
These methods update the UI based on the current TorConnect state
*/
- updateStage(stage) {
+ /**
+ * Update the shown stage.
+ *
+ * @param {ConnectStage} stage - The new stage to show.
+ * @param {boolean} [focusConnect=false] - Whether to try and focus the
+ * connect button, if we are in the Start stage.
+ */
+ updateStage(stage, focusConnect = false) {
if (stage.name === this.shownStage) {
return;
}
+ const prevStage = this.shownStage;
this.shownStage = stage.name;
this.selectedLocation = stage.defaultRegion;
+ // By default we want to reset the focus to the top of the page when
+ // changing the displayed page since we want a user to read the new page
+ // before activating a control.
+ let moveFocus = this.elements.heading;
+
+ if (stage.name === "Bootstrapping") {
+ this.preBootstrappingStage = prevStage;
+ this.preBootstrappingFocus = null;
+ if (focusConnect && stage.isQuickstart) {
+ // If this is the initial automatic bootstrap triggered by the
+ // quickstart preference, treat as if the previous shown stage was
+ // "Start" and the user clicked the "Connect" button.
+ // Then, if the user cancels, the focus should still move to the
+ // "Connect" button.
+ this.preBootstrappingStage = "Start";
+ this.preBootstrappingFocus = this.elements.connectButton;
+ } else if (this.elements.connectButton.contains(document.activeElement)) {
+ this.preBootstrappingFocus = this.elements.connectButton;
+ } else if (
+ this.elements.tryBridgeButton.contains(document.activeElement)
+ ) {
+ this.preBootstrappingFocus = this.elements.tryBridgeButton;
+ }
+ } else {
+ if (
+ this.userCancelled &&
+ prevStage === "Bootstrapping" &&
+ stage.name === this.preBootstrappingStage &&
+ this.preBootstrappingFocus &&
+ this.elements.cancelButton.contains(document.activeElement)
+ ) {
+ // If returning back to the same stage after the user tried to cancel
+ // bootstrapping from within this page, then we restore the focus to the
+ // connect button to allow the user to quickly re-try.
+ // If the bootstrap was cancelled for any other reason, we reset the
+ // focus as usual.
+ moveFocus = this.preBootstrappingFocus;
+ }
+ // Clear the Bootstrapping variables.
+ this.preBootstrappingStage = null;
+ this.preBootstrappingFocus = null;
+ }
+
+ // Clear the recording of the cancellation request.
+ this.userCancelled = false;
+
+ let isLoaded = true;
let showProgress = false;
let showLog = false;
switch (stage.name) {
@@ -368,14 +463,21 @@ class AboutTorConnect {
console.error("Should not be open when TorConnect is disabled");
break;
case "Loading":
+ // Unexpected for this page to open so early.
+ console.warn("Page opened whilst loading");
+ isLoaded = false;
+ break;
case "Start":
- // Loading is not currnetly handled, treat the same as "Start", but UI
- // will be unresponsive.
this.showStart(stage.tryAgain, stage.potentiallyBlocked);
+ if (focusConnect) {
+ moveFocus = this.elements.connectButton;
+ }
break;
case "Bootstrapping":
showProgress = true;
this.showBootstrapping(stage.bootstrapTrigger, stage.tryAgain);
+ // Always focus the cancel button.
+ moveFocus = this.elements.cancelButton;
break;
case "Offline":
showLog = true;
@@ -419,6 +521,9 @@ class AboutTorConnect {
} else {
this.hide(this.elements.viewLogButton);
}
+
+ document.body.classList.toggle("loaded", isLoaded);
+ moveFocus.focus();
}
updateBootstrappingStatus(data) {
@@ -452,10 +557,9 @@ class AboutTorConnect {
this.show(this.elements.quickstartContainer);
this.show(this.elements.configureButton);
this.show(this.elements.connectButton, true);
- this.elements.connectButton.focus();
- if (tryAgain) {
- this.elements.connectButton.textContent = TorStrings.torConnect.tryAgain;
- }
+ this.elements.connectButton.textContent = tryAgain
+ ? TorStrings.torConnect.tryAgain
+ : TorStrings.torConnect.torConnectButton;
if (potentiallyBlocked) {
this.setBreadcrumbsStatus(
BreadcrumbStatus.Active,
@@ -511,7 +615,6 @@ class AboutTorConnect {
}
this.hideButtons();
this.show(this.elements.cancelButton);
- this.elements.cancelButton.focus();
}
showOffline() {
@@ -541,7 +644,6 @@ class AboutTorConnect {
BreadcrumbStatus.Disabled
);
this.showLocationForm(true, TorStrings.torConnect.tryBridge);
- this.elements.tryBridgeButton.focus();
}
showRegionNotFound() {
@@ -557,7 +659,6 @@ class AboutTorConnect {
BreadcrumbStatus.Disabled
);
this.showLocationForm(false, TorStrings.torConnect.tryBridge);
- this.elements.tryBridgeButton.focus();
}
showConfirmRegion(error) {
@@ -573,7 +674,6 @@ class AboutTorConnect {
BreadcrumbStatus.Active
);
this.showLocationForm(false, TorStrings.torConnect.tryAgain);
- this.elements.tryBridgeButton.focus();
}
showFinalError(error) {
@@ -722,7 +822,8 @@ class AboutTorConnect {
this.elements.connectButton.textContent =
TorStrings.torConnect.torConnectButton;
this.elements.connectButton.addEventListener("click", () => {
- this.beginBootstrapping();
+ // Record as userClickedConnect if we are in the Start stage.
+ this.beginBootstrapping(this.shownStage === "Start");
});
this.populateLocations();
@@ -802,7 +903,13 @@ class AboutTorConnect {
this.initObservers();
this.initKeyboardShortcuts();
- this.updateStage(args.stage);
+ // If we have previously opened about:torconnect and the user tried the
+ // "Connect" button we want to focus the "Connect" button for easy
+ // activation.
+ // Otherwise, we do not want to focus it for first time users so they can
+ // read the full page first.
+ const focusConnect = args.userHasEverClickedConnect;
+ this.updateStage(args.stage, focusConnect);
this.updateQuickstart(args.quickstartEnabled);
}
}
=====================================
toolkit/modules/TorConnect.sys.mjs
=====================================
@@ -680,10 +680,14 @@ export const TorConnectStage = Object.freeze({
* A summary of the user stage.
* (This class is mirrored for Android in TorConnectStage.java. Changes should be mirrored there)
*
- * @property {string} name - The name of the stage.
+ * @property {string} name - The name of the stage. One of the values in
+ * `TorConnectStage`.
* @property {string} defaultRegion - The default region to show in the UI.
- * @property {?string} bootstrapTrigger - The TorConnectStage prior to this
+ * @property {?string} bootstrapTrigger - The name of the stage prior to this
* bootstrap attempt. Only set during the "Bootstrapping" stage.
+ * @property {boolean} isQuickstart - Whether the current bootstrap attempt was
+ * triggered by the `TorConnect.quickstart` preference. Will be `false`
+ * outside of the "Bootstrapping" stage.
* @property {?BootstrapError} error - The last bootstrapping error.
* @property {boolean} tryAgain - Whether a bootstrap attempt has failed, so
* that a normal bootstrap should be shown as "Try Again" instead of
@@ -752,6 +756,14 @@ export const TorConnect = {
*/
_bootstrapTrigger: null,
+ /**
+ * Whether the current bootstrapping attempt was triggered by the quickstart
+ * preference.
+ *
+ * @type {boolean}
+ */
+ _isQuickstart: false,
+
/**
* The alternative stage that we should move to after bootstrapping completes.
*
@@ -807,6 +819,7 @@ export const TorConnect = {
name: this._stageName,
defaultRegion: this._defaultRegion,
bootstrapTrigger: this._bootstrapTrigger,
+ isQuickstart: this._isQuickstart,
error: this._errorDetails
? {
code: this._errorDetails.code,
@@ -935,7 +948,7 @@ export const TorConnect = {
// And the previous bootstrap attempt must have succeeded.
!Services.prefs.getBoolPref(TorConnectPrefs.prompt_at_startup, true)
) {
- this.beginBootstrapping();
+ this._beginBootstrappingInternal(undefined, true);
}
},
@@ -1303,6 +1316,19 @@ export const TorConnect = {
* an auto-bootstrap attempt.
*/
async beginBootstrapping(regionCode) {
+ await this._beginBootstrappingInternal(regionCode, false);
+ },
+
+ /**
+ * Begin a bootstrap attempt.
+ *
+ * @param {string} [regionCode] - An optional region code string to use, or
+ * "automatic" to automatically determine the region. If given, will start
+ * an auto-bootstrap attempt.
+ * @param {boolean} isQuickstart - Whether this was triggered by the
+ * quickstart option.
+ */
+ async _beginBootstrappingInternal(regionCode, isQuickstart) {
lazy.logger.debug("TorConnect.beginBootstrapping()");
if (!this._confirmBootstrapping(regionCode)) {
@@ -1331,6 +1357,7 @@ export const TorConnect = {
}
this._requestedStage = null;
this._bootstrapTrigger = beginStage;
+ this._isQuickstart = isQuickstart;
this._setStage(TorConnectStage.Bootstrapping);
this._bootstrapAttempt = bootstrapAttempt;
@@ -1349,6 +1376,7 @@ export const TorConnect = {
const requestedStage = this._requestedStage;
this._requestedStage = null;
this._bootstrapTrigger = null;
+ this._isQuickstart = false;
this._bootstrapAttempt = null;
if (bootstrapAttempt.detectedRegion) {
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/f5c281…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/f5c281…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][main] Bug 41397: Fix linux-packages following changes from #41040
by morgan (@morgan) 17 Mar '25
by morgan (@morgan) 17 Mar '25
17 Mar '25
morgan pushed to branch main at The Tor Project / Applications / tor-browser-build
Commits:
74163819 by Nicolas Vigier at 2025-03-17T20:29:51+00:00
Bug 41397: Fix linux-packages following changes from #41040
- move var/linux-packages and var/linux-packages-aarch64 definition to
the root, since the browser-linux-* targets are no longer used when
building all platforms
- fix calls to linux-packages. Some var/browser-linux-* had not been
updated to var/browser_platforms/linux-*.
- in projects/linux-packages/config, remove test on the browser not
being mullvadbrowser to enable i686 packages, since that part is now
handled outside
- - - - -
2 changed files:
- projects/linux-packages/config
- projects/release/config
Changes:
=====================================
projects/linux-packages/config
=====================================
@@ -113,7 +113,7 @@ targets:
browser-linux-x86_64: 1
browser-linux-i686:
var:
- browser-linux-i686: '[% c("var/browser_type") != "mullvadbrowser" %]'
+ browser-linux-i686: 1
browser-linux-aarch64:
var:
browser-linux-aarch64: 1
=====================================
projects/release/config
=====================================
@@ -8,6 +8,9 @@ var:
publish_dir: '[% c("var/signed_status") %]/[% c("version") %]-[% c("var/torbrowser_build") %]'
containers_target: with_containers
+ linux-packages: '[% ( c("var/browser_platforms/linux-i686") || c("var/browser_platforms/linux-x86_64") ) && ( c("var/mullvad-browser") || c("var/nightly") ) %]'
+ linux-packages-aarch64: '[% c("var/browser_platforms/linux-aarch64") && (c("var/mullvad-browser") || c("var/nightly")) %]'
+
targets:
browser-all:
- browser-src
@@ -37,7 +40,6 @@ targets:
var:
browser_platforms:
linux-x86_64: 1
- linux-packages: '[% c("var/mullvad-browser") || c("var/tor-browser-linux-packages") %]'
browser-linux-x86_64-asan:
var:
browser_platforms:
@@ -47,12 +49,10 @@ targets:
var:
browser_platforms:
linux-i686: 1
- linux-packages: '[% c("var/mullvad-browser") || c("var/tor-browser-linux-packages") %]'
browser-linux-aarch64:
var:
browser_platforms:
linux-aarch64: 1
- linux-packages-aarch64: '[% (! c("var/browser-all-target") || c("var/testbuild") || c("var/nightly")) && (c("var/mullvad-browser") || c("var/tor-browser-linux-packages")) %]'
browser-windows-i686:
var:
browser_platforms:
@@ -96,7 +96,6 @@ targets:
var:
browser_type: torbrowser
git_tag_prefix: tbb
- tor-browser-linux-packages: '[% c("var/nightly") %]'
basebrowser:
var:
browser_type: basebrowser
@@ -201,13 +200,13 @@ input_files:
- name: deb-packages
project: linux-packages
- enable: '[% c("var/browser_platforms/packages") %]'
+ enable: '[% c("var/linux-packages") %]'
# Add $browser_type-linux-x86_64 target for container config
target:
- '[% IF c("var/testbuild") %]testbuild[% END %]'
- '[% c("var/build_target") %]'
- - '[% IF c("var/browser-linux-i686") %]browser-linux-i686[% END %]'
- - '[% IF c("var/browser-linux-x86_64") %]browser-linux-x86_64[% END %]'
+ - '[% IF c("var/browser_platforms/linux-i686") %]browser-linux-i686[% END %]'
+ - '[% IF c("var/browser_platforms/linux-x86_64") %]browser-linux-x86_64[% END %]'
- deb
- '[% c("var/browser_type") %]-linux-x86_64'
@@ -231,8 +230,8 @@ input_files:
target:
- '[% IF c("var/testbuild") %]testbuild[% END %]'
- '[% c("var/build_target") %]'
- - '[% IF c("var/browser-linux-i686") %]browser-linux-i686[% END %]'
- - '[% IF c("var/browser-linux-x86_64") %]browser-linux-x86_64[% END %]'
+ - '[% IF c("var/browser_platforms/linux-i686") %]browser-linux-i686[% END %]'
+ - '[% IF c("var/browser_platforms/linux-x86_64") %]browser-linux-x86_64[% END %]'
- '[% IF c("var/linux-packages-aarch64") %]browser-linux-aarch64[% END %]'
- rpm
- '[% c("var/browser_type") %]-linux-x86_64'
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/7…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/commit/7…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser-build][maint-14.0] 7 commits: Bug 41374: Remove support for migrate_archs and migrate_langs in update_responses
by morgan (@morgan) 17 Mar '25
by morgan (@morgan) 17 Mar '25
17 Mar '25
morgan pushed to branch maint-14.0 at The Tor Project / Applications / tor-browser-build
Commits:
ae7a43c7 by Nicolas Vigier at 2025-03-13T10:36:59+01:00
Bug 41374: Remove support for migrate_archs and migrate_langs in update_responses
Those options have not been used for some time, and removing them
simplifies the following changes we're doing to split files per platform.
- - - - -
5abd1cb7 by Nicolas Vigier at 2025-03-13T10:37:22+01:00
Bug 40799: Remove legacy locale iteration in update-responses
- - - - -
7c05ce80 by Nicolas Vigier at 2025-03-17T10:38:03+01:00
Bug 40799: Remove legacy locale support in tools/dmg2mar
- - - - -
b1bd59c3 by Nicolas Vigier at 2025-03-17T10:38:51+01:00
Bug 41363: Split update_responses files per platform
To make reverting update for a single platform easier, we split update
response files, using a separate directory for each platform.
- - - - -
7945d332 by Nicolas Vigier at 2025-03-17T10:39:15+01:00
Bug 41363: Make separate update_responses commit for each platform
- - - - -
80a959de by Nicolas Vigier at 2025-03-17T10:39:26+01:00
Bug 41363: Fix error message about $update_responses_repository_dir being undefined
- - - - -
03e261aa by Nicolas Vigier at 2025-03-17T10:40:27+01:00
Bug 41394: Don't try to add download-android-*.json files for Mullvad Browser
- - - - -
6 changed files:
- projects/release/create_update_responses_tar
- projects/release/update_responses_config.yml
- tools/dmg2mar
- tools/signing/functions
- tools/signing/upload-update_responses-to-staticiforme
- tools/update-responses/update_responses
Changes:
=====================================
projects/release/create_update_responses_tar
=====================================
@@ -1,9 +1,10 @@
#!/bin/bash
[% c("var/set_default_env") -%]
[% SET channel = c('var/channel') -%]
+rm -Rf [% shell_quote(c("basedir")) %]/tools/update-responses/htdocs/[% channel %]
[% shell_quote(c("basedir")) %]/tools/update-responses/update_responses [% channel %]
mkdir -p [% shell_quote(path(dest_dir)) %]/update-responses
mv [% shell_quote(c("basedir")) %]/tools/update-responses/htdocs/[% channel %] [% channel %]
-chmod 775 [% channel %]
-chmod 664 [% channel %]/.htaccess [% channel %]/*
+find [% channel %] -type d -exec chmod 775 {} \;
+find [% channel %] -type f -exec chmod 664 {} \;
tar cf [% shell_quote(path(dest_dir)) %]/update-responses/update-responses-[% channel %]-[% c("version") %].tar [% channel %]
=====================================
projects/release/update_responses_config.yml
=====================================
@@ -53,10 +53,6 @@ versions:
- [% ver %]
[% END;
END -%]
- # for example, osx32: osx64
- migrate_archs:
- # for example, pt-PT: pt-BR
- migrate_langs:
# minSupportedOsVersion on macOS corresponds to the Darwin version ( https://en.wikipedia.org/wiki/Darwin_(operating_system) )
macos:
# macOS v10.15.0
=====================================
tools/dmg2mar
=====================================
@@ -102,7 +102,7 @@ sub get_dmg_files_from_sha256sums {
next unless $filename;
chomp $filename;
next unless $filename =~ m/^$appname_dmg-macos-(.+)\.dmg$/;
- push @files, { filename => $filename, version => $1, lang => 'ALL' };
+ push @files, { filename => $filename, version => $1 };
}
return @files;
}
@@ -116,11 +116,7 @@ sub convert_files {
print "Finished $_[2]\n";
});
foreach my $file (get_dmg_files_from_sha256sums) {
- # The 'ja' locale is a special case: it is called 'ja-JP-mac'
- # internally on OSX, but the dmg file still uses 'ja' to avoid
- # confusing users.
- my $mar_lang = $file->{lang} eq 'ja' ? 'ja-JP-mac' : $file->{lang};
- my $output = "$appname_mar-macos-$file->{version}_$mar_lang.mar";
+ my $output = "$appname_mar-macos-$file->{version}_ALL.mar";
my $step_name = "$file->{filename} -> $output";
print "Starting $step_name\n";
$pm->start($step_name) and next;
=====================================
tools/signing/functions
=====================================
@@ -23,7 +23,7 @@ function check_update_responses_repository_dir {
if test -z "$update_responses_repository_dir" || ! test -d "$update_responses_repository_dir"
then
cat << 'EOF' > /dev/stderr
-$aus1_repository_dir is not defined, or the directory does not exist
+$update_responses_repository_dir is not defined, or the directory does not exist
You should clone git@gitlab.torproject.org:tpo/applications/tor-browser-update-responses.git
and set $update_responses_repository_dir in set-config.update-responses
EOF
=====================================
tools/signing/upload-update_responses-to-staticiforme
=====================================
@@ -39,8 +39,34 @@ do
mv "$file" "$tbb_version_type/$fname"
done
+# Keep directory from previous release if they are not in the new release.
+# This happens when a release does not include some platforms.
+for file in $(ls -1 "$old_ur/$tbb_version_type")
+do
+ test -d "$old_ur/$tbb_version_type/$file" || continue
+ test -d "$tbb_version_type/$file" && continue
+ mv -f "$old_ur/$tbb_version_type/$file" "$tbb_version_type/$file"
+done
+
+# Commit each sub-directory separately
+for file in $(ls -1 "$tbb_version_type")
+do
+ test -d "$tbb_version_type/$file" || continue
+ git add "$tbb_version_type/$file"
+ git add "$tbb_version_type/download-$file.json"
+ git diff --quiet --cached --exit-code || \
+ git commit -m "$tbb_version_type: new version, $tbb_version ($file)"
+done
+
+if is_project torbrowser; then
+ git add "$tbb_version_type"/download-android-*.json
+ git diff --quiet --cached --exit-code || \
+ git commit -m "$tbb_version_type: new version, $tbb_version (android)"
+fi
+
git add "$tbb_version_type"
-git commit -m "$tbb_version_type: new version, $tbb_version"
+git diff --quiet --cached --exit-code || \
+ git commit -m "$tbb_version_type: new version, $tbb_version"
git push
# we just need to push mullvadbrowser's update responses to git, not deploy to staticiforme
=====================================
tools/update-responses/update_responses
=====================================
@@ -29,7 +29,6 @@ setlocale(LC_ALL, "C");
my $htdocsdir = "$FindBin::Bin/htdocs";
my $config = LoadFile("$FindBin::Bin/config.yml");
-my %htdocsfiles;
my $releases_dir = $config->{releases_dir};
$releases_dir = "$FindBin::Bin/$releases_dir" unless $releases_dir =~ m/^\//;
my @check_errors;
@@ -51,12 +50,6 @@ sub get_tmpdir {
: ());
}
-sub build_targets_by_os {
- exit_error "Unknown build target for OS $_[0]" unless $config->{build_targets}{$_[0]};
- my $r = $config->{build_targets}{$_[0]};
- return ref $r eq 'ARRAY' ? @$r : ($r);
-}
-
sub get_nbprocs {
return $ENV{NUM_PROCS} if defined $ENV{NUM_PROCS};
if (-f '/proc/cpuinfo') {
@@ -80,19 +73,11 @@ sub setup_martools {
}
sub write_htdocs {
- my ($channel, $file, $content) = @_;
+ my ($channel, $dir, $file, $content) = @_;
mkdir $htdocsdir unless -d $htdocsdir;
mkdir "$htdocsdir/$channel" unless -d "$htdocsdir/$channel";
- write_file("$htdocsdir/$channel/$file", $content);
- $htdocsfiles{$channel}->{$file} = 1;
-}
-
-sub clean_htdocs {
- my ($channel) = @_;
- opendir(my $d, "$htdocsdir/$channel");
- my @files = grep { ! $htdocsfiles{$channel}->{$_} } readdir $d;
- closedir $d;
- unlink map { "$htdocsdir/$channel/$_" } @files;
+ mkdir "$htdocsdir/$channel/$dir" unless -d "$htdocsdir/$channel/$dir";
+ write_file("$htdocsdir/$channel/$dir/$file", $content);
}
sub get_sha512_hex_of_file {
@@ -113,9 +98,9 @@ sub get_version_files {
foreach my $file (readdir $d) {
next unless -f "$vdir/$file";
if ($file !~ m/incremental\.mar$/ &&
- $file =~ m/^$appname-(.+)-${version}_(.+)\.mar$/) {
- my ($os, $lang) = ($1, $2);
- $files->{$os}{$lang}{complete} = {
+ $file =~ m/^$appname-(.+)-${version}_ALL\.mar$/) {
+ my $os = $1;
+ $files->{$os}{complete} = {
type => 'complete',
URL => "$download_url/$file",
size => -s "$vdir/$file",
@@ -126,9 +111,9 @@ sub get_version_files {
};
next;
}
- if ($file =~ m/^$appname-(.+)--(.+)-${version}_(.+)\.incremental\.mar$/) {
- my ($os, $from_version, $lang) = ($1, $2, $3);
- $files->{$os}{$lang}{partial}{$from_version} = {
+ if ($file =~ m/^$appname-(.+)--(.+)-${version}_ALL\.incremental\.mar$/) {
+ my ($os, $from_version) = ($1, $2);
+ $files->{$os}{partial}{$from_version} = {
type => 'partial',
URL => "$download_url/$file",
size => -s "$vdir/$file",
@@ -235,14 +220,14 @@ sub extract_mar {
}
sub mar_filename {
- my ($config, $appname, $version, $os, $lang) = @_;
- version_dir($config, $version) . "/$appname-$os-${version}_$lang.mar";
+ my ($config, $appname, $version, $os) = @_;
+ version_dir($config, $version) . "/$appname-$os-${version}_ALL.mar";
}
sub create_incremental_mar {
- my ($config, $pm, $from_version, $new_version, $os, $lang, $channel) = @_;
+ my ($config, $pm, $from_version, $new_version, $os, $channel) = @_;
my $appname = $config->{appname_marfile};
- my $mar_file = "$appname-$os--${from_version}-${new_version}_$lang.incremental.mar";
+ my $mar_file = "$appname-$os--${from_version}-${new_version}_ALL.incremental.mar";
my $mar_file_path = version_dir($config, $new_version) . '/' . $mar_file;
if ($ENV{MAR_SKIP_EXISTING} && -f $mar_file_path) {
print "Skipping $mar_file\n";
@@ -253,7 +238,7 @@ sub create_incremental_mar {
my $finished_file = sub {
exit_error "Error creating $mar_file" unless $_[1] == 0;
print "Finished $mar_file\n";
- $config->{versions}{$new_version}{files}{$os}{$lang}{partial}{$from_version} = {
+ $config->{versions}{$new_version}{files}{$os}{partial}{$from_version} = {
type => 'partial',
URL => "$download_url/$mar_file",
size => -s $mar_file_path,
@@ -267,9 +252,9 @@ sub create_incremental_mar {
my $tmpdir = get_tmpdir($config);
my $mar_c_from = get_config($config, $from_version, $os, 'mar_compression');
my $mar_c_new = get_config($config, $new_version, $os, 'mar_compression');
- extract_mar(mar_filename($config, $appname, $from_version, $os, $lang),
+ extract_mar(mar_filename($config, $appname, $from_version, $os),
"$tmpdir/A", $mar_c_from);
- extract_mar(mar_filename($config, $appname, $new_version, $os, $lang),
+ extract_mar(mar_filename($config, $appname, $new_version, $os),
"$tmpdir/B", $mar_c_new);
# bug 26054: make sure previous macOS version is code signed
if (!$ENV{NO_CODESIGNATURE} && ($os eq 'macos')
@@ -306,10 +291,8 @@ sub create_incremental_mars_for_version {
get_version_files($config, $from_version);
my $from_v = $config->{versions}{$from_version};
foreach my $os (keys %{$v->{files}}) {
- foreach my $lang (keys %{$v->{files}{$os}}) {
- next unless defined $from_v->{files}{$os}{$lang}{complete};
- create_incremental_mar($config, $pm, $from_version, $version, $os, $lang, $channel);
- }
+ next unless defined $from_v->{files}{$os}{complete};
+ create_incremental_mar($config, $pm, $from_version, $version, $os, $channel);
}
}
$pm->wait_all_children;
@@ -333,31 +316,29 @@ sub get_buildinfos {
setup_martools;
my $files = $config->{versions}{$version}{files};
foreach my $os (keys %$files) {
- foreach my $lang (keys %{$files->{$os}}) {
- next unless $files->{$os}{$lang}{complete};
- my $tmpdir = get_tmpdir($config);
- my $mar_compression = get_config($config, $version, $os, 'mar_compression');
- extract_mar(
- mar_filename($config, $config->{appname_marfile}, $version, $os, $lang),
- "$tmpdir",
- $mar_compression);
- my $appfile = "$tmpdir/application.ini" if -f "$tmpdir/application.ini";
- $appfile = "$tmpdir/Contents/Resources/application.ini"
- if -f "$tmpdir/Contents/Resources/application.ini";
- exit_error "Could not find application.ini" unless $appfile;
- foreach my $line (read_file($appfile)) {
- if ($line =~ m/^BuildID=(.*)$/) {
- $config->{versions}{$version}{buildID} = $1;
- return;
- }
+ next unless $files->{$os}{complete};
+ my $tmpdir = get_tmpdir($config);
+ my $mar_compression = get_config($config, $version, $os, 'mar_compression');
+ extract_mar(
+ mar_filename($config, $config->{appname_marfile}, $version, $os),
+ "$tmpdir",
+ $mar_compression);
+ my $appfile = "$tmpdir/application.ini" if -f "$tmpdir/application.ini";
+ $appfile = "$tmpdir/Contents/Resources/application.ini"
+ if -f "$tmpdir/Contents/Resources/application.ini";
+ exit_error "Could not find application.ini" unless $appfile;
+ foreach my $line (read_file($appfile)) {
+ if ($line =~ m/^BuildID=(.*)$/) {
+ $config->{versions}{$version}{buildID} = $1;
+ return;
}
- exit_error "Could not extract buildID from application.ini";
}
+ exit_error "Could not extract buildID from application.ini";
}
}
sub get_response {
- my ($config, $versions, $os, $lang, $from_version) = @_;
+ my ($config, $versions, $os, $from_version) = @_;
my $res;
my $writer = XML::Writer->new(OUTPUT => \$res, ENCODING => 'UTF-8');
$writer->xmlDecl;
@@ -384,13 +365,13 @@ sub get_response {
defined $minversion ? ( minSupportedOSVersion => $minversion ) : (),
defined $mininstruc ? ( minSupportedInstructionSet => $mininstruc ) : (),
);
- if (my $patch = $config->{versions}{$version}{files}{$os}{$lang}{complete}) {
+ if (my $patch = $config->{versions}{$version}{files}{$os}{complete}) {
my @sorted_patch = map { $_ => $patch->{$_} } sort keys %$patch;
$writer->startTag('patch', @sorted_patch);
$writer->endTag('patch');
}
if ($from_version) {
- if (my $patch = $config->{versions}{$version}{files}{$os}{$lang}{partial}{$from_version}) {
+ if (my $patch = $config->{versions}{$version}{files}{$os}{partial}{$from_version}) {
my @sorted_patch = map { $_ => $patch->{$_} } sort keys %$patch;
$writer->startTag('patch', @sorted_patch);
$writer->endTag('patch');
@@ -407,87 +388,69 @@ sub get_response {
sub write_responses {
my ($config, $channel) = @_;
my $versions = as_array($config->{channels}{$channel});
- my (%oses, %langs, %from_versions);
+ my (%oses, %from_versions);
foreach my $version (@$versions) {
get_version_files($config, $version);
get_buildinfos($config, $version);
my $files = $config->{versions}{$version}{files};
- my $migrate_archs = $config->{versions}{$version}{migrate_archs} // {};
- foreach my $old_os (keys %$migrate_archs) {
- my $new_os = $migrate_archs->{$old_os};
- foreach my $lang (keys %{$files->{$new_os}}) {
- $files->{$old_os}{$lang}{complete} =
- $files->{$new_os}{$lang}{complete};
- }
- }
foreach my $os (keys %$files) {
$oses{$os} = 1;
- foreach my $lang (keys %{$files->{$os}}) {
- $langs{$lang} = 1;
- foreach my $from_version (keys %{$files->{$os}{$lang}{partial}}) {
- $from_versions{$from_version} = 1;
- }
+ foreach my $from_version (keys %{$files->{$os}{partial}}) {
+ $from_versions{$from_version} = 1;
}
}
}
my $versions_str = join('+', @$versions);
foreach my $os (keys %oses) {
- foreach my $lang (keys %langs) {
- my $resp = get_response($config, $versions, $os, $lang);
- write_htdocs($channel, "$versions_str-$os-$lang.xml", $resp);
- foreach my $from_version (keys %from_versions) {
- $resp = get_response($config, $versions, $os, $lang, $from_version);
- write_htdocs($channel, "$from_version-$versions_str-$os-$lang.xml", $resp);
- }
+ my $resp = get_response($config, $versions, $os);
+ write_htdocs($channel, $os, "$versions_str-$os.xml", $resp);
+ foreach my $from_version (keys %from_versions) {
+ $resp = get_response($config, $versions, $os, $from_version);
+ write_htdocs($channel, $os, "$from_version-$versions_str-$os.xml", $resp);
}
+ write_htdocs($channel, $os, 'no-update.xml',
+ '<?xml version="1.0" encoding="UTF-8"?>'
+ . "\n<updates></updates>\n");
}
- write_htdocs($channel, 'no-update.xml',
- '<?xml version="1.0" encoding="UTF-8"?>'
- . "\n<updates></updates>\n");
}
sub write_htaccess {
my ($config, $channel) = @_;
- my $flags = "[last]";
- my $htaccess = "RewriteEngine On\n";
- $htaccess .= $config->{htaccess_rewrite_rules}{$channel} // '';
+
+ my $htaccess_main = "RewriteEngine On\n";
+ $htaccess_main .= $config->{htaccess_rewrite_rules}{$channel} // '';
my $versions = as_array($config->{channels}{$channel});
my $versions_str = join('+', @$versions);
- my (%oses, %langs, %from_versions);
- my $migrate_langs;
+ foreach my $os (sort keys %{$config->{build_targets}}) {
+ foreach my $bt (@{ as_array($config->{build_targets}{$os}) }) {
+ $htaccess_main .= "RewriteRule ^$bt/(.*) $os/\$1 [last]\n";
+ }
+ }
+ write_htdocs($channel, '.', '.htaccess', $htaccess_main);
+
+ my (%oses, %from_versions);
foreach my $version (@$versions) {
- $migrate_langs = $config->{versions}{$version}{migrate_langs}
- if $config->{versions}{$version}{migrate_langs};
my $files = $config->{versions}{$version}{files};
foreach my $os (keys %$files) {
$oses{$os} = 1;
- foreach my $lang (keys %{$files->{$os}}) {
- $langs{$lang} = 1;
- foreach my $from_version (keys %{$files->{$os}{$lang}{partial}}) {
- $from_versions{$from_version} = 1;
- }
+ foreach my $from_version (keys %{$files->{$os}{partial}}) {
+ $from_versions{$from_version} = 1;
}
}
- $htaccess .= "RewriteRule ^[^\/]+/$version/ no-update.xml $flags\n";
}
- foreach my $os (sort keys %oses) {
- foreach my $bt (build_targets_by_os($os)) {
- foreach my $lang (sort keys %langs) {
- foreach my $from_version (sort keys %from_versions) {
- $htaccess .= "RewriteRule ^$bt/$from_version/$lang "
- . "$from_version-$versions_str-$os-$lang.xml $flags\n";
- }
- $htaccess .= "RewriteRule ^$bt/[^\/]+/$lang "
- . "$versions_str-$os-$lang.xml $flags\n";
- }
- foreach my $lang (sort keys %$migrate_langs) {
- $htaccess .= "RewriteRule ^$bt/[^\/]+/$lang "
- . "$versions_str-$os-$migrate_langs->{$lang}.xml $flags\n";
+ foreach my $version (@$versions) {
+ my $files = $config->{versions}{$version}{files};
+ foreach my $os (sort keys %oses) {
+ my $htaccess_os = "RewriteEngine On\n";
+ $htaccess_os .= "RewriteRule ^$version/ no-update.xml [last]\n";
+ foreach my $from_version (sort keys %from_versions) {
+ $htaccess_os .= "RewriteRule ^$from_version/ "
+ . "$from_version-$versions_str-$os.xml [last]\n";
}
- $htaccess .= "RewriteRule ^$bt/ $versions_str-$os-ALL.xml $flags\n";
+ $htaccess_os .= "RewriteRule ^[^\/]+/ $versions_str-$os.xml [last]\n";
+ write_htdocs($channel, $os, '.htaccess', $htaccess_os);
}
}
- write_htdocs($channel, '.htaccess', $htaccess);
}
sub write_downloads_json {
@@ -501,11 +464,11 @@ sub write_downloads_json {
tag => "$tag",
downloads => get_version_downloads($config, $version),
};
- write_htdocs($channel, 'downloads.json',
+ write_htdocs($channel, '.', 'downloads.json',
JSON->new->utf8->canonical->encode($data));
my $pp_downloads = get_perplatform_downloads($config, $version, $tag);
foreach my $os (keys %{$pp_downloads}) {
- write_htdocs($channel, "download-$os.json",
+ write_htdocs($channel, '.', "download-$os.json",
JSON->new->utf8->canonical->encode($pp_downloads->{$os}));
}
}
@@ -567,28 +530,26 @@ sub check_update_responses_channel {
my $channel_versions = as_array($config->{channels}{$channel});
my ($channel_version) = @$channel_versions;
foreach my $build_target (build_targets_list()) {
- foreach my $lang (qw(en-US de)) {
- my $url = "$base_url/$channel/$build_target/1.0/$lang";
- my $dom = get_remote_xml($url);
- if ($dom) {
- my $version = check_get_version($dom);
- log_step($url, 'version', $version eq $channel_version,
- "expected: $channel_version received: $version");
- }
- $url = "$base_url/$channel/$build_target/$channel_version/$lang";
+ my $url = "$base_url/$channel/$build_target/1.0/ALL";
+ my $dom = get_remote_xml($url);
+ if ($dom) {
+ my $version = check_get_version($dom);
+ log_step($url, 'version', $version eq $channel_version,
+ "expected: $channel_version received: $version");
+ }
+ $url = "$base_url/$channel/$build_target/$channel_version/ALL";
+ $dom = get_remote_xml($url);
+ log_step($url, 'no_update', check_no_update($dom)) if $dom;
+ my @inc = @{$config->{versions}{$channel_version}{incremental_from}}
+ if $config->{versions}{$channel_version}{incremental_from};
+ foreach my $inc_from (@inc) {
+ my $url = "$base_url/$channel/$build_target/$inc_from/ALL";
$dom = get_remote_xml($url);
- log_step($url, 'no_update', check_no_update($dom)) if $dom;
- my @inc = @{$config->{versions}{$channel_version}{incremental_from}}
- if $config->{versions}{$channel_version}{incremental_from};
- foreach my $inc_from (@inc) {
- my $url = "$base_url/$channel/$build_target/$inc_from/$lang";
- $dom = get_remote_xml($url);
- next unless $dom;
- my $version = check_get_version($dom);
- log_step($url, 'version', $version eq $channel_version,
- "expected: $channel_version received: $version");
- log_step($url, 'has_incremental', check_has_incremental($dom));
- }
+ next unless $dom;
+ my $version = check_get_version($dom);
+ log_step($url, 'version', $version eq $channel_version,
+ "expected: $channel_version received: $version");
+ log_step($url, 'has_incremental', check_has_incremental($dom));
}
}
}
@@ -667,11 +628,9 @@ my %actions = (
exit_error "Wrong arguments" unless @ARGV == 1;
my $channel = $ARGV[0];
exit_error "Unknown channel $channel" unless $config->{channels}{$channel};
- $htdocsfiles{$channel} = { '.' => 1, '..' => 1 };
write_responses($config, $channel);
write_htaccess($config, $channel);
write_downloads_json($config, $channel);
- clean_htdocs($channel);
},
gen_incrementals => sub {
my ($config) = @_;
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/compare/…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-build/-/compare/…
You're receiving this email because of your account on gitlab.torproject.org.
1
0

[Git][tpo/applications/tor-browser][base-browser-128.8.0esr-14.5-1] fixup! BB 41631: Prevent weird initial window dimensions caused by subpixel computations
by Pier Angelo Vendrame (@pierov) 17 Mar '25
by Pier Angelo Vendrame (@pierov) 17 Mar '25
17 Mar '25
Pier Angelo Vendrame pushed to branch base-browser-128.8.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits:
23e53d28 by Pier Angelo Vendrame at 2025-03-17T21:08:55+01:00
fixup! BB 41631: Prevent weird initial window dimensions caused by subpixel computations
BB 42670: Sometimes LB is applied even when disabled.
- - - - -
1 changed file:
- toolkit/components/resistfingerprinting/RFPHelper.sys.mjs
Changes:
=====================================
toolkit/components/resistfingerprinting/RFPHelper.sys.mjs
=====================================
@@ -603,7 +603,7 @@ class _RFPHelper {
log(`${logPrefix} roundDimensions(${aWidth}, ${aHeight})`);
- if (!(isInitialSize || this.letterboxingEnabled)) {
+ if (!this.letterboxingEnabled) {
// just round size to int
return r(aWidth, aHeight);
}
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/23e53d2…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/23e53d2…
You're receiving this email because of your account on gitlab.torproject.org.
1
0