lists.torproject.org
Sign In Sign Up
Manage this list Sign In Sign Up

Keyboard Shortcuts

Thread View

  • j: Next unread message
  • k: Previous unread message
  • j a: Jump to all threads
  • j l: Jump to MailingList overview

tbb-commits

Thread Start a new thread
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
tbb-commits@lists.torproject.org

  • 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

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. "&amp;" shows up as "&" and means "and" in english. --> + <string name="summer_2026_funding_tool_onionshare_description">secure filesharing, hosting &amp; 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
0 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

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
0 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

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
0 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

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
0 0
[Git][tpo/applications/tor-browser-build][main] Bug 41780: Add sha256sums-unsigned-build.txt to artifacts directories
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
0 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

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
0 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

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
0 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

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
0 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

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
0 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

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
0 0
  • ← Newer
  • 1
  • ...
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • ...
  • 2064
  • Older →

HyperKitty Powered by HyperKitty version 1.3.12.