diff --git a/app/lib/features/geckoview/features/browser/domain/services/browser_data.dart b/app/lib/features/geckoview/features/browser/domain/services/browser_data.dart index 69bbfd22..d85dafd8 100644 --- a/app/lib/features/geckoview/features/browser/domain/services/browser_data.dart +++ b/app/lib/features/geckoview/features/browser/domain/services/browser_data.dart @@ -63,6 +63,16 @@ class BrowserDataService extends _$BrowserDataService { return _service.clearDataForContext(contextId); } + Future clearContainerDataOnEngineStart( + List contextIds, + ) async { + if (!_onStartDeleted && contextIds.isNotEmpty) { + for (final contextId in contextIds) { + await clearDataForContext(contextId); + } + } + } + @override void build() {} } diff --git a/app/lib/features/geckoview/features/browser/domain/services/browser_data.g.dart b/app/lib/features/geckoview/features/browser/domain/services/browser_data.g.dart index 066e1488..c1922381 100644 --- a/app/lib/features/geckoview/features/browser/domain/services/browser_data.g.dart +++ b/app/lib/features/geckoview/features/browser/domain/services/browser_data.g.dart @@ -42,7 +42,7 @@ final class BrowserDataServiceProvider } String _$browserDataServiceHash() => - r'502dff526bcf7c10c218d2d9fa2cd0f41ad25622'; + r'861943be10c4dea325484b6a28ba21f3ff0d29fa'; abstract class _$BrowserDataService extends $Notifier { void build(); diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart index a99360ee..76427efc 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/browser_view.dart @@ -46,6 +46,7 @@ import 'package:weblibre/features/geckoview/features/browser/domain/services/eng import 'package:weblibre/features/geckoview/features/browser/domain/services/proxy_settings_replication.dart'; import 'package:weblibre/features/geckoview/features/history/domain/repositories/history.dart'; import 'package:weblibre/features/geckoview/features/preferences/data/repositories/preference_observer.dart'; +import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart'; import 'package:weblibre/features/share_intent/domain/entities/shared_content.dart'; import 'package:weblibre/features/user/data/models/general_settings.dart'; import 'package:weblibre/features/user/domain/repositories/cache.dart'; @@ -116,6 +117,17 @@ class _BrowserViewState extends ConsumerState ); } }); + + // Clear data for containers with clearDataOnExit enabled + final containersToClear = await ref + .read(containerRepositoryProvider.notifier) + .getContainersToClearOnExit(); + + if (containersToClear.isNotEmpty) { + await ref + .read(browserDataServiceProvider.notifier) + .clearContainerDataOnEngineStart(containersToClear); + } }); final hasTab = ref.watch( diff --git a/app/lib/features/geckoview/features/tabs/data/database/daos/container.dart b/app/lib/features/geckoview/features/tabs/data/database/daos/container.dart index 6615d8ef..48cd25b1 100644 --- a/app/lib/features/geckoview/features/tabs/data/database/daos/container.dart +++ b/app/lib/features/geckoview/features/tabs/data/database/daos/container.dart @@ -193,4 +193,8 @@ class ContainerDao extends DatabaseAccessor Selectable allAssignedSites() { return db.definitionsDrift.allAssignedSites(); } + + Selectable containersToClearOnExit() { + return db.definitionsDrift.containersToClearOnExit(); + } } diff --git a/app/lib/features/geckoview/features/tabs/data/database/definitions.drift b/app/lib/features/geckoview/features/tabs/data/database/definitions.drift index 11b79ad7..87988bba 100644 --- a/app/lib/features/geckoview/features/tabs/data/database/definitions.drift +++ b/app/lib/features/geckoview/features/tabs/data/database/definitions.drift @@ -310,10 +310,17 @@ siteAssignedContainerId: ); allAssignedSites WITH SiteAssignment: - SELECT + SELECT container.id, - COALESCE(container.metadata ->> '$.contextualIdentity', 'general') AS contextualIdentity, + COALESCE(container.metadata ->> '$.contextualIdentity', 'general') AS contextualIdentity, value AS assigned_site FROM container CROSS JOIN json_each(container.metadata, '$.assignedSites') WHERE value IS NOT NULL; + +containersToClearOnExit: + SELECT container.metadata ->> '$.contextualIdentity' AS contextual_identity + FROM container + WHERE + json_extract(container.metadata, '$.clearDataOnExit') = 1 + AND container.metadata ->> '$.contextualIdentity' IS NOT NULL; diff --git a/app/lib/features/geckoview/features/tabs/data/database/definitions.drift.dart b/app/lib/features/geckoview/features/tabs/data/database/definitions.drift.dart index d2449626..a072a86b 100644 --- a/app/lib/features/geckoview/features/tabs/data/database/definitions.drift.dart +++ b/app/lib/features/geckoview/features/tabs/data/database/definitions.drift.dart @@ -2383,6 +2383,14 @@ class DefinitionsDrift extends i7.ModularAccessor { ); } + i0.Selectable containersToClearOnExit() { + return customSelect( + 'SELECT container.metadata ->> \'\$.contextualIdentity\' AS contextual_identity FROM container WHERE json_extract(container.metadata, \'\$.clearDataOnExit\') = 1 AND container.metadata ->> \'\$.contextualIdentity\' IS NOT NULL', + variables: [], + readsFrom: {container}, + ).map((i0.QueryRow row) => row.readNullable('contextual_identity')); + } + i3.TabFts get tabFts => i7.ReadDatabaseContainer( attachedDatabase, ).resultSet('tab_fts'); diff --git a/app/lib/features/geckoview/features/tabs/data/models/container_data.dart b/app/lib/features/geckoview/features/tabs/data/models/container_data.dart index 0d2f070c..16c01568 100644 --- a/app/lib/features/geckoview/features/tabs/data/models/container_data.dart +++ b/app/lib/features/geckoview/features/tabs/data/models/container_data.dart @@ -73,6 +73,9 @@ class ContainerMetadata with FastEquatable { @JsonKey(defaultValue: false) final bool useProxy; + @JsonKey(defaultValue: false) + final bool clearDataOnExit; + final List? assignedSites; ContainerMetadata({ @@ -80,6 +83,7 @@ class ContainerMetadata with FastEquatable { required this.contextualIdentity, required this.authSettings, required this.useProxy, + required this.clearDataOnExit, required this.assignedSites, }); @@ -88,12 +92,14 @@ class ContainerMetadata with FastEquatable { String? contextualIdentity, ContainerAuthSettings? authSettings, bool? useProxy, + bool? clearDataOnExit, List? assignedSites, }) : this( iconData: iconData, contextualIdentity: contextualIdentity, authSettings: authSettings ?? ContainerAuthSettings.withDefaults(), useProxy: useProxy ?? false, + clearDataOnExit: clearDataOnExit ?? false, assignedSites: assignedSites, ); @@ -108,6 +114,7 @@ class ContainerMetadata with FastEquatable { contextualIdentity, authSettings, useProxy, + clearDataOnExit, assignedSites, ]; } diff --git a/app/lib/features/geckoview/features/tabs/data/models/container_data.g.dart b/app/lib/features/geckoview/features/tabs/data/models/container_data.g.dart index c4d8b341..001bb4b4 100644 --- a/app/lib/features/geckoview/features/tabs/data/models/container_data.g.dart +++ b/app/lib/features/geckoview/features/tabs/data/models/container_data.g.dart @@ -98,6 +98,8 @@ abstract class _$ContainerMetadataCWProxy { ContainerMetadata useProxy(bool useProxy); + ContainerMetadata clearDataOnExit(bool clearDataOnExit); + ContainerMetadata assignedSites(List? assignedSites); /// Creates a new instance with the provided field values. @@ -112,6 +114,7 @@ abstract class _$ContainerMetadataCWProxy { String? contextualIdentity, ContainerAuthSettings authSettings, bool useProxy, + bool clearDataOnExit, List? assignedSites, }); } @@ -137,6 +140,10 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy { @override ContainerMetadata useProxy(bool useProxy) => call(useProxy: useProxy); + @override + ContainerMetadata clearDataOnExit(bool clearDataOnExit) => + call(clearDataOnExit: clearDataOnExit); + @override ContainerMetadata assignedSites(List? assignedSites) => call(assignedSites: assignedSites); @@ -154,6 +161,7 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy { Object? contextualIdentity = const $CopyWithPlaceholder(), Object? authSettings = const $CopyWithPlaceholder(), Object? useProxy = const $CopyWithPlaceholder(), + Object? clearDataOnExit = const $CopyWithPlaceholder(), Object? assignedSites = const $CopyWithPlaceholder(), }) { return ContainerMetadata( @@ -174,6 +182,12 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy { ? _value.useProxy // ignore: cast_nullable_to_non_nullable : useProxy as bool, + clearDataOnExit: + clearDataOnExit == const $CopyWithPlaceholder() || + clearDataOnExit == null + ? _value.clearDataOnExit + // ignore: cast_nullable_to_non_nullable + : clearDataOnExit as bool, assignedSites: assignedSites == const $CopyWithPlaceholder() ? _value.assignedSites // ignore: cast_nullable_to_non_nullable @@ -311,6 +325,7 @@ ContainerMetadata _$ContainerMetadataFromJson(Map json) => json['authSettings'] as Map, ), useProxy: json['useProxy'] as bool? ?? false, + clearDataOnExit: json['clearDataOnExit'] as bool? ?? false, assignedSites: (json['assignedSites'] as List?) ?.map((e) => Uri.parse(e as String)) .toList(), @@ -326,6 +341,7 @@ Map _$ContainerMetadataToJson( 'contextualIdentity': instance.contextualIdentity, 'authSettings': instance.authSettings.toJson(), 'useProxy': instance.useProxy, + 'clearDataOnExit': instance.clearDataOnExit, 'assignedSites': instance.assignedSites?.map((e) => e.toString()).toList(), }; diff --git a/app/lib/features/geckoview/features/tabs/domain/repositories/container.dart b/app/lib/features/geckoview/features/tabs/domain/repositories/container.dart index 16926fd3..3c036431 100644 --- a/app/lib/features/geckoview/features/tabs/domain/repositories/container.dart +++ b/app/lib/features/geckoview/features/tabs/domain/repositories/container.dart @@ -170,6 +170,15 @@ class ContainerRepository extends _$ContainerRepository { .getSingle(); } + Future> getContainersToClearOnExit() async { + final contextIds = await ref + .read(tabDatabaseProvider) + .containerDao + .containersToClearOnExit() + .get(); + return contextIds.whereType().toList(); + } + @override void build() {} } diff --git a/app/lib/features/geckoview/features/tabs/domain/repositories/container.g.dart b/app/lib/features/geckoview/features/tabs/domain/repositories/container.g.dart index 76f2119d..c1a51d2e 100644 --- a/app/lib/features/geckoview/features/tabs/domain/repositories/container.g.dart +++ b/app/lib/features/geckoview/features/tabs/domain/repositories/container.g.dart @@ -42,7 +42,7 @@ final class ContainerRepositoryProvider } String _$containerRepositoryHash() => - r'2a377774f9f18604bb90cb1a8eb7cda5f944c601'; + r'3154277f7a6029d6f5b9fcc62ba057e117248e0c'; abstract class _$ContainerRepository extends $Notifier { void build(); diff --git a/app/lib/features/geckoview/features/tabs/presentation/screens/container_edit.dart b/app/lib/features/geckoview/features/tabs/presentation/screens/container_edit.dart index 2a62c132..3fb9aba4 100644 --- a/app/lib/features/geckoview/features/tabs/presentation/screens/container_edit.dart +++ b/app/lib/features/geckoview/features/tabs/presentation/screens/container_edit.dart @@ -77,6 +77,7 @@ class ContainerEditScreen extends HookConsumerWidget { ); final authSettings = useState(initialContainer.metadata.authSettings); final useProxy = useState(initialContainer.metadata.useProxy); + final clearDataOnExit = useState(initialContainer.metadata.clearDataOnExit); final assignedSites = useState(initialContainer.metadata.assignedSites); final textController = useTextEditingController( @@ -105,6 +106,8 @@ class ContainerEditScreen extends HookConsumerWidget { contextualIdentity: contextualIdentity.value, authSettings: authSettings.value, useProxy: useProxy.value && contextualIdentity.value != null, + clearDataOnExit: + clearDataOnExit.value && contextualIdentity.value != null, assignedSites: assignedSites.value, ), ); @@ -262,6 +265,20 @@ class ContainerEditScreen extends HookConsumerWidget { : null, }, ), + SwitchListTile.adaptive( + value: clearDataOnExit.value, + title: const Text('Clear Data on Exit'), + subtitle: const Text( + 'Clear cookies and site data when app closes', + ), + secondary: const Icon(MdiIcons.databaseRemove), + contentPadding: EdgeInsets.zero, + onChanged: (contextualIdentity.value != null) + ? (value) { + clearDataOnExit.value = value; + } + : null, + ), SwitchListTile.adaptive( value: authSettings.value.authenticationRequired, title: const Text('Require Authentication'),