fix cleanup issues

This commit is contained in:
Fabian Freund
2026-07-19 14:39:39 +02:00
parent 47a2a70795
commit 1b0c2b0d06
7 changed files with 237 additions and 57 deletions
@@ -17,8 +17,10 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/foundation.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/core/logger.dart';
import 'package:weblibre/features/bangs/domain/repositories/data.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/providers.dart';
import 'package:weblibre/features/user/data/models/general_settings.dart';
@@ -27,9 +29,17 @@ part 'browser_data.g.dart';
@Riverpod(keepAlive: true)
class BrowserDataService extends _$BrowserDataService {
final _service = GeckoDeleteBrowserDataService();
/// The [service] seam is test-only injection; production always constructs
/// the default. Kept `final` so the live singleton's backing service can't be
/// swapped at runtime.
@visibleForTesting
BrowserDataService({GeckoDeleteBrowserDataService? service})
: _service = service ?? GeckoDeleteBrowserDataService();
final GeckoDeleteBrowserDataService _service;
var _onStartDeleted = false;
var _onStartContainerDataCleared = false;
Future<void> deleteDataOnEngineStart(
Set<DeleteBrowsingDataType>? types,
@@ -73,14 +83,32 @@ class BrowserDataService extends _$BrowserDataService {
return _service.clearDataForContext(contextId);
}
Future<void> clearContainerDataOnEngineStart(List<String> contextIds) async {
if (!_onStartDeleted && contextIds.isNotEmpty) {
for (final contextId in contextIds) {
/// Clears Gecko session-context data for every [contextId], best-effort: a
/// single failing context is logged and skipped so it never blocks the rest.
///
/// Not gated by the one-shot startup guard — used both by the guarded
/// [clearContainerDataOnEngineStart] fallback and directly on explicit Quit.
Future<void> clearContainerData(List<String> contextIds) async {
for (final contextId in contextIds) {
try {
await clearDataForContext(contextId);
} catch (e, st) {
logger.e(
'Failed to clear data for container context $contextId',
error: e,
stackTrace: st,
);
}
}
}
Future<void> clearContainerDataOnEngineStart(List<String> contextIds) async {
if (!_onStartContainerDataCleared && contextIds.isNotEmpty) {
_onStartContainerDataCleared = true;
await clearContainerData(contextIds);
}
}
@override
void build() {}
}
@@ -42,7 +42,7 @@ final class BrowserDataServiceProvider
}
String _$browserDataServiceHash() =>
r'a317f0d56d1bfc61dc99af1e60080aaa248d6eed';
r'2df2f652342efc3e16606b92fdef6062b02f72df';
abstract class _$BrowserDataService extends $Notifier<void> {
void build();
@@ -122,31 +122,33 @@ class ContainerEditScreen extends HookConsumerWidget {
name: name.isNotEmpty ? name : null,
color: selectedColor.value,
isPinned: isPinned.value,
metadata: initialContainer.metadata.copyWith(
contextualIdentity: contextualIdentity.value,
iconData: selectedIcon.value,
proxyConnectionId: contextualIdentity.value != null
? proxyConnectionId.value
: null,
clearDataOnExit:
clearDataOnExit.value && contextualIdentity.value != null,
excludeFromIndex: excludeFromIndex.value,
// Requires a Gecko contextId: the native delegate can't hard-exclude
// a container's visits without one. sanitized() enforces the same
// invariant defensively on write.
excludeFromHistory:
excludeFromHistory.value && contextualIdentity.value != null,
bypassGlobalProxy:
contextualIdentity.value != null &&
proxyConnectionId.value == null &&
bypassGlobalProxy.value,
useCustomColor: useCustomColor.value,
assignedSites: assignedSites.value,
// Strict mode requires a Gecko contextId (the extension keys
// strictness on the tab's cookieStoreId). sanitized() enforces the
// same invariant defensively on write.
strictMode: strictMode.value && contextualIdentity.value != null,
).sanitized(),
metadata: initialContainer.metadata
.copyWith(
contextualIdentity: contextualIdentity.value,
iconData: selectedIcon.value,
proxyConnectionId: contextualIdentity.value != null
? proxyConnectionId.value
: null,
clearDataOnExit:
clearDataOnExit.value && contextualIdentity.value != null,
excludeFromIndex: excludeFromIndex.value,
// Requires a Gecko contextId: the native delegate can't hard-exclude
// a container's visits without one. sanitized() enforces the same
// invariant defensively on write.
excludeFromHistory:
excludeFromHistory.value && contextualIdentity.value != null,
bypassGlobalProxy:
contextualIdentity.value != null &&
proxyConnectionId.value == null &&
bypassGlobalProxy.value,
useCustomColor: useCustomColor.value,
assignedSites: assignedSites.value,
// Strict mode requires a Gecko contextId (the extension keys
// strictness on the tab's cookieStoreId). sanitized() enforces the
// same invariant defensively on write.
strictMode: strictMode.value && contextualIdentity.value != null,
)
.sanitized(),
);
}
@@ -543,7 +545,9 @@ class ContainerEditScreen extends HookConsumerWidget {
value: clearDataOnExit.value,
title: const Text('Clear Data on Exit'),
subtitle: const Text(
'Clear cookies and site data when app closes',
"Clear cookies and site data for this container's "
'regular tabs when the app closes. Isolated tabs '
'keep separate data.',
),
secondary: const Icon(MdiIcons.databaseRemove),
onChanged: (contextualIdentity.value != null)
@@ -572,7 +576,9 @@ class ContainerEditScreen extends HookConsumerWidget {
title: const Text('Exclude from History'),
subtitle: Text(
contextualIdentity.value != null
? "Don't record this container's browsing history"
? "Don't record new visits from this container's "
'regular tabs. Existing history is kept; '
'isolated tabs track separately.'
: 'Requires cookie isolation to be enabled',
),
secondary: const Icon(MdiIcons.incognito),