Low firefox Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactlow
DescriptionThe Firefox and Firefox Focus UI for the Android custom tab feature only showed the "site" that was loaded, not the full hostname. User supplied content hosted on a subdomain of a site could have been used to fool a user into thinking it was content from a different subdomain of that site.
ComponentCore
Bug ClassLogic Error
Tracker1979534
Fix commita2aff6652f29 (firefox) +113/-19
CISA KEVNot listed
CreditedMichel Le Bihan
Disclosed2025-10-14

Changed Functions

FunctionChangeNotes
if
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt
modified

Files Changed

  • mobile/android/android-components/components/support/utils/src/main/java/mozilla/components/support/utils/ColorUtils.kt
  • mobile/android/android-components/components/support/utils/src/test/java/mozilla/components/support/utils/ColorUtilsTest.kt
  • mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt
  • mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt
  • mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarIntegration.kt
  • mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabsIntegration.kt
  • mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/ThemeUtils.kt
  • mobile/android/fenix/app/src/test/java/org/mozilla/fenix/utils/ThemeUtilsTest.kt
diff --git a/mobile/android/android-components/components/support/utils/src/main/java/mozilla/components/support/utils/ColorUtils.kt b/mobile/android/android-components/components/support/utils/src/main/java/mozilla/components/support/utils/ColorUtils.kt
index 963cba64e0d..8f2911b650f 100644
--- a/mobile/android/android-components/components/support/utils/src/main/java/mozilla/components/support/utils/ColorUtils.kt
+++ b/mobile/android/android-components/components/support/utils/src/main/java/mozilla/components/support/utils/ColorUtils.kt
@@ -17,6 +17,7 @@ import androidx.compose.ui.graphics.Color as ComposeColor
  */
 private const val LIGHT_GRAY_HEX = "#66FBFBFE"
 private const val DARK_GRAY_HEX = "#6615141A"
