1
0
Fork 0

Use new API for ETP Exceptions

master
ekager 2019-10-01 21:45:04 -07:00 committed by Emily Kager
parent ca8ba57635
commit 18c0525ff6
15 changed files with 170 additions and 216 deletions

View File

@ -1378,7 +1378,7 @@ tracking_protection:
etp_setting_changed: etp_setting_changed:
type: event type: event
description: > description: >
A user added a tracking protection exception through the TP toggle in the panel. A user changed their tracking protection level setting to either strict or standard.
extra_keys: extra_keys:
etp_setting: etp_setting:
description: "The new setting for ETP: strict, standard" description: "The new setting for ETP: strict, standard"

View File

@ -11,17 +11,15 @@ import mozilla.components.browser.errorpages.ErrorType
import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.request.RequestInterceptor import mozilla.components.concept.engine.request.RequestInterceptor
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.exceptions.ExceptionDomains
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.tryGetHostFromUrl
class AppRequestInterceptor(private val context: Context) : RequestInterceptor { class AppRequestInterceptor(private val context: Context) : RequestInterceptor {
override fun onLoadRequest(session: EngineSession, uri: String): RequestInterceptor.InterceptionResponse? { override fun onLoadRequest(
val host = uri.tryGetHostFromUrl() session: EngineSession,
uri: String
adjustTrackingProtection(host, context, session) ): RequestInterceptor.InterceptionResponse? {
adjustTrackingProtection(context, session)
// WebChannel-driven authentication does not require a separate redirect interceptor. // WebChannel-driven authentication does not require a separate redirect interceptor.
return if (context.isInExperiment(Experiments.asFeatureWebChannelsDisabled)) { return if (context.isInExperiment(Experiments.asFeatureWebChannelsDisabled)) {
context.components.services.accountsAuthFeature.interceptor.onLoadRequest(session, uri) context.components.services.accountsAuthFeature.interceptor.onLoadRequest(session, uri)
@ -30,10 +28,9 @@ class AppRequestInterceptor(private val context: Context) : RequestInterceptor {
} }
} }
private fun adjustTrackingProtection(host: String, context: Context, session: EngineSession) { private fun adjustTrackingProtection(context: Context, session: EngineSession) {
val trackingProtectionException = ExceptionDomains(context).load().contains(host)
val trackingProtectionEnabled = context.settings().shouldUseTrackingProtection val trackingProtectionEnabled = context.settings().shouldUseTrackingProtection
if (trackingProtectionException || !trackingProtectionEnabled) { if (!trackingProtectionEnabled) {
session.disableTrackingProtection() session.disableTrackingProtection()
} else { } else {
val core = context.components.core val core = context.components.core

View File

@ -36,6 +36,7 @@ import mozilla.appservices.places.BookmarkRoot
import mozilla.components.browser.session.Session import mozilla.components.browser.session.Session
import mozilla.components.feature.contextmenu.ContextMenuCandidate import mozilla.components.feature.contextmenu.ContextMenuCandidate
import mozilla.components.feature.readerview.ReaderViewFeature import mozilla.components.feature.readerview.ReaderViewFeature
import mozilla.components.feature.session.TrackingProtectionUseCases
import mozilla.components.feature.sitepermissions.SitePermissions import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.base.feature.BackHandler import mozilla.components.support.base.feature.BackHandler
@ -224,14 +225,21 @@ class BrowserFragment : BaseBrowserFragment(), BackHandler {
} }
override fun navToTrackingProtectionPanel(session: Session) { override fun navToTrackingProtectionPanel(session: Session) {
val directions = val useCase = TrackingProtectionUseCases(
BrowserFragmentDirections.actionBrowserFragmentToTrackingProtectionPanelDialogFragment( sessionManager = requireComponents.core.sessionManager,
sessionId = session.id, engine = requireComponents.core.engine
url = session.url, )
trackingProtectionEnabled = session.trackerBlockingEnabled, useCase.containsException(session) { contains ->
gravity = getAppropriateLayoutGravity() val isEnabled = session.trackerBlockingEnabled && !contains
) val directions =
nav(R.id.browserFragment, directions) BrowserFragmentDirections.actionBrowserFragmentToTrackingProtectionPanelDialogFragment(
sessionId = session.id,
url = session.url,
trackingProtectionEnabled = isEnabled,
gravity = getAppropriateLayoutGravity()
)
nav(R.id.browserFragment, directions)
}
} }
override fun getEngineMargins(): Pair<Int, Int> { override fun getEngineMargins(): Pair<Int, Int> {

View File

@ -21,6 +21,7 @@ import mozilla.components.feature.pwa.ext.trustedOrigins
import mozilla.components.feature.pwa.feature.WebAppActivityFeature import mozilla.components.feature.pwa.feature.WebAppActivityFeature
import mozilla.components.feature.pwa.feature.WebAppHideToolbarFeature import mozilla.components.feature.pwa.feature.WebAppHideToolbarFeature
import mozilla.components.feature.pwa.feature.WebAppSiteControlsFeature import mozilla.components.feature.pwa.feature.WebAppSiteControlsFeature
import mozilla.components.feature.session.TrackingProtectionUseCases
import mozilla.components.feature.sitepermissions.SitePermissions import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.base.feature.BackHandler import mozilla.components.support.base.feature.BackHandler
@ -154,15 +155,22 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), BackHandler {
} }
override fun navToTrackingProtectionPanel(session: Session) { override fun navToTrackingProtectionPanel(session: Session) {
val directions = val useCase = TrackingProtectionUseCases(
ExternalAppBrowserFragmentDirections sessionManager = requireComponents.core.sessionManager,
.actionExternalAppBrowserFragmentToTrackingProtectionPanelDialogFragment( engine = requireComponents.core.engine
sessionId = session.id, )
url = session.url, useCase.containsException(session) { contains ->
trackingProtectionEnabled = session.trackerBlockingEnabled, val isEnabled = session.trackerBlockingEnabled && !contains
gravity = getAppropriateLayoutGravity() val directions =
) ExternalAppBrowserFragmentDirections
nav(R.id.externalAppBrowserFragment, directions) .actionExternalAppBrowserFragmentToTrackingProtectionPanelDialogFragment(
sessionId = session.id,
url = session.url,
trackingProtectionEnabled = isEnabled,
gravity = getAppropriateLayoutGravity()
)
nav(R.id.externalAppBrowserFragment, directions)
}
} }
override fun getEngineMargins(): Pair<Int, Int> { override fun getEngineMargins(): Pair<Int, Int> {

View File

@ -1,87 +0,0 @@
/* 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.exceptions
import android.content.Context
import android.content.SharedPreferences
import mozilla.components.support.ktx.android.content.PreferencesHolder
import mozilla.components.support.ktx.android.content.stringPreference
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.metrics
/**
* Contains functionality to manage custom domains for allow-list.
*/
class ExceptionDomains(val context: Context) : PreferencesHolder {
override val preferences: SharedPreferences =
context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE)
private var domains by stringPreference(KEY_DOMAINS, default = "")
/**
* Loads the previously added/saved custom domains from preferences.
*
* @return list of custom domains
*/
fun load(): List<String> {
if (exceptions == null) {
exceptions = domains.split(SEPARATOR).filter { it.isNotEmpty() }
}
return exceptions.orEmpty()
}
/**
* Saves the provided domains to preferences.
*
* @param domains list of domains
*/
fun save(domains: List<String>) {
exceptions = domains
this.domains = domains.joinToString(separator = SEPARATOR)
}
/**
* Adds the provided domain to preferences.
*
* @param domain the domain to add
*/
fun add(domain: String) {
save(domains = load() + domain)
}
/**
* Removes the provided domain from preferences.
*
* @param domains the domain to remove
*/
fun remove(domains: List<String>) {
save(domains = load() - domains)
}
/**
* Adds or removes the provided domain from preferences.
*
* If present, the domain will be removed. Otherwise, it will be added.
*/
fun toggle(domain: String) {
if (domain in load()) {
remove(listOf(domain))
} else {
context.metrics.track(Event.TrackingProtectionException)
add(domain)
}
}
companion object {
private const val PREFERENCE_NAME = "exceptions"
private const val KEY_DOMAINS = "exceptions_domains"
private const val SEPARATOR = "@<;>@"
private var exceptions: List<String>? = null
}
}

View File

@ -5,29 +5,28 @@
package org.mozilla.fenix.exceptions package org.mozilla.fenix.exceptions
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import kotlinx.android.synthetic.main.fragment_exceptions.view.* import kotlinx.android.synthetic.main.fragment_exceptions.view.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.coroutineScope import mozilla.components.feature.session.TrackingProtectionUseCases
import kotlinx.coroutines.launch
import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.lib.state.ext.consumeFrom
import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.settings.SupportUtils
class ExceptionsFragment : Fragment() { class ExceptionsFragment : Fragment() {
private lateinit var exceptionsStore: ExceptionsFragmentStore private lateinit var exceptionsStore: ExceptionsFragmentStore
private lateinit var exceptionsView: ExceptionsView private lateinit var exceptionsView: ExceptionsView
private lateinit var exceptionsInteractor: ExceptionsInteractor private lateinit var exceptionsInteractor: ExceptionsInteractor
private lateinit var trackingProtectionUseCases: TrackingProtectionUseCases
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
@ -41,16 +40,21 @@ class ExceptionsFragment : Fragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
val view = inflater.inflate(R.layout.fragment_exceptions, container, false) val view = inflater.inflate(R.layout.fragment_exceptions, container, false)
trackingProtectionUseCases = TrackingProtectionUseCases(
sessionManager = view.context.components.core.sessionManager,
engine = view.context.components.core.engine
)
exceptionsStore = StoreProvider.get(this) { exceptionsStore = StoreProvider.get(this) {
ExceptionsFragmentStore( ExceptionsFragmentStore(
ExceptionsFragmentState( ExceptionsFragmentState(
items = loadAndMapExceptions() items = listOf()
) )
) )
} }
exceptionsInteractor = exceptionsInteractor =
ExceptionsInteractor(::openLearnMore, ::deleteOneItem, ::deleteAllItems) ExceptionsInteractor(::openLearnMore, ::deleteOneItem, ::deleteAllItems)
exceptionsView = ExceptionsView(view.exceptionsLayout, exceptionsInteractor) exceptionsView = ExceptionsView(view.exceptionsLayout, exceptionsInteractor)
reloadExceptions()
return view return view
} }
@ -63,20 +67,15 @@ class ExceptionsFragment : Fragment() {
} }
private fun deleteAllItems() { private fun deleteAllItems() {
viewLifecycleOwner.lifecycleScope.launch(IO) { trackingProtectionUseCases.removeAllExceptions()
ExceptionDomains(requireContext()).run { reloadExceptions()
val domains = load()
remove(domains)
}
reloadData()
}
} }
private fun deleteOneItem(item: ExceptionsItem) { private fun deleteOneItem(item: ExceptionsItem) {
viewLifecycleOwner.lifecycleScope.launch(IO) { // We can't currently delete one item in this Exceptions list with a URL with the GV API
ExceptionDomains(requireContext()).remove(listOf(item.url)) // See https://github.com/mozilla-mobile/android-components/issues/4699
reloadData() Log.e("Remove one exception", "$item")
} reloadExceptions()
} }
private fun openLearnMore() { private fun openLearnMore() {
@ -88,18 +87,13 @@ class ExceptionsFragment : Fragment() {
) )
} }
private fun loadAndMapExceptions(): List<ExceptionsItem> { private fun reloadExceptions() {
return ExceptionDomains(requireContext()).load() trackingProtectionUseCases.fetchExceptions { resultList ->
.map { item -> ExceptionsItem(item) } exceptionsStore.dispatch(ExceptionsFragmentAction.Change(resultList.map {
} ExceptionsItem(
it
private suspend fun reloadData() { )
val items = loadAndMapExceptions() }))
coroutineScope {
launch(Main) {
exceptionsStore.dispatch(ExceptionsFragmentAction.Change(items))
}
} }
} }
} }

View File

@ -14,13 +14,14 @@ import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import mozilla.components.browser.session.Session import mozilla.components.browser.session.Session
import mozilla.components.feature.session.SessionUseCases.ReloadUrlUseCase import mozilla.components.feature.session.SessionUseCases.ReloadUrlUseCase
import mozilla.components.feature.session.TrackingProtectionUseCases
import mozilla.components.feature.sitepermissions.SitePermissions import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.feature.tabs.TabsUseCases.AddNewTabUseCase import mozilla.components.feature.tabs.TabsUseCases.AddNewTabUseCase
import mozilla.components.support.base.feature.OnNeedToRequestPermissions import mozilla.components.support.base.feature.OnNeedToRequestPermissions
import org.mozilla.fenix.browser.BrowserFragment import org.mozilla.fenix.browser.BrowserFragment
import org.mozilla.fenix.components.PermissionStorage import org.mozilla.fenix.components.PermissionStorage
import org.mozilla.fenix.exceptions.ExceptionDomains import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.tryGetHostFromUrl import org.mozilla.fenix.ext.metrics
import org.mozilla.fenix.settings.PhoneFeature import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.quicksettings.ext.shouldBeEnabled import org.mozilla.fenix.settings.quicksettings.ext.shouldBeEnabled
import org.mozilla.fenix.settings.toggle import org.mozilla.fenix.settings.toggle
@ -36,9 +37,8 @@ interface QuickSettingsController {
/** /**
* Handles turning on/off tracking protection. * Handles turning on/off tracking protection.
* *
* @param websiteUrl [String] the website URL for which to toggle tracking protection. * */
*/ fun handleTrackingProtectionToggled(trackingEnabled: Boolean)
fun handleTrackingProtectionToggled(websiteUrl: String, trackingEnabled: Boolean)
/** /**
* Handles showing the tracking protection settings. * Handles showing the tracking protection settings.
@ -89,7 +89,6 @@ interface QuickSettingsController {
* @param sitePermissions [SitePermissions]? list of website permissions and their status. * @param sitePermissions [SitePermissions]? list of website permissions and their status.
* @param settings [Settings] application settings. * @param settings [Settings] application settings.
* @param permissionStorage [PermissionStorage] app state for website permissions exception. * @param permissionStorage [PermissionStorage] app state for website permissions exception.
* @param trackingExceptions [ExceptionDomains] allows setting whether to allow trackers or not.
* @param reload [ReloadUrlUseCase] callback allowing for reloading the current web page. * @param reload [ReloadUrlUseCase] callback allowing for reloading the current web page.
* @param addNewTab [AddNewTabUseCase] callback allowing for loading a URL in a new tab. * @param addNewTab [AddNewTabUseCase] callback allowing for loading a URL in a new tab.
* @param requestRuntimePermissions [OnNeedToRequestPermissions] callback allowing for requesting * @param requestRuntimePermissions [OnNeedToRequestPermissions] callback allowing for requesting
@ -98,6 +97,7 @@ interface QuickSettingsController {
* @param displayTrackingProtection callback for when the [TrackingProtectionView] needs to be displayed. * @param displayTrackingProtection callback for when the [TrackingProtectionView] needs to be displayed.
* @param displayPermissions callback for when [WebsitePermissionsView] needs to be displayed. * @param displayPermissions callback for when [WebsitePermissionsView] needs to be displayed.
* @param dismiss callback allowing to request this entire Fragment to be dismissed. * @param dismiss callback allowing to request this entire Fragment to be dismissed.
* @param trackingProtectionUseCases usecase allowing us to add or remove tracking protection exceptions
*/ */
@Suppress("TooManyFunctions") @Suppress("TooManyFunctions")
class DefaultQuickSettingsController( class DefaultQuickSettingsController(
@ -109,22 +109,28 @@ class DefaultQuickSettingsController(
private var sitePermissions: SitePermissions?, private var sitePermissions: SitePermissions?,
private val settings: Settings, private val settings: Settings,
private val permissionStorage: PermissionStorage, private val permissionStorage: PermissionStorage,
private val trackingExceptions: ExceptionDomains,
private val reload: ReloadUrlUseCase, private val reload: ReloadUrlUseCase,
private val addNewTab: AddNewTabUseCase, private val addNewTab: AddNewTabUseCase,
private val requestRuntimePermissions: OnNeedToRequestPermissions = { }, private val requestRuntimePermissions: OnNeedToRequestPermissions = { },
private val reportSiteIssue: () -> Unit, private val reportSiteIssue: () -> Unit,
private val displayTrackingProtection: () -> Unit, private val displayTrackingProtection: () -> Unit,
private val displayPermissions: () -> Unit, private val displayPermissions: () -> Unit,
private val dismiss: () -> Unit private val dismiss: () -> Unit,
private val trackingProtectionUseCases: TrackingProtectionUseCases
) : QuickSettingsController { ) : QuickSettingsController {
override fun handleTrackingProtectionToggled( override fun handleTrackingProtectionToggled(
websiteUrl: String,
trackingEnabled: Boolean trackingEnabled: Boolean
) { ) {
val host = websiteUrl.tryGetHostFromUrl() session?.let {
trackingExceptions.toggle(host) if (trackingEnabled) {
trackingProtectionUseCases.removeException(it)
} else {
context.metrics.track(Event.TrackingProtectionException)
trackingProtectionUseCases.addException(it)
}
}
reload(session) reload(session)
quickSettingsStore.dispatch( quickSettingsStore.dispatch(
@ -246,13 +252,17 @@ class DefaultQuickSettingsController(
return when (this) { return when (this) {
PhoneFeature.CAMERA -> WebsitePermission.Camera( PhoneFeature.CAMERA -> WebsitePermission.Camera(
defaultStatus, defaultVisible, defaultEnabled, defaultBlockedByAndroid) defaultStatus, defaultVisible, defaultEnabled, defaultBlockedByAndroid
)
PhoneFeature.LOCATION -> WebsitePermission.Location( PhoneFeature.LOCATION -> WebsitePermission.Location(
defaultStatus, defaultVisible, defaultEnabled, defaultBlockedByAndroid) defaultStatus, defaultVisible, defaultEnabled, defaultBlockedByAndroid
)
PhoneFeature.MICROPHONE -> WebsitePermission.Microphone( PhoneFeature.MICROPHONE -> WebsitePermission.Microphone(
defaultStatus, defaultVisible, defaultEnabled, defaultBlockedByAndroid) defaultStatus, defaultVisible, defaultEnabled, defaultBlockedByAndroid
)
PhoneFeature.NOTIFICATION -> WebsitePermission.Notification( PhoneFeature.NOTIFICATION -> WebsitePermission.Notification(
defaultStatus, defaultVisible, defaultEnabled, defaultBlockedByAndroid) defaultStatus, defaultVisible, defaultEnabled, defaultBlockedByAndroid
)
PhoneFeature.AUTOPLAY -> defaultWebsitePermission!! // fail-fast PhoneFeature.AUTOPLAY -> defaultWebsitePermission!! // fail-fast
} }
} }

View File

@ -21,8 +21,8 @@ class QuickSettingsInteractor(
controller.handleReportTrackingProblem(websiteUrl) controller.handleReportTrackingProblem(websiteUrl)
} }
override fun onProtectionToggled(websiteUrl: String, trackingEnabled: Boolean) { override fun onProtectionToggled(trackingEnabled: Boolean) {
controller.handleTrackingProtectionToggled(websiteUrl, trackingEnabled) controller.handleTrackingProtectionToggled(trackingEnabled)
} }
override fun onProtectionSettingsSelected() { override fun onProtectionSettingsSelected() {

View File

@ -26,11 +26,11 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
import kotlinx.android.synthetic.main.fragment_quick_settings_dialog_sheet.* import kotlinx.android.synthetic.main.fragment_quick_settings_dialog_sheet.*
import kotlinx.android.synthetic.main.fragment_quick_settings_dialog_sheet.view.* import kotlinx.android.synthetic.main.fragment_quick_settings_dialog_sheet.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.feature.session.TrackingProtectionUseCases
import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.lib.state.ext.consumeFrom
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.IntentReceiverActivity import org.mozilla.fenix.IntentReceiverActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.exceptions.ExceptionDomains
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.settings.PhoneFeature import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
@ -50,7 +50,11 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
private lateinit var websiteTrackingProtectionView: TrackingProtectionView private lateinit var websiteTrackingProtectionView: TrackingProtectionView
private lateinit var interactor: QuickSettingsInteractor private lateinit var interactor: QuickSettingsInteractor
private val safeArguments get() = requireNotNull(arguments) private val safeArguments get() = requireNotNull(arguments)
private val promptGravity: Int by lazy { QuickSettingsSheetDialogFragmentArgs.fromBundle(safeArguments).gravity } private val promptGravity: Int by lazy {
QuickSettingsSheetDialogFragmentArgs.fromBundle(
safeArguments
).gravity
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -80,7 +84,6 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
sitePermissions = args.sitePermissions, sitePermissions = args.sitePermissions,
settings = Settings.getInstance(context), settings = Settings.getInstance(context),
permissionStorage = context.components.core.permissionStorage, permissionStorage = context.components.core.permissionStorage,
trackingExceptions = ExceptionDomains(context),
reload = context.components.useCases.sessionUseCases.reload, reload = context.components.useCases.sessionUseCases.reload,
addNewTab = context.components.useCases.tabsUseCases.addTab, addNewTab = context.components.useCases.tabsUseCases.addTab,
requestRuntimePermissions = { permissions -> requestRuntimePermissions = { permissions ->
@ -89,13 +92,20 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
reportSiteIssue = ::launchIntentReceiver, reportSiteIssue = ::launchIntentReceiver,
displayTrackingProtection = ::showTrackingProtectionView, displayTrackingProtection = ::showTrackingProtectionView,
displayPermissions = ::showPermissionsView, displayPermissions = ::showPermissionsView,
dismiss = ::dismiss dismiss = ::dismiss,
trackingProtectionUseCases = TrackingProtectionUseCases(
context.components.core.sessionManager,
context.components.core.engine
)
) )
interactor = QuickSettingsInteractor(quickSettingsController) interactor = QuickSettingsInteractor(quickSettingsController)
websiteTrackingProtectionView = TrackingProtectionView(rootView.trackingProtectionLayout, interactor) websiteTrackingProtectionView =
TrackingProtectionView(rootView.trackingProtectionLayout, interactor)
websiteInfoView = WebsiteInfoView(rootView.websiteInfoLayout) websiteInfoView = WebsiteInfoView(rootView.websiteInfoLayout)
websitePermissionsView = WebsitePermissionsView(rootView.websitePermissionsLayout, interactor) websitePermissionsView =
WebsitePermissionsView(rootView.websitePermissionsLayout, interactor)
return rootView return rootView
} }

View File

@ -26,7 +26,7 @@ interface TrackingProtectionInteractor {
/** /**
* Indicates the user want to toggle the tracking protection on / off. * Indicates the user want to toggle the tracking protection on / off.
*/ */
fun onProtectionToggled(websiteUrl: String, trackingEnabled: Boolean) fun onProtectionToggled(trackingEnabled: Boolean)
/** /**
* Indicates the user want to see all tracking protection settings. * Indicates the user want to see all tracking protection settings.
@ -84,7 +84,7 @@ class TrackingProtectionView(
trackingProtectionSwitch.isEnabled = state.isTrackingProtectionEnabledPerApp trackingProtectionSwitch.isEnabled = state.isTrackingProtectionEnabledPerApp
if (state.isTrackingProtectionEnabledPerApp) { if (state.isTrackingProtectionEnabledPerApp) {
trackingProtectionSwitch.setOnCheckedChangeListener { _, isChecked -> trackingProtectionSwitch.setOnCheckedChangeListener { _, isChecked ->
interactor.onProtectionToggled(state.websiteUrl, isChecked) interactor.onProtectionToggled(isChecked)
} }
} }
} }

View File

@ -32,12 +32,10 @@ import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.exceptions.ExceptionDomains
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.ext.metrics
import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.tryGetHostFromUrl
class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), BackHandler { class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), BackHandler {
@ -172,12 +170,22 @@ class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), BackHan
private fun toggleTrackingProtection(isEnabled: Boolean) { private fun toggleTrackingProtection(isEnabled: Boolean) {
context?.let { context -> context?.let { context ->
val host = url.tryGetHostFromUrl() val useCase = TrackingProtectionUseCases(
lifecycleScope.launch { sessionManager = context.components.core.sessionManager,
ExceptionDomains(context).toggle(host) engine = context.components.core.engine
} )
with(context.components) { val session = context.components.core.sessionManager.findSessionById(sessionId)
useCases.sessionUseCases.reload.invoke(core.sessionManager.findSessionById(sessionId)) session?.let {
if (isEnabled) {
useCase.removeException(it)
} else {
context.metrics.track(Event.TrackingProtectionException)
useCase.addException(it)
}
with(context.components) {
useCases.sessionUseCases.reload.invoke(session)
}
} }
} }
trackingProtectionStore.dispatch(TrackingProtectionAction.TrackerBlockingChanged(isEnabled)) trackingProtectionStore.dispatch(TrackingProtectionAction.TrackerBlockingChanged(isEnabled))

View File

@ -47,6 +47,7 @@
tools:text="mozilla.org" /> tools:text="mozilla.org" />
<ImageButton <ImageButton
android:visibility="gone"
android:id="@+id/delete_exception" android:id="@+id/delete_exception"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -24,6 +24,7 @@ import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import mozilla.components.browser.session.Session import mozilla.components.browser.session.Session
import mozilla.components.feature.session.SessionUseCases import mozilla.components.feature.session.SessionUseCases
import mozilla.components.feature.session.TrackingProtectionUseCases
import mozilla.components.feature.sitepermissions.SitePermissions import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.feature.sitepermissions.SitePermissions.Status.NO_DECISION import mozilla.components.feature.sitepermissions.SitePermissions.Status.NO_DECISION
import mozilla.components.feature.tabs.TabsUseCases import mozilla.components.feature.tabs.TabsUseCases
@ -33,7 +34,8 @@ import org.junit.runner.RunWith
import org.mozilla.fenix.TestApplication import org.mozilla.fenix.TestApplication
import org.mozilla.fenix.browser.BrowserFragment import org.mozilla.fenix.browser.BrowserFragment
import org.mozilla.fenix.components.PermissionStorage import org.mozilla.fenix.components.PermissionStorage
import org.mozilla.fenix.exceptions.ExceptionDomains import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.metrics
import org.mozilla.fenix.settings.PhoneFeature import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.quicksettings.ext.shouldBeEnabled import org.mozilla.fenix.settings.quicksettings.ext.shouldBeEnabled
import org.mozilla.fenix.settings.toggle import org.mozilla.fenix.settings.toggle
@ -53,7 +55,6 @@ class DefaultQuickSettingsControllerTest {
private val sitePermissions = SitePermissions(origin = "", savedAt = 123) private val sitePermissions = SitePermissions(origin = "", savedAt = 123)
private val appSettings = mockk<Settings>(relaxed = true) private val appSettings = mockk<Settings>(relaxed = true)
private val permissionStorage = mockk<PermissionStorage>(relaxed = true) private val permissionStorage = mockk<PermissionStorage>(relaxed = true)
private val trackingExceptions = mockk<ExceptionDomains>(relaxed = true)
private val reload = mockk<SessionUseCases.ReloadUrlUseCase>(relaxed = true) private val reload = mockk<SessionUseCases.ReloadUrlUseCase>(relaxed = true)
private val addNewTab = mockk<TabsUseCases.AddNewTabUseCase>(relaxed = true) private val addNewTab = mockk<TabsUseCases.AddNewTabUseCase>(relaxed = true)
private val requestPermissions = mockk<(Array<String>) -> Unit>(relaxed = true) private val requestPermissions = mockk<(Array<String>) -> Unit>(relaxed = true)
@ -61,6 +62,7 @@ class DefaultQuickSettingsControllerTest {
private val displayTrackingProtection = mockk<() -> Unit>(relaxed = true) private val displayTrackingProtection = mockk<() -> Unit>(relaxed = true)
private val displayPermissions = mockk<() -> Unit>(relaxed = true) private val displayPermissions = mockk<() -> Unit>(relaxed = true)
private val dismiss = mockk<() -> Unit>(relaxed = true) private val dismiss = mockk<() -> Unit>(relaxed = true)
private val trackingProtectionUseCases = mockk<TrackingProtectionUseCases>(relaxed = true)
private val controller = DefaultQuickSettingsController( private val controller = DefaultQuickSettingsController(
context = context, context = context,
quickSettingsStore = store, quickSettingsStore = store,
@ -70,32 +72,30 @@ class DefaultQuickSettingsControllerTest {
sitePermissions = sitePermissions, sitePermissions = sitePermissions,
settings = appSettings, settings = appSettings,
permissionStorage = permissionStorage, permissionStorage = permissionStorage,
trackingExceptions = trackingExceptions,
reload = reload, reload = reload,
addNewTab = addNewTab, addNewTab = addNewTab,
requestRuntimePermissions = requestPermissions, requestRuntimePermissions = requestPermissions,
reportSiteIssue = reportIssue, reportSiteIssue = reportIssue,
displayTrackingProtection = displayTrackingProtection, displayTrackingProtection = displayTrackingProtection,
displayPermissions = displayPermissions, displayPermissions = displayPermissions,
dismiss = dismiss dismiss = dismiss,
trackingProtectionUseCases = trackingProtectionUseCases
) )
@Test @Test
fun `handleTrackingProtectionToggled should toggle tracking and reload website`() { fun `handleTrackingProtectionToggled should toggle tracking and reload website`() {
val testWebsiteHost = "host.com"
val websiteHost = slot<String>()
val session = slot<Session>() val session = slot<Session>()
every { store.dispatch(any()) } returns mockk() every { store.dispatch(any()) } returns mockk()
controller.handleTrackingProtectionToggled("https://$testWebsiteHost/page1", false) controller.handleTrackingProtectionToggled(false)
verifyOrder { verifyOrder {
trackingExceptions.toggle(capture(websiteHost)) trackingProtectionUseCases.addException(capture(session))
context.metrics.track(Event.TrackingProtectionException)
reload(capture(session)) reload(capture(session))
} }
assertAll { assertAll {
assertThat(websiteHost.isCaptured).isTrue()
assertThat(websiteHost.captured).isEqualTo(testWebsiteHost)
assertThat(session.isCaptured).isTrue() assertThat(session.isCaptured).isTrue()
assertThat(session.captured).isEqualTo(browserSession) assertThat(session.captured).isEqualTo(browserSession)
} }
@ -118,7 +118,8 @@ class DefaultQuickSettingsControllerTest {
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
fun `handleReportTrackingProblem should open a report issue webpage and dismiss when in normal mode`() { fun `handleReportTrackingProblem should open a report issue webpage and dismiss when in normal mode`() {
val websiteWithIssuesUrl = "https://host.com/page1" val websiteWithIssuesUrl = "https://host.com/page1"
val testReportUrl = String.format(BrowserFragment.REPORT_SITE_ISSUE_URL, websiteWithIssuesUrl) val testReportUrl =
String.format(BrowserFragment.REPORT_SITE_ISSUE_URL, websiteWithIssuesUrl)
val reportUrl = slot<String>() val reportUrl = slot<String>()
// `handleReportTrackingProblem` will behave differently depending on `isCustomTabSession` // `handleReportTrackingProblem` will behave differently depending on `isCustomTabSession`
every { browserSession.isCustomTabSession() } returns false every { browserSession.isCustomTabSession() } returns false
@ -140,7 +141,8 @@ class DefaultQuickSettingsControllerTest {
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
fun `handleReportTrackingProblem should open a report issue in browser from custom tab and dismiss`() { fun `handleReportTrackingProblem should open a report issue in browser from custom tab and dismiss`() {
val websiteWithIssuesUrl = "https://host.com/page1" val websiteWithIssuesUrl = "https://host.com/page1"
val testReportUrl = String.format(BrowserFragment.REPORT_SITE_ISSUE_URL, websiteWithIssuesUrl) val testReportUrl =
String.format(BrowserFragment.REPORT_SITE_ISSUE_URL, websiteWithIssuesUrl)
val reportUrl = slot<String>() val reportUrl = slot<String>()
// `handleReportTrackingProblem` will behave differently depending on `isCustomTabSession` // `handleReportTrackingProblem` will behave differently depending on `isCustomTabSession`
every { browserSession.isCustomTabSession() } returns true every { browserSession.isCustomTabSession() } returns true
@ -237,7 +239,8 @@ class DefaultQuickSettingsControllerTest {
featureGranted.getCorrespondingPermission() featureGranted.getCorrespondingPermission()
} }
val permissionStatus = featureGranted.getActionLabel(context, sitePermissions, appSettings) val permissionStatus = featureGranted.getActionLabel(context, sitePermissions, appSettings)
val permissionEnabled = featureGranted.shouldBeEnabled(context, sitePermissions, appSettings) val permissionEnabled =
featureGranted.shouldBeEnabled(context, sitePermissions, appSettings)
val action = slot<QuickSettingsFragmentAction>() val action = slot<QuickSettingsFragmentAction>()
every { store.dispatch(any()) } returns mockk() every { store.dispatch(any()) } returns mockk()
@ -249,9 +252,15 @@ class DefaultQuickSettingsControllerTest {
assertAll { assertAll {
assertThat(action.isCaptured).isTrue() assertThat(action.isCaptured).isTrue()
assertThat(action.captured).isInstanceOf(WebsitePermissionAction.TogglePermission::class) assertThat(action.captured).isInstanceOf(WebsitePermissionAction.TogglePermission::class)
assertThat((action.captured as WebsitePermissionAction.TogglePermission).websitePermission).isEqualTo(permission) assertThat((action.captured as WebsitePermissionAction.TogglePermission).websitePermission).isEqualTo(
assertThat((action.captured as WebsitePermissionAction.TogglePermission).updatedStatus).isEqualTo(permissionStatus) permission
assertThat((action.captured as WebsitePermissionAction.TogglePermission).updatedEnabledStatus).isEqualTo(permissionEnabled) )
assertThat((action.captured as WebsitePermissionAction.TogglePermission).updatedStatus).isEqualTo(
permissionStatus
)
assertThat((action.captured as WebsitePermissionAction.TogglePermission).updatedEnabledStatus).isEqualTo(
permissionEnabled
)
} }
} }
@ -272,24 +281,25 @@ class DefaultQuickSettingsControllerTest {
@Test @Test
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
fun `handlePermissionsChange should store the updated permission and reload webpage`() = runBlocking { fun `handlePermissionsChange should store the updated permission and reload webpage`() =
val testPermissions = mockk<SitePermissions>() runBlocking {
val permissions = slot<SitePermissions>() val testPermissions = mockk<SitePermissions>()
val session = slot<Session>() val permissions = slot<SitePermissions>()
val session = slot<Session>()
controller.handlePermissionsChange(testPermissions) controller.handlePermissionsChange(testPermissions)
verifyOrder { verifyOrder {
permissionStorage.updateSitePermissions(capture(permissions)) permissionStorage.updateSitePermissions(capture(permissions))
reload(capture(session)) reload(capture(session))
}
assertAll {
assertThat(permissions.isCaptured).isTrue()
assertThat(permissions.captured).isEqualTo(testPermissions)
assertThat(session.isCaptured).isTrue()
assertThat(session.captured).isEqualTo(browserSession)
}
} }
assertAll {
assertThat(permissions.isCaptured).isTrue()
assertThat(permissions.captured).isEqualTo(testPermissions)
assertThat(session.isCaptured).isTrue()
assertThat(session.captured).isEqualTo(browserSession)
}
}
@Test @Test
fun `WebsitePermission#getBackingFeature should return the PhoneFeature this permission is mapped from`() { fun `WebsitePermission#getBackingFeature should return the PhoneFeature this permission is mapped from`() {

View File

@ -36,20 +36,15 @@ class QuickSettingsInteractorTest {
@Test @Test
fun `onProtectionToggled should delegate the controller`() { fun `onProtectionToggled should delegate the controller`() {
val websiteUrl = "https://host.com/page1"
val trackingEnabled = true val trackingEnabled = true
val url = slot<String>()
val trackingStatus = slot<Boolean>() val trackingStatus = slot<Boolean>()
interactor.onProtectionToggled(websiteUrl, trackingEnabled) interactor.onProtectionToggled(trackingEnabled)
verifyAll { verifyAll {
controller.handleTrackingProtectionToggled(capture(url), capture(trackingStatus)) controller.handleTrackingProtectionToggled(capture(trackingStatus))
} }
assertAll { assertAll {
assertThat(url.isCaptured).isTrue()
assertThat(url.captured).isEqualTo(websiteUrl)
assertThat(trackingStatus.isCaptured).isTrue() assertThat(trackingStatus.isCaptured).isTrue()
assertThat(trackingStatus.captured).isEqualTo(trackingEnabled) assertThat(trackingStatus.captured).isEqualTo(trackingEnabled)
} }

View File

@ -141,7 +141,7 @@ The following metrics are added to the ping:
| sync_auth.sign_up |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |User registered a new Firefox Account, and was signed into it |[1](https://github.com/mozilla-mobile/fenix/pull/4931#issuecomment-529740300)||2020-03-01 | | sync_auth.sign_up |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |User registered a new Firefox Account, and was signed into it |[1](https://github.com/mozilla-mobile/fenix/pull/4931#issuecomment-529740300)||2020-03-01 |
| tab.media_pause |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user pressed the pause icon on a tab from the home screen |[1](https://github.com/mozilla-mobile/fenix/pull/5266)||2020-03-01 | | tab.media_pause |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user pressed the pause icon on a tab from the home screen |[1](https://github.com/mozilla-mobile/fenix/pull/5266)||2020-03-01 |
| tab.media_play |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user pressed the play icon on a tab from the home screen |[1](https://github.com/mozilla-mobile/fenix/pull/5266)||2020-03-01 | | tab.media_play |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user pressed the play icon on a tab from the home screen |[1](https://github.com/mozilla-mobile/fenix/pull/5266)||2020-03-01 |
| tracking_protection.etp_setting_changed |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user added a tracking protection exception through the TP toggle in the panel. |[1](https://github.com/mozilla-mobile/fenix/pull/5414#issuecomment-532847188)|<ul><li>etp_setting: The new setting for ETP: strict, standard</li></ul>|2020-03-01 | | tracking_protection.etp_setting_changed |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user changed their tracking protection level setting to either strict or standard. |[1](https://github.com/mozilla-mobile/fenix/pull/5414#issuecomment-532847188)|<ul><li>etp_setting: The new setting for ETP: strict, standard</li></ul>|2020-03-01 |
| tracking_protection.etp_settings |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user opened tracking protection settings through settings. |[1](https://github.com/mozilla-mobile/fenix/pull/5414#issuecomment-532847188)||2020-03-01 | | tracking_protection.etp_settings |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user opened tracking protection settings through settings. |[1](https://github.com/mozilla-mobile/fenix/pull/5414#issuecomment-532847188)||2020-03-01 |
| tracking_protection.etp_shield |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user pressed the tracking protection shield icon in toolbar. |[1](https://github.com/mozilla-mobile/fenix/pull/5414#issuecomment-532847188)||2020-03-01 | | tracking_protection.etp_shield |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user pressed the tracking protection shield icon in toolbar. |[1](https://github.com/mozilla-mobile/fenix/pull/5414#issuecomment-532847188)||2020-03-01 |
| tracking_protection.etp_tracker_list |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user pressed into a list of categorized trackers in tracking protection panel. |[1](https://github.com/mozilla-mobile/fenix/pull/5414#issuecomment-532847188)||2020-03-01 | | tracking_protection.etp_tracker_list |[event](https://mozilla.github.io/glean/book/user/metrics/event.html) |A user pressed into a list of categorized trackers in tracking protection panel. |[1](https://github.com/mozilla-mobile/fenix/pull/5414#issuecomment-532847188)||2020-03-01 |