From c2167816dc1598ad879942b25ba0049b406497a9 Mon Sep 17 00:00:00 2001 From: Arturo Mejia Date: Fri, 11 Nov 2022 18:05:02 -0500 Subject: [PATCH] Bug 1797605 - Add API support for ignoring sites for cookie banner handling --- .../GeckoCookieBannersStorage.kt | 86 +++++++++++++++ .../GeckoCookieBannersStorageTest.kt | 102 ++++++++++++++++++ .../cookiehandling/CookieBannersStorage.kt | 47 ++++++++ docs/changelog.md | 3 + 4 files changed, 238 insertions(+) create mode 100644 android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/cookiebanners/GeckoCookieBannersStorage.kt create mode 100644 android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/cookiebanners/GeckoCookieBannersStorageTest.kt create mode 100644 android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/cookiehandling/CookieBannersStorage.kt diff --git a/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/cookiebanners/GeckoCookieBannersStorage.kt b/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/cookiebanners/GeckoCookieBannersStorage.kt new file mode 100644 index 000000000000..a9371d01b13d --- /dev/null +++ b/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/cookiebanners/GeckoCookieBannersStorage.kt @@ -0,0 +1,86 @@ +/* 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 mozilla.components.browser.engine.gecko.cookiebanners + +import androidx.annotation.VisibleForTesting +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import mozilla.components.browser.engine.gecko.await +import mozilla.components.concept.engine.EngineSession.CookieBannerHandlingMode +import mozilla.components.concept.engine.EngineSession.CookieBannerHandlingMode.DISABLED +import mozilla.components.concept.engine.cookiehandling.CookieBannersStorage +import org.mozilla.geckoview.GeckoRuntime +import org.mozilla.geckoview.StorageController + +/** + * A storage to store [CookieBannerHandlingMode] using GeckoView APIs. + */ +class GeckoCookieBannersStorage( + runtime: GeckoRuntime, +) : CookieBannersStorage { + + private val geckoStorage: StorageController = runtime.storageController + private val mainScope = CoroutineScope(Dispatchers.Main) + + override suspend fun adException( + uri: String, + privateBrowsing: Boolean, + ) { + setGeckoException(uri, DISABLED, privateBrowsing) + } + + override suspend fun findExceptionFor( + uri: String, + privateBrowsing: Boolean, + ): CookieBannerHandlingMode { + return queryExceptionInGecko(uri, privateBrowsing) + } + + override suspend fun hasException(uri: String, privateBrowsing: Boolean): Boolean { + return findExceptionFor(uri, privateBrowsing) == DISABLED + } + + override suspend fun removeException(uri: String, privateBrowsing: Boolean) { + removeGeckoException(uri, privateBrowsing) + } + + @VisibleForTesting + internal fun removeGeckoException(uri: String, privateBrowsing: Boolean) { + geckoStorage.removeCookieBannerModeForDomain(uri, privateBrowsing) + } + + @VisibleForTesting + internal fun setGeckoException( + uri: String, + mode: CookieBannerHandlingMode, + privateBrowsing: Boolean, + ) { + geckoStorage.setCookieBannerModeForDomain( + uri, + mode.mode, + privateBrowsing, + ) + } + + @VisibleForTesting + internal suspend fun queryExceptionInGecko( + uri: String, + privateBrowsing: Boolean, + ): CookieBannerHandlingMode { + return withContext(mainScope.coroutineContext) { + geckoStorage.getCookieBannerModeForDomain(uri, privateBrowsing).await() + ?.toCookieBannerHandlingMode() ?: throw IllegalArgumentException( + "An error happened trying to find cookie banners mode for the " + + "uri $uri and private browsing mode $privateBrowsing", + ) + } + } +} + +@VisibleForTesting +internal fun Int.toCookieBannerHandlingMode(): CookieBannerHandlingMode { + return CookieBannerHandlingMode.values().first { it.mode == this } +} diff --git a/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/cookiebanners/GeckoCookieBannersStorageTest.kt b/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/cookiebanners/GeckoCookieBannersStorageTest.kt new file mode 100644 index 000000000000..1c6524aa2ccb --- /dev/null +++ b/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/cookiebanners/GeckoCookieBannersStorageTest.kt @@ -0,0 +1,102 @@ +/* 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 mozilla.components.browser.engine.gecko.cookiebanners + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertFalse +import junit.framework.TestCase.assertTrue +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import mozilla.components.concept.engine.EngineSession.CookieBannerHandlingMode.DISABLED +import mozilla.components.concept.engine.EngineSession.CookieBannerHandlingMode.REJECT_OR_ACCEPT_ALL +import mozilla.components.support.test.mock +import mozilla.components.support.test.whenever +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.mozilla.geckoview.GeckoRuntime +import org.mozilla.geckoview.StorageController + +@ExperimentalCoroutinesApi +@RunWith(AndroidJUnit4::class) +class GeckoCookieBannersStorageTest { + private lateinit var runtime: GeckoRuntime + private lateinit var geckoStorage: GeckoCookieBannersStorage + private lateinit var storageController: StorageController + + @Before + fun setup() { + storageController = mock() + runtime = mock() + + whenever(runtime.storageController).thenReturn(storageController) + + geckoStorage = spy(GeckoCookieBannersStorage(runtime)) + } + + @Test + fun `GIVEN a cookie banner mode WHEN adding an exception THEN add an exception for the given uri and browsing mode`() = + runTest { + val uri = "https://www.mozilla.org" + + doReturn(Unit).`when`(geckoStorage) + .setGeckoException(uri = uri, mode = DISABLED, privateBrowsing = false) + + geckoStorage.adException(uri = uri, privateBrowsing = false) + + verify(geckoStorage).setGeckoException(uri, DISABLED, false) + } + + @Test + fun `GIVEN uri and browsing mode WHEN removing an exception THEN remove the exception`() = + runTest { + val uri = "https://www.mozilla.org" + + doReturn(Unit).`when`(geckoStorage).removeGeckoException(uri, false) + + geckoStorage.removeException(uri = uri, privateBrowsing = false) + + verify(geckoStorage).removeGeckoException(uri, false) + } + + @Test + fun `GIVEN uri and browsing mode WHEN querying an exception THEN return the matching exception`() = + runTest { + val uri = "https://www.mozilla.org" + + doReturn(REJECT_OR_ACCEPT_ALL).`when`(geckoStorage) + .queryExceptionInGecko(uri = uri, privateBrowsing = false) + + val result = geckoStorage.findExceptionFor(uri = uri, privateBrowsing = false) + assertEquals(REJECT_OR_ACCEPT_ALL, result) + } + + @Test + fun `GIVEN uri and browsing mode WHEN checking for an exception THEN indicate if it has exceptions`() = + runTest { + val uri = "https://www.mozilla.org" + + doReturn(REJECT_OR_ACCEPT_ALL).`when`(geckoStorage) + .queryExceptionInGecko(uri = uri, privateBrowsing = false) + + var result = geckoStorage.hasException(uri = uri, privateBrowsing = false) + + assertTrue(result) + + Mockito.reset(geckoStorage) + + doReturn(DISABLED).`when`(geckoStorage) + .queryExceptionInGecko(uri = uri, privateBrowsing = false) + + result = geckoStorage.hasException(uri = uri, privateBrowsing = false) + + assertFalse(result) + } +} diff --git a/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/cookiehandling/CookieBannersStorage.kt b/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/cookiehandling/CookieBannersStorage.kt new file mode 100644 index 000000000000..accbd4e9c0b6 --- /dev/null +++ b/android-components/components/concept/engine/src/main/java/mozilla/components/concept/engine/cookiehandling/CookieBannersStorage.kt @@ -0,0 +1,47 @@ +/* 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 mozilla.components.concept.engine.cookiehandling + +import mozilla.components.concept.engine.EngineSession.CookieBannerHandlingMode + +/** + * Represents a storage to manage [CookieBannerHandlingMode] exceptions. + */ +interface CookieBannersStorage { + /** + * Set the [CookieBannerHandlingMode.DISABLED] mode for the given [uri] and [privateBrowsing]. + * @param uri the [uri] for the site to be updated. + * @param privateBrowsing Indicates if given [uri] should be in private browsing or not. + */ + suspend fun adException( + uri: String, + privateBrowsing: Boolean, + ) + + /** + * Find a [CookieBannerHandlingMode] that matches the given [uri] and browsing mode. + * @param uri the [uri] to be used as filter in the search. + * @param privateBrowsing Indicates if given [uri] should be in private browsing or not. + * @return the [CookieBannerHandlingMode] for the provided [uri] and browsing mode. + */ + suspend fun findExceptionFor(uri: String, privateBrowsing: Boolean): CookieBannerHandlingMode + + /** + * Indicates if the given [uri] and browsing mode has the [CookieBannerHandlingMode.DISABLED] mode. + * @param uri the [uri] to be used as filter in the search. + * @param privateBrowsing Indicates if given [uri] should be in private browsing or not. + * @return A [Boolean] indicating if the [CookieBannerHandlingMode] has been updated, from the + * default value. + */ + suspend fun hasException(uri: String, privateBrowsing: Boolean): Boolean + + /** + * Remove any [CookieBannerHandlingMode] exception that has been applied to the given [uri] and + * browsing mode. + * @param uri the [uri] to be used as filter in the search. + * @param privateBrowsing Indicates if given [uri] should be in private browsing or not. + */ + suspend fun removeException(uri: String, privateBrowsing: Boolean) +} diff --git a/docs/changelog.md b/docs/changelog.md index 431f0c8ccd3f..add1d84fe10e 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -25,6 +25,9 @@ permalink: /changelog/ * **support-ktx**: * Added `String.toShortUrl` extension that allows making URLs more user friendly [#1796379](https://bugzilla.mozilla.org/show_bug.cgi?id=1796379) +* **browser-engine-gecko** + * 🆕 Added `GeckoCookieBannersStorage.kt` to manage cookie banner exceptions [bug #1797605](https://bugzilla.mozilla.org/show_bug.cgi?id=1797605). + # 108.0.0 * [Commits](https://github.com/mozilla-mobile/firefox-android/compare/v107.0.0...v108.0.0) * [Dependencies](https://github.com/mozilla-mobile/firefox-android/blob/v108.0.0/android-components/buildSrc/src/main/java/Dependencies.kt)