Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import androidx.test.annotation.UiThreadTest
import androidx.test.platform.app.InstrumentationRegistry
import com.duckduckgo.app.browser.session.WebViewSessionInMemoryStorage
import com.duckduckgo.app.fire.DuckDuckGoCookieManager
import com.duckduckgo.app.global.file.FileDeleter
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import kotlinx.coroutines.runBlocking
Expand All @@ -35,7 +36,9 @@ class WebViewDataManagerTest {

private val mockCookieManager: DuckDuckGoCookieManager = mock()
private val mockStorage: WebStorage = mock()
private val testee = WebViewDataManager(WebViewSessionInMemoryStorage(), mockCookieManager)
private val context = InstrumentationRegistry.getInstrumentation().targetContext
private val mockFileDeleter: FileDeleter = mock()
private val testee = WebViewDataManager(context, WebViewSessionInMemoryStorage(), mockCookieManager, mockFileDeleter)

@UiThreadTest
@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ import javax.inject.Singleton
PrivacyModule::class,
WidgetModule::class,
RatingModule::class,
AppUsageModule::class
AppUsageModule::class,
FileModule::class
]
)
interface TestAppComponent : AppComponent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

@Suppress("RemoveExplicitTypeArguments")
class WebViewCookieManagerTest {
Expand All @@ -33,10 +35,19 @@ class WebViewCookieManagerTest {
private val cookieManager: CookieManager = CookieManager.getInstance()

@Before
fun setup() {
fun setup() = runBlocking {
removeExistingCookies()
testee = WebViewCookieManager(cookieManager, host)
}

private suspend fun removeExistingCookies() {
withContext(Dispatchers.Main) {
suspendCoroutine<Unit> { continuation ->
cookieManager.removeAllCookies { continuation.resume(Unit) }
}
}
}

@Test
fun whenExternalCookiesClearedThenInternalCookiesRecreated() = runBlocking<Unit> {
cookieManager.setCookie(host, "da=abc")
Expand Down
22 changes: 21 additions & 1 deletion app/src/main/java/com/duckduckgo/app/browser/WebDataManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,29 @@

package com.duckduckgo.app.browser

import android.content.Context
import android.os.Build
import android.webkit.WebStorage
import android.webkit.WebView
import android.webkit.WebViewDatabase
import com.duckduckgo.app.browser.session.WebViewSessionStorage
import com.duckduckgo.app.fire.DuckDuckGoCookieManager
import com.duckduckgo.app.global.file.FileDeleter
import java.io.File
import javax.inject.Inject

interface WebDataManager {
suspend fun clearExternalCookies()
fun clearData(webView: WebView, webStorage: WebStorage, webViewDatabase: WebViewDatabase)
fun clearWebViewSessions()
suspend fun deleteWebViewDirectory()
}

class WebViewDataManager @Inject constructor(
private val context: Context,
private val webViewSessionStorage: WebViewSessionStorage,
private val cookieManager: DuckDuckGoCookieManager
private val cookieManager: DuckDuckGoCookieManager,
private val fileDeleter: FileDeleter
) : WebDataManager {

override fun clearData(webView: WebView, webStorage: WebStorage, webViewDatabase: WebViewDatabase) {
Expand Down Expand Up @@ -64,6 +70,11 @@ class WebViewDataManager @Inject constructor(
webView.clearFormData()
}

override suspend fun deleteWebViewDirectory() {
val webViewDataDirectory = File(context.applicationInfo.dataDir, WEBVIEW_DATA_DIRECTORY_NAME)
fileDeleter.deleteContents(webViewDataDirectory, FILENAMES_EXCLUDED_FROM_DELETION)
}

/**
* Deprecated and not needed on Oreo or later
*/
Expand All @@ -83,4 +94,13 @@ class WebViewDataManager @Inject constructor(
override fun clearWebViewSessions() {
webViewSessionStorage.deleteAllSessions()
}

companion object {
private const val WEBVIEW_DATA_DIRECTORY_NAME = "app_webview"

private val FILENAMES_EXCLUDED_FROM_DELETION = listOf(
"Cookies",
"Local Storage"
)
}
}
10 changes: 8 additions & 2 deletions app/src/main/java/com/duckduckgo/app/browser/di/BrowserModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.duckduckgo.app.browser.session.WebViewSessionStorage
import com.duckduckgo.app.fire.DuckDuckGoCookieManager
import com.duckduckgo.app.fire.WebViewCookieManager
import com.duckduckgo.app.global.AppUrl
import com.duckduckgo.app.global.file.FileDeleter
import com.duckduckgo.app.global.install.AppInstallStore
import com.duckduckgo.app.httpsupgrade.HttpsUpgrader
import com.duckduckgo.app.privacy.db.PrivacyProtectionCountDao
Expand Down Expand Up @@ -91,8 +92,13 @@ class BrowserModule {

@Singleton
@Provides
fun webDataManager(webViewSessionStorage: WebViewSessionStorage, cookieManager: DuckDuckGoCookieManager): WebDataManager =
WebViewDataManager(webViewSessionStorage, cookieManager)
fun webDataManager(
context: Context,
webViewSessionStorage: WebViewSessionStorage,
cookieManager: DuckDuckGoCookieManager,
fileDeleter: FileDeleter
): WebDataManager =
WebViewDataManager(context, webViewSessionStorage, cookieManager, fileDeleter)

@Provides
fun clipboardManager(context: Context): ClipboardManager {
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/com/duckduckgo/app/di/AppComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ import javax.inject.Singleton
PrivacyModule::class,
WidgetModule::class,
RatingModule::class,
AppUsageModule::class
AppUsageModule::class,
FileModule::class
]
)
interface AppComponent : AndroidInjector<DuckDuckGoApplication> {
Expand Down
33 changes: 33 additions & 0 deletions app/src/main/java/com/duckduckgo/app/di/FileModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2019 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.di

import com.duckduckgo.app.global.file.AndroidFileDeleter
import com.duckduckgo.app.global.file.FileDeleter
import dagger.Module
import dagger.Provides


@Module
class FileModule {

@Provides
fun providesFileDeleter(): FileDeleter {
return AndroidFileDeleter()
}

}
5 changes: 3 additions & 2 deletions app/src/main/java/com/duckduckgo/app/di/PrivacyModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import android.content.Context
import com.duckduckgo.app.browser.WebDataManager
import com.duckduckgo.app.entities.EntityMapping
import com.duckduckgo.app.fire.*
import com.duckduckgo.app.global.file.FileDeleter
import com.duckduckgo.app.global.install.AppInstallStore
import com.duckduckgo.app.global.view.ClearDataAction
import com.duckduckgo.app.global.view.ClearPersonalDataAction
Expand Down Expand Up @@ -82,7 +83,7 @@ class PrivacyModule {

@Provides
@Singleton
fun appCacheCleaner(context: Context): AppCacheClearer {
return AndroidAppCacheClearer(context)
fun appCacheCleaner(context: Context, fileDeleter: FileDeleter): AppCacheClearer {
return AndroidAppCacheClearer(context, fileDeleter)
}
}
21 changes: 15 additions & 6 deletions app/src/main/java/com/duckduckgo/app/fire/AppCacheClearer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
package com.duckduckgo.app.fire

import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import com.duckduckgo.app.global.file.FileDeleter


interface AppCacheClearer {
Expand All @@ -27,12 +26,22 @@ interface AppCacheClearer {

}

class AndroidAppCacheClearer(private val context: Context) : AppCacheClearer {
class AndroidAppCacheClearer(private val context: Context, private val fileDeleter: FileDeleter) : AppCacheClearer {

override suspend fun clearCache() {
withContext(Dispatchers.IO) {
context.cacheDir.deleteRecursively()
}
fileDeleter.deleteContents(context.cacheDir, FILENAMES_EXCLUDED_FROM_DELETION)
}

companion object {

/* Exclude this WebView cache directory, based on warning from Firefox Focus:
* "If the folder or its contents are deleted, WebView will stop using the disk cache entirely."
*/
private const val WEBVIEW_CACHE_DIR = "org.chromium.android_webview"

private val FILENAMES_EXCLUDED_FROM_DELETION = listOf(
WEBVIEW_CACHE_DIR
)
}

}
44 changes: 44 additions & 0 deletions app/src/main/java/com/duckduckgo/app/global/file/FileDeleter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2019 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.global.file

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File


interface FileDeleter {

/**
* Delete the contents of the given directory, but don't delete the directory itself
*
* Optionally: specify an exclusion list. Files with names exactly matching will not be deleted.
* Note, the exclusion list only applies to the top-level directory. All files in subdirectories will be deleted, regardless of exclusion list.
*/
suspend fun deleteContents(parentDirectory: File, excludedFiles: List<String> = emptyList())
}

class AndroidFileDeleter : FileDeleter {

override suspend fun deleteContents(parentDirectory: File, excludedFiles: List<String>) {
withContext(Dispatchers.IO) {
val files = parentDirectory.listFiles() ?: return@withContext
val filesToDelete = files.filterNot { excludedFiles.contains(it.name) }
filesToDelete.forEach { it.deleteRecursively() }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ class ClearPersonalDataAction @Inject constructor(

dataManager.clearData(createWebView(), createWebStorage(), WebViewDatabase.getInstance(context))
dataManager.clearExternalCookies()
dataManager.deleteWebViewDirectory()

appCacheClearer.clearCache()

Expand Down