[fenix] For https://github.com/mozilla-mobile/fenix/issues/18175 - Add a new total protection ETP cookies policy

This should only add the new option in settings based on a Nimbus experiment.
pull/600/head
Mugurell 3 years ago committed by mergify[bot]
parent 19bcd56a09
commit 733dce16d6

@ -219,7 +219,7 @@ private fun assertCustomTrackingProtectionSettings() {
private fun cookiesCheckbox() = onView(withText("Cookies"))
private fun cookiesDropDownMenuDefault() = onView(withText("Cross-site and social media trackers"))
private fun cookiesDropDownMenuDefault() = onView(withText("Isolate cross-site cookies"))
private fun trackingContentCheckbox() = onView(withText("Tracking content"))

@ -78,6 +78,7 @@ class TrackingProtectionPolicyFactory(
resources.getString(R.string.social) -> CookiePolicy.ACCEPT_NON_TRACKERS
resources.getString(R.string.unvisited) -> CookiePolicy.ACCEPT_VISITED
resources.getString(R.string.third_party) -> CookiePolicy.ACCEPT_ONLY_FIRST_PARTY
resources.getString(R.string.total_protection) -> CookiePolicy.ACCEPT_FIRST_PARTY_AND_ISOLATE_OTHERS
else -> CookiePolicy.ACCEPT_NONE
}
}

@ -0,0 +1,47 @@
/* 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.settings
import android.content.Context
import android.util.AttributeSet
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.settings
/**
* Custom [DropDownListPreference] that automatically builds the list of available options for the
* custom Enhanced Tracking Protection option depending on the current Nimbus experiments.
*/
class CustomEtpCookiesOptionsDropDownListPreference @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : DropDownListPreference(context, attrs) {
init {
with(context) {
entries = arrayOf(
getString(R.string.preference_enhanced_tracking_protection_custom_cookies_1),
getString(R.string.preference_enhanced_tracking_protection_custom_cookies_2),
getString(R.string.preference_enhanced_tracking_protection_custom_cookies_3),
getString(R.string.preference_enhanced_tracking_protection_custom_cookies_4),
)
entryValues = arrayOf(
getString(R.string.social),
getString(R.string.unvisited),
getString(R.string.third_party),
getString(R.string.all),
)
@Suppress("UNCHECKED_CAST")
if (context.settings().enabledTotalCookieProtectionSetting) {
// If the new "Total cookie protection" should be shown it must be first item.
entries = arrayOf(getString(R.string.preference_enhanced_tracking_protection_custom_cookies_5)) +
entries as Array<String>
entryValues = arrayOf(getString(R.string.total_protection)) + entryValues as Array<String>
}
}
setDefaultValue(entryValues.first())
}
}

