For #18629: add support for SmartBlock exceptions

upstream-sync
Arturo Mejia 3 years ago committed by mergify[bot]
parent eba500558f
commit cbb8f808c1

@ -23,6 +23,7 @@ enum class BrowserDirection(@IdRes val fragmentId: Int) {
FromTrackingProtectionExceptions(R.id.trackingProtectionExceptionsFragment),
FromAbout(R.id.aboutFragment),
FromTrackingProtection(R.id.trackingProtectionFragment),
FromTrackingProtectionDialog(R.id.trackingProtectionPanelDialogFragment),
FromSavedLoginsFragment(R.id.savedLoginsFragment),
FromAddNewDeviceFragment(R.id.addNewDeviceFragment),
FromAddSearchEngineFragment(R.id.addSearchEngineFragment),

@ -116,6 +116,7 @@ import org.mozilla.fenix.tabstray.TabsTrayFragment
import org.mozilla.fenix.tabstray.TabsTrayFragmentDirections
import org.mozilla.fenix.theme.DefaultThemeManager
import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.trackingprotection.TrackingProtectionPanelDialogFragmentDirections
import org.mozilla.fenix.utils.BrowsersCache
import org.mozilla.fenix.utils.Settings
import java.lang.ref.WeakReference
@ -746,6 +747,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
AboutFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromTrackingProtection ->
TrackingProtectionFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromTrackingProtectionDialog ->
TrackingProtectionPanelDialogFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromSavedLoginsFragment ->
SavedLoginsAuthFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromAddNewDeviceFragment ->

@ -50,7 +50,8 @@ object SupportUtils {
SEARCH_SUGGESTION("how-search-firefox-preview"),
CUSTOM_SEARCH_ENGINES("custom-search-engines"),
SYNC_SETUP("how-set-firefox-sync-firefox-android"),
QR_CAMERA_ACCESS("qr-camera-access")
QR_CAMERA_ACCESS("qr-camera-access"),
SMARTBLOCK("smartblock-enhanced-tracking-protection")
}
enum class MozillaPage(internal val path: String) {

@ -7,7 +7,6 @@ package org.mozilla.fenix.trackingprotection
import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.TrackingCategory
import mozilla.components.concept.engine.content.blocking.Tracker
import mozilla.components.concept.engine.content.blocking.TrackerLog
import mozilla.components.support.ktx.kotlin.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
@ -15,7 +14,7 @@ import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.SOCIAL_ME
import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.TRACKING_CONTENT
import java.util.EnumMap
typealias BucketMap = Map<TrackingProtectionCategory, List<String>>
typealias BucketMap = Map<TrackingProtectionCategory, List<TrackerLog>>
/**
* Sorts [Tracker]s into different buckets and exposes them as a map.
@ -85,14 +84,14 @@ class TrackerBuckets {
* Create an empty mutable map of [TrackingProtectionCategory] to hostnames.
*/
private fun createMap() =
EnumMap<TrackingProtectionCategory, MutableList<String>>(TrackingProtectionCategory::class.java)
EnumMap<TrackingProtectionCategory, MutableList<TrackerLog>>(TrackingProtectionCategory::class.java)
/**
* Add the hostname of the [TrackerLog.url] into the map for the given category
* from Android Components. The category is transformed into a corresponding Fenix bucket,
* and the item is discarded if the category doesn't have a match.
*/
private fun MutableMap<TrackingProtectionCategory, MutableList<String>>.addTrackerHost(
private fun MutableMap<TrackingProtectionCategory, MutableList<TrackerLog>>.addTrackerHost(
category: TrackingCategory,
tracker: TrackerLog
) {
@ -107,13 +106,13 @@ class TrackerBuckets {
}
/**
* Add the hostname of the [TrackerLog.url] into the map for the given [TrackingProtectionCategory].
* Add the hostname of the [TrackerLog] into the map for the given [TrackingProtectionCategory].
*/
private fun MutableMap<TrackingProtectionCategory, MutableList<String>>.addTrackerHost(
private fun MutableMap<TrackingProtectionCategory, MutableList<TrackerLog>>.addTrackerHost(
key: TrackingProtectionCategory,
tracker: TrackerLog
) {
getOrPut(key) { mutableListOf() }.add(tracker.url.tryGetHostFromUrl())
getOrPut(key) { mutableListOf() }.add(tracker)
}
}
}

@ -37,6 +37,7 @@ import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.components.StoreProvider
@ -45,6 +46,7 @@ import org.mozilla.fenix.databinding.FragmentTrackingProtectionBinding
import org.mozilla.fenix.ext.metrics
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.settings.SupportUtils
@ExperimentalCoroutinesApi
@Suppress("TooManyFunctions")
@ -102,6 +104,7 @@ class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), UserInt
store = trackingProtectionStore,
navController = { findNavController() },
openTrackingProtectionSettings = ::openTrackingProtectionSettings,
openLearnMoreLink = ::handleLearnMoreClicked,
sitePermissions = args.sitePermissions,
gravity = args.gravity,
getCurrentTab = ::getCurrentTab
@ -149,6 +152,16 @@ class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), UserInt
)
}
private fun handleLearnMoreClicked() {
(activity as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getGenericSumoURLForTopic(
SupportUtils.SumoTopic.SMARTBLOCK
),
newTab = true,
from = BrowserDirection.FromTrackingProtectionDialog
)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return if (args.gravity == Gravity.BOTTOM) {
object : BottomSheetDialog(requireContext(), this.theme) {

@ -24,6 +24,7 @@ class TrackingProtectionPanelInteractor(
private val store: TrackingProtectionStore,
private val navController: () -> NavController,
private val openTrackingProtectionSettings: () -> Unit,
private val openLearnMoreLink: () -> Unit,
internal var sitePermissions: SitePermissions?,
private val gravity: Int,
private val getCurrentTab: () -> SessionState?
@ -33,6 +34,10 @@ class TrackingProtectionPanelInteractor(
store.dispatch(TrackingProtectionAction.EnterDetailsMode(category, categoryBlocked))
}
override fun onLearnMoreClicked() {
openLearnMoreLink()
}
override fun selectTrackingProtectionSettings() {
openTrackingProtectionSettings.invoke()
}

@ -4,21 +4,26 @@
package org.mozilla.fenix.trackingprotection
import android.text.method.LinkMovementMethod
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.text.HtmlCompat
import androidx.core.view.AccessibilityDelegateCompat
import androidx.core.view.ViewCompat
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import mozilla.components.browser.state.state.CustomTabSessionState
import mozilla.components.concept.engine.content.blocking.TrackerLog
import mozilla.components.support.ktx.kotlin.tryGetHostFromUrl
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.databinding.ComponentTrackingProtectionPanelBinding
import org.mozilla.fenix.ext.addUnderline
import org.mozilla.fenix.ext.metrics
import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.CROSS_SITE_TRACKING_COOKIES
import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.CRYPTOMINERS
@ -53,6 +58,11 @@ interface TrackingProtectionPanelViewInteractor {
* @param categoryBlocked The trackers from this category were blocked
*/
fun openDetails(category: TrackingProtectionCategory, categoryBlocked: Boolean)
/**
* Called when the Learn more link for SmartBlock is clicked.
*/
fun onLearnMoreClicked()
}
/**
@ -126,10 +136,28 @@ class TrackingProtectionPanelView(
category: TrackingProtectionCategory,
categoryBlocked: Boolean
) {
val containASmartBlockItem = bucketedTrackers.get(category, categoryBlocked).any { it.unBlockedBySmartBlock }
binding.normalMode.visibility = View.GONE
binding.detailsMode.visibility = View.VISIBLE
binding.categoryTitle.setText(category.title)
binding.blockingTextList.text = bucketedTrackers.get(category, categoryBlocked).joinToString("\n")
binding.smartblockDescription.isVisible = containASmartBlockItem
binding.smartblockLearnMore.isVisible = containASmartBlockItem
val trackersList = bucketedTrackers.get(category, categoryBlocked).joinToString("<br/>") {
createTrackerItem(it, containASmartBlockItem)
}
binding.blockingTextList.text = HtmlCompat.fromHtml(trackersList, HtmlCompat.FROM_HTML_MODE_COMPACT)
// show description for SmartBlock tracking content in details
if (containASmartBlockItem) {
with(binding.smartblockLearnMore) {
movementMethod = LinkMovementMethod.getInstance()
addUnderline()
setOnClickListener { interactor.onLearnMoreClicked() }
}
}
binding.categoryDescription.setText(category.description)
binding.detailsBlockingHeader.setText(
if (categoryBlocked) {
@ -143,6 +171,15 @@ class TrackingProtectionPanelView(
binding.detailsBack.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
}
private fun createTrackerItem(tracker: TrackerLog, isUnblockedSection: Boolean): String {
val space = if (isUnblockedSection) "&nbsp;&nbsp;" else ""
return if (tracker.unBlockedBySmartBlock) {
"<b>*${tracker.url.tryGetHostFromUrl()}</b>"
} else {
"$space${tracker.url.tryGetHostFromUrl()}"
}
}
/**
* Will force accessibility focus to last entered details category.
* Called when user returns from details_mode.

@ -242,6 +242,34 @@
app:layout_constraintTop_toBottomOf="@id/category_title"
tools:text="@tools:sample/lorem" />
<TextView
android:id="@+id/smartblock_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="52dp"
android:layout_marginEnd="19dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:textColor="?secondaryText"
android:drawablePadding="16dp"
app:drawableTint="?accent"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/category_description"
android:text="@string/preference_etp_smartblock_description" />
<org.mozilla.fenix.utils.LinkTextView
android:id="@+id/smartblock_learn_more"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/preference_etp_smartblock_learn_more"
android:textColor="?accent"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/smartblock_description"
app:layout_constraintTop_toBottomOf="@id/smartblock_description" />
<View
android:id="@+id/line_divider_details"
android:layout_width="match_parent"
@ -249,33 +277,35 @@
android:layout_marginTop="12dp"
android:layout_marginBottom="8dp"
android:background="?neutralFaded"
app:layout_constraintTop_toBottomOf="@id/category_description" />
<TextView
android:id="@+id/details_blocking_header"
android:accessibilityHeading="true"
style="@style/QuickSettingsText"
android:layout_width="wrap_content"
android:layout_height="@dimen/tracking_protection_item_height"
android:layout_marginStart="52dp"
android:layout_marginEnd="26dp"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:text="@string/enhanced_tracking_protection_blocked"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/line_divider_details"
tools:targetApi="p" />
app:layout_constraintTop_toBottomOf="@id/smartblock_learn_more" />
<androidx.core.widget.NestedScrollView
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/blocking_scrollview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true"
android:scrollbars="vertical"
android:orientation="vertical"
android:isScrollContainer="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/details_blocking_header"
app:layout_constraintTop_toBottomOf="@id/details_blocking_header">
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/line_divider_details">
<TextView
android:id="@+id/details_blocking_header"
android:accessibilityHeading="true"
style="@style/QuickSettingsText"
android:layout_width="wrap_content"
android:layout_height="@dimen/tracking_protection_item_height"
android:layout_marginStart="52dp"
android:layout_marginEnd="26dp"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:text="@string/enhanced_tracking_protection_blocked"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/blocking_scrollview"
tools:targetApi="p" />
<TextView
android:id="@+id/blocking_text_list"
@ -286,6 +316,6 @@
android:lineSpacingMultiplier="1.2"
android:layout_marginBottom="12dp"
tools:text="@tools:sample/lorem/random" />
</androidx.core.widget.NestedScrollView>
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1439,6 +1439,10 @@
<string name="etp_redirect_trackers_title">Redirect Trackers</string>
<!-- Description of redirect tracker cookies that can be blocked by Enhanced Tracking Protection -->
<string name="etp_redirect_trackers_description">Clears cookies set by redirects to known tracking websites.</string>
<!-- Description of the SmartBlock Enhanced Tracking Protection feature -->
<string name="preference_etp_smartblock_description">Some trackers marked below have been partially unblocked on this page because you interacted with them *.</string>
<!-- Text displayed that links to website about enhanced tracking protection SmartBlock -->
<string name="preference_etp_smartblock_learn_more">Learn more</string>
<!-- About page link text to open support link -->
<string name="about_support">Support</string>

@ -27,21 +27,22 @@ class TrackerBucketsTest {
@Test
fun `getter accesses corresponding bucket`() {
val buckets = TrackerBuckets()
val google = TrackerLog("https://google.com", listOf(), listOf(FINGERPRINTING))
val facebook = TrackerLog("http://facebook.com", listOf(MOZILLA_SOCIAL))
buckets.updateIfNeeded(
listOf(
TrackerLog(
"http://facebook.com",
listOf(MOZILLA_SOCIAL)
),
TrackerLog("https://google.com", listOf(), listOf(FINGERPRINTING)),
google,
facebook,
TrackerLog("https://mozilla.com")
)
)
assertEquals(listOf("google.com"), buckets.buckets.blockedBucketMap[FINGERPRINTERS])
assertEquals(google, buckets.buckets.blockedBucketMap[FINGERPRINTERS]!!.first())
assertEquals(
listOf("facebook.com"),
facebook,
buckets.buckets.loadedBucketMap[FenixTrackingProtectionCategory.SOCIAL_MEDIA_TRACKERS]
!!.first()
)
assertTrue(buckets.buckets.blockedBucketMap[CRYPTOMINERS].isNullOrEmpty())
assertTrue(buckets.buckets.loadedBucketMap[CRYPTOMINERS].isNullOrEmpty())
@ -50,27 +51,27 @@ class TrackerBucketsTest {
@Test
fun `sorts trackers into bucket`() {
val buckets = TrackerBuckets()
val google = TrackerLog("https://google.com", listOf(), listOf(FINGERPRINTING))
val facebook = TrackerLog("http://facebook.com", listOf(MOZILLA_SOCIAL))
val mozilla = TrackerLog("https://mozilla.com")
buckets.updateIfNeeded(
listOf(
TrackerLog(
"http://facebook.com",
listOf(MOZILLA_SOCIAL)
),
TrackerLog("https://google.com", listOf(), listOf(FINGERPRINTING)),
TrackerLog("https://mozilla.com")
facebook,
google,
mozilla
)
)
assertEquals(
mapOf(
FenixTrackingProtectionCategory.SOCIAL_MEDIA_TRACKERS to listOf("facebook.com")
FenixTrackingProtectionCategory.SOCIAL_MEDIA_TRACKERS to listOf(facebook)
),
buckets.buckets.loadedBucketMap
)
assertEquals(
mapOf(
FINGERPRINTERS to listOf("google.com")
FINGERPRINTERS to listOf(google)
),
buckets.buckets.blockedBucketMap
)
@ -86,24 +87,21 @@ class TrackerBucketsTest {
SCRIPTS_AND_SUB_RESOURCES
)
buckets.updateIfNeeded(
listOf(
TrackerLog(
url = "http://facebook.com",
cookiesHasBeenBlocked = true,
blockedCategories = acCategories,
loadedCategories = acCategories
)
)
val trackerLog = TrackerLog(
url = "http://facebook.com",
cookiesHasBeenBlocked = true,
blockedCategories = acCategories,
loadedCategories = acCategories
)
buckets.updateIfNeeded(listOf(trackerLog))
val expectedBlockedMap =
mapOf(
FenixTrackingProtectionCategory.SOCIAL_MEDIA_TRACKERS to listOf("facebook.com"),
FenixTrackingProtectionCategory.TRACKING_CONTENT to listOf("facebook.com"),
FenixTrackingProtectionCategory.FINGERPRINTERS to listOf("facebook.com"),
FenixTrackingProtectionCategory.CRYPTOMINERS to listOf("facebook.com"),
FenixTrackingProtectionCategory.CROSS_SITE_TRACKING_COOKIES to listOf("facebook.com")
FenixTrackingProtectionCategory.SOCIAL_MEDIA_TRACKERS to listOf(trackerLog),
FenixTrackingProtectionCategory.TRACKING_CONTENT to listOf(trackerLog),
FenixTrackingProtectionCategory.FINGERPRINTERS to listOf(trackerLog),
FenixTrackingProtectionCategory.CRYPTOMINERS to listOf(trackerLog),
FenixTrackingProtectionCategory.CROSS_SITE_TRACKING_COOKIES to listOf(trackerLog)
)
val expectedLoadedMap =
expectedBlockedMap - FenixTrackingProtectionCategory.CROSS_SITE_TRACKING_COOKIES

@ -48,12 +48,14 @@ class TrackingProtectionPanelInteractorTest {
private lateinit var tab: TabSessionState
private var learnMoreClicked = false
private var openSettings = false
private var gravity = 54
@Before
fun setup() {
MockKAnnotations.init(this)
learnMoreClicked = false
context = spyk(testContext)
tab = createTab("https://mozilla.org")
@ -64,6 +66,7 @@ class TrackingProtectionPanelInteractorTest {
store = store,
navController = { navController },
openTrackingProtectionSettings = { openSettings = true },
openLearnMoreLink = { learnMoreClicked = true },
sitePermissions = sitePermissions,
gravity = gravity,
getCurrentTab = { tab }
@ -115,6 +118,13 @@ class TrackingProtectionPanelInteractorTest {
assertEquals(true, openSettings)
}
@Test
fun `WHEN on the learn more link is clicked THEN onLearnMoreClicked should be invoked`() {
interactor.onLearnMoreClicked()
assertEquals(true, learnMoreClicked)
}
@Test
fun `WHEN onBackPressed is called THEN call popBackStack and navigate`() {
interactor.onBackPressed()

Loading…
Cancel
Save