diff --git a/app/src/main/java/org/mozilla/fenix/AppRequestInterceptor.kt b/app/src/main/java/org/mozilla/fenix/AppRequestInterceptor.kt index 74de1f35a..be4a4b336 100644 --- a/app/src/main/java/org/mozilla/fenix/AppRequestInterceptor.kt +++ b/app/src/main/java/org/mozilla/fenix/AppRequestInterceptor.kt @@ -47,7 +47,8 @@ class AppRequestInterceptor(private val context: Context) : RequestInterceptor { session.disableTrackingProtection() } else { val core = context.components.core - val policy = core.createTrackingProtectionPolicy(normalMode = true) + val policy = core.trackingProtectionPolicyFactory + .createTrackingProtectionPolicy(normalMode = true) core.engine.settings.trackingProtectionPolicy = policy session.enableTrackingProtection(policy) } diff --git a/app/src/main/java/org/mozilla/fenix/components/Core.kt b/app/src/main/java/org/mozilla/fenix/components/Core.kt index 4020585d9..c67d35ef9 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -25,9 +25,6 @@ import mozilla.components.browser.storage.sync.PlacesBookmarksStorage import mozilla.components.browser.storage.sync.PlacesHistoryStorage import mozilla.components.concept.engine.DefaultSettings import mozilla.components.concept.engine.Engine -import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy -import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.CookiePolicy -import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.TrackingCategory import mozilla.components.concept.engine.mediaquery.PreferredColorScheme import mozilla.components.concept.fetch.Client import mozilla.components.feature.customtabs.store.CustomTabsServiceStore @@ -67,7 +64,7 @@ class Core(private val context: Context) { requestInterceptor = AppRequestInterceptor(context), remoteDebuggingEnabled = context.settings().isRemoteDebuggingEnabled, testingModeEnabled = false, - trackingProtectionPolicy = createTrackingProtectionPolicy(), + trackingProtectionPolicy = trackingProtectionPolicyFactory.createTrackingProtectionPolicy(), historyTrackingDelegate = HistoryDelegate(historyStorage), preferredColorScheme = getPreferredColorScheme(), automaticFontSizeAdjustment = context.settings().shouldUseAutoSize, @@ -252,74 +249,7 @@ class Core(private val context: Context) { getSecureAbove22Preferences().putString(PASSWORDS_KEY, it) } - /** - * Constructs a [TrackingProtectionPolicy] based on current preferences. - * - * @param normalMode whether or not tracking protection should be enabled - * in normal browsing mode, defaults to the current preference value. - * @param privateMode whether or not tracking protection should be enabled - * in private browsing mode, default to the current preference value. - * @return the constructed tracking protection policy based on preferences. - */ - @Suppress("ComplexMethod") - fun createTrackingProtectionPolicy( - normalMode: Boolean = context.settings().shouldUseTrackingProtection, - privateMode: Boolean = true - ): TrackingProtectionPolicy { - val trackingProtectionPolicy = - when { - context.settings().useStrictTrackingProtection -> TrackingProtectionPolicy.strict() - context.settings().useCustomTrackingProtection -> return TrackingProtectionPolicy.select( - cookiePolicy = geCustomCookiePolicy(), - trackingCategories = getCustomTrackingCategories() - ).apply { - if (context.settings().blockTrackingContentSelectionInCustomTrackingProtection == "private") { - forPrivateSessionsOnly() - } - } - else -> TrackingProtectionPolicy.recommended() - } - - return when { - normalMode && privateMode -> trackingProtectionPolicy - normalMode && !privateMode -> trackingProtectionPolicy.forRegularSessionsOnly() - !normalMode && privateMode -> trackingProtectionPolicy.forPrivateSessionsOnly() - else -> TrackingProtectionPolicy.none() - } - } - - private fun geCustomCookiePolicy(): CookiePolicy { - return when (context.settings().blockCookiesSelectionInCustomTrackingProtection) { - "all" -> CookiePolicy.ACCEPT_NONE - "social" -> CookiePolicy.ACCEPT_NON_TRACKERS - "unvisited" -> CookiePolicy.ACCEPT_VISITED - "third-party" -> CookiePolicy.ACCEPT_ONLY_FIRST_PARTY - else -> CookiePolicy.ACCEPT_NONE - } - } - - private fun getCustomTrackingCategories(): Array { - val categories = arrayListOf( - TrackingCategory.AD, - TrackingCategory.ANALYTICS, - TrackingCategory.SOCIAL, - TrackingCategory.MOZILLA_SOCIAL - ) - - if (context.settings().blockTrackingContentInCustomTrackingProtection) { - categories.add(TrackingCategory.SCRIPTS_AND_SUB_RESOURCES) - } - - if (context.settings().blockFingerprintersInCustomTrackingProtection) { - categories.add(TrackingCategory.FINGERPRINTING) - } - - if (context.settings().blockCryptominersInCustomTrackingProtection) { - categories.add(TrackingCategory.CRYPTOMINING) - } - - return categories.toTypedArray() - } + val trackingProtectionPolicyFactory = TrackingProtectionPolicyFactory(context.settings()) /** * Sets Preferred Color scheme based on Dark/Light Theme Settings or Current Configuration diff --git a/app/src/main/java/org/mozilla/fenix/components/TrackingProtectionPolicyFactory.kt b/app/src/main/java/org/mozilla/fenix/components/TrackingProtectionPolicyFactory.kt new file mode 100644 index 000000000..16a68787d --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/components/TrackingProtectionPolicyFactory.kt @@ -0,0 +1,93 @@ +/* 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.components + +import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy +import org.mozilla.fenix.utils.Settings + +/** + * Handles the logic behind creating new [TrackingProtectionPolicy]s. + */ +class TrackingProtectionPolicyFactory(private val settings: Settings) { + + /** + * Constructs a [TrackingProtectionPolicy] based on current preferences. + * + * @param normalMode whether or not tracking protection should be enabled + * in normal browsing mode, defaults to the current preference value. + * @param privateMode whether or not tracking protection should be enabled + * in private browsing mode, default to the current preference value. + * @return the constructed tracking protection policy based on preferences. + */ + @Suppress("ComplexMethod") + fun createTrackingProtectionPolicy( + normalMode: Boolean = settings.shouldUseTrackingProtection, + privateMode: Boolean = true + ): TrackingProtectionPolicy { + val trackingProtectionPolicy = + when { + settings.useStrictTrackingProtection -> TrackingProtectionPolicy.strict() + settings.useCustomTrackingProtection -> return createCustomTrackingProtectionPolicy() + else -> TrackingProtectionPolicy.recommended() + } + + return when { + normalMode && privateMode -> trackingProtectionPolicy + normalMode && !privateMode -> trackingProtectionPolicy.forRegularSessionsOnly() + !normalMode && privateMode -> trackingProtectionPolicy.forPrivateSessionsOnly() + else -> TrackingProtectionPolicy.none() + } + } + + private fun createCustomTrackingProtectionPolicy(): TrackingProtectionPolicy { + return TrackingProtectionPolicy.select( + cookiePolicy = getCustomCookiePolicy(), + trackingCategories = getCustomTrackingCategories() + ).let { + if (settings.blockTrackingContentSelectionInCustomTrackingProtection == "private") { + it.forPrivateSessionsOnly() + } else { + it + } + } + } + + private fun getCustomCookiePolicy(): TrackingProtectionPolicy.CookiePolicy { + return if (!settings.blockCookiesInCustomTrackingProtection) { + TrackingProtectionPolicy.CookiePolicy.ACCEPT_ALL + } else { + when (settings.blockCookiesSelectionInCustomTrackingProtection) { + "all" -> TrackingProtectionPolicy.CookiePolicy.ACCEPT_NONE + "social" -> TrackingProtectionPolicy.CookiePolicy.ACCEPT_NON_TRACKERS + "unvisited" -> TrackingProtectionPolicy.CookiePolicy.ACCEPT_VISITED + "third-party" -> TrackingProtectionPolicy.CookiePolicy.ACCEPT_ONLY_FIRST_PARTY + else -> TrackingProtectionPolicy.CookiePolicy.ACCEPT_NONE + } + } + } + + private fun getCustomTrackingCategories(): Array { + val categories = arrayListOf( + TrackingProtectionPolicy.TrackingCategory.AD, + TrackingProtectionPolicy.TrackingCategory.ANALYTICS, + TrackingProtectionPolicy.TrackingCategory.SOCIAL, + TrackingProtectionPolicy.TrackingCategory.MOZILLA_SOCIAL + ) + + if (settings.blockTrackingContentInCustomTrackingProtection) { + categories.add(TrackingProtectionPolicy.TrackingCategory.SCRIPTS_AND_SUB_RESOURCES) + } + + if (settings.blockFingerprintersInCustomTrackingProtection) { + categories.add(TrackingProtectionPolicy.TrackingCategory.FINGERPRINTING) + } + + if (settings.blockCryptominersInCustomTrackingProtection) { + categories.add(TrackingProtectionPolicy.TrackingCategory.CRYPTOMINING) + } + + return categories.toTypedArray() + } +} diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingTrackingProtectionViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingTrackingProtectionViewHolder.kt index bdfc6a474..7f74018ca 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingTrackingProtectionViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingTrackingProtectionViewHolder.kt @@ -105,7 +105,8 @@ class OnboardingTrackingProtectionViewHolder(view: View) : RecyclerView.ViewHold private fun updateTrackingProtectionSetting(enabled: Boolean) { itemView.context.settings().shouldUseTrackingProtection = enabled with(itemView.context.components) { - val policy = core.createTrackingProtectionPolicy(enabled) + val policy = core.trackingProtectionPolicyFactory + .createTrackingProtectionPolicy(enabled) useCases.settingsUseCases.updateTrackingProtection.invoke(policy) useCases.sessionUseCases.reload.invoke() } @@ -113,7 +114,8 @@ class OnboardingTrackingProtectionViewHolder(view: View) : RecyclerView.ViewHold private fun updateTrackingProtectionPolicy() { itemView.context?.components?.let { - val policy = it.core.createTrackingProtectionPolicy() + val policy = it.core.trackingProtectionPolicyFactory + .createTrackingProtectionPolicy() it.useCases.settingsUseCases.updateTrackingProtection.invoke(policy) it.useCases.sessionUseCases.reload.invoke() } diff --git a/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt index b5edd0758..063e76381 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt @@ -23,8 +23,8 @@ import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.showToolbar /** - * Displays the toggle for tracking protection and a button to open - * the tracking protection [org.mozilla.fenix.exceptions.ExceptionsFragment]. + * Displays the toggle for tracking protection, options for tracking protection policy and a button + * to open info about the tracking protection [org.mozilla.fenix.exceptions.ExceptionsFragment]. */ class TrackingProtectionFragment : PreferenceFragmentCompat() { @@ -66,7 +66,8 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() { preference.context.settings().shouldUseTrackingProtection = trackingProtectionOn with(preference.context.components) { - val policy = core.createTrackingProtectionPolicy(trackingProtectionOn) + val policy = core.trackingProtectionPolicyFactory + .createTrackingProtectionPolicy(trackingProtectionOn) useCases.settingsUseCases.updateTrackingProtection(policy) useCases.sessionUseCases.reload() } @@ -100,20 +101,17 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() { radioStrict = requireNotNull(findPreference(keyStrict)) radioStrict.contentDescription = getString(R.string.preference_enhanced_tracking_protection_strict_info_button) - radioStrict.onPreferenceChangeListener = object : SharedPreferenceUpdater() { - override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { - if (newValue == true) { - updateTrackingProtectionPolicy() - context?.metrics?.track( - Event.TrackingProtectionSettingChanged( - Event.TrackingProtectionSettingChanged.Setting.STRICT - ) - ) - } - updateCustomOptionsVisibility() - return super.onPreferenceChange(preference, newValue) - } + + radioStrict.onClickListener { + updateCustomOptionsVisibility() + updateTrackingProtectionPolicy() + context?.metrics?.track( + Event.TrackingProtectionSettingChanged( + Event.TrackingProtectionSettingChanged.Setting.STRICT + ) + ) } + radioStrict.onInfoClickListener { nav( R.id.trackingProtectionFragment, @@ -130,20 +128,17 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() { radioStandard = requireNotNull(findPreference(keyStandard)) radioStandard.contentDescription = getString(R.string.preference_enhanced_tracking_protection_standard_info_button) - radioStandard.onPreferenceChangeListener = object : SharedPreferenceUpdater() { - override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { - if (newValue == true) { - updateTrackingProtectionPolicy() - context?.metrics?.track( - Event.TrackingProtectionSettingChanged( - Event.TrackingProtectionSettingChanged.Setting.STANDARD - ) - ) - } - updateCustomOptionsVisibility() - return super.onPreferenceChange(preference, newValue) - } + + radioStandard.onClickListener { + updateCustomOptionsVisibility() + updateTrackingProtectionPolicy() + context?.metrics?.track( + Event.TrackingProtectionSettingChanged( + Event.TrackingProtectionSettingChanged.Setting.STANDARD + ) + ) } + radioStandard.onInfoClickListener { nav( R.id.trackingProtectionFragment, @@ -160,14 +155,10 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() { radioCustom = requireNotNull(findPreference(keyCustom)) radioCustom.contentDescription = getString(R.string.preference_enhanced_tracking_protection_custom_info_button) - radioCustom.onPreferenceChangeListener = object : SharedPreferenceUpdater() { - override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { - if (newValue == true) { - updateTrackingProtectionPolicy() - } - updateCustomOptionsVisibility() - return super.onPreferenceChange(preference, newValue) - } + + radioCustom.onClickListener { + updateCustomOptionsVisibility() + updateTrackingProtectionPolicy() } radioCustom.onInfoClickListener { nav( @@ -213,34 +204,52 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() { customCookies.onPreferenceChangeListener = object : SharedPreferenceUpdater() { override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { - updateTrackingProtectionPolicy() customCookiesSelect.isVisible = !customCookies.isChecked - return super.onPreferenceChange(preference, newValue) + return super.onPreferenceChange(preference, newValue).also { + updateTrackingProtectionPolicy() + } } } customTracking.onPreferenceChangeListener = object : SharedPreferenceUpdater() { override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { - updateTrackingProtectionPolicy() customTrackingSelect.isVisible = !customTracking.isChecked - return super.onPreferenceChange(preference, newValue) + return super.onPreferenceChange(preference, newValue).also { + updateTrackingProtectionPolicy() + } } } customCookiesSelect.onPreferenceChangeListener = object : StringSharedPreferenceUpdater() { override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { - updateTrackingProtectionPolicy() - val newValueEntry = (preference as DropDownListPreference).findEntry(key = newValue) - return super.onPreferenceChange(preference, newValueEntry) + return super.onPreferenceChange(preference, newValue).also { + updateTrackingProtectionPolicy() + } } } customTrackingSelect.onPreferenceChangeListener = object : StringSharedPreferenceUpdater() { override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { - updateTrackingProtectionPolicy() - val newValueEntry = (preference as DropDownListPreference).findEntry(key = newValue) - return super.onPreferenceChange(preference, newValueEntry) + return super.onPreferenceChange(preference, newValue).also { + updateTrackingProtectionPolicy() + } + } + } + + customCryptominers.onPreferenceChangeListener = object : SharedPreferenceUpdater() { + override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { + return super.onPreferenceChange(preference, newValue).also { + updateTrackingProtectionPolicy() + } + } + } + + customFingerprinters.onPreferenceChangeListener = object : SharedPreferenceUpdater() { + override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { + return super.onPreferenceChange(preference, newValue).also { + updateTrackingProtectionPolicy() + } } } @@ -249,7 +258,8 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() { private fun updateTrackingProtectionPolicy() { context?.components?.let { - val policy = it.core.createTrackingProtectionPolicy() + val policy = it.core.trackingProtectionPolicyFactory + .createTrackingProtectionPolicy() it.useCases.settingsUseCases.updateTrackingProtection.invoke(policy) it.useCases.sessionUseCases.reload.invoke() } 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 131f0e2f6..16d224665 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -247,9 +247,10 @@ class Settings private constructor( appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_cookies), true ) + val blockCookiesSelectionInCustomTrackingProtection by stringPreference( appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_cookies_select), - "" + "social" ) val blockTrackingContentInCustomTrackingProtection by booleanPreference( @@ -259,7 +260,7 @@ class Settings private constructor( val blockTrackingContentSelectionInCustomTrackingProtection by stringPreference( appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_tracking_content_select), - "" + "all" ) val blockCryptominersInCustomTrackingProtection by booleanPreference( diff --git a/app/src/test/java/org/mozilla/fenix/components/TrackingProtectionPolicyFactoryTest.kt b/app/src/test/java/org/mozilla/fenix/components/TrackingProtectionPolicyFactoryTest.kt new file mode 100644 index 000000000..d6a443345 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/components/TrackingProtectionPolicyFactoryTest.kt @@ -0,0 +1,377 @@ +package org.mozilla.fenix.components + +import io.mockk.every +import io.mockk.mockk +import mozilla.components.concept.engine.EngineSession +import mozilla.components.support.test.robolectric.testContext +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.R +import org.mozilla.fenix.TestApplication +import org.mozilla.fenix.utils.Settings +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(application = TestApplication::class) +class TrackingProtectionPolicyFactoryTest { + + @Test + fun `WHEN useStrictMode is true then SHOULD return strict mode`() { + val expected = EngineSession.TrackingProtectionPolicy.strict() + + val factory = TrackingProtectionPolicyFactory(mockSettings(useStrict = true)) + + val privateOnly = factory.createTrackingProtectionPolicy(normalMode = false, privateMode = true) + val normalOnly = factory.createTrackingProtectionPolicy(normalMode = true, privateMode = false) + val always = factory.createTrackingProtectionPolicy(normalMode = true, privateMode = true) + val none = factory.createTrackingProtectionPolicy(normalMode = false, privateMode = false) + + expected.assertPolicyEquals(privateOnly, checkPrivacy = false) + expected.assertPolicyEquals(normalOnly, checkPrivacy = false) + expected.assertPolicyEquals(always, checkPrivacy = false) + EngineSession.TrackingProtectionPolicy.none().assertPolicyEquals(none, checkPrivacy = false) + } + + @Test + fun `WHEN neither use strict nor use custom is true SHOULD return recommended mode`() { + val expected = EngineSession.TrackingProtectionPolicy.recommended() + + val factory = TrackingProtectionPolicyFactory(mockSettings(useStrict = false, useCustom = false)) + + val privateOnly = factory.createTrackingProtectionPolicy(normalMode = false, privateMode = true) + val normalOnly = factory.createTrackingProtectionPolicy(normalMode = true, privateMode = false) + val always = factory.createTrackingProtectionPolicy(normalMode = true, privateMode = true) + val none = factory.createTrackingProtectionPolicy(normalMode = false, privateMode = false) + + expected.assertPolicyEquals(privateOnly, checkPrivacy = false) + expected.assertPolicyEquals(normalOnly, checkPrivacy = false) + expected.assertPolicyEquals(always, checkPrivacy = false) + EngineSession.TrackingProtectionPolicy.none().assertPolicyEquals(none, checkPrivacy = false) + } + + @Test + fun `GIVEN custom policy WHEN should not block cookies THEN tracking policy should not block cookies`() { + val expected = EngineSession.TrackingProtectionPolicy.select( + cookiePolicy = EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_ALL, + trackingCategories = allTrackingCategories + ) + + val factory = TrackingProtectionPolicyFactory(settingsForCustom(shouldBlockCookiesInCustom = false)) + + 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 block all THEN tracking policy should have cookie policy allow none`() { + val expected = EngineSession.TrackingProtectionPolicy.select( + cookiePolicy = EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_NONE, + trackingCategories = allTrackingCategories + ) + + val factory = TrackingProtectionPolicyFactory(settingsForCustom(shouldBlockCookiesInCustom = true, blockCookiesSelection = "all")) + + 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 social THEN tracking policy should have cookie policy allow non-trackers`() { + val expected = EngineSession.TrackingProtectionPolicy.select( + cookiePolicy = EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_NON_TRACKERS, + trackingCategories = allTrackingCategories + ) + + val factory = TrackingProtectionPolicyFactory(settingsForCustom(shouldBlockCookiesInCustom = true, blockCookiesSelection = "social")) + + 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 accept visited THEN tracking policy should have cookie policy allow visited`() { + val expected = EngineSession.TrackingProtectionPolicy.select( + cookiePolicy = EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_VISITED, + trackingCategories = allTrackingCategories + ) + + val factory = TrackingProtectionPolicyFactory(settingsForCustom(shouldBlockCookiesInCustom = true, blockCookiesSelection = "unvisited")) + + 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 block third party THEN tracking policy should have cookie policy allow first party`() { + val expected = EngineSession.TrackingProtectionPolicy.select( + cookiePolicy = EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_ONLY_FIRST_PARTY, + trackingCategories = allTrackingCategories + ) + + val factory = TrackingProtectionPolicyFactory(settingsForCustom(shouldBlockCookiesInCustom = true, blockCookiesSelection = "third-party")) + + 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 = EngineSession.TrackingProtectionPolicy.select( + cookiePolicy = EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_NONE, + trackingCategories = allTrackingCategories + ) + + val factory = TrackingProtectionPolicyFactory(settingsForCustom(shouldBlockCookiesInCustom = true, blockCookiesSelection = "some text!")) + + 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 `all cookies_options_entry_values values should create policies without crashing`() { + testContext.resources.getStringArray(R.array.cookies_options_entry_values).forEach { + TrackingProtectionPolicyFactory(settingsForCustom(shouldBlockCookiesInCustom = true, blockCookiesSelection = it)) + .createTrackingProtectionPolicy(normalMode = true, privateMode = true) + } + } + + @Test + fun `factory should construct policies with privacy settings that match their inputs`() { + val allFactories = listOf( + TrackingProtectionPolicyFactory(mockSettings(useStrict = true)), + TrackingProtectionPolicyFactory(mockSettings(useStrict = false, useCustom = false)) + ) + + allFactories.map { + it.createTrackingProtectionPolicy(normalMode = true, privateMode = false) + }.forEach { + assertTrue(it.useForRegularSessions) + assertFalse(it.useForPrivateSessions) + } + + allFactories.map { + it.createTrackingProtectionPolicy(normalMode = false, privateMode = true) + }.forEach { + assertTrue(it.useForPrivateSessions) + assertFalse(it.useForRegularSessions) + } + + allFactories.map { + it.createTrackingProtectionPolicy(normalMode = true, privateMode = true) + }.forEach { + assertTrue(it.useForRegularSessions) + assertTrue(it.useForPrivateSessions) + } + + // `normalMode = true, privateMode = true` can never be shown to the user + } + + @Test + fun `custom tabs should respect their privacy rules`() { + val allSettings = listOf( + settingsForCustom(shouldBlockCookiesInCustom = false, blockTrackingContentInCustom = "all"), + settingsForCustom(shouldBlockCookiesInCustom = true, blockCookiesSelection = "all", blockTrackingContentInCustom = "all"), + settingsForCustom(shouldBlockCookiesInCustom = true, blockCookiesSelection = "all", blockTrackingContentInCustom = "all"), + settingsForCustom(shouldBlockCookiesInCustom = true, blockCookiesSelection = "unvisited", blockTrackingContentInCustom = "all"), + settingsForCustom(shouldBlockCookiesInCustom = true, blockCookiesSelection = "third-party", blockTrackingContentInCustom = "all"), + settingsForCustom(shouldBlockCookiesInCustom = true, blockCookiesSelection = "some text!", blockTrackingContentInCustom = "all") + ) + + val privateSettings = listOf( + settingsForCustom(shouldBlockCookiesInCustom = false, blockTrackingContentInCustom = "private"), + settingsForCustom(shouldBlockCookiesInCustom = true, blockCookiesSelection = "all", blockTrackingContentInCustom = "private"), + settingsForCustom(shouldBlockCookiesInCustom = true, blockCookiesSelection = "all", blockTrackingContentInCustom = "private"), + settingsForCustom(shouldBlockCookiesInCustom = true, blockCookiesSelection = "unvisited", blockTrackingContentInCustom = "private"), + settingsForCustom(shouldBlockCookiesInCustom = true, blockCookiesSelection = "third-party", blockTrackingContentInCustom = "private"), + settingsForCustom(shouldBlockCookiesInCustom = true, blockCookiesSelection = "some text!", blockTrackingContentInCustom = "private") + ) + + allSettings.map { TrackingProtectionPolicyFactory(it).createTrackingProtectionPolicy( + normalMode = true, + privateMode = true + ) } + .forEach { + assertTrue(it.useForRegularSessions) + assertTrue(it.useForPrivateSessions) + } + + privateSettings.map { TrackingProtectionPolicyFactory(it).createTrackingProtectionPolicy( + normalMode = true, + privateMode = true + ) } + .forEach { + assertFalse(it.useForRegularSessions) + assertTrue(it.useForPrivateSessions) + } + } + + @Test + fun `GIVEN custom policy WHEN default tracking policies THEN tracking policies should match default`() { + val defaultTrackingCategories = arrayOf( + EngineSession.TrackingProtectionPolicy.TrackingCategory.AD, + EngineSession.TrackingProtectionPolicy.TrackingCategory.ANALYTICS, + EngineSession.TrackingProtectionPolicy.TrackingCategory.SOCIAL, + EngineSession.TrackingProtectionPolicy.TrackingCategory.MOZILLA_SOCIAL + ) + + val expected = EngineSession.TrackingProtectionPolicy.select( + cookiePolicy = EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_NONE, + trackingCategories = defaultTrackingCategories + ) + + val factory = TrackingProtectionPolicyFactory( + settingsForCustom( + shouldBlockCookiesInCustom = true, + blockTrackingContent = false, + blockFingerprinters = false, + blockCryptominers = false + ) + ) + val actual = factory.createTrackingProtectionPolicy(normalMode = true, privateMode = true) + + expected.assertPolicyEquals(actual, checkPrivacy = false) + } + + @Test + fun `GIVEN custom policy WHEN all tracking policies THEN tracking policies should match all`() { + val expected = EngineSession.TrackingProtectionPolicy.select( + cookiePolicy = EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_NONE, + trackingCategories = allTrackingCategories + ) + + val factory = TrackingProtectionPolicyFactory( + settingsForCustom( + shouldBlockCookiesInCustom = true, + blockTrackingContent = true, + blockFingerprinters = true, + blockCryptominers = true + ) + ) + val actual = factory.createTrackingProtectionPolicy(normalMode = true, privateMode = true) + + expected.assertPolicyEquals(actual, checkPrivacy = false) + } + + @Test + fun `GIVEN custom policy WHEN some tracking policies THEN tracking policies should match passed policies`() { + val someTrackingCategories = arrayOf( + EngineSession.TrackingProtectionPolicy.TrackingCategory.AD, + EngineSession.TrackingProtectionPolicy.TrackingCategory.ANALYTICS, + EngineSession.TrackingProtectionPolicy.TrackingCategory.SOCIAL, + EngineSession.TrackingProtectionPolicy.TrackingCategory.MOZILLA_SOCIAL, + EngineSession.TrackingProtectionPolicy.TrackingCategory.FINGERPRINTING + ) + + val expected = EngineSession.TrackingProtectionPolicy.select( + cookiePolicy = EngineSession.TrackingProtectionPolicy.CookiePolicy.ACCEPT_NONE, + trackingCategories = someTrackingCategories + ) + + val factory = TrackingProtectionPolicyFactory( + settingsForCustom( + shouldBlockCookiesInCustom = true, + blockTrackingContent = false, + blockFingerprinters = true, + blockCryptominers = false + ) + ) + val actual = factory.createTrackingProtectionPolicy(normalMode = true, privateMode = true) + + expected.assertPolicyEquals(actual, checkPrivacy = false) + } +} + +private fun mockSettings( + useStrict: Boolean = false, + useCustom: Boolean = false +): Settings = mockk { + every { useStrictTrackingProtection } returns useStrict + every { useCustomTrackingProtection } returns useCustom +} + +@Suppress("LongParameterList") +private fun settingsForCustom( + shouldBlockCookiesInCustom: Boolean, + blockTrackingContentInCustom: String = "all", // ["private", "all"] + blockCookiesSelection: String = "all", // values from R.array.cookies_options_entry_values + blockTrackingContent: Boolean = true, + blockFingerprinters: Boolean = true, + blockCryptominers: Boolean = true +): Settings = mockSettings(useStrict = false, useCustom = true).apply { + + every { blockTrackingContentSelectionInCustomTrackingProtection } returns blockTrackingContentInCustom + + every { blockCookiesInCustomTrackingProtection } returns shouldBlockCookiesInCustom + every { blockCookiesSelectionInCustomTrackingProtection } returns blockCookiesSelection + every { blockTrackingContentInCustomTrackingProtection } returns blockTrackingContent + every { blockFingerprintersInCustomTrackingProtection } returns blockFingerprinters + every { blockCryptominersInCustomTrackingProtection } returns blockCryptominers +} + +private fun EngineSession.TrackingProtectionPolicy.assertPolicyEquals( + actual: EngineSession.TrackingProtectionPolicy, + checkPrivacy: Boolean +) { + assertEquals(this.cookiePolicy, actual.cookiePolicy) + // TODO Uncomment this assertion after the fix in AC#6079 lands +// assertEquals(this.strictSocialTrackingProtection, actual.strictSocialTrackingProtection) + // E.g., atm, RECOMMENDED == AD + ANALYTICS + SOCIAL + TEST + MOZILLA_SOCIAL + CRYPTOMINING. + // If all of these are set manually, the equality check should not fail + if (this.trackingCategories.toInt() != actual.trackingCategories.toInt()) { + assertArrayEquals(this.trackingCategories, actual.trackingCategories) + } + + if (checkPrivacy) { + assertEquals(this.useForPrivateSessions, actual.useForPrivateSessions) + assertEquals(this.useForRegularSessions, actual.useForRegularSessions) + } +} + +private fun Array.toInt(): Int { + return fold(initial = 0) { acc, next -> acc + next.id } +} + +private val allTrackingCategories = arrayOf( + EngineSession.TrackingProtectionPolicy.TrackingCategory.AD, + EngineSession.TrackingProtectionPolicy.TrackingCategory.ANALYTICS, + EngineSession.TrackingProtectionPolicy.TrackingCategory.SOCIAL, + EngineSession.TrackingProtectionPolicy.TrackingCategory.MOZILLA_SOCIAL, + EngineSession.TrackingProtectionPolicy.TrackingCategory.SCRIPTS_AND_SUB_RESOURCES, + EngineSession.TrackingProtectionPolicy.TrackingCategory.FINGERPRINTING, + EngineSession.TrackingProtectionPolicy.TrackingCategory.CRYPTOMINING +)