tor-browser

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

commit c872c9e69a6702fec9b6f85e39f388e70fcd453f
parent b09c265119bbab348cf9a1662fe9c4d80cb20d93
Author: mcarare <48995920+mcarare@users.noreply.github.com>
Date:   Wed, 26 Nov 2025 10:41:05 +0000

Bug 1996806 - Centralize and improve "delete data on quit" logic r=android-reviewers,jonalmeida

This patch refactors the "delete browsing data on quit" feature to improve its structure, testability, and reliability.

The core changes include:
- Refactoring `deleteAndQuit` into a new `deleteDataAndQuit` extension of `DeleteBrowsingDataController`
- This new structure ensures that the app termination logic (e.g., `finishAndRemoveTask()`) is always called, even if data deletion fails.
- The `DeleteBrowsingDataController` is now instantiated and passed down from UI entry points (`HomeFragment`, `BaseBrowserFragment`), making the feature's dependencies explicit and improving testability.
- Added comprehensive unit tests for the `deleteDataAndQuit` logic covering various scenarios.

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

Diffstat:
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt | 30+++++++++++++++++++++++++++---
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/menu/MenuDialogFragment.kt | 45+++++++++++++++++++++++++++++++++++----------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt | 3++-
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt | 18++++++++++++++++++
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeMenuView.kt | 14+++++++++-----
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/toolbar/HomeToolbarView.kt | 3+++
Dmobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuit.kt | 59-----------------------------------------------------------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataController.kt | 180+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataFragment.kt | 21+++++++++++++++------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataOnQuitType.kt | 9+++++++++
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt | 28++++++++++++++++++++++++++++
Mmobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/HomeMenuViewTest.kt | 1+
Mmobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/toolbar/HomeToolbarViewTest.kt | 11++++++++++-
Mmobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/deletebrowsingdata/DefaultDeleteBrowsingDataControllerTest.kt | 171++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Dmobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuitTest.kt | 152-------------------------------------------------------------------------------
Mmobile/android/fenix/config/detekt-baseline.xml | 12------------
16 files changed, 465 insertions(+), 292 deletions(-)

diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -35,6 +35,7 @@ import androidx.core.text.HtmlCompat import androidx.core.view.OnApplyWindowInsetsListener import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController @@ -232,7 +233,7 @@ import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks import org.mozilla.fenix.search.awesomebar.AwesomeBarComposable import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.settings.biometric.BiometricPromptFeature -import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit +import org.mozilla.fenix.settings.deletebrowsingdata.DefaultDeleteBrowsingDataController import org.mozilla.fenix.snackbar.FenixSnackbarDelegate import org.mozilla.fenix.snackbar.SnackbarBinding import org.mozilla.fenix.tabstray.Page @@ -579,6 +580,25 @@ abstract class BaseBrowserFragment : _findInPageLauncher = { launchFindInPageFeature(view, store) } + + val deleteBrowsingDataController = DefaultDeleteBrowsingDataController( + deleteDataUseCases = DefaultDeleteBrowsingDataController.DeleteDataUseCases( + removeAllTabs = activity.components.useCases.tabsUseCases.removeAllTabs, + removeAllDownloads = activity.components.useCases.downloadUseCases.removeAllDownloads, + ), + dataStorage = DefaultDeleteBrowsingDataController.DataStorage( + history = activity.components.core.historyStorage, + permissions = activity.components.core.permissionStorage, + ), + stores = DefaultDeleteBrowsingDataController.Stores( + appStore = activity.components.appStore, + browserStore = activity.components.core.store, + ), + engine = activity.components.core.engine, + settings = activity.components.settings, + coroutineContext = activity.lifecycleScope.coroutineContext, + ) + _browserToolbarMenuController = DefaultBrowserToolbarMenuController( fragment = this, store = store, @@ -601,8 +621,12 @@ abstract class BaseBrowserFragment : tabCollectionStorage = requireComponents.core.tabCollectionStorage, topSitesStorage = requireComponents.core.topSitesStorage, pinnedSiteStorage = requireComponents.core.pinnedSiteStorage, - deleteAndQuit = { activity: HomeActivity -> - deleteAndQuit(activity, activity.lifecycleScope) + deleteAndQuit = { activity: FragmentActivity -> + lifecycleScope.launch { + deleteBrowsingDataController.clearBrowsingDataOnQuit { + activity.finishAndRemoveTask() + } + } }, ) 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 @@ -42,7 +42,6 @@ import androidx.core.graphics.drawable.toDrawable import androidx.core.net.toUri import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat.Type.systemBars -import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs @@ -50,6 +49,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json import mozilla.components.browser.state.selector.findCustomTab @@ -97,7 +97,11 @@ import org.mozilla.fenix.ext.runIfFragmentIsAttached import org.mozilla.fenix.ext.settings import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.settings.SupportUtils -import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit +import org.mozilla.fenix.settings.deletebrowsingdata.DefaultDeleteBrowsingDataController +import org.mozilla.fenix.settings.deletebrowsingdata.DefaultDeleteBrowsingDataController.DataStorage +import org.mozilla.fenix.settings.deletebrowsingdata.DefaultDeleteBrowsingDataController.DeleteDataUseCases +import org.mozilla.fenix.settings.deletebrowsingdata.DefaultDeleteBrowsingDataController.Stores +import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataController import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.utils.DELAY_MS_MAIN_MENU import org.mozilla.fenix.utils.DELAY_MS_SUB_MENU @@ -140,6 +144,28 @@ class MenuDialogFragment : BottomSheetDialogFragment() { private var bottomSheetBehavior: BottomSheetBehavior<View>? = null private var isPrivate: Boolean = false + private val deleteBrowsingDataController: DeleteBrowsingDataController by lazy { + DefaultDeleteBrowsingDataController( + deleteDataUseCases = DeleteDataUseCases( + removeAllTabs = + requireComponents.useCases.tabsUseCases.removeAllTabs, + removeAllDownloads = + requireComponents.useCases.downloadUseCases.removeAllDownloads, + ), + dataStorage = DataStorage( + history = requireComponents.core.historyStorage, + permissions = requireComponents.core.permissionStorage, + ), + stores = Stores( + appStore = requireComponents.appStore, + browserStore = requireComponents.core.store, + ), + engine = requireComponents.core.engine, + settings = requireComponents.settings, + coroutineContext = lifecycleScope.coroutineContext, + ) + } + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { Events.toolbarMenuVisible.record(NoExtras()) @@ -291,14 +317,13 @@ class MenuDialogFragment : BottomSheetDialogFragment() { materialAlertDialogBuilder = MaterialAlertDialogBuilder(context), topSitesMaxLimit = components.settings.topSitesMaxLimit, onDeleteAndQuit = { - deleteAndQuit( - activity = activity as HomeActivity, - // This menu's coroutineScope would cancel all in progress operations - // when the dialog is closed. - // Need to use a scope that will ensure the background operation - // will continue even if the dialog is closed. - coroutineScope = (activity as LifecycleOwner).lifecycleScope, - ) + activity?.let { activity -> + activity.lifecycleScope.launch { + deleteBrowsingDataController.clearBrowsingDataOnQuit { + activity.finishAndRemoveTask() + } + } + } }, onDismiss = { withContext(Dispatchers.Main) { diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarMenuController.kt @@ -7,6 +7,7 @@ package org.mozilla.fenix.components.toolbar import android.content.Intent import androidx.annotation.VisibleForTesting import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity import androidx.navigation.NavController import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.CoroutineScope @@ -92,7 +93,7 @@ class DefaultBrowserToolbarMenuController( private val tabCollectionStorage: TabCollectionStorage, private val topSitesStorage: DefaultTopSitesStorage, private val pinnedSiteStorage: PinnedSiteStorage, - private val deleteAndQuit: (HomeActivity) -> Unit, + private val deleteAndQuit: (FragmentActivity) -> Unit, ) : BrowserToolbarMenuController { private val currentSession diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -159,6 +159,7 @@ import org.mozilla.fenix.search.SearchDialogFragment import org.mozilla.fenix.search.awesomebar.AwesomeBarComposable import org.mozilla.fenix.search.toolbar.DefaultSearchSelectorController import org.mozilla.fenix.search.toolbar.SearchSelectorMenu +import org.mozilla.fenix.settings.deletebrowsingdata.DefaultDeleteBrowsingDataController import org.mozilla.fenix.snackbar.FenixSnackbarDelegate import org.mozilla.fenix.snackbar.SnackbarBinding import org.mozilla.fenix.tabstray.Page @@ -637,6 +638,23 @@ class HomeFragment : Fragment() { interactor = sessionControlInteractor, homeFragment = this, homeActivity = activity, + deleteBrowsingDataController = DefaultDeleteBrowsingDataController( + deleteDataUseCases = DefaultDeleteBrowsingDataController.DeleteDataUseCases( + removeAllTabs = activity.components.useCases.tabsUseCases.removeAllTabs, + removeAllDownloads = activity.components.useCases.downloadUseCases.removeAllDownloads, + ), + dataStorage = DefaultDeleteBrowsingDataController.DataStorage( + history = activity.components.core.historyStorage, + permissions = activity.components.core.permissionStorage, + ), + stores = DefaultDeleteBrowsingDataController.Stores( + appStore = activity.components.appStore, + browserStore = activity.components.core.store, + ), + engine = activity.components.core.engine, + settings = activity.components.settings, + coroutineContext = activity.lifecycleScope.coroutineContext, + ), ) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeMenuView.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/HomeMenuView.kt @@ -11,6 +11,7 @@ import androidx.core.content.ContextCompat import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController +import kotlinx.coroutines.launch import mozilla.appservices.places.BookmarkRoot import mozilla.components.browser.menu.view.MenuButton import mozilla.components.concept.sync.FxAEntryPoint @@ -26,7 +27,7 @@ import org.mozilla.fenix.components.usecases.FenixBrowserUseCases import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.settings import org.mozilla.fenix.settings.SupportUtils -import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit +import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataController import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.whatsnew.WhatsNew import java.lang.ref.WeakReference @@ -43,6 +44,7 @@ import org.mozilla.fenix.GleanMetrics.HomeMenu as HomeMenuMetrics * @param menuButton The [MenuButton] that will be used to create a menu when the button is * clicked. * @param fxaEntrypoint The source entry point to FxA. + * @param deleteBrowsingDataController [DeleteBrowsingDataController] used to delete browsing data. */ @Suppress("LongParameterList") class HomeMenuView( @@ -53,6 +55,7 @@ class HomeMenuView( private val fenixBrowserUseCases: FenixBrowserUseCases, private val menuButton: WeakReference<MenuButton>, private val fxaEntrypoint: FxAEntryPoint = FenixFxAEntryPoint.HomeMenu, + private val deleteBrowsingDataController: DeleteBrowsingDataController, ) { /** @@ -201,10 +204,11 @@ class HomeMenuView( ) } HomeMenu.Item.Quit -> { - deleteAndQuit( - activity = homeActivity, - coroutineScope = homeActivity.lifecycleScope, - ) + homeActivity.lifecycleScope.launch { + deleteBrowsingDataController.clearBrowsingDataOnQuit { + homeActivity.finishAndRemoveTask() + } + } } HomeMenu.Item.ReconnectSync -> { navController.nav( diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/toolbar/HomeToolbarView.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/home/toolbar/HomeToolbarView.kt @@ -32,6 +32,7 @@ import org.mozilla.fenix.ext.settings import org.mozilla.fenix.home.HomeFragment import org.mozilla.fenix.home.HomeMenuView import org.mozilla.fenix.search.toolbar.SearchSelector +import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataController import org.mozilla.fenix.utils.ToolbarPopupWindow import java.lang.ref.WeakReference @@ -43,6 +44,7 @@ internal class HomeToolbarView( private val interactor: ToolbarInteractor, private val homeFragment: HomeFragment, private val homeActivity: HomeActivity, + private val deleteBrowsingDataController: DeleteBrowsingDataController, ) : FenixHomeToolbar { private var context = homeFragment.requireContext() @@ -161,6 +163,7 @@ internal class HomeToolbarView( navController = homeFragment.findNavController(), fenixBrowserUseCases = context.components.useCases.fenixBrowserUseCases, menuButton = WeakReference(toolbarBinding.menuButton), + deleteBrowsingDataController = deleteBrowsingDataController, ).also { it.build() } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuit.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuit.kt @@ -1,59 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -package org.mozilla.fenix.settings.deletebrowsingdata - -import android.app.Activity -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.joinAll -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.mozilla.fenix.components.appstate.AppAction -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.settings - -/** - * Deletes selected browsing data and finishes the activity. - */ -fun deleteAndQuit(activity: Activity, coroutineScope: CoroutineScope) { - coroutineScope.launch { - val appStore = activity.components.appStore - appStore.dispatch(AppAction.DeleteAndQuitStarted) - - val settings = activity.settings() - val controller = DefaultDeleteBrowsingDataController( - activity.components.useCases.tabsUseCases.removeAllTabs, - activity.components.useCases.downloadUseCases.removeAllDownloads, - activity.components.core.historyStorage, - activity.components.core.permissionStorage, - activity.components.core.store, - activity.components.core.engine, - coroutineContext, - ) - - DeleteBrowsingDataOnQuitType.entries.map { type -> - launch { - if (settings.getDeleteDataOnQuit(type)) { - controller.deleteType(type) - } - } - }.joinAll() - - activity.finishAndRemoveTask() - } -} - -private suspend fun DeleteBrowsingDataController.deleteType(type: DeleteBrowsingDataOnQuitType) { - when (type) { - DeleteBrowsingDataOnQuitType.TABS -> deleteTabs() - DeleteBrowsingDataOnQuitType.HISTORY -> deleteBrowsingHistory() - DeleteBrowsingDataOnQuitType.COOKIES -> deleteCookiesAndSiteData() - DeleteBrowsingDataOnQuitType.CACHE -> deleteCachedFiles() - DeleteBrowsingDataOnQuitType.PERMISSIONS -> withContext(IO) { - deleteSitePermissions() - } - DeleteBrowsingDataOnQuitType.DOWNLOADS -> deleteDownloads() - } -} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataController.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataController.kt @@ -5,6 +5,9 @@ package org.mozilla.fenix.settings.deletebrowsingdata import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.withContext import mozilla.components.browser.state.action.EngineAction import mozilla.components.browser.state.action.RecentlyClosedAction @@ -16,40 +19,139 @@ import mozilla.components.concept.engine.translate.OperationLevel import mozilla.components.concept.storage.HistoryStorage import mozilla.components.feature.downloads.DownloadsUseCases import mozilla.components.feature.tabs.TabsUseCases +import org.mozilla.fenix.components.AppStore import org.mozilla.fenix.components.PermissionStorage +import org.mozilla.fenix.components.appstate.AppAction +import org.mozilla.fenix.utils.Settings import kotlin.coroutines.CoroutineContext +/** + * A controller responsible for handling the deletion of different categories of browsing data. + */ interface DeleteBrowsingDataController { + /** + * Deletes all open tabs. + */ suspend fun deleteTabs() + + /** + * Deletes browsing history, search history, and recently closed tabs. + */ suspend fun deleteBrowsingHistory() + + /** + * Deletes cookies, active logins, and site data (e.g., localStorage, sessionStorage). + */ suspend fun deleteCookiesAndSiteData() + + /** + * Deletes cached files, including image caches, startup caches, and cached + * translation models. + */ suspend fun deleteCachedFiles() + + /** + * Deletes all site permissions. + */ suspend fun deleteSitePermissions() + + /** + * Deletes all downloaded files from the downloads list. + * Note: This only removes the entries from the app's download list, + * it does not delete the actual files from the device's storage. + */ suspend fun deleteDownloads() + + /** + * Deletes a specific category of browsing data. + * + * @param type The type of data to delete, as defined by [DeleteBrowsingDataOnQuitType]. + */ + suspend fun deleteType(type: DeleteBrowsingDataOnQuitType) + + /** + * Deletes browsing data selected in the "Delete browsing data on quit" settings. + * + * This function is designed as a fire-and-forget operation from the caller's perspective. + * If no data types are selected for deletion, the callback is invoked immediately. + * + * @param onDeletionComplete A callback function to be executed after all selected + * data has been deleted. This is typically used to trigger the final application shutdown. + */ + suspend fun clearBrowsingDataOnQuit(onDeletionComplete: () -> Unit) } -@Suppress("LongParameterList") +/** + * The default implementation of [DeleteBrowsingDataController]. + * + * This class coordinates with various components like the browser engine, use cases, + * and storage layers to perform the deletion of specific browsing data categories. + * + * @param deleteDataUseCases A collection of use cases for deleting data like tabs and downloads. + * @param dataStorage A container for low-level data persistence layers like history and permissions. + * @param stores A container for the app and browser-level state stores. + * @param engine The browser engine instance responsible for handling low-level data. + * @param settings The application settings provider, used to determine which data to clear on quit. + * @param coroutineContext The coroutine context on which deletion operations are performed. + */ class DefaultDeleteBrowsingDataController( - private val removeAllTabs: TabsUseCases.RemoveAllTabsUseCase, - private val removeAllDownloads: DownloadsUseCases.RemoveAllDownloadsUseCase, - private val historyStorage: HistoryStorage, - private val permissionStorage: PermissionStorage, - private val store: BrowserStore, + private val deleteDataUseCases: DeleteDataUseCases, + private val dataStorage: DataStorage, + private val stores: Stores, private val engine: Engine, + private val settings: Settings, private val coroutineContext: CoroutineContext = Dispatchers.Main, ) : DeleteBrowsingDataController { + /** + * A collection of use cases for deleting various types of browsing data. + * + * @property removeAllTabs The use case for removing all tabs. + * @property removeAllDownloads The use case for removing all download entries. + */ + data class DeleteDataUseCases( + val removeAllTabs: TabsUseCases.RemoveAllTabsUseCase, + val removeAllDownloads: DownloadsUseCases.RemoveAllDownloadsUseCase, + ) + + /** + * A container for central state stores. + * + * This data class groups together app-level and browser-level state stores + * required by the controller. + * + * @property appStore The central state store for the application. + * @property browserStore The central state store for the browser. + */ + data class Stores( + val appStore: AppStore, + val browserStore: BrowserStore, + ) + + /** + * A container for low-level data persistence layers. + * + * This data class groups together storage-related dependencies required by the controller. + * + * @property history The storage handler for browsing history. + * @property permissions The storage handler for site permissions. + */ + data class DataStorage( + val history: HistoryStorage, + val permissions: PermissionStorage, + ) + override suspend fun deleteTabs() { withContext(coroutineContext) { - removeAllTabs.invoke(false) + deleteDataUseCases.removeAllTabs.invoke(false) } } override suspend fun deleteBrowsingHistory() { withContext(coroutineContext) { - historyStorage.deleteEverything() - store.dispatch(EngineAction.PurgeHistoryAction) - store.dispatch(RecentlyClosedAction.RemoveAllClosedTabAction) + dataStorage.history.deleteEverything() + stores.browserStore.dispatch(EngineAction.PurgeHistoryAction) + stores.browserStore.dispatch(RecentlyClosedAction.RemoveAllClosedTabAction) } } @@ -87,12 +189,66 @@ class DefaultDeleteBrowsingDataController( Engine.BrowsingData.select(Engine.BrowsingData.ALL_SITE_SETTINGS), ) } - permissionStorage.deleteAllSitePermissions() + dataStorage.permissions.deleteAllSitePermissions() } override suspend fun deleteDownloads() { withContext(coroutineContext) { - removeAllDownloads.invoke() + deleteDataUseCases.removeAllDownloads.invoke() + } + } + + override suspend fun deleteType(type: DeleteBrowsingDataOnQuitType) { + when (type) { + DeleteBrowsingDataOnQuitType.TABS -> deleteTabs() + DeleteBrowsingDataOnQuitType.HISTORY -> deleteBrowsingHistory() + DeleteBrowsingDataOnQuitType.COOKIES -> deleteCookiesAndSiteData() + DeleteBrowsingDataOnQuitType.CACHE -> deleteCachedFiles() + DeleteBrowsingDataOnQuitType.PERMISSIONS -> withContext(IO) { + deleteSitePermissions() + } + + DeleteBrowsingDataOnQuitType.DOWNLOADS -> deleteDownloads() + } + } + + override suspend fun clearBrowsingDataOnQuit( + onDeletionComplete: () -> Unit, + ) { + val typesToDelete = determineDataTypesToDelete(settings) + + if (typesToDelete.isEmpty()) { + onDeletionComplete() + return + } + + stores.appStore.dispatch(AppAction.DeleteAndQuitStarted) + + try { + supervisorScope { + typesToDelete.forEach { type -> + launch { + deleteType(type) + } + } + } + } finally { + onDeletionComplete() + } + } + + /** + * Determines which data types to delete based on the user's settings. + * + * This function iterates through all possible `DeleteBrowsingDataOnQuitType` values + * and checks the corresponding setting to see if it has been enabled for deletion on quit. + * + * @param settings The [Settings] instance to query for the "delete on quit" preferences. + * @return A list of [DeleteBrowsingDataOnQuitType] enums that are configured to be deleted. + */ + private fun determineDataTypesToDelete(settings: Settings): List<DeleteBrowsingDataOnQuitType> { + return DeleteBrowsingDataOnQuitType.entries.filter { type -> + settings.getDeleteDataOnQuit(type) } } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataFragment.kt @@ -48,12 +48,21 @@ class DeleteBrowsingDataFragment : Fragment(R.layout.fragment_delete_browsing_da _binding = FragmentDeleteBrowsingDataBinding.bind(view) controller = DefaultDeleteBrowsingDataController( - tabsUseCases.removeAllTabs, - downloadUseCases.removeAllDownloads, - requireComponents.core.historyStorage, - requireComponents.core.permissionStorage, - requireComponents.core.store, - requireComponents.core.engine, + deleteDataUseCases = DefaultDeleteBrowsingDataController.DeleteDataUseCases( + removeAllTabs = tabsUseCases.removeAllTabs, + removeAllDownloads = downloadUseCases.removeAllDownloads, + ), + dataStorage = DefaultDeleteBrowsingDataController.DataStorage( + history = requireComponents.core.historyStorage, + permissions = requireComponents.core.permissionStorage, + ), + stores = DefaultDeleteBrowsingDataController.Stores( + appStore = requireComponents.appStore, + browserStore = requireComponents.core.store, + ), + engine = requireComponents.core.engine, + settings = requireComponents.settings, + coroutineContext = requireActivity().lifecycleScope.coroutineContext, ) settings = requireContext().settings() diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataOnQuitType.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteBrowsingDataOnQuitType.kt @@ -9,6 +9,15 @@ import androidx.annotation.StringRes import org.mozilla.fenix.R import org.mozilla.fenix.ext.getPreferenceKey +/** + * Represents the different types of browsing data that can be configured + * to be deleted automatically when the user quits the application. + * + * Each enum constant is associated with a specific preference key, which is used + * to store the user's choice for that data type. + * + * @param prefKey The string resource ID for the preference key. + */ enum class DeleteBrowsingDataOnQuitType(@param:StringRes private val prefKey: Int) { TABS(R.string.pref_key_delete_open_tabs_on_quit), HISTORY(R.string.pref_key_delete_browsing_history_on_quit), 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 @@ -1429,13 +1429,41 @@ class Settings( return touchExplorationIsEnabled || switchServiceIsEnabled } + /** + * Checks if a specific type of browsing data is configured to be deleted on quit. + * + * @param type The [DeleteBrowsingDataOnQuitType] to check. + * @return `true` if the data type is set to be deleted on quit, `false` otherwise. + */ fun getDeleteDataOnQuit(type: DeleteBrowsingDataOnQuitType): Boolean = preferences.getBoolean(type.getPreferenceKey(appContext), false) + /** + * Sets whether a specific type of browsing data should be deleted on quit. + * + * This function is used to configure the "Delete browsing data on quit" feature. + * It writes the user's choice to `SharedPreferences` for the given data type. + * The value is later retrieved by `getDeleteDataOnQuit`. + * + * @param type The [DeleteBrowsingDataOnQuitType] to configure. + * @param value `true` to enable deletion for this type on quit, `false` to disable it. + */ fun setDeleteDataOnQuit(type: DeleteBrowsingDataOnQuitType, value: Boolean) { preferences.edit { putBoolean(type.getPreferenceKey(appContext), value) } } + /** + * Checks if any browsing data type is configured to be deleted on quit. + * + * This function provides a quick way to determine if the "Delete browsing data on quit" + * feature is active in any capacity. It iterates through all possible data types + * and returns `true` if at least one of them is set for deletion. + * + * This is useful for UI components that need to know whether to display a general + * indicator that the feature is enabled, without needing to know the specific details. + * + * @return `true` if one or more data types are set to be deleted on quit, `false` otherwise. + */ fun shouldDeleteAnyDataOnQuit() = DeleteBrowsingDataOnQuitType.entries.any { getDeleteDataOnQuit(it) } diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/HomeMenuViewTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/HomeMenuViewTest.kt @@ -73,6 +73,7 @@ class HomeMenuViewTest { navController = navController, fenixBrowserUseCases = fenixBrowserUseCases, menuButton = WeakReference(menuButton), + deleteBrowsingDataController = mockk(relaxed = true), ) } diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/toolbar/HomeToolbarViewTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/home/toolbar/HomeToolbarViewTest.kt @@ -43,7 +43,16 @@ class HomeToolbarViewTest { binding = FragmentHomeBinding.inflate(LayoutInflater.from(testContext)) every { homeFragment.requireContext() } returns context every { context.components.settings } returns mockk(relaxed = true) - toolbarView = spyk(HomeToolbarView(binding, mockk(relaxed = true), homeFragment, homeActivity)) + toolbarView = + spyk( + HomeToolbarView( + binding, + mockk(relaxed = true), + homeFragment, + homeActivity, + mockk(), + ), + ) every { toolbarView.buildHomeMenu() } returns mockk(relaxed = true) every { toolbarView.buildTabCounter() } returns mockk(relaxed = true) } diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/deletebrowsingdata/DefaultDeleteBrowsingDataControllerTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/deletebrowsingdata/DefaultDeleteBrowsingDataControllerTest.kt @@ -4,12 +4,17 @@ package org.mozilla.fenix.settings.deletebrowsingdata +import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.every import io.mockk.mockk import io.mockk.slot import io.mockk.spyk import io.mockk.verify -import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withContext import mozilla.components.browser.state.action.EngineAction import mozilla.components.browser.state.action.RecentlyClosedAction import mozilla.components.browser.state.store.BrowserStore @@ -20,44 +25,55 @@ import mozilla.components.concept.engine.translate.OperationLevel import mozilla.components.concept.storage.HistoryStorage import mozilla.components.feature.downloads.DownloadsUseCases import mozilla.components.feature.tabs.TabsUseCases -import mozilla.components.support.test.rule.MainCoroutineRule -import mozilla.components.support.test.rule.runTestOnMain +import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before -import org.junit.Ignore -import org.junit.Rule import org.junit.Test +import org.mozilla.fenix.components.AppStore import org.mozilla.fenix.components.PermissionStorage +import org.mozilla.fenix.components.appstate.AppAction +import org.mozilla.fenix.utils.Settings class DefaultDeleteBrowsingDataControllerTest { - @get:Rule - val coroutinesTestRule = MainCoroutineRule() + val testDispatcher = StandardTestDispatcher() - private var removeAllTabs: TabsUseCases.RemoveAllTabsUseCase = mockk(relaxed = true) - private var removeAllDownloads: DownloadsUseCases.RemoveAllDownloadsUseCase = mockk(relaxed = true) - private var historyStorage: HistoryStorage = mockk(relaxed = true) - private var permissionStorage: PermissionStorage = mockk(relaxed = true) - private var store: BrowserStore = mockk(relaxed = true) + private val removeAllTabs: TabsUseCases.RemoveAllTabsUseCase = mockk(relaxed = true) + private val removeAllDownloads: DownloadsUseCases.RemoveAllDownloadsUseCase = mockk(relaxed = true) + private val historyStorage: HistoryStorage = mockk(relaxed = true) + private val permissionStorage: PermissionStorage = mockk(relaxed = true) + private val store: BrowserStore = mockk(relaxed = true) private val engine: Engine = mockk(relaxed = true) + private val appStore: AppStore = mockk(relaxed = true) + private val settings: Settings = mockk(relaxed = true) + private lateinit var controller: DefaultDeleteBrowsingDataController @Before - @OptIn(DelicateCoroutinesApi::class) // coroutineContext usage fun setup() { - controller = DefaultDeleteBrowsingDataController( - removeAllTabs = removeAllTabs, - removeAllDownloads = removeAllDownloads, - historyStorage = historyStorage, - store = store, - permissionStorage = permissionStorage, - engine = engine, - coroutineContext = coroutinesTestRule.testDispatcher, + controller = spyk( + DefaultDeleteBrowsingDataController( + deleteDataUseCases = DefaultDeleteBrowsingDataController.DeleteDataUseCases( + removeAllTabs = removeAllTabs, + removeAllDownloads = removeAllDownloads, + ), + dataStorage = DefaultDeleteBrowsingDataController.DataStorage( + history = historyStorage, + permissions = permissionStorage, + ), + stores = DefaultDeleteBrowsingDataController.Stores( + appStore = appStore, + browserStore = store, + ), + engine = engine, + settings = settings, + coroutineContext = testDispatcher, + ), ) } @Test - fun deleteTabs() = runTestOnMain { + fun deleteTabs() = runTest(testDispatcher) { controller.deleteTabs() verify { @@ -66,8 +82,7 @@ class DefaultDeleteBrowsingDataControllerTest { } @Test - fun deleteBrowsingHistory() = runTestOnMain { - controller = spyk(controller) + fun deleteBrowsingHistory() = runTest(testDispatcher) { controller.deleteBrowsingHistory() coVerify { @@ -78,7 +93,7 @@ class DefaultDeleteBrowsingDataControllerTest { } @Test - fun deleteCookiesAndSiteData() = runTestOnMain { + fun deleteCookiesAndSiteData() = runTest(testDispatcher) { controller.deleteCookiesAndSiteData() verify { @@ -92,12 +107,19 @@ class DefaultDeleteBrowsingDataControllerTest { } } - @Ignore("Disabled: Fails if new tests are added: https://bugzilla.mozilla.org/show_bug.cgi?id=1956618") @Test - fun deleteCachedFiles() = runTestOnMain { + fun deleteCachedFiles() = runTest(testDispatcher) { val onSuccessSlot = slot<() -> Unit>() val onErrorSlot = slot<(Throwable) -> Unit>() + every { + engine.manageTranslationsLanguageModel( + options = any(), + onSuccess = capture(onSuccessSlot), + onError = capture(onErrorSlot), + ) + } returns Unit + controller.deleteCachedFiles() verify { @@ -106,8 +128,8 @@ class DefaultDeleteBrowsingDataControllerTest { operation = ModelOperation.DELETE, operationLevel = OperationLevel.CACHE, ), - onSuccess = capture(onSuccessSlot), - onError = capture(onErrorSlot), + onSuccess = any(), + onError = any(), ) engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.ALL_CACHES)) } @@ -117,7 +139,7 @@ class DefaultDeleteBrowsingDataControllerTest { } @Test - fun deleteSitePermissions() = runTestOnMain { + fun deleteSitePermissions() = runTest(testDispatcher) { controller.deleteSitePermissions() coVerify { @@ -127,11 +149,98 @@ class DefaultDeleteBrowsingDataControllerTest { } @Test - fun deleteDownloads() = runTestOnMain { + fun deleteDownloads() = runTest(testDispatcher) { controller.deleteDownloads() verify { removeAllDownloads.invoke() } } + + @Test + fun `clearBrowsingDataOnQuit - when no data types are selected - completes immediately`() = runTest(testDispatcher) { + val onDeletionComplete: () -> Unit = mockk(relaxed = true) + + every { settings.getDeleteDataOnQuit(any()) } returns false // No types are selected + + controller.clearBrowsingDataOnQuit(onDeletionComplete) + + coVerify(exactly = 0) { appStore.dispatch(any()) } + coVerify(exactly = 0) { controller.deleteType(any()) } + + verify { onDeletionComplete.invoke() } + } + + @Test + fun `clearBrowsingDataOnQuit - when one data type is selected - deletes it and completes`() = runTest(testDispatcher) { + val onDeletionComplete: () -> Unit = mockk(relaxed = true) + + val typeToDelete = DeleteBrowsingDataOnQuitType.TABS + + every { settings.getDeleteDataOnQuit(any()) } returns false // Reset all + every { settings.getDeleteDataOnQuit(typeToDelete) } returns true // Select one type + + controller.clearBrowsingDataOnQuit(onDeletionComplete) + + coVerify { + appStore.dispatch(AppAction.DeleteAndQuitStarted) + controller.deleteType(typeToDelete) + } + + coVerify(exactly = 1) { controller.deleteType(any()) } + verify { onDeletionComplete.invoke() } + } + + @Test + fun `clearBrowsingDataOnQuit - when multiple data types are selected - deletes all of them`() = runTest(testDispatcher) { + val onDeletionComplete: () -> Unit = mockk(relaxed = true) + + val typesToDelete = listOf( + DeleteBrowsingDataOnQuitType.HISTORY, + DeleteBrowsingDataOnQuitType.COOKIES, + ) + + every { settings.getDeleteDataOnQuit(any()) } returns false // Reset all + typesToDelete.forEach { type -> + every { settings.getDeleteDataOnQuit(type) } returns true + } + + controller.clearBrowsingDataOnQuit(onDeletionComplete) + + coVerify { appStore.dispatch(AppAction.DeleteAndQuitStarted) } + typesToDelete.forEach { type -> + coVerify { controller.deleteType(type) } + } + + coVerify(exactly = typesToDelete.size) { controller.deleteType(any()) } + verify { onDeletionComplete.invoke() } + } + + @Test + fun `clearBrowsingDataOnQuit - onDeletionComplete is called even if one deletion throws an exception`() = runTest(testDispatcher) { + val onDeletionComplete: () -> Unit = mockk(relaxed = true) + + val failingType = DeleteBrowsingDataOnQuitType.CACHE + val succeedingType = DeleteBrowsingDataOnQuitType.TABS + + val exceptionHandler = CoroutineExceptionHandler { _, throwable -> + assertTrue(throwable is RuntimeException) + assertEquals("Deletion failed!", throwable.message) + } + + every { settings.getDeleteDataOnQuit(failingType) } returns true + every { settings.getDeleteDataOnQuit(succeedingType) } returns true + + coEvery { controller.deleteType(failingType) } throws RuntimeException("Deletion failed!") + coEvery { controller.deleteType(succeedingType) } returns Unit + + withContext(coroutineContext + exceptionHandler) { + controller.clearBrowsingDataOnQuit(onDeletionComplete) + } + + coVerify { controller.deleteType(failingType) } + coVerify { controller.deleteType(succeedingType) } + + verify { onDeletionComplete.invoke() } + } } diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuitTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/settings/deletebrowsingdata/DeleteAndQuitTest.kt @@ -1,152 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -@file:Suppress("DEPRECATION") - -package org.mozilla.fenix.settings.deletebrowsingdata - -import io.mockk.coVerify -import io.mockk.every -import io.mockk.mockk -import io.mockk.verifyOrder -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.advanceUntilIdle -import mozilla.components.browser.icons.BrowserIcons -import mozilla.components.browser.storage.sync.PlacesHistoryStorage -import mozilla.components.concept.engine.Engine -import mozilla.components.feature.downloads.DownloadsUseCases.RemoveAllDownloadsUseCase -import mozilla.components.feature.tabs.TabsUseCases -import mozilla.components.support.test.rule.MainCoroutineRule -import mozilla.components.support.test.rule.runTestOnMain -import org.junit.Before -import org.junit.Ignore -import org.junit.Rule -import org.junit.Test -import org.mozilla.fenix.HomeActivity -import org.mozilla.fenix.components.AppStore -import org.mozilla.fenix.components.PermissionStorage -import org.mozilla.fenix.components.appstate.AppAction -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataOnQuitType.CACHE -import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataOnQuitType.COOKIES -import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataOnQuitType.DOWNLOADS -import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataOnQuitType.HISTORY -import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataOnQuitType.PERMISSIONS -import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataOnQuitType.TABS -import org.mozilla.fenix.utils.Settings - -class DeleteAndQuitTest { - - @get:Rule - val coroutinesTestRule = MainCoroutineRule() - - private val activity: HomeActivity = mockk(relaxed = true) - private val appStore: AppStore = mockk(relaxed = true) - private val settings: Settings = mockk(relaxed = true) - private val tabUseCases: TabsUseCases = mockk(relaxed = true) - private val historyStorage: PlacesHistoryStorage = mockk(relaxed = true) - private val permissionStorage: PermissionStorage = mockk(relaxed = true) - private val iconsStorage: BrowserIcons = mockk() - private val engine: Engine = mockk(relaxed = true) - private val removeAllTabsUseCases: TabsUseCases.RemoveAllTabsUseCase = mockk(relaxed = true) - private val downloadsUseCases: RemoveAllDownloadsUseCase = mockk(relaxed = true) - - @Before - fun setUp() { - every { activity.components.appStore } returns appStore - every { activity.components.core.historyStorage } returns historyStorage - every { activity.components.core.permissionStorage } returns permissionStorage - every { activity.components.useCases.tabsUseCases } returns tabUseCases - every { activity.components.useCases.downloadUseCases.removeAllDownloads } returns downloadsUseCases - every { tabUseCases.removeAllTabs } returns removeAllTabsUseCases - every { activity.components.core.engine } returns engine - every { activity.components.settings } returns settings - every { activity.components.core.icons } returns iconsStorage - } - - @Ignore("Failing test; need more investigation.") - @OptIn(ExperimentalCoroutinesApi::class) // advanceUntilIdle - @Test - fun `delete only tabs and quit`() = runTestOnMain { - // When - every { settings.getDeleteDataOnQuit(TABS) } returns true - - deleteAndQuit(activity, this) - - advanceUntilIdle() - - verifyOrder { - appStore.dispatch(AppAction.DeleteAndQuitStarted) - removeAllTabsUseCases.invoke(false) - activity.finishAndRemoveTask() - } - - coVerify(exactly = 0) { - engine.clearData( - Engine.BrowsingData.select( - Engine.BrowsingData.COOKIES, - ), - ) - - permissionStorage.deleteAllSitePermissions() - - engine.clearData(Engine.BrowsingData.allCaches()) - } - - coVerify(exactly = 0) { - historyStorage.deleteEverything() - iconsStorage.clear() - } - } - - @Ignore("Failing test; need more investigation.") - @OptIn(ExperimentalCoroutinesApi::class) // advanceUntilIdle - @Test - fun `delete everything and quit`() = runTestOnMain { - // When - every { settings.getDeleteDataOnQuit(TABS) } returns true - every { settings.getDeleteDataOnQuit(HISTORY) } returns true - every { settings.getDeleteDataOnQuit(COOKIES) } returns true - every { settings.getDeleteDataOnQuit(CACHE) } returns true - every { settings.getDeleteDataOnQuit(PERMISSIONS) } returns true - every { settings.getDeleteDataOnQuit(DOWNLOADS) } returns true - - deleteAndQuit(activity, this) - - advanceUntilIdle() - - coVerify(exactly = 1) { - appStore.dispatch(AppAction.DeleteAndQuitStarted) - - // Delete tabs - removeAllTabsUseCases.invoke(false) - - // Delete browsing data - engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.DOM_STORAGES)) - historyStorage.deleteEverything() - iconsStorage.clear() - - // Delete cookies - engine.clearData( - Engine.BrowsingData.select( - Engine.BrowsingData.COOKIES, - Engine.BrowsingData.AUTH_SESSIONS, - ), - ) - - // Delete cached files - engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.ALL_CACHES)) - - // Delete permissions - engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.ALL_SITE_SETTINGS)) - permissionStorage.deleteAllSitePermissions() - - // Delete downloads - downloadsUseCases.invoke() - - // Finish activity - activity.finishAndRemoveTask() - } - } -} diff --git a/mobile/android/fenix/config/detekt-baseline.xml b/mobile/android/fenix/config/detekt-baseline.xml @@ -263,12 +263,9 @@ <ID>UndocumentedPublicClass:DefaultSyncController.kt$SyncController</ID> <ID>UndocumentedPublicClass:DefaultSyncInteractor.kt$SyncInteractor</ID> <ID>UndocumentedPublicClass:DefaultTopSitesView.kt$DefaultTopSitesView : TopSitesView</ID> - <ID>UndocumentedPublicClass:DeleteBrowsingDataController.kt$DefaultDeleteBrowsingDataController : DeleteBrowsingDataController</ID> - <ID>UndocumentedPublicClass:DeleteBrowsingDataController.kt$DeleteBrowsingDataController</ID> <ID>UndocumentedPublicClass:DeleteBrowsingDataFragment.kt$DeleteBrowsingDataFragment : Fragment</ID> <ID>UndocumentedPublicClass:DeleteBrowsingDataItem.kt$DeleteBrowsingDataItem : ConstraintLayout</ID> <ID>UndocumentedPublicClass:DeleteBrowsingDataOnQuitFragment.kt$DeleteBrowsingDataOnQuitFragment : PreferenceFragmentCompat</ID> - <ID>UndocumentedPublicClass:DeleteBrowsingDataOnQuitType.kt$DeleteBrowsingDataOnQuitType</ID> <ID>UndocumentedPublicClass:DesktopFolders.kt$DesktopFolders</ID> <ID>UndocumentedPublicClass:DownloadService.kt$DownloadService : AbstractFetchDownloadService</ID> <ID>UndocumentedPublicClass:DropDownListPreference.kt$DropDownListPreference : DropDownPreference</ID> @@ -571,12 +568,6 @@ <ID>UndocumentedPublicFunction:DefaultToolbarMenu.kt$DefaultToolbarMenu$@VisibleForTesting(otherwise = PRIVATE) fun canAddToHomescreen(): Boolean</ID> <ID>UndocumentedPublicFunction:DefaultToolbarMenu.kt$DefaultToolbarMenu$@VisibleForTesting(otherwise = PRIVATE) fun shouldShowOpenInApp(): Boolean</ID> <ID>UndocumentedPublicFunction:DefaultToolbarMenu.kt$DefaultToolbarMenu$@VisibleForTesting(otherwise = PRIVATE) fun shouldShowReaderViewCustomization(): Boolean</ID> - <ID>UndocumentedPublicFunction:DeleteBrowsingDataController.kt$DeleteBrowsingDataController$suspend fun deleteBrowsingHistory()</ID> - <ID>UndocumentedPublicFunction:DeleteBrowsingDataController.kt$DeleteBrowsingDataController$suspend fun deleteCachedFiles()</ID> - <ID>UndocumentedPublicFunction:DeleteBrowsingDataController.kt$DeleteBrowsingDataController$suspend fun deleteCookiesAndSiteData()</ID> - <ID>UndocumentedPublicFunction:DeleteBrowsingDataController.kt$DeleteBrowsingDataController$suspend fun deleteDownloads()</ID> - <ID>UndocumentedPublicFunction:DeleteBrowsingDataController.kt$DeleteBrowsingDataController$suspend fun deleteSitePermissions()</ID> - <ID>UndocumentedPublicFunction:DeleteBrowsingDataController.kt$DeleteBrowsingDataController$suspend fun deleteTabs()</ID> <ID>UndocumentedPublicFunction:DeleteBrowsingDataOnQuitType.kt$DeleteBrowsingDataOnQuitType$fun getPreferenceKey(context: Context)</ID> <ID>UndocumentedPublicFunction:DesktopFolders.kt$DesktopFolders$suspend fun withOptionalDesktopFolders(node: BookmarkNode): BookmarkNode</ID> <ID>UndocumentedPublicFunction:DownloadState.kt$fun DownloadState.isActiveDownload(): Boolean</ID> @@ -718,7 +709,6 @@ <ID>UndocumentedPublicFunction:SearchStringValidator.kt$SearchStringValidator$fun isSearchStringValid(client: Client, searchString: String): Result</ID> <ID>UndocumentedPublicFunction:Settings.kt$Settings$@VisibleForTesting(otherwise = PRIVATE) fun setStrictETP()</ID> <ID>UndocumentedPublicFunction:Settings.kt$Settings$fun amoCollectionOverrideConfigured(): Boolean</ID> - <ID>UndocumentedPublicFunction:Settings.kt$Settings$fun getDeleteDataOnQuit(type: DeleteBrowsingDataOnQuitType): Boolean</ID> <ID>UndocumentedPublicFunction:Settings.kt$Settings$fun getSitePermissionsCustomSettingsRules(): SitePermissionsRules</ID> <ID>UndocumentedPublicFunction:Settings.kt$Settings$fun getTabTimeout(): Long</ID> <ID>UndocumentedPublicFunction:Settings.kt$Settings$fun getTabTimeoutPingString(): String</ID> @@ -729,9 +719,7 @@ <ID>UndocumentedPublicFunction:Settings.kt$Settings$fun incrementShowLoginsSecureWarningSyncCount()</ID> <ID>UndocumentedPublicFunction:Settings.kt$Settings$fun incrementVisitedInstallableCount()</ID> <ID>UndocumentedPublicFunction:Settings.kt$Settings$fun recordPasswordsEncryptionKeyGenerated()</ID> - <ID>UndocumentedPublicFunction:Settings.kt$Settings$fun setDeleteDataOnQuit(type: DeleteBrowsingDataOnQuitType, value: Boolean)</ID> <ID>UndocumentedPublicFunction:Settings.kt$Settings$fun setSitePermissionSettingListener(lifecycleOwner: LifecycleOwner, listener: () -&gt; Unit)</ID> - <ID>UndocumentedPublicFunction:Settings.kt$Settings$fun shouldDeleteAnyDataOnQuit()</ID> <ID>UndocumentedPublicFunction:ShareCloseView.kt$ShareCloseInteractor$fun onShareClosed()</ID> <ID>UndocumentedPublicFunction:ShareCloseView.kt$ShareCloseView$fun setTabs(tabs: List&lt;ShareData&gt;)</ID> <ID>UndocumentedPublicFunction:ShareController.kt$DefaultShareController$@VisibleForTesting fun getShareText()</ID>