For #9294: Add option to clear current site data in quick settings dialog.
This commit is contained in:
parent
e150f6118b
commit
9bfc94b793
|
@ -96,4 +96,9 @@ object FeatureFlags {
|
|||
* Enables showing the homescreen onboarding card.
|
||||
*/
|
||||
const val showHomeOnboarding = false
|
||||
|
||||
/**
|
||||
* Enables showing the option to clear site data.
|
||||
*/
|
||||
val showClearSiteData = Config.channel.isNightlyOrDebug
|
||||
}
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
/* 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.settings.quicksettings
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.view.isVisible
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.mozilla.fenix.FeatureFlags
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.databinding.QuicksettingsClearSiteDataBinding
|
||||
import org.mozilla.fenix.ext.components
|
||||
|
||||
/**
|
||||
* Contract declaring all possible user interactions with [ClearSiteDataView].
|
||||
*/
|
||||
interface ClearSiteDataViewInteractor {
|
||||
/**
|
||||
* Shows the confirmation dialog to clear site data for [baseDomain].
|
||||
*/
|
||||
fun onClearSiteDataClicked(baseDomain: String)
|
||||
}
|
||||
|
||||
/**
|
||||
* MVI View to access the dialog to clear site cookies and data.
|
||||
*
|
||||
* @param containerView [ViewGroup] in which this View will inflate itself.
|
||||
* @param interactor [TrackingProtectionInteractor] which will have delegated to all user
|
||||
* interactions.
|
||||
*/
|
||||
class ClearSiteDataView(
|
||||
val context: Context,
|
||||
private val ioScope: CoroutineScope,
|
||||
val containerView: ViewGroup,
|
||||
val containerDivider: View,
|
||||
val interactor: ClearSiteDataViewInteractor
|
||||
) {
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
lateinit var websiteUrl: String
|
||||
|
||||
val binding = QuicksettingsClearSiteDataBinding.inflate(
|
||||
LayoutInflater.from(context),
|
||||
containerView,
|
||||
true
|
||||
)
|
||||
|
||||
fun update(webInfoState: WebsiteInfoState) {
|
||||
if (!FeatureFlags.showClearSiteData) {
|
||||
setVisibility(false)
|
||||
return
|
||||
}
|
||||
|
||||
websiteUrl = webInfoState.websiteUrl
|
||||
|
||||
setVisibility(true)
|
||||
binding.clearSiteData.setOnClickListener {
|
||||
askToClear()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setVisibility(visible: Boolean) {
|
||||
binding.root.isVisible = visible
|
||||
containerDivider.isVisible = visible
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
internal fun askToClear() {
|
||||
|
||||
ioScope.launch {
|
||||
val publicSuffixList = context.components.publicSuffixList
|
||||
val host = websiteUrl.toUri().host.orEmpty()
|
||||
val domain = publicSuffixList.getPublicSuffixPlusOne(host).await()
|
||||
|
||||
domain?.let { baseDomain ->
|
||||
launch(Dispatchers.Main) {
|
||||
showConfirmationDialog(baseDomain)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showConfirmationDialog(baseDomain: String) {
|
||||
AlertDialog.Builder(context).apply {
|
||||
setMessage(
|
||||
HtmlCompat.fromHtml(
|
||||
context.getString(
|
||||
R.string.confirm_clear_site_data,
|
||||
baseDomain
|
||||
),
|
||||
HtmlCompat.FROM_HTML_MODE_LEGACY
|
||||
)
|
||||
)
|
||||
|
||||
setNegativeButton(R.string.delete_browsing_data_prompt_cancel) { it: DialogInterface, _ ->
|
||||
it.cancel()
|
||||
}
|
||||
|
||||
setPositiveButton(R.string.delete_browsing_data_prompt_allow) { it: DialogInterface, _ ->
|
||||
it.dismiss()
|
||||
interactor.onClearSiteDataClicked(baseDomain)
|
||||
}
|
||||
create()
|
||||
}.show()
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.browser.state.selector.findTabOrCustomTab
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.concept.engine.Engine
|
||||
import mozilla.components.concept.engine.permission.SitePermissions
|
||||
import mozilla.components.feature.session.SessionUseCases.ReloadUrlUseCase
|
||||
import mozilla.components.feature.tabs.TabsUseCases.AddNewTabUseCase
|
||||
|
@ -74,6 +75,12 @@ interface QuickSettingsController {
|
|||
* "Secured or Insecure Connection" section.
|
||||
*/
|
||||
fun handleConnectionDetailsClicked()
|
||||
|
||||
/**
|
||||
* Clears site data for the current website. Called when a user clicks
|
||||
* on the section to clear site data.
|
||||
*/
|
||||
fun handleClearSiteDataClicked(baseDomain: String)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -111,6 +118,7 @@ class DefaultQuickSettingsController(
|
|||
private val addNewTab: AddNewTabUseCase,
|
||||
private val requestRuntimePermissions: OnNeedToRequestPermissions = { },
|
||||
private val displayPermissions: () -> Unit,
|
||||
private val engine: Engine = context.components.core.engine,
|
||||
private val dismiss: () -> Unit
|
||||
) : QuickSettingsController {
|
||||
override fun handlePermissionsShown() {
|
||||
|
@ -193,7 +201,11 @@ class DefaultQuickSettingsController(
|
|||
sessionUseCases.reload.invoke(session.id)
|
||||
}
|
||||
|
||||
quickSettingsStore.dispatch(TrackingProtectionAction.ToggleTrackingProtectionEnabled(isEnabled))
|
||||
quickSettingsStore.dispatch(
|
||||
TrackingProtectionAction.ToggleTrackingProtectionEnabled(
|
||||
isEnabled
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun handleDetailsClicked() {
|
||||
|
@ -228,6 +240,17 @@ class DefaultQuickSettingsController(
|
|||
navController().navigate(directions)
|
||||
}
|
||||
|
||||
override fun handleClearSiteDataClicked(baseDomain: String) {
|
||||
engine.clearData(
|
||||
host = baseDomain,
|
||||
data = Engine.BrowsingData.select(
|
||||
Engine.BrowsingData.AUTH_SESSIONS,
|
||||
Engine.BrowsingData.ALL_SITE_DATA,
|
||||
),
|
||||
)
|
||||
navController().popBackStack()
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a certain set of runtime Android permissions.
|
||||
*
|
||||
|
|
|
@ -41,7 +41,6 @@ data class WebsiteInfoState(
|
|||
val websiteSecurityUiValues: WebsiteSecurityUiValues,
|
||||
val certificateName: String
|
||||
) : State {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Construct an initial [WebsiteInfoState]
|
||||
|
|
|
@ -15,7 +15,7 @@ package org.mozilla.fenix.settings.quicksettings
|
|||
*/
|
||||
class QuickSettingsInteractor(
|
||||
private val controller: QuickSettingsController
|
||||
) : WebsitePermissionInteractor, TrackingProtectionInteractor, WebSiteInfoInteractor {
|
||||
) : WebsitePermissionInteractor, TrackingProtectionInteractor, WebSiteInfoInteractor, ClearSiteDataViewInteractor {
|
||||
override fun onPermissionsShown() {
|
||||
controller.handlePermissionsShown()
|
||||
}
|
||||
|
@ -39,4 +39,8 @@ class QuickSettingsInteractor(
|
|||
override fun onConnectionDetailsClicked() {
|
||||
controller.handleConnectionDetailsClicked()
|
||||
}
|
||||
|
||||
override fun onClearSiteDataClicked(baseDomain: String) {
|
||||
controller.handleClearSiteDataClicked(baseDomain)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ class QuickSettingsSheetDialogFragment : FenixDialogFragment() {
|
|||
private lateinit var quickSettingsController: QuickSettingsController
|
||||
private lateinit var websiteInfoView: WebsiteInfoView
|
||||
private lateinit var websitePermissionsView: WebsitePermissionsView
|
||||
private lateinit var clearSiteDataView: ClearSiteDataView
|
||||
|
||||
@VisibleForTesting
|
||||
internal lateinit var trackingProtectionView: TrackingProtectionView
|
||||
|
@ -116,6 +117,13 @@ class QuickSettingsSheetDialogFragment : FenixDialogFragment() {
|
|||
WebsitePermissionsView(binding.websitePermissionsLayout, interactor)
|
||||
trackingProtectionView =
|
||||
TrackingProtectionView(binding.trackingProtectionLayout, interactor, context.settings())
|
||||
clearSiteDataView = ClearSiteDataView(
|
||||
context = context,
|
||||
ioScope = viewLifecycleOwner.lifecycleScope + Dispatchers.IO,
|
||||
containerView = binding.clearSiteDataLayout,
|
||||
containerDivider = binding.clearSiteDataDivider,
|
||||
interactor = interactor
|
||||
)
|
||||
|
||||
return rootView
|
||||
}
|
||||
|
@ -127,6 +135,7 @@ class QuickSettingsSheetDialogFragment : FenixDialogFragment() {
|
|||
websiteInfoView.update(it.webInfoState)
|
||||
websitePermissionsView.update(it.websitePermissionsState)
|
||||
trackingProtectionView.update(it.trackingProtectionState)
|
||||
clearSiteDataView.update(it.webInfoState)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
android:id="@+id/trackingProtectionLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
app:layout_constraintBottom_toTopOf="@+id/clearSiteDataLayout" />
|
||||
|
||||
<View
|
||||
android:id="@+id/trackingProtectionDivider"
|
||||
|
@ -52,6 +52,21 @@
|
|||
android:background="?neutralFaded"
|
||||
app:layout_constraintBottom_toTopOf="@id/trackingProtectionLayout" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/clearSiteDataLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<View
|
||||
android:id="@+id/clearSiteDataDivider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="?neutralFaded"
|
||||
app:layout_constraintBottom_toTopOf="@id/clearSiteDataLayout" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/websitePermissionsGroup"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -59,5 +74,6 @@
|
|||
android:visibility="gone"
|
||||
app:constraint_referenced_ids="websitePermissionsLayout,webSitePermissionsDivider"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/website_clear_site_data"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/clearSiteData"
|
||||
style="@style/QuickSettingsLargeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="@dimen/quicksettings_item_height"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="@string/clear_site_data" />
|
||||
|
||||
</LinearLayout>
|
|
@ -1790,6 +1790,10 @@
|
|||
<string name="quick_settings_sheet_secure_connection_2">Connection is secure</string>
|
||||
<!-- Label that indicates a site is using a insecure connection -->
|
||||
<string name="quick_settings_sheet_insecure_connection_2">Connection is not secure</string>
|
||||
<!-- Label to clear site data -->
|
||||
<string name="clear_site_data">Clear cookies and site data</string>
|
||||
<!-- Confirmation message for a dialog confirming if the user wants to delete all data for current site -->
|
||||
<string name="confirm_clear_site_data"><![CDATA[Are you sure that you want to clear all the cookies and data for the site <b>%s</b>?]]></string>
|
||||
<!-- Confirmation message for a dialog confirming if the user wants to delete all the permissions for all sites-->
|
||||
<string name="confirm_clear_permissions_on_all_sites">Are you sure that you want to clear all the permissions on all sites?</string>
|
||||
<!-- Confirmation message for a dialog confirming if the user wants to delete all the permissions for a site-->
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/* 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.settings.quicksettings
|
||||
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.mockk
|
||||
import io.mockk.spyk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.test.TestCoroutineScope
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.databinding.QuicksettingsClearSiteDataBinding
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class ClearSiteDataViewTest {
|
||||
private lateinit var view: ClearSiteDataView
|
||||
private lateinit var binding: QuicksettingsClearSiteDataBinding
|
||||
private lateinit var interactor: ClearSiteDataViewInteractor
|
||||
private val coroutinesScope = TestCoroutineScope()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockKAnnotations.init(this)
|
||||
interactor = mockk(relaxed = true)
|
||||
view = spyk(
|
||||
ClearSiteDataView(
|
||||
testContext,
|
||||
coroutinesScope,
|
||||
FrameLayout(testContext),
|
||||
View(testContext),
|
||||
interactor
|
||||
)
|
||||
)
|
||||
binding = view.binding
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clear site`() {
|
||||
val state = WebsiteInfoState(
|
||||
websiteUrl = "https://developers.mozilla.org",
|
||||
websiteTitle = "Mozilla",
|
||||
websiteSecurityUiValues = WebsiteSecurityUiValues.SECURE,
|
||||
certificateName = "Certificate"
|
||||
)
|
||||
|
||||
view.update(state)
|
||||
|
||||
binding.clearSiteData.callOnClick()
|
||||
|
||||
verify { view.askToClear() }
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ import mozilla.components.browser.state.state.BrowserState
|
|||
import mozilla.components.browser.state.state.TabSessionState
|
||||
import mozilla.components.browser.state.state.createTab
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.concept.engine.Engine
|
||||
import mozilla.components.concept.engine.permission.SitePermissions
|
||||
import mozilla.components.concept.engine.permission.SitePermissions.Status.NO_DECISION
|
||||
import mozilla.components.feature.session.SessionUseCases
|
||||
|
@ -71,6 +72,9 @@ class DefaultQuickSettingsControllerTest {
|
|||
@MockK(relaxed = true)
|
||||
private lateinit var permissionStorage: PermissionStorage
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var engine: Engine
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var reload: SessionUseCases.ReloadUrlUseCase
|
||||
|
||||
|
@ -104,6 +108,7 @@ class DefaultQuickSettingsControllerTest {
|
|||
reload = reload,
|
||||
addNewTab = addNewTab,
|
||||
requestRuntimePermissions = requestPermissions,
|
||||
engine = engine,
|
||||
displayPermissions = {},
|
||||
dismiss = {}
|
||||
)
|
||||
|
@ -384,6 +389,21 @@ class DefaultQuickSettingsControllerTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN handleClearSiteData THEN call clearSite`() {
|
||||
controller.handleClearSiteDataClicked("mozilla.org")
|
||||
|
||||
verify {
|
||||
engine.clearData(
|
||||
host = "mozilla.org",
|
||||
data = Engine.BrowsingData.select(
|
||||
Engine.BrowsingData.AUTH_SESSIONS,
|
||||
Engine.BrowsingData.ALL_SITE_DATA,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createController(
|
||||
requestPermissions: (Array<String>) -> Unit = { _ -> },
|
||||
displayPermissions: () -> Unit = { },
|
||||
|
|
|
@ -82,4 +82,13 @@ class QuickSettingsInteractorTest {
|
|||
controller.handleConnectionDetailsClicked()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN calling onClearSiteDataClicked THEN delegate to the controller`() {
|
||||
interactor.onClearSiteDataClicked("baseDomain")
|
||||
|
||||
verify {
|
||||
controller.handleClearSiteDataClicked("baseDomain")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue