Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionMultiple prompts and panels from both Firefox and the Android OS could be used to obscure the notification announcing the transition to fullscreen mode after the fix for CVE-2023-6870 in Firefox 121. This could lead to spoofing the browser UI if the sudden appearance of the prompt distracted the user from noticing the visual transition happening behind the prompt. These notifications now use the Android Toast feature. <br>*This bug only affects Firefox on Android. Other operating systems are unaffected.*
ComponentCore
Bug ClassLogic Error
Tracker1902996
Fix commit2bcbc3b41644 (firefox) +246/-257
CISA KEVNot listed
CreditedShaheen Fazim, Raphael Saniyazov, Rifa&apos;i Rejal Maynando, James Lee, P Umar Farooq, Hafiizh
Disclosed2024-09-03

Changed Functions

FunctionChangeNotes
FullScreenNotificationToast
mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/FullScreenNotification.kt
modified
FullScreenNotificationDialog
mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/FullScreenNotificationDialog.kt
modified
if
mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/GestureNavUtils.kt
modified
FullScreenNotificationTest
mobile/android/android-components/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/FullScreenNotificationTest.kt
modified

Files Changed

  • mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/FullScreenNotification.kt
  • mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/FullScreenNotificationDialog.kt
  • mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/GestureNavUtils.kt
  • mobile/android/android-components/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/FullScreenNotificationTest.kt
  • mobile/android/android-components/docs/changelog.md
  • mobile/android/fenix/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt
  • mobile/android/fenix/app/src/main/res/values/strings.xml
  • mobile/android/focus-android/app/src/main/java/org/mozilla/focus/browser/integration/FullScreenIntegration.kt
  • mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/BrowserFragment.kt
  • mobile/android/focus-android/app/src/main/res/values/strings.xml
  • mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FullScreenIntegrationTest.kt
