Commit bb5058da authored by MozLando's avatar MozLando
Browse files

Merge #7666



7666: Closes #7665: Add support for converting TrackingProtectionPolicy to ContentBlocking.Setting r=Amejia481 a=jonalmeida



Co-authored-by: default avatarJonathan Almeida <jalmeida@mozilla.com>
parents 82bcabef e3d7ead3
Loading
Loading
Loading
Loading
+53 −0
Original line number Diff line number Diff line
/* 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.browser.engine.gecko.ext

import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy
import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.TrackingCategory
import org.mozilla.geckoview.ContentBlocking
import org.mozilla.geckoview.GeckoRuntimeSettings

/**
 * Converts a [TrackingProtectionPolicy] into a GeckoView setting that can be used with [GeckoRuntimeSettings.Builder].
 */
fun TrackingProtectionPolicy.toContentBlockingSetting(
    safeBrowsingPolicy: Array<EngineSession.SafeBrowsingPolicy> = arrayOf(EngineSession.SafeBrowsingPolicy.RECOMMENDED)
) = ContentBlocking.Settings.Builder().apply {
    enhancedTrackingProtectionLevel(getEtpLevel())
    antiTracking(getAntiTrackingPolicy())
    cookieBehavior(cookiePolicy.id)
    safeBrowsing(safeBrowsingPolicy.sumBy { it.id })
    // This will be fixed on merge day when strictSocialTrackingProtection will be available on beta
    // strictSocialTrackingProtection(getStrictSocialTrackingProtection())
}.build()

/**
 * Returns the [TrackingProtectionPolicy] categories as an Enhanced Tracking Protection level for GeckoView.
 */
internal fun TrackingProtectionPolicy.getEtpLevel(): Int {
    return when {
        trackingCategories.contains(TrackingCategory.NONE) -> ContentBlocking.EtpLevel.NONE
        else -> ContentBlocking.EtpLevel.STRICT
    }
}

/**
 * Returns the [TrackingProtectionPolicy] as a tracking policy for GeckoView.
 */
