tbb-commits
Threads by month
- ----- 2026 -----
- July
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- 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
- 20633 discussions
[Git][tpo/applications/tor-browser][tor-browser-140.10.1esr-15.0-1] 2 commits: Bug_44747: Add FtC strings
by clairehurst (@clairehurst) 28 Apr '26
by clairehurst (@clairehurst) 28 Apr '26
28 Apr '26
clairehurst pushed to branch tor-browser-140.10.1esr-15.0-1 at The Tor Project / Applications / Tor Browser
Commits:
259110bb by clairehurst at 2026-04-27T16:23:56-06:00
Bug_44747: Add FtC strings
We can drop this after the Fund the Commons campaign concludes
- - - - -
927330f8 by clairehurst at 2026-04-28T12:53:04-06:00
Bug_44747: Tor x FtC crowdfunding campaign
We can drop this after the Fund the Commons campaign concludes on 2026-06-19
- - - - -
8 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/home/ui/SearchBar.kt
- + mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/CampaignCompose.kt
- + mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorCampaignViewModel.kt
- mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorHomePage.kt
- + mobile/android/fenix/app/src/main/res/drawable/globe_chain_burst_yec.xml
- + mobile/android/fenix/app/src/main/res/drawable/heart.xml
- mobile/android/fenix/app/src/main/res/values/torbrowser_strings.xml
Changes:
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
=====================================
@@ -161,6 +161,7 @@ import org.mozilla.fenix.wallpapers.Wallpaper
import org.mozilla.fenix.GleanMetrics.TabStrip as TabStripMetrics
import org.mozilla.fenix.components.toolbar.ToolbarPosition
+import org.mozilla.fenix.tor.TorCampaignViewModel
import org.mozilla.fenix.tor.TorHomePage
import org.mozilla.fenix.tor.UrlQuickLoadViewModel
@@ -179,6 +180,7 @@ class HomeFragment : Fragment(), UserInteractionHandler {
private val homeViewModel: HomeScreenViewModel by activityViewModels()
private val urlQuickLoadViewModel: UrlQuickLoadViewModel by activityViewModels()
+ private val torCampaignViewModel: TorCampaignViewModel by activityViewModels()
private var _bottomToolbarContainerView: BottomToolbarContainerView? = null
private val bottomToolbarContainerView: BottomToolbarContainerView
@@ -974,7 +976,16 @@ class HomeFragment : Fragment(), UserInteractionHandler {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
TorHomePage(
- toolBarAtTop = settings().toolbarPosition == ToolbarPosition.TOP
+ shouldInitiallyShowPromo = torCampaignViewModel.shouldInitiallyShowPromo,
+ toolBarAtTop = settings().toolbarPosition == ToolbarPosition.TOP,
+ toolPair = torCampaignViewModel.getToolPair(),
+ onClicked = {
+ (requireActivity() as HomeActivity).openToBrowserAndLoad(
+ searchTermOrURL = "https://internetfreedom.torproject.org",
+ newTab = true,
+ from = BrowserDirection.FromHome,
+ )
+ }
)
}
}
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/ui/SearchBar.kt
=====================================
@@ -34,7 +34,7 @@ internal fun SearchBar(
@Composable
@PreviewLightDark
-private fun SearchBarPreview() {
+fun SearchBarPreview() {
FirefoxTheme {
Column(
modifier = Modifier.background(color = FirefoxTheme.colors.layer1),
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/CampaignCompose.kt
=====================================
@@ -0,0 +1,310 @@
+package org.mozilla.fenix.tor
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+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.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.foundation.shape.RoundedCornerShape
+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.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.fromHtml
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import mozilla.components.ui.colors.PhotonColors
+import org.mozilla.fenix.R
+
+
+private val alternateLayoutThreshHold = 500.dp
+
+@Composable
+@CampaignComposePreview
+fun CampaignBox(
+ shouldShowPromo: MutableState<Boolean> = mutableStateOf(true),
+ onDonateButtonClicked: () -> Unit = {},
+ toolPair: Pair<TorCampaignViewModel.Tool, TorCampaignViewModel.Tool> = Pair(TorCampaignViewModel.toolList[0], TorCampaignViewModel.toolList[1]),
+ ) {
+ BoxWithConstraints(
+ contentAlignment = Alignment.Center,
+ modifier = Modifier
+ .fillMaxWidth()
+ .fillMaxHeight(),
+ ) {
+ val alternateLayout = this.maxWidth >= alternateLayoutThreshHold
+
+ CampaignLayout(
+ alternateLayout,
+ maxWidth = this.maxWidth,
+ shouldShowPromo,
+ onDonateButtonClicked = onDonateButtonClicked,
+ toolPair = toolPair,
+ )
+ }
+}
+
+@Composable
+private fun CampaignLayout(
+ alternateLayout: Boolean,
+ maxWidth: Dp,
+ shouldShowPromo: MutableState<Boolean>,
+ onDonateButtonClicked: () -> Unit,
+ toolPair: Pair<TorCampaignViewModel.Tool, TorCampaignViewModel.Tool>,
+) {
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 22.dp)
+ .fillMaxWidth(getVariableWidth(maxWidth))
+ .wrapContentHeight(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ PurpleBox(
+ alternateLayout,
+ shouldShowPromo,
+ onDonateButtonClicked = onDonateButtonClicked,
+ toolPair = toolPair,
+ )
+ Spacer(Modifier.size(8.dp))
+ Text(
+ text = stringResource(R.string.no_donation_required_yec),
+ style = TextStyle(
+ fontSize = 12.5.sp,
+ lineHeight = 18.75.sp,
+ fontFamily = FontFamily.SansSerif,
+ fontWeight = FontWeight(400),
+ color = PhotonColors.LightGrey05,
+ textAlign = TextAlign.Center,
+ ),
+ )
+ }
+}
+
+private fun getVariableWidth(width: Dp): Float =
+ (alternateLayoutThreshHold / width).coerceIn(0.80f, 1.0f)
+
+@Composable
+private fun PurpleBox(
+ alternateLayout: Boolean,
+ shouldShowPromo: MutableState<Boolean>,
+ onDonateButtonClicked: () -> Unit,
+ toolPair: Pair<TorCampaignViewModel.Tool, TorCampaignViewModel.Tool>,
+) {
+ Box(
+ modifier = Modifier.background(
+ colorResource(mozilla.components.ui.colors.R.color.photonViolet90),
+ shape = RoundedCornerShape(8.dp),
+ ),
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentHeight(),
+ horizontalArrangement = Arrangement.End,
+ ) {
+ ExitIcon(shouldShowPromo)
+ }
+ DynamicCampaignContent(
+ alternateLayout, onDonateButtonClicked = onDonateButtonClicked,
+ toolPair = toolPair
+ )
+ }
+}
+
+@Composable
+private fun ExitIcon(shouldShowYec: MutableState<Boolean>) {
+ IconButton(
+ modifier = Modifier.padding(8.dp),
+ onClick = {
+ shouldShowYec.value = false
+ },
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_close),
+ tint = PhotonColors.White,
+ contentDescription = stringResource(R.string.close_yec_button_description),
+ )
+ }
+}
+
+
+@Composable
+private fun DynamicCampaignContent(
+ alternateLayout: Boolean,
+ onDonateButtonClicked: () -> Unit,
+ toolPair: Pair<TorCampaignViewModel.Tool, TorCampaignViewModel.Tool>,
+) {
+ @Composable
+ fun Icon(shouldShow: Boolean) {
+ if (shouldShow) {
+ Image(
+ painterResource(R.drawable.globe_chain_burst_yec),
+ contentDescription = null,
+ alignment = Alignment.Center,
+ )
+ }
+ }
+ Row(
+ modifier = Modifier
+ .padding(start = 16.dp, top = 32.dp, end = 16.dp, bottom = 24.dp)
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth(if (alternateLayout) 0.75f else 1.0f),
+ horizontalAlignment = if (alternateLayout) Alignment.Start else Alignment.CenterHorizontally,
+ ) {
+ Icon(shouldShow = !alternateLayout)
+ Spacer(Modifier.size(24.dp))
+ TitleText()
+ Spacer(Modifier.size(16.dp))
+ MainText( toolPair = toolPair )
+ Spacer(Modifier.size(24.dp))
+ Row(
+ horizontalArrangement = if (alternateLayout) Arrangement.Start else Arrangement.Center,
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ DonateButton(onDonateButtonClicked = onDonateButtonClicked, alternateLayout)
+ }
+ }
+ Icon(shouldShow = alternateLayout)
+ }
+}
+
+
+@Composable
+private fun TitleText() {
+ Text(
+ text = stringResource(R.string.summer_2026_funding_heading),
+ style = TextStyle(
+ fontSize = 24.sp,
+ lineHeight = 32.sp,
+ fontWeight = FontWeight(400),
+ color = PhotonColors.White,
+ textAlign = TextAlign.Center,
+ letterSpacing = 0.18.sp,
+ ),
+ )
+}
+
+
+@Composable
+private fun MainText(toolPair: Pair<TorCampaignViewModel.Tool, TorCampaignViewModel.Tool>) {
+ Column {
+ Text(
+ AnnotatedString.fromHtml(
+ // Relevant documentation on HTML markup for android https://developer.android.com/guide/topics/resources/string-resource?utm_so…
+ stringResource(
+ R.string.summer_2026_funding_intro,
+ "<b>" + toolPair.first.name + "</b>",
+ stringResource(toolPair.first.description),
+ "<b>" + toolPair.second.name + "</b>",
+ stringResource(toolPair.second.description),
+ ),
+ ), style = TextStyle(
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ color = PhotonColors.White,
+ fontWeight = FontWeight(400),
+ letterSpacing = 0.25.sp,
+ )
+ )
+ Spacer(Modifier.size(8.dp))
+ Text(
+ text = AnnotatedString.fromHtml(
+ stringResource(
+ R.string.summer_2026_funding_outro,
+ "<b>" + stringResource(R.string.summer_2026_call_to_donate) + "</b>"
+ )
+ ),
+ modifier = Modifier.fillMaxWidth(),
+ style = TextStyle(
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ fontWeight = FontWeight(400),
+ letterSpacing = 0.25.sp,
+ color = PhotonColors.LightGrey05,
+ )
+ )
+ }
+}
+
+@Composable
+private fun DonateButton(onDonateButtonClicked: () -> Unit, alternateLayout: Boolean) {
+ Button(
+ onClick = onDonateButtonClicked,
+ colors = ButtonDefaults.buttonColors(
+ colorResource(mozilla.components.ui.colors.R.color.photonViolet60),
+ ),
+ shape = RoundedCornerShape(4.dp),
+ contentPadding = PaddingValues(horizontal = 16.dp),
+ modifier = if (alternateLayout) Modifier.wrapContentWidth() else Modifier.fillMaxWidth()
+ ) {
+ Image(
+ painterResource(R.drawable.heart),
+ contentDescription = null,
+ )
+ Spacer(
+ Modifier.size(8.dp),
+ )
+ Text(
+ text = stringResource(R.string.donate_now_yec),
+ textAlign = TextAlign.Center,
+ fontSize = 16.sp,
+ fontWeight = FontWeight.SemiBold,
+ color = PhotonColors.LightGrey05,
+ )
+ }
+}
+
+
+
+@Preview(
+ name = "Small Window",
+ widthDp = 400,
+)
+@Preview(
+ name = "Medium Window",
+ widthDp = 700,
+)
+@Preview(
+ name = "Large Window",
+ widthDp = 1000,
+)
+@Preview(
+ name = "RTL Small Window",
+ locale = "ar",
+ widthDp = 400,
+)
+@Preview(
+ name = "RTL Large Window",
+ locale = "ar",
+ widthDp = 1000
+)
+annotation class CampaignComposePreview
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorCampaignViewModel.kt
=====================================
@@ -0,0 +1,67 @@
+package org.mozilla.fenix.tor
+
+import android.app.Application
+import android.util.Log
+import androidx.annotation.StringRes
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.application
+import org.mozilla.fenix.R
+import org.mozilla.fenix.ext.components
+import java.text.SimpleDateFormat
+import java.util.Date
+
+class TorCampaignViewModel(application: Application) : AndroidViewModel(application) {
+
+ val shouldInitiallyShowPromo: MutableState<Boolean> by lazy {
+ mutableStateOf(shouldInitiallyShowPromo())
+ }
+
+ fun shouldInitiallyShowPromo(): Boolean {
+ val dateFormat = SimpleDateFormat("yyyy-MM-dd-hh-zzz")
+ // From https://gitlab.torproject.org/tpo/applications/tor-browser/-/work_items/447…
+ val startDate = dateFormat.parse("2026-05-19-15-UTC")
+ val endDate = dateFormat.parse("2026-06-19-00-UTC")
+ val currentDate = Date()
+
+ if (currentDate.before(startDate) || currentDate.after(endDate)) {
+ return false
+ }
+ Log.d(
+ "TorCampaignViewModel",
+ "org.mozilla.fenix.BuildConfig.BUILD_TYPE = ${org.mozilla.fenix.BuildConfig.BUILD_TYPE}"
+ )
+ return (org.mozilla.fenix.BuildConfig.BUILD_TYPE == "release") || (org.mozilla.fenix.BuildConfig.BUILD_TYPE == "debug")
+ }
+
+ companion object {
+ val toolList: List<Tool> = listOf(
+ Tool(name = "Onion Browser", description = R.string.summer_2026_funding_tool_onion_browser_description),
+ Tool(name = "Quiet", description = R.string.summer_2026_funding_tool_quiet_description),
+ Tool(name = "Ricochet Refresh", description = R.string.summer_2026_funding_tool_ricochet_refresh_description),
+ Tool(name = "SecureDrop", description = R.string.summer_2026_funding_tool_securedrop_description),
+ Tool(name = "OnionShare", description = R.string.summer_2026_funding_tool_onionshare_description),
+ Tool(name = "Digital Security Helpdesk", description = R.string.summer_2026_funding_tool_digital_security_helpdesk_description),
+ Tool(name = "Paskoocheh", description = R.string.summer_2026_funding_tool_paskoocheh_description),
+ Tool(name = "Unredacted", description = R.string.summer_2026_funding_tool_unredacted_description),
+ Tool(name = "Osservatorio Nessuno", description = R.string.summer_2026_funding_tool_osservatorio_nessuno_description),
+ Tool(name = "Save", description = R.string.summer_2026_funding_tool_save_description),
+ Tool(name = "OONI", description = R.string.summer_2026_funding_tool_ooni_description),
+ ).shuffled()
+ }
+
+ private var toolIndex = 0
+ private fun incrementToolIndex() {
+ toolIndex = (toolIndex + 2) % toolList.size
+ }
+
+ data class Tool(
+ val name: String,
+ @param:StringRes val description: Int,
+ )
+
+ fun getToolPair() : Pair<Tool, Tool> {
+ return Pair(toolList[toolIndex], toolList[(toolIndex + 1) % toolList.size]).also { incrementToolIndex() }
+ }
+}
=====================================
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tor/TorHomePage.kt
=====================================
@@ -1,6 +1,7 @@
package org.mozilla.fenix.tor
import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -12,6 +13,10 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.paint
@@ -26,16 +31,33 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import mozilla.components.compose.base.annotation.FlexibleWindowLightDarkPreview
import org.mozilla.fenix.R
+import org.mozilla.fenix.home.ui.SearchBarPreview
@Composable
-@FlexibleWindowLightDarkPreview
fun TorHomePage(
- toolBarAtTop: Boolean = true,
+ shouldInitiallyShowPromo: MutableState<Boolean> = mutableStateOf(true),
+ onClicked: () -> Unit = {},
+ toolBarAtTop: Boolean = false,
+ toolPair: Pair<TorCampaignViewModel.Tool, TorCampaignViewModel.Tool>,
) {
+ // Will persist across a single session, but not multiple sessions.
+ // Tapping the close button 'X' will hide the promo for the duration of the session
+ val shouldShowPromo = rememberSaveable {
+ shouldInitiallyShowPromo
+ }
+
+ // Will persist through screen rotations, but not navigations (e.g. Tap on settings -> come back will update)
+ // Is expected to change with every new visit to about:tor
+ val toolPair = remember {
+ toolPair
+ }
+
Column(
modifier = Modifier
.fillMaxSize()
@@ -82,30 +104,78 @@ fun TorHomePage(
)
}
Spacer(Modifier.weight(1f))
- Text(
- // Moved from the commit 5bb3cc6b93346dabd8d46677fae7f86a8f8a4fc2
- // "[android] Modify UI/UX", and the file HomeFragment.
- // Splits by full stops or commas and puts the parts in different lines.
- // Ignoring separators at the end of the string, it is expected
- // that there are at most two parts (e.g. "Explore. Privately.").
- text = stringResource(R.string.tor_explore_privately).replace(
+ if (shouldShowPromo.value) {
+ CampaignBox(
+ shouldShowPromo,
+ onDonateButtonClicked = onClicked,
+ toolPair = toolPair
+ )
+ Spacer(Modifier.weight(1f))
+ } else {
+ Text(
+ // Moved from the commit 5bb3cc6b93346dabd8d46677fae7f86a8f8a4fc2
+ // "[android] Modify UI/UX", and the file HomeFragment.
+ // Splits by full stops or commas and puts the parts in different lines.
+ // Ignoring separators at the end of the string, it is expected
+ // that there are at most two parts (e.g. "Explore. Privately.").
+ text = stringResource(R.string.tor_explore_privately).replace(
" *([.,。।]) *".toRegex(),
"$1\n",
).trim(),
- style = TextStyle(
- color = Color(color = 0xDEFFFFFF),
- fontSize = 40.sp,
- textAlign = TextAlign.Start,
- ),
- modifier = Modifier.align(Alignment.CenterHorizontally),
- )
- Spacer(Modifier.weight(1f))
- Image(
- painter = painterResource(
- id = R.drawable.ic_onion_pattern,
- ),
- contentDescription = null, Modifier.fillMaxWidth(),
- )
+ style = TextStyle(
+ color = Color(color = 0xDEFFFFFF),
+ fontSize = 40.sp,
+ textAlign = TextAlign.Start,
+ ),
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ )
+ }
+ if (!shouldShowPromo.value) {
+ Spacer(Modifier.weight(1f))
+ Image(
+ painter = painterResource(
+ id = R.drawable.ic_onion_pattern,
+ ),
+ contentDescription = null, Modifier.fillMaxWidth(),
+ )
+ }
}
Spacer(modifier = Modifier.size(17.dp))
}
+
+@Composable
+@Preview
+/**
+ * Relevant documentation
+ * https://developer.android.com/develop/ui/compose/tooling/previews#preview-v…
+ */
+private fun TorHomePagePreview(
+ @PreviewParameter(
+ BooleanBooleanPreviewParameterProvider::class,
+ ) booleanMatrix: Pair<Boolean, Boolean>,
+) {
+ val toolbarAtTop = booleanMatrix.second
+ Box(
+ contentAlignment = if (toolbarAtTop) Alignment.TopStart else Alignment.BottomStart,
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ SearchBarPreview() // unrestricted vertically so will follow contentAlignment
+ TorHomePage(
+ // restricted vertically so will not follow contentAlignment
+ shouldInitiallyShowPromo = mutableStateOf(booleanMatrix.first),
+ toolBarAtTop = toolbarAtTop,
+ toolPair = Pair(TorCampaignViewModel.toolList[0], TorCampaignViewModel.toolList[1]),
+ )
+ }
+}
+
+private class BooleanBooleanPreviewParameterProvider :
+ PreviewParameterProvider<Pair<Boolean, Boolean>> {
+ override val values: Sequence<Pair<Boolean, Boolean>>
+ get() = sequenceOf(
+ Pair(true, true),
+ Pair(true, false),
+ Pair(false, true),
+ Pair(false, false),
+ )
+}
=====================================
mobile/android/fenix/app/src/main/res/drawable/globe_chain_burst_yec.xml
=====================================
@@ -0,0 +1,35 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="124dp" android:viewportHeight="124" android:viewportWidth="160" android:width="160dp">
+
+ <path android:fillColor="#B6E368" android:pathData="M79.97,86.16C65.19,89.07 56.28,80.34 53.67,75.61L34.02,84.97L55.14,84.24L42.62,106.51L63.16,91.39L61.15,117.92L73.64,93L84.86,111.85L79.98,86.16H79.97Z"/>
+
+ <path android:fillColor="#B6E368" android:pathData="M72.38,32.02C87.13,28.91 96.16,37.52 98.83,42.21L118.35,32.58L97.24,33.6L109.46,11.17L89.13,26.57L90.78,0L78.63,25.09L67.15,6.4L72.38,32.02Z"/>
+
+ <path android:fillAlpha="0.8" android:fillColor="#C272FF" android:fillType="evenOdd" android:pathData="M82.86,42.77H84.19V44.18H82.79V42.84H80.14V44.18H81.46V45.52H82.79V48.27H84.19V51.09H82.79V48.34H81.4V45.59H80.06V44.18H78.74V42.84H77.34V53.77H84.19V51.09H85.59V53.77H89.58V55.18H92.31V56.59H89.51V55.18H85.6V67.44H89.58V66.1H92.38V67.44H93.71V68.85H92.38V70.26H91.05V71.67H89.65V73.01H88.25V74.35H86.99V75.76H85.6V74.35H86.86V72.94H88.25V71.6H89.65V70.26H90.98V68.85H92.31V67.51H89.58V68.85H85.6V71.67H84.2V74.42H82.8V75.76H85.6V77.17H81.4V74.35H82.8V71.6H84.19V68.85H77.34V77.17H81.4V78.43H71.88V77.17H67.76V75.76H66.43V74.35H65.1V73.01H63.7V71.67H62.38V70.26H63.78V71.6H65.1V72.94H66.5V74.35H67.83V75.76H70.56V74.42H69.16V71.67H67.83V68.85H63.7V67.51H60.98V68.85H59.58V67.44H58.25V55.18H59.65V67.44H60.98V66.1H63.78V67.44H67.83V55.18H63.78V56.59H60.98V55.18H59.65V52.36H60.98V49.61H62.38V48.27H63.78V46.93H65.1V45.59H66.43V44.18H69.23V45.59H66.5V47H65.17V48.34H63.77V49.68H62.37V52.43H61.04V55.18H63.7V53.77H67.83V51.09H69.22V53.77H75.94V42.84H74.68V44.18H73.28V42.84H70.62V44.18H69.22V42.77H70.55V41.43H82.86V42.77V42.77ZM69.22,71.59H70.55V74.34H71.95V77.16H75.94V68.84H69.23V71.59H69.22ZM77.34,67.44H84.19V55.17H77.34V67.44ZM69.22,67.44H75.94V55.17H69.22V67.44Z" android:strokeAlpha="0.8"/>
+
+ <path android:fillAlpha="0.8" android:fillColor="#C272FF" android:pathData="M62.37,70.25H60.98V68.84H62.37V70.25Z" android:strokeAlpha="0.8"/>
+
+ <path android:fillAlpha="0.8" android:fillColor="#C272FF" android:pathData="M95.1,67.44H93.7V55.17H95.1V67.44Z" android:strokeAlpha="0.8"/>
+
+ <path android:fillAlpha="0.8" android:fillColor="#C272FF" android:pathData="M86.99,45.58H88.25V46.92H89.64V48.26H90.97V49.6H92.37V52.35H93.7V55.17H92.3V52.42H90.97V49.67H89.58V48.33H88.25V46.99H86.85V45.58H84.19V44.17H86.99V45.58V45.58Z" android:strokeAlpha="0.8"/>
+
+ <path android:fillAlpha="0.8" android:fillColor="#C272FF" android:pathData="M73.28,45.58H71.95V48.33H70.62V51.08H69.22V48.26H70.55V45.51H71.88V44.17H73.28V45.58Z" android:strokeAlpha="0.8"/>
+
+ <path android:fillAlpha="0.8" android:fillColor="#00000000" android:pathData="M84.19,44.18V42.77H82.86V41.43H70.55V42.77H69.22V44.18M84.19,44.18H82.79V42.84H80.14V44.18H81.46V45.52H82.79V48.27H84.19V51.09M84.19,44.18V45.59H86.85V47H88.25V48.34H89.58V49.68H90.97V52.43H92.3V55.18M84.19,44.18H86.99V45.59H88.25V46.93H89.64V48.27H90.97V49.61H92.37V52.35H93.7V55.17M69.22,44.18H66.43V45.59H65.1V46.93H63.77V48.27H62.37V49.61H60.98V52.35H59.65V55.17M69.22,44.18V45.59H66.5V47H65.17V48.34H63.77V49.68H62.37V52.43H61.04V55.18H63.7V53.77H67.82V51.09H69.22M69.22,44.18H70.62V42.84H73.28V44.18M93.7,55.17H95.1V67.44H93.7M93.7,55.17V67.44M84.19,51.08H82.79V48.33H81.4V45.58H80.06V44.17H78.74V42.84H77.34V53.76H84.19V51.08H84.19ZM84.19,51.08H85.59V53.76H89.58V55.17H92.3M93.7,67.44H92.37V66.1H89.58V67.44H85.59V55.17H89.5V56.58H92.3V55.17H93.7M93.7,67.44V68.85H92.37V70.26H91.04V71.67H89.64V73.01H88.25V74.35H86.99V75.76H85.59M85.59,75.75V74.34H86.85V72.93H88.25V71.59H89.64V70.25H90.97V68.84H92.3V67.5H89.58V68.84H85.59V71.66H84.19V74.41H82.79V75.75H85.59V75.75ZM85.59,75.75V77.16H81.39M81.39,77.16V74.34H82.79V71.59H84.19V68.84H77.33V77.16H81.39H81.39ZM81.39,77.16V78.43H71.88V77.16H67.75V75.75H66.43V74.34H65.1V73H63.7V71.66H62.37V70.25M62.37,70.25H63.77V71.59H65.1V72.93H66.5V74.34H67.83V75.75H70.55V74.41H69.16V71.66H67.83V68.85H63.7V67.51H60.97V68.85M62.37,70.25H60.98V68.84H62.37V70.25ZM60.97,68.84H59.57V67.43H58.25V55.17H59.65M59.65,55.17V67.44H60.98V66.1H63.77V67.44H67.83V55.17H63.77V56.58H60.98V55.17H59.65ZM69.22,51.08V53.76H75.93V42.84H74.68V44.17H73.28M69.22,51.08H70.62V48.33H71.95V45.58H73.28V44.17H71.88V45.51H70.55V48.26H69.22V51.08ZM69.22,71.59H70.55V74.34H71.95V77.16H75.94V68.84H69.23V71.59H69.22ZM77.34,67.44H84.19V55.17H77.34V67.44ZM69.22,67.44H75.93V55.17H69.22V67.44Z" android:strokeAlpha="0.8" android:strokeColor="#C272FF" android:strokeWidth="0.23451"/>
+
+ <path android:fillColor="#ffffff" android:pathData="M53.84,42.78H56.64L54.84,40.42L58.14,39.7L55.15,36.24L57.62,36.17L57.73,35.5L41.11,29.32C40.64,29.14 40.17,29 39.7,28.88L31.58,7.75C29.81,3.15 25.32,0.06 20.41,0.06C18.94,0.06 17.49,0.33 16.11,0.87L7.65,4.16C1.5,6.55 -1.57,13.54 0.81,19.73L12.4,49.92C14.17,54.53 18.66,57.62 23.56,57.62C24.22,57.62 24.87,57.57 25.51,57.46C26.74,58.58 28.17,59.46 29.77,60.05L46.5,66.27L44.54,62.76H48.38L47.08,59.56H51.23L49.53,56.39L38.61,52.33C42.24,49.92 44.27,45.7 43.94,41.38L56.68,46.11L53.84,42.78ZM49.04,56.96L50.07,58.86H46.04L47.35,62.07H43.36L44.97,64.96L30.01,59.4C26.89,58.24 24.41,55.93 23.03,52.89C21.64,49.86 21.52,46.48 22.67,43.35L24.89,37.34C25.95,34.46 27.98,32.2 30.46,30.8L33.78,39.46C33.55,39.74 33.36,40.07 33.23,40.44L31.01,46.45C30.68,47.35 30.72,48.31 31.11,49.17C31.5,50.04 32.21,50.7 33.1,51.03L49.04,56.96ZM21.23,46.86C21.17,46.76 21.12,46.65 21.08,46.54L9.48,16.35C9.13,15.45 9.36,14.68 9.53,14.31C9.7,13.93 10.11,13.24 11.01,12.89L19.46,9.6C19.78,9.48 20.1,9.42 20.41,9.42C21.32,9.42 22.44,9.95 22.9,11.14L30.21,30.15C27.54,31.61 25.37,34.02 24.24,37.1L22.02,43.11C21.57,44.33 21.3,45.6 21.23,46.86ZM33.88,40.68C33.93,40.52 34,40.38 34.08,40.24L34.5,41.32C34.85,42.23 34.62,42.99 34.45,43.37H34.45C34.28,43.75 33.88,44.43 32.98,44.78L32.26,45.06L33.88,40.68ZM37.79,52.02L33.34,50.37C32.62,50.11 32.06,49.58 31.74,48.88C31.42,48.19 31.39,47.41 31.66,46.7L31.94,45.93L33.23,45.43C34.36,45 34.87,44.13 35.08,43.65C35.29,43.17 35.58,42.21 35.14,41.07L34.58,39.61V39.59L34.35,39.01L34.32,38.91L31.07,30.48L31.06,30.46L30.85,29.91L30.82,29.82L23.55,10.88C22.97,9.39 21.56,8.72 20.41,8.72C20.01,8.72 19.61,8.8 19.21,8.95L10.76,12.24C9.63,12.68 9.11,13.55 8.9,14.02C8.69,14.5 8.4,15.47 8.84,16.61L20.43,46.8C20.61,47.27 20.88,47.67 21.2,47.97C21.24,49.75 21.64,51.52 22.4,53.18C23.03,54.56 23.87,55.79 24.88,56.85C24.44,56.9 24,56.93 23.56,56.93C18.94,56.93 14.71,54.01 13.04,49.67L1.45,19.48C-0.79,13.65 2.1,7.07 7.9,4.81L16.35,1.52C17.66,1.01 19.03,0.75 20.41,0.75C25.04,0.75 29.26,3.67 30.93,8.01L38.88,28.7L38.91,28.78L39.16,29.43L39.18,29.47L42.53,38.2C42.8,38.9 42.99,39.61 43.11,40.33C43.12,40.35 43.13,40.37 43.13,40.4C43.17,40.61 43.2,40.83 43.22,41.05C43.22,41.07 43.22,41.09 43.22,41.11C43.68,45.46 41.58,49.76 37.79,52.02ZM43.86,40.6C43.74,39.71 43.51,38.81 43.18,37.94L40.01,29.69C40.29,29.77 40.58,29.86 40.87,29.97L55.81,35.53L53.67,35.59L56.85,39.27L53.63,39.97L55.24,42.09H52.33L54.41,44.53L43.86,40.6Z"/>
+
+ <path android:fillColor="#ffffff" android:pathData="M157.76,99.65L136.71,69.02C134.41,65.69 130.67,63.66 126.65,63.57C125.48,62.52 124.09,61.66 122.51,61.08L107.43,55.47L107.36,55.44L103.68,55.55L106.41,58.69L102.99,59.44L105.06,62.15H102.4L104.54,64.66L115.19,68.62L112.76,70.31C109.62,72.5 107.78,75.86 107.41,79.41L97.62,75.77L99.32,78.92H94.98L96.29,82.12H92.65L94.1,84.72L111.42,91.16C111.59,91.22 111.75,91.28 111.92,91.33L130.63,118.54C132.98,121.96 136.85,124 140.98,124C143.54,124 146.01,123.22 148.12,121.75L154.58,117.26C157.34,115.33 159.19,112.44 159.79,109.12C160.4,105.79 159.68,102.43 157.76,99.65ZM104.95,64.06L103.91,62.85H106.46L104.21,59.89L107.7,59.13L105.17,56.2L107.25,56.14L122.27,61.73C123.5,62.19 124.62,62.82 125.59,63.59C123.31,63.73 121.12,64.49 119.22,65.82L115.89,68.13L104.95,64.06ZM111.65,90.5L111.25,90.35L94.58,84.15L93.84,82.82H97.32L96.01,79.61H100.48L99.1,77.06L107.36,80.13L108.04,80.38L114.5,82.79C115.37,83.11 116.28,83.1 117.1,82.82C117.32,82.75 117.54,82.65 117.74,82.53C118.53,82.1 119.17,81.39 119.5,80.48L120.95,76.56L124.77,73.9C125.25,73.57 125.79,73.39 126.36,73.39C126.8,73.39 127.92,73.51 128.68,74.61L129.76,76.19C129.69,76.47 129.61,76.75 129.51,77.02L127.19,83.33L127.08,83.63C126.11,86.07 124.45,88.03 122.42,89.34C122.23,89.47 122.03,89.59 121.83,89.7C119.04,91.29 115.64,91.75 112.36,90.74C112.13,90.67 111.89,90.59 111.65,90.5ZM117.15,80.21C117.22,79.78 117.48,78.97 118.31,78.39L119.95,77.26L118.85,80.24C118.57,81 118.04,81.58 117.38,81.94C117.04,81.24 117.08,80.57 117.15,80.21ZM130.06,77.56L130.17,77.25C130.2,77.14 130.24,77.04 130.27,76.93L149.73,105.24C150.31,106.08 150.27,106.93 150.19,107.36C150.11,107.79 149.85,108.6 149.02,109.18L142.57,113.67C142.09,114.01 141.55,114.17 140.98,114.17C140.53,114.17 139.41,114.06 138.66,112.95L122.82,89.91C124.96,88.52 126.7,86.46 127.72,83.89L130.06,77.56ZM159.11,108.99C158.54,112.14 156.79,114.87 154.18,116.68L147.73,121.18C145.73,122.57 143.4,123.3 140.98,123.3C137.07,123.3 133.42,121.38 131.2,118.15L112.97,91.63C113.95,91.86 114.94,91.97 115.91,91.97C118.15,91.97 120.32,91.38 122.23,90.28L138.09,113.35C139.03,114.72 140.42,114.87 140.98,114.87C141.7,114.87 142.36,114.66 142.96,114.25L149.42,109.75C150.45,109.03 150.77,108.02 150.87,107.48C150.96,106.95 151.02,105.89 150.3,104.85L130.51,76.06L129.97,75.26L129.25,74.22C128.3,72.84 126.91,72.7 126.36,72.7H126.36C125.64,72.7 124.97,72.91 124.38,73.32L121.37,75.42L120.37,76.11L117.92,77.82C116.88,78.54 116.56,79.55 116.46,80.08C116.39,80.53 116.33,81.34 116.74,82.2C116.1,82.39 115.41,82.38 114.74,82.13L108.08,79.66C108.39,76.25 110.14,72.99 113.15,70.89L115.99,68.91L116.68,68.43L119.61,66.39C121.6,65 123.94,64.27 126.36,64.27H126.37C126.72,64.27 127.07,64.28 127.41,64.31C130.91,64.62 134.12,66.48 136.13,69.42L157.19,100.05C159,102.68 159.68,105.85 159.11,108.99Z"/>
+
+ <path android:fillColor="#C272FF" android:pathData="M26.73,77.81L30.91,77.83L30.91,79.04L26.73,79.03V84.32H25.51V79.03L21.2,79.02L21.2,77.8L25.51,77.81V72.54H26.73V77.81Z"/>
+
+ <path android:fillColor="#C272FF" android:pathData="M132.82,37.87L137,37.88L137,39.08L132.82,39.07V44.31H131.6V39.07L127.29,39.06L127.29,37.85L131.6,37.86V32.65H132.82V37.87Z"/>
+
+ <path android:fillColor="#C272FF" android:pathData="M20.17,99.2C20.17,98.79 19.84,98.46 19.43,98.46C19.02,98.46 18.69,98.79 18.69,99.2C18.69,99.62 19.02,99.95 19.43,99.95V101.17C18.36,101.17 17.49,100.29 17.49,99.2C17.49,98.12 18.36,97.24 19.43,97.24C20.51,97.24 21.38,98.12 21.38,99.2C21.38,100.29 20.51,101.17 19.43,101.17V99.95C19.84,99.95 20.17,99.62 20.17,99.2Z"/>
+
+ <path android:fillColor="#C272FF" android:pathData="M116.28,43.22C117.35,43.22 118.22,44.1 118.22,45.18C118.22,46.27 117.35,47.15 116.28,47.15C115.2,47.15 114.33,46.27 114.33,45.18C114.33,44.1 115.2,43.22 116.28,43.22Z"/>
+
+ <path android:fillColor="#C272FF" android:pathData="M134.74,51.08C135.81,51.08 136.68,51.96 136.68,53.04C136.68,54.12 135.81,55 134.74,55C133.66,55 132.79,54.12 132.79,53.04C132.79,51.96 133.66,51.08 134.74,51.08Z"/>
+
+ <path android:fillColor="#C272FF" android:pathData="M32.81,91.67C32.81,91.26 32.47,90.93 32.06,90.93C31.66,90.93 31.32,91.26 31.32,91.67C31.32,92.09 31.66,92.42 32.06,92.42V93.64C30.99,93.64 30.12,92.76 30.12,91.67C30.12,90.59 30.99,89.71 32.06,89.71C33.14,89.71 34.01,90.59 34.01,91.67C34.01,92.76 33.14,93.64 32.06,93.64V92.42C32.47,92.42 32.81,92.09 32.81,91.67Z"/>
+
+</vector>
=====================================
mobile/android/fenix/app/src/main/res/drawable/heart.xml
=====================================
@@ -0,0 +1,7 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="16dp" android:viewportHeight="16" android:viewportWidth="16" android:width="16dp">
+
+ <path android:fillColor="#FBFBFE" android:pathData="M8,6C8,6 8,2 11.5,2C15,2 15,5 15,6C15,10.5 8,15 8,15V6Z"/>
+
+ <path android:fillColor="#FBFBFE" android:pathData="M8,6C8,6 8,2 4.5,2C1,2 1,5 1,6C1,10.5 8,15 8,15L9,9L8,6Z"/>
+
+</vector>
=====================================
mobile/android/fenix/app/src/main/res/values/torbrowser_strings.xml
=====================================
@@ -153,4 +153,36 @@
<!-- Year End Campaign. Here "Close" is a verb, referring to closing the banner. This is used primarily for screen readers to give the small "x" button a name. -->
<string name="close_yec_button_description">Close</string>
+ <!-- Summer 2026 Tor Project funding campaign. -->
+
+ <string name="summer_2026_funding_heading">A new way to fund internet freedom</string>
+ <!-- '%1$s' will be replaced with the name of some internet freedom tool, this is not translated and will only use the Latin alphabet, and will be made to appear bold. Similarly, '%3$s' will be replaced with the name of some other internet freedom tool. '%2$s' and '%4$s' will be replaced with the respective descriptions for the tools, this is translated. Note, since '%2$s' and '%4$s' will contain arbitrary text, you will likely need to separate them out from the rest of the text. The English text uses brackets '(' and ')' to do this. -->
+ <string name="summer_2026_funding_intro">Did you know that internet freedom tools serve millions of people every day? This includes tools like %1$s (%2$s) and %3$s (%4$s).</string>
+ <!-- '%s' will be replaced with summer_2026_call_to_donate and will be made bold. Here "Tor" refers to The Tor Project. -->
+ <string name="summer_2026_funding_outro">But unprecedented funding cuts have harmed the small organizations that maintain these tools. %s—all donations will be matched by friends of Tor.</string>
+ <!-- This will replace '%s' in summer_2026_funding_outro and will be made bold -->
+ <string name="summer_2026_call_to_donate">Help protect internet freedom by donating cryptocurrency</string>
+ <!-- A short description of "Onion Browser": onionbrowser.com -->
+ <string name="summer_2026_funding_tool_onion_browser_description">Tor-powered browser for iOS</string>
+ <!-- A short description of "Quiet": tryquiet.org -->
+ <string name="summer_2026_funding_tool_quiet_description">private group messaging</string>
+ <!-- A short description of "Ricochet Refresh": ricochetrefresh.net -->
+ <string name="summer_2026_funding_tool_ricochet_refresh_description">secure and anonymous messaging</string>
+ <!-- A short description of "SecureDrop": securedrop.org -->
+ <string name="summer_2026_funding_tool_securedrop_description">anonymous whistleblowing app</string>
+ <!-- A short description of "OnionShare": onionshare.org. "&" shows up as "&" and means "and" in english. -->
+ <string name="summer_2026_funding_tool_onionshare_description">secure filesharing, hosting & chatting</string>
+ <!-- A short description of "Digital Security Helpdesk": miaan.org/projects/miaan-digital-security-helpdesk -->
+ <string name="summer_2026_funding_tool_digital_security_helpdesk_description">internet freedom for Iranians</string>
+ <!-- A short description of "Paskoocheh": paskoocheh.com -->
+ <string name="summer_2026_funding_tool_paskoocheh_description">countering digital authoritarianism</string>
+ <!-- A short description of "Unredacted": unredacted.org -->
+ <string name="summer_2026_funding_tool_unredacted_description">keeping people connected, no matter where</string>
+ <!-- A short description of "Osservatorio Nessuno": osservatorionessuno.org -->
+ <string name="summer_2026_funding_tool_osservatorio_nessuno_description">software that leaves no trace</string>
+ <!-- A short description of "Save": open-archive.org/save -->
+ <string name="summer_2026_funding_tool_save_description">private archiving</string>
+ <!-- A short description of "OONI": ooni.org -->
+ <string name="summer_2026_funding_tool_ooni_description">tracking censorship globally</string>
+
</resources>
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/9289a7…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/9289a7…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/tor-browser][tor-browser-150.0a1-16.0-2] 8 commits: fixup! TB 40933: Add tor-launcher functionality
by henry (@henry) 28 Apr '26
by henry (@henry) 28 Apr '26
28 Apr '26
henry pushed to branch tor-browser-150.0a1-16.0-2 at The Tor Project / Applications / Tor Browser
Commits:
b61277be by Henry Wilkes at 2026-04-28T12:46:37+01:00
fixup! TB 40933: Add tor-launcher functionality
TB 44796: Convert `TorProviders` types to strings.
This allows them to be used in log messages.
- - - - -
6a41ffd8 by Henry Wilkes at 2026-04-28T12:53:36+01:00
fixup! TB 40933: Add tor-launcher functionality
TB 44796: Add `TorProviderState`, which tracks the state of a provider.
We create a new `TorProviderBase` class which tracks this state, and the
existing `TorProvider` now extends.
`TorProviderBuilder.sys.mjs`:
+ We provide a callback to let the current provider instance let
`TorProviderBuilder` know when this state has changed.
+ This will emit `TorProviderSateChanged` as a replacement for
`TorProcessExited`. The `Stopped` state has the same role as the
previous event, except it is more generic. This event also lets any
consumers know when a provider has been just replaced (`Starting`) and
successfully initialised (`Running`).
`TorControlPort.sys.mjs`:
+ Add `onClosed` callback to signal to the `TorProvider` that the
control port has closed, which will trigger `_stoppedInternal`.
+ Delay adding the event handler so the `TorProvider` only listens to a
control port after it is successfully set up and adopted by the
`TorProvider`.
`TorProvider.sys.mjs`:
+ `TorProvider` no longer emits `TorProviderTopics.ProcessExited`.
+ Instead, `TorProvider` calls `_stoppedInternal` whenever the provider
stops running. I.e. whenever the control port is closed, which
includes when the process exits but also other scenarios where the
control port might close unexpectedly.
- - - - -
7167229f by Henry Wilkes at 2026-04-28T12:53:37+01:00
fixup! TB 40933: Add tor-launcher functionality
TB 44796: Convert the "none" provider into a `TorProviderNone` class, so
it can be handled by the same logic as `TorProvider`.
- - - - -
adba934e by Henry Wilkes at 2026-04-28T12:53:39+01:00
fixup! TB 40933: Add tor-launcher functionality
TB 44796: Add the `settledState` method to allow `TorConnect` to wait
for the first provider's state after it has settled. This is a way to
get the current providers state prior to having listened to
`TorProviderStateChange`.
Add the `replace` method to allow `TorConnect` to request a new provider
on the user's behalf.
- - - - -
e74c8d84 by Henry Wilkes at 2026-04-28T12:53:40+01:00
fixup! TB 40933: Add tor-launcher functionality
TB 44796: Drop `#torExited` and internals of `firstWindowLoaded` and
instead use promptProviderState which handles both. These are only
temporary changes to be dropped in tor-browser#43570.
- - - - -
615707ff by Henry Wilkes at 2026-04-28T12:53:41+01:00
fixup! TB 40597: Implement TorSettings module
TB 44796: Listen for ProviderStateChanged rather than ProcessStopped.
- - - - -
48f5bfb2 by Henry Wilkes at 2026-04-28T12:53:42+01:00
fixup! TB 40933: Add tor-launcher functionality
TB 44796: Drop `TorProvider.isRunning`.
- - - - -
424f8e1a by Henry Wilkes at 2026-04-28T12:53:43+01:00
fixup! TB 40597: Implement TorSettings module
TB 44796: Use `TorProvider.state` instead of `isRunning`.
- - - - -
8 changed files:
- toolkit/components/tor-launcher/TorControlPort.sys.mjs
- toolkit/components/tor-launcher/TorProvider.sys.mjs
- + toolkit/components/tor-launcher/TorProviderBase.sys.mjs
- toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs
- + toolkit/components/tor-launcher/TorProviderNone.sys.mjs
- toolkit/components/tor-launcher/moz.build
- toolkit/modules/TorConnect.sys.mjs
- toolkit/modules/TorSettings.sys.mjs
Changes:
=====================================
toolkit/components/tor-launcher/TorControlPort.sys.mjs
=====================================
@@ -384,21 +384,19 @@ export class TorController {
/**
* The event handler.
*
- * @type {TorEventHandler}
+ * @type {?TorEventHandler}
*/
- #eventHandler;
+ #eventHandler = null;
/**
* Connect to a control port over a Unix socket.
* Not available on Windows.
*
* @param {nsIFile} ipcFile The path to the Unix socket to connect to
- * @param {TorEventHandler} eventHandler The event handler to use for
- * asynchronous notifications
* @returns {TorController}
*/
- static fromIpcFile(ipcFile, eventHandler) {
- return new TorController(AsyncSocket.fromIpcFile(ipcFile), eventHandler);
+ static fromIpcFile(ipcFile) {
+ return new TorController(AsyncSocket.fromIpcFile(ipcFile));
}
/**
@@ -406,15 +404,10 @@ export class TorController {
*
* @param {string} host The hostname to connect to
* @param {number} port The port to connect the to
- * @param {TorEventHandler} eventHandler The event handler to use for
- * asynchronous notifications
* @returns {TorController}
*/
- static fromSocketAddress(host, port, eventHandler) {
- return new TorController(
- AsyncSocket.fromSocketAddress(host, port),
- eventHandler
- );
+ static fromSocketAddress(host, port) {
+ return new TorController(AsyncSocket.fromSocketAddress(host, port));
}
/**
@@ -425,15 +418,22 @@ export class TorController {
*
* @private
* @param {AsyncSocket} socket The socket to use
- * @param {TorEventHandler} eventHandler The event handler to use for
- * asynchronous notifications
*/
- constructor(socket, eventHandler) {
+ constructor(socket) {
this.#socket = socket;
- this.#eventHandler = eventHandler;
this.#startMessagePump();
}
+ /**
+ * Set an event handler for this instance.
+ *
+ * @param {TorEventHandler} eventHandler The event handler to use for
+ * asynchronous notifications
+ */
+ setEventHandler(eventHandler) {
+ this.#eventHandler = eventHandler;
+ }
+
// Socket and communication handling
/**
@@ -649,6 +649,7 @@ export class TorController {
this.#socket?.close();
} finally {
this.#socket = null;
+ this.#eventHandler?.onClosed();
}
}
@@ -1324,6 +1325,7 @@ export class TorController {
* The controller owner can implement this methods to receive asynchronous
* notifications from the controller.
*
+ * @property {OnClosed} onClosed Called when the socket is closed.
* @property {OnBootstrapStatus} onBootstrapStatus Called when a bootstrap
* status is received (i.e., a STATUS_CLIENT event with a BOOTSTRAP action)
* @property {OnLogMessage} onLogMessage Called when a log message is received
@@ -1336,6 +1338,9 @@ export class TorController {
* a connect cell along a circuit (i.e., a STREAM event with a SENTCONNECT
* status)
*/
+/**
+ * @callback OnClosed
+ */
/**
* @callback OnBootstrapStatus
*
=====================================
toolkit/components/tor-launcher/TorProvider.sys.mjs
=====================================
@@ -6,6 +6,7 @@ import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs";
import { TorLauncherUtil } from "resource://gre/modules/TorLauncherUtil.sys.mjs";
import { TorParsers } from "resource://gre/modules/TorParsers.sys.mjs";
+import { TorProviderBase } from "resource://gre/modules/TorProviderBase.sys.mjs";
import {
TorBootstrapError,
TorProviderTopics,
@@ -88,7 +89,7 @@ const TorConfigKeys = Object.freeze({
* It can start a new tor instance, or connect to an existing one.
* In the former case, it also takes its ownership by default.
*/
-export class TorProvider {
+export class TorProvider extends TorProviderBase {
/**
* The control port settings.
*
@@ -184,7 +185,7 @@ export class TorProvider {
* Starts a new tor process and connect to its control port, or connect to the
* control port of an existing tor daemon.
*/
- async init() {
+ async _initInternal() {
logger.debug("Initializing the Tor provider.");
// These settings might be customized in the following steps.
@@ -259,7 +260,7 @@ export class TorProvider {
* control connection is closed. Therefore, as a matter of facts, calling this
* function also makes the child Tor instance stop.
*/
- uninit() {
+ async _uninitInternal() {
logger.debug("Uninitializing the Tor provider.");
if (this.#torProcess) {
@@ -509,17 +510,6 @@ export class TorProvider {
return TorLauncherUtil.shouldStartAndOwnTor;
}
- /**
- * TODO: Rename to isReady once we remove finish the migration.
- *
- * @returns {boolean} true if we currently have a connection to the control
- * port. We take for granted that if we have one, we authenticated to it, and
- * so we have already verified we can send and receive data.
- */
- get isRunning() {
- return this.#controlConnection?.isOpen ?? false;
- }
-
/**
* Return the data about the current bridge, if any, or null.
* We can detect bridge only when the configured bridge lines include the
@@ -740,6 +730,11 @@ export class TorProvider {
};
tryConnect();
});
+ // The previous TorControlPort instances may have failed, in which case we
+ // want to ignore their events (such as `onClose`). This instance has just
+ // succeeded and will be owned by this TorProvider instance, therefore we
+ // want to start listening to its events now.
+ this.#controlConnection.setEventHandler(this);
// The following code will never throw, but we still want to wait for it
// before marking the provider as initialized.
@@ -751,7 +746,6 @@ export class TorProvider {
this.#torProcess.onExit = exitCode => {
logger.info(`The tor process exited with code ${exitCode}`);
this.#closeConnection("The tor process exited suddenly");
- Services.obs.notifyObservers(null, TorProviderTopics.ProcessExited);
};
if (!TorLauncherUtil.shouldOnlyConfigureTor) {
await this.#takeOwnership();
@@ -823,14 +817,12 @@ export class TorProvider {
let controlPort;
if (this.#controlPortSettings.ipcFile) {
controlPort = lazy.TorController.fromIpcFile(
- this.#controlPortSettings.ipcFile,
- this
+ this.#controlPortSettings.ipcFile
);
} else {
controlPort = lazy.TorController.fromSocketAddress(
this.#controlPortSettings.host,
- this.#controlPortSettings.port,
- this
+ this.#controlPortSettings.port
);
}
try {
@@ -864,21 +856,27 @@ export class TorProvider {
* attempt)
*/
#closeConnection(reason) {
- this.#cancelConnection(reason);
- if (this.#controlConnection) {
- logger.info("Closing the control connection", reason);
- try {
- this.#controlConnection.close();
- } catch (e) {
- logger.error("Failed to close the control port connection", e);
+ try {
+ this.#cancelConnection(reason);
+ if (this.#controlConnection) {
+ logger.info("Closing the control connection", reason);
+ try {
+ this.#controlConnection.close();
+ } catch (e) {
+ logger.error("Failed to close the control port connection", e);
+ }
+ this.#controlConnection = null;
+ } else {
+ logger.trace(
+ "Requested to close an already closed control port connection"
+ );
}
- this.#controlConnection = null;
- } else {
- logger.trace(
- "Requested to close an already closed control port connection"
- );
+ } finally {
+ this.#lastWarning = {};
+ // #controlConnection.close() may already trigger onClosed, but we
+ // call it once more for error cases as well.
+ this._stoppedInternal();
}
- this.#lastWarning = {};
}
// Authentication
@@ -975,6 +973,13 @@ export class TorProvider {
// Notification handlers
+ /**
+ * Notification that the control port has closed.
+ */
+ onClosed() {
+ this._stoppedInternal();
+ }
+
/**
* Receive and process a notification with the bootstrap status.
*
=====================================
toolkit/components/tor-launcher/TorProviderBase.sys.mjs
=====================================
@@ -0,0 +1,200 @@
+import {
+ TorProviderState,
+ TorProviderInitError,
+} from "resource://gre/modules/TorProviderBuilder.sys.mjs";
+
+/**
+ * @callback StateChangedCallback
+ */
+
+/**
+ * The base class for tor providers.
+ */
+export class TorProviderBase {
+ /**
+ * The current provider state.
+ *
+ * @type {string}
+ */
+ #state = TorProviderState.Starting;
+
+ /**
+ * The callback for when our state changes. `null` once the `uninit` method is
+ * called.
+ *
+ * @type {?StateChangedCallback}
+ */
+ #stateChangedCallback;
+
+ /**
+ * The promise to return from the `init` method.
+ *
+ * @type {?Promise<undefined>}
+ */
+ #initPromise = null;
+
+ /**
+ * The promise to return from the `uninit` method.
+ *
+ * @type {?Promise<undefined>}
+ */
+ #uninitPromise = null;
+
+ /**
+ * Create a new provider.
+ *
+ * @param {StateChangedCallback} stateChangedCallback - A callback to let an
+ * owner know that a provider's state has changed.
+ */
+ // NOTE: This should *not* be overridden by implementations.
+ constructor(stateChangedCallback) {
+ this.#stateChangedCallback = stateChangedCallback;
+ }
+
+ /**
+ * Initialize the provider and wait for it to be `Running`.
+ *
+ * This is safe to call multiple times, with each call waiting for the
+ * provider to be ready.
+ */
+ // NOTE: This should *not* be overridden by implementations.
+ async init() {
+ if (!this.#initPromise) {
+ this.#initPromise = this._initInternal().then(
+ () => {
+ this.#setState(TorProviderState.Running);
+ },
+ error => {
+ this.#setState(TorProviderState.Stopped);
+ // Wrap the error in `TorProviderInitError` to let callers know that
+ // this error is an initialization error.
+ throw new TorProviderInitError(error);
+ }
+ );
+ }
+ return this.#initPromise;
+ }
+
+ /**
+ * Uninitialize the provider and wait for it to be cleaned up.
+ *
+ * This is safe to call multiple times, with each call waiting for the
+ * clean up.
+ */
+ // NOTE: This should *not* be overridden by implementations.
+ async uninit() {
+ if (!this.#uninitPromise) {
+ this.#setState(TorProviderState.Stopped);
+ this.#stateChangedCallback = null;
+ this.#uninitPromise = this._uninitInternal();
+ }
+ return this.#uninitPromise;
+ }
+
+ /**
+ * The current `TorProviderState` state of the provider.
+ *
+ * @type {string}
+ */
+ // NOTE: This should *not* be overridden by implementations.
+ get state() {
+ return this.#state;
+ }
+
+ /**
+ * Set the state of the provider. Announcing this via a callback.
+ *
+ * @param {string} state - The new `TorProviderState`.
+ */
+ #setState(state) {
+ if (state === this.#state) {
+ return;
+ }
+ if (this.#state === TorProviderState.Stopped) {
+ // Ignore any changes away from the `Stopped` state, which is unexpected
+ // since implementations can only trigger the `Stopped` state, and
+ // everything else is handled by `TorProviderBase`.
+ return;
+ }
+ this.#state = state;
+ this.#stateChangedCallback?.();
+ }
+
+ /**
+ * An internal method to be called by implementations when they stop working.
+ *
+ * Optional and safe to call as part of `_uninitInternal`.
+ */
+ // NOTE: This should *not* be overridden by implementations.
+ _stoppedInternal() {
+ this.#setState(TorProviderState.Stopped);
+ }
+
+ // Implementation methods.
+
+ /**
+ * An internal initialization method for provider instances to implement.
+ */
+ async _initInternal() {
+ throw new Error("_initInternal not implemented.");
+ }
+
+ /**
+ * An internal uninitialization method for provider instances to implement.
+ */
+ async _uninitInternal() {
+ throw new Error("_uninitInternal not implemented.");
+ }
+
+ async writeBridgeSettings(_bridges) {
+ throw new Error("writeBridgeSettings not implemented.");
+ }
+
+ async writeProxySettings(_proxy) {
+ throw new Error("writeProxySettings not implemented.");
+ }
+
+ async writeFirewallSettings(_firewall) {
+ throw new Error("writeFirewallSettings not implemented.");
+ }
+
+ async flushSettings() {
+ throw new Error("flushSettings not implemented.");
+ }
+
+ async connect() {
+ throw new Error("connect not implemented.");
+ }
+
+ async stopBootstrap() {
+ throw new Error("stopBootstrap not implemented.");
+ }
+
+ async newnym() {
+ throw new Error("newnym not implemented.");
+ }
+
+ async getBridges() {
+ throw new Error("getBridges not implemented.");
+ }
+
+ async getPluggableTransports() {
+ throw new Error("getPluggableTransports not implemented.");
+ }
+
+ async onionAuthAdd(_address, _b64PrivateKey, _isPermanent) {
+ throw new Error("onionAuthAdd not implemented.");
+ }
+
+ async onionAuthRemove(_address) {
+ throw new Error("onionAuthRemove not implemented.");
+ }
+
+ async onionAuthViewKeys() {
+ throw new Error("onionAuthViewKeys not implemented.");
+ }
+
+ get currentBridge() {
+ throw new Error("currentBridge not implemented.");
+ }
+}
=====================================
toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs
=====================================
@@ -6,10 +6,19 @@ const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
TorLauncherUtil: "resource://gre/modules/TorLauncherUtil.sys.mjs",
TorProvider: "resource://gre/modules/TorProvider.sys.mjs",
+ TorProviderNone: "resource://gre/modules/TorProviderNone.sys.mjs",
+});
+
+ChromeUtils.defineLazyGetter(lazy, "logger", () => {
+ return console.createInstance({
+ // Share preference with TorProvider.
+ maxLogLevelPref: "browser.tor_provider.log_level",
+ prefix: "TorProviderBuilder",
+ });
});
export const TorProviderTopics = Object.freeze({
- ProcessExited: "TorProcessExited",
+ ProviderStateChanged: "TorProviderStateChanged",
BootstrapStatus: "TorBootstrapStatus",
BootstrapError: "TorBootstrapError",
TorLog: "TorLog",
@@ -18,6 +27,15 @@ export const TorProviderTopics = Object.freeze({
CircuitCredentialsMatched: "TorCircuitCredentialsMatched",
});
+/**
+ * The tracked state a provider might be in.
+ */
+export const TorProviderState = Object.freeze({
+ Starting: "Starting",
+ Running: "Running",
+ Stopped: "Stopped",
+});
+
/**
* Wrapper error class for errors raised during TorProvider.init.
*/
@@ -54,8 +72,8 @@ export class TorBootstrapError extends Error {
}
export const TorProviders = Object.freeze({
- none: 0,
- tor: 1,
+ none: "none",
+ tor: "tor",
});
/**
@@ -65,6 +83,17 @@ export const TorProviders = Object.freeze({
* @property {string} msg The message
*/
+/**
+ * @typedef {object} TorProviderData
+ *
+ * The data associated with a tor provider.
+ *
+ * @property {TorProviderBase} provider - The provider instance.
+ * @property {Promise<undefined>} initPromise - A promise that fulfils after the
+ * previous provider is cleaned up and the new provider's initialization
+ * completes or throws an error.
+ */
+
/**
* The factory to get a Tor provider.
* Currently we support only TorProvider, i.e., the one that interacts with
@@ -72,14 +101,35 @@ export const TorProviders = Object.freeze({
*/
export class TorProviderBuilder {
/**
- * A promise with the instance of the provider that we are using.
+ * Data about the current provider instance.
*
- * @type {Promise<TorProvider>?}
+ * @type {?TorProviderData}
*/
- static #provider = null;
+ static #providerData = null;
/**
- * A record of the log messages from all TorProvider instances.
+ * Whether the `uninit` method has been called.
+ *
+ * @type {boolean}
+ */
+ static #uninitialized = false;
+
+ /**
+ * Check that we are active before a public call.
+ *
+ * @throws {Error} Throws if we are not active.
+ */
+ static #checkActive() {
+ if (this.#uninitialized) {
+ throw new Error("TorProviderBuilder has already been uninitialized.");
+ }
+ if (!this.#providerData) {
+ throw new Error("TorProviderBuilder has not been initialized.");
+ }
+ }
+
+ /**
+ * A record of the log messages from all provider instances.
*
* @type {LogEntry[]}
*/
@@ -120,22 +170,6 @@ export class TorProviderBuilder {
this.#log.push(logEntry);
}
- /**
- * The observer that checks when the tor process exits, and reinitializes the
- * provider.
- *
- * @type {Function}
- */
- static #exitObserver = null;
-
- /**
- * Tell whether the browser UI is ready.
- * We ignore any errors until it is because we cannot show them.
- *
- * @type {boolean}
- */
- static #uiReady = false;
-
/**
* Initialize the provider of choice.
*/
@@ -149,115 +183,214 @@ export class TorProviderBuilder {
};
Services.obs.addObserver(this.#logObserver, TorProviderTopics.TorLog);
+ // Even though initialization of the initial provider is asynchronous, we do
+ // not expect the caller to await it. The reason is that any call to build()
+ // will wait the initialization anyway (and re-throw any initialization
+ // error).
+ this.#replaceProvider();
+ }
+
+ /**
+ * Replace the provider with a new instance.
+ */
+ static #replaceProvider() {
+ // NOTE: We need to ensure that the #providerData is set as soon as
+ // TorProviderBuilder.init is called.
+ // I.e. it should be safe to call
+ // TorProviderBuilder.init();
+ // TorProviderBuilder.build();
+ // TorProviderBuilder.settledState();
+ // // etc
+ // without any await.
+ //
+ // In particular, this is needed by `TorConnect.init`, which will call
+ // `settledState`. It will also call `build` immediately if quickstart is
+ // set. See tor-browser#41921.
+ if (this.#providerData) {
+ lazy.logger.info(
+ `Replacing the provider with a "${this.providerType}" provider.`
+ );
+ } else {
+ lazy.logger.info(`Creating the initial "${this.providerType}" provider.`);
+ }
+
+ let providerClass;
switch (this.providerType) {
case TorProviders.tor:
- // Even though initialization of the initial TorProvider is
- // asynchronous, we do not expect the caller to await it. The reason is
- // that any call to build() will wait the initialization anyway (and
- // re-throw any initialization error).
- this.#initTorProvider();
+ providerClass = lazy.TorProvider;
break;
case TorProviders.none:
- lazy.TorLauncherUtil.setProxyConfiguration(
- lazy.TorLauncherUtil.getPreferredSocksConfiguration()
- );
+ providerClass = lazy.TorProviderNone;
break;
default:
- console.error(`Unknown tor provider ${this.providerType}.`);
+ lazy.logger.error(`Unknown tor provider ${this.providerType}.`);
break;
}
+ // NOTE: It should be safe to create another provider instance whilst the
+ // existing one is still active. However, we will wait until the other is
+ // uninitialized before we initialize the new one.
+ const provider = new providerClass(() => {
+ this.#notifyStateChanged(provider);
+ });
+ const prevProviderData = this.#providerData;
+ // NOTE: We want `#providerData` to be set prior to our call to
+ // `provider.init`, so we create the `initPromise` prior to setting it.
+ const { promise: initPromise, resolve, reject } = Promise.withResolvers();
+ this.#providerData = { provider, initPromise };
+ // Let observers know we are restarting the provider.
+ this.#notifyStateChanged(provider);
+
+ // Run the rest of the init in an async operation that will cause
+ // `initPromise` to settle.
+ // NOTE: `#cleanupProviderData` should not throw, unlike `provider.init()`,
+ // which may throw.
+ // NOTE: We wait for `#cleanupProviderData` to complete before calling
+ // `provider.init()` in case the implementation relies on this.
+ this.#cleanupProviderData(prevProviderData).finally(() => {
+ provider.init().then(resolve, reject);
+ });
}
/**
- * Replace #provider with a new instance.
+ * Notify any listeners that the state of the current provider has changed.
*
- * @returns {Promise<TorProvider>} The new instance.
+ * @param {TorProviderBase} provider - The provider who's state has changed.
*/
- static #initTorProvider() {
- if (!this.#exitObserver) {
- this.#exitObserver = this.#torExited.bind(this);
- Services.obs.addObserver(
- this.#exitObserver,
- TorProviderTopics.ProcessExited
- );
+ static #notifyStateChanged(provider) {
+ if (this.#uninitialized) {
+ // Do not signal the final state changes when we uninitialize.
+ return;
+ }
+ if (provider !== this.#providerData.provider) {
+ // Delayed call from an old provider. Ignore.
+ return;
}
- // NOTE: We need to ensure that the #provider is set as soon
- // TorProviderBuilder.init is called.
- // I.e. it should be safe to call
- // TorProviderBuilder.init();
- // TorProviderBuilder.build();
- // without any await.
- //
- // Therefore, we await the oldProvider within the Promise rather than make
- // #initTorProvider async.
- //
- // In particular, this is needed by TorConnect when the user has selected
- // quickstart, in which case `TorConnect.init` will immediately request the
- // provider. See tor-browser#41921.
- this.#provider = this.#replaceTorProvider(this.#provider);
- return this.#provider;
+ Services.obs.notifyObservers(
+ null,
+ TorProviderTopics.ProviderStateChanged,
+ provider.state
+ );
+
+ this.#promptProviderState(false);
}
/**
- * Replace a TorProvider instance. Resolves once the TorProvider is
- * initialised.
+ * Check the given provider's state.
+ *
+ * If the provider is no longer the current one, it is considered to be
+ * "Stopped".
+ *
+ * If it is still the current provider, the state will be updated and
+ * returned.
*
- * @param {Promise<TorProvider>?} oldProvider - The previous's provider's
- * promise, if any.
- * @returns {TorProvider} The new TorProvider instance.
+ * @param {TorProviderBase} provider - The provider to check.
+ * @returns {string} - The `TorProviderState` state for the provider.
*/
- static async #replaceTorProvider(oldProvider) {
+ static #checkProviderState(provider) {
+ if (this.#providerData?.provider !== provider) {
+ // Replaced.
+ lazy.logger.debug("The checked provider has been replaced.");
+ return TorProviderState.Stopped;
+ }
+ return this.#providerData.provider.state;
+ }
+
+ /**
+ * Cleanup the given provider data.
+ *
+ * @param {?TorProviderData} providerData - The data to clean up.
+ */
+ static async #cleanupProviderData(providerData) {
+ if (!providerData) {
+ return;
+ }
try {
- // Uninitialise the old TorProvider, if there is any.
- (await oldProvider)?.uninit();
+ await providerData.initPromise;
} catch {}
- const provider = new lazy.TorProvider();
+
+ // Call `uninit` to clean up, even if `init` threw.
+ // Should be safe to call more than once (via `uninit`).
try {
- await provider.init();
+ await providerData.provider.uninit();
} catch (error) {
- // Wrap in an error type for callers to know whether the error comes from
- // initialisation or something else.
- throw new TorProviderInitError(error);
+ lazy.logger.error("Error in uninitializing provider", error);
}
- return provider;
}
static uninit() {
- this.#provider?.then(provider => {
- provider.uninit();
- this.#provider = null;
- });
- if (this.#exitObserver) {
- Services.obs.removeObserver(
- this.#exitObserver,
- TorProviderTopics.ProcessExited
- );
- this.#exitObserver = null;
- }
+ this.#uninitialized = true;
+
+ // NOTE: `uninit` should not be followed by any further calls to public
+ // methods. So we can clear the `#providerData` without keeping it for any
+ // future provider instances to wait on.
+ const providerData = this.#providerData;
+ this.#providerData = null;
+ this.#cleanupProviderData(providerData);
+
Services.obs.removeObserver(this.#logObserver, TorProviderTopics.TorLog);
}
/**
- * Build a provider.
- * This method will wait for the system to be initialized, and allows you to
- * catch also any initialization errors.
+ * Request the current instance of the Tor provider.
+ *
+ * This method will wait for the system to be initialized before returning the
+ * provider.
+ *
+ * This will throw any initialization errors of the provider, if it had any.
+ * This will also throw if the provider is no longer active.
*
* @returns {TorProvider} A TorProvider instance
*/
static async build() {
- if (!this.#provider && this.providerType === TorProviders.none) {
+ this.#checkActive();
+ if (this.#providerData.provider instanceof lazy.TorProviderNone) {
throw new Error(
"Tor Browser has been configured to use only the proxy functionalities."
);
- } else if (!this.#provider) {
- throw new Error(
- "The provider has not been initialized or already uninitialized."
+ }
+
+ const { provider, initPromise } = this.#providerData;
+ // initPromise may throw.
+ await initPromise;
+ if (this.#checkProviderState(provider) !== TorProviderState.Running) {
+ lazy.logger.warn("Request was made for a provider that has stopped.");
+ // TODO: Wait for the new instance instead?
+ throw new TorProviderInitError(
+ new Error("Provider is no longer active.")
);
}
- return this.#provider;
+ return provider;
+ }
+
+ /**
+ * Get the state of the current provider instance. Waits until the provider
+ * has finished initialisation first.
+ *
+ * If the provider has been replaced, the Stopped state will be returned.
+ *
+ * @returns {string} - The `TorProviderState` state for the provider that
+ * existed when this method was called.
+ */
+ static async settledState() {
+ this.#checkActive();
+ const { provider, initPromise } = this.#providerData;
+ try {
+ await initPromise;
+ } catch {}
+ return this.#checkProviderState(provider);
}
+ /**
+ * Replace the current provider instance with a new provider.
+ */
+ static replace() {
+ this.#checkActive();
+ this.#replaceProvider();
+ }
+
+ // TODO: Remove firstWindowLoaded, #uiReady, #prompting, #promptProviderState
+ // and use TorConnect instead. tor-browser#43570.
/**
* Check if the provider has been succesfully initialized when the first
* browser window is shown.
@@ -266,50 +399,72 @@ export class TorProviderBuilder {
* but we should modify TorConnect and about:torconnect to handle this case
* there with a better UX.
*/
- static async firstWindowLoaded() {
- // FIXME: Just integrate this with the about:torconnect or about:tor UI.
- if (
- !lazy.TorLauncherUtil.shouldStartAndOwnTor ||
- this.providerType !== TorProviders.tor
- ) {
- // If we are not managing the Tor daemon we cannot restart it, so just
- // early return.
- return;
- }
- let running = false;
- try {
- const provider = await this.#provider;
- // The initialization might have succeeded, but so far we have ignored any
- // error notification. So, check that the process has not exited after the
- // provider has been initialized successfully, but the UI was not ready
- // yet.
- running = provider.isRunning;
- } catch {
- // Not even initialized, running is already false.
- }
- while (!running && lazy.TorLauncherUtil.showRestartPrompt(true)) {
- try {
- await this.#initTorProvider();
- running = true;
- } catch {}
- }
- // The user might have canceled the restart, but at this point the UI is
- // ready in any case.
- this.#uiReady = true;
+ static firstWindowLoaded() {
+ this.#promptProviderState(true);
}
- static async #torExited() {
+ /**
+ * Tell whether the browser UI is ready.
+ * We ignore any errors until it is because we cannot show them.
+ *
+ * @type {boolean}
+ */
+ static #uiReady = false;
+
+ /**
+ * Whether we are prompting the user for a restart of the provider.
+ *
+ * @type {boolean}
+ */
+ static #prompting = false;
+
+ /**
+ * Prompt the user to restart the provider, if this is necessary.
+ *
+ * @param {boolean} uiReady - Whether this is being called for the first time
+ * when the UI is ready.
+ */
+ static async #promptProviderState(uiReady) {
+ if (uiReady) {
+ this.#uiReady = true;
+ }
+ if (this.#providerData.provider.state === TorProviderState.Running) {
+ // Nothing to wait for.
+ return;
+ }
if (!this.#uiReady) {
- console.warn(
- `Seen ${TorProviderTopics.ProcessExited}, but not doing anything because the UI is not ready yet.`
+ lazy.logger.warn(
+ "Seen exit, but not doing anything because the UI is not ready yet."
);
return;
}
- while (lazy.TorLauncherUtil.showRestartPrompt(false)) {
- try {
- await this.#initTorProvider();
- break;
- } catch {}
+ if (this.#prompting) {
+ // Already prompting, so don't duplicate.
+ return;
+ }
+
+ this.#prompting = true;
+ let waitForInit = uiReady;
+ let retry = true;
+ try {
+ while (retry) {
+ if (waitForInit) {
+ try {
+ await this.#providerData.initPromise;
+ } catch {}
+ }
+ if (
+ this.#providerData.provider.state === TorProviderState.Stopped &&
+ lazy.TorLauncherUtil.showRestartPrompt(uiReady)
+ ) {
+ waitForInit = true;
+ this.replace();
+ } else {
+ retry = false;
+ }
+ }
+ } finally {
+ this.#prompting = false;
}
}
@@ -320,7 +475,7 @@ export class TorProviderBuilder {
* Otherwise, if it is not valid, the C tor implementation is chosen as the
* default one.
*
- * @returns {number} An entry from TorProviders
+ * @returns {string} An entry from TorProviders
*/
static get providerType() {
// TODO: Add a preference to permanently save this without and avoid always
=====================================
toolkit/components/tor-launcher/TorProviderNone.sys.mjs
=====================================
@@ -0,0 +1,19 @@
+import { TorProviderBase } from "resource://gre/modules/TorProviderBase.sys.mjs";
+
+const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+ TorLauncherUtil: "resource://gre/modules/TorLauncherUtil.sys.mjs",
+});
+
+/**
+ * A provider that only sets the proxy settings.
+ */
+export class TorProviderNone extends TorProviderBase {
+ async _initInternal() {
+ lazy.TorLauncherUtil.setProxyConfiguration(
+ lazy.TorLauncherUtil.getPreferredSocksConfiguration()
+ );
+ }
+
+ async _uninitInternal() {}
+}
=====================================
toolkit/components/tor-launcher/moz.build
=====================================
@@ -7,7 +7,9 @@ EXTRA_JS_MODULES += [
"TorProcess.sys.mjs",
"TorProcessAndroid.sys.mjs",
"TorProvider.sys.mjs",
+ "TorProviderBase.sys.mjs",
"TorProviderBuilder.sys.mjs",
+ "TorProviderNone.sys.mjs",
"TorStartupService.sys.mjs",
]
=====================================
toolkit/modules/TorConnect.sys.mjs
=====================================
@@ -9,6 +9,7 @@ const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
MoatRPC: "resource://gre/modules/Moat.sys.mjs",
TorBootstrapRequest: "resource://gre/modules/TorBootstrapRequest.sys.mjs",
+ TorProviderState: "resource://gre/modules/TorProviderBuilder.sys.mjs",
TorProviderTopics: "resource://gre/modules/TorProviderBuilder.sys.mjs",
TorBootstrapError: "resource://gre/modules/TorProviderBuilder.sys.mjs",
TorProviderInitError: "resource://gre/modules/TorProviderBuilder.sys.mjs",
@@ -913,7 +914,7 @@ export const TorConnect = {
};
// register the Tor topics we always care about
- observeTopic(lazy.TorProviderTopics.ProcessExited);
+ observeTopic(lazy.TorProviderTopics.ProviderStateChanged);
observeTopic(lazy.TorProviderTopics.HasWarnOrErr);
observeTopic(lazy.TorSettingsTopics.SettingsChanged);
observeTopic(NETWORK_LINK_TOPIC);
@@ -933,7 +934,7 @@ export const TorConnect = {
}
},
- async observe(subject, topic) {
+ async observe(subject, topic, data) {
lazy.logger.debug(`Observed ${topic}`);
switch (topic) {
@@ -947,7 +948,10 @@ export const TorConnect = {
this._notifyBootstrapProgress();
}
break;
- case lazy.TorProviderTopics.ProcessExited:
+ case lazy.TorProviderTopics.ProviderStateChanged:
+ if (data !== lazy.TorProviderState.Stopped) {
+ break;
+ }
lazy.logger.info("Starting again since the tor process exited");
// Treat a failure as a possibly broken configuration.
// So, prevent quickstart at the next start.
@@ -1341,7 +1345,7 @@ export const TorConnect = {
this._tryAgain = true;
if (error instanceof lazy.TorProviderInitError) {
- // Treat like TorProviderTopics.ProcessExited. We expect a user
+ // Treat like TorProviderTopics.ProviderStateChanged. We expect a user
// notification when this happens.
// Treat a failure as a possibly broken configuration.
// So, prevent quickstart at the next start.
=====================================
toolkit/modules/TorSettings.sys.mjs
=====================================
@@ -9,6 +9,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
Lox: "resource://gre/modules/Lox.sys.mjs",
LoxTopics: "resource://gre/modules/Lox.sys.mjs",
TorParsers: "resource://gre/modules/TorParsers.sys.mjs",
+ TorProviderState: "resource://gre/modules/TorProviderBuilder.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "logger", () => {
@@ -940,7 +941,10 @@ class TorSettingsImpl {
// Test whether the provider is no longer running or has been replaced.
const providerRunning = () => {
- return providerRef === this.#providerRef && provider.isRunning;
+ return (
+ providerRef === this.#providerRef &&
+ provider.state !== lazy.TorProviderState.Stopped
+ );
};
lazy.logger.debug("Passing on settings to the provider", apply, details);
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/6e2555…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/6e2555…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/mullvad-browser][mullvad-browser-150.0a1-16.0-2] BB 44832: Modify error pages for Base Browser.
by henry (@henry) 28 Apr '26
by henry (@henry) 28 Apr '26
28 Apr '26
henry pushed to branch mullvad-browser-150.0a1-16.0-2 at The Tor Project / Applications / Mullvad Browser
Commits:
3d7561e8 by Henry Wilkes at 2026-04-28T12:39:29+01:00
BB 44832: Modify error pages for Base Browser.
- - - - -
1 changed file:
- toolkit/content/net-error-card.mjs
Changes:
=====================================
toolkit/content/net-error-card.mjs
=====================================
@@ -998,6 +998,8 @@ export class NetErrorCard extends MozLitElement {
}
const { bodyTitleL10nId, image } = this.errorConfig;
+ // Drop the Firefox mascot images. tor-browser#44832.
+ // eslint-disable-next-line no-unused-vars
const { src, alt } = image ?? NET_ERROR_ILLUSTRATIONS.securityError;
const title = bodyTitleL10nId ?? "fp-certerror-body-title";
@@ -1006,9 +1008,6 @@ export class NetErrorCard extends MozLitElement {
href="chrome://global/skin/aboutNetError.css"
/>
<article class="felt-privacy-container">
- <div class="img-container">
- <img src=${src} data-l10n-id=${alt} data-l10n-attrs="alt" />
- </div>
<div class="container">
${this.showCustomNetErrorCard
? html`${this.customNetErrorContainerTemplate()}`
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/3d7…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/3d7…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/tor-browser][tor-browser-150.0a1-16.0-2] BB 44832: Modify error pages for Base Browser.
by henry (@henry) 28 Apr '26
by henry (@henry) 28 Apr '26
28 Apr '26
henry pushed to branch tor-browser-150.0a1-16.0-2 at The Tor Project / Applications / Tor Browser
Commits:
6e25550a by Henry Wilkes at 2026-04-28T11:31:36+01:00
BB 44832: Modify error pages for Base Browser.
- - - - -
1 changed file:
- toolkit/content/net-error-card.mjs
Changes:
=====================================
toolkit/content/net-error-card.mjs
=====================================
@@ -998,6 +998,8 @@ export class NetErrorCard extends MozLitElement {
}
const { bodyTitleL10nId, image } = this.errorConfig;
+ // Drop the Firefox mascot images. tor-browser#44832.
+ // eslint-disable-next-line no-unused-vars
const { src, alt } = image ?? NET_ERROR_ILLUSTRATIONS.securityError;
const title = bodyTitleL10nId ?? "fp-certerror-body-title";
@@ -1006,9 +1008,6 @@ export class NetErrorCard extends MozLitElement {
href="chrome://global/skin/aboutNetError.css"
/>
<article class="felt-privacy-container">
- <div class="img-container">
- <img src=${src} data-l10n-id=${alt} data-l10n-attrs="alt" />
- </div>
<div class="container">
${this.showCustomNetErrorCard
? html`${this.customNetErrorContainerTemplate()}`
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/6e25550…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/6e25550…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/tor-browser-build][main] Bug 41780: Add sha256sums-unsigned-build.txt to artifacts directories
by boklm (@boklm) 28 Apr '26
by boklm (@boklm) 28 Apr '26
28 Apr '26
boklm pushed to branch main at The Tor Project / Applications / tor-browser-build
Commits:
74e56d51 by Nicolas Vigier at 2026-04-28T12:05:07+02:00
Bug 41780: Add sha256sums-unsigned-build.txt to artifacts directories
- - - - -
1 changed file:
- projects/release/build
Changes:
=====================================
projects/release/build
=====================================
@@ -71,15 +71,22 @@ RewriteRule ^tor-browser-linux64-(.*)_ALL.tar.xz$ tor-browser-linux-x86_64-$1.ta
[% END -%]
EOF
-# empty any existing sh256sums file
-echo -n > sha256sums-unsigned-build.txt
-# concat sha256sum entry for each file in set
-for i in $(ls -1 *.exe *.tar.xz *.dmg *.mar *.zip *.tar.bz2 *.tar.gz *.apk *.bspatch *.json *.deb *.rpm | grep -v '\.incremental\.mar$' | sort)
+for dir in . [% IF c("var/dev_artifacts") %]artifacts/*[% END %]
do
- sha256sum $i >> sha256sums-unsigned-build.txt
+ [ -d "$dir" ] || continue
+ pushd "$dir" > /dev/null
+ # empty any existing sh256sums file
+ echo -n > sha256sums-unsigned-build.txt
+ # concat sha256sum entry for each file in set
+ for i in $(ls -1 *.exe *.tar.xz *.tar.zst *.dmg *.mar *.zip *.tar.bz2 *.tar.gz *.apk *.bspatch *.json *.deb *.rpm *.xpi | grep -v '\.incremental\.mar$' | sort)
+ do
+ sha256sum $i >> sha256sums-unsigned-build.txt
+ done
+
+ [% IF c("var/sign_build") -%]
+ gpg -abs [% c("var/sign_build_gpg_opts") %] sha256sums-unsigned-build.txt
+ [% END -%]
+ popd > /dev/null
done
-[% IF c("var/sign_build") -%]
- gpg -abs [% c("var/sign_build_gpg_opts") %] sha256sums-unsigned-build.txt
-[% END -%]
cat sha256sums-unsigned-build.txt
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. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/tor-browser-update-responses][main] 6 commits: release: new version, 15.0.11 (linux-i686)
by ma1 (@ma1) 28 Apr '26
by ma1 (@ma1) 28 Apr '26
28 Apr '26
ma1 pushed to branch main at The Tor Project / Applications / Tor Browser update responses
Commits:
625de1a8 by hackademix at 2026-04-28T11:23:26+02:00
release: new version, 15.0.11 (linux-i686)
- - - - -
64bbc773 by hackademix at 2026-04-28T11:23:27+02:00
release: new version, 15.0.11 (linux-x86_64)
- - - - -
f1192181 by hackademix at 2026-04-28T11:23:29+02:00
release: new version, 15.0.11 (macos)
- - - - -
6de8efd2 by hackademix at 2026-04-28T11:23:31+02:00
release: new version, 15.0.11 (windows-i686)
- - - - -
125ac14f by hackademix at 2026-04-28T11:23:33+02:00
release: new version, 15.0.11 (windows-x86_64)
- - - - -
048b5a36 by hackademix at 2026-04-28T11:23:36+02:00
release: new version, 15.0.11
- - - - -
36 changed files:
- update_3/release/download-android-aarch64.json
- update_3/release/download-android-armv7.json
- update_3/release/download-android-x86.json
- update_3/release/download-android-x86_64.json
- update_3/release/download-linux-i686.json
- update_3/release/download-linux-x86_64.json
- update_3/release/download-macos.json
- update_3/release/download-windows-i686.json
- update_3/release/download-windows-x86_64.json
- update_3/release/downloads.json
- update_3/release/linux-i686/.htaccess
- update_3/release/linux-i686/update-15.0.7-15.0.10-linux-i686.xml → update_3/release/linux-i686/update-15.0.10-15.0.11-linux-i686.xml
- update_3/release/linux-i686/update-15.0.10-linux-i686.xml → update_3/release/linux-i686/update-15.0.11-linux-i686.xml
- update_3/release/linux-i686/update-15.0.8-15.0.10-linux-i686.xml → update_3/release/linux-i686/update-15.0.8-15.0.11-linux-i686.xml
- update_3/release/linux-i686/update-15.0.9-15.0.10-linux-i686.xml → update_3/release/linux-i686/update-15.0.9-15.0.11-linux-i686.xml
- update_3/release/linux-x86_64/.htaccess
- update_3/release/linux-x86_64/update-15.0.7-15.0.10-linux-x86_64.xml → update_3/release/linux-x86_64/update-15.0.10-15.0.11-linux-x86_64.xml
- update_3/release/linux-x86_64/update-15.0.10-linux-x86_64.xml → update_3/release/linux-x86_64/update-15.0.11-linux-x86_64.xml
- + update_3/release/linux-x86_64/update-15.0.8-15.0.11-linux-x86_64.xml
- − update_3/release/linux-x86_64/update-15.0.9-15.0.10-linux-x86_64.xml
- update_3/release/linux-x86_64/update-15.0.8-15.0.10-linux-x86_64.xml → update_3/release/linux-x86_64/update-15.0.9-15.0.11-linux-x86_64.xml
- update_3/release/macos/.htaccess
- update_3/release/macos/update-15.0.7-15.0.10-macos.xml → update_3/release/macos/update-15.0.10-15.0.11-macos.xml
- update_3/release/macos/update-15.0.10-macos.xml → update_3/release/macos/update-15.0.11-macos.xml
- update_3/release/macos/update-15.0.8-15.0.10-macos.xml → update_3/release/macos/update-15.0.8-15.0.11-macos.xml
- update_3/release/macos/update-15.0.9-15.0.10-macos.xml → update_3/release/macos/update-15.0.9-15.0.11-macos.xml
- update_3/release/windows-i686/.htaccess
- update_3/release/windows-i686/update-15.0.7-15.0.10-windows-i686.xml → update_3/release/windows-i686/update-15.0.10-15.0.11-windows-i686.xml
- update_3/release/windows-i686/update-15.0.10-windows-i686.xml → update_3/release/windows-i686/update-15.0.11-windows-i686.xml
- update_3/release/windows-i686/update-15.0.8-15.0.10-windows-i686.xml → update_3/release/windows-i686/update-15.0.8-15.0.11-windows-i686.xml
- update_3/release/windows-i686/update-15.0.9-15.0.10-windows-i686.xml → update_3/release/windows-i686/update-15.0.9-15.0.11-windows-i686.xml
- update_3/release/windows-x86_64/.htaccess
- update_3/release/windows-x86_64/update-15.0.7-15.0.10-windows-x86_64.xml → update_3/release/windows-x86_64/update-15.0.10-15.0.11-windows-x86_64.xml
- update_3/release/windows-x86_64/update-15.0.10-windows-x86_64.xml → update_3/release/windows-x86_64/update-15.0.11-windows-x86_64.xml
- update_3/release/windows-x86_64/update-15.0.9-15.0.10-windows-x86_64.xml → update_3/release/windows-x86_64/update-15.0.8-15.0.11-windows-x86_64.xml
- update_3/release/windows-x86_64/update-15.0.8-15.0.10-windows-x86_64.xml → update_3/release/windows-x86_64/update-15.0.9-15.0.11-windows-x86_64.xml
The diff was not included because it is too large.
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-update-responses…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser-update-responses…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/mullvad-browser][mullvad-browser-150.0a1-16.0-2] fixup! BB 32308: Use direct browser sizing for letterboxing.
by henry (@henry) 28 Apr '26
by henry (@henry) 28 Apr '26
28 Apr '26
henry pushed to branch mullvad-browser-150.0a1-16.0-2 at The Tor Project / Applications / Mullvad Browser
Commits:
fbb6188c by Henry Wilkes at 2026-04-28T09:56:05+01:00
fixup! BB 32308: Use direct browser sizing for letterboxing.
BB 44685: Use border radius for letterboxing. This allows us to drop
browserDecorator.
BB 44847: Drop the custom sidebar border radius logic since upstream now
permanently uses a rounded corner for the browserContainer element when
the sidebar is shown. We move the letterboxing background styling to
browserContainer as well, so it does not leak out the corners.
- - - - -
3 changed files:
- browser/components/tabbrowser/content/tabbrowser.js
- toolkit/components/resistfingerprinting/RFPHelper.sys.mjs
- toolkit/components/resistfingerprinting/content/letterboxing.css
Changes:
=====================================
browser/components/tabbrowser/content/tabbrowser.js
=====================================
@@ -2684,10 +2684,6 @@
stack.className = "browserStack";
stack.appendChild(b);
- let decorator = document.createXULElement("hbox");
- decorator.className = "browserDecorator";
- stack.appendChild(decorator);
-
let browserContainer = document.createXULElement("vbox");
browserContainer.className = "browserContainer";
browserContainer.appendChild(stack);
=====================================
toolkit/components/resistfingerprinting/RFPHelper.sys.mjs
=====================================
@@ -26,8 +26,6 @@ const kPrefLetterboxingRememberSize =
const kTopicDOMWindowOpened = "domwindowopened";
const kTopicDOMWindowClosed = "domwindowclosed";
-const kTopicFullscreenNavToolbox = "fullscreen-nav-toolbox";
-
const kPrefVerticalTabs = "sidebar.verticalTabs";
const kPrefResizeWarnings = "privacy.resistFingerprinting.resizeWarnings";
@@ -166,7 +164,6 @@ class _RFPHelper {
Services.prefs.addObserver(kPrefLetterboxing, this);
Services.prefs.addObserver(kPrefLetterboxingVcenter, this);
Services.prefs.addObserver(kPrefVerticalTabs, this);
- Services.obs.addObserver(this, kTopicFullscreenNavToolbox);
XPCOMUtils.defineLazyPreferenceGetter(
this,
@@ -202,7 +199,6 @@ class _RFPHelper {
Services.prefs.removeObserver(kPrefLetterboxingVcenter, this);
Services.prefs.removeObserver(kPrefLetterboxing, this);
Services.prefs.removeObserver(kPrefVerticalTabs, this);
- Services.obs.removeObserver(this, kTopicFullscreenNavToolbox);
// Remove the RFP observers, swallowing exceptions if they weren't present
this._removeLanguagePrefObservers();
}
@@ -224,15 +220,6 @@ class _RFPHelper {
case kTopicDOMWindowClosed:
this._handleDOMWindowClosed(subject);
break;
- case kTopicFullscreenNavToolbox:
- // The `subject` is the gNavToolbox.
- // Record whether the toobox has been hidden when the browser (not
- // content) is in fullscreen.
- subject.ownerGlobal.gBrowser.tabbox.classList.toggle(
- "letterboxing-nav-toolbox-hidden",
- data === "hidden"
- );
- break;
default:
break;
}
@@ -728,14 +715,6 @@ class _RFPHelper {
gapVertical >= this._letterboxingBorderRadius ||
gapHorizontal >= this._letterboxingBorderRadius
);
- // When the Letterboxing area is top-aligned, only show the sidebar corner
- // if there is enough horizontal space.
- // The factor of 4 is from the horizontal centre-alignment and wanting
- // enough space for twice the corner radius.
- browserParent.classList.toggle(
- "letterboxing-show-sidebar-corner",
- gapHorizontal >= 4 * this._letterboxingBorderRadius
- );
if (win.gBrowser.selectedBrowser == aBrowser) {
const updateStatus = async args => {
win.XULBrowserWindow.letterboxingStatus = args
@@ -797,10 +776,7 @@ class _RFPHelper {
_resetContentSize(aBrowser) {
aBrowser.parentElement.classList.add("exclude-letterboxing");
- aBrowser.parentElement.classList.remove(
- "letterboxing-show-outline",
- "letterboxing-show-sidebar-corner"
- );
+ aBrowser.parentElement.classList.remove("letterboxing-show-outline");
}
_updateSizeForTabsInWindow(aWindow) {
=====================================
toolkit/components/resistfingerprinting/content/letterboxing.css
=====================================
@@ -66,45 +66,12 @@
}
}
- background: var(--letterboxing-bgcolor);
-
- &:has(.deck-selected .browserContainer:not(.responsive-mode) > .browserStack:not(.exclude-letterboxing).letterboxing-show-outline) {
- /* Letterboxing outline is visible for the current tab. Replace the usual
- * outline to match the Letterboxing outline. For most scenarios, this
- * should be mostly the same colour as when Letterboxing is not visible. But
- * it may make a difference for some theme combinations. */
- outline-color: var(--letterboxing-outline-color);
- outline-width: var(--letterboxing-outline-width);
- }
-
/* Override the --tabpanel-background-color. */
/* TODO: FIX this for newtab pages. tor-browser#44085 */
--tabpanel-background-color: transparent;
- /* stylelint-disable-next-line media-query-no-invalid */
- @media -moz-pref("sidebar.revamp") {
- :root:not([inDOMFullscreen]) &[sidebar-shown]:not(.letterboxing-nav-toolbox-hidden):is(
- /* When the Letterboxing area is aligned to the top, show the rounded
- * corner if there is enough vertical space between the sidebar and the
- * browser element, which is not rounded at the top. */
- :not(.letterboxing-vcenter):has(.deck-selected .browserContainer:not(.responsive-mode) > .browserStack:not(.exclude-letterboxing).letterboxing-show-sidebar-corner),
- /* When the Letterboxing area is aligned to the centre, show the rounded
- * corner if the Letterboxing border is shown. */
- .letterboxing-vcenter:has(.deck-selected .browserContainer:not(.responsive-mode) > .browserStack:not(.exclude-letterboxing).letterboxing-show-outline)
- ) {
- /* stylelint-disable-next-line media-query-no-invalid */
- @media -moz-pref("sidebar.position_start") {
- border-start-start-radius: var(--letterboxing-border-radius);
- }
-
- /* stylelint-disable-next-line media-query-no-invalid */
- @media not -moz-pref("sidebar.position_start") {
- border-start-end-radius: var(--letterboxing-border-radius);
- }
- }
- }
-
.browserContainer {
+ background: var(--letterboxing-bgcolor);
/*
* From Firefox 115 on, .browserContainer layout is flex / column,
* and without this trick the .browserStack's resize observer
@@ -119,29 +86,22 @@
}
}
-.browserContainer:not(.responsive-mode) > .browserStack:not(.exclude-letterboxing) {
- :root:not([inDOMFullscreen]) .letterboxing.letterboxing-ready & {
+.letterboxing .deck-selected .browserContainer:not(.responsive-mode):has(> .browserStack:not(.exclude-letterboxing).letterboxing-show-outline) {
+ /* Letterboxing outline is visible for the current tab. Replace the usual
+ * outline to match the Letterboxing outline. For most scenarios, this
+ * should be mostly the same colour as when Letterboxing is not visible. But
+ * it may make a difference for some theme combinations. */
+ outline-color: var(--letterboxing-outline-color);
+ outline-width: var(--letterboxing-outline-width);
+}
+
+.letterboxing .browserContainer:not(.responsive-mode) > .browserStack:not(.exclude-letterboxing) {
+ :root:not([inDOMFullscreen]) .letterboxing-ready & {
place-content: var(--letterboxing-vertical-alignment) center;
}
- :root:not([inDOMFullscreen]) .letterboxing &.letterboxing-show-outline {
+ :root:not([inDOMFullscreen]) &.letterboxing-show-outline {
browser {
- /* We use clip-path rather than border-radius because border-radius on its
- * own leads to rendering artefacts in the corners (tested with GNOME).
- * See tor-browser#44214 (comment 3262962). */
- /* TODO: Use border-radius once bugzilla bug 1991874 is resolved. */
- clip-path: rect(
- auto auto auto auto round var(--letterboxing-border-radius-top) var(--letterboxing-border-radius-top) var(--letterboxing-border-radius)
- var(--letterboxing-border-radius)
- );
- }
-
- .browserDecorator {
- /* Need a separate browserDecorator element because the clip-path on the
- * browser would exclude the outline and box-shadow. */
- /* TODO: Move these rules to the browser element once bugzilla bug 1991874
- * is resolved, and drop browserDecorator. */
- display: block;
border-radius: var(--letterboxing-border-radius-top) var(--letterboxing-border-radius-top) var(--letterboxing-border-radius)
var(--letterboxing-border-radius);
/* NOTE: The top outline will not be visible when this is aligned to the
@@ -175,11 +135,3 @@
max-width: calc(var(--letterboxing-width) * 0.5);
}
}
-
-.browserDecorator {
- display: none;
- pointer-events: none;
- background: transparent;
- position: relative;
- z-index: 1;
-}
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/fbb…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/fbb…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/mullvad-browser][mullvad-browser-150.0a1-16.0-2] BB 44902: Temporary fix for privacy settings initialisation.
by henry (@henry) 28 Apr '26
by henry (@henry) 28 Apr '26
28 Apr '26
henry pushed to branch mullvad-browser-150.0a1-16.0-2 at The Tor Project / Applications / Mullvad Browser
Commits:
6c7c9f55 by Henry Wilkes at 2026-04-28T09:53:53+01:00
BB 44902: Temporary fix for privacy settings initialisation.
Can be dropped with Firefox version 151 due to bugzilla bug 1999769.
- - - - -
1 changed file:
- browser/components/preferences/privacy.js
Changes:
=====================================
browser/components/preferences/privacy.js
=====================================
@@ -5417,11 +5417,9 @@ var gPrivacyPane = {
gMainPane.manageProfiles
);
- setEventListener(
- "dataCollectionViewProfilesMultiProfileBackupWarning",
- "click",
- gMainPane.manageProfiles
- );
+ // tor-browser#44902.
+ // #dataCollectionViewProfilesMultiProfileBackupWarning is not in markup.
+ // This should be dropped with bugzilla bug 1999769 (Firefox version 151).
let listener = () => gPrivacyPane.updateProfilesPrivacyInfo();
SelectableProfileService.on("enableChanged", listener);
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/6c7…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/commit/6c7…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/mullvad-browser][mullvad-browser-150.0a1-16.0-2] 2 commits: fixup! Firefox preference overrides.
by Pier Angelo Vendrame (@pierov) 28 Apr '26
by Pier Angelo Vendrame (@pierov) 28 Apr '26
28 Apr '26
Pier Angelo Vendrame pushed to branch mullvad-browser-150.0a1-16.0-2 at The Tor Project / Applications / Mullvad Browser
Commits:
8ac56cce by Pier Angelo Vendrame at 2026-04-28T08:52:55+02:00
fixup! Firefox preference overrides.
BB 44521: Disable widget.wayland.fractional-scale.enabled.
It introduces fingerprinting opportunities to users who manually
enabled Wayland, so disable it until we decide to make Wayland the
default also for our browsers.
- - - - -
775f0ef3 by Pier Angelo Vendrame at 2026-04-28T08:53:06+02:00
fixup! BB 44772: Disable efficient randomization for canvases.
BB 44895: Revert "BB 44772: Disable efficient randomization for canvases."
This reverts commit ced1d9a35c7d8cbb727d8c1c450b2dce62cd7289.
We have investigated this issue more, and this change was not needed
after all, therefore we can just revert this patch.
- - - - -
2 changed files:
- browser/app/profile/001-base-profile.js
- dom/canvas/CanvasUtils.cpp
Changes:
=====================================
browser/app/profile/001-base-profile.js
=====================================
@@ -585,6 +585,9 @@ pref("widget.wayland.vsync.enabled", false);
// marked with a specific rating is loaded or not.
pref("security.restrict_to_adults.always", false);
pref("security.restrict_to_adults.respect_platform", false);
+// tor-browser#44521: Disable Wayland fractional scaling at least until we keep
+// Xwayland as the default.
+pref("widget.wayland.fractional-scale.enabled", false);
// tor-browser#41943: defense-in-depth, but do not lock anymore (enabled in Firefox 119, http://bugzil.la/1851162)
pref("javascript.options.spectre.disable_for_isolated_content", false);
=====================================
dom/canvas/CanvasUtils.cpp
=====================================
@@ -381,9 +381,14 @@ ImageExtraction ImageExtractionResult(dom::HTMLCanvasElement* aCanvasElement,
return ImageExtraction::Placeholder;
}
- if ((ownerDoc->ShouldResistFingerprinting(
- RFPTarget::EfficientCanvasRandomization) ||
- ownerDoc->ShouldResistFingerprinting(RFPTarget::CanvasRandomization) ||
+ if (ownerDoc->ShouldResistFingerprinting(
+ RFPTarget::EfficientCanvasRandomization) &&
+ GetCanvasExtractDataPermission(aPrincipal) !=
+ nsIPermissionManager::ALLOW_ACTION) {
+ return ImageExtraction::EfficientRandomize;
+ }
+
+ if ((ownerDoc->ShouldResistFingerprinting(RFPTarget::CanvasRandomization) ||
ownerDoc->ShouldResistFingerprinting(RFPTarget::WebGLRandomization)) &&
GetCanvasExtractDataPermission(aPrincipal) !=
nsIPermissionManager::ALLOW_ACTION) {
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/c3…
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/compare/c3…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0
[Git][tpo/applications/mullvad-browser] Pushed new tag mullvad-browser-150.0a1-16.0-2-build1
by Pier Angelo Vendrame (@pierov) 28 Apr '26
by Pier Angelo Vendrame (@pierov) 28 Apr '26
28 Apr '26
Pier Angelo Vendrame pushed new tag mullvad-browser-150.0a1-16.0-2-build1 at The Tor Project / Applications / Mullvad Browser
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/mullvad-browser/-/tree/mullv…
You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
1
0