+private const val SECONDARY_COLOR_BACKGROUND_BLEND_RATIO = 0.25f
 
 object ColorUtils {
 
@@ -24,15 +25,24 @@ object ColorUtils {
      * Get text color (white or black) that is readable on top of the provided background color.
      */
     @JvmStatic
-    fun getReadableTextColor(@ColorInt backgroundColor: Int): Int {
+    @ColorInt fun getReadableTextColor(@ColorInt backgroundColor: Int): Int {
         return if (isDark(backgroundColor)) Color.WHITE else Color.BLACK
     }
 
+    /**
+     * Get secondary color (light or dark) that is readable on top of the provided background color.
+     */
+    @JvmStatic
+    @ColorInt fun getSecondaryReadableTextColor(@ColorInt backgroundColor: Int): Int {
+        val primaryTextColor = getReadableTextColor(backgroundColor)
+        return ColorUtils.blendARGB(primaryTextColor, backgroundColor, SECONDARY_COLOR_BACKGROUND_BLEND_RATIO)
+    }
+
     /**
      * Get disabled text color (light gray or dark gray) that is readable on top of the provided background color.
      */
     @JvmStatic
-    fun getDisabledReadableTextColor(@ColorInt backgroundColor: Int): Int {
+    @ColorInt fun getDisabledReadableTextColor(@ColorInt backgroundColor: Int): Int {
         return if (isDark(backgroundColor)) {
             LIGHT_GRAY_HEX.toColorInt()
         } else {
@@ -56,7 +66,7 @@ object ColorUtils {
     }
 
     @SuppressWarnings("MagicNumber")
-    private fun grayscaleFromRGB(@ColorInt color: Int): Int {
+    @ColorInt private fun grayscaleFromRGB(@ColorInt color: Int): Int {
         val red = Color.red(color)
         val green = Color.green(color)
         val blue = Color.blue(color)
diff --git a/mobile/android/android-components/components/support/utils/src/test/java/mozilla/components/support/utils/ColorUtilsTest.kt b/mobile/android/android-components/components/support/utils/src/test/java/mozilla/components/support/utils/ColorUtilsTest.kt
index 680a08ac601..10a071a4e81 100644
--- a/mobile/android/android-components/components/support/utils/src/test/java/mozilla/components/support/utils/ColorUtilsTest.kt
+++ b/mobile/android/android-components/components/support/utils/src/test/java/mozilla/components/support/utils/ColorUtilsTest.kt
@@ -6,6 +6,7 @@ package mozilla.components.support.utils
 
 import android.graphics.Color
 import androidx.compose.ui.graphics.toArgb
+import androidx.core.graphics.ColorUtils.calculateContrast
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import mozilla.components.support.utils.ColorUtils.calculateAlphaFromPercentage
 import mozilla.components.support.utils.ColorUtils.darken
@@ -41,6 +42,15 @@ class ColorUtilsTest {
         assertEquals(Color.WHITE.toLong(), ColorUtils.getReadableTextColor(-0xa8400).toLong())
     }
 
+    @Test
+    fun `getSecondaryReadableTextColor has a good contrast with the background color`() {
+        listOf(Color.GRAY, Color.CYAN, Color.YELLOW).forEach { backgroundColor ->
+            val secondaryReadableTextColor = ColorUtils.getSecondaryReadableTextColor(backgroundColor)
+            val contrast = calculateContrast(secondaryReadableTextColor, backgroundColor)
+            assertTrue(contrast > 2.5f)
+        }
+    }
+
     @Test
     fun isDark() {
         assertTrue(ColorUtils.isDark(Color.BLACK))
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt
index 08ec4a58241..f7406745785 100644
--- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt
+++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarView.kt
@@ -21,6 +21,7 @@ import mozilla.components.browser.toolbar.BrowserToolbar
 import mozilla.components.browser.toolbar.display.DisplayToolbar
 import mozilla.components.concept.engine.utils.ABOUT_HOME_URL
 import mozilla.components.concept.toolbar.ScrollableToolbar
+import mozilla.components.feature.customtabs.getConfiguredColorSchemeParams
 import mozilla.components.support.ktx.util.URLStringUtils
 import org.mozilla.fenix.R
 import org.mozilla.fenix.components.toolbar.interactor.BrowserToolbarInteractor
@@ -30,6 +31,7 @@ import org.mozilla.fenix.ext.pixelSizeFor
 import org.mozilla.fenix.theme.ThemeManager
 import org.mozilla.fenix.utils.Settings
 import org.mozilla.fenix.utils.ToolbarPopupWindow
+import org.mozilla.fenix.utils.getAppNightMode
 import java.lang.ref.WeakReference
 
 /**
@@ -158,6 +160,11 @@ class BrowserToolbarView(
             }
 
             toolbarIntegration = if (customTabSession != null) {
+                val colorSchemeParams = customTabSession.config.getConfiguredColorSchemeParams(
+                    currentNightMode = context.resources.configuration.uiMode,
+                    preferredNightMode = settings.getAppNightMode(),
+                )
+
                 CustomTabToolbarIntegration(
                     context = this,
                     toolbar = toolbar,
@@ -166,6 +173,7 @@ class BrowserToolbarView(
                     interactor = interactor,
                     customTabId = customTabSession.id,
                     isPrivate = customTabSession.content.private,
+                    backgroundColor = colorSchemeParams?.toolbarColor,
                 )
             } else {
                 DefaultToolbarIntegration(
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt
index 2f94eecead1..b89c673e0e5 100644
--- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt
+++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt
@@ -5,6 +5,7 @@
 package org.mozilla.fenix.components.toolbar
 
 import android.content.Context
+import androidx.annotation.ColorInt
 import androidx.annotation.VisibleForTesting
 import androidx.appcompat.content.res.AppCompatResources
 import androidx.core.content.ContextCompat
@@ -22,6 +23,8 @@ import mozilla.components.feature.toolbar.ToolbarPresenter
 import mozilla.components.support.base.feature.LifecycleAwareFeature
 import mozilla.components.support.ktx.android.content.getColorFromAttr
 import mozilla.components.support.ktx.android.view.hideKeyboard
+import mozilla.components.support.utils.ColorUtils.getReadableTextColor
+import mozilla.components.support.utils.ColorUtils.getSecondaryReadableTextColor
 import mozilla.components.ui.tabcounter.TabCounterMenu
 import mozilla.telemetry.glean.private.NoExtras
 import org.mozilla.fenix.GleanMetrics.AddressToolbar
@@ -46,6 +49,7 @@ abstract class ToolbarIntegration(
     private val customTabId: String?,
     isPrivate: Boolean,
     renderStyle: ToolbarFeature.RenderStyle,
+    @param:ColorInt val backgroundColor: Int? = null,
 ) : LifecycleAwareFeature {
 
     val store = context.components.core.store
@@ -56,8 +60,16 @@ abstract class ToolbarIntegration(
         shouldDisplaySearchTerms = true,
         urlRenderConfiguration = ToolbarFeature.UrlRenderConfiguration(
             context.components.publicSuffixList,
-            context.getColorFromAttr(R.attr.textPrimary),
-            context.getColorFromAttr(R.attr.textSecondary),
+            if (backgroundColor != null && !isPrivate) {
+                getReadableTextColor(backgroundColor)
+            } else {
+                context.getColorFromAttr(R.attr.textPrimary)
+            },
+            if (backgroundColor != null && !isPrivate) {
+                getSecondaryReadableTextColor(backgroundColor)
+            } else {
+                context.getColorFromAttr(R.attr.textSecondary)
+            },
             renderStyle = renderStyle,
         ),
     )
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarIntegration.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarIntegration.kt
index 8444e329f09..383202a8ec8 100644
--- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarIntegration.kt
+++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabToolbarIntegration.kt
@@ -5,6 +5,7 @@
 package org.mozilla.fenix.customtabs
 
 import android.content.Context
+import androidx.annotation.ColorInt
 import mozilla.components.browser.toolbar.BrowserToolbar
 import mozilla.components.concept.toolbar.ScrollableToolbar
 import mozilla.components.feature.toolbar.ToolbarFeature
@@ -21,6 +22,7 @@ class CustomTabToolbarIntegration(
     interactor: BrowserToolbarInteractor,
     customTabId: String,
     isPrivate: Boolean,
+    @ColorInt backgroundColor: Int? = null,
 ) : ToolbarIntegration(
     context = context,
     toolbar = toolbar,
@@ -30,4 +32,5 @@ class CustomTabToolbarIntegration(
     customTabId = customTabId,
     isPrivate = isPrivate,
     renderStyle = ToolbarFeature.RenderStyle.ColoredDomain,
+    backgroundColor = backgroundColor,
 )
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabsIntegration.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabsIntegration.kt
index 1268556fa7d..bba065b6565 100644
--- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabsIntegration.kt
+++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabsIntegration.kt
@@ -8,9 +8,6 @@ import android.app.Activity
 import android.content.Context
 import androidx.annotation.ColorInt
 import androidx.annotation.VisibleForTesting
-import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
-import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/mobile/android/android-components/components/support/utils/src/test/java/mozilla/components/support/utils/ColorUtilsTest.kt b/mobile/android/android-components/components/support/utils/src/test/java/mozilla/components/support/utils/ColorUtilsTest.kt
index 680a08ac601..10a071a4e81 100644
--- a/mobile/android/android-components/components/support/utils/src/test/java/mozilla/components/support/utils/ColorUtilsTest.kt
+++ b/mobile/android/android-components/components/support/utils/src/test/java/mozilla/components/support/utils/ColorUtilsTest.kt
@@ -6,6 +6,7 @@ package mozilla.components.support.utils
 
 import android.graphics.Color
 import androidx.compose.ui.graphics.toArgb
+import androidx.core.graphics.ColorUtils.calculateContrast
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import mozilla.components.support.utils.ColorUtils.calculateAlphaFromPercentage
 import mozilla.components.support.utils.ColorUtils.darken
@@ -41,6 +42,15 @@ class ColorUtilsTest {
         assertEquals(Color.WHITE.toLong(), ColorUtils.getReadableTextColor(-0xa8400).toLong())
     }
 
+    @Test
+    fun `getSecondaryReadableTextColor has a good contrast with the background color`() {
+        listOf(Color.GRAY, Color.CYAN, Color.YELLOW).forEach { backgroundColor ->
+            val secondaryReadableTextColor = ColorUtils.getSecondaryReadableTextColor(backgroundColor)
+            val contrast = calculateContrast(secondaryReadableTextColor, backgroundColor)
+            assertTrue(contrast > 2.5f)
+        }
+    }
+
     @Test
     fun isDark() {
         assertTrue(ColorUtils.isDark(Color.BLACK))
diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/utils/ThemeUtilsTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/utils/ThemeUtilsTest.kt
new file mode 100644
index 00000000000..7f60d0bd15d
--- /dev/null
+++ b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/utils/ThemeUtilsTest.kt
@@ -0,0 +1,42 @@
+/* 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.utils
+
+import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
+import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
+import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import mozilla.components.support.test.mock
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.doReturn
+
+@RunWith(AndroidJUnit4::class)
+class ThemeUtilsTest {
+
+    @Test
+    fun `getAppNightMode returns MODE_NIGHT_FOLLOW_SYSTEM when shouldFollowDeviceTheme is true`() {
+        val settings = mock<Settings>()
+        doReturn(true).`when`(settings).shouldFollowDeviceTheme
+        assertEquals(MODE_NIGHT_FOLLOW_SYSTEM, settings.getAppNightMode())
+    }
+
+    @Test
+    fun `getAppNightMode returns MODE_NIGHT_NO when shouldFollowDeviceTheme is false and shouldUseLightTheme is true`() {
+        val settings = mock<Settings>()
+        doReturn(false).`when`(settings).shouldFollowDeviceTheme
+        doReturn(true).`when`(settings).shouldUseLightTheme
+        assertEquals(MODE_NIGHT_NO, settings.getAppNightMode())
+    }
+
+    @Test
+    fun `getAppNightMode returns MODE_NIGHT_YES when shouldFollowDeviceTheme is false and shouldUseLightTheme is false`() {
+        val settings = mock<Settings>()
+        doReturn(false).`when`(settings).shouldFollowDeviceTheme
+        doReturn(false).`when`(settings).shouldUseLightTheme
+        assertEquals(MODE_NIGHT_YES, settings.getAppNightMode())
+    }
+}
Loading diff…