fix site clear for cookies/cache
This commit is contained in:
+99
-12
@@ -6,6 +6,7 @@
|
||||
|
||||
package eu.weblibre.flutter_mozilla_components.api
|
||||
|
||||
import androidx.core.net.toUri
|
||||
import eu.weblibre.flutter_mozilla_components.GlobalComponents
|
||||
import eu.weblibre.flutter_mozilla_components.pigeons.ClearDataType
|
||||
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoDeleteBrowsingDataController
|
||||
@@ -16,6 +17,9 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import mozilla.components.browser.state.action.EngineAction
|
||||
import mozilla.components.browser.state.action.RecentlyClosedAction
|
||||
import mozilla.components.browser.state.selector.allTabs
|
||||
import mozilla.components.browser.state.state.SessionState
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.concept.engine.Engine
|
||||
import mozilla.components.concept.engine.translate.ModelManagementOptions
|
||||
import mozilla.components.concept.engine.translate.ModelOperation
|
||||
@@ -30,6 +34,29 @@ class GeckoDeleteBrowsingDataControllerImpl : GeckoDeleteBrowsingDataController
|
||||
requireNotNull(GlobalComponents.components) { "Components not initialized" }
|
||||
}
|
||||
|
||||
/**
|
||||
* GeckoView's storage clearing APIs warn that any open session may re-accumulate
|
||||
* previously cleared data. We synchronously close the underlying [EngineSession]
|
||||
* (so its in-memory cookie/storage state is torn down before the clear runs) and
|
||||
* then unlink it from the browser store. The next reload spins up a fresh engine
|
||||
* session that reads from the cleared storage.
|
||||
*
|
||||
* NOTE: We deliberately don't use [EngineAction.SuspendEngineSessionAction] here -
|
||||
* its middleware runs `engineSession.close()` inside a `scope.launch`, racing with
|
||||
* the `clearData` call we're about to issue.
|
||||
*/
|
||||
private fun closeMatchingSessions(
|
||||
store: BrowserStore,
|
||||
predicate: (SessionState) -> Boolean,
|
||||
) {
|
||||
val tabs = store.state.allTabs.filter {
|
||||
it.engineState.engineSession != null && predicate(it)
|
||||
}
|
||||
|
||||
tabs.forEach { it.engineState.engineSession?.close() }
|
||||
tabs.forEach { store.dispatch(EngineAction.UnlinkEngineSessionAction(it.id)) }
|
||||
}
|
||||
|
||||
override fun deleteTabs(callback: (Result<Unit>) -> Unit) {
|
||||
coroutineScope.launch {
|
||||
withContext(Dispatchers.Main) {
|
||||
@@ -56,15 +83,22 @@ class GeckoDeleteBrowsingDataControllerImpl : GeckoDeleteBrowsingDataController
|
||||
override fun deleteCookiesAndSiteData(callback: (Result<Unit>) -> Unit) {
|
||||
coroutineScope.launch {
|
||||
withContext(Dispatchers.Main) {
|
||||
closeMatchingSessions(components.core.store) { true }
|
||||
|
||||
components.core.engine.clearData(
|
||||
Engine.BrowsingData.select(
|
||||
Engine.BrowsingData.COOKIES,
|
||||
Engine.BrowsingData.AUTH_SESSIONS,
|
||||
),
|
||||
onSuccess = {
|
||||
components.core.engine.clearData(
|
||||
Engine.BrowsingData.select(Engine.BrowsingData.DOM_STORAGES),
|
||||
onSuccess = { callback(Result.success(Unit)) },
|
||||
onError = { callback(Result.failure(it)) },
|
||||
)
|
||||
},
|
||||
onError = { callback(Result.failure(it)) },
|
||||
)
|
||||
components.core.engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.DOM_STORAGES))
|
||||
|
||||
callback(Result.success(Unit))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,9 +116,9 @@ class GeckoDeleteBrowsingDataControllerImpl : GeckoDeleteBrowsingDataController
|
||||
)
|
||||
components.core.engine.clearData(
|
||||
Engine.BrowsingData.select(Engine.BrowsingData.ALL_CACHES),
|
||||
onSuccess = { callback(Result.success(Unit)) },
|
||||
onError = { callback(Result.failure(it)) },
|
||||
)
|
||||
|
||||
callback(Result.success(Unit))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,10 +128,18 @@ class GeckoDeleteBrowsingDataControllerImpl : GeckoDeleteBrowsingDataController
|
||||
withContext(Dispatchers.Main) {
|
||||
components.core.engine.clearData(
|
||||
Engine.BrowsingData.select(Engine.BrowsingData.ALL_SITE_SETTINGS),
|
||||
onSuccess = {
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
components.core.permissionStorage.deleteAllSitePermissions()
|
||||
callback(Result.success(Unit))
|
||||
} catch (e: Throwable) {
|
||||
callback(Result.failure(e))
|
||||
}
|
||||
}
|
||||
},
|
||||
onError = { callback(Result.failure(it)) },
|
||||
)
|
||||
components.core.permissionStorage.deleteAllSitePermissions()
|
||||
|
||||
callback(Result.success(Unit))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,6 +160,14 @@ class GeckoDeleteBrowsingDataControllerImpl : GeckoDeleteBrowsingDataController
|
||||
) {
|
||||
coroutineScope.launch {
|
||||
withContext(Dispatchers.Main) {
|
||||
// Detach engine sessions for any tab in this context so they don't
|
||||
// re-accumulate data while the (fire-and-forget) clear is processed.
|
||||
closeMatchingSessions(components.core.store) { it.contextId == contextId }
|
||||
|
||||
// GeckoView's clearDataForSessionContext uses dispatch (fire-and-forget),
|
||||
// so there's no completion signal we can chain against. Fire it and
|
||||
// signal completion immediately - by suspending matching sessions above
|
||||
// we've at least ensured the operation can take effect.
|
||||
components.core.runtime.storageController.clearDataForSessionContext(contextId)
|
||||
|
||||
callback(Result.success(Unit))
|
||||
@@ -133,17 +183,54 @@ class GeckoDeleteBrowsingDataControllerImpl : GeckoDeleteBrowsingDataController
|
||||
coroutineScope.launch {
|
||||
try {
|
||||
withContext(Dispatchers.Main) {
|
||||
if (dataTypes.contains(ClearDataType.ALL_SITE_DATA) && (dataTypes.contains(
|
||||
ClearDataType.ONLY_COOKIES
|
||||
) || dataTypes.contains(ClearDataType.ONLY_CACHES))
|
||||
) {
|
||||
callback(Result.failure(Exception("Cookies/Cache must be exclusively!")))
|
||||
}
|
||||
|
||||
// Convert ClearDataType to Engine.BrowsingData flags
|
||||
val browsingDataTypes = dataTypes.map { dataType ->
|
||||
when (dataType) {
|
||||
ClearDataType.AUTH_SESSIONS -> Engine.BrowsingData.AUTH_SESSIONS
|
||||
ClearDataType.ALL_SITE_DATA -> Engine.BrowsingData.ALL_SITE_DATA
|
||||
ClearDataType.COOKIES -> Engine.BrowsingData.COOKIES
|
||||
ClearDataType.ALL_CACHES -> Engine.BrowsingData.ALL_CACHES
|
||||
ClearDataType.ONLY_COOKIES -> Engine.BrowsingData.COOKIES
|
||||
ClearDataType.ONLY_CACHES -> Engine.BrowsingData.ALL_CACHES
|
||||
}
|
||||
}.toIntArray()
|
||||
|
||||
// Clear data for the specific host
|
||||
// Find tabs on this host (so we can detach their engine sessions and
|
||||
// also discover any container/contextId partitions that need clearing).
|
||||
val matchingTabs = components.core.store.state.allTabs.filter { tab ->
|
||||
val tabHost = runCatching { tab.content.url.toUri().host }.getOrNull()
|
||||
?: return@filter false
|
||||
tabHost == host || tabHost.endsWith(".$host")
|
||||
}
|
||||
|
||||
// GeckoView warns that open sessions may re-accumulate previously
|
||||
// cleared data. Close them synchronously before clearing.
|
||||
matchingTabs.forEach { it.engineState.engineSession?.close() }
|
||||
matchingTabs.forEach {
|
||||
components.core.store.dispatch(EngineAction.UnlinkEngineSessionAction(it.id))
|
||||
}
|
||||
|
||||
// GeckoView's clearDataFromBaseDomain (used by engine.clearData(host=))
|
||||
// only targets the default origin attributes partition. Tabs that live
|
||||
// inside a contextual identity ("container") store their cookies/storage
|
||||
// under a `geckoViewSessionContextId` origin attribute that the
|
||||
// base-domain clear does not touch. To make clearing actually effective
|
||||
// for container tabs, also fire clearDataForSessionContext for any
|
||||
// contextIds we found. This is broader than ideal (it clears the whole
|
||||
// container, not just this host) but there is no GeckoView API to
|
||||
// combine host + context.
|
||||
val contextIds = matchingTabs.mapNotNull { it.contextId }.toSet()
|
||||
contextIds.forEach { contextId ->
|
||||
components.core.runtime.storageController
|
||||
.clearDataForSessionContext(contextId)
|
||||
}
|
||||
|
||||
// Clear data for the specific host (default partition).
|
||||
components.core.engine.clearData(
|
||||
data = Engine.BrowsingData.select(*browsingDataTypes),
|
||||
host = host,
|
||||
@@ -160,4 +247,4 @@ class GeckoDeleteBrowsingDataControllerImpl : GeckoDeleteBrowsingDataController
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-3
@@ -681,12 +681,15 @@ enum class MlProgressStatus(val raw: Int) {
|
||||
enum class ClearDataType(val raw: Int) {
|
||||
/** Authentication sessions */
|
||||
AUTH_SESSIONS(0),
|
||||
/** All site data (cookies, storage, etc.) */
|
||||
/**
|
||||
* All site data (cookies, storage, etc.)
|
||||
* WARNING: If this is set it already includes cookies and allCaches. Passing the additionally will lead to issues
|
||||
*/
|
||||
ALL_SITE_DATA(1),
|
||||
/** Cookies only */
|
||||
COOKIES(2),
|
||||
ONLY_COOKIES(2),
|
||||
/** Cache only */
|
||||
ALL_CACHES(3);
|
||||
ONLY_CACHES(3);
|
||||
|
||||
companion object {
|
||||
fun ofRaw(raw: Int): ClearDataType? {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2087,13 +2087,14 @@ enum ClearDataType {
|
||||
authSessions,
|
||||
|
||||
/// All site data (cookies, storage, etc.)
|
||||
/// WARNING: If this is set it already includes cookies and allCaches. Passing the additionally will lead to issues
|
||||
allSiteData,
|
||||
|
||||
/// Cookies only
|
||||
cookies,
|
||||
onlyCookies,
|
||||
|
||||
/// Cache only
|
||||
allCaches,
|
||||
onlyCaches,
|
||||
}
|
||||
|
||||
@HostApi()
|
||||
|
||||
Reference in New Issue
Block a user