implement tracking protection per site

This commit is contained in:
Fabian Freund
2026-01-16 07:36:38 +01:00
parent d416b8963d
commit 2c00504b42
20 changed files with 1277 additions and 13 deletions
@@ -35,6 +35,7 @@ import eu.weblibre.flutter_mozilla_components.pigeons.GeckoHistoryApi
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoIconsApi
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoPublicSuffixListApi
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoSitePermissionsApi
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoTrackingProtectionApi
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoLogging
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoMlApi
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoPrefApi
@@ -268,6 +269,7 @@ class GeckoBrowserApiImpl : GeckoBrowserApi {
GeckoBookmarksApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoBookmarksApiImpl())
GeckoSitePermissionsApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoSitePermissionsApiImpl())
GeckoPublicSuffixListApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoPublicSuffixListApiImpl(profileApplicationContext))
GeckoTrackingProtectionApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoTrackingProtectionApiImpl())
// Viewport API for dynamic toolbar and keyboard handling
val viewportEvents = GeckoViewportEvents(_flutterPluginBinding.binaryMessenger)
@@ -0,0 +1,78 @@
/*
* 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 https://mozilla.org/MPL/2.0/.
*/
package eu.weblibre.flutter_mozilla_components.api
import eu.weblibre.flutter_mozilla_components.GlobalComponents
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoTrackingProtectionApi
import eu.weblibre.flutter_mozilla_components.pigeons.TrackingProtectionException
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException as MozillaTrackingProtectionException
/**
* Implementation of GeckoTrackingProtectionApi that manages per-site
* Enhanced Tracking Protection exceptions using Mozilla Android Components'
* TrackingProtectionUseCases.
*/
class GeckoTrackingProtectionApiImpl : GeckoTrackingProtectionApi {
private val components by lazy {
requireNotNull(GlobalComponents.components) { "Components not initialized" }
}
override fun containsException(tabId: String, callback: (Result<Boolean>) -> Unit) {
try {
components.useCases.trackingProtectionUseCases.containsException(tabId) { hasException ->
callback(Result.success(hasException))
}
} catch (e: Exception) {
callback(Result.failure(e))
}
}
override fun addException(tabId: String) {
components.useCases.trackingProtectionUseCases.addException(tabId)
}
override fun removeException(tabId: String) {
components.useCases.trackingProtectionUseCases.removeException(tabId)
}
override fun removeExceptionByUrl(url: String, callback: (Result<Unit>) -> Unit) {
try {
// Create a simple TrackingProtectionException implementation for removal
val exception = object : MozillaTrackingProtectionException {
override val url: String = url
}
components.useCases.trackingProtectionUseCases.removeException(exception)
// The Mozilla API is synchronous, callback immediately after completion
callback(Result.success(Unit))
} catch (e: Exception) {
callback(Result.failure(e))
}
}
override fun fetchExceptions(callback: (Result<List<TrackingProtectionException>>) -> Unit) {
try {
components.useCases.trackingProtectionUseCases.fetchExceptions { mozillaExceptions ->
val pigeonExceptions = mozillaExceptions.map { mozillaException ->
TrackingProtectionException(url = mozillaException.url)
}
callback(Result.success(pigeonExceptions))
}
} catch (e: Exception) {
callback(Result.failure(e))
}
}
override fun removeAllExceptions(callback: (Result<Unit>) -> Unit) {
try {
components.useCases.trackingProtectionUseCases.removeAllExceptions {
callback(Result.success(Unit))
}
} catch (e: Exception) {
callback(Result.failure(e))
}
}
}
@@ -51,5 +51,5 @@ class UseCases(
val appLinksUseCases by lazy { AppLinksUseCases(context) }
//val trackingProtectionUseCases by lazy { TrackingProtectionUseCases(store, engine) }
val trackingProtectionUseCases by lazy { TrackingProtectionUseCases(store, engine) }
}
@@ -2825,6 +2825,41 @@ data class SitePermissions (
override fun hashCode(): Int = toList().hashCode()
}
/**
* Tracking protection exception for a site
*
* This represents a site that has been added to the exceptions list,
* meaning tracking protection is disabled for this specific site.
*
* Generated class from Pigeon that represents data sent in messages.
*/
data class TrackingProtectionException (
val url: String
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): TrackingProtectionException {
val url = pigeonVar_list[0] as String
return TrackingProtectionException(url)
}
}
fun toList(): List<Any?> {
return listOf(
url,
)
}
override fun equals(other: Any?): Boolean {
if (other !is TrackingProtectionException) {
return false
}
if (this === other) {
return true
}
return GeckoPigeonUtils.deepEquals(toList(), other.toList()) }
override fun hashCode(): Int = toList().hashCode()
}
private open class GeckoPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
@@ -3233,6 +3268,11 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
SitePermissions.fromList(it)
}
}
210.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
TrackingProtectionException.fromList(it)
}
}
else -> super.readValueOfType(type, buffer)
}
}
@@ -3562,6 +3602,10 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
stream.write(209)
writeValue(stream, value.toList())
}
is TrackingProtectionException -> {
stream.write(210)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
@@ -6717,3 +6761,176 @@ interface GeckoPublicSuffixListApi {
}
}
}
/**
* API for managing per-site tracking protection exceptions
*
* This API wraps Mozilla Android Components' TrackingProtectionUseCases
* to allow Flutter code to add/remove/check tracking protection exceptions
* on a per-site basis.
*
* Generated interface from Pigeon that represents a handler of messages from Flutter.
*/
interface GeckoTrackingProtectionApi {
/**
* Check if a tab has a tracking protection exception
*
* Uses callback pattern to match Mozilla Android Components API.
* Returns true if the site is in the exceptions list (ETP disabled).
*/
fun containsException(tabId: String, callback: (Result<Boolean>) -> Unit)
/**
* Add tracking protection exception for a tab (disable ETP for this site)
*
* This adds the current tab's URL to the exceptions list.
* ETP will be disabled for this site until the exception is removed.
*/
fun addException(tabId: String)
/**
* Remove tracking protection exception for a tab (enable ETP for this site)
*
* This removes the current tab's URL from the exceptions list.
* ETP will be re-enabled for this site.
*/
fun removeException(tabId: String)
/**
* Remove a specific exception by URL
*
* Alternative to removeException(tabId) for cases where you
* have a URL rather than a tabId.
*/
fun removeExceptionByUrl(url: String, callback: (Result<Unit>) -> Unit)
/**
* Fetch all tracking protection exceptions
*
* Returns list of all sites that have exceptions (ETP disabled).
*/
fun fetchExceptions(callback: (Result<List<TrackingProtectionException>>) -> Unit)
/**
* Remove all tracking protection exceptions
*
* This re-enables ETP for all exception sites.
*/
fun removeAllExceptions(callback: (Result<Unit>) -> Unit)
companion object {
/** The codec used by GeckoTrackingProtectionApi. */
val codec: MessageCodec<Any?> by lazy {
GeckoPigeonCodec()
}
/** Sets up an instance of `GeckoTrackingProtectionApi` to handle messages through the `binaryMessenger`. */
@JvmOverloads
fun setUp(binaryMessenger: BinaryMessenger, api: GeckoTrackingProtectionApi?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTrackingProtectionApi.containsException$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val tabIdArg = args[0] as String
api.containsException(tabIdArg) { result: Result<Boolean> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(GeckoPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(GeckoPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTrackingProtectionApi.addException$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val tabIdArg = args[0] as String
val wrapped: List<Any?> = try {
api.addException(tabIdArg)
listOf(null)
} catch (exception: Throwable) {
GeckoPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTrackingProtectionApi.removeException$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val tabIdArg = args[0] as String
val wrapped: List<Any?> = try {
api.removeException(tabIdArg)
listOf(null)
} catch (exception: Throwable) {
GeckoPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTrackingProtectionApi.removeExceptionByUrl$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val urlArg = args[0] as String
api.removeExceptionByUrl(urlArg) { result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(GeckoPigeonUtils.wrapError(error))
} else {
reply.reply(GeckoPigeonUtils.wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTrackingProtectionApi.fetchExceptions$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
api.fetchExceptions{ result: Result<List<TrackingProtectionException>> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(GeckoPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(GeckoPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoTrackingProtectionApi.removeAllExceptions$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
api.removeAllExceptions{ result: Result<Unit> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(GeckoPigeonUtils.wrapError(error))
} else {
reply.reply(GeckoPigeonUtils.wrapResult(null))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
@@ -58,6 +58,7 @@ export 'src/pigeons/gecko.g.dart'
GeckoSitePermissionsApi,
GeckoSuggestion,
GeckoSuggestionType,
GeckoTrackingProtectionApi,
GeoHitResult,
HistoryMetadataKey,
HitResult,
@@ -75,10 +76,11 @@ export 'src/pigeons/gecko.g.dart'
Resource,
ResourceSize,
SecurityInfoState,
SitePermissions,
SitePermissionStatus,
SitePermissions,
TabContent,
TabContentState,
TrackingProtectionException,
TrackingProtectionPolicy,
UnknownHitResult,
VideoHitResult,
@@ -3613,6 +3613,51 @@ class SitePermissions {
;
}
/// Tracking protection exception for a site
///
/// This represents a site that has been added to the exceptions list,
/// meaning tracking protection is disabled for this specific site.
class TrackingProtectionException {
TrackingProtectionException({
required this.url,
});
String url;
List<Object?> _toList() {
return <Object?>[
url,
];
}
Object encode() {
return _toList(); }
static TrackingProtectionException decode(Object result) {
result as List<Object?>;
return TrackingProtectionException(
url: result[0]! as String,
);
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
bool operator ==(Object other) {
if (other is! TrackingProtectionException || other.runtimeType != runtimeType) {
return false;
}
if (identical(this, other)) {
return true;
}
return _deepEquals(encode(), other.encode());
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => Object.hashAll(_toList())
;
}
class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec();
@@ -3864,6 +3909,9 @@ class _PigeonCodec extends StandardMessageCodec {
} else if (value is SitePermissions) {
buffer.putUint8(209);
writeValue(buffer, value.encode());
} else if (value is TrackingProtectionException) {
buffer.putUint8(210);
writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
@@ -4062,6 +4110,8 @@ class _PigeonCodec extends StandardMessageCodec {
return BookmarkInfo.decode(readValue(buffer)!);
case 209:
return SitePermissions.decode(readValue(buffer)!);
case 210:
return TrackingProtectionException.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
}
@@ -7866,3 +7916,186 @@ class GeckoPublicSuffixListApi {
}
}
}
/// API for managing per-site tracking protection exceptions
///
/// This API wraps Mozilla Android Components' TrackingProtectionUseCases
/// to allow Flutter code to add/remove/check tracking protection exceptions
/// on a per-site basis.
class GeckoTrackingProtectionApi {
/// Constructor for [GeckoTrackingProtectionApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
GeckoTrackingProtectionApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
: pigeonVar_binaryMessenger = binaryMessenger,
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
final BinaryMessenger? pigeonVar_binaryMessenger;
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
final String pigeonVar_messageChannelSuffix;
/// Check if a tab has a tracking protection exception
///
/// Uses callback pattern to match Mozilla Android Components API.
/// Returns true if the site is in the exceptions list (ETP disabled).
Future<bool> containsException(String tabId) async {
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTrackingProtectionApi.containsException$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[tabId]);
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as bool?)!;
}
}
/// Add tracking protection exception for a tab (disable ETP for this site)
///
/// This adds the current tab's URL to the exceptions list.
/// ETP will be disabled for this site until the exception is removed.
Future<void> addException(String tabId) async {
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTrackingProtectionApi.addException$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[tabId]);
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}
/// Remove tracking protection exception for a tab (enable ETP for this site)
///
/// This removes the current tab's URL from the exceptions list.
/// ETP will be re-enabled for this site.
Future<void> removeException(String tabId) async {
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTrackingProtectionApi.removeException$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[tabId]);
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}
/// Remove a specific exception by URL
///
/// Alternative to removeException(tabId) for cases where you
/// have a URL rather than a tabId.
Future<void> removeExceptionByUrl(String url) async {
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTrackingProtectionApi.removeExceptionByUrl$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[url]);
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}
/// Fetch all tracking protection exceptions
///
/// Returns list of all sites that have exceptions (ETP disabled).
Future<List<TrackingProtectionException>> fetchExceptions() async {
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTrackingProtectionApi.fetchExceptions$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as List<Object?>?)!.cast<TrackingProtectionException>();
}
}
/// Remove all tracking protection exceptions
///
/// This re-enables ETP for all exception sites.
Future<void> removeAllExceptions() async {
final pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoTrackingProtectionApi.removeAllExceptions$pigeonVar_messageChannelSuffix';
final pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return;
}
}
}
@@ -1825,3 +1825,59 @@ abstract class GeckoPublicSuffixListApi {
@async
String getPublicSuffixPlusOne(String host);
}
/// Tracking protection exception for a site
///
/// This represents a site that has been added to the exceptions list,
/// meaning tracking protection is disabled for this specific site.
class TrackingProtectionException {
final String url;
TrackingProtectionException({required this.url});
}
/// API for managing per-site tracking protection exceptions
///
/// This API wraps Mozilla Android Components' TrackingProtectionUseCases
/// to allow Flutter code to add/remove/check tracking protection exceptions
/// on a per-site basis.
@HostApi()
abstract class GeckoTrackingProtectionApi {
/// Check if a tab has a tracking protection exception
///
/// Uses callback pattern to match Mozilla Android Components API.
/// Returns true if the site is in the exceptions list (ETP disabled).
@async
bool containsException(String tabId);
/// Add tracking protection exception for a tab (disable ETP for this site)
///
/// This adds the current tab's URL to the exceptions list.
/// ETP will be disabled for this site until the exception is removed.
void addException(String tabId);
/// Remove tracking protection exception for a tab (enable ETP for this site)
///
/// This removes the current tab's URL from the exceptions list.
/// ETP will be re-enabled for this site.
void removeException(String tabId);
/// Remove a specific exception by URL
///
/// Alternative to removeException(tabId) for cases where you
/// have a URL rather than a tabId.
@async
void removeExceptionByUrl(String url);
/// Fetch all tracking protection exceptions
///
/// Returns list of all sites that have exceptions (ETP disabled).
@async
List<TrackingProtectionException> fetchExceptions();
/// Remove all tracking protection exceptions
///
/// This re-enables ETP for all exception sites.
@async
void removeAllExceptions();
}