From 38af31a3a2b84d426ad82513429a45421e15f6d9 Mon Sep 17 00:00:00 2001 From: Aitor Viana Date: Mon, 2 Dec 2024 21:09:50 +0000 Subject: [PATCH] Add localStorage exceptions to duckduckgo.com --- .../app/browser/WebViewDataManagerTest.kt | 19 ++++++- .../duckduckgo/app/browser/WebDataManager.kt | 53 +++++++++++-------- 2 files changed, 50 insertions(+), 22 deletions(-) diff --git a/app/src/androidTest/java/com/duckduckgo/app/browser/WebViewDataManagerTest.kt b/app/src/androidTest/java/com/duckduckgo/app/browser/WebViewDataManagerTest.kt index bc76b3476e36..898dead27cc2 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/browser/WebViewDataManagerTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/browser/WebViewDataManagerTest.kt @@ -18,7 +18,9 @@ package com.duckduckgo.app.browser import android.annotation.SuppressLint import android.content.Context +import android.webkit.ValueCallback import android.webkit.WebStorage +import android.webkit.WebStorage.Origin import android.webkit.WebView import androidx.test.platform.app.InstrumentationRegistry import com.duckduckgo.app.browser.httpauth.WebViewHttpAuthStore @@ -29,9 +31,14 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withContext import org.junit.Assert.assertTrue +import org.junit.Before import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer import org.mockito.kotlin.mock +import org.mockito.kotlin.never import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever @Suppress("RemoveExplicitTypeArguments") @SuppressLint("NoHardcodedCoroutineDispatcher") @@ -44,6 +51,15 @@ class WebViewDataManagerTest { private val mockWebViewHttpAuthStore: WebViewHttpAuthStore = mock() private val testee = WebViewDataManager(context, WebViewSessionInMemoryStorage(), mockCookieManager, mockFileDeleter, mockWebViewHttpAuthStore) + @Before + fun setup() { + doAnswer { invocation -> + val callback = invocation.arguments[0] as ValueCallback> + callback.onReceiveValue(emptyMap()) // Simulate callback invocation + null + }.whenever(mockStorage).getOrigins(any()) + } + @Test fun whenDataClearedThenWebViewHistoryCleared() = runTest { withContext(Dispatchers.Main) { @@ -76,7 +92,8 @@ class WebViewDataManagerTest { withContext(Dispatchers.Main) { val webView = TestWebView(context) testee.clearData(webView, mockStorage) - verify(mockStorage).deleteAllData() + // we call deleteOrigin() instead and we should make sure we don't call deleteAllData() + verify(mockStorage, never()).deleteAllData() } } diff --git a/app/src/main/java/com/duckduckgo/app/browser/WebDataManager.kt b/app/src/main/java/com/duckduckgo/app/browser/WebDataManager.kt index 0522ea462901..b3861342e93c 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/WebDataManager.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/WebDataManager.kt @@ -18,6 +18,7 @@ package com.duckduckgo.app.browser import android.content.Context import android.webkit.WebStorage +import android.webkit.WebStorage.Origin import android.webkit.WebView import com.duckduckgo.app.browser.httpauth.WebViewHttpAuthStore import com.duckduckgo.app.browser.session.WebViewSessionStorage @@ -25,6 +26,8 @@ import com.duckduckgo.app.global.file.FileDeleter import com.duckduckgo.cookies.api.DuckDuckGoCookieManager import java.io.File import javax.inject.Inject +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine interface WebDataManager { suspend fun clearData( @@ -53,7 +56,7 @@ class WebViewDataManager @Inject constructor( clearFormData(webView) clearAuthentication(webView) clearExternalCookies() - clearWebViewDirectories(exclusions = WEBVIEW_FILES_EXCLUDED_FROM_DELETION) + clearWebViewDirectories() } private fun clearWebViewCache(webView: WebView) { @@ -64,8 +67,25 @@ class WebViewDataManager @Inject constructor( webView.clearHistory() } - private fun clearWebStorage(webStorage: WebStorage) { - webStorage.deleteAllData() + private suspend fun clearWebStorage(webStorage: WebStorage) { + suspendCoroutine { continuation -> + webStorage.getOrigins { origins -> + kotlin.runCatching { + for (origin in origins) { + val originString = (origin as Origin).origin + + // Check if this is the domain to exclude + if (!originString.endsWith(".duckduckgo.com")) { + // Delete all other origins + webStorage.deleteOrigin(originString) + } + } + continuation.resume(Unit) + }.onFailure { + continuation.resume(Unit) + } + } + } } private fun clearFormData(webView: WebView) { @@ -73,17 +93,19 @@ class WebViewDataManager @Inject constructor( } /** - * Deletes web view directory content. The Cookies file is kept as we clear cookies separately to avoid a crash and maintain ddg cookies. - * Cookies may appear in files: - * app_webview/Cookies - * app_webview/Default/Cookies + * Deletes web view directory content except the following directories + * app_webview/Cookies + * app_webview/Default/Cookies + * app_webview/Default/Local Storage + * + * the excluded directories above are to avoid clearing unnecessary cookies and because localStorage is cleared using clearWebStorage */ - private suspend fun clearWebViewDirectories(exclusions: List) { + private suspend fun clearWebViewDirectories() { val dataDir = context.applicationInfo.dataDir - fileDeleter.deleteContents(File(dataDir, WEBVIEW_DATA_DIRECTORY_NAME), exclusions) + fileDeleter.deleteContents(File(dataDir, "app_webview"), listOf("Default", "Cookies")) // We don't delete the Default dir as Cookies may be inside however we do clear any other content - fileDeleter.deleteContents(File(dataDir, WEBVIEW_DEFAULT_DIRECTORY_NAME), exclusions) + fileDeleter.deleteContents(File(dataDir, "app_webview/Default"), listOf("Cookies", "Local Storage")) } private suspend fun clearAuthentication(webView: WebView) { @@ -98,15 +120,4 @@ class WebViewDataManager @Inject constructor( override fun clearWebViewSessions() { webViewSessionStorage.deleteAllSessions() } - - companion object { - private const val WEBVIEW_DATA_DIRECTORY_NAME = "app_webview" - private const val WEBVIEW_DEFAULT_DIRECTORY_NAME = "app_webview/Default" - private const val DATABASES_DIRECTORY_NAME = "databases" - - private val WEBVIEW_FILES_EXCLUDED_FROM_DELETION = listOf( - "Default", - "Cookies", - ) - } }