@ -11,7 +11,7 @@ import androidx.preference.DropDownPreference
import androidx.preference.ListPreference
import org.mozilla.fenix.R
class DropDownListPreference @JvmOverloads constructor(
open class DropDownListPreference @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : DropDownPreference(context, attrs) {

@ -6,6 +6,7 @@ package org.mozilla.fenix.trackingprotection
import android.os.Bundle
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.navArgs
@ -19,11 +20,22 @@ class TrackingProtectionBlockingFragment :
private val args: TrackingProtectionBlockingFragmentArgs by navArgs()
private val settings by lazy { requireContext().settings() }
@VisibleForTesting
internal lateinit var binding: FragmentTrackingProtectionBlockingBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentTrackingProtectionBlockingBinding.bind(view)
binding = FragmentTrackingProtectionBlockingBinding.bind(view)
// Text for the updated "Total cookie protection" option should be updated as part of a staged rollout
if (requireContext().settings().enabledTotalCookieProtectionSetting) {
binding.categoryCookies.apply {
trackingProtectionCategoryTitle.text = getText(R.string.etp_cookies_title_2)
trackingProtectionCategoryItemDescription.text = getText(R.string.etp_cookies_description_2)
}
}
when (args.protectionMode) {
TrackingProtectionMode.STANDARD -> {
binding.categoryTrackingContent.isVisible = false

@ -17,12 +17,12 @@ class TrackingProtectionCategoryItem @JvmOverloads constructor(
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
init {
val binding = TrackingProtectionCategoryBinding.inflate(
LayoutInflater.from(context),
this
)
private val binding = TrackingProtectionCategoryBinding.inflate(
LayoutInflater.from(context),
this
)
init {
context.withStyledAttributes(
attrs,
R.styleable.TrackingProtectionCategory,
@ -43,4 +43,14 @@ class TrackingProtectionCategoryItem @JvmOverloads constructor(
)
}
}
/**
* The displayed title of this item.
*/
val trackingProtectionCategoryTitle = binding.trackingProtectionCategoryTitle
/**
* The displayed description of this item.
*/
val trackingProtectionCategoryItemDescription = binding.trackingProtectionCategoryItemDescription
}

@ -25,6 +25,7 @@ import org.mozilla.fenix.GleanMetrics.TrackingProtection
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.ComponentTrackingProtectionPanelBinding
import org.mozilla.fenix.ext.addUnderline
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.CROSS_SITE_TRACKING_COOKIES
import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.CRYPTOMINERS
import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.FINGERPRINTERS
@ -128,6 +129,11 @@ class TrackingProtectionPanelView(
binding.notBlockingHeader.isGone = bucketedTrackers.loadedIsEmpty()
binding.blockingHeader.isGone = bucketedTrackers.blockedIsEmpty()
if (containerView.context.settings().enabledTotalCookieProtectionSetting) {
binding.crossSiteTracking.text = containerView.context.getString(R.string.etp_cookies_title_2)
binding.crossSiteTrackingLoaded.text = containerView.context.getString(R.string.etp_cookies_title_2)
}
updateCategoryVisibility()
focusAccessibilityLastUsedCategory(state.lastAccessedCategory)
}
@ -139,7 +145,16 @@ class TrackingProtectionPanelView(
val containASmartBlockItem = bucketedTrackers.get(category, categoryBlocked).any { it.unBlockedBySmartBlock }
binding.normalMode.visibility = View.GONE
binding.detailsMode.visibility = View.VISIBLE
binding.categoryTitle.setText(category.title)
if (category == CROSS_SITE_TRACKING_COOKIES &&
containerView.context.settings().enabledTotalCookieProtectionSetting
) {
binding.categoryTitle.setText(R.string.etp_cookies_title_2)
binding.categoryDescription.setText(R.string.etp_cookies_description_2)
} else {
binding.categoryTitle.setText(category.title)
binding.categoryDescription.setText(category.description)
}
binding.smartblockDescription.isVisible = containASmartBlockItem
binding.smartblockLearnMore.isVisible = containASmartBlockItem
@ -158,7 +173,7 @@ class TrackingProtectionPanelView(
setOnClickListener { interactor.onLearnMoreClicked() }
}
}
binding.categoryDescription.setText(category.description)
binding.detailsBlockingHeader.setText(
if (categoryBlocked) {
R.string.enhanced_tracking_protection_blocked

@ -571,9 +571,16 @@ class Settings(private val appContext: Context) : PreferencesHolder {
val enabledTotalCookieProtection: Boolean
get() = FxNimbus.features.engineSettings.value().totalCookieProtectionEnabled
val enabledTotalCookieProtectionSetting: Boolean
get() = mr2022Sections[Mr2022Section.TCP_CFR] == true
val blockCookiesSelectionInCustomTrackingProtection by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_cookies_select),
appContext.getString(R.string.social)
key = appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_cookies_select),
default = if (enabledTotalCookieProtectionSetting) {
appContext.getString(R.string.total_protection)
} else {
appContext.getString(R.string.social)
}
)
val blockTrackingContentInCustomTrackingProtection by booleanPreference(

@ -3,6 +3,7 @@
- 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/. -->
<resources>
<string name="total_protection">total-protection</string>
<string name="social">social</string>
<string name="unvisited">unvisited</string>
<string name="third_party">third-party</string>
@ -10,6 +11,7 @@
<string name="private_string">private</string>
<string-array name="cookies_options_entries">
<item>@string/preference_enhanced_tracking_protection_custom_cookies_5</item>
<item>@string/preference_enhanced_tracking_protection_custom_cookies_1</item>
<item>@string/preference_enhanced_tracking_protection_custom_cookies_2</item>
<item>@string/preference_enhanced_tracking_protection_custom_cookies_3</item>
@ -17,6 +19,7 @@
</string-array>
<string-array name="cookies_options_entry_values">
<item>@string/total_protection</item>
<item>@string/social</item>
<item>@string/unvisited</item>
<item>@string/third_party</item>

@ -1275,6 +1275,8 @@
<string name="preference_enhanced_tracking_protection_custom_cookies_3">All third-party cookies (may cause websites to break)</string>
<!-- Option for enhanced tracking protection for the custom protection settings for cookies-->
<string name="preference_enhanced_tracking_protection_custom_cookies_4">All cookies (will cause websites to break)</string>
<!-- Option for enhanced tracking protection for the custom protection settings for cookies-->
<string name="preference_enhanced_tracking_protection_custom_cookies_5">Isolate cross-site cookies</string>
<!-- Preference for enhanced tracking protection for the custom protection settings for tracking content -->
<string name="preference_enhanced_tracking_protection_custom_tracking_content">Tracking content</string>
<!-- Option for enhanced tracking protection for the custom protection settings for tracking content-->
@ -1297,8 +1299,12 @@
<string name="etp_social_media_trackers_description">Limits the ability of social networks to track your browsing activity around the web.</string>
<!-- Category of trackers (cross-site tracking cookies) that can be blocked by Enhanced Tracking Protection -->
<string name="etp_cookies_title">Cross-Site Tracking Cookies</string>
<!-- Category of trackers (cross-site tracking cookies) that can be blocked by Enhanced Tracking Protection -->
<string name="etp_cookies_title_2">Cross-Site Cookies</string>
<!-- Description of cross-site tracking cookies that can be blocked by Enhanced Tracking Protection -->
<string name="etp_cookies_description">Blocks cookies that ad networks and analytics companies use to compile your browsing data across many sites.</string>
<!-- Description of cross-site tracking cookies that can be blocked by Enhanced Tracking Protection -->
<string name="etp_cookies_description_2">Total Cookie Protection isolates cookies to the site you\'re on so trackers like ad networks can\'t use them to follow you across sites.</string>
<!-- Category of trackers (cryptominers) that can be blocked by Enhanced Tracking Protection -->
<string name="etp_cryptominers_title">Cryptominers</string>
<!-- Description of cryptominers that can be blocked by Enhanced Tracking Protection -->

@ -38,7 +38,7 @@
android:key="@string/pref_key_tracking_protection_custom_cookies"
android:layout="@layout/checkbox_left_preference_etp"
android:title="@string/preference_enhanced_tracking_protection_custom_cookies" />
<org.mozilla.fenix.settings.DropDownListPreference
<org.mozilla.fenix.settings.CustomEtpCookiesOptionsDropDownListPreference
android:defaultValue="@string/social"
android:dependency="@string/pref_key_tracking_protection_custom_cookies"
android:entries="@array/cookies_options_entries"

@ -253,6 +253,30 @@ class TrackingProtectionPolicyFactoryTest {
expected.assertPolicyEquals(always, checkPrivacy = false)
}
@Test
fun `GIVEN custom policy WHEN cookie policy is total protection THEN tracking policy should have cookie policy to block cross-site cookies`() {
val expected = TrackingProtectionPolicy.select(
cookiePolicy = TrackingProtectionPolicy.CookiePolicy.ACCEPT_FIRST_PARTY_AND_ISOLATE_OTHERS,
trackingCategories = allTrackingCategories
)
val factory = TrackingProtectionPolicyFactory(
settingsForCustom(
shouldBlockCookiesInCustom = true,
blockCookiesSelection = "total-protection"
),
testContext.resources
)
val privateOnly = factory.createTrackingProtectionPolicy(normalMode = false, privateMode = true)
val normalOnly = factory.createTrackingProtectionPolicy(normalMode = true, privateMode = false)
val always = factory.createTrackingProtectionPolicy(normalMode = true, privateMode = true)
expected.assertPolicyEquals(privateOnly, checkPrivacy = false)
expected.assertPolicyEquals(normalOnly, checkPrivacy = false)
expected.assertPolicyEquals(always, checkPrivacy = false)
}
@Test
fun `GIVEN custom policy WHEN cookie policy unrecognized THEN tracking policy should have cookie policy block all`() {
val expected = TrackingProtectionPolicy.select(

@ -0,0 +1,86 @@
/* 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.settings
import android.content.Context
import androidx.preference.Preference
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class)
class CustomEtpCookiesOptionsDropDownListPreferenceTest {
@Test
fun `GIVEN total cookie protection is enabled WHEN using this preference THEN show the total cookie protection option`() {
val expectedEntries = arrayOf(
testContext.getString(R.string.preference_enhanced_tracking_protection_custom_cookies_5)
) + defaultEntries
val expectedValues = arrayOf(testContext.getString(R.string.total_protection)) + defaultValues
mockkStatic("org.mozilla.fenix.ext.ContextKt") {
every { any<Context>().settings() } returns mockk {
every { enabledTotalCookieProtectionSetting } returns true
}
val preference = CustomEtpCookiesOptionsDropDownListPreference(testContext)
assertArrayEquals(expectedEntries, preference.entries)
assertArrayEquals(expectedValues, preference.entryValues)
assertEquals(expectedValues[0], preference.getDefaultValue())
}
}
@Test
fun `GIVEN total cookie protection is disabled WHEN using this preference THEN don't show the total cookie protection option`() {
mockkStatic("org.mozilla.fenix.ext.ContextKt") {
every { any<Context>().settings() } returns mockk {
every { enabledTotalCookieProtectionSetting } returns false
}
val preference = CustomEtpCookiesOptionsDropDownListPreference(testContext)
assertArrayEquals(defaultEntries, preference.entries)
assertArrayEquals(defaultValues, preference.entryValues)
assertEquals(defaultValues[0], preference.getDefaultValue())
}
}
/**
* Use reflection to get the private member holding the default value set for this preference.
*/
private fun CustomEtpCookiesOptionsDropDownListPreference.getDefaultValue(): String {
return Preference::class.java
.getDeclaredField("mDefaultValue").let { field ->
field.isAccessible = true
return@let field.get(this) as String
}
}
private val defaultEntries = with(testContext) {
arrayOf(
getString(R.string.preference_enhanced_tracking_protection_custom_cookies_1),
getString(R.string.preference_enhanced_tracking_protection_custom_cookies_2),
getString(R.string.preference_enhanced_tracking_protection_custom_cookies_3),
getString(R.string.preference_enhanced_tracking_protection_custom_cookies_4),
)
}
private val defaultValues = with(testContext) {
arrayOf(
getString(R.string.social),
getString(R.string.unvisited),
getString(R.string.third_party),
getString(R.string.all),
)
}
}

@ -0,0 +1,79 @@
/* 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.trackingprotection
import android.content.Context
import androidx.fragment.app.FragmentActivity
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.trackingprotection.TrackingProtectionMode.CUSTOM
import org.robolectric.Robolectric
@RunWith(FenixRobolectricTestRunner::class)
class TrackingProtectionBlockingFragmentTest {
@Test
fun `GIVEN total cookie protection is enabled WHEN showing details about the protection options THEN show update details for tracking protection`() {
val expectedTitle = testContext.getString(R.string.etp_cookies_title_2)
val expectedDescription = testContext.getString(R.string.etp_cookies_description_2)
mockkStatic("org.mozilla.fenix.ext.ContextKt") {
every { any<Context>().settings() } returns mockk(relaxed = true) {
every { enabledTotalCookieProtectionSetting } returns true
}
val fragment = createFragment()
val cookiesCategory = fragment.binding.categoryCookies
assertEquals(expectedTitle, cookiesCategory.trackingProtectionCategoryTitle.text)
assertEquals(expectedDescription, cookiesCategory.trackingProtectionCategoryItemDescription.text)
}
}
@Test
fun `GIVEN total cookie protection is not enabled WHEN showing details about the protection options THEN show the default details for tracking protection`() {
val expectedTitle = testContext.getString(R.string.etp_cookies_title)
val expectedDescription = testContext.getString(R.string.etp_cookies_description)
mockkStatic("org.mozilla.fenix.ext.ContextKt") {
every { any<Context>().settings() } returns mockk(relaxed = true) {
every { enabledTotalCookieProtectionSetting } returns false
}
val fragment = createFragment()
val cookiesCategory = fragment.binding.categoryCookies
assertEquals(expectedTitle, cookiesCategory.trackingProtectionCategoryTitle.text)
assertEquals(expectedDescription, cookiesCategory.trackingProtectionCategoryItemDescription.text)
}
}
private fun createFragment(): TrackingProtectionBlockingFragment {
// Create and attach the fragment ourself instead of using "createAddedTestFragment"
// to prevent having "onResume -> showToolbar" called.
val activity = Robolectric.buildActivity(FragmentActivity::class.java)
.create()
.start()
.get()
val fragment = TrackingProtectionBlockingFragment().apply {
arguments = TrackingProtectionBlockingFragmentArgs(
protectionMode = CUSTOM
).toBundle()
}
activity.supportFragmentManager.beginTransaction()
.add(fragment, "test")
.commitNow()
return fragment
}
}

@ -4,11 +4,13 @@
package org.mozilla.fenix.trackingprotection
import android.content.Context
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.isVisible
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.verify
import mozilla.components.service.glean.testing.GleanTestRule
import mozilla.components.support.test.robolectric.testContext
@ -24,6 +26,7 @@ import org.junit.runner.RunWith
import org.mozilla.fenix.GleanMetrics.TrackingProtection
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.CROSS_SITE_TRACKING_COOKIES
import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.SOCIAL_MEDIA_TRACKERS
@ -55,12 +58,46 @@ class TrackingProtectionPanelViewTest {
@Test
fun testNormalModeUi() {
view.update(baseState.copy(mode = TrackingProtectionState.Mode.Normal))
assertFalse(view.binding.detailsMode.isVisible)
assertTrue(view.binding.normalMode.isVisible)
assertTrue(view.binding.protectionSettings.isVisible)
assertFalse(view.binding.notBlockingHeader.isVisible)
assertFalse(view.binding.blockingHeader.isVisible)
mockkStatic("org.mozilla.fenix.ext.ContextKt") {
every { any<Context>().settings() } returns mockk(relaxed = true)
view.update(baseState.copy(mode = TrackingProtectionState.Mode.Normal))
assertFalse(view.binding.detailsMode.isVisible)
assertTrue(view.binding.normalMode.isVisible)
assertTrue(view.binding.protectionSettings.isVisible)
assertFalse(view.binding.notBlockingHeader.isVisible)
assertFalse(view.binding.blockingHeader.isVisible)
}
}
@Test
fun testNormalModeUiCookiesWithTotalCookieProtectionEnabled() {
mockkStatic("org.mozilla.fenix.ext.ContextKt") {
every { any<Context>().settings() } returns mockk {
every { enabledTotalCookieProtectionSetting } returns true
}
val expectedTitle = testContext.getString(R.string.etp_cookies_title_2)
view.update(baseState.copy(mode = TrackingProtectionState.Mode.Normal))
assertEquals(expectedTitle, view.binding.crossSiteTracking.text)
assertEquals(expectedTitle, view.binding.crossSiteTrackingLoaded.text)
}
}
@Test
fun testNormalModeUiCookiesWithTotalCookieProtectionDisabled() {
mockkStatic("org.mozilla.fenix.ext.ContextKt") {
every { any<Context>().settings() } returns mockk {
every { enabledTotalCookieProtectionSetting } returns false
}
val expectedTitle = testContext.getString(R.string.etp_cookies_title)
view.update(baseState.copy(mode = TrackingProtectionState.Mode.Normal))
assertEquals(expectedTitle, view.binding.crossSiteTracking.text)
assertEquals(expectedTitle, view.binding.crossSiteTrackingLoaded.text)
}
}
@Test
@ -89,6 +126,52 @@ class TrackingProtectionPanelViewTest {
)
}
@Test
fun testPrivateModeUiCookiesWithTotalCookieProtectionEnabled() {
mockkStatic("org.mozilla.fenix.ext.ContextKt") {
every { any<Context>().settings() } returns mockk {
every { enabledTotalCookieProtectionSetting } returns true
}
val expectedTitle = testContext.getString(R.string.etp_cookies_title_2)
val expectedDescription = testContext.getString(R.string.etp_cookies_description_2)
view.update(
baseState.copy(
mode = TrackingProtectionState.Mode.Details(
selectedCategory = CROSS_SITE_TRACKING_COOKIES,
categoryBlocked = false
)
)
)
assertEquals(expectedTitle, view.binding.categoryTitle.text)
assertEquals(expectedDescription, view.binding.categoryDescription.text)
}
}
@Test
fun testPrivateModeUiCookiesWithTotalCookieProtectionDisabled() {
mockkStatic("org.mozilla.fenix.ext.ContextKt") {
every { any<Context>().settings() } returns mockk {
every { enabledTotalCookieProtectionSetting } returns false
}
val expectedTitle = testContext.getString(R.string.etp_cookies_title)
val expectedDescription = testContext.getString(R.string.etp_cookies_description)
view.update(
baseState.copy(
mode = TrackingProtectionState.Mode.Details(
selectedCategory = CROSS_SITE_TRACKING_COOKIES,
categoryBlocked = false
)
)
)
assertEquals(expectedTitle, view.binding.categoryTitle.text)
assertEquals(expectedDescription, view.binding.categoryDescription.text)
}
}
@Test
fun testProtectionSettings() {
view.binding.protectionSettings.performClick()

Loading…
Cancel
Save