improved preference and tracking protection handling

This commit is contained in:
Fabian Freund
2026-03-10 17:52:22 +01:00
parent e2c85febf4
commit 3519f8238b
13 changed files with 212 additions and 25 deletions
@@ -24,6 +24,7 @@ import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/features/geckoview/features/preferences/data/repositories/preference_observer.dart';
import 'package:weblibre/features/geckoview/features/preferences/data/repositories/preference_settings.dart';
import 'package:weblibre/features/user/domain/repositories/engine_settings.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
@@ -143,6 +144,7 @@ class EngineSettingsReplicationService
TrackingProtectionPolicy.custom) {
await _service.customTrackingProtectionPolicy(
trackingProtectionPolicy: settings.trackingProtectionPolicy,
contentBlocking: settings.contentBlocking,
blockCookies: settings.blockCookies,
customCookiePolicy: settings.customCookiePolicy,
blockTrackingContent: settings.blockTrackingContent,
@@ -160,6 +162,7 @@ class EngineSettingsReplicationService
} else {
await _service.trackingProtectionPolicy(
settings.trackingProtectionPolicy,
contentBlocking: settings.contentBlocking,
);
}
}
@@ -237,8 +240,7 @@ class EngineSettingsReplicationService
);
}
// Web Content Settings
if (previous.value?.webFontsEnabled !=
settings.webFontsEnabled) {
if (previous.value?.webFontsEnabled != settings.webFontsEnabled) {
await _service.webFontsEnabled(settings.webFontsEnabled);
}
if (previous.value?.automaticFontSizeAdjustment !=
@@ -247,8 +249,7 @@ class EngineSettingsReplicationService
settings.automaticFontSizeAdjustment,
);
}
if (previous.value?.fontSizeFactor !=
settings.fontSizeFactor) {
if (previous.value?.fontSizeFactor != settings.fontSizeFactor) {
await _service.fontSizeFactor(settings.fontSizeFactor);
}
if (previous.value?.fontInflationEnabled !=
@@ -267,8 +268,7 @@ class EngineSettingsReplicationService
if (previous.value?.lnaBlocking != settings.lnaBlocking) {
await _service.lnaBlocking(settings.lnaBlocking);
}
if (previous.value?.lnaBlockTrackers !=
settings.lnaBlockTrackers) {
if (previous.value?.lnaBlockTrackers != settings.lnaBlockTrackers) {
await _service.lnaBlockTrackers(settings.lnaBlockTrackers);
}
if (previous.value?.lnaEnabled != settings.lnaEnabled) {
@@ -276,6 +276,9 @@ class EngineSettingsReplicationService
}
} else {
await _service.setDefaultSettings(settings);
await ref
.read(startupPreferenceEnforcementServiceProvider.notifier)
.apply();
await ref
.read(preferenceFixatorProvider.notifier)
.register('pdfjs.disabled', !settings.enablePdfJs);
@@ -44,7 +44,7 @@ final class EngineSettingsReplicationServiceProvider
}
String _$engineSettingsReplicationServiceHash() =>
r'20fc7f07daed37346f44e692928bb225653f67c8';
r'a2fe25ca6458dd22d705affee74036e1ba379972';
abstract class _$EngineSettingsReplicationService extends $Notifier<void> {
void build();
@@ -489,11 +489,11 @@ class _BrowserViewState extends ConsumerState<BrowserView>
ref.listenManual(
fireImmediately: true,
engineSettingsReplicationServiceProvider,
preferenceFixatorProvider,
(previous, next) {},
onError: (error, stackTrace) {
logger.e(
'Error listening to engineSettingsReplicationServiceProvider',
'Error listening to preferenceFixatorProvider',
error: error,
stackTrace: stackTrace,
);
@@ -515,11 +515,11 @@ class _BrowserViewState extends ConsumerState<BrowserView>
ref.listenManual(
fireImmediately: true,
preferenceFixatorProvider,
engineSettingsReplicationServiceProvider,
(previous, next) {},
onError: (error, stackTrace) {
logger.e(
'Error listening to preferenceFixatorProvider',
'Error listening to engineSettingsReplicationServiceProvider',
error: error,
stackTrace: stackTrace,
);
@@ -65,6 +65,7 @@ class PreferenceSetting with FastEquatable {
final bool requireUserOptIn;
final bool shouldBeDefault;
final bool enforceOnStartup;
PreferenceSetting({
required this.value,
@@ -73,6 +74,7 @@ class PreferenceSetting with FastEquatable {
this.current,
this.requireUserOptIn = false,
this.shouldBeDefault = false,
this.enforceOnStartup = false,
});
factory PreferenceSetting.fromJson(Map<String, dynamic> json) =>
@@ -88,5 +90,6 @@ class PreferenceSetting with FastEquatable {
description,
requireUserOptIn,
shouldBeDefault,
enforceOnStartup,
];
}
@@ -86,6 +86,8 @@ abstract class _$PreferenceSettingCWProxy {
PreferenceSetting shouldBeDefault(bool shouldBeDefault);
PreferenceSetting enforceOnStartup(bool enforceOnStartup);
/// Creates a new instance with the provided field values.
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `PreferenceSetting(...).copyWith.fieldName(value)`.
///
@@ -100,6 +102,7 @@ abstract class _$PreferenceSettingCWProxy {
GeckoPref? current,
bool requireUserOptIn,
bool shouldBeDefault,
bool enforceOnStartup,
});
}
@@ -131,6 +134,10 @@ class _$PreferenceSettingCWProxyImpl implements _$PreferenceSettingCWProxy {
PreferenceSetting shouldBeDefault(bool shouldBeDefault) =>
call(shouldBeDefault: shouldBeDefault);
@override
PreferenceSetting enforceOnStartup(bool enforceOnStartup) =>
call(enforceOnStartup: enforceOnStartup);
@override
/// Creates a new instance with the provided field values.
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `PreferenceSetting(...).copyWith.fieldName(value)`.
@@ -146,6 +153,7 @@ class _$PreferenceSettingCWProxyImpl implements _$PreferenceSettingCWProxy {
Object? current = const $CopyWithPlaceholder(),
Object? requireUserOptIn = const $CopyWithPlaceholder(),
Object? shouldBeDefault = const $CopyWithPlaceholder(),
Object? enforceOnStartup = const $CopyWithPlaceholder(),
}) {
return PreferenceSetting(
value: value == const $CopyWithPlaceholder() || value == null
@@ -176,6 +184,12 @@ class _$PreferenceSettingCWProxyImpl implements _$PreferenceSettingCWProxy {
? _value.shouldBeDefault
// ignore: cast_nullable_to_non_nullable
: shouldBeDefault as bool,
enforceOnStartup:
enforceOnStartup == const $CopyWithPlaceholder() ||
enforceOnStartup == null
? _value.enforceOnStartup
// ignore: cast_nullable_to_non_nullable
: enforceOnStartup as bool,
);
}
}
@@ -199,6 +213,7 @@ PreferenceSetting _$PreferenceSettingFromJson(Map<String, dynamic> json) =>
description: json['description'] as String?,
requireUserOptIn: json['requireUserOptIn'] as bool? ?? false,
shouldBeDefault: json['shouldBeDefault'] as bool? ?? false,
enforceOnStartup: json['enforceOnStartup'] as bool? ?? false,
);
Map<String, dynamic> _$PreferenceSettingToJson(PreferenceSetting instance) =>
@@ -208,4 +223,5 @@ Map<String, dynamic> _$PreferenceSettingToJson(PreferenceSetting instance) =>
'description': instance.description,
'requireUserOptIn': instance.requireUserOptIn,
'shouldBeDefault': instance.shouldBeDefault,
'enforceOnStartup': instance.enforceOnStartup,
};
@@ -30,6 +30,59 @@ import 'package:weblibre/features/geckoview/features/tabs/utils/setting_groups_s
part 'preference_settings.g.dart';
@Riverpod(keepAlive: true)
class StartupPreferenceEnforcementService
extends _$StartupPreferenceEnforcementService {
Future<void> apply() async {
final content = await ref.read(_preferenceSettingContentProvider.future);
final groups = deserializePreferenceSettingGroups(
PreferencePartition.user,
content,
);
final startupPrefs = {
for (final group in groups.values)
...Map.fromEntries(
group.settings.entries
.where(
(entry) =>
entry.value.enforceOnStartup &&
!entry.value.requireUserOptIn,
)
.map((entry) => MapEntry(entry.key, entry.value.value)),
),
};
if (startupPrefs.isEmpty) {
return;
}
final currentPrefs = await GeckoPrefService().getPrefs(
startupPrefs.keys.toList(),
);
// Respect persisted user opt-outs by only enforcing startup values if the
// profile already carries the same user-branch value.
final prefsToEnforce = Map.fromEntries(
startupPrefs.entries.where((entry) {
final current = currentPrefs[entry.key];
if (current == null) {
return true;
}
return current.hasUserChangedValue && current.userValue == entry.value;
}),
);
if (prefsToEnforce.isNotEmpty) {
await GeckoPrefService().applyPrefs(prefsToEnforce);
}
}
@override
void build() {}
}
@Riverpod(keepAlive: true)
Future<Map<String, dynamic>> _preferenceSettingContent(Ref ref) async {
return await rootBundle
@@ -9,6 +9,62 @@ part of 'preference_settings.dart';
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(StartupPreferenceEnforcementService)
final startupPreferenceEnforcementServiceProvider =
StartupPreferenceEnforcementServiceProvider._();
final class StartupPreferenceEnforcementServiceProvider
extends $NotifierProvider<StartupPreferenceEnforcementService, void> {
StartupPreferenceEnforcementServiceProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'startupPreferenceEnforcementServiceProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() =>
_$startupPreferenceEnforcementServiceHash();
@$internal
@override
StartupPreferenceEnforcementService create() =>
StartupPreferenceEnforcementService();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(void value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<void>(value),
);
}
}
String _$startupPreferenceEnforcementServiceHash() =>
r'107cdcf7550b7977b53a3cf7419861cdcac0fe3a';
abstract class _$StartupPreferenceEnforcementService extends $Notifier<void> {
void build();
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<void, void>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<void, void>,
void,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}
@ProviderFor(_preferenceSettingContent)
final _preferenceSettingContentProvider = _PreferenceSettingContentProvider._();
@@ -151,7 +151,7 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
queryParameterStripping: queryParameterStripping,
queryParameterStrippingAllowList: '',
queryParameterStrippingStripList:
'__hsfp __hssc __hstc __s _bhlid _branch_match_id _branch_referrer _gl _hsenc _kx _openstat at_recipient_id at_recipient_list bbeml bsft_clkid bsft_uid dclid et_rid fb_action_ids fb_comment_id fbclid gbraid gclid guce_referrer guce_referrer_sig hsCtaTracking igshid irclickid mc_eid mkt_tok ml_subscriber ml_subscriber_hash msclkid mtm_cid oft_c oft_ck oft_d oft_id oft_ids oft_k oft_lk oft_sk oly_anon_id oly_enc_id pk_cid rb_clickid s_cid sc_customer sc_eh sc_uid srsltid ss_email_id twclid unicorn_click_id vero_conv vero_id vgo_ee wbraid wickedid yclid ymclid ysclid',
'__hsfp __hssc __hstc __s _bhlid _branch_match_id _branch_referrer _gl _hsenc _kx _openstat at_recipient_id at_recipient_list bbeml bsft_clkid bsft_uid dclid et_rid fb_action_ids fb_comment_id fbclid gbraid gclid guce_referrer guce_referrer_sig hsCtaTracking igshid irclickid mc_eid mkt_tok ml_subscriber ml_subscriber_hash msclkid mtm_cid oft_c oft_ck oft_d oft_id oft_ids oft_k oft_lk oft_sk oly_anon_id oly_enc_id pk_cid rb_clickid s_cid sc_customer sc_eh sc_uid sms_click sms_source sms_uph srsltid ss_email_id syclid ttclid twclid unicorn_click_id vero_conv vero_id vgo_ee wbraid wickedid yclid ymclid ysclid',
bounceTrackingProtectionMode: bounceTrackingProtectionMode,
);
@@ -256,10 +256,9 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
bool? lnaBlockTrackers,
bool? lnaEnabled,
}) : queryParameterStripping =
queryParameterStripping ?? QueryParameterStripping.disabled,
queryParameterStripping ?? QueryParameterStripping.enabled,
bounceTrackingProtectionMode =
bounceTrackingProtectionMode ??
BounceTrackingProtectionMode.disabled,
bounceTrackingProtectionMode ?? BounceTrackingProtectionMode.enabled,
dohSettingsMode = dohSettingsMode ?? DohSettingsMode.increased,
dohProviderUrl = dohProviderUrl ?? BuiltInDohProviders.quad9.url,
dohDefaultProviderUrl =
@@ -34,7 +34,7 @@ final class EngineSettingsRepositoryProvider
}
String _$engineSettingsRepositoryHash() =>
r'a3bcd3a82d251ce6fd0adc4922e512533d70a10a';
r'3d062d316bfcb86f3216846ed2cf5edd148460eb';
abstract class _$EngineSettingsRepository
extends $StreamNotifier<EngineSettings> {
@@ -67,8 +67,16 @@ object EngineProvider {
}
)
contentBlocking.queryParameterStrippingAllowList(components.contentBlocking.queryParameterStrippingAllowList)
contentBlocking.queryParameterStrippingStripList(components.contentBlocking.queryParameterStrippingStripList)
if (components.contentBlocking.queryParameterStrippingAllowList.isNotEmpty()) {
contentBlocking.queryParameterStrippingAllowList(
components.contentBlocking.queryParameterStrippingAllowList,
)
}
if (components.contentBlocking.queryParameterStrippingStripList.isNotEmpty()) {
contentBlocking.queryParameterStrippingStripList(
components.contentBlocking.queryParameterStrippingStripList,
)
}
// if (isCrashReportActive) {
// builder.crashHandler(CrashHandlerService::class.java)
@@ -44,6 +44,8 @@ import java.io.File
import java.util.concurrent.TimeUnit
private const val HISTORY_METADATA_MAX_AGE_IN_MS = 14L * 24 * 60 * 60 * 1000 // 14 days
private const val DEFAULT_QUERY_PARAMETER_STRIPPING_STRIP_LIST =
"__hsfp __hssc __hstc __s _bhlid _branch_match_id _branch_referrer _gl _hsenc _kx _openstat at_recipient_id at_recipient_list bbeml bsft_clkid bsft_uid dclid et_rid fb_action_ids fb_comment_id fbclid gbraid gclid guce_referrer guce_referrer_sig hsCtaTracking igshid irclickid mc_eid mkt_tok ml_subscriber ml_subscriber_hash msclkid mtm_cid oft_c oft_ck oft_d oft_id oft_ids oft_k oft_lk oft_sk oly_anon_id oly_enc_id pk_cid rb_clickid s_cid sc_customer sc_eh sc_uid sms_click sms_source sms_uph srsltid ss_email_id syclid ttclid twclid unicorn_click_id vero_conv vero_id vgo_ee wbraid wickedid yclid ymclid ysclid"
object GlobalComponents {
private var _components: Components? = null
@@ -281,10 +283,10 @@ object GlobalComponents {
}
val contentBlocking = ContentBlocking(
queryParameterStripping = QueryParameterStripping.DISABLED,
queryParameterStripping = QueryParameterStripping.ENABLED,
queryParameterStrippingAllowList = "",
queryParameterStrippingStripList = "",
bounceTrackingProtectionMode = BounceTrackingProtectionMode.DISABLED,
queryParameterStrippingStripList = DEFAULT_QUERY_PARAMETER_STRIPPING_STRIP_LIST,
bounceTrackingProtectionMode = BounceTrackingProtectionMode.ENABLED,
)
setUp(
@@ -11,6 +11,7 @@ import androidx.preference.PreferenceManager
import eu.weblibre.flutter_mozilla_components.GlobalComponents
import eu.weblibre.flutter_mozilla_components.R
import eu.weblibre.flutter_mozilla_components.pigeons.AppLinksMode
import eu.weblibre.flutter_mozilla_components.pigeons.BounceTrackingProtectionMode as PigeonBounceTrackingProtectionMode
import eu.weblibre.flutter_mozilla_components.pigeons.ColorScheme
import eu.weblibre.flutter_mozilla_components.pigeons.CookieBannerHandlingMode
import eu.weblibre.flutter_mozilla_components.pigeons.CustomCookiePolicy
@@ -31,6 +32,41 @@ import mozilla.components.feature.addons.logger
import mozilla.components.feature.session.SettingsUseCases
import mozilla.components.feature.session.TrackingProtectionUseCases
internal fun PigeonBounceTrackingProtectionMode.toEngineBounceTrackingProtectionMode():
EngineSession.BounceTrackingProtectionMode =
when (this) {
PigeonBounceTrackingProtectionMode.ENABLED ->
EngineSession.BounceTrackingProtectionMode.ENABLED
PigeonBounceTrackingProtectionMode.DISABLED ->
EngineSession.BounceTrackingProtectionMode.DISABLED
PigeonBounceTrackingProtectionMode.ENABLED_STANDBY ->
EngineSession.BounceTrackingProtectionMode.ENABLED_STANDBY
PigeonBounceTrackingProtectionMode.ENABLED_DRY_RUN ->
EngineSession.BounceTrackingProtectionMode.ENABLED_DRY_RUN
}
internal fun TrackingProtectionPolicy.withBounceTrackingProtectionMode(
bounceTrackingProtectionMode: PigeonBounceTrackingProtectionMode,
): TrackingProtectionPolicy {
val updatedPolicy = TrackingProtectionPolicy.select(
trackingCategories = trackingCategories,
cookiePolicy = cookiePolicy,
cookiePolicyPrivateMode = cookiePolicyPrivateMode,
strictSocialTrackingProtection = strictSocialTrackingProtection,
cookiePurging = cookiePurging,
bounceTrackingProtectionMode =
bounceTrackingProtectionMode.toEngineBounceTrackingProtectionMode(),
allowListBaselineTrackingProtection = allowListBaselineTrackingProtection,
allowListConvenienceTrackingProtection = allowListConvenienceTrackingProtection,
)
return when {
useForPrivateSessions && !useForRegularSessions -> updatedPolicy.forPrivateSessionsOnly()
!useForPrivateSessions && useForRegularSessions -> updatedPolicy.forRegularSessionsOnly()
else -> updatedPolicy
}
}
/**
* Implementation of GeckoEngineSettingsApi that manages engine-specific settings
*/
@@ -218,8 +254,11 @@ class GeckoEngineSettingsApiImpl : GeckoEngineSettingsApi {
}
components.core.engineSettings.queryParameterStrippingAllowList = settings.contentBlocking.queryParameterStrippingAllowList
components.core.engineSettings.queryParameterStrippingStripList = settings.contentBlocking.queryParameterStrippingStripList
//TODO: Add bounce tracking protection when available
components.core.engineSettings.trackingProtectionPolicy =
(components.core.engineSettings.trackingProtectionPolicy
?: TrackingProtectionPolicy.select()).withBounceTrackingProtectionMode(
settings.contentBlocking.bounceTrackingProtectionMode,
)
}
if(settings.enterpriseRootsEnabled != null) {
components.core.engineSettings.enterpriseRootsEnabled = settings.enterpriseRootsEnabled;
@@ -29,9 +29,15 @@ class GeckoEngineSettingsService {
);
}
Future<void> trackingProtectionPolicy(TrackingProtectionPolicy state) {
Future<void> trackingProtectionPolicy(
TrackingProtectionPolicy state, {
required ContentBlocking contentBlocking,
}) {
return _api.updateRuntimeSettings(
GeckoEngineSettings(trackingProtectionPolicy: state),
GeckoEngineSettings(
trackingProtectionPolicy: state,
contentBlocking: contentBlocking,
),
);
}
@@ -39,6 +45,7 @@ class GeckoEngineSettingsService {
/// Use this when in CUSTOM mode and any custom setting changes.
Future<void> customTrackingProtectionPolicy({
required TrackingProtectionPolicy trackingProtectionPolicy,
required ContentBlocking contentBlocking,
bool? blockCookies,
CustomCookiePolicy? customCookiePolicy,
bool? blockTrackingContent,
@@ -54,6 +61,7 @@ class GeckoEngineSettingsService {
return _api.updateRuntimeSettings(
GeckoEngineSettings(
trackingProtectionPolicy: trackingProtectionPolicy,
contentBlocking: contentBlocking,
blockCookies: blockCookies,
customCookiePolicy: customCookiePolicy,
blockTrackingContent: blockTrackingContent,