diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackerBuckets.kt b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackerBuckets.kt index c9c074744..b2e0d4343 100644 --- a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackerBuckets.kt +++ b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackerBuckets.kt @@ -4,33 +4,39 @@ package org.mozilla.fenix.trackingprotection -import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.TrackingCategory.AD -import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.TrackingCategory.ANALYTICS +import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.TrackingCategory import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.TrackingCategory.CRYPTOMINING import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.TrackingCategory.FINGERPRINTING -import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.TrackingCategory.SOCIAL +import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.TrackingCategory.MOZILLA_SOCIAL import mozilla.components.concept.engine.content.blocking.Tracker +import mozilla.components.concept.engine.content.blocking.TrackerLog import org.mozilla.fenix.ext.tryGetHostFromUrl +import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.CROSS_SITE_TRACKING_COOKIES import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.CRYPTOMINERS import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.FINGERPRINTERS import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.SOCIAL_MEDIA_TRACKERS import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.TRACKING_CONTENT import java.util.EnumMap +typealias BucketMap = Map> + /** * Sorts [Tracker]s into different buckets and exposes them as a map. */ class TrackerBuckets { - private var trackers = emptyList() - var buckets = emptyMap>() + private var trackers = emptyList() + + data class BucketedTrackerLog(var blockedBucketMap: BucketMap, var loadedBucketMap: BucketMap) + + var buckets: BucketedTrackerLog = BucketedTrackerLog(emptyMap(), emptyMap()) private set /** * If [newTrackers] has changed since the last call, - * update [buckets] based on the new trackers list. + * update [buckets] based on the new tracker log list. */ - fun updateIfNeeded(newTrackers: List) { + fun updateIfNeeded(newTrackers: List) { if (newTrackers != trackers) { trackers = newTrackers buckets = putTrackersInBuckets(newTrackers) @@ -38,44 +44,75 @@ class TrackerBuckets { } /** - * Returns true if there are no trackers. + * Returns true if there are no trackers being blocked. */ - fun isEmpty() = buckets.isEmpty() + fun blockedIsEmpty() = buckets.blockedBucketMap.isEmpty() + + /** + * Returns true if there are no trackers loaded. + */ + fun loadedIsEmpty() = buckets.loadedBucketMap.isEmpty() /** * Gets the tracker URLs for a given category. */ - operator fun get(key: TrackingProtectionCategory) = buckets[key].orEmpty() + operator fun get(key: TrackingProtectionCategory, blocked: Boolean) = + if (blocked) buckets.blockedBucketMap[key].orEmpty() else buckets.loadedBucketMap[key].orEmpty() companion object { - + @Suppress("ComplexMethod") private fun putTrackersInBuckets( - list: List - ): Map> { - val map = EnumMap>(TrackingProtectionCategory::class.java) + list: List + ): BucketedTrackerLog { + val blockedMap = + EnumMap>(TrackingProtectionCategory::class.java) + val loadedMap = + EnumMap>(TrackingProtectionCategory::class.java) for (item in list) { when { - CRYPTOMINING in item.trackingCategories -> { - map[CRYPTOMINERS] = map[CRYPTOMINERS].orEmpty() + + // Blocked categories + item.cookiesHasBeenBlocked -> { + blockedMap[CROSS_SITE_TRACKING_COOKIES] = + blockedMap[CROSS_SITE_TRACKING_COOKIES].orEmpty() + item.url.tryGetHostFromUrl() + } + CRYPTOMINING in item.blockedCategories -> { + blockedMap[CRYPTOMINERS] = blockedMap[CRYPTOMINERS].orEmpty() + item.url.tryGetHostFromUrl() } - FINGERPRINTING in item.trackingCategories -> { - map[FINGERPRINTERS] = map[FINGERPRINTERS].orEmpty() + + FINGERPRINTING in item.blockedCategories -> { + blockedMap[FINGERPRINTERS] = blockedMap[FINGERPRINTERS].orEmpty() + item.url.tryGetHostFromUrl() } - SOCIAL in item.trackingCategories -> { - map[SOCIAL_MEDIA_TRACKERS] = map[SOCIAL_MEDIA_TRACKERS].orEmpty() + + MOZILLA_SOCIAL in item.blockedCategories -> { + blockedMap[SOCIAL_MEDIA_TRACKERS] = + blockedMap[SOCIAL_MEDIA_TRACKERS].orEmpty() + + item.url.tryGetHostFromUrl() + } + TrackingCategory.SCRIPTS_AND_SUB_RESOURCES in item.blockedCategories -> { + blockedMap[TRACKING_CONTENT] = blockedMap[TRACKING_CONTENT].orEmpty() + item.url.tryGetHostFromUrl() } - AD in item.trackingCategories || - SOCIAL in item.trackingCategories || - ANALYTICS in item.trackingCategories -> { - map[TRACKING_CONTENT] = map[TRACKING_CONTENT].orEmpty() + + // Loaded categories + CRYPTOMINING in item.loadedCategories -> { + loadedMap[CRYPTOMINERS] = loadedMap[CRYPTOMINERS].orEmpty() + + item.url.tryGetHostFromUrl() + } + FINGERPRINTING in item.loadedCategories -> { + loadedMap[FINGERPRINTERS] = loadedMap[FINGERPRINTERS].orEmpty() + + item.url.tryGetHostFromUrl() + } + MOZILLA_SOCIAL in item.loadedCategories -> { + loadedMap[SOCIAL_MEDIA_TRACKERS] = + loadedMap[SOCIAL_MEDIA_TRACKERS].orEmpty() + + item.url.tryGetHostFromUrl() + } + TrackingCategory.SCRIPTS_AND_SUB_RESOURCES in item.loadedCategories -> { + loadedMap[TRACKING_CONTENT] = loadedMap[TRACKING_CONTENT].orEmpty() + item.url.tryGetHostFromUrl() } } } - return map + return BucketedTrackerLog(blockedMap, loadedMap) } } } diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelDialogFragment.kt index 3343632a7..8264630ba 100644 --- a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelDialogFragment.kt @@ -24,8 +24,10 @@ import kotlinx.android.synthetic.main.fragment_tracking_protection.view.* import kotlinx.coroutines.launch import mozilla.components.browser.session.Session import mozilla.components.concept.engine.content.blocking.Tracker +import mozilla.components.feature.session.TrackingProtectionUseCases import mozilla.components.lib.state.ext.observe import mozilla.components.support.base.feature.BackHandler +import mozilla.components.support.base.log.logger.Logger import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.components.StoreProvider @@ -92,8 +94,7 @@ class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), BackHan session, url, trackingProtectionEnabled, - session?.trackersBlocked ?: listOf(), - session?.trackersLoaded ?: listOf(), + listOf(), TrackingProtectionState.Mode.Normal ) ) @@ -105,6 +106,7 @@ class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), BackHan ) trackingProtectionView = TrackingProtectionPanelView(view.fragment_tp, trackingProtectionInteractor) + updateTrackers() return view } @@ -116,20 +118,33 @@ class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), BackHan } override fun onTrackerBlocked(session: Session, tracker: Tracker, all: List) { - trackingProtectionStore.dispatch( - TrackingProtectionAction.TrackerListChange(all) - ) - trackingProtectionStore.dispatch( - TrackingProtectionAction.TrackerLoadedListChange(session.trackersLoaded) - ) + updateTrackers() } override fun onTrackerLoaded(session: Session, tracker: Tracker, all: List) { - trackingProtectionStore.dispatch( - TrackingProtectionAction.TrackerListChange(session.trackersBlocked) + updateTrackers() + } + } + + private fun updateTrackers() { + context?.let { context -> + val session = + context.components.core.sessionManager.findSessionById(sessionId) ?: return + val useCase = TrackingProtectionUseCases( + sessionManager = context.components.core.sessionManager, + engine = context.components.core.engine ) - trackingProtectionStore.dispatch( - TrackingProtectionAction.TrackerLoadedListChange(all) + + useCase.fetchTrackingLogs( + session, + onSuccess = { + trackingProtectionStore.dispatch( + TrackingProtectionAction.TrackerLogChange(it) + ) + }, + onError = { + Logger.error("TrackingProtectionUseCases - fetchTrackingLogs onError", it) + } ) } } diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelView.kt b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelView.kt index 5b190b574..70b3ebaec 100644 --- a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelView.kt +++ b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelView.kt @@ -68,7 +68,6 @@ class TrackingProtectionPanelView( private var mode: TrackingProtectionState.Mode = TrackingProtectionState.Mode.Normal private var bucketedTrackers = TrackerBuckets() - private var bucketedLoadedTrackers = TrackerBuckets() fun update(state: TrackingProtectionState) { if (state.mode != mode) { @@ -76,7 +75,6 @@ class TrackingProtectionPanelView( } bucketedTrackers.updateIfNeeded(state.listTrackers) - bucketedLoadedTrackers.updateIfNeeded(state.listTrackersLoaded) when (val mode = state.mode) { is TrackingProtectionState.Mode.Normal -> setUIForNormalMode(state) @@ -92,32 +90,31 @@ class TrackingProtectionPanelView( normal_mode.visibility = View.VISIBLE protection_settings.isGone = state.session?.customTabConfig != null - not_blocking_header.isGone = bucketedLoadedTrackers.isEmpty() + not_blocking_header.isGone = bucketedTrackers.loadedIsEmpty() bindUrl(state.url) bindTrackingProtectionInfo(state.isTrackingProtectionEnabled) protection_settings.setOnClickListener { interactor.selectTrackingProtectionSettings() } - blocking_header.isGone = bucketedTrackers.isEmpty() + blocking_header.isGone = bucketedTrackers.blockedIsEmpty() updateCategoryVisibility() setCategoryClickListeners() } private fun updateCategoryVisibility() { - cross_site_tracking.isGone = bucketedTrackers[CROSS_SITE_TRACKING_COOKIES].isEmpty() - social_media_trackers.isGone = bucketedTrackers[SOCIAL_MEDIA_TRACKERS].isEmpty() - fingerprinters.isGone = bucketedTrackers[FINGERPRINTERS].isEmpty() - tracking_content.isGone = bucketedTrackers[TRACKING_CONTENT].isEmpty() - cryptominers.isGone = bucketedTrackers[CRYPTOMINERS].isEmpty() + cross_site_tracking.isGone = + bucketedTrackers.get(CROSS_SITE_TRACKING_COOKIES, true).isEmpty() + social_media_trackers.isGone = bucketedTrackers.get(SOCIAL_MEDIA_TRACKERS, true).isEmpty() + fingerprinters.isGone = bucketedTrackers.get(FINGERPRINTERS, true).isEmpty() + tracking_content.isGone = bucketedTrackers.get(TRACKING_CONTENT, true).isEmpty() + cryptominers.isGone = bucketedTrackers.get(CRYPTOMINERS, true).isEmpty() - cross_site_tracking_loaded.isGone = - bucketedLoadedTrackers[CROSS_SITE_TRACKING_COOKIES].isEmpty() social_media_trackers_loaded.isGone = - bucketedLoadedTrackers[SOCIAL_MEDIA_TRACKERS].isEmpty() - fingerprinters_loaded.isGone = bucketedLoadedTrackers[FINGERPRINTERS].isEmpty() - tracking_content_loaded.isGone = bucketedLoadedTrackers[TRACKING_CONTENT].isEmpty() - cryptominers_loaded.isGone = bucketedLoadedTrackers[CRYPTOMINERS].isEmpty() + bucketedTrackers.get(SOCIAL_MEDIA_TRACKERS, false).isEmpty() + fingerprinters_loaded.isGone = bucketedTrackers.get(FINGERPRINTERS, false).isEmpty() + tracking_content_loaded.isGone = bucketedTrackers.get(TRACKING_CONTENT, false).isEmpty() + cryptominers_loaded.isGone = bucketedTrackers.get(CRYPTOMINERS, false).isEmpty() } private fun setCategoryClickListeners() { @@ -128,7 +125,6 @@ class TrackingProtectionPanelView( cryptominers.setOnClickListener(this) social_media_trackers_loaded.setOnClickListener(this) fingerprinters_loaded.setOnClickListener(this) - cross_site_tracking_loaded.setOnClickListener(this) tracking_content_loaded.setOnClickListener(this) cryptominers_loaded.setOnClickListener(this) } @@ -148,7 +144,7 @@ class TrackingProtectionPanelView( normal_mode.visibility = View.GONE details_mode.visibility = View.VISIBLE category_title.text = context.getString(category.title) - blocking_text_list.text = bucketedTrackers[category].joinToString("\n") + blocking_text_list.text = bucketedTrackers.get(category, categoryBlocked).joinToString("\n") category_description.text = context.getString(category.description) details_blocking_header.text = context.getString( if (categoryBlocked) { @@ -195,7 +191,7 @@ class TrackingProtectionPanelView( private fun getCategory(v: View) = when (v.id) { R.id.social_media_trackers, R.id.social_media_trackers_loaded -> SOCIAL_MEDIA_TRACKERS R.id.fingerprinters, R.id.fingerprinters_loaded -> FINGERPRINTERS - R.id.cross_site_tracking, R.id.cross_site_tracking_loaded -> CROSS_SITE_TRACKING_COOKIES + R.id.cross_site_tracking -> CROSS_SITE_TRACKING_COOKIES R.id.tracking_content, R.id.tracking_content_loaded -> TRACKING_CONTENT R.id.cryptominers, R.id.cryptominers_loaded -> CRYPTOMINERS else -> null @@ -207,7 +203,6 @@ class TrackingProtectionPanelView( private fun isLoaded(v: View) = when (v.id) { R.id.social_media_trackers_loaded, R.id.fingerprinters_loaded, - R.id.cross_site_tracking_loaded, R.id.tracking_content_loaded, R.id.cryptominers_loaded -> true diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionStore.kt b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionStore.kt index 3dd1e199c..66f31dfb0 100644 --- a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionStore.kt +++ b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionStore.kt @@ -5,7 +5,7 @@ package org.mozilla.fenix.trackingprotection import mozilla.components.browser.session.Session -import mozilla.components.concept.engine.content.blocking.Tracker +import mozilla.components.concept.engine.content.blocking.TrackerLog import mozilla.components.lib.state.Action import mozilla.components.lib.state.State import mozilla.components.lib.state.Store @@ -27,16 +27,12 @@ sealed class TrackingProtectionAction : Action { data class Change( val url: String, val isTrackingProtectionEnabled: Boolean, - val listTrackers: List, - val listTrackersLoaded: List, + val listTrackers: List, val mode: TrackingProtectionState.Mode ) : TrackingProtectionAction() data class UrlChange(val url: String) : TrackingProtectionAction() - data class TrackerListChange(val listTrackers: List) : TrackingProtectionAction() - data class TrackerLoadedListChange(val listTrackersLoaded: List) : - TrackingProtectionAction() - + data class TrackerLogChange(val listTrackers: List) : TrackingProtectionAction() data class TrackerBlockingChanged(val isTrackingProtectionEnabled: Boolean) : TrackingProtectionAction() @@ -52,16 +48,14 @@ sealed class TrackingProtectionAction : Action { * The state for the Tracking Protection Panel * @property url Current URL to display * @property isTrackingProtectionEnabled Current status of tracking protection for this session (ie is an exception) - * @property listTrackers List of currently blocked Trackers - * @property listTrackersLoaded List of currently not blocked Trackers + * @property listTrackers Current Tracker Log list of blocked and loaded tracker categories * @property mode Current Mode of TrackingProtection */ data class TrackingProtectionState( val session: Session?, val url: String, val isTrackingProtectionEnabled: Boolean, - val listTrackers: List, - val listTrackersLoaded: List, + val listTrackers: List, val mode: Mode ) : State { sealed class Mode { @@ -110,12 +104,7 @@ fun trackingProtectionStateReducer( is TrackingProtectionAction.UrlChange -> state.copy( url = action.url ) - is TrackingProtectionAction.TrackerListChange -> state.copy( - listTrackers = action.listTrackers - ) - is TrackingProtectionAction.TrackerLoadedListChange -> state.copy( - listTrackersLoaded = action.listTrackersLoaded - ) + is TrackingProtectionAction.TrackerLogChange -> state.copy(listTrackers = action.listTrackers) TrackingProtectionAction.ExitDetailsMode -> state.copy( mode = TrackingProtectionState.Mode.Normal ) diff --git a/app/src/main/res/layout/component_tracking_protection_panel.xml b/app/src/main/res/layout/component_tracking_protection_panel.xml index aebd3ab24..8dbbf56ff 100644 --- a/app/src/main/res/layout/component_tracking_protection_panel.xml +++ b/app/src/main/res/layout/component_tracking_protection_panel.xml @@ -111,16 +111,6 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tracking_content" /> - - + app:layout_constraintTop_toBottomOf="@id/not_blocking_header" /> (), buckets[CRYPTOMINERS]) + assertEquals(listOf("google.com"), buckets.buckets.blockedBucketMap[FINGERPRINTERS]) + assertEquals( + listOf("facebook.com"), + buckets.buckets.loadedBucketMap[TrackingProtectionCategory.SOCIAL_MEDIA_TRACKERS] + ) + assertTrue(buckets.buckets.blockedBucketMap[CRYPTOMINERS].isNullOrEmpty()) + assertTrue(buckets.buckets.loadedBucketMap[CRYPTOMINERS].isNullOrEmpty()) } @Test fun `sorts trackers into bucket`() { val buckets = TrackerBuckets() - buckets.updateIfNeeded(listOf( - Tracker("http://facebook.com", listOf(FINGERPRINTING, AD)), - Tracker("https://google.com", listOf(AD)), - Tracker("https://mozilla.com") - )) + buckets.updateIfNeeded( + listOf( + TrackerLog( + "http://facebook.com", + listOf(MOZILLA_SOCIAL) + ), + TrackerLog("https://google.com", listOf(), listOf(FINGERPRINTING)), + TrackerLog("https://mozilla.com") + ) + ) - assertEquals(mapOf( - TRACKING_CONTENT to listOf("google.com"), - FINGERPRINTERS to listOf("facebook.com") - ), buckets.buckets) + assertEquals( + mapOf( + TrackingProtectionCategory.SOCIAL_MEDIA_TRACKERS to listOf("facebook.com") + ), buckets.buckets.loadedBucketMap + ) + + assertEquals( + mapOf( + FINGERPRINTERS to listOf("google.com") + ), buckets.buckets.blockedBucketMap + ) } } diff --git a/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionStoreTest.kt b/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionStoreTest.kt index 405230031..636ba57db 100644 --- a/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionStoreTest.kt +++ b/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionStoreTest.kt @@ -5,11 +5,11 @@ package org.mozilla.fenix.trackingprotection import io.mockk.mockk -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotSame import kotlinx.coroutines.runBlocking import mozilla.components.browser.session.Session -import mozilla.components.concept.engine.content.blocking.Tracker +import mozilla.components.concept.engine.content.blocking.TrackerLog +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotSame import org.junit.Test class TrackingProtectionStoreTest { @@ -69,9 +69,9 @@ class TrackingProtectionStoreTest { fun trackerListChanged() = runBlocking { val initialState = defaultState() val store = TrackingProtectionStore(initialState) - val tracker = Tracker("url", listOf()) + val tracker = TrackerLog("url", listOf()) - store.dispatch(TrackingProtectionAction.TrackerListChange(listOf(tracker))).join() + store.dispatch(TrackingProtectionAction.TrackerLogChange(listOf(tracker))).join() assertNotSame(initialState, store.state) assertEquals( listOf(tracker), @@ -79,20 +79,6 @@ class TrackingProtectionStoreTest { ) } - @Test - fun trackerLoadedListChanged() = runBlocking { - val initialState = defaultState() - val store = TrackingProtectionStore(initialState) - val tracker = Tracker("url", listOf()) - - store.dispatch(TrackingProtectionAction.TrackerLoadedListChange(listOf(tracker))).join() - assertNotSame(initialState, store.state) - assertEquals( - listOf(tracker), - store.state.listTrackersLoaded - ) - } - @Test fun urlChanged() = runBlocking { val initialState = defaultState() @@ -110,14 +96,13 @@ class TrackingProtectionStoreTest { fun onChange() = runBlocking { val initialState = defaultState() val store = TrackingProtectionStore(initialState) - val tracker = Tracker("url", listOf()) + val tracker = TrackerLog("url", listOf(), listOf(), cookiesHasBeenBlocked = false) store.dispatch( TrackingProtectionAction.Change( "newURL", false, listOf(tracker), - listOf(), TrackingProtectionState.Mode.Details( TrackingProtectionCategory.FINGERPRINTERS, true @@ -133,10 +118,6 @@ class TrackingProtectionStoreTest { false, store.state.isTrackingProtectionEnabled ) - assertEquals( - listOf(), - store.state.listTrackersLoaded - ) assertEquals( listOf(tracker), store.state.listTrackers @@ -152,7 +133,6 @@ class TrackingProtectionStoreTest { url = "www.mozilla.org", isTrackingProtectionEnabled = true, listTrackers = listOf(), - listTrackersLoaded = listOf(), mode = TrackingProtectionState.Mode.Normal ) @@ -161,7 +141,6 @@ class TrackingProtectionStoreTest { url = "www.mozilla.org", isTrackingProtectionEnabled = true, listTrackers = listOf(), - listTrackersLoaded = listOf(), mode = TrackingProtectionState.Mode.Details(TrackingProtectionCategory.CRYPTOMINERS, true) ) }