tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 3ebcaf67c8a6d34c63054091c62a2cf6244cd349
parent a7b3b2903c2b6fb87d0869ef12851c39adca480e
Author: Ryan VanderMeulen <rvandermeulen@mozilla.com>
Date:   Fri, 10 Oct 2025 11:32:08 +0000

Bug 1993596 - Use LocalResources.current instead of context.getString. r=android-reviewers,mcarare

Explanation for issues of type "LocalContextGetResourceValueCall":
Changes to the Configuration object will not cause LocalContext.current reads to be
invalidated, so calls to APIs such as Context.getString() will not be updated when the
Configuration changes, and so stale values might be used. Instead, use LocalResources.current
and query properties from Resources directly - this will invalidate callers when the
Configuration changes, to ensure that these calls reflect the latest values.

Differential Revision: https://phabricator.services.mozilla.com/D268216

Diffstat:
Mmobile/android/android-components/samples/compose-browser/src/main/java/org/mozilla/samples/compose/browser/browser/BrowserScreen.kt | 35+++++++++++++++++++++--------------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/MenuDialogFragment.kt | 16+++++++---------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/MenuBanner.kt | 3+--
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/debugsettings/ui/FenixOverlay.kt | 15+++++----------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/perf/ProfilerStartDialogFragment.kt | 32++++++++++++--------------------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/perf/ProfilerStopDialogFragment.kt | 22+++++++++++-----------
Mmobile/android/focus-android/app/src/main/java/org/mozilla/focus/biometrics/BiometricAuthenticationFragmentCompose.kt | 17++++++-----------
Mmobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/BrowserToolbarIntegration.kt | 17++++++-----------
Mmobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingFirstScreenCompose.kt | 8++++----
Mmobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingSecondScreenCompose.kt | 6++----
Mmobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/PromoteSearchWidgetDialogCompose.kt | 17+++++------------
Mmobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsFragmentCompose.kt | 7++-----
12 files changed, 82 insertions(+), 113 deletions(-)

