Skip to content
Open
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 @@ -36,6 +36,16 @@ interface Autoconsent {
*/
fun changeSetting(setting: Boolean)

/**
* Updates the user's cookie pop-up blocking preference.
*/
fun changeCookiePopUpPreference(preference: CookiePopUpPreference)

/**
* @return the user's cookie pop-up blocking preference.
*/
fun getCookiePopUpPreference(): CookiePopUpPreference

/**
* @return `true` if autoconsent was enabled by the user, `false` otherwise.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2026 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.autoconsent.api

enum class CookiePopUpPreference {
OFF,
DEFAULT,
MAX,
}
2 changes: 1 addition & 1 deletion autoconsent/autoconsent-impl/libs/autoconsent-bundle.js

Large diffs are not rendered by default.

5 changes: 1 addition & 4 deletions autoconsent/autoconsent-impl/libs/userscript.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import AutoConsent from '@duckduckgo/autoconsent';
import { consentomatic } from '@duckduckgo/autoconsent/rules/consentomatic.json'

const autoconsent = new AutoConsent(
(message) => {
AutoconsentAndroid.process(JSON.stringify(message));
},
null,
{ consentomatic },
}
);
window.autoconsentMessageCallback = (msg) => {
autoconsent.receiveMessageCallback(msg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.duckduckgo.app.di.IsMainProcess
import com.duckduckgo.app.privacy.db.UserAllowListRepository
import com.duckduckgo.autoconsent.api.Autoconsent
import com.duckduckgo.autoconsent.api.AutoconsentCallback
import com.duckduckgo.autoconsent.api.CookiePopUpPreference
import com.duckduckgo.autoconsent.impl.AutoconsentInterface.Companion.AUTOCONSENT_INTERFACE
import com.duckduckgo.autoconsent.impl.cache.AutoconsentSettingsCache
import com.duckduckgo.autoconsent.impl.handlers.ReplyHandler
Expand Down Expand Up @@ -86,6 +87,14 @@ class RealAutoconsent @Inject constructor(
settingsRepository.userSetting = setting
}

override fun changeCookiePopUpPreference(preference: CookiePopUpPreference) {
settingsRepository.cookiePopUpPreference = preference
}

override fun getCookiePopUpPreference(): CookiePopUpPreference {
return settingsRepository.cookiePopUpPreference
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enabled state ignores cookie preference

High Severity

With the new settings UI, changeCookiePopUpPreference only persists cookiePopUpPreference, while isSettingEnabled() and isAutoconsentEnabled() still read the legacy userSetting flag. Enabling auto-manage when legacy storage is false leaves isAutoconsentEnabled() false, so injectAutoconsent skips injecting the autoconsent script and cookie pop-up handling never runs despite the user turning protection on.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4a39d83. Configure here.

override fun isSettingEnabled(): Boolean {
return settingsRepository.userSetting
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.core.net.toUri
import com.duckduckgo.app.di.AppCoroutineScope
import com.duckduckgo.autoconsent.api.AutoconsentCallback
import com.duckduckgo.autoconsent.api.AutoconsentResult
import com.duckduckgo.autoconsent.api.CookiePopUpPreference
import com.duckduckgo.autoconsent.impl.AutoconsentReloadLoopDetector
import com.duckduckgo.autoconsent.impl.MessageHandlerPlugin
import com.duckduckgo.autoconsent.impl.adapters.JSONObjectAdapter
Expand Down Expand Up @@ -75,10 +76,15 @@ class InitMessageHandlerPlugin @Inject constructor(

reloadLoopDetector.updateUrl(webView, url)

// Remove comment to promote feature and remove @Ignore from tests
val isAutoconsentDisabled = !settingsRepository.userSetting // && settingsRepository.firstPopupHandled
val preference = if (autoconsentFeature.cookiePopUpPreferenceSetting().isEnabled()) {
settingsRepository.cookiePopUpPreference
} else if (settingsRepository.userSetting) {
CookiePopUpPreference.DEFAULT
} else {
CookiePopUpPreference.OFF
}

if (isAutoconsentDisabled) {
if (preference == CookiePopUpPreference.OFF) {
autoconsentPixelManager.fireDailyPixel(AutoConsentPixel.AUTOCONSENT_DISABLED_FOR_SITE_DAILY)
return@launch
}
Expand All @@ -98,22 +104,8 @@ class InitMessageHandlerPlugin @Inject constructor(
)

val settings = settingsCache.getSettings() ?: return@launch
val autoAction = getAutoAction(webView)
val enablePreHide = settingsRepository.userSetting
val detectRetries = 20
val disabledCmps = settings.disabledCMPs
val config =
Config(
enabled = true,
autoAction,
disabledCmps,
enablePreHide,
detectRetries,
isMainWorld = true,
enableCosmeticRules = true,
enableHeuristicDetection = true,
enableHeuristicAction = autoconsentFeature.heuristicAction().isEnabled(),
)
val cookiePopUpPreferenceSettingEnabled = autoconsentFeature.cookiePopUpPreferenceSetting().isEnabled()
val config = buildConfig(preference, cookiePopUpPreferenceSettingEnabled, settings.disabledCMPs, webView)
val initResp = if (autoconsentFeature.ruleFiltering().isEnabled()) {
InitResp(config = config, rules = filterCompactRules(settings.compactRuleList, url))
} else {
Expand Down Expand Up @@ -143,6 +135,40 @@ class InitMessageHandlerPlugin @Inject constructor(
return "optOut"
}

private fun buildConfig(
preference: CookiePopUpPreference,
cookiePopUpPreferenceSettingEnabled: Boolean,
disabledCmps: List<String>,
webView: WebView,
): Config {
return Config(
enabled = true,
autoAction = getAutoAction(webView),
disabledCmps = disabledCmps,
enablePrehide = true,
detectRetries = 20,
isMainWorld = true,
enableCosmeticRules = true,
enableHeuristicDetection = true,
heuristicMode = getHeuristicMode(preference, cookiePopUpPreferenceSettingEnabled),
)
}

private fun getHeuristicMode(
preference: CookiePopUpPreference,
cookiePopUpPreferenceSettingEnabled: Boolean,
): String {
if (!autoconsentFeature.heuristicAction().isEnabled()) {
return "off"
}

return when (preference) {
CookiePopUpPreference.MAX -> "tier2"
CookiePopUpPreference.DEFAULT -> if (cookiePopUpPreferenceSettingEnabled) "tier1" else "reject"
CookiePopUpPreference.OFF -> "off"
}
}

private fun parseMessage(jsonString: String): InitMessage? {
val jsonAdapter: JsonAdapter<InitMessage> = moshi.adapter(InitMessage::class.java)
return jsonAdapter.fromJson(jsonString)
Expand Down Expand Up @@ -245,7 +271,7 @@ class InitMessageHandlerPlugin @Inject constructor(
val isMainWorld: Boolean,
val enableCosmeticRules: Boolean,
val enableHeuristicDetection: Boolean,
val enableHeuristicAction: Boolean,
val heuristicMode: String,
)

data class AutoconsentRuleset(val compact: Any?)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,10 @@ interface AutoconsentFeature {

@Toggle.DefaultValue(DefaultFeatureValue.FALSE)
fun heuristicAction(): Toggle

/**
* Gate for the new Cookie Pop-up Preference settings UI and behavior.
*/
@Toggle.DefaultValue(DefaultFeatureValue.FALSE)
fun cookiePopUpPreferenceSetting(): Toggle
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ package com.duckduckgo.autoconsent.impl.store
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import com.duckduckgo.autoconsent.api.CookiePopUpPreference
import com.duckduckgo.autoconsent.impl.remoteconfig.AutoconsentFeature
import com.duckduckgo.common.utils.DispatcherProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

interface AutoconsentSettingsDataStore {
var cookiePopUpPreference: CookiePopUpPreference
var userSetting: Boolean
var firstPopupHandled: Boolean
fun invalidateCache()
Expand All @@ -38,7 +40,8 @@ class RealAutoconsentSettingsDataStore constructor(
) : AutoconsentSettingsDataStore {

private val preferences: SharedPreferences by lazy { context.getSharedPreferences(FILENAME, Context.MODE_PRIVATE) }
private var cachedInternalUserSetting: Boolean? = null
private var cachedLegacyUserSetting: Boolean? = null
private var cachedCookiePopUpPreference: CookiePopUpPreference? = null

private var _defaultValue: Boolean? = null
private val defaultValue: Boolean
Expand All @@ -51,24 +54,32 @@ class RealAutoconsentSettingsDataStore constructor(

init {
appCoroutineScope.launch(dispatcherProvider.io()) {
cachedInternalUserSetting = preferences.getBoolean(AUTOCONSENT_USER_SETTING, defaultValue)
cachedLegacyUserSetting = readLegacyUserSetting()
cachedCookiePopUpPreference = readCookiePopUpPreference()
}
}

override var userSetting: Boolean
override var cookiePopUpPreference: CookiePopUpPreference
get() {
return cachedInternalUserSetting ?: preferences.getBoolean(AUTOCONSENT_USER_SETTING, defaultValue).also {
cachedInternalUserSetting = it
return cachedCookiePopUpPreference ?: readCookiePopUpPreference().also {
cachedCookiePopUpPreference = it
}
}
set(value) {
preferences.edit(commit = true) {
putBoolean(AUTOCONSENT_USER_SETTING, value)
putString(AUTOCONSENT_COOKIE_POP_UP_PREFERENCE, value.name)
}.also {
cachedInternalUserSetting = value
cachedCookiePopUpPreference = value
}
}

override var userSetting: Boolean
get() = cachedLegacyUserSetting ?: readLegacyUserSetting().also { cachedLegacyUserSetting = it }
set(value) {
writeLegacyUserSetting(value)
cachedLegacyUserSetting = value
}

override var firstPopupHandled: Boolean
get() = preferences.getBoolean(AUTOCONSENT_FIRST_POPUP_HANDLED, false)
set(value) {
Expand All @@ -80,13 +91,60 @@ class RealAutoconsentSettingsDataStore constructor(
override fun invalidateCache() {
appCoroutineScope.launch(dispatcherProvider.io()) {
_defaultValue = autoconsentFeature.onByDefault().isEnabled()
cachedInternalUserSetting = null // invalidate cache
cachedLegacyUserSetting = null
cachedCookiePopUpPreference = null
}
}

private fun readCookiePopUpPreference(): CookiePopUpPreference {
if (preferences.contains(AUTOCONSENT_COOKIE_POP_UP_PREFERENCE)) {
return parsePreference(preferences.getString(AUTOCONSENT_COOKIE_POP_UP_PREFERENCE, null))
}
val migrated = migrateFromLegacySetting()
if (preferences.contains(AUTOCONSENT_USER_SETTING)) {
preferences.edit(commit = true) {
putString(AUTOCONSENT_COOKIE_POP_UP_PREFERENCE, migrated.name)
}
}
return migrated
}

private fun migrateFromLegacySetting(): CookiePopUpPreference {
return if (preferences.contains(AUTOCONSENT_USER_SETTING)) {
if (preferences.getBoolean(AUTOCONSENT_USER_SETTING, false)) {
CookiePopUpPreference.DEFAULT
} else {
CookiePopUpPreference.OFF
}
} else if (defaultValue) {
CookiePopUpPreference.DEFAULT
} else {
CookiePopUpPreference.OFF
}
}

private fun readLegacyUserSetting(): Boolean {
return preferences.getBoolean(AUTOCONSENT_USER_SETTING, defaultValue)
}

private fun writeLegacyUserSetting(value: Boolean) {
preferences.edit(commit = true) {
putBoolean(AUTOCONSENT_USER_SETTING, value)
}
}

private fun parsePreference(value: String?): CookiePopUpPreference {
return try {
CookiePopUpPreference.valueOf(value!!)
} catch (_: Exception) {
CookiePopUpPreference.DEFAULT
}
}

companion object {
private const val FILENAME = "com.duckduckgo.autoconsent.store.settings"
private const val AUTOCONSENT_USER_SETTING = "AutoconsentUserSetting"
private const val AUTOCONSENT_COOKIE_POP_UP_PREFERENCE = "AutoconsentCookiePopUpPreference"
private const val AUTOCONSENT_FIRST_POPUP_HANDLED = "AutoconsentFirstPopupHandled"
}
}
Loading
Loading