tor-browser

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

commit 199af893683795b60093a321bcb91b92b59fd4c6
parent 7b0c7c8034392fdb8cd13f220973f47927c4bfc4
Author: Akhil Pindiprolu <apindiprolu@mozilla.com>
Date:   Wed, 17 Dec 2025 19:13:51 +0000

Bug 1994281 - [Tab Management Search] Add feature flags for releasing the Tab Search feature r=android-reviewers,007

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

Diffstat:
Mmobile/android/fenix/app/nimbus.fml.yaml | 19+++++++++++++++++++
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SecretSettingsFragment.kt | 6++++++
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayStore.kt | 9+++++++--
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/TabManagementFragment.kt | 1+
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/fab/TabManagerFloatingToolbar.kt | 80+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt | 8++++++++
Mmobile/android/fenix/app/src/main/res/values/preference_keys.xml | 3+++
Mmobile/android/fenix/app/src/main/res/values/static_strings.xml | 3+++
Mmobile/android/fenix/app/src/main/res/xml/secret_settings_preferences.xml | 4++++
Mmobile/android/fenix/app/src/test/java/org/mozilla/fenix/tabstray/TabsTrayStateTest.kt | 21+++++++++++++++++++--
10 files changed, 118 insertions(+), 36 deletions(-)

