From 107b2a9aa6ed6e85ae933bedd9dfa2771b781b16 Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Sun, 9 Nov 2025 07:25:54 +0100 Subject: [PATCH] implemented container site assignements & improved routing --- app/lib/core/providers/router.dart | 2 +- app/lib/core/providers/router.g.dart | 2 +- app/lib/core/routing/routes.browser.dart | 32 ++-- app/lib/core/routing/routes.dart | 2 + app/lib/core/routing/routes.feeds.dart | 9 +- app/lib/core/routing/routes.g.dart | 94 +++++----- .../geckoview/domain/repositories/tab.dart | 68 +++++++ .../geckoview/domain/repositories/tab.g.dart | 2 +- .../services/proxy_settings_replication.dart | 11 +- .../proxy_settings_replication.g.dart | 2 +- .../browser/presentation/screens/browser.dart | 4 +- .../widgets/browser_modules/browser_view.dart | 2 +- .../presentation/widgets/view_tabs.dart | 14 +- .../tabs/data/database/daos/container.dart | 26 ++- .../features/tabs/data/database/daos/tab.dart | 11 +- .../tabs/data/database/definitions.drift | 39 ++++ .../tabs/data/database/definitions.drift.dart | 46 +++++ .../tabs/data/models/container_data.dart | 21 +++ .../tabs/data/models/container_data.g.dart | 78 +++++++- .../tabs/data/models/site_assignment.dart | 17 ++ .../features/tabs/domain/providers.dart | 6 + .../tabs/domain/repositories/container.dart | 54 +++++- .../tabs/domain/repositories/container.g.dart | 2 +- .../screens/container_draft_suggestions.dart | 3 +- .../presentation/screens/container_edit.dart | 25 +++ .../presentation/screens/container_list.dart | 12 +- .../screens/container_selection.dart | 8 +- .../presentation/screens/container_sites.dart | 157 ++++++++++++++++ .../tor/domain/repositories/tor_proxy.dart | 18 ++ .../tor/domain/repositories/tor_proxy.g.dart | 2 +- .../tor/domain/services/tor_proxy.g.dart | 2 +- .../presentation/screens/feed_list.dart | 2 +- .../EngineProvider.kt | 5 +- .../api/GeckoContainerProxyApiImpl.kt | 4 + .../components/Core.kt | 4 +- .../feature/ContainerProxyFeature.kt | 39 +++- .../pigeons/Gecko.g.kt | 95 +++++++++- .../container_proxy/package-lock.json | 177 ++++-------------- .../src/background/BackgroundMain.ts | 73 +++++++- .../container_proxy/src/background/index.ts | 9 +- .../container_proxy/src/store/Store.ts | 18 ++ .../services/gecko_container_proxy.dart | 4 + .../lib/src/domain/services/gecko_event.dart | 16 ++ .../lib/src/pigeons/gecko.g.dart | 129 ++++++++++++- .../pigeons/gecko.dart | 22 +++ 45 files changed, 1104 insertions(+), 264 deletions(-) create mode 100644 app/lib/features/geckoview/features/tabs/data/models/site_assignment.dart create mode 100644 app/lib/features/geckoview/features/tabs/presentation/screens/container_sites.dart diff --git a/app/lib/core/providers/router.dart b/app/lib/core/providers/router.dart index b6d23ed6..5631be7e 100644 --- a/app/lib/core/providers/router.dart +++ b/app/lib/core/providers/router.dart @@ -49,6 +49,6 @@ Future router(Ref ref) async { return GoRouter( debugLogDiagnostics: true, routes: $appRoutes, - initialLocation: initialLocation, + initialLocation: initialLocation ?? BrowserRoute().location, ); } diff --git a/app/lib/core/providers/router.g.dart b/app/lib/core/providers/router.g.dart index ec9a59b9..18f82e53 100644 --- a/app/lib/core/providers/router.g.dart +++ b/app/lib/core/providers/router.g.dart @@ -41,4 +41,4 @@ final class RouterProvider } } -String _$routerHash() => r'87b6e6c1097a0677f3d25570341b343b4392aa59'; +String _$routerHash() => r'ab1d1e2ea27dd41fe78d7430bfeba87051d88d36'; diff --git a/app/lib/core/routing/routes.browser.dart b/app/lib/core/routing/routes.browser.dart index 5efd4735..be2a0368 100644 --- a/app/lib/core/routing/routes.browser.dart +++ b/app/lib/core/routing/routes.browser.dart @@ -21,7 +21,7 @@ part of 'routes.dart'; @TypedGoRoute( name: BrowserRoute.name, - path: '/', + path: '/browser', routes: [ TypedGoRoute( name: 'SearchRoute', @@ -44,11 +44,11 @@ part of 'routes.dart'; routes: [ TypedGoRoute( name: 'ContainerCreateRoute', - path: 'create', + path: 'create/:containerData', ), TypedGoRoute( name: 'ContainerEditRoute', - path: 'edit', + path: 'edit/:containerData', ), ], ), @@ -135,37 +135,45 @@ class ContainerSelectionRoute extends GoRouteData } class ContainerEditRoute extends GoRouteData with $ContainerEditRoute { - final ContainerDataWithCount $extra; + final String containerData; - ContainerEditRoute(this.$extra); + ContainerEditRoute({required this.containerData}); @override Widget build(BuildContext context, GoRouterState state) { - return ContainerEditScreen.edit(initialContainer: $extra); + return ContainerEditScreen.edit( + initialContainer: ContainerData.fromJson( + jsonDecode(containerData) as Map, + ), + ); } } class ContainerCreateRoute extends GoRouteData with $ContainerCreateRoute { - final ContainerData $extra; + final String containerData; - ContainerCreateRoute(this.$extra); + ContainerCreateRoute({required this.containerData}); @override Widget build(BuildContext context, GoRouterState state) { - return ContainerEditScreen.create(initialContainer: $extra); + return ContainerEditScreen.create( + initialContainer: ContainerData.fromJson( + jsonDecode(containerData) as Map, + ), + ); } } class ContextMenuRoute extends GoRouteData with $ContextMenuRoute { - final String $extra; + final String hitResult; - const ContextMenuRoute(this.$extra); + const ContextMenuRoute({required this.hitResult}); @override Page buildPage(BuildContext context, GoRouterState state) { return DialogPage( builder: (_) => - ContextMenuDialog(hitResult: HitResultJson.fromJson($extra)), + ContextMenuDialog(hitResult: HitResultJson.fromJson(hitResult)), ); } } diff --git a/app/lib/core/routing/routes.dart b/app/lib/core/routing/routes.dart index 0d415edd..317a094d 100644 --- a/app/lib/core/routing/routes.dart +++ b/app/lib/core/routing/routes.dart @@ -21,6 +21,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:nullability/nullability.dart'; import 'package:weblibre/core/routing/widgets/dialog_page.dart'; import 'package:weblibre/features/about/presentation/screens/about.dart'; import 'package:weblibre/features/bangs/presentation/screens/categories.dart'; @@ -39,6 +40,7 @@ import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/c import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/container_edit.dart'; import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/container_list.dart'; import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/container_selection.dart'; +import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/container_sites.dart'; import 'package:weblibre/features/onboarding/presentation/onboarding.dart'; import 'package:weblibre/features/settings/presentation/screens/addon_collection.dart'; import 'package:weblibre/features/settings/presentation/screens/bang_settings.dart'; diff --git a/app/lib/core/routing/routes.feeds.dart b/app/lib/core/routing/routes.feeds.dart index cf4b4285..28fb2dc2 100644 --- a/app/lib/core/routing/routes.feeds.dart +++ b/app/lib/core/routing/routes.feeds.dart @@ -90,15 +90,18 @@ class FeedEditRoute extends GoRouteData with $FeedEditRoute { } class FeedAddRoute extends GoRouteData with $FeedAddRoute { - final Uri? $extra; + final String? uri; static const name = 'FeedAddRoute'; - const FeedAddRoute({this.$extra}); + const FeedAddRoute({required this.uri}); @override Page buildPage(BuildContext context, GoRouterState state) { - return DialogPage(builder: (_) => AddFeedDialog(initialUri: $extra)); + return DialogPage( + builder: (_) => + AddFeedDialog(initialUri: uri.mapNotNull((uri) => Uri.tryParse(uri))), + ); } } diff --git a/app/lib/core/routing/routes.g.dart b/app/lib/core/routing/routes.g.dart index b2d63e83..ade4917c 100644 --- a/app/lib/core/routing/routes.g.dart +++ b/app/lib/core/routing/routes.g.dart @@ -376,7 +376,7 @@ mixin $AddonCollectionRoute on GoRouteData { } RouteBase get $browserRoute => GoRouteData.$route( - path: '/', + path: '/browser', name: 'BrowserRoute', factory: $BrowserRoute._fromState, routes: [ @@ -416,12 +416,12 @@ RouteBase get $browserRoute => GoRouteData.$route( factory: $ContainerListRoute._fromState, routes: [ GoRouteData.$route( - path: 'create', + path: 'create/:containerData', name: 'ContainerCreateRoute', factory: $ContainerCreateRoute._fromState, ), GoRouteData.$route( - path: 'edit', + path: 'edit/:containerData', name: 'ContainerEditRoute', factory: $ContainerEditRoute._fromState, ), @@ -449,7 +449,7 @@ mixin $BrowserRoute on GoRouteData { static BrowserRoute _fromState(GoRouterState state) => BrowserRoute(); @override - String get location => GoRouteData.$location('/'); + String get location => GoRouteData.$location('/browser'); @override void go(BuildContext context) => context.go(location); @@ -483,7 +483,7 @@ mixin $SearchRoute on GoRouteData { @override String get location => GoRouteData.$location( - '/search/${Uri.encodeComponent(_$TabTypeEnumMap[_self.tabType]!)}/${Uri.encodeComponent(_self.searchText)}', + '/browser/search/${Uri.encodeComponent(_$TabTypeEnumMap[_self.tabType]!)}/${Uri.encodeComponent(_self.searchText)}', queryParams: { if (_self.launchedFromIntent != false) 'launched-from-intent': _self.launchedFromIntent.toString(), @@ -514,7 +514,7 @@ mixin $TorProxyRoute on GoRouteData { static TorProxyRoute _fromState(GoRouterState state) => TorProxyRoute(); @override - String get location => GoRouteData.$location('/tor_proxy'); + String get location => GoRouteData.$location('/browser/tor_proxy'); @override void go(BuildContext context) => context.go(location); @@ -534,7 +534,7 @@ mixin $HistoryRoute on GoRouteData { static HistoryRoute _fromState(GoRouterState state) => HistoryRoute(); @override - String get location => GoRouteData.$location('/history'); + String get location => GoRouteData.$location('/browser/history'); @override void go(BuildContext context) => context.go(location); @@ -554,7 +554,7 @@ mixin $TabViewRoute on GoRouteData { static TabViewRoute _fromState(GoRouterState state) => TabViewRoute(); @override - String get location => GoRouteData.$location('/tab_view'); + String get location => GoRouteData.$location('/browser/tab_view'); @override void go(BuildContext context) => context.go(location); @@ -572,27 +572,28 @@ mixin $TabViewRoute on GoRouteData { mixin $ContextMenuRoute on GoRouteData { static ContextMenuRoute _fromState(GoRouterState state) => - ContextMenuRoute(state.extra as String); + ContextMenuRoute(hitResult: state.uri.queryParameters['hit-result']!); ContextMenuRoute get _self => this as ContextMenuRoute; @override - String get location => GoRouteData.$location('/context_menu'); + String get location => GoRouteData.$location( + '/browser/context_menu', + queryParams: {'hit-result': _self.hitResult}, + ); @override - void go(BuildContext context) => context.go(location, extra: _self.$extra); + void go(BuildContext context) => context.go(location); @override - Future push(BuildContext context) => - context.push(location, extra: _self.$extra); + Future push(BuildContext context) => context.push(location); @override void pushReplacement(BuildContext context) => - context.pushReplacement(location, extra: _self.$extra); + context.pushReplacement(location); @override - void replace(BuildContext context) => - context.replace(location, extra: _self.$extra); + void replace(BuildContext context) => context.replace(location); } mixin $ContainerDraftRoute on GoRouteData { @@ -600,7 +601,7 @@ mixin $ContainerDraftRoute on GoRouteData { ContainerDraftRoute(); @override - String get location => GoRouteData.$location('/container_draft'); + String get location => GoRouteData.$location('/browser/container_draft'); @override void go(BuildContext context) => context.go(location); @@ -621,7 +622,7 @@ mixin $ContainerListRoute on GoRouteData { ContainerListRoute(); @override - String get location => GoRouteData.$location('/containers'); + String get location => GoRouteData.$location('/browser/containers'); @override void go(BuildContext context) => context.go(location); @@ -639,52 +640,54 @@ mixin $ContainerListRoute on GoRouteData { mixin $ContainerCreateRoute on GoRouteData { static ContainerCreateRoute _fromState(GoRouterState state) => - ContainerCreateRoute(state.extra as ContainerData); + ContainerCreateRoute( + containerData: state.pathParameters['containerData']!, + ); ContainerCreateRoute get _self => this as ContainerCreateRoute; @override - String get location => GoRouteData.$location('/containers/create'); + String get location => GoRouteData.$location( + '/browser/containers/create/${Uri.encodeComponent(_self.containerData)}', + ); @override - void go(BuildContext context) => context.go(location, extra: _self.$extra); + void go(BuildContext context) => context.go(location); @override - Future push(BuildContext context) => - context.push(location, extra: _self.$extra); + Future push(BuildContext context) => context.push(location); @override void pushReplacement(BuildContext context) => - context.pushReplacement(location, extra: _self.$extra); + context.pushReplacement(location); @override - void replace(BuildContext context) => - context.replace(location, extra: _self.$extra); + void replace(BuildContext context) => context.replace(location); } mixin $ContainerEditRoute on GoRouteData { static ContainerEditRoute _fromState(GoRouterState state) => - ContainerEditRoute(state.extra as ContainerDataWithCount); + ContainerEditRoute(containerData: state.pathParameters['containerData']!); ContainerEditRoute get _self => this as ContainerEditRoute; @override - String get location => GoRouteData.$location('/containers/edit'); + String get location => GoRouteData.$location( + '/browser/containers/edit/${Uri.encodeComponent(_self.containerData)}', + ); @override - void go(BuildContext context) => context.go(location, extra: _self.$extra); + void go(BuildContext context) => context.go(location); @override - Future push(BuildContext context) => - context.push(location, extra: _self.$extra); + Future push(BuildContext context) => context.push(location); @override void pushReplacement(BuildContext context) => - context.pushReplacement(location, extra: _self.$extra); + context.pushReplacement(location); @override - void replace(BuildContext context) => - context.replace(location, extra: _self.$extra); + void replace(BuildContext context) => context.replace(location); } mixin $ContainerSelectionRoute on GoRouteData { @@ -692,7 +695,7 @@ mixin $ContainerSelectionRoute on GoRouteData { ContainerSelectionRoute(); @override - String get location => GoRouteData.$location('/select_container'); + String get location => GoRouteData.$location('/browser/select_container'); @override void go(BuildContext context) => context.go(location); @@ -716,7 +719,7 @@ mixin $TabTreeRoute on GoRouteData { @override String get location => GoRouteData.$location( - '/tab_tree/${Uri.encodeComponent(_self.rootTabId)}', + '/browser/tab_tree/${Uri.encodeComponent(_self.rootTabId)}', ); @override @@ -743,7 +746,7 @@ mixin $OpenSharedContentRoute on GoRouteData { @override String get location => GoRouteData.$location( - '/open_content', + '/browser/open_content', queryParams: { if (_self.sharedUrl != 'about:blank') 'shared-url': _self.sharedUrl, }, @@ -974,27 +977,28 @@ mixin $FeedListRoute on GoRouteData { mixin $FeedAddRoute on GoRouteData { static FeedAddRoute _fromState(GoRouterState state) => - FeedAddRoute($extra: state.extra as Uri?); + FeedAddRoute(uri: state.uri.queryParameters['uri']); FeedAddRoute get _self => this as FeedAddRoute; @override - String get location => GoRouteData.$location('/feeds/add'); + String get location => GoRouteData.$location( + '/feeds/add', + queryParams: {if (_self.uri != null) 'uri': _self.uri}, + ); @override - void go(BuildContext context) => context.go(location, extra: _self.$extra); + void go(BuildContext context) => context.go(location); @override - Future push(BuildContext context) => - context.push(location, extra: _self.$extra); + Future push(BuildContext context) => context.push(location); @override void pushReplacement(BuildContext context) => - context.pushReplacement(location, extra: _self.$extra); + context.pushReplacement(location); @override - void replace(BuildContext context) => - context.replace(location, extra: _self.$extra); + void replace(BuildContext context) => context.replace(location); } mixin $FeedArticleListRoute on GoRouteData { diff --git a/app/lib/features/geckoview/domain/repositories/tab.dart b/app/lib/features/geckoview/domain/repositories/tab.dart index ffb96ff6..ebd07a48 100644 --- a/app/lib/features/geckoview/domain/repositories/tab.dart +++ b/app/lib/features/geckoview/domain/repositories/tab.dart @@ -388,6 +388,73 @@ class TabRepository extends _$TabRepository { ); }); + final containerSiteAssignementSub = eventSerivce.siteAssignementEvent.listen(( + event, + ) async { + if (event.tabId != null) { + // ignore: only_use_keep_alive_inside_keep_alive + final tabState = ref.read(tabStateProvider(event.tabId)); + if (tabState != null) { + final uri = Uri.parse(event.url); + final originUri = event.originUrl.mapNotNull(Uri.parse); + + final targetContainerId = await ref + .read(containerRepositoryProvider.notifier) + .siteAssignedContainerId(Uri.parse(uri.origin)); + final containerData = await targetContainerId.mapNotNull( + (id) => ref + .read(containerRepositoryProvider.notifier) + .getContainerData(id), + ); + + if (containerData != null) { + final tabIsEmpty = + tabState.url == TabState.$default(tabState.id).url && + tabState.historyState.items.isEmpty; + + if (event.blocked || tabIsEmpty) { + await addTab( + url: uri, + private: tabState.isPrivate, + container: Value(containerData), + parentId: tabState.id, + ); + + if (tabIsEmpty) { + await closeTab(tabState.id); + } + } else { + final tabContainerId = await ref + .read(tabDataRepositoryProvider.notifier) + .getContainerTabId(tabState.id); + + if (targetContainerId != tabContainerId) { + if (originUri == null) { + await ref + .read(tabDataRepositoryProvider.notifier) + .assignContainer(tabState.id, containerData); + } else if (tabState.url == originUri) { + await ref + .read(tabDataRepositoryProvider.notifier) + .assignContainer( + tabState.id, + containerData, + closeOldTab: false, + ); + } else { + logger.w( + 'Could not match origin url for assignment ${tabState.url} to request ${event.originUrl}', + ); + } + } + } + } + } else { + logger.w('Could not get tab for assignement ${tabState?.url}'); + } + } + }); + final tabContentSub = tabContentService.tabContentStream.listen(( content, ) async { @@ -468,6 +535,7 @@ class TabRepository extends _$TabRepository { tabStateDebouncer.dispose(); await tabAddedSub.cancel(); await tabContentSub.cancel(); + await containerSiteAssignementSub.cancel(); }); } } diff --git a/app/lib/features/geckoview/domain/repositories/tab.g.dart b/app/lib/features/geckoview/domain/repositories/tab.g.dart index d7ca88e3..78387af2 100644 --- a/app/lib/features/geckoview/domain/repositories/tab.g.dart +++ b/app/lib/features/geckoview/domain/repositories/tab.g.dart @@ -41,7 +41,7 @@ final class TabRepositoryProvider } } -String _$tabRepositoryHash() => r'c3dd50e6263df20ce803e979151997e0b10e0c01'; +String _$tabRepositoryHash() => r'84c3abff33a3b2ad6af91b97d6d3c4e9efea4ac2'; abstract class _$TabRepository extends $Notifier { void build(); diff --git a/app/lib/features/geckoview/features/browser/domain/services/proxy_settings_replication.dart b/app/lib/features/geckoview/features/browser/domain/services/proxy_settings_replication.dart index c6e43c89..8fc1e76d 100644 --- a/app/lib/features/geckoview/features/browser/domain/services/proxy_settings_replication.dart +++ b/app/lib/features/geckoview/features/browser/domain/services/proxy_settings_replication.dart @@ -53,7 +53,7 @@ class ProxySettingsReplication extends _$ProxySettingsReplication { ref.listen( fireImmediately: true, - containersWithCountProvider.select( + watchContainersWithCountProvider.select( (value) => EquatableValue(value.value), ), (previous, next) async { @@ -134,5 +134,14 @@ class ProxySettingsReplication extends _$ProxySettingsReplication { ); }, ); + + // ignore: only_use_keep_alive_inside_keep_alive + ref.listen(watchAllAssignedSitesProvider, (previous, next) async { + if (next.hasValue) { + await ref + .read(torProxyRepositoryProvider.notifier) + .setSiteAssignments(next.requireValue); + } + }); } } diff --git a/app/lib/features/geckoview/features/browser/domain/services/proxy_settings_replication.g.dart b/app/lib/features/geckoview/features/browser/domain/services/proxy_settings_replication.g.dart index 9f9dd1be..c2255587 100644 --- a/app/lib/features/geckoview/features/browser/domain/services/proxy_settings_replication.g.dart +++ b/app/lib/features/geckoview/features/browser/domain/services/proxy_settings_replication.g.dart @@ -42,7 +42,7 @@ final class ProxySettingsReplicationProvider } String _$proxySettingsReplicationHash() => - r'f58752de40db3b59553f86b56ab6c4558dda988d'; + r'9ded7af1d745e25c59e22edecea582e25230e5e7'; abstract class _$ProxySettingsReplication extends $Notifier { void build(); diff --git a/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart b/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart index 06576a6f..8bb8f5dd 100644 --- a/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart +++ b/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart @@ -83,7 +83,9 @@ class BrowserScreen extends HookConsumerWidget { useOnStreamChange( eventService.longPressEvent, onData: (event) async { - await ContextMenuRoute(event.hitResult.toJson()).push(context); + await ContextMenuRoute( + hitResult: event.hitResult.toJson(), + ).push(context); }, ); 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 c623dd6e..dbd3af85 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 @@ -143,7 +143,7 @@ class _BrowserViewState extends ConsumerState if (next.value.mapNotNull(Uri.tryParse) case final Uri url) { if (GoRouterState.of(context).topRoute?.name != FeedAddRoute.name) { if (ref.read(addFeedDialogBlockingProvider.notifier).canPush(url)) { - await FeedAddRoute($extra: url).push(context); + await FeedAddRoute(uri: url.toString()).push(context); } } } diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/view_tabs.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/view_tabs.dart index 8151d95d..37ac2e96 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/view_tabs.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/view_tabs.dart @@ -18,6 +18,7 @@ * along with this program. If not, see . */ import 'dart:async'; +import 'dart:convert'; import 'dart:math' as math; import 'package:fading_scroll/fading_scroll.dart'; @@ -353,7 +354,9 @@ class _TabViewHeader extends HookConsumerWidget { .clearContainer(); }, onLongPress: (container) async { - await ContainerEditRoute(container).push(context); + await ContainerEditRoute( + containerData: jsonEncode(container.toJson()), + ).push(context); }, ); }, @@ -540,10 +543,11 @@ class ViewTabsWidget extends HookConsumerWidget { ); } else { if (newIndex < oldIndex) { - key = await containerRepository.getOrderKeyAfterTab( - filteredTabEntities.value[newIndex - 1].tabId, - containerId, - ); + key = (await containerRepository + .getOrderKeyAfterTab( + filteredTabEntities.value[newIndex - 1].tabId, + containerId, + ))!; } else { key = await containerRepository .getOrderKeyBeforeTab( 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 23b1fc2e..53426496 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 @@ -17,6 +17,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import 'dart:convert'; import 'dart:ui'; import 'package:drift/drift.dart'; @@ -24,6 +25,7 @@ import 'package:weblibre/features/geckoview/features/tabs/data/database/daos/con import 'package:weblibre/features/geckoview/features/tabs/data/database/database.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/database/definitions.drift.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'; @DriftAccessor() class ContainerDao extends DatabaseAccessor @@ -115,7 +117,7 @@ class ContainerDao extends DatabaseAccessor ); } - SingleSelectable generateOrderKeyAfterTabId( + SingleOrNullSelectable generateOrderKeyAfterTabId( String? containerId, String tabId, ) { @@ -134,4 +136,26 @@ class ContainerDao extends DatabaseAccessor tabId: tabId, ); } + + SingleSelectable isSiteAssignedToContainer(Uri uri) { + return db.definitionsDrift.isSiteAssignedToContainer(uri: uri.origin); + } + + SingleSelectable areSitesAvailable( + Iterable origins, + String ignoredContainerId, + ) { + return db.definitionsDrift.areSitesAvailable( + ignoreContainerId: ignoredContainerId, + uriList: jsonEncode(origins.map((value) => value.origin).toList()), + ); + } + + SingleSelectable siteAssignedContainerId(Uri uri) { + return db.definitionsDrift.siteAssignedContainerId(uri: uri.origin); + } + + Selectable allAssignedSites() { + return db.definitionsDrift.allAssignedSites(); + } } diff --git a/app/lib/features/geckoview/features/tabs/data/database/daos/tab.dart b/app/lib/features/geckoview/features/tabs/data/database/daos/tab.dart index dd4145f5..4b9565bc 100644 --- a/app/lib/features/geckoview/features/tabs/data/database/daos/tab.dart +++ b/app/lib/features/geckoview/features/tabs/data/database/daos/tab.dart @@ -62,11 +62,14 @@ class TabDao extends DatabaseAccessor with $TabDaoMixin { Future _generateOrderKey({ required Value parentId, required Value containerId, - }) { + }) async { if (parentId.value.isNotEmpty) { - return db.containerDao - .generateOrderKeyAfterTabId(containerId.value, parentId.value!) - .getSingle(); + return await db.containerDao + .generateOrderKeyAfterTabId(containerId.value, parentId.value!) + .getSingleOrNull() ?? + await db.containerDao + .generateLeadingOrderKey(containerId.value) + .getSingle(); } else { return db.containerDao .generateLeadingOrderKey(containerId.value) 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 369f60f3..7e533922 100644 --- a/app/lib/features/geckoview/features/tabs/data/database/definitions.drift +++ b/app/lib/features/geckoview/features/tabs/data/database/definitions.drift @@ -1,6 +1,7 @@ import 'package:weblibre/data/database/converters/color.dart'; import 'package:weblibre/data/database/converters/uri.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/models/tab_query_result.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/database/converters/container_metadata_converter.dart'; @@ -273,3 +274,41 @@ nextTabByOrderKey(:tab_id AS TEXT, :container_id AS TEXT OR NULL, :skip_containe SELECT next_tab_id FROM ranked_tabs WHERE id = :tab_id; + +isSiteAssignedToContainer: + SELECT EXISTS ( + SELECT 1 + FROM container + CROSS JOIN json_each(container.metadata, '$.assignedSites') + WHERE json_each.value = :uri + ) AS existing; + +areSitesAvailable: + SELECT NOT EXISTS ( + SELECT 1 + FROM container + CROSS JOIN json_each(container.metadata, '$.assignedSites') + WHERE json_each.value IN ( + SELECT value + FROM json_each(:uriList) + ) AND + container.id IS NOT :ignore_container_id + ) AS existing; + +siteAssignedContainerId: + SELECT id + FROM container + WHERE EXISTS ( + SELECT 1 + FROM json_each(container.metadata, '$.assignedSites') + WHERE value = :uri + ); + +allAssignedSites WITH SiteAssignment: + SELECT + container.id, + 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; 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 969569a7..405f4775 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 @@ -13,6 +13,8 @@ import 'package:weblibre/data/database/converters/uri.dart' as i6; import 'package:drift/internal/modular.dart' as i7; import 'package:weblibre/features/geckoview/features/tabs/data/models/tab_query_result.dart' as i8; +import 'package:weblibre/features/geckoview/features/tabs/data/models/site_assignment.dart' + as i9; typedef $ContainerCreateCompanionBuilder = i3.ContainerCompanion Function({ @@ -2277,6 +2279,50 @@ class DefinitionsDrift extends i7.ModularAccessor { ).map((i0.QueryRow row) => row.readNullable('next_tab_id')); } + i0.Selectable isSiteAssignedToContainer({String? uri}) { + return customSelect( + 'SELECT EXISTS (SELECT 1 AS _c0 FROM container CROSS JOIN json_each(container.metadata, \'\$.assignedSites\')WHERE json_each.value = ?1) AS existing', + variables: [i0.Variable(uri)], + readsFrom: {container}, + ).map((i0.QueryRow row) => row.read('existing')); + } + + i0.Selectable areSitesAvailable({ + required String uriList, + required String ignoreContainerId, + }) { + return customSelect( + 'SELECT NOT EXISTS (SELECT 1 AS _c0 FROM container CROSS JOIN json_each(container.metadata, \'\$.assignedSites\')WHERE json_each.value IN (SELECT value FROM json_each(?1)) AND container.id IS NOT ?2) AS existing', + variables: [ + i0.Variable(uriList), + i0.Variable(ignoreContainerId), + ], + readsFrom: {container}, + ).map((i0.QueryRow row) => row.read('existing')); + } + + i0.Selectable siteAssignedContainerId({String? uri}) { + return customSelect( + 'SELECT id FROM container WHERE EXISTS (SELECT 1 AS _c0 FROM json_each(container.metadata, \'\$.assignedSites\')WHERE value = ?1)', + variables: [i0.Variable(uri)], + readsFrom: {container}, + ).map((i0.QueryRow row) => row.read('id')); + } + + i0.Selectable allAssignedSites() { + return customSelect( + 'SELECT container.id, 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', + variables: [], + readsFrom: {container}, + ).map( + (i0.QueryRow row) => i9.SiteAssignment( + id: row.read('id'), + contextualIdentity: row.read('contextualIdentity'), + assignedSite: row.readNullable('assigned_site'), + ), + ); + } + 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 4e0dcf6b..0d2f070c 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 @@ -21,6 +21,7 @@ import 'package:copy_with_extension/copy_with_extension.dart'; import 'package:fast_equatable/fast_equatable.dart'; import 'package:flutter/widgets.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:weblibre/data/database/converters/color.dart'; import 'package:weblibre/data/database/converters/icon_data.dart'; part 'container_data.g.dart'; @@ -72,11 +73,14 @@ class ContainerMetadata with FastEquatable { @JsonKey(defaultValue: false) final bool useProxy; + final List? assignedSites; + ContainerMetadata({ required this.iconData, required this.contextualIdentity, required this.authSettings, required this.useProxy, + required this.assignedSites, }); ContainerMetadata.withDefaults({ @@ -84,11 +88,13 @@ class ContainerMetadata with FastEquatable { String? contextualIdentity, ContainerAuthSettings? authSettings, bool? useProxy, + List? assignedSites, }) : this( iconData: iconData, contextualIdentity: contextualIdentity, authSettings: authSettings ?? ContainerAuthSettings.withDefaults(), useProxy: useProxy ?? false, + assignedSites: assignedSites, ); factory ContainerMetadata.fromJson(Map json) => @@ -102,13 +108,16 @@ class ContainerMetadata with FastEquatable { contextualIdentity, authSettings, useProxy, + assignedSites, ]; } +@JsonSerializable() @CopyWith() class ContainerData with FastEquatable { final String id; final String? name; + @ColorJsonConverter() final Color color; final ContainerMetadata metadata; @@ -119,10 +128,16 @@ class ContainerData with FastEquatable { ContainerMetadata? metadata, }) : metadata = metadata ?? ContainerMetadata.withDefaults(); + factory ContainerData.fromJson(Map json) => + _$ContainerDataFromJson(json); + + Map toJson() => _$ContainerDataToJson(this); + @override List get hashParameters => [id, name, color, metadata]; } +@JsonSerializable() class ContainerDataWithCount extends ContainerData { final int? tabCount; @@ -134,6 +149,12 @@ class ContainerDataWithCount extends ContainerData { required this.tabCount, }); + factory ContainerDataWithCount.fromJson(Map json) => + _$ContainerDataWithCountFromJson(json); + + @override + Map toJson() => _$ContainerDataWithCountToJson(this); + @override List get hashParameters => [...super.hashParameters, tabCount]; } 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 796334f4..c4d8b341 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 assignedSites(List? assignedSites); + /// 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 `ContainerMetadata(...).copyWith.fieldName(value)`. /// @@ -110,6 +112,7 @@ abstract class _$ContainerMetadataCWProxy { String? contextualIdentity, ContainerAuthSettings authSettings, bool useProxy, + List? assignedSites, }); } @@ -134,6 +137,10 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy { @override ContainerMetadata useProxy(bool useProxy) => call(useProxy: useProxy); + @override + ContainerMetadata assignedSites(List? assignedSites) => + call(assignedSites: assignedSites); + @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 `ContainerMetadata(...).copyWith.fieldName(value)`. @@ -147,6 +154,7 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy { Object? contextualIdentity = const $CopyWithPlaceholder(), Object? authSettings = const $CopyWithPlaceholder(), Object? useProxy = const $CopyWithPlaceholder(), + Object? assignedSites = const $CopyWithPlaceholder(), }) { return ContainerMetadata( iconData: iconData == const $CopyWithPlaceholder() @@ -166,6 +174,10 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy { ? _value.useProxy // ignore: cast_nullable_to_non_nullable : useProxy as bool, + assignedSites: assignedSites == const $CopyWithPlaceholder() + ? _value.assignedSites + // ignore: cast_nullable_to_non_nullable + : assignedSites as List?, ); } } @@ -299,18 +311,23 @@ ContainerMetadata _$ContainerMetadataFromJson(Map json) => json['authSettings'] as Map, ), useProxy: json['useProxy'] as bool? ?? false, + assignedSites: (json['assignedSites'] as List?) + ?.map((e) => Uri.parse(e as String)) + .toList(), ); -Map _$ContainerMetadataToJson(ContainerMetadata instance) => - { - 'iconData': _$JsonConverterToJson, IconData>( - instance.iconData, - const IconDataJsonConverter().toJson, - ), - 'contextualIdentity': instance.contextualIdentity, - 'authSettings': instance.authSettings.toJson(), - 'useProxy': instance.useProxy, - }; +Map _$ContainerMetadataToJson( + ContainerMetadata instance, +) => { + 'iconData': _$JsonConverterToJson, IconData>( + instance.iconData, + const IconDataJsonConverter().toJson, + ), + 'contextualIdentity': instance.contextualIdentity, + 'authSettings': instance.authSettings.toJson(), + 'useProxy': instance.useProxy, + 'assignedSites': instance.assignedSites?.map((e) => e.toString()).toList(), +}; Value? _$JsonConverterFromJson( Object? json, @@ -321,3 +338,44 @@ Json? _$JsonConverterToJson( Value? value, Json? Function(Value value) toJson, ) => value == null ? null : toJson(value); + +ContainerData _$ContainerDataFromJson( + Map json, +) => ContainerData( + id: json['id'] as String, + name: json['name'] as String?, + color: const ColorJsonConverter().fromJson((json['color'] as num).toInt()), + metadata: json['metadata'] == null + ? null + : ContainerMetadata.fromJson(json['metadata'] as Map), +); + +Map _$ContainerDataToJson(ContainerData instance) => + { + 'id': instance.id, + 'name': instance.name, + 'color': const ColorJsonConverter().toJson(instance.color), + 'metadata': instance.metadata.toJson(), + }; + +ContainerDataWithCount _$ContainerDataWithCountFromJson( + Map json, +) => ContainerDataWithCount( + id: json['id'] as String, + name: json['name'] as String?, + color: const ColorJsonConverter().fromJson((json['color'] as num).toInt()), + metadata: json['metadata'] == null + ? null + : ContainerMetadata.fromJson(json['metadata'] as Map), + tabCount: (json['tabCount'] as num?)?.toInt(), +); + +Map _$ContainerDataWithCountToJson( + ContainerDataWithCount instance, +) => { + 'id': instance.id, + 'name': instance.name, + 'color': const ColorJsonConverter().toJson(instance.color), + 'metadata': instance.metadata.toJson(), + 'tabCount': instance.tabCount, +}; diff --git a/app/lib/features/geckoview/features/tabs/data/models/site_assignment.dart b/app/lib/features/geckoview/features/tabs/data/models/site_assignment.dart new file mode 100644 index 00000000..a69472b4 --- /dev/null +++ b/app/lib/features/geckoview/features/tabs/data/models/site_assignment.dart @@ -0,0 +1,17 @@ +import 'package:fast_equatable/fast_equatable.dart'; + +class SiteAssignment with FastEquatable { + final String id; + final String? contextualIdentity; + final Uri assignedSite; + + SiteAssignment({ + required this.id, + required this.contextualIdentity, + //Drift type casting issue should be non null + required String? assignedSite, + }) : assignedSite = Uri.parse(assignedSite!); + + @override + List get hashParameters => [id, contextualIdentity, assignedSite]; +} diff --git a/app/lib/features/geckoview/features/tabs/domain/providers.dart b/app/lib/features/geckoview/features/tabs/domain/providers.dart index 0d33f068..02da66ba 100644 --- a/app/lib/features/geckoview/features/tabs/domain/providers.dart +++ b/app/lib/features/geckoview/features/tabs/domain/providers.dart @@ -23,6 +23,7 @@ import 'package:riverpod_annotation/riverpod_annotation.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'; @@ -118,3 +119,8 @@ Stream watchContainerTabId(Ref ref, String tabId) { .getTabContainerId(tabId) .watchSingleOrNull(); } + +@Riverpod() +Stream> watchAllAssignedSites(Ref ref) { + return ref.read(tabDatabaseProvider).containerDao.allAssignedSites().watch(); +} 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 1633b255..b497352e 100644 --- a/app/lib/features/geckoview/features/tabs/domain/repositories/container.dart +++ b/app/lib/features/geckoview/features/tabs/domain/repositories/container.dart @@ -20,6 +20,7 @@ import 'dart:ui'; import 'package:collection/collection.dart'; +import 'package:nullability/nullability.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/providers.dart'; @@ -30,7 +31,16 @@ part 'container.g.dart'; @Riverpod(keepAlive: true) class ContainerRepository extends _$ContainerRepository { - Future addContainer(ContainerData container) { + Future addContainer(ContainerData container) async { + if (container.metadata.assignedSites.isNotEmpty) { + if (!await areSitesAvailable( + container.metadata.assignedSites!, + container.id, + )) { + throw Exception('Sites already assigned to another container'); + } + } + return ref.read(tabDatabaseProvider).containerDao.addContainer(container); } @@ -42,7 +52,16 @@ class ContainerRepository extends _$ContainerRepository { .get(); } - Future replaceContainer(ContainerData container) { + Future replaceContainer(ContainerData container) async { + if (container.metadata.assignedSites.isNotEmpty) { + if (!await areSitesAvailable( + container.metadata.assignedSites!, + container.id, + )) { + throw Exception('Sites already assigned to another container'); + } + } + return ref .read(tabDatabaseProvider) .containerDao @@ -98,12 +117,12 @@ class ContainerRepository extends _$ContainerRepository { .getSingle(); } - Future getOrderKeyAfterTab(String tabId, String? containerId) { + Future getOrderKeyAfterTab(String tabId, String? containerId) { return ref .read(tabDatabaseProvider) .containerDao .generateOrderKeyAfterTabId(containerId, tabId) - .getSingle(); + .getSingleOrNull(); } Future getOrderKeyBeforeTab(String tabId, String? containerId) { @@ -126,6 +145,33 @@ class ContainerRepository extends _$ContainerRepository { return randomColor; } + Future isSiteAssignedToContainer(Uri uri) { + return ref + .read(tabDatabaseProvider) + .containerDao + .isSiteAssignedToContainer(uri) + .getSingle(); + } + + Future areSitesAvailable( + Iterable origins, + String ignoreContainerId, + ) { + return ref + .read(tabDatabaseProvider) + .containerDao + .areSitesAvailable(origins, ignoreContainerId) + .getSingle(); + } + + Future siteAssignedContainerId(Uri uri) { + return ref + .read(tabDatabaseProvider) + .containerDao + .siteAssignedContainerId(uri) + .getSingle(); + } + @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 541af910..4650d9f1 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'6d84d672e24ac3d2cb376e93cbbe61fc223d89b3'; + r'8c9252a49a096c29279ad3066f6e5936e15524b2'; abstract class _$ContainerRepository extends $Notifier { void build(); diff --git a/app/lib/features/geckoview/features/tabs/presentation/screens/container_draft_suggestions.dart b/app/lib/features/geckoview/features/tabs/presentation/screens/container_draft_suggestions.dart index 3525fc8b..e5f7dacb 100644 --- a/app/lib/features/geckoview/features/tabs/presentation/screens/container_draft_suggestions.dart +++ b/app/lib/features/geckoview/features/tabs/presentation/screens/container_draft_suggestions.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:math' as math; import 'package:fast_equatable/fast_equatable.dart'; @@ -198,7 +199,7 @@ class ContainerDraftSuggestionsScreen extends HookConsumerWidget { if (context.mounted) { final result = await ContainerCreateRoute( - initialContainer, + containerData: jsonEncode(initialContainer.toJson()), ).push(context); if (result != null) { 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 7dc4d928..c0af4e21 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 @@ -17,15 +17,18 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ + import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:nullability/nullability.dart'; import 'package:weblibre/core/uuid.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart'; import 'package:weblibre/features/geckoview/features/tabs/presentation/controllers/container_topic.dart'; +import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/container_sites.dart'; import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/color_picker_dialog.dart'; import 'package:weblibre/features/user/domain/services/local_authentication.dart'; import 'package:weblibre/presentation/icons/tor_icons.dart'; @@ -74,6 +77,7 @@ class ContainerEditScreen extends HookConsumerWidget { ); final authSettings = useState(initialContainer.metadata.authSettings); final useProxy = useState(initialContainer.metadata.useProxy); + final assignedSites = useState(initialContainer.metadata.assignedSites); final textController = useTextEditingController( text: initialContainer.name, @@ -101,6 +105,7 @@ class ContainerEditScreen extends HookConsumerWidget { contextualIdentity: contextualIdentity.value, authSettings: authSettings.value, useProxy: useProxy.value && contextualIdentity.value != null, + assignedSites: assignedSites.value, ), ); @@ -299,6 +304,26 @@ class ContainerEditScreen extends HookConsumerWidget { }, ), ), + ListTile( + leading: const Icon(Icons.web), + title: const Text('Assigned Sites'), + trailing: const Icon(Icons.chevron_right), + contentPadding: EdgeInsets.zero, + onTap: () async { + final result = await showDialog>( + context: context, + builder: (context) => ContainerSitesScreen( + initialSites: assignedSites.value?.toSet() ?? {}, + ), + ); + + if (result.isEmpty) { + assignedSites.value = null; + } else { + assignedSites.value = result!.toList(); + } + }, + ), ], ), ), diff --git a/app/lib/features/geckoview/features/tabs/presentation/screens/container_list.dart b/app/lib/features/geckoview/features/tabs/presentation/screens/container_list.dart index bf42a636..6937cb3e 100644 --- a/app/lib/features/geckoview/features/tabs/presentation/screens/container_list.dart +++ b/app/lib/features/geckoview/features/tabs/presentation/screens/container_list.dart @@ -17,6 +17,8 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import 'dart:convert'; + import 'package:fading_scroll/fading_scroll.dart'; import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; @@ -40,7 +42,7 @@ class ContainerListScreen extends HookConsumerWidget { appBar: AppBar(title: const Text('Containers')), body: HookConsumer( builder: (context, ref, child) { - final containersAsync = ref.watch(containersWithCountProvider); + final containersAsync = ref.watch(watchContainersWithCountProvider); final selectedContainer = ref.watch(selectedContainerProvider); return Skeletonizer( @@ -128,7 +130,9 @@ class ContainerListScreen extends HookConsumerWidget { container, isSelected: container.id == selectedContainer, onTap: () async { - await ContainerEditRoute(container).push(context); + await ContainerEditRoute( + containerData: jsonEncode(container.toJson()), + ).push(context); }, ), ); @@ -157,7 +161,9 @@ class ContainerListScreen extends HookConsumerWidget { if (context.mounted) { await ContainerCreateRoute( - ContainerData(id: uuid.v7(), color: initialColor), + containerData: jsonEncode( + ContainerData(id: uuid.v7(), color: initialColor).toJson(), + ), ).push(context); } }, diff --git a/app/lib/features/geckoview/features/tabs/presentation/screens/container_selection.dart b/app/lib/features/geckoview/features/tabs/presentation/screens/container_selection.dart index e8639248..b96f6309 100644 --- a/app/lib/features/geckoview/features/tabs/presentation/screens/container_selection.dart +++ b/app/lib/features/geckoview/features/tabs/presentation/screens/container_selection.dart @@ -17,6 +17,8 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import 'dart:convert'; + import 'package:fading_scroll/fading_scroll.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -38,7 +40,7 @@ class ContainerSelectionScreen extends HookConsumerWidget { appBar: AppBar(title: const Text('Select Container')), body: HookConsumer( builder: (context, ref, child) { - final containersAsync = ref.watch(containersWithCountProvider); + final containersAsync = ref.watch(watchContainersWithCountProvider); return Skeletonizer( enabled: containersAsync.isLoading, @@ -84,7 +86,9 @@ class ContainerSelectionScreen extends HookConsumerWidget { if (context.mounted) { await ContainerCreateRoute( - ContainerData(id: uuid.v7(), color: initialColor), + containerData: jsonEncode( + ContainerData(id: uuid.v7(), color: initialColor).toJson(), + ), ).push(context); } }, diff --git a/app/lib/features/geckoview/features/tabs/presentation/screens/container_sites.dart b/app/lib/features/geckoview/features/tabs/presentation/screens/container_sites.dart new file mode 100644 index 00000000..65f43cd4 --- /dev/null +++ b/app/lib/features/geckoview/features/tabs/presentation/screens/container_sites.dart @@ -0,0 +1,157 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:nullability/nullability.dart'; +import 'package:weblibre/features/geckoview/features/tabs/domain/repositories/container.dart'; +import 'package:weblibre/presentation/widgets/url_icon.dart'; +import 'package:weblibre/utils/form_validators.dart'; +import 'package:weblibre/utils/ui_helper.dart' as ui_helper; +import 'package:weblibre/utils/uri_parser.dart' as uri_parser; + +class ContainerSitesScreen extends HookConsumerWidget { + final Set initialSites; + + const ContainerSitesScreen({super.key, required this.initialSites}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final formKey = useMemoized(() => GlobalKey()); + final textController = useTextEditingController(); + + final sites = useState(initialSites); + + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, result) { + if (!didPop) { + context.pop(result ?? sites.value); + } + }, + child: Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + leading: BackButton( + onPressed: () { + context.pop(sites.value); + }, + ), + title: const Text('Site Assignments'), + ), + body: SafeArea( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Form( + key: formKey, + child: TextFormField( + decoration: InputDecoration( + label: const Text('Add Site'), + hintText: 'example.com', + floatingLabelBehavior: FloatingLabelBehavior.always, + suffix: TextButton( + onPressed: () { + if (formKey.currentState?.validate() == true) { + formKey.currentState?.save(); + } + }, + child: const Text('Add'), + ), + ), + controller: textController, + keyboardType: TextInputType.url, + validator: (value) { + final uriValid = validateUrl( + value, + onlyHttpProtocol: true, + eagerParsing: true, + ); + + if (uriValid != null) { + return uriValid; + } + + final origin = Uri.parse( + uri_parser + .tryParseUrl(value, eagerParsing: true)! + .origin, + ); + + if (sites.value.contains(origin)) { + return 'This site has been already assigned'; + } + + return null; + }, + onSaved: (newValue) async { + final origin = Uri.parse( + uri_parser + .tryParseUrl(newValue, eagerParsing: true)! + .origin, + ); + + final isAssigned = await ref + .read(containerRepositoryProvider.notifier) + .isSiteAssignedToContainer(origin); + + if (!isAssigned) { + sites.value = {...sites.value, origin}; + } else { + final assignedContainerId = await ref + .read(containerRepositoryProvider.notifier) + .siteAssignedContainerId(origin); + final assignedContainer = await assignedContainerId + .mapNotNull( + (id) => ref + .read(containerRepositoryProvider.notifier) + .getContainerData(id), + ); + + if (context.mounted) { + ui_helper.showErrorMessage( + context, + (assignedContainer?.name.isNotEmpty ?? false) + ? '$origin has already been assigned to container "${assignedContainer?.name}"' + : '$origin has already been assigned to another container', + ); + } + } + + textController.clear(); + }, + onFieldSubmitted: (_) { + if (formKey.currentState?.validate() == true) { + formKey.currentState?.save(); + } + }, + ), + ), + ), + Expanded( + child: ListView.builder( + itemCount: sites.value.length, + itemBuilder: (context, index) { + final site = sites.value.elementAt(index); + + return ListTile( + leading: UrlIcon([site], iconSize: 20), + title: Text(site.host), + subtitle: Text(site.toString()), + trailing: IconButton( + onPressed: () { + sites.value = {...sites.value}..remove(site); + }, + icon: const Icon(Icons.delete), + ), + ); + }, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/app/lib/features/tor/domain/repositories/tor_proxy.dart b/app/lib/features/tor/domain/repositories/tor_proxy.dart index 083fb66a..0983b4fa 100644 --- a/app/lib/features/tor/domain/repositories/tor_proxy.dart +++ b/app/lib/features/tor/domain/repositories/tor_proxy.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:synchronized/synchronized.dart'; +import 'package:weblibre/features/geckoview/features/tabs/data/models/site_assignment.dart'; part 'tor_proxy.g.dart'; @@ -35,6 +36,23 @@ class TorProxyRepository extends _$TorProxyRepository { }); } + Future setSiteAssignments(List assignements) { + return _serviceLock.synchronized(() async { + await _waitHealthcheck().timeout(const Duration(seconds: 10)); + + return _service.setSiteAssignments( + Map.fromEntries( + assignements.map( + (e) => MapEntry( + e.assignedSite.origin, + e.contextualIdentity ?? 'general', + ), + ), + ), + ); + }); + } + Future _waitHealthcheck({ Duration timeout = const Duration(seconds: 15), }) async { diff --git a/app/lib/features/tor/domain/repositories/tor_proxy.g.dart b/app/lib/features/tor/domain/repositories/tor_proxy.g.dart index ee223caf..d1a3b06f 100644 --- a/app/lib/features/tor/domain/repositories/tor_proxy.g.dart +++ b/app/lib/features/tor/domain/repositories/tor_proxy.g.dart @@ -42,7 +42,7 @@ final class TorProxyRepositoryProvider } String _$torProxyRepositoryHash() => - r'3bdb83bdb1c5d1bb5d8953878cd587d042d30342'; + r'83c2976750f3f7907274b1ae4f926cd1de89be83'; abstract class _$TorProxyRepository extends $Notifier { void build(); diff --git a/app/lib/features/tor/domain/services/tor_proxy.g.dart b/app/lib/features/tor/domain/services/tor_proxy.g.dart index 7a88bdaf..b6950339 100644 --- a/app/lib/features/tor/domain/services/tor_proxy.g.dart +++ b/app/lib/features/tor/domain/services/tor_proxy.g.dart @@ -33,7 +33,7 @@ final class TorProxyServiceProvider TorProxyService create() => TorProxyService(); } -String _$torProxyServiceHash() => r'21592493593c7c2a6cf1ac8311526a7fd6114b98'; +String _$torProxyServiceHash() => r'c63adf96d7b0a865917b8d902071a0b3ae4dfcc6'; abstract class _$TorProxyService extends $AsyncNotifier { FutureOr build(); diff --git a/app/lib/features/web_feed/presentation/screens/feed_list.dart b/app/lib/features/web_feed/presentation/screens/feed_list.dart index b2d88aa9..df476c70 100644 --- a/app/lib/features/web_feed/presentation/screens/feed_list.dart +++ b/app/lib/features/web_feed/presentation/screens/feed_list.dart @@ -67,7 +67,7 @@ class FeedListScreen extends HookConsumerWidget { label: const Text('Feed'), icon: const Icon(Icons.add), onPressed: () async { - await const FeedAddRoute().push(context); + await const FeedAddRoute(uri: null).push(context); }, ), ); diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/EngineProvider.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/EngineProvider.kt index 242c3d0b..552ff679 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/EngineProvider.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/EngineProvider.kt @@ -12,6 +12,7 @@ import eu.weblibre.flutter_mozilla_components.feature.BrowserExtensionFeature import eu.weblibre.flutter_mozilla_components.feature.MLEngineFeature import eu.weblibre.flutter_mozilla_components.pigeons.BounceTrackingProtectionMode import eu.weblibre.flutter_mozilla_components.pigeons.BrowserExtensionEvents +import eu.weblibre.flutter_mozilla_components.pigeons.GeckoStateEvents import eu.weblibre.flutter_mozilla_components.pigeons.QueryParameterStripping import mozilla.components.browser.engine.gecko.GeckoEngine import mozilla.components.browser.engine.gecko.fetch.GeckoViewFetchClient @@ -81,7 +82,7 @@ object EngineProvider { return runtime!! } - fun createEngine(context: Context, defaultSettings: DefaultSettings, extensionEvents: BrowserExtensionEvents): Engine { + fun createEngine(context: Context, defaultSettings: DefaultSettings, extensionEvents: BrowserExtensionEvents, stateEvents: GeckoStateEvents): Engine { Logger.debug("Creating Engine") val runtime = getOrCreateRuntime(context) @@ -89,7 +90,7 @@ object EngineProvider { WebCompatFeature.install(it) //CookieManagerFeature.install(it) PrefManagerFeature.install(it) - ContainerProxyFeature.install(it) + ContainerProxyFeature.install(it, stateEvents) BrowserExtensionFeature.install(it, extensionEvents) MLEngineFeature.install(it) } diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoContainerProxyApiImpl.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoContainerProxyApiImpl.kt index 477a343c..eae80190 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoContainerProxyApiImpl.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoContainerProxyApiImpl.kt @@ -25,6 +25,10 @@ class GeckoContainerProxyApiImpl : GeckoContainerProxyApi { ContainerProxyFeature.scheduleRequest("removeContainerProxy", contextId) } + override fun setSiteAssignments(assignments: Map) { + ContainerProxyFeature.scheduleRequest("setSiteAssignments", JSONObject(assignments)) + } + override fun healthcheck(callback: (Result) -> Unit) { ContainerProxyFeature.scheduleRequestWithResponse("healthcheck", Unit, object : ResultConsumer { diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/components/Core.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/components/Core.kt index eef01939..446432cc 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/components/Core.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/components/Core.kt @@ -114,7 +114,7 @@ class Core( } val engine: Engine by lazy { - EngineProvider.createEngine(context, engineSettings, extensionEvents) + EngineProvider.createEngine(context, engineSettings, extensionEvents, flutterEvents) } /** @@ -158,7 +158,7 @@ class Core( collectionUser = components.addonCollection.collectionUser, collectionName = components.addonCollection.collectionName, maxCacheAgeInMinutes = AMO_COLLECTION_MAX_CACHE_AGE - ) else + ) else AMOAddonsProvider( context, client, diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/feature/ContainerProxyFeature.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/feature/ContainerProxyFeature.kt index fdb4b850..df3fa900 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/feature/ContainerProxyFeature.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/feature/ContainerProxyFeature.kt @@ -7,6 +7,9 @@ package eu.weblibre.flutter_mozilla_components.feature import androidx.annotation.VisibleForTesting +import eu.weblibre.flutter_mozilla_components.GlobalComponents +import eu.weblibre.flutter_mozilla_components.pigeons.ContainerSiteAssignment +import eu.weblibre.flutter_mozilla_components.pigeons.GeckoStateEvents import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex @@ -16,20 +19,27 @@ import mozilla.components.concept.engine.webextension.MessageHandler import mozilla.components.concept.engine.webextension.Port import mozilla.components.concept.engine.webextension.WebExtensionRuntime import mozilla.components.support.base.log.logger.Logger +import mozilla.components.support.ktx.android.org.json.tryGetString import mozilla.components.support.webextensions.BuiltInWebExtensionController import org.json.JSONObject +import org.mozilla.gecko.util.ThreadUtils.runOnUiThread object ContainerProxyFeature { private val logger = Logger("container_proxy") private const val CONTAINER_PROXY_REPORTER_EXTENSION_ID = "container-proxy@weblibre.eu" - private const val CONTAINER_PROXY_REPORTER_EXTENSION_URL = "resource://android/assets/extensions/container_proxy/" + private const val CONTAINER_PROXY_REPORTER_EXTENSION_URL = + "resource://android/assets/extensions/container_proxy/" private const val CONTAINER_PROXY_REPORTER_MESSAGING_ID = "containerProxy" private var nextRequestId: Int = 0 private val requestHandlers = HashMap>() private val mutex = Mutex() + private val components by lazy { + requireNotNull(GlobalComponents.components) { "Components not initialized" } + } + @VisibleForTesting // This is an internal var to make it mutable for unit testing purposes only internal var extensionController = BuiltInWebExtensionController( @@ -74,7 +84,9 @@ object ContainerProxyFeature { } } - private class ContainerProxyBackgroundMessageHandler() : MessageHandler { + private class ContainerProxyBackgroundMessageHandler( + private var events: GeckoStateEvents + ) : MessageHandler { override fun onPortMessage(message: Any, port: Port) { runBlocking { withContext(Dispatchers.Default) { @@ -94,6 +106,25 @@ object ContainerProxyFeature { message.getString("error") ) } + } else if (type == "assignedSiteRequested") { + val requestId = messageJSON.getString("id") + val status = messageJSON.getString("status") + val details = messageJSON.getJSONObject("result") + + if (status == "success") { + runOnUiThread { + events.onContainerSiteAssignment( + System.currentTimeMillis(), + ContainerSiteAssignment( + requestId = requestId, + tabId = components.core.store.state.selectedTabId, + originUrl = details.tryGetString("originUrl"), + url = details.getString("url"), + blocked = details.getBoolean("blocked") + ) + ) { _ -> } + } + } } } } @@ -108,9 +139,9 @@ object ContainerProxyFeature { * @param productName a custom product name used to automatically label reports. Defaults to * "android-components". */ - fun install(runtime: WebExtensionRuntime) { + fun install(runtime: WebExtensionRuntime, events: GeckoStateEvents) { extensionController.registerBackgroundMessageHandler( - ContainerProxyBackgroundMessageHandler() + ContainerProxyBackgroundMessageHandler(events) ) extensionController.install( runtime, diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/pigeons/Gecko.g.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/pigeons/Gecko.g.kt index d02af0eb..db82188c 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/pigeons/Gecko.g.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/pigeons/Gecko.g.kt @@ -2277,6 +2277,46 @@ data class GeckoPref ( override fun hashCode(): Int = toList().hashCode() } +/** Generated class from Pigeon that represents data sent in messages. */ +data class ContainerSiteAssignment ( + val requestId: String, + val tabId: String? = null, + val originUrl: String? = null, + val url: String, + val blocked: Boolean +) + { + companion object { + fun fromList(pigeonVar_list: List): ContainerSiteAssignment { + val requestId = pigeonVar_list[0] as String + val tabId = pigeonVar_list[1] as String? + val originUrl = pigeonVar_list[2] as String? + val url = pigeonVar_list[3] as String + val blocked = pigeonVar_list[4] as Boolean + return ContainerSiteAssignment(requestId, tabId, originUrl, url, blocked) + } + } + fun toList(): List { + return listOf( + requestId, + tabId, + originUrl, + url, + blocked, + ) + } + override fun equals(other: Any?): Boolean { + if (other !is ContainerSiteAssignment) { + return false + } + if (this === other) { + return true + } + return GeckoPigeonUtils.deepEquals(toList(), other.toList()) } + + override fun hashCode(): Int = toList().hashCode() +} + /** Generated class from Pigeon that represents data sent in messages. */ data class GeckoHeader ( val key: String, @@ -2743,15 +2783,20 @@ private open class GeckoPigeonCodec : StandardMessageCodec() { } 195.toByte() -> { return (readValue(buffer) as? List)?.let { - GeckoHeader.fromList(it) + ContainerSiteAssignment.fromList(it) } } 196.toByte() -> { return (readValue(buffer) as? List)?.let { - GeckoFetchRequest.fromList(it) + GeckoHeader.fromList(it) } } 197.toByte() -> { + return (readValue(buffer) as? List)?.let { + GeckoFetchRequest.fromList(it) + } + } + 198.toByte() -> { return (readValue(buffer) as? List)?.let { GeckoFetchResponse.fromList(it) } @@ -3025,18 +3070,22 @@ private open class GeckoPigeonCodec : StandardMessageCodec() { stream.write(194) writeValue(stream, value.toList()) } - is GeckoHeader -> { + is ContainerSiteAssignment -> { stream.write(195) writeValue(stream, value.toList()) } - is GeckoFetchRequest -> { + is GeckoHeader -> { stream.write(196) writeValue(stream, value.toList()) } - is GeckoFetchResponse -> { + is GeckoFetchRequest -> { stream.write(197) writeValue(stream, value.toList()) } + is GeckoFetchResponse -> { + stream.write(198) + writeValue(stream, value.toList()) + } else -> super.writeValue(stream, value) } } @@ -4257,6 +4306,7 @@ interface GeckoContainerProxyApi { fun setProxyPort(port: Long) fun addContainerProxy(contextId: String) fun removeContainerProxy(contextId: String) + fun setSiteAssignments(assignments: Map) fun healthcheck(callback: (Result) -> Unit) companion object { @@ -4322,6 +4372,24 @@ interface GeckoContainerProxyApi { channel.setMessageHandler(null) } } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.setSiteAssignments$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val assignmentsArg = args[0] as Map + val wrapped: List = try { + api.setSiteAssignments(assignmentsArg) + listOf(null) + } catch (exception: Throwable) { + GeckoPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.healthcheck$separatedMessageChannelSuffix", codec) if (api != null) { @@ -4744,6 +4812,23 @@ class GeckoStateEvents(private val binaryMessenger: BinaryMessenger, private val } } } + fun onContainerSiteAssignment(timestampArg: Long, detailsArg: ContainerSiteAssignment, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onContainerSiteAssignment$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(timestampArg, detailsArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(GeckoPigeonUtils.createConnectionError(channelName))) + } + } + } } /** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */ class GeckoLogging(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") { diff --git a/packages/flutter_mozilla_components/javascript/container_proxy/package-lock.json b/packages/flutter_mozilla_components/javascript/container_proxy/package-lock.json index c809c52e..ba842047 100644 --- a/packages/flutter_mozilla_components/javascript/container_proxy/package-lock.json +++ b/packages/flutter_mozilla_components/javascript/container_proxy/package-lock.json @@ -486,7 +486,6 @@ "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -573,7 +572,6 @@ "integrity": "sha512-9z+8yjKr5Wn73Pt17/ldnmQToaFHZxK0N1GHysuk/JIPT8RIdQeoInM01wWPgypRcvb6VH1drjuFpQ4zmY437g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } @@ -640,7 +638,6 @@ "integrity": "sha512-nvLlj+831dhdm4LR2Ly+HTpdLyBaMynoOr6wpIxS19d/bPeHQxFU5XQ6Gp6ohBpxvCWZM1uHQIC2+ySRH1rGrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } @@ -706,6 +703,7 @@ "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", "@typescript-eslint/scope-manager": "5.62.0", @@ -741,6 +739,7 @@ "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -1209,7 +1208,6 @@ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -1224,6 +1222,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1362,6 +1361,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -1543,7 +1543,6 @@ "integrity": "sha512-zV4Ky0v1F8dBrdYElwTvQhweQ0P7Kwc1aluqJsYtOBP01jXcWCyW2IEfI1YiqsG+Iy7ZR+o5LF1N+PGECBxHWA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "file-type": "^4.2.0" }, @@ -1557,7 +1556,6 @@ "integrity": "sha512-f2UbFQEk7LXgWpi5ntcO86OeA/cC80fuDDDaX/fZ2ZGel+AF7leRQqBBW1eJNiiQkrZlAoM6P+VYP5P6bOlDEQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -1604,8 +1602,7 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/array-includes": { "version": "3.1.9", @@ -1926,8 +1923,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", @@ -1982,7 +1978,6 @@ "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" @@ -2102,6 +2097,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001733", "electron-to-chromium": "^1.5.199", @@ -2135,7 +2131,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -2147,7 +2142,6 @@ "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "buffer-alloc-unsafe": "^1.1.0", "buffer-fill": "^1.0.0" @@ -2158,8 +2152,7 @@ "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/buffer-crc32": { "version": "0.2.13", @@ -2183,8 +2176,7 @@ "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/buffer-from": { "version": "1.1.2", @@ -2263,7 +2255,6 @@ "integrity": "sha512-vag0O2LKZ/najSoUwDbVlnlCFvhBE/7mGTY2B5FgCBDcRD+oVV1HYTOwM6JZfMg/hIcM6IwnTZ1uQQL5/X3xIQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "clone-response": "1.0.2", "get-stream": "3.0.0", @@ -2280,7 +2271,6 @@ "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -2290,8 +2280,7 @@ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/cacheable-request/node_modules/keyv": { "version": "3.0.0", @@ -2299,7 +2288,6 @@ "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "json-buffer": "3.0.0" } @@ -2310,7 +2298,6 @@ "integrity": "sha512-RPlX0+PHuvxVDZ7xX+EBVAp4RsVxP/TdDSN2mJYdiq1Lc4Hz7EUSjUI7RZrKKlmrIzVhf6Jo2stj7++gVarS0A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -2866,7 +2853,6 @@ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "5.2.1" }, @@ -2880,7 +2866,6 @@ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -2890,8 +2875,7 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/copy-webpack-plugin": { "version": "12.0.2", @@ -3117,7 +3101,6 @@ "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10" } @@ -3128,7 +3111,6 @@ "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "decompress-tar": "^4.0.0", "decompress-tarbz2": "^4.0.0", @@ -3162,7 +3144,6 @@ "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "file-type": "^5.2.0", "is-stream": "^1.1.0", @@ -3178,7 +3159,6 @@ "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -3189,7 +3169,6 @@ "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "decompress-tar": "^4.1.0", "file-type": "^6.1.0", @@ -3207,7 +3186,6 @@ "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -3218,7 +3196,6 @@ "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "decompress-tar": "^4.1.1", "file-type": "^5.2.0", @@ -3234,7 +3211,6 @@ "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -3245,7 +3221,6 @@ "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "file-type": "^3.8.0", "get-stream": "^2.2.0", @@ -3262,7 +3237,6 @@ "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -3273,7 +3247,6 @@ "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "object-assign": "^4.0.1", "pinkie-promise": "^2.0.0" @@ -3288,7 +3261,6 @@ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -3299,7 +3271,6 @@ "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "pify": "^3.0.0" }, @@ -3313,7 +3284,6 @@ "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -3324,7 +3294,6 @@ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -3646,7 +3615,6 @@ "integrity": "sha512-ASRY5QhDk7FK+XrQtQyvhpDKanLluEEQtWl/J7Lxuf/b+i8RYh997QeXvL85xitrmRKVlx9c7eTrcRdq2GS4eA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "archive-type": "^4.0.0", "content-disposition": "^0.5.2", @@ -3670,7 +3638,6 @@ "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@sindresorhus/is": "^0.7.0", "cacheable-request": "^2.1.1", @@ -3700,7 +3667,6 @@ "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -3711,7 +3677,6 @@ "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -3722,7 +3687,6 @@ "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -3733,7 +3697,6 @@ "integrity": "sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -3744,7 +3707,6 @@ "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "prepend-http": "^2.0.0" }, @@ -3853,8 +3815,7 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/electron-to-chromium": { "version": "1.5.200", @@ -4173,8 +4134,7 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "4.0.0", @@ -4196,6 +4156,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -4394,6 +4355,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -4461,6 +4423,7 @@ "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "builtins": "^5.0.1", "eslint-plugin-es": "^4.1.0", @@ -4572,6 +4535,7 @@ "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "eslint-plugin-es": "^3.0.0", "eslint-utils": "^2.0.0", @@ -4603,6 +4567,7 @@ "integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -4619,6 +4584,7 @@ "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", @@ -4892,7 +4858,6 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -4966,7 +4931,6 @@ "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mime-db": "^1.28.0" }, @@ -4980,7 +4944,6 @@ "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ext-list": "^2.0.0", "sort-keys-length": "^1.0.0" @@ -5140,7 +5103,6 @@ "integrity": "sha512-rM0UO7Qm9K7TWTtA6AShI/t7H5BPjDeGVDaNyg9BjHAj3PysKy7+8C8D137R88jnR3rFJZQB/tFgydl5sN5m7g==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -5151,7 +5113,6 @@ "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -5162,7 +5123,6 @@ "integrity": "sha512-5EFZ//MsvJgXjBAFJ+Bh2YaCTRF/VP1YOmGrgt+KJ4SFRLjI87EIdwLLuT6wQX0I4F9W41xutobzczjsOKlI/g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "filename-reserved-regex": "^2.0.0", "strip-outer": "^1.0.0", @@ -5321,7 +5281,6 @@ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -5332,7 +5291,6 @@ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -5343,7 +5301,6 @@ "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" @@ -5354,8 +5311,7 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fs-extra": { "version": "11.3.1", @@ -6110,7 +6066,6 @@ "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "*" } @@ -6134,7 +6089,6 @@ "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "has-symbol-support-x": "^1.4.1" }, @@ -6223,8 +6177,7 @@ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", "dev": true, - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/http-signature": { "version": "1.2.0", @@ -6272,7 +6225,6 @@ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -6299,8 +6251,7 @@ "url": "https://feross.org/support" } ], - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/ignore": { "version": "5.3.2", @@ -6449,7 +6400,6 @@ "integrity": "sha512-TcdjPibTksa1NQximqep2r17ISRiNE9fwlfbg3F8ANdvP5/yrFTew86VcO//jk4QTaMlbjypPBq76HN2zaKfZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "from2": "^2.1.1", "p-is-promise": "^1.1.0" @@ -6464,7 +6414,6 @@ "integrity": "sha512-zL7VE4JVS2IFSkR2GQKDSPEVxkoH43/p7oEnwpdCndKYJO0HVeRB7fA8TJwuLOTBREtK0ea8eHaxdwcpob5dmg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -6488,7 +6437,6 @@ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.10" } @@ -6881,8 +6829,7 @@ "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/is-negative-zero": { "version": "2.0.3", @@ -6953,7 +6900,6 @@ "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7259,7 +7205,6 @@ "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "has-to-string-tag-x": "^1.2.0", "is-object": "^1.0.1" @@ -7875,7 +7820,6 @@ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "pify": "^4.0.1", "semver": "^5.6.0" @@ -7890,7 +7834,6 @@ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver" } @@ -7938,7 +7881,6 @@ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -7981,7 +7923,6 @@ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -8006,7 +7947,6 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "mime": "cli.js" }, @@ -8423,7 +8363,6 @@ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -8531,7 +8470,6 @@ "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "prepend-http": "^2.0.0", "query-string": "^5.0.1", @@ -8547,7 +8485,6 @@ "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -8558,7 +8495,6 @@ "integrity": "sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "is-plain-obj": "^1.0.0" }, @@ -8884,7 +8820,6 @@ "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -8905,7 +8840,6 @@ "integrity": "sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-timeout": "^2.0.1" }, @@ -8919,7 +8853,6 @@ "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -8972,7 +8905,6 @@ "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-finally": "^1.0.0" }, @@ -9089,7 +9021,6 @@ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -9621,7 +9552,6 @@ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -9696,7 +9626,6 @@ "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "decode-uri-component": "^0.2.0", "object-assign": "^4.1.0", @@ -9760,7 +9689,6 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -10595,7 +10523,6 @@ "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "commander": "^2.8.1" }, @@ -10609,8 +10536,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/selenium-webdriver": { "version": "4.35.0", @@ -10745,8 +10671,7 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/sha.js": { "version": "2.4.11", @@ -10993,7 +10918,6 @@ "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "is-plain-obj": "^1.0.0" }, @@ -11007,7 +10931,6 @@ "integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "sort-keys": "^1.0.0" }, @@ -11286,6 +11209,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", "@eslint/eslintrc": "^0.3.0", @@ -11393,6 +11317,7 @@ "integrity": "sha512-hNVtyhiEtZmpsabL4neEj+6M5DCLgpYyG9nzJY8lZQeQXEn5UPW1DpUdsMHMXsq98dbNm7nt1w9ZMSVpfJdi8Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "array-includes": "^3.1.3", "array.prototype.flat": "^1.2.4", @@ -11446,6 +11371,7 @@ "integrity": "sha512-XgdcdyNzHfmlQyweOPTxmc7pIsS6dE4MvwhXWMQ2Dxs1XAL2GJDilUsjWen6TWik0aSI+zD/PqocZBblcm9rdA==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": "^10.12.0 || >=12.0.0" }, @@ -11459,6 +11385,7 @@ "integrity": "sha512-ZMbFvZ1WAYSZKY662MBVEWR45VaBT6KSJCiupjrNlcdakB90juaZeDCbJq19e73JZQubqFtgETohwgAt8u5P6w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "array-includes": "^3.1.3", "array.prototype.flatmap": "^1.2.4", @@ -11801,7 +11728,6 @@ "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -12024,7 +11950,6 @@ "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "is-natural-number": "^4.0.1" } @@ -12058,7 +11983,6 @@ "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "escape-string-regexp": "^1.0.2" }, @@ -12072,7 +11996,6 @@ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.8.0" } @@ -12182,7 +12105,6 @@ "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "bl": "^1.0.0", "buffer-alloc": "^1.2.0", @@ -12343,7 +12265,6 @@ "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "isarray": "^2.0.5", "safe-buffer": "^5.2.1", @@ -12358,8 +12279,7 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/to-readable-stream": { "version": "1.0.0", @@ -12390,7 +12310,6 @@ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.6" } @@ -12434,7 +12353,6 @@ "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "escape-string-regexp": "^1.0.2" }, @@ -12448,7 +12366,6 @@ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.8.0" } @@ -12966,7 +12883,6 @@ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -13076,6 +12992,7 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13109,7 +13026,6 @@ "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "buffer": "^5.2.1", "through": "^2.3.8" @@ -13164,7 +13080,6 @@ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -13327,7 +13242,6 @@ "integrity": "sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 4" } @@ -13345,7 +13259,6 @@ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4.0" } @@ -13384,7 +13297,6 @@ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -14068,7 +13980,6 @@ "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -14245,7 +14156,6 @@ "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -14313,7 +14223,6 @@ "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -14323,8 +14232,7 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/webextensions-geckodriver/node_modules/dom-serializer": { "version": "1.4.1", @@ -14398,7 +14306,6 @@ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -14615,7 +14522,6 @@ "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -14811,7 +14717,6 @@ "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "depd": "~1.1.2", "inherits": "2.0.4", @@ -14989,8 +14894,7 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/webextensions-geckodriver/node_modules/mkdirp": { "version": "1.0.4", @@ -15080,7 +14984,6 @@ "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ee-first": "1.1.1" }, @@ -15182,8 +15085,7 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/webextensions-geckodriver/node_modules/pino": { "version": "7.9.1", @@ -15299,7 +15201,6 @@ "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.6" }, @@ -15313,7 +15214,6 @@ "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "bytes": "3.1.1", "http-errors": "1.8.1", @@ -15382,7 +15282,6 @@ "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "debug": "2.6.9", "depd": "~1.1.2", @@ -15407,8 +15306,7 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/webextensions-geckodriver/node_modules/serve-static": { "version": "1.14.2", @@ -15416,7 +15314,6 @@ "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -15456,7 +15353,6 @@ "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -15736,6 +15632,7 @@ "integrity": "sha512-rHY3vHXRbkSfhG6fH8zYQdth/BtDgXXuR2pHF++1f/EBkI8zkgM5XWfsC3BvOoW9pr1CvZ1qQCxhCEsbNgT50g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -15785,6 +15682,7 @@ "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", @@ -16228,7 +16126,6 @@ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.4" } diff --git a/packages/flutter_mozilla_components/javascript/container_proxy/src/background/BackgroundMain.ts b/packages/flutter_mozilla_components/javascript/container_proxy/src/background/BackgroundMain.ts index 4765e127..1afa6e52 100644 --- a/packages/flutter_mozilla_components/javascript/container_proxy/src/background/BackgroundMain.ts +++ b/packages/flutter_mozilla_components/javascript/container_proxy/src/background/BackgroundMain.ts @@ -5,6 +5,7 @@ import { ProxyType } from '../domain/ProxyType' import BlockingResponse = browser.webRequest.BlockingResponse import _OnAuthRequiredDetails = browser.webRequest._OnAuthRequiredDetails import _OnRequestDetails = browser.proxy._OnRequestDetails +import _OnBeforeRequestDetails = browser.webRequest._OnBeforeRequestDetails const localhosts = new Set(['localhost', '127.0.0.1', '[::1]']) @@ -127,13 +128,79 @@ export default class BackgroundMain { return doNotProxy } - run(browser: { proxy: any }): void { - const filter = { urls: [''] } + async onBeforeRequest(options: _OnBeforeRequestDetails, port: browser.runtime.Port): Promise { + const tab = (options.tabId > -1) ? (await browser.tabs.get(options.tabId)) : null - browser.proxy.onRequest.addListener(this.onRequest.bind(this), filter) + if (options.frameId !== 0 || tab === null) { + return {}; + } + + const url = URL.parse(options.url); + if (url !== null && this.store.isSiteOriginAssigned(url)) { + let cookieStoreId: string + + if (tab.cookieStoreId?.startsWith(containerIdentifier) === true) { + cookieStoreId = tab.cookieStoreId.substring(containerIdentifier.length) + } else if (tab.cookieStoreId === privateIdentifier) { + // Handle private tabs - use 'private' as identifier + cookieStoreId = 'private' + } else { + cookieStoreId = 'general' + } + + if (this.store.isSiteOriginInSameContext(url, cookieStoreId)) { + if (tab.highlighted) { + port.postMessage({ + "type": "assignedSiteRequested", + "id": options.requestId, + "status": "success", + "result": { + "originUrl": options.originUrl, + "url": options.url, + "blocked": false + } + }); + + return {}; + } else { + //When tab not selected, block the request + return { + cancel: true, + }; + } + } else { + //Only send events when tab is selected + if (tab.highlighted) { + port.postMessage({ + "type": "assignedSiteRequested", + "id": options.requestId, + "status": "success", + "result": { + "originUrl": options.originUrl, + "url": options.url, + "blocked": true + } + }); + } + + return { + cancel: true, + }; + } + } + + return {}; + } + + run(browser: { proxy: any, webRequest: any }, port: browser.runtime.Port): void { + browser.proxy.onRequest.addListener(this.onRequest.bind(this), { urls: [''] }) browser.proxy.onError.addListener((e: Error) => { console.error('Proxy error', e) }) + + browser.webRequest.onBeforeRequest.addListener((options: _OnBeforeRequestDetails) => { + return this.onBeforeRequest(options, port); + }, { urls: [""], types: ["main_frame"] }, ["blocking"]) } } diff --git a/packages/flutter_mozilla_components/javascript/container_proxy/src/background/index.ts b/packages/flutter_mozilla_components/javascript/container_proxy/src/background/index.ts index c9517680..4d636a7c 100644 --- a/packages/flutter_mozilla_components/javascript/container_proxy/src/background/index.ts +++ b/packages/flutter_mozilla_components/javascript/container_proxy/src/background/index.ts @@ -8,7 +8,7 @@ const store = new Store() interface Message { id: String | undefined; - action: 'setProxyPort' | 'addContainerProxy' | 'removeContainerProxy' | 'healthcheck'; + action: 'setProxyPort' | 'addContainerProxy' | 'removeContainerProxy' | 'healthcheck' | 'setSiteAssignments'; args: any; } @@ -42,6 +42,11 @@ port.onMessage.addListener((raw: unknown): void => { store.removeContainerProxyRelation(message.args, "tor") console.log('removed container relation ' + message.args) break + case "setSiteAssignments": + const entries = new Map(Object.entries(message.args)) + console.log('set site assignments ' + JSON.stringify(message.args)) + store.setSiteAssignments(entries); + break case "healthcheck": port.postMessage({ "type": "healthcheck", @@ -54,4 +59,4 @@ port.onMessage.addListener((raw: unknown): void => { }); const backgroundListener = new BackgroundMain({ store }) -backgroundListener.run(browser) +backgroundListener.run(browser, port) diff --git a/packages/flutter_mozilla_components/javascript/container_proxy/src/store/Store.ts b/packages/flutter_mozilla_components/javascript/container_proxy/src/store/Store.ts index f6dc9bf0..5eecbdad 100644 --- a/packages/flutter_mozilla_components/javascript/container_proxy/src/store/Store.ts +++ b/packages/flutter_mozilla_components/javascript/container_proxy/src/store/Store.ts @@ -79,6 +79,24 @@ export class Store { private proxies: ProxyDao[] = [] private relations: { [key: string]: string[] } = {} + private siteAssignments: Map = new Map() + + setSiteAssignments(sites: Map): void { + this.siteAssignments = new Map( + Array.from(sites, ([key, value]) => { + return [URL.parse(key)!.origin, value as string] + }) + ); + } + + isSiteOriginAssigned(uri: URL): boolean { + return this.siteAssignments.has(uri.origin) + } + + isSiteOriginInSameContext(uri: URL, contextId: string): boolean { + return this.siteAssignments.get(uri.origin) === contextId; + } + getAllProxies(): ProxySettings[] { const proxyDaos = this.getAllProxyDaos() return proxyDaos.map(tryFromDao).filter(p => p !== undefined) as ProxySettings[] diff --git a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_container_proxy.dart b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_container_proxy.dart index e8213491..bc0885e4 100644 --- a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_container_proxy.dart +++ b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_container_proxy.dart @@ -23,6 +23,10 @@ class GeckoContainerProxyService { return _apiInstance.removeContainerProxy(contextId); } + Future setSiteAssignments(Map assignments) { + return _apiInstance.setSiteAssignments(assignments); + } + Future healthcheck() async { try { return await _apiInstance.healthcheck().timeout( diff --git a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_event.dart b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_event.dart index e1f5ee3b..be18b8b8 100644 --- a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_event.dart +++ b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_event.dart @@ -40,6 +40,7 @@ class GeckoEventService extends GeckoStateEvents { final _longPressSubject = PublishSubject(); final _scrollEventSubject = PublishSubject(); final _prefUpdateSubject = PublishSubject(); + final _siteAssignementSubject = PublishSubject(); final _tabAddedSubject = PublishSubject(); @@ -61,6 +62,8 @@ class GeckoEventService extends GeckoStateEvents { Stream get longPressEvent => _longPressSubject.stream; Stream get scrollEvent => _scrollEventSubject.stream; Stream get prefUpdateEvent => _prefUpdateSubject.stream; + Stream get siteAssignementEvent => + _siteAssignementSubject.stream; Stream get tabAddedStream => _tabAddedSubject.stream; @@ -184,6 +187,18 @@ class GeckoEventService extends GeckoStateEvents { _prefUpdateSubject.addWhenMoreRecent(timestamp, value.name, value); } + @override + void onContainerSiteAssignment( + int timestamp, + ContainerSiteAssignment details, + ) { + _siteAssignementSubject.addWhenMoreRecent( + timestamp, + details.requestId, + details, + ); + } + GeckoEventService.setUp({ BinaryMessenger? binaryMessenger, String messageChannelSuffix = '', @@ -212,5 +227,6 @@ class GeckoEventService extends GeckoStateEvents { unawaited(_scrollEventSubject.close()); unawaited(_tabAddedSubject.close()); unawaited(_prefUpdateSubject.close()); + unawaited(_siteAssignementSubject.close()); } } diff --git a/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart b/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart index 9a649027..d58b6c39 100644 --- a/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart +++ b/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart @@ -2875,6 +2875,67 @@ class GeckoPref { ; } +class ContainerSiteAssignment { + ContainerSiteAssignment({ + required this.requestId, + this.tabId, + this.originUrl, + required this.url, + required this.blocked, + }); + + String requestId; + + String? tabId; + + String? originUrl; + + String url; + + bool blocked; + + List _toList() { + return [ + requestId, + tabId, + originUrl, + url, + blocked, + ]; + } + + Object encode() { + return _toList(); } + + static ContainerSiteAssignment decode(Object result) { + result as List; + return ContainerSiteAssignment( + requestId: result[0]! as String, + tabId: result[1] as String?, + originUrl: result[2] as String?, + url: result[3]! as String, + blocked: result[4]! as bool, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! ContainerSiteAssignment || 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 GeckoHeader { GeckoHeader({ required this.key, @@ -3284,15 +3345,18 @@ class _PigeonCodec extends StandardMessageCodec { } else if (value is GeckoPref) { buffer.putUint8(194); writeValue(buffer, value.encode()); - } else if (value is GeckoHeader) { + } else if (value is ContainerSiteAssignment) { buffer.putUint8(195); writeValue(buffer, value.encode()); - } else if (value is GeckoFetchRequest) { + } else if (value is GeckoHeader) { buffer.putUint8(196); writeValue(buffer, value.encode()); - } else if (value is GeckoFetchResponse) { + } else if (value is GeckoFetchRequest) { buffer.putUint8(197); writeValue(buffer, value.encode()); + } else if (value is GeckoFetchResponse) { + buffer.putUint8(198); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -3456,10 +3520,12 @@ class _PigeonCodec extends StandardMessageCodec { case 194: return GeckoPref.decode(readValue(buffer)!); case 195: - return GeckoHeader.decode(readValue(buffer)!); + return ContainerSiteAssignment.decode(readValue(buffer)!); case 196: - return GeckoFetchRequest.decode(readValue(buffer)!); + return GeckoHeader.decode(readValue(buffer)!); case 197: + return GeckoFetchRequest.decode(readValue(buffer)!); + case 198: return GeckoFetchResponse.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -4990,6 +5056,29 @@ class GeckoContainerProxyApi { } } + Future setSiteAssignments(Map assignments) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.setSiteAssignments$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([assignments]); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + 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; + } + } + Future healthcheck() async { final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoContainerProxyApi.healthcheck$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( @@ -5170,6 +5259,8 @@ abstract class GeckoStateEvents { void onPreferenceChange(int timestamp, GeckoPref value); + void onContainerSiteAssignment(int timestamp, ContainerSiteAssignment details); + static void setUp(GeckoStateEvents? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { @@ -5641,6 +5732,34 @@ abstract class GeckoStateEvents { }); } } + { + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onContainerSiteAssignment$messageChannelSuffix', pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onContainerSiteAssignment was null.'); + final List args = (message as List?)!; + final int? arg_timestamp = (args[0] as int?); + assert(arg_timestamp != null, + 'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onContainerSiteAssignment was null, expected non-null int.'); + final ContainerSiteAssignment? arg_details = (args[1] as ContainerSiteAssignment?); + assert(arg_details != null, + 'Argument for dev.flutter.pigeon.flutter_mozilla_components.GeckoStateEvents.onContainerSiteAssignment was null, expected non-null ContainerSiteAssignment.'); + try { + api.onContainerSiteAssignment(arg_timestamp!, arg_details!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } } } diff --git a/packages/flutter_mozilla_components/pigeons/gecko.dart b/packages/flutter_mozilla_components/pigeons/gecko.dart index 28d817d8..c831d9dd 100644 --- a/packages/flutter_mozilla_components/pigeons/gecko.dart +++ b/packages/flutter_mozilla_components/pigeons/gecko.dart @@ -1131,6 +1131,7 @@ abstract class GeckoContainerProxyApi { void setProxyPort(int port); void addContainerProxy(String contextId); void removeContainerProxy(String contextId); + void setSiteAssignments(Map assignments); @async bool healthcheck(); @@ -1183,6 +1184,22 @@ abstract class GeckoCookieApi { ); } +class ContainerSiteAssignment { + final String requestId; + final String? tabId; + final String? originUrl; + final String url; + final bool blocked; + + ContainerSiteAssignment({ + required this.requestId, + required this.tabId, + required this.originUrl, + required this.url, + required this.blocked, + }); +} + @FlutterApi() abstract class GeckoStateEvents { void onViewReadyStateChange(int timestamp, bool state); @@ -1210,6 +1227,11 @@ abstract class GeckoStateEvents { void onScrollChange(int timestamp, String tabId, int scrollY); void onPreferenceChange(int timestamp, GeckoPref value); + + void onContainerSiteAssignment( + int timestamp, + ContainerSiteAssignment details, + ); } @FlutterApi()