diff --git a/app/metrics.yaml b/app/metrics.yaml
index df4ea7bd3a..943076505c 100644
--- a/app/metrics.yaml
+++ b/app/metrics.yaml
@@ -8530,7 +8530,38 @@ cookie_banners:
metadata:
tags:
- Privacy&Security
-
+ cfr_shown:
+ type: event
+ description: The cookie banner cfr has been shown
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1859393
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1859393#c2
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ metadata:
+ tags:
+ - Privacy&Security
+ cfr_dismissal:
+ type: event
+ description: |
+ The cookie banners CFR was dismissed by the user by interacting
+ with the outside of the popup
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1859393
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1859393#c2
+ data_sensitivity:
+ - interaction
+ notification_emails:
+ - android-probes@mozilla.com
+ expires: never
+ metadata:
+ tags:
+ - Privacy&Security
site_permissions:
prompt_shown:
type: event
diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarCFRPresenter.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarCFRPresenter.kt
index 90a5d9d3ab..aa691c1ffd 100644
--- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarCFRPresenter.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarCFRPresenter.kt
@@ -8,9 +8,17 @@ import android.content.Context
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.Icon
import androidx.compose.material.Text
+import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
@@ -34,8 +42,11 @@ import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.compose.cfr.CFRPopup
import mozilla.components.compose.cfr.CFRPopup.PopupAlignment.INDICATOR_CENTERED_IN_ANCHOR
import mozilla.components.compose.cfr.CFRPopupProperties
+import mozilla.components.concept.engine.EngineSession.CookieBannerHandlingStatus
import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.service.glean.private.NoExtras
+import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
+import org.mozilla.fenix.GleanMetrics.CookieBanners
import org.mozilla.fenix.GleanMetrics.Shopping
import org.mozilla.fenix.GleanMetrics.TrackingProtection
import org.mozilla.fenix.R
@@ -109,6 +120,24 @@ class BrowserToolbarCFRPresenter(
}
}
}
+ ToolbarCFR.COOKIE_BANNERS -> {
+ scope = browserStore.flowScoped { flow ->
+ flow.mapNotNull { it.findCustomTabOrSelectedTab(sessionId) }
+ .ifAnyChanged { tab ->
+ arrayOf(
+ tab.cookieBanner,
+ )
+ }
+ .filter {
+ it.content.private && it.cookieBanner == CookieBannerHandlingStatus.HANDLED
+ }
+ .collect {
+ scope?.cancel()
+ settings.shouldShowCookieBannersCFR = false
+ showCookieBannersCFR()
+ }
+ }
+ }
ToolbarCFR.SHOPPING, ToolbarCFR.SHOPPING_OPTED_IN -> {
scope = browserStore.flowScoped { flow ->
@@ -190,6 +219,10 @@ class BrowserToolbarCFRPresenter(
settings.openTabsCount >= CFR_MINIMUM_NUMBER_OPENED_TABS
) -> ToolbarCFR.TCP
+ isPrivate && settings.shouldShowCookieBannersCFR && settings.shouldUseCookieBannerPrivateMode -> {
+ ToolbarCFR.COOKIE_BANNERS
+ }
+
shoppingExperienceFeature.isEnabled &&
settings.shouldShowReviewQualityCheckCFR -> whichShoppingCFR()
@@ -325,6 +358,64 @@ class BrowserToolbarCFRPresenter(
}
}
+ @VisibleForTesting
+ @Suppress("LongMethod")
+ internal fun showCookieBannersCFR() {
+ CFRPopup(
+ anchor = toolbar.findViewById(
+ R.id.mozac_browser_toolbar_security_indicator,
+ ),
+ properties = CFRPopupProperties(
+ popupAlignment = INDICATOR_CENTERED_IN_ANCHOR,
+ popupBodyColors = listOf(
+ getColor(context, R.color.fx_mobile_layer_color_gradient_end),
+ getColor(context, R.color.fx_mobile_layer_color_gradient_start),
+ ),
+ popupVerticalOffset = CFR_TO_ANCHOR_VERTICAL_PADDING.dp,
+ dismissButtonColor = getColor(context, R.color.fx_mobile_icon_color_oncolor),
+ indicatorDirection = if (settings.toolbarPosition == ToolbarPosition.TOP) {
+ CFRPopup.IndicatorDirection.UP
+ } else {
+ CFRPopup.IndicatorDirection.DOWN
+ },
+ ),
+ onDismiss = {
+ CookieBanners.cfrDismissal.record(NoExtras())
+ },
+ text = {
+ FirefoxTheme {
+ Column {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_cookies_disabled),
+ contentDescription = null,
+ tint = FirefoxTheme.colors.iconPrimary,
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ Text(
+ text = context.getString(R.string.cookie_banner_cfr_title),
+ color = FirefoxTheme.colors.textOnColorPrimary,
+ style = FirefoxTheme.typography.subtitle2,
+ )
+ }
+ Text(
+ text = context.getString(R.string.cookie_banner_cfr_message),
+ color = FirefoxTheme.colors.textOnColorPrimary,
+ style = FirefoxTheme.typography.body2,
+ modifier = Modifier.padding(top = 2.dp),
+ )
+ }
+ }
+ },
+ ).run {
+ popup = this
+ show()
+ CookieBanners.cfrShown.record(NoExtras())
+ }
+ }
+
@VisibleForTesting
internal fun showShoppingCFR(shouldShowOptedInCFR: Boolean) {
CFRPopup(
@@ -395,5 +486,5 @@ class BrowserToolbarCFRPresenter(
* The CFR to be shown in the toolbar.
*/
private enum class ToolbarCFR {
- TCP, SHOPPING, SHOPPING_OPTED_IN, ERASE, NONE
+ TCP, SHOPPING, SHOPPING_OPTED_IN, ERASE, COOKIE_BANNERS, NONE
}
diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
index dc38009fd9..1f5aeba0f4 100644
--- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
+++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt
@@ -778,6 +778,15 @@ class Settings(private val appContext: Context) : PreferencesHolder {
default = { feltPrivateBrowsingEnabled },
)
+ /**
+ * Indicates if the cookie banners CRF should be shown.
+ */
+ var shouldShowCookieBannersCFR by lazyFeatureFlagPreference(
+ appContext.getPreferenceKey(R.string.pref_key_should_show_cookie_banners_action_popup),
+ featureFlag = true,
+ default = { shouldShowCookieBannerUI },
+ )
+
val blockCookiesSelectionInCustomTrackingProtection by stringPreference(
key = appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_cookies_select),
default = if (enabledTotalCookieProtection) {
diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml
index eaa6c3fc62..d1debd6b16 100644
--- a/app/src/main/res/values/preference_keys.xml
+++ b/app/src/main/res/values/preference_keys.xml
@@ -275,6 +275,8 @@
pref_key_should_show_total_cookie_protection_popup
pref_key_should_show_erase_action_popup
+
+ pref_key_should_show_cookie_banners_action_popup
pref_key_debug_settings
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2a89ca1d84..94a0a16eb0 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -498,6 +498,10 @@
You’ll see fewer cookie requests
Allow
+
+ Firefox just refused cookies for you
+
+ Less distractions, less cookies tracking you on this site.
Automatically attempts to connect to sites using HTTPS encryption protocol for increased security.
diff --git a/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserToolbarCFRPresenterTest.kt b/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserToolbarCFRPresenterTest.kt
index 2268dd9a0a..c5bfe6124e 100644
--- a/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserToolbarCFRPresenterTest.kt
+++ b/app/src/test/java/org/mozilla/fenix/components/toolbar/BrowserToolbarCFRPresenterTest.kt
@@ -15,6 +15,7 @@ import io.mockk.verify
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import mozilla.components.browser.state.action.ContentAction
+import mozilla.components.browser.state.action.CookieBannerAction
import mozilla.components.browser.state.action.TabListAction
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.CustomTabSessionState
@@ -23,6 +24,7 @@ import mozilla.components.browser.state.state.createCustomTab
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.browser.toolbar.BrowserToolbar
+import mozilla.components.concept.engine.EngineSession
import mozilla.components.support.test.ext.joinBlocking
import mozilla.components.support.test.robolectric.testContext
import mozilla.components.support.test.rule.MainCoroutineRule
@@ -69,6 +71,40 @@ class BrowserToolbarCFRPresenterTest {
verify { presenter.showTcpCfr() }
}
+ @Test
+ fun `GIVEN the cookie banners handling CFR should be shown for a custom tab WHEN the custom tab is fully loaded THEN the TCP CFR is shown`() {
+ val privateTab = createTab(url = "", private = true)
+ val browserStore = createBrowserStore(tab = privateTab, selectedTabId = privateTab.id)
+ val settings: Settings = mockk(relaxed = true) {
+ every { shouldShowTotalCookieProtectionCFR } returns false
+ every { shouldShowReviewQualityCheckCFR } returns false
+ every { reviewQualityCheckOptInTimeInMillis } returns System.currentTimeMillis()
+ every { shouldShowEraseActionCFR } returns false
+ every { shouldShowCookieBannersCFR } returns true
+ every { shouldUseCookieBannerPrivateMode } returns true
+ every { reviewQualityCheckCfrDisplayTimeInMillis } returns 0L
+ }
+ val presenter = createPresenter(
+ isPrivate = true,
+ browserStore = browserStore,
+ settings = settings,
+ )
+
+ presenter.start()
+
+ assertNotNull(presenter.scope)
+
+ browserStore.dispatch(
+ CookieBannerAction.UpdateStatusAction(
+ privateTab.id,
+ EngineSession.CookieBannerHandlingStatus.HANDLED,
+ ),
+ ).joinBlocking()
+
+ verify { presenter.showCookieBannersCFR() }
+ verify { settings.shouldShowCookieBannersCFR = false }
+ }
+
@Test
fun `GIVEN the TCP CFR should be shown WHEN the current normal tab is fully loaded THEN the TCP CFR is shown`() {
val normalTab = createTab(url = "", private = false)
@@ -457,6 +493,7 @@ class BrowserToolbarCFRPresenterTest {
every { shouldShowTotalCookieProtectionCFR } returns true
every { shouldShowEraseActionCFR } returns true
every { openTabsCount } returns 5
+ every { shouldShowCookieBannersCFR } returns true
every { shouldShowReviewQualityCheckCFR } returns true
},
toolbar: BrowserToolbar = mockk {