diff --git a/mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/FullScreenNotification.kt b/mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/FullScreenNotification.kt
deleted file mode 100644
index ed20bf5234b..00000000000
--- a/mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/FullScreenNotification.kt
+++ /dev/null
@@ -1,42 +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 mozilla.components.feature.prompts.dialog
-
-import android.app.Activity
-import android.widget.Toast
-
-/**
- * UI to show a 'full screen mode' notification.
- */
-interface FullScreenNotification {
-    /**
-     * Show the notification.
-     */
-    fun show()
-}
-
-/**
- * A [Toast] to show a full screen notification message
- * @property activity The activity to show the toast on.
- * @property gestureNavString The string to show when in gesture navigation mode.
- * @property backButtonString The string to show when in 3-button navigation mode.
- * @property gestureNavUtils Utility to detect gesture navigation.
- */
-class FullScreenNotificationToast(
-    private val activity: Activity,
-    private val gestureNavString: String,
-    private val backButtonString: String,
-    private val gestureNavUtils: GestureNavUtils,
-) : FullScreenNotification {
-    override fun show() {
-        val toastString =
-            if (gestureNavUtils.isInGestureNavigationMode(activity.window)) {
-                gestureNavString
-            } else {
-                backButtonString
-            }
-        Toast.makeText(activity, toastString, Toast.LENGTH_LONG).show()
-    }
-}
diff --git a/mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/FullScreenNotificationDialog.kt b/mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/FullScreenNotificationDialog.kt
new file mode 100644
index 00000000000..3c403f3734e
--- /dev/null
+++ b/mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/FullScreenNotificationDialog.kt
@@ -0,0 +1,70 @@
+/* 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 mozilla.components.feature.prompts.dialog
+
+import android.app.Dialog
+import android.os.Bundle
+import android.view.Gravity
+import android.view.WindowManager
+import androidx.annotation.LayoutRes
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentManager
+import androidx.lifecycle.lifecycleScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+private const val TAG = "mozac_feature_prompts_full_screen_notification_dialog"
+private const val SNACKBAR_DURATION_LONG_MS = 3000L
+
+/**
+ * UI to show a 'full screen mode' notification.
+ */
+interface FullScreenNotification {
+    /**
+     * Show the notification.
+     *
+     * @param fragmentManager the [FragmentManager] to add this notification to.
+     */
+    fun show(fragmentManager: FragmentManager)
+}
+
+/**
+ * [DialogFragment] that is configured to match the style and behaviour of a Snackbar.
+ *
+ * @property layout the layout to use for the dialog.
+ */
+class FullScreenNotificationDialog(@LayoutRes val layout: Int) :
+    DialogFragment(), FullScreenNotification {
+    override fun show(fragmentManager: FragmentManager) = super.show(fragmentManager, TAG)
+
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = requireActivity().let {
+        val view = layoutInflater.inflate(layout, null)
+        AlertDialog.Builder(it).setView(view).create()
+    }
+
+    override fun onStart() {
+        super.onStart()
+
+        dialog?.let { dialog ->
+            dialog.window?.let { window ->
+                // Prevent any user input from key or other button events to it.
+                window.setFlags(
+                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+                )
+
+                window.setGravity(Gravity.BOTTOM)
+                window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+            }
+        }
+
+        // Attempt to automatically dismiss the dialog after the given duration.
+        lifecycleScope.launch {
+            delay(SNACKBAR_DURATION_LONG_MS)
+            dialog?.dismiss()
+        }
+    }
+}
diff --git a/mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/GestureNavUtils.kt b/mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/GestureNavUtils.kt
deleted file mode 100644
index 203e0c45dbd..00000000000
--- a/mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/GestureNavUtils.kt
+++ /dev/null
@@ -1,42 +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 mozilla.components.feature.prompts.dialog
-
-import android.os.Build
-import android.view.Window
-import androidx.core.view.WindowInsetsCompat
-
-/**
- * A place to keep utility functions for gesture navigation.
- */
-object GestureNavUtils {
-    /**
-     * Find out if the device supports gesture navigation and has gestures enabled.
-     *
-     * Gesture nav support was added in Android Q, but on recent devices it is possible to
-     * switch navigation mode between gesture nav and 3-button navigation.
-     * The system gesture inset needs to be greater than 0 at the left edge of the screen
-     * for the back gesture to be picked up, so this seems to be the most reliable way of
-     * detecting gesture navigation use.
-     *
-     * * See also: [Gesture Navigation: handling visual overlaps (II)](https://medium.com/androiddevelopers/gesture-navigation-handling-visual-overlaps-4aed565c134c)
-     * * See also: [How to detect full screen gesture mode](https://stackoverflow.com/a/70514883)
-     *
-     * @param window The containing [Window] of the current `Activity`.
-     * @return true if the device supports gesture navigation and gestures are enabled.
-     */
-    fun isInGestureNavigationMode(window: Window): Boolean =
-        (
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-                WindowInsetsCompat
-                    .toWindowInsetsCompat(
-                        window.decorView.rootWindowInsets,
-                    ).getInsets(WindowInsetsCompat.Type.systemGestures())
-                    .left
-            } else {
-                0
-            }
-            ) > 0
-}
diff --git a/mobile/android/android-components/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/FullScreenNotificationTest.kt b/mobile/android/android-components/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/FullScreenNotificationTest.kt
deleted file mode 100644
index a388ce24311..00000000000
--- a/mobile/android/android-components/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/FullScreenNotificationTest.kt
+++ /dev/null
@@ -1,67 +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 mozilla.components.feature.prompts.dialog
-
-import android.app.Activity
-import androidx.test.core.app.ActivityScenario
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import mozilla.components.support.test.any
-import mozilla.components.support.test.whenever
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-import org.robolectric.Robolectric
-import org.robolectric.shadows.ShadowToast
-
-@RunWith(AndroidJUnit4::class)
-class FullScreenNotificationTest {
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/mobile/android/android-components/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/FullScreenNotificationTest.kt b/mobile/android/android-components/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/FullScreenNotificationTest.kt
deleted file mode 100644
index a388ce24311..00000000000
--- a/mobile/android/android-components/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/FullScreenNotificationTest.kt
+++ /dev/null
@@ -1,67 +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 mozilla.components.feature.prompts.dialog
-
-import android.app.Activity
-import androidx.test.core.app.ActivityScenario
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import mozilla.components.support.test.any
-import mozilla.components.support.test.whenever
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-import org.robolectric.Robolectric
-import org.robolectric.shadows.ShadowToast
-
-@RunWith(AndroidJUnit4::class)
-class FullScreenNotificationTest {
-
-    @Mock
-    lateinit var mockGestureNavUtils: GestureNavUtils
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.openMocks(this)
-        val activity = Robolectric.buildActivity(EmptyActivity::class.java)
-        activity.create()
-    }
-
-    @Test
-    fun `when in 3 button navigation mode, full screen notification shows exit instructions for back button`() {
-        whenever(mockGestureNavUtils.isInGestureNavigationMode(any())).thenReturn(false)
-        ActivityScenario.launch(EmptyActivity::class.java).use { scenario ->
-            scenario.onActivity { activity: EmptyActivity ->
-                FullScreenNotificationToast(
-                    activity,
-                    "gesture",
-                    "button",
-                    mockGestureNavUtils,
-                ).show()
-                assertTrue(ShadowToast.showedToast("button"))
-            }
-        }
-    }
-
-    @Test
-    fun `when in gesture navigation mode, full screen notification shows exit instructions for gesture nav`() {
-        whenever(mockGestureNavUtils.isInGestureNavigationMode(any())).thenReturn(true)
-        ActivityScenario.launch(EmptyActivity::class.java).use { scenario ->
-            scenario.onActivity { activity: EmptyActivity ->
-                FullScreenNotificationToast(
-                    activity,
-                    "gesture",
-                    "button",
-                    mockGestureNavUtils,
-                ).show()
-                assertTrue(ShadowToast.showedToast("gesture"))
-            }
-        }
-    }
-}
-
-internal class EmptyActivity : Activity()
diff --git a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FullScreenIntegrationTest.kt b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FullScreenIntegrationTest.kt
index 64e4a865ad5..69b7a031b4b 100644
--- a/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FullScreenIntegrationTest.kt
+++ b/mobile/android/focus-android/app/src/test/java/org/mozilla/focus/browser/integration/FullScreenIntegrationTest.kt
@@ -22,7 +22,6 @@ import mozilla.components.support.test.robolectric.testContext
 import org.junit.Test
 import org.junit.jupiter.api.Assertions.assertEquals
 import org.junit.runner.RunWith