internal fun TrackingProtectionPolicy.getAntiTrackingPolicy(): Int {
    /**
     * The [TrackingProtectionPolicy.TrackingCategory.SCRIPTS_AND_SUB_RESOURCES] is an
     * artificial category, created with the sole purpose of going around this bug
     * https://bugzilla.mozilla.org/show_bug.cgi?id=1579264, for this reason we have to
     * remove its value from the valid anti tracking categories, when is present.
     */
    val total = trackingCategories.sumBy { it.id }
    return if (contains(TrackingCategory.SCRIPTS_AND_SUB_RESOURCES)) {
        total - TrackingCategory.SCRIPTS_AND_SUB_RESOURCES.id
    } else {
        total
    }
}
+41 −0
Original line number Diff line number Diff line
/*
 * 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.browser.engine.gecko.ext

import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy
import org.junit.Assert.assertEquals
import org.junit.Test
import org.mozilla.geckoview.ContentBlocking.EtpLevel

class TrackingProtectionPolicyKtTest {

    private val defaultSafeBrowsing = arrayOf(EngineSession.SafeBrowsingPolicy.RECOMMENDED)

    @Test
    fun `transform the policy to a GeckoView ContentBlockingSetting`() {
        val policy = TrackingProtectionPolicy.recommended()
        val setting = policy.toContentBlockingSetting()

        assertEquals(policy.getEtpLevel(), setting.enhancedTrackingProtectionLevel)
        assertEquals(policy.getAntiTrackingPolicy(), setting.antiTrackingCategories)
        assertEquals(policy.cookiePolicy.id, setting.cookieBehavior)
        assertEquals(defaultSafeBrowsing.sumBy { it.id }, setting.safeBrowsingCategories)
        // TODO Add this when we have it from GV; https://bugzilla.mozilla.org/show_bug.cgi?id=1651454
        // assertEquals(policy.getStrictSocialTrackingProtection(), setting.strictSocialTrackingProtection)

        val policyWithSafeBrowsing = TrackingProtectionPolicy.recommended().toContentBlockingSetting(emptyArray())
        assertEquals(0, policyWithSafeBrowsing.safeBrowsingCategories)
    }

    @Test
    fun `getEtpLevel is always Strict unless None`() {
        assertEquals(EtpLevel.STRICT, TrackingProtectionPolicy.recommended().getEtpLevel())
        assertEquals(EtpLevel.STRICT, TrackingProtectionPolicy.strict().getEtpLevel())
        assertEquals(EtpLevel.NONE, TrackingProtectionPolicy.none().getEtpLevel())
    }
}
+20 −30
Original line number Diff line number Diff line
@@ -7,6 +7,9 @@ package mozilla.components.browser.engine.gecko
import android.content.Context
import android.util.AttributeSet
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.engine.gecko.ext.getAntiTrackingPolicy
import mozilla.components.browser.engine.gecko.ext.getEtpLevel
import mozilla.components.browser.engine.gecko.ext.getStrictSocialTrackingProtection
import mozilla.components.browser.engine.gecko.integration.LocaleSettingUpdater
import mozilla.components.browser.engine.gecko.mediaquery.from
import mozilla.components.browser.engine.gecko.mediaquery.toGeckoValue
@@ -519,39 +522,26 @@ class GeckoEngine(
        override var trackingProtectionPolicy: TrackingProtectionPolicy? = null
            set(value) {
                value?.let { policy ->
                    val activateStrictSocialTracking =
                        policy.strictSocialTrackingProtection ?: policy.trackingCategories.contains(
                            TrackingCategory.STRICT
                        )
                    val etpLevel =
                        when {
                            policy.trackingCategories.contains(TrackingCategory.NONE) ->
                                ContentBlocking.EtpLevel.NONE
                            else -> ContentBlocking.EtpLevel.STRICT
                        }
                    runtime.settings.contentBlocking.setEnhancedTrackingProtectionLevel(etpLevel)
                    runtime.settings.contentBlocking.setStrictSocialTrackingProtection(
                        activateStrictSocialTracking
                    )
                    runtime.settings.contentBlocking.setAntiTracking(policy.getAntiTrackingPolicy())
                    runtime.settings.contentBlocking.setCookieBehavior(policy.cookiePolicy.id)
                    defaultSettings?.trackingProtectionPolicy = value
                    field = value
                    with(runtime.settings.contentBlocking) {
                        if (enhancedTrackingProtectionLevel != value.getEtpLevel()) {
                            enhancedTrackingProtectionLevel = value.getEtpLevel()
                        }

                        if (strictSocialTrackingProtection != value.getStrictSocialTrackingProtection()) {
                            strictSocialTrackingProtection = policy.getStrictSocialTrackingProtection()
                        }

        private fun TrackingProtectionPolicy.getAntiTrackingPolicy(): Int {
            /**
             * The [TrackingProtectionPolicy.TrackingCategory.SCRIPTS_AND_SUB_RESOURCES] is an
             * artificial category, created with the sole purpose of going around this bug
             * https://bugzilla.mozilla.org/show_bug.cgi?id=1579264, for this reason we have to
             * remove its value from the valid anti tracking categories, when is present.
             */
            val total = trackingCategories.sumBy { it.id }
            return if (contains(TrackingCategory.SCRIPTS_AND_SUB_RESOURCES)) {
                total - TrackingProtectionPolicy.TrackingCategory.SCRIPTS_AND_SUB_RESOURCES.id
            } else {
                total
                        if (antiTrackingCategories != value.getAntiTrackingPolicy()) {
                            setAntiTracking(policy.getAntiTrackingPolicy())
                        }

                        if (cookieBehavior != value.cookiePolicy.id) {
                            cookieBehavior = value.cookiePolicy.id
                        }
                    }

                    defaultSettings?.trackingProtectionPolicy = value
                    field = value
                }
            }

