Files
MrbWebLibre/apps/weblibre/lib/features/geckoview/features/tabs/domain/providers.dart
T

254 lines
8.1 KiB
Dart

/*
* Copyright (c) 2024-2026 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* 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:fast_equatable/fast_equatable.dart';
import 'package:nullability/nullability.dart';
import 'package:riverpod/riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:weblibre/features/geckoview/domain/entities/states/tab.dart';
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/database/definitions.drift.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/entities/container_filter.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/models/site_assignment.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/providers.dart';
import 'package:weblibre/features/search/util/tokenized_filter.dart';
part 'providers.g.dart';
@Riverpod(keepAlive: true)
Stream<List<ContainerDataWithCount>> watchContainersWithCount(Ref ref) {
final db = ref.watch(tabDatabaseProvider);
return db.definitionsDrift.containersWithCount().watch();
}
@Riverpod()
AsyncValue<List<ContainerDataWithCount>> matchSortedContainersWithCount(
Ref ref,
String? searchText,
) {
return ref.watch(
watchContainersWithCountProvider.select((value) {
if (searchText.isEmpty) {
return value;
}
return value.whenData(
(cb) => TokenizedFilter.sort(
items: cb,
toString: (item) => item.name,
query: searchText!,
).filtered,
);
}),
);
}
@Riverpod()
Stream<List<String>> watchContainerTabIds(
Ref ref,
ContainerFilter containerFilter,
) {
final db = ref.watch(tabDatabaseProvider);
switch (containerFilter) {
case ContainerFilterById(:final containerId):
return db.containerDao.getContainerTabIds(containerId).watch();
case ContainerFilterDisabled():
return db.tabDao.getAllTabIds().watch();
}
}
@Riverpod(keepAlive: true)
Stream<List<TabData>> watchTabsFifo(Ref ref) {
final db = ref.watch(tabDatabaseProvider);
return db.tabDao.getTabsFifo().watch();
}
@Riverpod()
Future<int> containerTabCount(Ref ref, ContainerFilter containerFilter) {
return ref.watch(
watchContainerTabIdsProvider(
containerFilter,
).selectAsync((tabs) => tabs.length),
);
}
@Riverpod()
Stream<List<TabTreesResult>> watchTabTrees(Ref ref) {
final db = ref.watch(tabDatabaseProvider);
return db.definitionsDrift.tabTrees().watch();
}
@Riverpod()
Stream<List<TabsWithRootAndDepthResult>> watchTabsWithRootAndDepth(
Ref ref,
String? containerId,
) {
final db = ref.watch(tabDatabaseProvider);
return db.definitionsDrift
.tabsWithRootAndDepth(containerId: containerId)
.watch();
}
@Riverpod()
Stream<TabData?> watchTabDbData(Ref ref, String tabId) {
final db = ref.watch(tabDatabaseProvider);
return db.tabDao.getTabDataById(tabId).watchSingleOrNull();
}
@Riverpod()
Stream<Map<String, String?>> watchTabDescendants(Ref ref, String tabId) {
final db = ref.watch(tabDatabaseProvider);
return db.definitionsDrift.unorderedTabDescendants(tabId: tabId).watch().map((
results,
) {
return Map.fromEntries(
results.map((pair) => MapEntry(pair.id, pair.parentId)),
);
});
}
@Riverpod()
Stream<List<TabData>> watchContainerTabsData(Ref ref, String? containerId) {
final db = ref.watch(tabDatabaseProvider);
return db.containerDao.getContainerTabsData(containerId).watch();
}
@Riverpod(keepAlive: true)
Stream<Set<String>> watchPinnedTabIds(Ref ref) {
final db = ref.watch(tabDatabaseProvider);
return db.tabDao.getPinnedTabIds().watch().map((ids) => ids.toSet());
}
@Riverpod()
Stream<Map<String, DateTime>> watchTabTimestamps(Ref ref) {
final db = ref.watch(tabDatabaseProvider);
return db.tabDao.getTabTimestamps().watch().map(Map.fromEntries);
}
@Riverpod(keepAlive: true)
Stream<Map<String, String>> watchTabOrderKeys(Ref ref) {
final db = ref.watch(tabDatabaseProvider);
return db.tabDao.getTabOrderKeys().watch().map(Map.fromEntries);
}
@Riverpod()
Stream<ContainerData?> watchContainerData(Ref ref, String containerId) {
final db = ref.watch(tabDatabaseProvider);
return db.containerDao.getContainerData(containerId).watchSingleOrNull();
}
@Riverpod()
Stream<String?> watchContainerTabId(Ref ref, String tabId) {
final db = ref.watch(tabDatabaseProvider);
return db.tabDao.getTabContainerId(tabId).watchSingleOrNull();
}
@Riverpod()
Stream<ContainerData?> watchTabContainerData(Ref ref, String? tabId) {
if (tabId == null) {
return const Stream.empty();
}
final db = ref.watch(tabDatabaseProvider);
return db.tabDao.getTabContainerData(tabId).watchSingleOrNull();
}
@Riverpod()
Stream<Map<String, String?>> watchTabsContainerId(
Ref ref,
EquatableValue<List<String>> tabIds,
) {
final db = ref.watch(tabDatabaseProvider);
return db.tabDao
.getTabsContainerId(tabIds.value)
.watch()
.map(Map.fromEntries);
}
@Riverpod(keepAlive: true)
Stream<List<SiteAssignment>> watchAllAssignedSites(Ref ref) {
final db = ref.watch(tabDatabaseProvider);
return db.containerDao.allAssignedSites().watch();
}
/// Strict-mode enforcement map: each Gecko cookie-store context that must be
/// enforced (a strict container's base context, plus the isolation contexts of
/// its isolated tabs) mapped to the base contextualIdentities its site
/// assignments are keyed on. Replicated to the container-proxy extension by
/// ProxySettingsReplication.
@Riverpod(keepAlive: true)
Stream<Map<String, List<String>>> watchStrictContextAssignments(Ref ref) {
final db = ref.watch(tabDatabaseProvider);
return db.containerDao.strictContextAssignments().watch().map((rows) {
final assignments = <String, Set<String>>{};
for (final row in rows) {
final contextId = row.contextId;
final assignmentContextId = row.assignmentContextId;
if (contextId == null || assignmentContextId == null) continue;
assignments
.putIfAbsent(contextId, () => <String>{})
.add(assignmentContextId);
}
return {
for (final entry in assignments.entries)
entry.key: entry.value.toList()..sort(),
};
});
}
/// Watches distinct (isolationContextId, containerId) pairs for isolated tabs
/// assigned to containers. Used by ProxySettingsReplication to manage proxy
/// aliases for isolated contexts.
///
/// Returns a map from isolation context ID to the set of container IDs it
/// appears in. An isolation context needs an explicit routing alias if any
/// associated container has a proxy connection or bypasses global routing.
@Riverpod(keepAlive: true)
Stream<Map<String, Set<String>>> watchIsolatedContextContainerMap(Ref ref) {
final db = ref.watch(tabDatabaseProvider);
return db.tabDao.isolatedContextContainerPairs().watch().map((pairs) {
final map = <String, Set<String>>{};
for (final p in pairs) {
map.putIfAbsent(p.isolationContextId!, () => {}).add(p.containerId!);
}
return map;
});
}
@Riverpod()
Stream<bool> watchIsCurrentSiteAssignedToContainer(Ref ref) {
final currentUri = ref.watch(
selectedTabStateProvider.select(
(value) => value?.url ?? TabState.$default('').url,
),
);
final db = ref.watch(tabDatabaseProvider);
return db.containerDao.allAssignedSites().watch().map((assignments) {
return assignments.any(
(a) => siteAssignmentMatches(a.assignedSite, currentUri),
);
});
}