Firefox · Core
CVE-2026-74975
Logic Error in Core
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
mobile/android/android-components/components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadsFeature.ktmobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadsFeatureTest.ktmobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/AddonPopupBaseFragment.ktmobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
Patch
diff --git a/mobile/android/android-components/components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadsFeature.kt b/mobile/android/android-components/components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadsFeature.kt
index 896701bbd5c..eefbf26fb7e 100644
--- a/mobile/android/android-components/components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadsFeature.kt
+++ b/mobile/android/android-components/components/feature/downloads/src/main/java/mozilla/components/feature/downloads/DownloadsFeature.kt
@@ -123,6 +123,8 @@ value class OpenFileCallback(val value: () -> Unit)
* manager is provided, a dialog will be shown before every download.
* @property promptsStyling styling properties for the dialog.
* @property onDownloadStartedListener a callback invoked when a download is started.
+ * @property dismissCustomFirstPartyDownloadDialog A callback invoked when the custom first party
+ * download dialog should be dismissed.
* @property shouldForwardToThirdParties Indicates if downloads should be forward to third party apps,
* if there are multiple apps a chooser dialog will shown.
* @property customFirstPartyDownloadDialog An optional delegate for showing a dialog for a download
@@ -146,6 +148,7 @@ class DownloadsFeature(
private val fragmentManager: FragmentManager? = null,
private val promptsStyling: PromptsStyling? = null,
private val onDownloadStartedListener: ((String) -> Unit) = {},
+ private val dismissCustomFirstPartyDownloadDialog: () -> Unit = {},
private val shouldForwardToThirdParties: () -> Boolean = { false },
private val customFirstPartyDownloadDialog: (
(
@@ -556,6 +559,7 @@ class DownloadsFeature(
internal fun dismissAllDownloadDialogs() {
findPreviousDownloadDialogFragment()?.dismiss()
findPreviousAppDownloaderDialogFragment()?.dismiss()
+ dismissCustomFirstPartyDownloadDialog.invoke()
}
private val ActivityInfo.identifier: String get() = packageName + name
diff --git a/mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadsFeatureTest.kt b/mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadsFeatureTest.kt
index ee4f52f8e7a..5dc52d87826 100644
--- a/mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadsFeatureTest.kt
+++ b/mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadsFeatureTest.kt
@@ -1460,6 +1460,64 @@ class DownloadsFeatureTest {
verify(cancelDownloadRequestUseCase).invoke(anyString(), anyString())
}
+ @Test
+ fun `GIVEN a custom download dialog is used WHEN dismissAllDownloadDialogs is called THEN the dialog is dismissed`() = runTest(testDispatcher) {
+ val dismissCustomDialog = mock<() -> Unit>()
+ val feature = DownloadsFeature(
+ testContext,
+ store,
+ useCases = DownloadsUseCases(store, mock()),
+ downloadFileUtils = FakeDownloadFileUtils(),
+ downloadManager = mock(),
+ mainDispatcher = testDispatcher,
+ dismissCustomFirstPartyDownloadDialog = dismissCustomDialog,
+ )
+
+ feature.dismissAllDownloadDialogs()
+
+ verify(dismissCustomDialog).invoke()
+ }
+
+ @Test
+ fun `GIVEN a custom download dialog is used WHEN navigating to another website THEN the dialog is dismissed`() = runTest(testDispatcher) {
+ val dismissCustomDialog = mock<() -> Unit>()
+ val downloadsUseCases = spy(DownloadsUseCases(store, mock()))
+ val cancelDownloadRequestUseCase = mock<CancelDownloadRequestUseCase>()
+ val download = DownloadState(url = "https://www.mozilla.org", sessionId = "test-tab")
+ store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download = download))
+
+ doReturn(cancelDownloadRequestUseCase).`when`(downloadsUseCases).cancelDownloadRequest
+
+ val feature = spy(
+ DownloadsFeature(
+ testContext,
+ store,
+ useCases = downloadsUseCases,
+ downloadFileUtils = FakeDownloadFileUtils(),
+ downloadManager = mock(),
+ mainDispatcher = testDispatcher,
+ dismissCustomFirstPartyDownloadDialog = dismissCustomDialog,
+ ),
+ )
+
+ doReturn(true).`when`(feature).processDownload(any(), any())
+
+ feature.start()
+ testDispatcher.scheduler.advanceUntilIdle()
+
+ store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download = download))
+ testDispatcher.scheduler.advanceUntilIdle()
+
+ grantPermissions()
+
+ val tab = createTab("https://www.firefox.com")
+ store.dispatch(TabListAction.AddTabAction(tab, select = true))
+ testDispatcher.scheduler.advanceUntilIdle()
+
+ verify(feature).dismissAllDownloadDialogs()
+ verify(dismissCustomDialog).invoke()
+ }
+
@Test
fun `ResolveInfo to DownloaderApps`() = runTest(testDispatcher) {
val spyContext = spy(testContext)
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/AddonPopupBaseFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/AddonPopupBaseFragment.kt
index 2b99f6a892a..08200d7052e 100644
--- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/AddonPopupBaseFragment.kt
+++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/addons/AddonPopupBaseFragment.kt
@@ -161,6 +161,10 @@ abstract class AddonPopupBaseFragment :
onNeedToRequestPermissions = { permissions ->
requestPermissions(permissions, REQUEST_CODE_DOWNLOAD_PERMISSIONS)
},
+ dismissCustomFirstPartyDownloadDialog = {
+ dismissRenameDialog()
+ downloadDialog?.dismiss()
+ },
customFirstPartyDownloadDialog = { currentDownloadState, _, positiveAction, negativeAction, _ ->
run {
if (canShowDownloadDialog()) {
@@ -394,6 +398,13 @@ abstract class AddonPopupBaseFragment :
return downloadDialog == null && !isRenameFragmentShowing
}
+ private fun dismissRenameDialog() {
+ val renameDialog = childFragmentManager.findFragmentByTag(
+ RenameAndChangeLocationDialogFragment.RENAME_AND_CHANGE_LOCATION_DIALOG_TAG,
+ ) as? RenameAndChangeLocationDialogFragment
+ renameDialog?.dismissAllowingStateLoss()
+ }
+
/**
* Forwards activity results to the [ActivityResultHandler] features.
*/
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
index 314b0b195a5..b2eeea2f8b5 100644
--- 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
@@ -742,6 +742,10 @@ abstract class BaseBrowserFragment :
onNeedToRequestPermissions = { permissions ->
requestPermissions(permissions, REQUEST_CODE_DOWNLOAD_PERMISSIONS)
},
+ dismissCustomFirstPartyDownloadDialog = {
+ dismissRenameDialog()
+ dismissDownloadDialogs()
+ },
customFirstPartyDownloadDialog = {
currentDownloadState,
fileNameIfAlreadyDownloaded,
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadsFeatureTest.kt b/mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadsFeatureTest.kt
index ee4f52f8e7a..5dc52d87826 100644
--- a/mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadsFeatureTest.kt
+++ b/mobile/android/android-components/components/feature/downloads/src/test/java/mozilla/components/feature/downloads/DownloadsFeatureTest.kt
@@ -1460,6 +1460,64 @@ class DownloadsFeatureTest {
verify(cancelDownloadRequestUseCase).invoke(anyString(), anyString())
}
+ @Test
+ fun `GIVEN a custom download dialog is used WHEN dismissAllDownloadDialogs is called THEN the dialog is dismissed`() = runTest(testDispatcher) {
+ val dismissCustomDialog = mock<() -> Unit>()
+ val feature = DownloadsFeature(
+ testContext,
+ store,
+ useCases = DownloadsUseCases(store, mock()),
+ downloadFileUtils = FakeDownloadFileUtils(),
+ downloadManager = mock(),
+ mainDispatcher = testDispatcher,
+ dismissCustomFirstPartyDownloadDialog = dismissCustomDialog,
+ )
+
+ feature.dismissAllDownloadDialogs()
+
+ verify(dismissCustomDialog).invoke()
+ }
+
+ @Test
+ fun `GIVEN a custom download dialog is used WHEN navigating to another website THEN the dialog is dismissed`() = runTest(testDispatcher) {
+ val dismissCustomDialog = mock<() -> Unit>()
+ val downloadsUseCases = spy(DownloadsUseCases(store, mock()))
+ val cancelDownloadRequestUseCase = mock<CancelDownloadRequestUseCase>()
+ val download = DownloadState(url = "https://www.mozilla.org", sessionId = "test-tab")
+ store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download = download))
+
+ doReturn(cancelDownloadRequestUseCase).`when`(downloadsUseCases).cancelDownloadRequest
+
+ val feature = spy(
+ DownloadsFeature(
+ testContext,
+ store,
+ useCases = downloadsUseCases,
+ downloadFileUtils = FakeDownloadFileUtils(),
+ downloadManager = mock(),
+ mainDispatcher = testDispatcher,
+ dismissCustomFirstPartyDownloadDialog = dismissCustomDialog,
+ ),
+ )
+
+ doReturn(true).`when`(feature).processDownload(any(), any())
+
+ feature.start()
+ testDispatcher.scheduler.advanceUntilIdle()
+
+ store.dispatch(ContentAction.UpdateDownloadAction("test-tab", download = download))
+ testDispatcher.scheduler.advanceUntilIdle()
+
+ grantPermissions()
+
+ val tab = createTab("https://www.firefox.com")
+ store.dispatch(TabListAction.AddTabAction(tab, select = true))
+ testDispatcher.scheduler.advanceUntilIdle()
+
+ verify(feature).dismissAllDownloadDialogs()
+ verify(dismissCustomDialog).invoke()
+ }
+
@Test
fun `ResolveInfo to DownloaderApps`() = runTest(testDispatcher) {
val spyContext = spy(testContext)
Loading diff…
References
On This Page