+59 −0
Original line number Diff line number Diff line
/* 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.browser.engine.gecko.ext

import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy
import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.TrackingCategory
import org.mozilla.geckoview.ContentBlocking
import org.mozilla.geckoview.GeckoRuntimeSettings

/**
 * Converts a [TrackingProtectionPolicy] into a GeckoView setting that can be used with [GeckoRuntimeSettings.Builder].
 */
fun TrackingProtectionPolicy.toContentBlockingSetting(
    safeBrowsingPolicy: Array<EngineSession.SafeBrowsingPolicy> = arrayOf(EngineSession.SafeBrowsingPolicy.RECOMMENDED)
) = ContentBlocking.Settings.Builder().apply {
    enhancedTrackingProtectionLevel(getEtpLevel())
    antiTracking(getAntiTrackingPolicy())
    cookieBehavior(cookiePolicy.id)
    safeBrowsing(safeBrowsingPolicy.sumBy { it.id })
    strictSocialTrackingProtection(getStrictSocialTrackingProtection())
}.build()

/**
 * Returns whether [TrackingCategory.STRICT] is enabled in the [TrackingProtectionPolicy].
 */
internal fun TrackingProtectionPolicy.getStrictSocialTrackingProtection(): Boolean {
    return strictSocialTrackingProtection ?: trackingCategories.contains(TrackingCategory.STRICT)
}

/**
 * Returns the [TrackingProtectionPolicy] categories as an Enhanced Tracking Protection level for GeckoView.
 */
internal fun TrackingProtectionPolicy.getEtpLevel(): Int {
    return when {
        trackingCategories.contains(TrackingCategory.NONE) -> ContentBlocking.EtpLevel.NONE
        else -> ContentBlocking.EtpLevel.STRICT
    }
}

/**
 * Returns the [TrackingProtectionPolicy] as a tracking policy for GeckoView.
 */