-import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.never
@@ -43,7 +42,17 @@ internal class FullScreenIntegrationTest {
     @Test
     fun `WHEN the integration is started THEN start FullScreenFeature`() {
         val feature: FullScreenFeature = mock()
-        val integration = createFullScreenIntegration().apply {
+        val integration = FullScreenIntegration(
+            mock(),
+            mock(),
+            null,
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+        ).apply {
             this.feature = feature
         }
 
@@ -55,7 +64,17 @@ internal class FullScreenIntegrationTest {
     @Test
     fun `WHEN the integration is stopped THEN stop FullScreenFeature`() {
         val feature: FullScreenFeature = mock()
-        val integration = createFullScreenIntegration().apply {
+        val integration = FullScreenIntegration(
+            mock(),
+            mock(),
+            null,
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+        ).apply {
             this.feature = feature
         }
 
@@ -67,7 +86,17 @@ internal class FullScreenIntegrationTest {
     @Test
     fun `WHEN back is pressed THEN send this to the feature`() {
         val feature: FullScreenFeature = mock()
-        val integration = createFullScreenIntegration().apply {
+        val integration = FullScreenIntegration(
+            mock(),
+            mock(),
+            null,
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+        ).apply {
             this.feature = feature
         }
 
@@ -83,7 +112,17 @@ internal class FullScreenIntegrationTest {
         val activity: Activity = mock()
         doReturn(activityWindow).`when`(activity).window
         doReturn(windowAttributes).`when`(activityWindow).attributes
-        val integration = createFullScreenIntegration(activity = activity)
+        val integration = FullScreenIntegration(
+            activity,
+            mock(),
+            null,
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+        )
 
         integration.viewportFitChanged(33)
 
@@ -102,7 +141,17 @@ internal class FullScreenIntegrationTest {
         doReturn(decorView).`when`(activityWindow).decorView
         doReturn(layoutParams).`when`(activityWindow).attributes
 
-        val integration = createFullScreenIntegration(activity = activity)
+        val integration = FullScreenIntegration(
+            activity,
+            mock(),
+            null,
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+        )
 
         integration.switchToImmersiveMode()
 
@@ -126,7 +175,17 @@ internal class FullScreenIntegrationTest {
         doReturn(layoutParams).`when`(activityWindow).attributes
         doReturn(insetsController).`when`(activityWindow).insetsController
 
-        val integration = createFullScreenIntegration(activity = activity)
+        val integration = FullScreenIntegration(
+            activity,
+            mock(),
+            null,
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+        )
 
         integration.switchToImmersiveMode()
 
@@ -155,7 +214,17 @@ internal class FullScreenIntegrationTest {
         doReturn(activityWindow).`when`(activity).window
         doReturn(decorView).`when`(activityWindow).decorView
         doReturn(layoutParams).`when`(activityWindow).attributes
-        val integration = createFullScreenIntegration(activity = activity)
+        val integration = FullScreenIntegration(
+            activity,
+            mock(),
+            null,
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+        )
 
         integration.exitImmersiveMode()
         // Hiding the system bar hides the status and navigation bars.
@@ -178,7 +247,17 @@ internal class FullScreenIntegrationTest {
         doReturn(layoutParams).`when`(activityWindow).attributes
         doReturn(insetsController).`when`(activityWindow).insetsController
 
-        val integration = createFullScreenIntegration(activity = activity)
+        val integration = FullScreenIntegration(
+            activity,
+            mock(),
+            null,
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+            mock(),
+        )
 
         integration.exitImmersiveMode()
 
@@ -199,10 +278,16 @@ internal class FullScreenIntegrationTest {
         doReturn(mock<View>()).`when`(engineView).asView()
         val settings: Settings = mock()
         doReturn(true).`when`(settings).isAccessibilityEnabled()
-        val integration = createFullScreenIntegration(
-            settings = settings,
-            toolbar = toolbar,
-            engineView = engineView,
+        val integration = FullScreenIntegration(
+            mock(),
+            mock(),
+            null,
+            mock(),
+            settings,
+            toolbar,
+            mock(),
+            engineView,
+            mock(),
         )
 
         integration.enterBrowserFullscreen()
@@ -219,10 +304,16 @@ internal class FullScreenIntegrationTest {
         doReturn(mock<View>()).`when`(engineView).asView()
         val settings: Settings = mock()
         doReturn(false).`when`(settings).isAccessibilityEnabled()
-        val integration = createFullScreenIntegration(
-            settings = settings,
-            toolbar = toolbar,
-            engineView = engineView,
+        val integration = FullScreenIntegration(
+            mock(),
+            mock(),
+            null,
+            mock(),
+            settings,
+            toolbar,
+            mock(),
+            engineView,
+            mock(),
         )
 
         integration.enterBrowserFullscreen()
@@ -244,13 +335,17 @@ internal class FullScreenIntegrationTest {
         val resources: Resources = mock()
         val activity: Activity = mock()
         doReturn(resources).`when`(activity).resources
-        val integration =
-            createFullScreenIntegration(
-                activity = activity,
-                settings = settings,
-                toolbar = toolbar,
-                engineView = engineView,
-            )
+        val integration = FullScreenIntegration(
+            activity,
+            mock(),
+            null,
+            mock(),
+            settings,
+            toolbar,
+            mock(),
+            engineView,
+            mock(),
... (truncated)
Loading diff…