diff --git a/mobile/android/fenix/app/nimbus.fml.yaml b/mobile/android/fenix/app/nimbus.fml.yaml @@ -889,6 +889,25 @@ features: enabled: true opening_animation_enabled: false + tab-search: + description: Control the visibility of the tab search feature. + variables: + enabled: + description: > + Whether or not to enable the tab search feature. + type: Boolean + default: false + defaults: + - channel: release + value: + enabled: false + - channel: beta + value: + enabled: false + - channel: nightly + value: + enabled: false + suppress-sponsored-top-sites: description: Suppress sponsored top sites for new users for 14 days. variables: diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SecretSettingsFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/SecretSettingsFragment.kt @@ -473,6 +473,12 @@ class SecretSettingsFragment : PreferenceFragmentCompat() { isChecked = context.settings().enablePersistentOnboarding onPreferenceChangeListener = SharedPreferenceUpdater() } + + requirePreference<SwitchPreference>(R.string.pref_key_tab_search).apply { + isVisible = true + isChecked = context.settings().tabSearchEnabled + onPreferenceChangeListener = SharedPreferenceUpdater() + } } override fun onPreferenceTreeClick(preference: Preference): Boolean { diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayStore.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayStore.kt @@ -29,6 +29,7 @@ private const val DEFAULT_SYNCED_TABS_EXPANDED_STATE = true * @property syncedTabs The list of synced tabs. * @property syncing Whether the Synced Tabs feature should fetch the latest tabs from paired devices. * @property selectedTabId The ID of the currently selected (active) tab. + * @property tabSearchEnabled Whether the tab search feature is enabled. * @property backStack The navigation history of the Tab Manager feature. * @property expandedSyncedTabs The list of expansion states for the syncedTabs. */ @@ -42,6 +43,7 @@ data class TabsTrayState( val syncedTabs: List<SyncedTabsListItem> = emptyList(), val syncing: Boolean = false, val selectedTabId: String? = null, + val tabSearchEnabled: Boolean = false, val backStack: List<TabManagerNavDestination> = listOf(TabManagerNavDestination.Root), val expandedSyncedTabs: List<Boolean> = emptyList(), ) : State { @@ -72,7 +74,7 @@ data class TabsTrayState( * Whether the Tab Search button is visible. */ val searchIconVisible: Boolean - get() = selectedPage != Page.SyncedTabs + get() = tabSearchEnabled && selectedPage != Page.SyncedTabs /** * Whether the Tab Search button is enabled. @@ -330,8 +332,11 @@ internal object TabsTrayReducer { is TabsTrayAction.CloseAllPrivateTabs -> state is TabsTrayAction.BookmarkSelectedTabs -> state is TabsTrayAction.ThreeDotMenuShown -> state - is TabsTrayAction.TabSearchClicked -> + + is TabsTrayAction.TabSearchClicked -> { state.copy(backStack = state.backStack + TabManagerNavDestination.TabSearch) + } + is TabsTrayAction.NavigateBackInvoked -> { when { state.mode is TabsTrayState.Mode.Select -> state.copy(mode = TabsTrayState.Mode.Normal) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/TabManagementFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/TabManagementFragment.kt @@ -174,6 +174,7 @@ class TabManagementFragment : DialogFragment() { normalTabs = normalTabs, privateTabs = requireComponents.core.store.state.privateTabs, selectedTabId = requireComponents.core.store.state.selectedTabId, + tabSearchEnabled = requireComponents.settings.tabSearchEnabled, ), middlewares = listOf( TabsTrayTelemetryMiddleware(requireComponents.nimbus.events), diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/fab/TabManagerFloatingToolbar.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/fab/TabManagerFloatingToolbar.kt @@ -49,9 +49,7 @@ import mozilla.components.compose.base.text.Text import mozilla.components.compose.base.theme.surfaceDimVariant import mozilla.components.lib.state.ext.observeAsState import org.mozilla.fenix.R -import org.mozilla.fenix.tabstray.DefaultTabManagementFeatureHelper import org.mozilla.fenix.tabstray.Page -import org.mozilla.fenix.tabstray.TabManagementFeatureHelper import org.mozilla.fenix.tabstray.TabsTrayAction import org.mozilla.fenix.tabstray.TabsTrayState import org.mozilla.fenix.tabstray.TabsTrayState.Mode @@ -70,7 +68,6 @@ import mozilla.components.ui.icons.R as iconsR * @param expanded Controls the expansion state of this FAB. In an expanded state, the FAB will * show both the icon and text. In a collapsed state, the FAB will show only the icon. * @param pbmLocked Whether the private browsing mode is currently locked. - * @param featureHelper The feature flag helper for the Tab Manager feature. * @param onOpenNewNormalTabClicked Invoked when the fab is clicked in [Page.NormalTabs]. * @param onOpenNewPrivateTabClicked Invoked when the fab is clicked in [Page.PrivateTabs]. * @param onSyncedTabsFabClicked Invoked when the fab is clicked in [Page.SyncedTabs]. @@ -87,7 +84,6 @@ internal fun TabManagerFloatingToolbar( modifier: Modifier = Modifier, expanded: Boolean = true, pbmLocked: Boolean = false, - featureHelper: TabManagementFeatureHelper = DefaultTabManagementFeatureHelper, onOpenNewNormalTabClicked: () -> Unit, onOpenNewPrivateTabClicked: () -> Unit, onSyncedTabsFabClicked: () -> Unit, @@ -114,7 +110,6 @@ internal fun TabManagerFloatingToolbar( ) { FloatingToolbarActions( state = state, - featureHelper = featureHelper, onMenuShown = { tabsTrayStore.dispatch(TabsTrayAction.ThreeDotMenuShown) }, @@ -154,7 +149,6 @@ internal fun TabManagerFloatingToolbar( @Composable private fun FloatingToolbarActions( state: TabsTrayState, - featureHelper: TabManagementFeatureHelper, onMenuShown: () -> Unit, onEnterMultiselectModeClick: () -> Unit, onTabSettingsClick: () -> Unit, @@ -190,7 +184,7 @@ private fun FloatingToolbarActions( modifier = Modifier.padding(all = FirefoxTheme.layout.space.static100), horizontalArrangement = Arrangement.spacedBy(FirefoxTheme.layout.space.static50), ) { - if (featureHelper.tabSearchEnabled && state.searchIconVisible) { + if (state.searchIconVisible) { IconButton( onClick = onSearchClicked, modifier = Modifier.testTag(TabsTrayTestTag.TAB_SEARCH_ICON), @@ -432,77 +426,99 @@ private class TabManagerFloatingToolbarParameterProvider : PreviewParameterProvider<TabManagerFloatingToolbarPreviewModel> { override val values: Sequence<TabManagerFloatingToolbarPreviewModel> get() = sequenceOf( - // Normal tab page, disabled search icon, collapsed fab TabManagerFloatingToolbarPreviewModel( - state = TabsTrayState(selectedPage = Page.NormalTabs), + state = TabsTrayState( + selectedPage = Page.NormalTabs, + tabSearchEnabled = false, + normalTabs = listOf(createTab(url = "url")), + ), expanded = false, ), - // Normal tab page, disabled search icon, expanded fab TabManagerFloatingToolbarPreviewModel( - state = TabsTrayState(selectedPage = Page.NormalTabs), + state = TabsTrayState( + selectedPage = Page.NormalTabs, + tabSearchEnabled = false, + normalTabs = listOf(createTab(url = "url")), + ), expanded = true, ), - // Normal tab page, enabled search icon, collapsed fab TabManagerFloatingToolbarPreviewModel( state = TabsTrayState( selectedPage = Page.NormalTabs, + tabSearchEnabled = true, normalTabs = listOf(createTab(url = "url")), ), expanded = false, ), - // Normal tab page, enabled search icon, expanded fab TabManagerFloatingToolbarPreviewModel( state = TabsTrayState( selectedPage = Page.NormalTabs, + tabSearchEnabled = true, normalTabs = listOf(createTab(url = "url")), ), expanded = true, ), - // Private tab page, disabled search icon, collapsed fab - TabManagerFloatingToolbarPreviewModel( - state = TabsTrayState(selectedPage = Page.PrivateTabs), - expanded = false, - ), - // Private tab page, disabled search icon, expanded fab TabManagerFloatingToolbarPreviewModel( - state = TabsTrayState(selectedPage = Page.PrivateTabs), + state = TabsTrayState( + selectedPage = Page.NormalTabs, + tabSearchEnabled = true, + normalTabs = emptyList(), + ), expanded = true, ), - // Private tab page, enabled search icon, collapsed fab TabManagerFloatingToolbarPreviewModel( state = TabsTrayState( selectedPage = Page.PrivateTabs, - privateTabs = listOf(createTab(url = "url")), + tabSearchEnabled = true, + privateTabs = listOf(createTab(url = "url", private = true)), ), expanded = false, ), - // Private tab page, enabled search icon, expanded fab TabManagerFloatingToolbarPreviewModel( state = TabsTrayState( selectedPage = Page.PrivateTabs, - privateTabs = listOf(createTab(url = "url")), + tabSearchEnabled = true, + privateTabs = listOf(createTab(url = "url", private = true)), ), expanded = true, ), - // Synced tab page, signed-in, collapsed fab TabManagerFloatingToolbarPreviewModel( - state = TabsTrayState(selectedPage = Page.SyncedTabs), + state = TabsTrayState( + selectedPage = Page.PrivateTabs, + tabSearchEnabled = true, + privateTabs = emptyList(), + ), + expanded = true, + ), + TabManagerFloatingToolbarPreviewModel( + state = TabsTrayState( + selectedPage = Page.SyncedTabs, + tabSearchEnabled = true, + ), expanded = false, + isSignedIn = true, ), - // Synced tab page, signed-in, expanded fab TabManagerFloatingToolbarPreviewModel( - state = TabsTrayState(selectedPage = Page.SyncedTabs), + state = TabsTrayState( + selectedPage = Page.SyncedTabs, + tabSearchEnabled = true, + ), expanded = true, + isSignedIn = true, ), - // Synced tab page, signed-out, collapsed fab TabManagerFloatingToolbarPreviewModel( - state = TabsTrayState(selectedPage = Page.SyncedTabs), + state = TabsTrayState( + selectedPage = Page.SyncedTabs, + tabSearchEnabled = true, + ), expanded = false, isSignedIn = false, ), - // Synced tab page, signed-out, expanded fab TabManagerFloatingToolbarPreviewModel( - state = TabsTrayState(selectedPage = Page.SyncedTabs), + state = TabsTrayState( + selectedPage = Page.SyncedTabs, + tabSearchEnabled = true, + ), expanded = true, isSignedIn = false, ), diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -2828,6 +2828,14 @@ class Settings( ) /** + * Whether the Tab Search feature is enabled. + */ + var tabSearchEnabled by booleanPreference( + key = appContext.getPreferenceKey(R.string.pref_key_tab_search), + default = { DefaultTabManagementFeatureHelper.tabSearchEnabled }, + ) + + /** * Indicates whether the app should automatically clean up downloaded files. */ fun shouldCleanUpDownloadsAutomatically(): Boolean { diff --git a/mobile/android/fenix/app/src/main/res/values/preference_keys.xml b/mobile/android/fenix/app/src/main/res/values/preference_keys.xml @@ -520,4 +520,7 @@ <!-- Tab Manager--> <string name="pref_key_tab_manager_enhancements" translatable="false">pref_key_tab_manager_enhancements</string> <string name="pref_key_tab_manager_opening_animation" translatable="false">pref_key_tab_manager_opening_animation</string> + + <!-- Tab Search --> + <string name="pref_key_tab_search" translatable="false">pref_key_tab_search_feature</string> </resources> diff --git a/mobile/android/fenix/app/src/main/res/values/static_strings.xml b/mobile/android/fenix/app/src/main/res/values/static_strings.xml @@ -122,6 +122,9 @@ <!-- Label for enabling the persistent onboarding --> <string name="preferences_debug_settings_enable_persistent_onboarding" translatable="false">Show onboarding on each app cold open</string> + <!-- Label for toggling the Tab Search feature --> + <string name="preferences_tab_search_feature" translatable="false">Enable Tab Search</string> + <!-- Label for toggling the Tab Manager enhancements --> <string name="preferences_tab_manager_enhancements" translatable="false">Enable Tab Manager enhancements</string> <!-- Label for toggling the Tab Manager opening animation --> diff --git a/mobile/android/fenix/app/src/main/res/xml/secret_settings_preferences.xml b/mobile/android/fenix/app/src/main/res/xml/secret_settings_preferences.xml @@ -173,6 +173,10 @@ android:title="@string/preferences_tab_manager_opening_animation" app:iconSpaceReserved="false" /> <SwitchPreference + android:key="@string/pref_key_tab_search" + android:title="@string/preferences_tab_search_feature" + app:iconSpaceReserved="false" /> + <SwitchPreference android:key="@string/pref_key_terms_accepted" android:title="@string/preferences_terms_of_use_accepted" app:iconSpaceReserved="false" /> diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/tabstray/TabsTrayStateTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/tabstray/TabsTrayStateTest.kt @@ -221,13 +221,28 @@ class TabsTrayStateTest { @Test fun `WHEN the user is on the normal tabs page THEN the search icon is visible`() { - val testState = TabsTrayState(selectedPage = Page.NormalTabs) + val testState = TabsTrayState( + selectedPage = Page.NormalTabs, + tabSearchEnabled = true, + ) assertTrue(testState.searchIconVisible) } @Test + fun `GIVEN Tab Search is not enabled WHEN the user is on the normal tabs page THEN the search icon is not visible`() { + val testState = TabsTrayState( + selectedPage = Page.NormalTabs, + tabSearchEnabled = false, + ) + assertFalse(testState.searchIconVisible) + } + + @Test fun `WHEN the user is on the private tabs page THEN the search icon is visible`() { - val testState = TabsTrayState(selectedPage = Page.PrivateTabs) + val testState = TabsTrayState( + selectedPage = Page.PrivateTabs, + tabSearchEnabled = true, + ) assertTrue(testState.searchIconVisible) } @@ -255,6 +270,7 @@ class TabsTrayStateTest { val testState = TabsTrayState( selectedPage = Page.NormalTabs, normalTabs = listOf(createTab(url = "url")), + tabSearchEnabled = true, ) assertTrue(testState.searchIconEnabled) } @@ -273,6 +289,7 @@ class TabsTrayStateTest { val testState = TabsTrayState( selectedPage = Page.PrivateTabs, privateTabs = listOf(createTab(url = "url")), + tabSearchEnabled = true, ) assertTrue(testState.searchIconEnabled) }