diff --git a/mobile/android/android-components/samples/compose-browser/src/main/java/org/mozilla/samples/compose/browser/browser/BrowserScreen.kt b/mobile/android/android-components/samples/compose-browser/src/main/java/org/mozilla/samples/compose/browser/browser/BrowserScreen.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource import androidx.navigation.NavController import mozilla.components.browser.state.helper.Target import mozilla.components.compose.base.theme.AcornTheme @@ -122,7 +123,7 @@ fun BrowserScreen(navController: NavController) { } /** - * Shows the lit of tabs. + * Shows the list of tabs. */ @Composable fun TabsTray( @@ -182,33 +183,41 @@ private fun Suggestions( ) { val context = LocalContext.current val components = components() + val keyboardController = LocalSoftwareKeyboardController.current + val switchToTabDescription = stringResource(awesomebarR.string.switch_to_tab_description) + val sponsoredSuggestionDescription = stringResource(fxsuggestR.string.sponsored_suggestion_description) - val sessionSuggestionProvider = remember(context) { + val sessionSuggestionProvider = remember( + components.store, + components.tabsUseCases.selectTab, + switchToTabDescription, + ) { SessionSuggestionProvider( components.store, components.tabsUseCases.selectTab, - switchToTabDescription = context.getString( - awesomebarR.string.switch_to_tab_description, - ), + switchToTabDescription = switchToTabDescription, ) } - val searchActionProvider = remember { + val searchActionProvider = remember(components.store, components.searchUseCases.defaultSearch) { SearchActionProvider(components.store, components.searchUseCases.defaultSearch) } - val fxSuggestSuggestionProvider = remember(context) { + val fxSuggestSuggestionProvider = remember(components.sessionUseCases.loadUrl, sponsoredSuggestionDescription) { FxSuggestSuggestionProvider( loadUrlUseCase = components.sessionUseCases.loadUrl, includeSponsoredSuggestions = false, includeNonSponsoredSuggestions = true, - sponsoredSuggestionDescription = context.getString( - fxsuggestR.string.sponsored_suggestion_description, - ), + sponsoredSuggestionDescription = sponsoredSuggestionDescription, ) } - val searchSuggestionProvider = remember(context) { + val searchSuggestionProvider = remember( + components.store, + components.searchUseCases.defaultSearch, + components.client, + components.engine, + ) { SearchSuggestionProvider( components.store, components.searchUseCases.defaultSearch, @@ -219,15 +228,13 @@ private fun Suggestions( ) } - val clipboardSuggestionProvider = remember(context) { + val clipboardSuggestionProvider = remember(context, components.sessionUseCases.loadUrl) { ClipboardSuggestionProvider( context, components.sessionUseCases.loadUrl, ) } - val keyboardController = LocalSoftwareKeyboardController.current - AwesomeBar( url, providers = listOf( diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/MenuDialogFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/MenuDialogFragment.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toDrawable import androidx.core.net.toUri @@ -329,13 +330,12 @@ class MenuDialogFragment : BottomSheetDialogFragment() { ) } + val descCustom = stringResource(R.string.browser_custom_tab_menu_handlebar_content_description) + val descMain = stringResource(R.string.browser_close_main_menu_handlebar_content_description) + var handlebarContentDescription by remember { mutableStateOf( - if (args.accesspoint == MenuAccessPoint.External) { - context.getString(R.string.browser_custom_tab_menu_handlebar_content_description) - } else { - context.getString(R.string.browser_close_main_menu_handlebar_content_description) - }, + if (args.accesspoint == MenuAccessPoint.External) descCustom else descMain, ) } @@ -519,8 +519,7 @@ class MenuDialogFragment : BottomSheetDialogFragment() { ) { route -> when (route) { Route.MainMenu -> { - handlebarContentDescription = - context.getString(R.string.browser_close_main_menu_handlebar_content_description) + handlebarContentDescription = descMain val account by syncStore.observeAsState(initialValue = null) { state -> state.account } val accountState by syncStore.observeAsState(initialValue = NotAuthenticated) { state -> @@ -760,8 +759,7 @@ class MenuDialogFragment : BottomSheetDialogFragment() { val isSiteLoading by browserStore.observeAsState(false) { state -> args.customTabSessionId?.let { state.findCustomTab(it)?.content?.loading } ?: false } - handlebarContentDescription = - context.getString(R.string.browser_custom_tab_menu_handlebar_content_description) + handlebarContentDescription = descCustom CustomTabMenu( canGoBack = customTab?.content?.canGoBack ?: true, diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/MenuBanner.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/compose/MenuBanner.kt @@ -24,7 +24,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -54,7 +53,7 @@ fun MenuBanner( onDismiss: () -> Unit, onClick: () -> Unit, ) { - val appName = LocalContext.current.getString(R.string.app_name) + val appName = stringResource(R.string.app_name) val shape = RoundedCornerShape(28.dp) val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/debugsettings/ui/FenixOverlay.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/debugsettings/ui/FenixOverlay.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.core.net.toUri import androidx.lifecycle.compose.LocalLifecycleOwner @@ -93,16 +94,10 @@ fun FenixOverlay( intent.data = debugViewLink.toUri() context.startActivity(intent) }, - showToast = { pingType -> - val toast = Toast.makeText( - context, - context.getString( - R.string.glean_debug_tools_send_ping_toast_message, - pingType, - ), - Toast.LENGTH_LONG, - ) - toast.show() + showToast = stringResource(R.string.glean_debug_tools_send_ping_toast_message).let { template -> + { pingType: String -> + Toast.makeText(context, template.format(pingType), Toast.LENGTH_LONG).show() + } }, ), ), diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/perf/ProfilerStartDialogFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/perf/ProfilerStartDialogFragment.kt @@ -61,28 +61,20 @@ class ProfilerStartDialogFragment : AppCompatDialogFragment() { val uiState by viewModel.uiState.collectAsState() val context = LocalContext.current - LaunchedEffect(uiState) { - when (val state = uiState) { - is ProfilerUiState.ShowToast -> { - Toast.makeText( - context, - context.getString(state.messageResId) + state.extra, - Toast.LENGTH_LONG, - ).show() - } - is ProfilerUiState.Running, is ProfilerUiState.Finished -> { - // No action needed - these states will auto-dismiss - } - is ProfilerUiState.Error -> { - Toast.makeText( - context, - context.getString(state.messageResId) + " " + state.errorDetails, - Toast.LENGTH_LONG, - ).show() - } - else -> {} + val toastMessage: String? = when (val state = uiState) { + is ProfilerUiState.ShowToast -> { + stringResource(state.messageResId) + state.extra } + is ProfilerUiState.Error -> { + stringResource(state.messageResId) + " " + state.errorDetails + } + else -> null + } + LaunchedEffect(uiState) { + toastMessage?.let { + Toast.makeText(context, it, Toast.LENGTH_LONG).show() + } if (uiState.shouldDismiss()) { dismissAllowingStateLoss() } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/perf/ProfilerStopDialogFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/perf/ProfilerStopDialogFragment.kt @@ -54,24 +54,24 @@ class ProfilerStopDialogFragment : DialogFragment() { val uiState by viewModel.uiState.collectAsState() val context = LocalContext.current + val toastMessage: String? = when (val state = uiState) { + is ProfilerUiState.ShowToast -> + stringResource(state.messageResId) + state.extra + is ProfilerUiState.Error -> + stringResource(state.messageResId) + state.errorDetails + else -> null + } + LaunchedEffect(uiState) { - when (val state = uiState) { + when (uiState) { is ProfilerUiState.ShowToast -> { - Toast.makeText( - context, - context.getString(state.messageResId) + state.extra, - Toast.LENGTH_LONG, - ).show() + Toast.makeText(context, toastMessage.orEmpty(), Toast.LENGTH_LONG).show() } is ProfilerUiState.Finished -> { dismissAllowingStateLoss() } is ProfilerUiState.Error -> { - Toast.makeText( - context, - context.getString(state.messageResId) + state.errorDetails, - Toast.LENGTH_LONG, - ).show() + Toast.makeText(context, toastMessage.orEmpty(), Toast.LENGTH_LONG).show() dismissAllowingStateLoss() } else -> {} diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/biometrics/BiometricAuthenticationFragmentCompose.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/biometrics/BiometricAuthenticationFragmentCompose.kt @@ -17,7 +17,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -55,9 +54,8 @@ fun BiometricPromptContent(biometricErrorText: String, showBiometricPrompt: () - ) { Image( painter = painterResource(R.drawable.wordmark2), - contentDescription = LocalContext.current.getString(R.string.app_name), - modifier = Modifier - .padding(start = 24.dp, end = 24.dp), + contentDescription = stringResource(R.string.app_name), + modifier = Modifier.padding(start = 24.dp, end = 24.dp), ) Text( style = focusTypography.onboardingButton, @@ -65,9 +63,7 @@ fun BiometricPromptContent(biometricErrorText: String, showBiometricPrompt: () - text = biometricErrorText, modifier = Modifier.padding(top = 16.dp, bottom = 16.dp), ) - ComponentShowBiometricPromptButton { - showBiometricPrompt() - } + ComponentShowBiometricPromptButton { showBiometricPrompt() } } } @@ -84,13 +80,12 @@ private fun ComponentShowBiometricPromptButton(showBiometricPrompt: () -> Unit) ) { Image( painter = painterResource(R.drawable.ic_fingerprint), - contentDescription = LocalContext.current.getString(R.string.biometric_auth_image_description), - modifier = Modifier - .padding(end = 10.dp), + contentDescription = stringResource(R.string.biometric_auth_image_description), + modifier = Modifier.padding(end = 10.dp), ) Text( color = PhotonColors.White, - text = AnnotatedString(stringResource(id = R.string.show_biometric_button_text)), + text = AnnotatedString(stringResource(R.string.show_biometric_button_text)), ) } } diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/BrowserToolbarIntegration.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/BrowserToolbarIntegration.kt @@ -11,7 +11,6 @@ import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.widget.AppCompatEditText import androidx.compose.material3.Text -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextDecoration @@ -349,9 +348,7 @@ class BrowserToolbarIntegration( internal fun observeCookieBannerCfr() { cookieBannerCfrScope = fragment.components?.appStore?.flowScoped( - coroutineScope = CoroutineScope( - coroutineDispatcher + SupervisorJob(), - ), + coroutineScope = CoroutineScope(coroutineDispatcher + SupervisorJob()), ) { flow -> flow.mapNotNull { state -> state.showCookieBannerCfr } .distinctUntilChanged() @@ -383,20 +380,18 @@ class BrowserToolbarIntegration( ), onDismiss = { onDismissCookieBannerCfr() }, text = { + val appName = stringResource(R.string.onboarding_short_app_name) + val linkText = stringResource(R.string.cfr_cookie_banner_link) val textCookieBannerCfr = stringResource( id = R.string.cfr_cookie_banner, - LocalContext.current.getString(R.string.onboarding_short_app_name), - LocalContext.current.getString(R.string.cfr_cookie_banner_link), + appName, + linkText, ) ClickableSubstringLink( text = textCookieBannerCfr, style = focusTypography.cfrCookieBannerTextStyle, linkTextDecoration = TextDecoration.Underline, - clickableStartIndex = textCookieBannerCfr.indexOf( - LocalContext.current.getString( - R.string.cfr_cookie_banner_link, - ), - ), + clickableStartIndex = textCookieBannerCfr.indexOf(linkText), clickableEndIndex = textCookieBannerCfr.length, onClick = { fragment.requireComponents.appStore.dispatch( diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingFirstScreenCompose.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingFirstScreenCompose.kt @@ -4,6 +4,7 @@ package org.mozilla.focus.fragment.onboarding +import android.content.res.Configuration import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -19,7 +20,7 @@ import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -34,7 +35,6 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import mozilla.components.support.utils.ext.isLandscape import mozilla.components.ui.colors.PhotonColors import org.mozilla.focus.R import org.mozilla.focus.ui.theme.FocusTheme @@ -77,11 +77,11 @@ fun OnBoardingFirstScreenCompose( Image( painter = painterResource(R.drawable.onboarding_logo), - contentDescription = LocalContext.current.getString(R.string.app_name), + contentDescription = stringResource(R.string.app_name), modifier = Modifier .size(150.dp, 150.dp) .then( - if (LocalContext.current.isLandscape()) { + if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE) { Modifier.weight(1f, false) } else { Modifier diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingSecondScreenCompose.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/onboarding/OnboardingSecondScreenCompose.kt @@ -18,7 +18,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -64,7 +63,7 @@ fun OnBoardingSecondScreenCompose( Image( painter = painterResource(R.drawable.onboarding_second_screen_icon), - contentDescription = LocalContext.current.getString(R.string.app_name), + contentDescription = stringResource(R.string.app_name), modifier = Modifier .size(200.dp, 300.dp) .weight(1f, false), @@ -75,8 +74,7 @@ fun OnBoardingSecondScreenCompose( R.string.onboarding_second_screen_title, stringResource(R.string.onboarding_short_app_name), ), - modifier = Modifier - .padding(top = 32.dp, start = 16.dp, end = 16.dp), + modifier = Modifier.padding(top = 32.dp, start = 16.dp, end = 16.dp), textAlign = TextAlign.Center, style = focusTypography.onboardingTitle, ) diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/PromoteSearchWidgetDialogCompose.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/searchwidget/PromoteSearchWidgetDialogCompose.kt @@ -27,7 +27,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -124,11 +123,8 @@ fun PromoteSearchWidgetDialogCompose( @Composable private fun DialogTitle() { Text( - text = stringResource( - id = R.string.promote_search_widget_dialog_title, - ), - modifier = Modifier - .padding(16.dp), + text = stringResource(id = R.string.promote_search_widget_dialog_title), + modifier = Modifier.padding(16.dp), color = focusColors.dialogTextColor, textAlign = TextAlign.Center, style = focusTypography.dialogTitle, @@ -140,10 +136,9 @@ private fun DialogSubtitle() { Text( text = stringResource( id = R.string.promote_search_widget_dialog_subtitle, - LocalContext.current.getString(R.string.onboarding_short_app_name), + stringResource(R.string.onboarding_short_app_name), ), - modifier = Modifier - .padding(top = 16.dp, start = 16.dp, end = 16.dp), + modifier = Modifier.padding(top = 16.dp, start = 16.dp, end = 16.dp), color = focusColors.dialogTextColor, textAlign = TextAlign.Center, style = focusTypography.dialogContent, @@ -154,9 +149,7 @@ private fun DialogSubtitle() { private fun DialogImage() { Image( painter = painterResource(R.drawable.focus_search_widget_promote_dialog), - contentDescription = LocalContext.current.getString( - R.string.promote_search_widget_dialog_picture_content_description, - ), + contentDescription = stringResource(R.string.promote_search_widget_dialog_picture_content_description), modifier = Modifier .fillMaxWidth() .padding(start = 10.dp, end = 10.dp) diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsFragmentCompose.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/settings/permissions/permissionoptions/SitePermissionOptionsFragmentCompose.kt @@ -28,7 +28,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role @@ -256,10 +255,8 @@ private fun ComponentPermissionBlockedByAndroidText( Text( textAlign = TextAlign.Start, color = focusColors.settingsTextColor, - text = LocalContext.current.getString(stringRes, permissionLabel).parseBold(), - style = TextStyle( - fontSize = 16.sp, - ), + text = stringResource(id = stringRes, permissionLabel ?: "").parseBold(), + style = TextStyle(fontSize = 16.sp), modifier = Modifier.padding(start = 55.dp, end = 16.dp, bottom = bottomPadding), ) }