From 4726f12431c17521621fab5f9ae108985ca6a1f1 Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Wed, 19 Jun 2024 21:10:41 +0200 Subject: [PATCH] apply content blocking --- .../features/web_view/domain/providers.dart | 26 ++++++++ .../features/web_view/domain/providers.g.dart | 25 ++++++++ .../presentation/widgets/web_view.dart | 59 ++++++++++++++----- 3 files changed, 94 insertions(+), 16 deletions(-) create mode 100644 app/lib/features/web_view/domain/providers.dart create mode 100644 app/lib/features/web_view/domain/providers.g.dart diff --git a/app/lib/features/web_view/domain/providers.dart b/app/lib/features/web_view/domain/providers.dart new file mode 100644 index 00000000..df3cb4c7 --- /dev/null +++ b/app/lib/features/web_view/domain/providers.dart @@ -0,0 +1,26 @@ +import 'package:bang_navigator/features/content_block/domain/repositories/host.dart'; +import 'package:bang_navigator/features/settings/data/repositories/settings_repository.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'providers.g.dart'; + +@Riverpod(keepAlive: true) +Stream?> blockContentHosts(BlockContentHostsRef ref) { + final hostRepository = ref.watch(hostRepositoryProvider.notifier); + final enableContentBlocking = ref.watch( + settingsRepositoryProvider + .select((value) => value.valueOrNull?.enableContentBlocking ?? false), + ); + final enableHostList = ref.watch( + settingsRepositoryProvider + .select((value) => value.valueOrNull?.enableHostList ?? {}), + ); + + if (!enableContentBlocking || enableHostList.isEmpty) { + return Stream.value(null); + } + + return hostRepository + .watchHosts(sources: enableHostList) + .map((hosts) => hosts.toSet()); +} diff --git a/app/lib/features/web_view/domain/providers.g.dart b/app/lib/features/web_view/domain/providers.g.dart new file mode 100644 index 00000000..09f2435a --- /dev/null +++ b/app/lib/features/web_view/domain/providers.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'providers.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$blockContentHostsHash() => r'27e7902adda8d8d3b6d29fdc43c5d7ad5c3a8e9a'; + +/// See also [blockContentHosts]. +@ProviderFor(blockContentHosts) +final blockContentHostsProvider = StreamProvider?>.internal( + blockContentHosts, + name: r'blockContentHostsProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$blockContentHostsHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef BlockContentHostsRef = StreamProviderRef?>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/app/lib/features/web_view/presentation/widgets/web_view.dart b/app/lib/features/web_view/presentation/widgets/web_view.dart index 1f3e8975..afcf6744 100644 --- a/app/lib/features/web_view/presentation/widgets/web_view.dart +++ b/app/lib/features/web_view/presentation/widgets/web_view.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'package:bang_navigator/core/logger.dart'; import 'package:bang_navigator/features/bangs/domain/providers.dart'; @@ -9,6 +10,7 @@ import 'package:bang_navigator/features/search_browser/domain/entities/sheet.dar import 'package:bang_navigator/features/search_browser/domain/providers.dart'; import 'package:bang_navigator/features/settings/data/repositories/settings_repository.dart'; import 'package:bang_navigator/features/web_view/domain/entities/web_view_page.dart'; +import 'package:bang_navigator/features/web_view/domain/providers.dart'; import 'package:bang_navigator/features/web_view/presentation/controllers/switch_new_tab.dart'; import 'package:bang_navigator/features/web_view/presentation/widgets/web_page_dialog.dart'; import 'package:bang_navigator/features/web_view/utils/download_helper.dart'; @@ -86,12 +88,6 @@ class _WebViewState extends ConsumerState { @override Widget build(BuildContext context) { - final showEarlyAccessFeatures = ref.watch( - settingsRepositoryProvider.select( - (value) => value.valueOrNull?.showEarlyAccessFeatures ?? true, - ), - ); - final initialSettings = useMemoized( () => InAppWebViewSettings( // isInspectable: kDebugMode, @@ -109,6 +105,36 @@ class _WebViewState extends ConsumerState { ), ); + final showEarlyAccessFeatures = ref.watch( + settingsRepositoryProvider.select( + (value) => value.valueOrNull?.showEarlyAccessFeatures ?? true, + ), + ); + + // We keep listening to changes and cache them in this ref. + // This is important, since our WebView will not have the main scope anymore + // to react to updates from ref.watch + // + // With this solution we dont need to read on every interception request + // and can just obtain the value from here + final blockContentHosts = useRef?>(null); + ref.listen( + blockContentHostsProvider, + (previous, next) { + blockContentHosts.value = next.valueOrNull; + }, + ); + + ref.listen( + settingsRepositoryProvider + .select((value) => value.valueOrNull?.enableJavascript), + (previous, next) async { + await widget.page.value.controller?.setSettings( + settings: initialSettings.copy()..javaScriptEnabled = next, + ); + }, + ); + final webViewProgress = useValueNotifier(100); useOnAppLifecycleStateChange((previous, current) async { @@ -131,16 +157,6 @@ class _WebViewState extends ConsumerState { } }); - ref.listen( - settingsRepositoryProvider - .select((value) => value.valueOrNull?.enableJavascript), - (previous, next) async { - await widget.page.value.controller?.setSettings( - settings: initialSettings.copy()..javaScriptEnabled = next, - ); - }, - ); - return Stack( children: [ InAppWebView( @@ -218,6 +234,17 @@ class _WebViewState extends ConsumerState { action: ServerTrustAuthResponseAction.PROCEED, ); }, + shouldInterceptRequest: (controller, request) async { + if (blockContentHosts.value?.contains(request.url.host) == true) { + return WebResourceResponse( + contentType: "text/plain", + data: utf8.encode("Blocked"), + statusCode: 403, + reasonPhrase: "Forbidden", + ); + } + return null; + }, onProgressChanged: (controller, progress) { webViewProgress.value = progress; },