tor-browser

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

commit c212381ff1bf4420806af3bc3c967acf49e2520a
parent eab164ba1f92a24404f33d2054da35efc1d8afd9
Author: Andrey Zinovyev <azinovyev@mozilla.com>
Date:   Wed,  1 Oct 2025 13:47:22 +0000

Bug 1986714 - Remove (activity as HomeActivity) casts in AddonsManagement r=jonalmeida,android-reviewers

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

Diffstat:
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt | 30++++++++----------------------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/Extensions.kt | 32++++++++++++++++++++------------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/InstalledAddonDetailsFragment.kt | 36++++++++++++++----------------------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/NavController.kt | 11+++++++++++
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/extension/WebExtensionPromptFeature.kt | 7-------
Mmobile/android/fenix/app/src/test/java/org/mozilla/fenix/addons/InstalledAddonDetailsFragmentTest.kt | 78++++++++++++++++++++++++++++++++++++++++++++----------------------------------
6 files changed, 97 insertions(+), 97 deletions(-)

diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt @@ -26,11 +26,10 @@ import mozilla.components.feature.addons.Addon import mozilla.components.feature.addons.AddonManager import mozilla.components.feature.addons.AddonManagerException import mozilla.components.feature.addons.ui.AddonsManagerAdapter -import org.mozilla.fenix.BrowserDirection -import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.databinding.FragmentAddOnsManagementBinding import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.openToBrowser import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.runIfFragmentIsAttached import org.mozilla.fenix.ext.showToolbar @@ -50,19 +49,10 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management) private var adapter: AddonsManagerAdapter? = null - private val browsingModeManager by lazy { - (activity as HomeActivity).browsingModeManager - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding = FragmentAddOnsManagementBinding.bind(view) bindRecyclerView() - (activity as HomeActivity).webExtensionPromptFeature.onAddonChanged = { - runIfFragmentIsAttached { - adapter?.updateAddon(it) - } - } } override fun onResume() { @@ -75,7 +65,6 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management) // letting go of the resources to avoid memory leak. adapter = null binding = null - (activity as HomeActivity).webExtensionPromptFeature.onAddonChanged = {} } private fun bindRecyclerView() { @@ -84,12 +73,7 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management) onInstallButtonClicked = ::installAddon, onMoreAddonsButtonClicked = ::openAMO, onLearnMoreClicked = { link, addon -> - openLearnMoreLink( - activity as HomeActivity, - link, - addon, - BrowserDirection.FromAddonsManagementFragment, - ) + binding?.root?.openLearnMoreLink(link, addon) }, ) @@ -217,10 +201,12 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management) } private fun openAMO() { - openLinkInNewTab( - activity as HomeActivity, - AMO_HOMEPAGE_FOR_ANDROID, - BrowserDirection.FromAddonsManagementFragment, + findNavController().openToBrowser() + val isPrivate = requireComponents.appStore.state.mode.isPrivate + requireComponents.useCases.fenixBrowserUseCases.loadUrlOrSearch( + searchTermOrURL = AMO_HOMEPAGE_FOR_ANDROID, + newTab = true, + private = isPrivate, ) } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/Extensions.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/Extensions.kt @@ -5,13 +5,14 @@ package org.mozilla.fenix.addons import android.view.View +import androidx.navigation.findNavController import mozilla.components.feature.addons.Addon import mozilla.components.feature.addons.ui.AddonsManagerAdapterDelegate -import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.BuildConfig -import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.compose.snackbar.Snackbar import org.mozilla.fenix.compose.snackbar.SnackbarState +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.openToBrowser import org.mozilla.fenix.settings.SupportUtils /** @@ -35,21 +36,28 @@ internal fun showSnackBar( ).show() } -internal fun openLearnMoreLink( - activity: HomeActivity, +internal fun View.openLearnMoreLink( link: AddonsManagerAdapterDelegate.LearnMoreLinks, addon: Addon, - from: BrowserDirection, ) { - val url = when (link) { + val url = resolveLearnMoreUrl(link, addon) ?: return + findNavController().openToBrowser() + val isPrivate = context.components.appStore.state.mode.isPrivate + context.components.useCases.fenixBrowserUseCases.loadUrlOrSearch( + searchTermOrURL = url, + newTab = true, + private = isPrivate, + ) +} + +private fun resolveLearnMoreUrl( + link: AddonsManagerAdapterDelegate.LearnMoreLinks, + addon: Addon, +): String? { + return when (link) { AddonsManagerAdapterDelegate.LearnMoreLinks.BLOCKLISTED_ADDON -> "${BuildConfig.AMO_BASE_URL}/android/blocked-addon/${addon.id}/${addon.version}/" AddonsManagerAdapterDelegate.LearnMoreLinks.ADDON_NOT_CORRECTLY_SIGNED -> - SupportUtils.getSumoURLForTopic(activity.baseContext, SupportUtils.SumoTopic.UNSIGNED_ADDONS) + SupportUtils.getGenericSumoURLForTopic(SupportUtils.SumoTopic.UNSIGNED_ADDONS) } - openLinkInNewTab(activity, url, from) -} - -internal fun openLinkInNewTab(activity: HomeActivity, url: String, from: BrowserDirection) { - activity.openToBrowserAndLoad(searchTermOrURL = url, newTab = true, from = from) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/InstalledAddonDetailsFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/InstalledAddonDetailsFragment.kt @@ -28,9 +28,7 @@ import mozilla.components.feature.addons.ui.translateName import mozilla.components.support.base.log.logger.Logger import mozilla.components.support.ktx.android.content.appName import mozilla.components.support.ktx.android.content.appVersionName -import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.BuildConfig -import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.databinding.FragmentInstalledAddOnDetailsBinding import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.runIfFragmentIsAttached @@ -168,12 +166,7 @@ class InstalledAddonDetailsFragment : Fragment() { messageBarWarningView, messageBarErrorView, onLearnMoreLinkClicked = { link -> - openLearnMoreLink( - activity as HomeActivity, - link, - addon, - BrowserDirection.FromAddonDetailsFragment, - ) + binding.root.openLearnMoreLink(link, addon) }, addon, addon.translateName(it), @@ -228,12 +221,12 @@ class InstalledAddonDetailsFragment : Fragment() { switch.isClickable = true enableButtons() switch.isChecked = addon.isEnabled() - context?.let { + context?.let { ctx -> showSnackBar( binding.root, getString( addonsR.string.mozac_feature_addons_failed_to_enable, - addon.translateName(it), + addon.translateName(ctx), ), ) } @@ -257,12 +250,12 @@ class InstalledAddonDetailsFragment : Fragment() { switch.isClickable = true switch.isChecked = addon.isEnabled() enableButtons() - context?.let { + context?.let { ctx -> showSnackBar( binding.root, getString( addonsR.string.mozac_feature_addons_failed_to_disable, - addon.translateName(it), + addon.translateName(ctx), ), ) } @@ -337,17 +330,17 @@ class InstalledAddonDetailsFragment : Fragment() { } private fun bindReportButton() { - binding.reportAddOn.setOnClickListener { - val shouldCreatePrivateSession = (activity as HomeActivity).browsingModeManager.mode.isPrivate + binding.reportAddOn.setOnClickListener { v -> + val shouldCreatePrivateSession = v.context.components.appStore.state.mode.isPrivate - it.context.components.useCases.tabsUseCases.selectOrAddTab( + v.context.components.useCases.tabsUseCases.selectOrAddTab( url = "${BuildConfig.AMO_BASE_URL}/android/feedback/addon/${addon.id}/", private = shouldCreatePrivateSession, ignoreFragment = true, ) // Send user to the newly open tab. - it.findNavController().navigate( + v.findNavController().navigate( InstalledAddonDetailsFragmentDirections.actionGlobalBrowser(null), ) } @@ -356,12 +349,11 @@ class InstalledAddonDetailsFragment : Fragment() { private fun bindSettings() { binding.settings.apply { isVisible = shouldSettingsBeVisible() - setOnClickListener { + setOnClickListener { v -> val settingUrl = addon.installedState?.optionsPageUrl ?: return@setOnClickListener val directions = if (addon.installedState?.openOptionsPageInTab == true) { - val components = it.context.components - val shouldCreatePrivateSession = - (activity as HomeActivity).browsingModeManager.mode.isPrivate + val components = v.context.components + val shouldCreatePrivateSession = v.context.components.appStore.state.mode.isPrivate // If the addon settings page is already open in a tab, select that one components.useCases.tabsUseCases.selectOrAddTab( @@ -414,12 +406,12 @@ class InstalledAddonDetailsFragment : Fragment() { onError = { _, _ -> runIfFragmentIsAttached { setAllInteractiveViewsClickable(binding, true) - context?.let { + context?.let { ctx -> showSnackBar( binding.root, getString( addonsR.string.mozac_feature_addons_failed_to_uninstall, - addon.translateName(it), + addon.translateName(ctx), ), ) } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/NavController.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/ext/NavController.kt @@ -12,6 +12,7 @@ import androidx.navigation.NavOptions import mozilla.components.concept.base.crash.Breadcrumb import mozilla.components.lib.crash.CrashReporter import mozilla.components.support.base.log.logger.Logger +import org.mozilla.fenix.R /** * Navigate from the fragment with [id] using the given [directions]. @@ -72,3 +73,13 @@ fun NavController.hasTopDestination(fragmentClassName: String): Boolean { true, ) == true } + +/** + * Navigate into the Browser destination without casting to HomeActivity. + * Fragments can call: findNavController().openToBrowser(direction) + */ +fun NavController.openToBrowser() { + if (alreadyOnDestination(R.id.browserFragment)) return + val dest = R.id.browserFragment + navigate(dest) +} diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/extension/WebExtensionPromptFeature.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/extension/WebExtensionPromptFeature.kt @@ -55,13 +55,6 @@ class WebExtensionPromptFeature( ) : LifecycleAwareFeature { /** - * (optional) callback invoked when an add-on was updated due to an interaction with a - * [WebExtensionPromptRequest]. - * Won't be needed after https://bugzilla.mozilla.org/show_bug.cgi?id=1858484. - */ - var onAddonChanged: (Addon) -> Unit = {} - - /** * Whether or not an add-on installation is in progress. */ private var isInstallationInProgress = false diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/addons/InstalledAddonDetailsFragmentTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/addons/InstalledAddonDetailsFragmentTest.kt @@ -33,9 +33,12 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.BuildConfig import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.R +import org.mozilla.fenix.components.AppStore +import org.mozilla.fenix.components.appstate.AppState +import org.mozilla.fenix.components.usecases.FenixBrowserUseCases import org.mozilla.fenix.databinding.FragmentInstalledAddOnDetailsBinding import org.mozilla.fenix.ext.components import org.mozilla.fenix.settings.SupportUtils @@ -167,7 +170,6 @@ class InstalledAddonDetailsFragmentTest { fun `GIVEN an add-on WHEN clicking the report button THEN a new tab is open`() { val addon = mockAddon() every { fragment.addon } returns addon - every { fragment.activity } returns mockk<HomeActivity>(relaxed = true) val useCases = mockk<TabsUseCases>() val selectOrAddTab = mockk<TabsUseCases.SelectOrAddUseCase>() every { selectOrAddTab.invoke(any(), any(), any(), any(), any()) } returns "some-tab-id" @@ -185,6 +187,15 @@ class InstalledAddonDetailsFragmentTest { val navController = mockk<NavController>(relaxed = true) Navigation.setViewNavController(fragment.binding.root, navController) + val appStore = mockk<AppStore>() + val appState = mockk<AppState>() + every { appState.mode } returns mockk(relaxed = true) { + every { isPrivate } returns false // or true + } + + every { appStore.state } returns appState + every { testContext.components.appStore } returns appStore + // Click the report button. fragment.binding.reportAddOn.performClick() @@ -206,14 +217,12 @@ class InstalledAddonDetailsFragmentTest { fun `GIVEN an add-on and private browsing mode is used WHEN clicking the report button THEN a new private tab is open`() { val addon = mockAddon() every { fragment.addon } returns addon - val homeActivity = mockk<HomeActivity>(relaxed = true) - every { homeActivity.browsingModeManager.mode.isPrivate } returns true - every { fragment.activity } returns homeActivity val useCases = mockk<TabsUseCases>() val selectOrAddTab = mockk<TabsUseCases.SelectOrAddUseCase>() every { selectOrAddTab.invoke(any(), any(), any(), any(), any()) } returns "some-tab-id" every { useCases.selectOrAddTab } returns selectOrAddTab every { testContext.components.useCases.tabsUseCases } returns useCases + every { testContext.components.appStore.state.mode.isPrivate } returns true // We create the `binding` instance and bind the UI here because `onCreateView()` checks a late init variable // and we cannot easily mock it to skip the check. fragment.setBindingAndBindUI( @@ -319,13 +328,7 @@ class InstalledAddonDetailsFragmentTest { every { addon.isDisabledAsBlocklisted() } returns true every { fragment.addon } returns addon every { fragment.context } returns testContext - every { - (fragment.activity as HomeActivity).openToBrowserAndLoad( - searchTermOrURL = any(), - newTab = any(), - from = any(), - ) - } returns Unit + // We create the `binding` instance and bind the UI here because `onCreateView()` checks a late init variable // and we cannot easily mock it to skip the check. val binding = FragmentInstalledAddOnDetailsBinding.inflate( @@ -334,6 +337,12 @@ class InstalledAddonDetailsFragmentTest { false, ) fragment.setBindingAndBindUI(binding) + val navController = mockk<NavController>(relaxed = true) + Navigation.setViewNavController(binding.root, navController) + + val fenix = mockk<FenixBrowserUseCases>(relaxed = true) + every { testContext.components.useCases.fenixBrowserUseCases } returns fenix + every { testContext.components.appStore.state.mode.isPrivate } returns false val warningView = binding.root.findViewById<View>(addonsR.id.add_on_messagebar_warning) @@ -344,11 +353,12 @@ class InstalledAddonDetailsFragmentTest { errorView.findViewById<TextView>(addonsR.id.add_on_messagebar_error_learn_more_link) .performClick() + verify { navController.navigate(R.id.browserFragment) } verify { - (fragment.activity as HomeActivity).openToBrowserAndLoad( + fenix.loadUrlOrSearch( searchTermOrURL = "${BuildConfig.AMO_BASE_URL}/android/blocked-addon/some-addon-id/1.2.3/", newTab = true, - from = BrowserDirection.FromAddonDetailsFragment, + private = false, ) } } @@ -359,14 +369,7 @@ class InstalledAddonDetailsFragmentTest { every { addon.isDisabledAsNotCorrectlySigned() } returns true every { fragment.addon } returns addon every { fragment.context } returns testContext - every { - (fragment.activity as HomeActivity).openToBrowserAndLoad( - searchTermOrURL = any(), - newTab = any(), - from = any(), - ) - } returns Unit - every { (fragment.activity as HomeActivity).baseContext } returns testContext + // We create the `binding` instance and bind the UI here because `onCreateView()` checks a late init variable // and we cannot easily mock it to skip the check. val binding = FragmentInstalledAddOnDetailsBinding.inflate( @@ -375,6 +378,12 @@ class InstalledAddonDetailsFragmentTest { false, ) fragment.setBindingAndBindUI(binding) + val navController = mockk<NavController>(relaxed = true) + Navigation.setViewNavController(binding.root, navController) + + val fenix = mockk<FenixBrowserUseCases>(relaxed = true) + every { testContext.components.useCases.fenixBrowserUseCases } returns fenix + every { testContext.components.appStore.state.mode.isPrivate } returns false val warningView = binding.root.findViewById<View>(addonsR.id.add_on_messagebar_warning) @@ -385,11 +394,12 @@ class InstalledAddonDetailsFragmentTest { errorView.findViewById<TextView>(addonsR.id.add_on_messagebar_error_learn_more_link) .performClick() + verify { navController.navigate(R.id.browserFragment) } verify { - (fragment.activity as HomeActivity).openToBrowserAndLoad( - searchTermOrURL = SupportUtils.getSumoURLForTopic(testContext, SupportUtils.SumoTopic.UNSIGNED_ADDONS), + fenix.loadUrlOrSearch( + searchTermOrURL = SupportUtils.getGenericSumoURLForTopic(SupportUtils.SumoTopic.UNSIGNED_ADDONS), newTab = true, - from = BrowserDirection.FromAddonDetailsFragment, + private = false, ) } } @@ -400,13 +410,6 @@ class InstalledAddonDetailsFragmentTest { every { addon.isSoftBlocked() } returns true every { fragment.addon } returns addon every { fragment.context } returns testContext - every { - (fragment.activity as HomeActivity).openToBrowserAndLoad( - searchTermOrURL = any(), - newTab = any(), - from = any(), - ) - } returns Unit // We create the `binding` instance and bind the UI here because `onCreateView()` checks a late init variable // and we cannot easily mock it to skip the check. @@ -416,6 +419,12 @@ class InstalledAddonDetailsFragmentTest { false, ) fragment.setBindingAndBindUI(binding) + val navController = mockk<NavController>(relaxed = true) + Navigation.setViewNavController(binding.root, navController) + + val fenix = mockk<FenixBrowserUseCases>(relaxed = true) + every { testContext.components.useCases.fenixBrowserUseCases } returns fenix + every { testContext.components.appStore.state.mode.isPrivate } returns false val warningView = binding.root.findViewById<View>(addonsR.id.add_on_messagebar_warning) assertTrue(warningView.isVisible) @@ -425,11 +434,12 @@ class InstalledAddonDetailsFragmentTest { warningView.findViewById<TextView>(addonsR.id.add_on_messagebar_warning_learn_more_link) .performClick() + verify { navController.navigate(R.id.browserFragment) } verify { - (fragment.activity as HomeActivity).openToBrowserAndLoad( + fenix.loadUrlOrSearch( searchTermOrURL = "${BuildConfig.AMO_BASE_URL}/android/blocked-addon/some-addon-id/1.2.3/", newTab = true, - from = BrowserDirection.FromAddonDetailsFragment, + private = false, ) } }