added support for custom addon collections

This commit is contained in:
Fabian Freund
2025-09-30 13:02:17 +02:00
parent 7c4662a7be
commit 751e22ac5e
20 changed files with 423 additions and 41 deletions
+1
View File
@@ -38,6 +38,7 @@ import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/c
import 'package:weblibre/features/geckoview/features/tabs/presentation/screens/container_list.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_selection.dart';
import 'package:weblibre/features/onboarding/presentation/onboarding.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'; import 'package:weblibre/features/settings/presentation/screens/bang_settings.dart';
import 'package:weblibre/features/settings/presentation/screens/developer_settings.dart'; import 'package:weblibre/features/settings/presentation/screens/developer_settings.dart';
import 'package:weblibre/features/settings/presentation/screens/general_settings.dart'; import 'package:weblibre/features/settings/presentation/screens/general_settings.dart';
+29
View File
@@ -112,6 +112,13 @@ RouteBase get $settingsRoute => GoRouteData.$route(
path: 'developer', path: 'developer',
name: 'DeveloperSettingsRoute', name: 'DeveloperSettingsRoute',
factory: $DeveloperSettingsRoute._fromState, factory: $DeveloperSettingsRoute._fromState,
routes: [
GoRouteData.$route(
path: 'addon_collection',
name: 'AddonCollectionRoute',
factory: $AddonCollectionRoute._fromState,
),
],
), ),
], ],
); );
@@ -268,6 +275,28 @@ mixin $DeveloperSettingsRoute on GoRouteData {
void replace(BuildContext context) => context.replace(location); void replace(BuildContext context) => context.replace(location);
} }
mixin $AddonCollectionRoute on GoRouteData {
static AddonCollectionRoute _fromState(GoRouterState state) =>
AddonCollectionRoute();
@override
String get location =>
GoRouteData.$location('/settings/developer/addon_collection');
@override
void go(BuildContext context) => context.go(location);
@override
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
@override
void pushReplacement(BuildContext context) =>
context.pushReplacement(location);
@override
void replace(BuildContext context) => context.replace(location);
}
RouteBase get $browserRoute => GoRouteData.$route( RouteBase get $browserRoute => GoRouteData.$route(
path: '/', path: '/',
name: 'BrowserRoute', name: 'BrowserRoute',
+13
View File
@@ -47,6 +47,12 @@ part of 'routes.dart';
TypedGoRoute<DeveloperSettingsRoute>( TypedGoRoute<DeveloperSettingsRoute>(
name: 'DeveloperSettingsRoute', name: 'DeveloperSettingsRoute',
path: 'developer', path: 'developer',
routes: [
TypedGoRoute<AddonCollectionRoute>(
name: 'AddonCollectionRoute',
path: 'addon_collection',
),
],
), ),
], ],
) )
@@ -85,6 +91,13 @@ class DeveloperSettingsRoute extends GoRouteData with $DeveloperSettingsRoute {
} }
} }
class AddonCollectionRoute extends GoRouteData with $AddonCollectionRoute {
@override
Widget build(BuildContext context, GoRouterState state) {
return const AddonCollectionScreen();
}
}
class WebEngineHardeningRoute extends GoRouteData class WebEngineHardeningRoute extends GoRouteData
with $WebEngineHardeningRoute { with $WebEngineHardeningRoute {
@override @override
@@ -0,0 +1,143 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:universal_io/io.dart';
import 'package:weblibre/features/user/data/models/engine_settings.dart';
import 'package:weblibre/features/user/domain/repositories/engine_settings.dart';
import 'package:weblibre/utils/form_validators.dart';
const _defaultServerUrl = 'https://services.addons.mozilla.org';
class AddonCollectionScreen extends HookConsumerWidget {
const AddonCollectionScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final formKey = useMemoized(() => GlobalKey<FormState>());
final addonCollectionSetting = ref.watch(
engineSettingsWithDefaultsProvider.select(
(value) => value.addonCollection,
),
);
final serverURLController = useTextEditingController(
text: addonCollectionSetting?.serverURL ?? _defaultServerUrl,
keys: [addonCollectionSetting],
);
final collectionUserController = useTextEditingController(
text: addonCollectionSetting?.collectionUser,
keys: [addonCollectionSetting],
);
final collectionNameController = useTextEditingController(
text: addonCollectionSetting?.collectionName,
keys: [addonCollectionSetting],
);
return Scaffold(
appBar: AppBar(
title: const Text('Custom Extension Collection'),
actions: [
if (addonCollectionSetting != null)
IconButton(
onPressed: () async {
await ref
.read(engineSettingsRepositoryProvider.notifier)
.updateSettings(
(currentSettings) =>
currentSettings.copyWith.addonCollection(null),
);
unawaited(
Future.delayed(const Duration(seconds: 1)).whenComplete(() {
exit(0);
}),
);
},
icon: const Icon(Icons.delete),
),
],
),
body: Form(
key: formKey,
child: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: ListView(
children: [
TextFormField(
controller: serverURLController,
decoration: const InputDecoration(
label: Text('Server URL'),
hintText: _defaultServerUrl,
floatingLabelBehavior: FloatingLabelBehavior.always,
),
keyboardType: TextInputType.url,
validator: (value) {
return validateUrl(
value,
onlyHttpProtocol: true,
eagerParsing: false,
);
},
),
const SizedBox(height: 8),
TextFormField(
controller: collectionUserController,
decoration: const InputDecoration(
label: Text('Collection User'),
floatingLabelBehavior: FloatingLabelBehavior.always,
),
validator: validateRequired,
),
const SizedBox(height: 8),
TextFormField(
controller: collectionNameController,
decoration: const InputDecoration(
label: Text('Collection Name'),
floatingLabelBehavior: FloatingLabelBehavior.always,
),
validator: validateRequired,
),
const SizedBox(height: 32),
FilledButton(
onPressed: () async {
if (formKey.currentState?.validate() == true) {
await ref
.read(engineSettingsRepositoryProvider.notifier)
.updateSettings(
(
currentSettings,
) => currentSettings.copyWith.addonCollection(
AddonCollection(
serverURL: serverURLController.text,
collectionUser: collectionUserController.text,
collectionName: collectionNameController.text,
),
),
);
unawaited(
Future.delayed(const Duration(seconds: 1)).whenComplete(
() {
exit(0);
},
),
);
}
},
child: const Text('Save & Restart Browser'),
),
],
),
),
),
),
);
}
}
@@ -29,6 +29,7 @@ import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:universal_io/io.dart'; import 'package:universal_io/io.dart';
import 'package:weblibre/core/logger.dart'; import 'package:weblibre/core/logger.dart';
import 'package:weblibre/core/routing/routes.dart';
import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart'; import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart';
import 'package:weblibre/features/settings/presentation/widgets/custom_list_tile.dart'; import 'package:weblibre/features/settings/presentation/widgets/custom_list_tile.dart';
import 'package:weblibre/features/user/data/models/engine_settings.dart'; import 'package:weblibre/features/user/data/models/engine_settings.dart';
@@ -198,6 +199,17 @@ class DeveloperSettingsScreen extends HookConsumerWidget {
label: const Text('Copy'), label: const Text('Copy'),
), ),
), ),
ListTile(
leading: const Icon(MdiIcons.puzzle),
title: const Text('Custom Extension Collection'),
subtitle: const Text(
'Custom Add-on Collections are curated lists of extensions that users can create and share.',
),
trailing: const Icon(Icons.chevron_right),
onTap: () async {
await AddonCollectionRoute().push(context);
},
),
], ],
); );
}, },
@@ -17,10 +17,13 @@
* You should have received a copy of the GNU Affero General Public License * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import 'dart:convert';
import 'package:copy_with_extension/copy_with_extension.dart'; import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:fast_equatable/fast_equatable.dart'; import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:nullability/nullability.dart';
part 'engine_settings.g.dart'; part 'engine_settings.g.dart';
@@ -60,6 +63,9 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
final BounceTrackingProtectionMode bounceTrackingProtectionMode; final BounceTrackingProtectionMode bounceTrackingProtectionMode;
@JsonKey(fromJson: _addonCollectionFromJson, toJson: _addonCollectionToJson)
final AddonCollection? addonCollection;
@override @override
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
ContentBlocking get contentBlocking => ContentBlocking( ContentBlocking get contentBlocking => ContentBlocking(
@@ -85,6 +91,7 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
required super.enterpriseRootsEnabled, required super.enterpriseRootsEnabled,
required this.queryParameterStripping, required this.queryParameterStripping,
required this.bounceTrackingProtectionMode, required this.bounceTrackingProtectionMode,
required this.addonCollection,
}); });
EngineSettings.withDefaults({ EngineSettings.withDefaults({
@@ -102,6 +109,7 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
BounceTrackingProtectionMode? bounceTrackingProtectionMode, BounceTrackingProtectionMode? bounceTrackingProtectionMode,
super.userAgent, super.userAgent,
bool? enterpriseRootsEnabled, bool? enterpriseRootsEnabled,
this.addonCollection,
}) : queryParameterStripping = }) : queryParameterStripping =
queryParameterStripping ?? QueryParameterStripping.disabled, queryParameterStripping ?? QueryParameterStripping.disabled,
bounceTrackingProtectionMode = bounceTrackingProtectionMode =
@@ -129,6 +137,14 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
enterpriseRootsEnabled: enterpriseRootsEnabled ?? false, enterpriseRootsEnabled: enterpriseRootsEnabled ?? false,
); );
static AddonCollection? _addonCollectionFromJson(String? json) =>
json.mapNotNull(
(collection) => AddonCollection.decode(jsonDecode(collection) as List),
);
static String? _addonCollectionToJson(AddonCollection? collection) =>
collection.mapNotNull((collection) => jsonEncode(collection.encode()));
factory EngineSettings.fromJson(Map<String, dynamic> json) => factory EngineSettings.fromJson(Map<String, dynamic> json) =>
_$EngineSettingsFromJson(json); _$EngineSettingsFromJson(json);
@@ -136,19 +152,8 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable {
@override @override
List<Object?> get hashParameters => [ List<Object?> get hashParameters => [
super.javascriptEnabled,
super.trackingProtectionPolicy,
super.httpsOnlyMode,
super.globalPrivacyControlEnabled,
super.preferredColorScheme,
super.cookieBannerHandlingMode,
super.cookieBannerHandlingModePrivateBrowsing,
super.cookieBannerHandlingGlobalRules,
super.cookieBannerHandlingGlobalRulesSubFrames,
super.webContentIsolationStrategy,
super.userAgent,
queryParameterStripping, queryParameterStripping,
bounceTrackingProtectionMode, bounceTrackingProtectionMode,
super.enterpriseRootsEnabled, addonCollection,
]; ];
} }
@@ -51,6 +51,8 @@ abstract class _$EngineSettingsCWProxy {
BounceTrackingProtectionMode bounceTrackingProtectionMode, BounceTrackingProtectionMode bounceTrackingProtectionMode,
); );
EngineSettings addonCollection(AddonCollection? addonCollection);
/// Creates a new instance with the provided field values. /// 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 `EngineSettings(...).copyWith.fieldName(value)`. /// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `EngineSettings(...).copyWith.fieldName(value)`.
/// ///
@@ -73,6 +75,7 @@ abstract class _$EngineSettingsCWProxy {
bool? enterpriseRootsEnabled, bool? enterpriseRootsEnabled,
QueryParameterStripping queryParameterStripping, QueryParameterStripping queryParameterStripping,
BounceTrackingProtectionMode bounceTrackingProtectionMode, BounceTrackingProtectionMode bounceTrackingProtectionMode,
AddonCollection? addonCollection,
}); });
} }
@@ -153,6 +156,10 @@ class _$EngineSettingsCWProxyImpl implements _$EngineSettingsCWProxy {
BounceTrackingProtectionMode bounceTrackingProtectionMode, BounceTrackingProtectionMode bounceTrackingProtectionMode,
) => call(bounceTrackingProtectionMode: bounceTrackingProtectionMode); ) => call(bounceTrackingProtectionMode: bounceTrackingProtectionMode);
@override
EngineSettings addonCollection(AddonCollection? addonCollection) =>
call(addonCollection: addonCollection);
@override @override
/// Creates a new instance with the provided field values. /// 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 `EngineSettings(...).copyWith.fieldName(value)`. /// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `EngineSettings(...).copyWith.fieldName(value)`.
@@ -178,6 +185,7 @@ class _$EngineSettingsCWProxyImpl implements _$EngineSettingsCWProxy {
Object? enterpriseRootsEnabled = const $CopyWithPlaceholder(), Object? enterpriseRootsEnabled = const $CopyWithPlaceholder(),
Object? queryParameterStripping = const $CopyWithPlaceholder(), Object? queryParameterStripping = const $CopyWithPlaceholder(),
Object? bounceTrackingProtectionMode = const $CopyWithPlaceholder(), Object? bounceTrackingProtectionMode = const $CopyWithPlaceholder(),
Object? addonCollection = const $CopyWithPlaceholder(),
}) { }) {
return EngineSettings( return EngineSettings(
javascriptEnabled: javascriptEnabled == const $CopyWithPlaceholder() javascriptEnabled: javascriptEnabled == const $CopyWithPlaceholder()
@@ -251,6 +259,10 @@ class _$EngineSettingsCWProxyImpl implements _$EngineSettingsCWProxy {
? _value.bounceTrackingProtectionMode ? _value.bounceTrackingProtectionMode
// ignore: cast_nullable_to_non_nullable // ignore: cast_nullable_to_non_nullable
: bounceTrackingProtectionMode as BounceTrackingProtectionMode, : bounceTrackingProtectionMode as BounceTrackingProtectionMode,
addonCollection: addonCollection == const $CopyWithPlaceholder()
? _value.addonCollection
// ignore: cast_nullable_to_non_nullable
: addonCollection as AddonCollection?,
); );
} }
} }
@@ -308,6 +320,9 @@ EngineSettings _$EngineSettingsFromJson(Map<String, dynamic> json) =>
), ),
userAgent: json['userAgent'] as String?, userAgent: json['userAgent'] as String?,
enterpriseRootsEnabled: json['enterpriseRootsEnabled'] as bool?, enterpriseRootsEnabled: json['enterpriseRootsEnabled'] as bool?,
addonCollection: EngineSettings._addonCollectionFromJson(
json['addonCollection'] as String?,
),
); );
Map<String, dynamic> _$EngineSettingsToJson( Map<String, dynamic> _$EngineSettingsToJson(
@@ -337,6 +352,9 @@ Map<String, dynamic> _$EngineSettingsToJson(
'bounceTrackingProtectionMode': 'bounceTrackingProtectionMode':
_$BounceTrackingProtectionModeEnumMap[instance _$BounceTrackingProtectionModeEnumMap[instance
.bounceTrackingProtectionMode]!, .bounceTrackingProtectionMode]!,
'addonCollection': EngineSettings._addonCollectionToJson(
instance.addonCollection,
),
}; };
const _$TrackingProtectionPolicyEnumMap = { const _$TrackingProtectionPolicyEnumMap = {
@@ -94,6 +94,10 @@ class EngineSettingsRepository extends _$EngineSettingsRepository {
DriftSqlType.bool, DriftSqlType.bool,
db.typeMapping, db.typeMapping,
), ),
'addonCollection': settings['addonCollection']?.readAs(
DriftSqlType.string,
db.typeMapping,
),
}); });
} }
@@ -34,7 +34,7 @@ final class EngineSettingsRepositoryProvider
} }
String _$engineSettingsRepositoryHash() => String _$engineSettingsRepositoryHash() =>
r'd0c2fbe060b5eaee7189f6cb0dd58c97cf5987b5'; r'8cf0a615f825ddc6411c2985d2cdc19ef31251cb';
abstract class _$EngineSettingsRepository abstract class _$EngineSettingsRepository
extends $StreamNotifier<EngineSettings> { extends $StreamNotifier<EngineSettings> {
+1
View File
@@ -91,6 +91,7 @@ void main() async {
await GeckoBrowserService().initialize( await GeckoBrowserService().initialize(
kDebugMode ? LogLevel.debug : LogLevel.warn, kDebugMode ? LogLevel.debug : LogLevel.warn,
engineSettings.contentBlocking, engineSettings.contentBlocking,
engineSettings.addonCollection,
); );
await ref await ref
+8
View File
@@ -47,3 +47,11 @@ String? validateUrl(
return 'Inavlid URL'; return 'Inavlid URL';
} }
String? validateRequired(String? value, {String message = 'Value required'}) {
if (value.isNotEmpty) {
return null;
}
return message;
}
@@ -14,6 +14,7 @@ import eu.weblibre.flutter_mozilla_components.components.Features
import eu.weblibre.flutter_mozilla_components.components.Search import eu.weblibre.flutter_mozilla_components.components.Search
import eu.weblibre.flutter_mozilla_components.components.Services import eu.weblibre.flutter_mozilla_components.components.Services
import eu.weblibre.flutter_mozilla_components.components.UseCases import eu.weblibre.flutter_mozilla_components.components.UseCases
import eu.weblibre.flutter_mozilla_components.pigeons.AddonCollection
import eu.weblibre.flutter_mozilla_components.pigeons.BrowserExtensionEvents import eu.weblibre.flutter_mozilla_components.pigeons.BrowserExtensionEvents
import eu.weblibre.flutter_mozilla_components.pigeons.ContentBlocking import eu.weblibre.flutter_mozilla_components.pigeons.ContentBlocking
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoAddonEvents import eu.weblibre.flutter_mozilla_components.pigeons.GeckoAddonEvents
@@ -37,6 +38,7 @@ class Components(private val context: Context,
val selectionAction: SelectionActionDelegate, val selectionAction: SelectionActionDelegate,
val logLevel: Log.Priority, val logLevel: Log.Priority,
val contentBlocking: ContentBlocking, val contentBlocking: ContentBlocking,
val addonCollection: AddonCollection?,
private val addonEvents: GeckoAddonEvents, private val addonEvents: GeckoAddonEvents,
private val tabContentEvents: GeckoTabContentEvents, private val tabContentEvents: GeckoTabContentEvents,
private val extensionEvents: BrowserExtensionEvents private val extensionEvents: BrowserExtensionEvents
@@ -7,6 +7,7 @@
package eu.weblibre.flutter_mozilla_components package eu.weblibre.flutter_mozilla_components
import android.content.Context import android.content.Context
import eu.weblibre.flutter_mozilla_components.pigeons.AddonCollection
import eu.weblibre.flutter_mozilla_components.pigeons.BrowserExtensionEvents import eu.weblibre.flutter_mozilla_components.pigeons.BrowserExtensionEvents
import eu.weblibre.flutter_mozilla_components.pigeons.ContentBlocking import eu.weblibre.flutter_mozilla_components.pigeons.ContentBlocking
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoAddonEvents import eu.weblibre.flutter_mozilla_components.pigeons.GeckoAddonEvents
@@ -60,7 +61,8 @@ object GlobalComponents {
tabContentEvents: GeckoTabContentEvents, tabContentEvents: GeckoTabContentEvents,
extensionEvents: BrowserExtensionEvents, extensionEvents: BrowserExtensionEvents,
logLevel: Log.Priority, logLevel: Log.Priority,
contentBlocking: ContentBlocking contentBlocking: ContentBlocking,
addonCollection: AddonCollection?,
) { ) {
Logger.debug("Creating new components") Logger.debug("Creating new components")
@@ -71,6 +73,7 @@ object GlobalComponents {
selectionAction, selectionAction,
logLevel, logLevel,
contentBlocking, contentBlocking,
addonCollection,
addonEvents, addonEvents,
tabContentEvents, tabContentEvents,
extensionEvents, extensionEvents,
@@ -15,6 +15,7 @@ import eu.weblibre.flutter_mozilla_components.GeckoViewFactory
import eu.weblibre.flutter_mozilla_components.GlobalComponents import eu.weblibre.flutter_mozilla_components.GlobalComponents
import eu.weblibre.flutter_mozilla_components.activities.NotificationActivity import eu.weblibre.flutter_mozilla_components.activities.NotificationActivity
import eu.weblibre.flutter_mozilla_components.feature.DefaultSelectionActionDelegate import eu.weblibre.flutter_mozilla_components.feature.DefaultSelectionActionDelegate
import eu.weblibre.flutter_mozilla_components.pigeons.AddonCollection
import eu.weblibre.flutter_mozilla_components.pigeons.BrowserExtensionEvents import eu.weblibre.flutter_mozilla_components.pigeons.BrowserExtensionEvents
import eu.weblibre.flutter_mozilla_components.pigeons.ContentBlocking import eu.weblibre.flutter_mozilla_components.pigeons.ContentBlocking
import eu.weblibre.flutter_mozilla_components.pigeons.GeckoAddonEvents import eu.weblibre.flutter_mozilla_components.pigeons.GeckoAddonEvents
@@ -140,7 +141,11 @@ class GeckoBrowserApiImpl : GeckoBrowserApi {
return GeckoViewBuildConfig.MOZ_APP_VERSION + "-" + GeckoViewBuildConfig.MOZ_APP_BUILDID return GeckoViewBuildConfig.MOZ_APP_VERSION + "-" + GeckoViewBuildConfig.MOZ_APP_BUILDID
} }
override fun initialize(logLevel: LogLevel, contentBlocking: ContentBlocking) { override fun initialize(
logLevel: LogLevel,
contentBlocking: ContentBlocking,
addonCollection: AddonCollection?
) {
synchronized(this) { synchronized(this) {
if(!isGeckoInitialized) { if(!isGeckoInitialized) {
val geckoLogging = GeckoLogging(_flutterPluginBinding.binaryMessenger) val geckoLogging = GeckoLogging(_flutterPluginBinding.binaryMessenger)
@@ -154,7 +159,7 @@ class GeckoBrowserApiImpl : GeckoBrowserApi {
Log.addSink(PriorityAwareLogSink(level, geckoLogging)) Log.addSink(PriorityAwareLogSink(level, geckoLogging))
setupGeckoEngine(level, contentBlocking) setupGeckoEngine(level, contentBlocking, addonCollection)
isGeckoInitialized = true isGeckoInitialized = true
} }
} }
@@ -170,7 +175,11 @@ class GeckoBrowserApiImpl : GeckoBrowserApi {
return false return false
} }
private fun setupGeckoEngine(logLevel: Log.Priority, contentBlocking: ContentBlocking) { private fun setupGeckoEngine(
logLevel: Log.Priority,
contentBlocking: ContentBlocking,
addonCollection: AddonCollection?
) {
val selectionActionEvents = GeckoSelectionActionEvents(_flutterPluginBinding.binaryMessenger) val selectionActionEvents = GeckoSelectionActionEvents(_flutterPluginBinding.binaryMessenger)
val selectionActionDelegate = DefaultSelectionActionDelegate(selectionActionEvents) { actions -> val selectionActionDelegate = DefaultSelectionActionDelegate(selectionActionEvents) { actions ->
@@ -201,7 +210,8 @@ class GeckoBrowserApiImpl : GeckoBrowserApi {
tabContentEvents, tabContentEvents,
extensionEvents, extensionEvents,
logLevel, logLevel,
contentBlocking contentBlocking,
addonCollection
) )
GeckoEngineSettingsApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoEngineSettingsApiImpl()) GeckoEngineSettingsApi.setUp(_flutterPluginBinding.binaryMessenger, GeckoEngineSettingsApiImpl())
@@ -60,9 +60,10 @@ import mozilla.components.feature.webnotifications.WebNotificationFeature
import mozilla.components.support.base.worker.Frequency import mozilla.components.support.base.worker.Frequency
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
private const val DAY_IN_MINUTES = 24 * 60L private const val AMO_COLLECTION_MAX_CACHE_AGE = 24 * 60L
class Core(private val context: Context, class Core(
private val context: Context,
private val components: Components, private val components: Components,
private val flutterEvents: GeckoStateEvents, private val flutterEvents: GeckoStateEvents,
private val extensionEvents: BrowserExtensionEvents private val extensionEvents: BrowserExtensionEvents
@@ -137,22 +138,33 @@ class Core(private val context: Context,
val addonUpdater by lazy { val addonUpdater by lazy {
DefaultAddonUpdater( DefaultAddonUpdater(
context, context,
Frequency(1, TimeUnit.DAYS), Frequency(12, TimeUnit.HOURS),
components.notificationsDelegate components.notificationsDelegate
) )
} }
val addonsProvider by lazy { val addonsProvider by lazy {
if (components.addonCollection != null)
AMOAddonsProvider( AMOAddonsProvider(
context, context,
client, client,
collectionName = "7dfae8669acc4312a65e8ba5553036", serverURL = components.addonCollection.serverURL,
maxCacheAgeInMinutes = DAY_IN_MINUTES, collectionUser = components.addonCollection.collectionUser,
collectionName = components.addonCollection.collectionName,
maxCacheAgeInMinutes = AMO_COLLECTION_MAX_CACHE_AGE
) else
AMOAddonsProvider(
context,
client,
maxCacheAgeInMinutes = AMO_COLLECTION_MAX_CACHE_AGE
) )
} }
val supportedAddonsChecker by lazy { val supportedAddonsChecker by lazy {
DefaultSupportedAddonsChecker(context, Frequency(1, TimeUnit.DAYS)) DefaultSupportedAddonsChecker(
context,
Frequency(12, TimeUnit.HOURS)
)
} }
val fileUploadsDirCleaner: FileUploadsDirCleaner by lazy { val fileUploadsDirCleaner: FileUploadsDirCleaner by lazy {
@@ -164,7 +176,7 @@ class Core(private val context: Context,
BrowserStore( BrowserStore(
middleware = listOf( middleware = listOf(
FlutterEventMiddleware(flutterEvents), FlutterEventMiddleware(flutterEvents),
DownloadMiddleware(context, DownloadService::class.java, {false}), DownloadMiddleware(context, DownloadService::class.java, { false }),
ThumbnailsMiddleware(thumbnailStorage), ThumbnailsMiddleware(thumbnailStorage),
ReaderViewMiddleware(), ReaderViewMiddleware(),
UndoMiddleware(), UndoMiddleware(),
@@ -227,7 +239,7 @@ class Core(private val context: Context,
*/ */
val historyStorage by lazy { lazyHistoryStorage.value } val historyStorage by lazy { lazyHistoryStorage.value }
val permissionStorage by lazy { PermissionStorage( geckoSitePermissionsStorage ) } val permissionStorage by lazy { PermissionStorage(geckoSitePermissionsStorage) }
/** /**
* Constructs a [TrackingProtectionPolicy] based on current preferences. * Constructs a [TrackingProtectionPolicy] based on current preferences.
@@ -241,7 +253,7 @@ class Core(private val context: Context,
* @return the constructed tracking protection policy based on preferences. * @return the constructed tracking protection policy based on preferences.
*/ */
private fun createTrackingProtectionPolicy( private fun createTrackingProtectionPolicy(
trackingPolicy: EngineSession. TrackingProtectionPolicyForSessionTypes, trackingPolicy: EngineSession.TrackingProtectionPolicyForSessionTypes,
normalMode: Boolean = true, normalMode: Boolean = true,
privateMode: Boolean = true, privateMode: Boolean = true,
): TrackingProtectionPolicy { ): TrackingProtectionPolicy {
@@ -2104,6 +2104,40 @@ data class ShareInternetResourceState (
override fun hashCode(): Int = toList().hashCode() override fun hashCode(): Int = toList().hashCode()
} }
/** Generated class from Pigeon that represents data sent in messages. */
data class AddonCollection (
val serverURL: String,
val collectionUser: String,
val collectionName: String
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): AddonCollection {
val serverURL = pigeonVar_list[0] as String
val collectionUser = pigeonVar_list[1] as String
val collectionName = pigeonVar_list[2] as String
return AddonCollection(serverURL, collectionUser, collectionName)
}
}
fun toList(): List<Any?> {
return listOf(
serverURL,
collectionUser,
collectionName,
)
}
override fun equals(other: Any?): Boolean {
if (other !is AddonCollection) {
return false
}
if (this === other) {
return true
}
return GeckoPigeonUtils.deepEquals(toList(), other.toList()) }
override fun hashCode(): Int = toList().hashCode()
}
private open class GeckoPigeonCodec : StandardMessageCodec() { private open class GeckoPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) { return when (type) {
@@ -2402,6 +2436,11 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
ShareInternetResourceState.fromList(it) ShareInternetResourceState.fromList(it)
} }
} }
188.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
AddonCollection.fromList(it)
}
}
else -> super.readValueOfType(type, buffer) else -> super.readValueOfType(type, buffer)
} }
} }
@@ -2643,6 +2682,10 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
stream.write(187) stream.write(187)
writeValue(stream, value.toList()) writeValue(stream, value.toList())
} }
is AddonCollection -> {
stream.write(188)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value) else -> super.writeValue(stream, value)
} }
} }
@@ -2652,7 +2695,7 @@ private open class GeckoPigeonCodec : StandardMessageCodec() {
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ /** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface GeckoBrowserApi { interface GeckoBrowserApi {
fun getGeckoVersion(): String fun getGeckoVersion(): String
fun initialize(logLevel: LogLevel, contentBlocking: ContentBlocking) fun initialize(logLevel: LogLevel, contentBlocking: ContentBlocking, addonCollection: AddonCollection?)
fun showNativeFragment(): Boolean fun showNativeFragment(): Boolean
fun onTrimMemory(level: Long) fun onTrimMemory(level: Long)
@@ -2687,8 +2730,9 @@ interface GeckoBrowserApi {
val args = message as List<Any?> val args = message as List<Any?>
val logLevelArg = args[0] as LogLevel val logLevelArg = args[0] as LogLevel
val contentBlockingArg = args[1] as ContentBlocking val contentBlockingArg = args[1] as ContentBlocking
val addonCollectionArg = args[2] as AddonCollection?
val wrapped: List<Any?> = try { val wrapped: List<Any?> = try {
api.initialize(logLevelArg, contentBlockingArg) api.initialize(logLevelArg, contentBlockingArg, addonCollectionArg)
listOf(null) listOf(null)
} catch (exception: Throwable) { } catch (exception: Throwable) {
GeckoPigeonUtils.wrapError(exception) GeckoPigeonUtils.wrapError(exception)
@@ -31,6 +31,7 @@ export 'src/domain/services/gecko_tab_content.dart';
export 'src/geckoview_widget.dart'; export 'src/geckoview_widget.dart';
export 'src/pigeons/gecko.g.dart' export 'src/pigeons/gecko.g.dart'
show show
AddonCollection,
AudioHitResult, AudioHitResult,
BounceTrackingProtectionMode, BounceTrackingProtectionMode,
ColorScheme, ColorScheme,
@@ -17,8 +17,12 @@ class GeckoBrowserService {
return _api.getGeckoVersion(); return _api.getGeckoVersion();
} }
Future<void> initialize(LogLevel logLevel, ContentBlocking contentBlocking) { Future<void> initialize(
return _api.initialize(logLevel, contentBlocking); LogLevel logLevel,
ContentBlocking contentBlocking,
AddonCollection? addonCollection,
) {
return _api.initialize(logLevel, contentBlocking, addonCollection);
} }
Future<bool> showNativeFragment() { Future<bool> showNativeFragment() {
@@ -2664,6 +2664,57 @@ class ShareInternetResourceState {
; ;
} }
class AddonCollection {
AddonCollection({
required this.serverURL,
required this.collectionUser,
required this.collectionName,
});
String serverURL;
String collectionUser;
String collectionName;
List<Object?> _toList() {
return <Object?>[
serverURL,
collectionUser,
collectionName,
];
}
Object encode() {
return _toList(); }
static AddonCollection decode(Object result) {
result as List<Object?>;
return AddonCollection(
serverURL: result[0]! as String,
collectionUser: result[1]! as String,
collectionName: result[2]! as String,
);
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
bool operator ==(Object other) {
if (other is! AddonCollection || other.runtimeType != runtimeType) {
return false;
}
if (identical(this, other)) {
return true;
}
return _deepEquals(encode(), other.encode());
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => Object.hashAll(_toList())
;
}
class _PigeonCodec extends StandardMessageCodec { class _PigeonCodec extends StandardMessageCodec {
const _PigeonCodec(); const _PigeonCodec();
@@ -2849,6 +2900,9 @@ class _PigeonCodec extends StandardMessageCodec {
} else if (value is ShareInternetResourceState) { } else if (value is ShareInternetResourceState) {
buffer.putUint8(187); buffer.putUint8(187);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is AddonCollection) {
buffer.putUint8(188);
writeValue(buffer, value.encode());
} else { } else {
super.writeValue(buffer, value); super.writeValue(buffer, value);
} }
@@ -2993,6 +3047,8 @@ class _PigeonCodec extends StandardMessageCodec {
return DownloadState.decode(readValue(buffer)!); return DownloadState.decode(readValue(buffer)!);
case 187: case 187:
return ShareInternetResourceState.decode(readValue(buffer)!); return ShareInternetResourceState.decode(readValue(buffer)!);
case 188:
return AddonCollection.decode(readValue(buffer)!);
default: default:
return super.readValueOfType(type, buffer); return super.readValueOfType(type, buffer);
} }
@@ -3040,14 +3096,14 @@ class GeckoBrowserApi {
} }
} }
Future<void> initialize(LogLevel logLevel, ContentBlocking contentBlocking) async { Future<void> initialize(LogLevel logLevel, ContentBlocking contentBlocking, AddonCollection? addonCollection) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoBrowserApi.initialize$pigeonVar_messageChannelSuffix'; final String pigeonVar_channelName = 'dev.flutter.pigeon.flutter_mozilla_components.GeckoBrowserApi.initialize$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName, pigeonVar_channelName,
pigeonChannelCodec, pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger, binaryMessenger: pigeonVar_binaryMessenger,
); );
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[logLevel, contentBlocking]); final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[logLevel, contentBlocking, addonCollection]);
final List<Object?>? pigeonVar_replyList = final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?; await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) { if (pigeonVar_replyList == null) {
@@ -827,6 +827,18 @@ class ShareInternetResourceState {
enum LogLevel { debug, info, warn, error } enum LogLevel { debug, info, warn, error }
class AddonCollection {
final String serverURL;
final String collectionUser;
final String collectionName;
AddonCollection({
required this.serverURL,
required this.collectionUser,
required this.collectionName,
});
}
@ConfigurePigeon( @ConfigurePigeon(
PigeonOptions( PigeonOptions(
dartOut: 'lib/src/pigeons/gecko.g.dart', dartOut: 'lib/src/pigeons/gecko.g.dart',
@@ -842,7 +854,11 @@ enum LogLevel { debug, info, warn, error }
@HostApi() @HostApi()
abstract class GeckoBrowserApi { abstract class GeckoBrowserApi {
String getGeckoVersion(); String getGeckoVersion();
void initialize(LogLevel logLevel, ContentBlocking contentBlocking); void initialize(
LogLevel logLevel,
ContentBlocking contentBlocking,
AddonCollection? addonCollection,
);
bool showNativeFragment(); bool showNativeFragment();
void onTrimMemory(int level); void onTrimMemory(int level);
} }