internal fun TrackingProtectionPolicy.getAntiTrackingPolicy(): Int {
    /**
     * The [TrackingProtectionPolicy.TrackingCategory.SCRIPTS_AND_SUB_RESOURCES] is an
     * artificial category, created with the sole purpose of going around this bug
     * https://bugzilla.mozilla.org/show_bug.cgi?id=1579264, for this reason we have to
     * remove its value from the valid anti tracking categories, when is present.
     */
    val total = trackingCategories.sumBy { it.id }
    return if (contains(TrackingCategory.SCRIPTS_AND_SUB_RESOURCES)) {
        total - TrackingCategory.SCRIPTS_AND_SUB_RESOURCES.id
    } else {
        total
    }
}
+98 −14
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ package mozilla.components.browser.engine.gecko
import android.app.Activity
import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.browser.engine.gecko.ext.getAntiTrackingPolicy
import mozilla.components.browser.engine.gecko.mediaquery.toGeckoValue
import mozilla.components.concept.engine.DefaultSettings
import mozilla.components.concept.engine.Engine
@@ -43,11 +44,13 @@ import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mozilla.gecko.util.GeckoBundle
import org.mozilla.geckoview.ContentBlocking
import org.mozilla.geckoview.ContentBlocking.CookieBehavior
import org.mozilla.geckoview.ContentBlockingController
import org.mozilla.geckoview.ContentBlockingController.Event
import org.mozilla.geckoview.GeckoResult
@@ -252,7 +255,7 @@ class GeckoEngineTest {

        val trackingStrictCategories = TrackingProtectionPolicy.strict().trackingCategories.sumBy { it.id }
        val artificialCategory =
            TrackingProtectionPolicy.TrackingCategory.SCRIPTS_AND_SUB_RESOURCES.id
            TrackingCategory.SCRIPTS_AND_SUB_RESOURCES.id
        assertEquals(
            trackingStrictCategories - artificialCategory,
            contentBlockingSettings.antiTrackingCategories
@@ -280,29 +283,30 @@ class GeckoEngineTest {

    @Test
    fun `the SCRIPTS_AND_SUB_RESOURCES tracking protection category must not be passed to gecko view`() {
        val mockRuntime = mock<GeckoRuntime>()
        val settings = spy(ContentBlocking.Settings.Builder().build())
        whenever(mockRuntime.settings).thenReturn(mock())
        whenever(mockRuntime.settings.contentBlocking).thenReturn(settings)

        val geckoRunTime = GeckoRuntime.getDefault(testContext)

        val engine = GeckoEngine(testContext, runtime = geckoRunTime)
        val engine = GeckoEngine(testContext, runtime = mockRuntime)

        engine.settings.trackingProtectionPolicy = TrackingProtectionPolicy.strict()

        val trackingStrictCategories = TrackingProtectionPolicy.strict().trackingCategories.sumBy { it.id }
        val artificialCategory =
            TrackingProtectionPolicy.TrackingCategory.SCRIPTS_AND_SUB_RESOURCES.id
        val artificialCategory = TrackingCategory.SCRIPTS_AND_SUB_RESOURCES.id

        assertEquals(
            trackingStrictCategories - artificialCategory,
            geckoRunTime.settings.contentBlocking.antiTrackingCategories
            mockRuntime.settings.contentBlocking.antiTrackingCategories
        )

        geckoRunTime.settings.contentBlocking.setAntiTracking(0)
        mockRuntime.settings.contentBlocking.setAntiTracking(0)

        engine.settings.trackingProtectionPolicy = TrackingProtectionPolicy.select(
            arrayOf(TrackingProtectionPolicy.TrackingCategory.SCRIPTS_AND_SUB_RESOURCES)
            arrayOf(TrackingCategory.SCRIPTS_AND_SUB_RESOURCES)
        )

        assertEquals(0, geckoRunTime.settings.contentBlocking.antiTrackingCategories)
        assertEquals(0, mockRuntime.settings.contentBlocking.antiTrackingCategories)
    }

    @Test
@@ -333,11 +337,63 @@ class GeckoEngineTest {
        )
    }

    @Test
    fun `setAntiTracking is only invoked when the value is changed`() {
        val mockRuntime = mock<GeckoRuntime>()
        val settings = spy(ContentBlocking.Settings.Builder().build())
        whenever(mockRuntime.settings).thenReturn(mock())
        whenever(mockRuntime.settings.contentBlocking).thenReturn(settings)

        val engine = GeckoEngine(testContext, runtime = mockRuntime)
        val policy = TrackingProtectionPolicy.recommended()

        engine.settings.trackingProtectionPolicy = policy

        verify(mockRuntime.settings.contentBlocking).setAntiTracking(
            policy.getAntiTrackingPolicy()
        )

        reset(settings)

        engine.settings.trackingProtectionPolicy = policy

        verify(mockRuntime.settings.contentBlocking, never()).setAntiTracking(
            policy.getAntiTrackingPolicy()
        )
    }

    @Test
    fun `setCookieBehavior is only invoked when the value is changed`() {
        val mockRuntime = mock<GeckoRuntime>()
        val settings = spy(ContentBlocking.Settings.Builder().build())
        whenever(mockRuntime.settings).thenReturn(mock())
        whenever(mockRuntime.settings.contentBlocking).thenReturn(settings)
        whenever(mockRuntime.settings.contentBlocking.cookieBehavior).thenReturn(CookieBehavior.ACCEPT_NONE)

        val engine = GeckoEngine(testContext, runtime = mockRuntime)
        val policy = TrackingProtectionPolicy.recommended()

        engine.settings.trackingProtectionPolicy = policy

        verify(mockRuntime.settings.contentBlocking).setCookieBehavior(
            policy.cookiePolicy.id
        )

        reset(settings)

        engine.settings.trackingProtectionPolicy = policy

        verify(mockRuntime.settings.contentBlocking, never()).setCookieBehavior(
            policy.cookiePolicy.id
        )
    }

    @Test
    fun `setEnhancedTrackingProtectionLevel MUST always be set to STRICT unless the tracking protection policy is none`() {
        val mockRuntime = mock<GeckoRuntime>()
        val settings = spy(ContentBlocking.Settings.Builder().build())
        whenever(mockRuntime.settings).thenReturn(mock())
        whenever(mockRuntime.settings.contentBlocking).thenReturn(mock())
        whenever(mockRuntime.settings.contentBlocking).thenReturn(settings)

        val engine = GeckoEngine(testContext, runtime = mockRuntime)

@@ -347,17 +403,43 @@ class GeckoEngineTest {
            ContentBlocking.EtpLevel.STRICT
        )

        reset(settings)

        engine.settings.trackingProtectionPolicy = TrackingProtectionPolicy.recommended()

        verify(mockRuntime.settings.contentBlocking, never()).setEnhancedTrackingProtectionLevel(
            ContentBlocking.EtpLevel.STRICT
        )

        reset(settings)

        engine.settings.trackingProtectionPolicy = TrackingProtectionPolicy.strict()

        verify(mockRuntime.settings.contentBlocking, times(2)).setEnhancedTrackingProtectionLevel(
        verify(mockRuntime.settings.contentBlocking, never()).setEnhancedTrackingProtectionLevel(
            ContentBlocking.EtpLevel.STRICT
        )

        engine.settings.trackingProtectionPolicy = TrackingProtectionPolicy.none()
        reset(settings)

        engine.settings.trackingProtectionPolicy = TrackingProtectionPolicy.none()
        verify(mockRuntime.settings.contentBlocking).setEnhancedTrackingProtectionLevel(
            ContentBlocking.EtpLevel.NONE
        )

        reset(settings)

        engine.settings.trackingProtectionPolicy = TrackingProtectionPolicy.none()
        verify(mockRuntime.settings.contentBlocking, never()).setEnhancedTrackingProtectionLevel(
            ContentBlocking.EtpLevel.NONE
        )

        reset(settings)

        engine.settings.trackingProtectionPolicy = TrackingProtectionPolicy.strict()

        verify(mockRuntime.settings.contentBlocking).setEnhancedTrackingProtectionLevel(
            ContentBlocking.EtpLevel.STRICT
        )
    }

    @Test
@@ -365,6 +447,7 @@ class GeckoEngineTest {
        val mockRuntime = mock<GeckoRuntime>()
        whenever(mockRuntime.settings).thenReturn(mock())
        whenever(mockRuntime.settings.contentBlocking).thenReturn(mock())
        whenever(mockRuntime.settings.contentBlocking.strictSocialTrackingProtection).thenReturn(true)

        val engine = GeckoEngine(testContext, runtime = mockRuntime)

@@ -393,6 +476,7 @@ class GeckoEngineTest {
        val mockRuntime = mock<GeckoRuntime>()
        whenever(mockRuntime.settings).thenReturn(mock())
        whenever(mockRuntime.settings.contentBlocking).thenReturn(mock())
        whenever(mockRuntime.settings.contentBlocking.strictSocialTrackingProtection).thenReturn(true)

        val engine = GeckoEngine(testContext, runtime = mockRuntime)

@@ -439,7 +523,7 @@ class GeckoEngineTest {

        val trackingStrictCategories = TrackingProtectionPolicy.strict().trackingCategories.sumBy { it.id }
        val artificialCategory =
            TrackingProtectionPolicy.TrackingCategory.SCRIPTS_AND_SUB_RESOURCES.id
            TrackingCategory.SCRIPTS_AND_SUB_RESOURCES.id
        assertEquals(
            trackingStrictCategories - artificialCategory,
            contentBlockingSettings.antiTrackingCategories
Loading