diff --git a/packages/webview_flutter/webview_flutter/CHANGELOG.md b/packages/webview_flutter/webview_flutter/CHANGELOG.md index 48b96405d38..34495364cf5 100644 --- a/packages/webview_flutter/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.8.0 + +* Adds `onHttpError` callback to `NavigationDelegate` to catch HTTP error status codes. + ## 4.7.0 * Adds support to track scroll position changes. diff --git a/packages/webview_flutter/webview_flutter/README.md b/packages/webview_flutter/webview_flutter/README.md index 399b6af4e46..91c201a1584 100644 --- a/packages/webview_flutter/webview_flutter/README.md +++ b/packages/webview_flutter/webview_flutter/README.md @@ -32,6 +32,7 @@ controller = WebViewController() }, onPageStarted: (String url) {}, onPageFinished: (String url) {}, + onHttpError: (HttpResponseError error) {}, onWebResourceError: (WebResourceError error) {}, onNavigationRequest: (NavigationRequest request) { if (request.url.startsWith('https://www.youtube.com/')) { diff --git a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart index 51f1cd872d8..8460a602ab0 100644 --- a/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter/example/integration_test/webview_flutter_test.dart @@ -672,6 +672,65 @@ Future main() async { expect(currentUrl, isNot(contains('youtube.com'))); }); + testWidgets('onHttpError', (WidgetTester tester) async { + final Completer errorCompleter = + Completer(); + + final WebViewController controller = WebViewController(); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + + final NavigationDelegate delegate = NavigationDelegate( + onHttpError: (HttpResponseError error) { + errorCompleter.complete(error); + }, + ); + unawaited(controller.setNavigationDelegate(delegate)); + + unawaited(controller.loadRequest( + Uri.parse('$prefixUrl/favicon.ico'), + )); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + final HttpResponseError error = await errorCompleter.future; + + expect(error, isNotNull); + expect(error.response?.statusCode, 404); + }); + + testWidgets('onHttpError is not called when no HTTP error is received', + (WidgetTester tester) async { + const String testPage = ''' + + + + + + '''; + + final Completer errorCompleter = + Completer(); + final Completer pageFinishCompleter = Completer(); + + final WebViewController controller = WebViewController(); + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + + final NavigationDelegate delegate = NavigationDelegate( + onPageFinished: pageFinishCompleter.complete, + onHttpError: (HttpResponseError error) { + errorCompleter.complete(error); + }, + ); + unawaited(controller.setNavigationDelegate(delegate)); + + unawaited(controller.loadHtmlString(testPage)); + + await tester.pumpWidget(WebViewWidget(controller: controller)); + + expect(errorCompleter.future, doesNotComplete); + await pageFinishCompleter.future; + }); + testWidgets('supports asynchronous decisions', (WidgetTester tester) async { Completer pageLoaded = Completer(); diff --git a/packages/webview_flutter/webview_flutter/example/lib/main.dart b/packages/webview_flutter/webview_flutter/example/lib/main.dart index d0a3f965c4e..d24ec11424f 100644 --- a/packages/webview_flutter/webview_flutter/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter/example/lib/main.dart @@ -170,6 +170,9 @@ Page resource error: debugPrint('allowing navigation to ${request.url}'); return NavigationDecision.navigate; }, + onHttpError: (HttpResponseError error) { + debugPrint('Error occurred on page: ${error.response?.statusCode}'); + }, onUrlChange: (UrlChange change) { debugPrint('url change to ${change.url}'); }, diff --git a/packages/webview_flutter/webview_flutter/example/lib/simple_example.dart b/packages/webview_flutter/webview_flutter/example/lib/simple_example.dart index dfee9e6bd23..06a7bd31dd8 100644 --- a/packages/webview_flutter/webview_flutter/example/lib/simple_example.dart +++ b/packages/webview_flutter/webview_flutter/example/lib/simple_example.dart @@ -34,6 +34,7 @@ class _WebViewExampleState extends State { }, onPageStarted: (String url) {}, onPageFinished: (String url) {}, + onHttpError: (HttpResponseError error) {}, onWebResourceError: (WebResourceError error) {}, onNavigationRequest: (NavigationRequest request) { if (request.url.startsWith('https://www.youtube.com/')) { diff --git a/packages/webview_flutter/webview_flutter/example/pubspec.yaml b/packages/webview_flutter/webview_flutter/example/pubspec.yaml index 789d9498a0c..7c5eb72fa97 100644 --- a/packages/webview_flutter/webview_flutter/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter/example/pubspec.yaml @@ -17,8 +17,8 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - webview_flutter_android: ^3.15.0 - webview_flutter_wkwebview: ^3.12.0 + webview_flutter_android: ^3.16.0 + webview_flutter_wkwebview: ^3.13.0 dev_dependencies: build_runner: ^2.1.5 diff --git a/packages/webview_flutter/webview_flutter/example/test/main_test.dart b/packages/webview_flutter/webview_flutter/example/test/main_test.dart index c4b43428ec9..4bb788578a1 100644 --- a/packages/webview_flutter/webview_flutter/example/test/main_test.dart +++ b/packages/webview_flutter/webview_flutter/example/test/main_test.dart @@ -121,4 +121,7 @@ class FakeNavigationDelegate extends PlatformNavigationDelegate { Future setOnHttpAuthRequest( HttpAuthRequestCallback handler, ) async {} + + @override + Future setOnHttpError(HttpResponseErrorCallback onHttpError) async {} } diff --git a/packages/webview_flutter/webview_flutter/lib/src/navigation_delegate.dart b/packages/webview_flutter/webview_flutter/lib/src/navigation_delegate.dart index a490fb1ca8b..746caed140d 100644 --- a/packages/webview_flutter/webview_flutter/lib/src/navigation_delegate.dart +++ b/packages/webview_flutter/webview_flutter/lib/src/navigation_delegate.dart @@ -50,6 +50,7 @@ class NavigationDelegate { void Function(WebResourceError error)? onWebResourceError, void Function(UrlChange change)? onUrlChange, void Function(HttpAuthRequest request)? onHttpAuthRequest, + void Function(HttpResponseError error)? onHttpError, }) : this.fromPlatformCreationParams( const PlatformNavigationDelegateCreationParams(), onNavigationRequest: onNavigationRequest, @@ -59,6 +60,7 @@ class NavigationDelegate { onWebResourceError: onWebResourceError, onUrlChange: onUrlChange, onHttpAuthRequest: onHttpAuthRequest, + onHttpError: onHttpError, ); /// Constructs a [NavigationDelegate] from creation params for a specific @@ -102,6 +104,7 @@ class NavigationDelegate { void Function(WebResourceError error)? onWebResourceError, void Function(UrlChange change)? onUrlChange, void Function(HttpAuthRequest request)? onHttpAuthRequest, + void Function(HttpResponseError error)? onHttpError, }) : this.fromPlatform( PlatformNavigationDelegate(params), onNavigationRequest: onNavigationRequest, @@ -111,6 +114,7 @@ class NavigationDelegate { onWebResourceError: onWebResourceError, onUrlChange: onUrlChange, onHttpAuthRequest: onHttpAuthRequest, + onHttpError: onHttpError, ); /// Constructs a [NavigationDelegate] from a specific platform implementation. @@ -125,6 +129,7 @@ class NavigationDelegate { this.onWebResourceError, void Function(UrlChange change)? onUrlChange, HttpAuthRequestCallback? onHttpAuthRequest, + void Function(HttpResponseError error)? onHttpError, }) { if (onNavigationRequest != null) { platform.setOnNavigationRequest(onNavigationRequest!); @@ -147,6 +152,9 @@ class NavigationDelegate { if (onHttpAuthRequest != null) { platform.setOnHttpAuthRequest(onHttpAuthRequest); } + if (onHttpError != null) { + platform.setOnHttpError(onHttpError); + } } /// Implementation of [PlatformNavigationDelegate] for the current platform. diff --git a/packages/webview_flutter/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/webview_flutter/lib/webview_flutter.dart index 97f127def1a..e135e3c5bcb 100644 --- a/packages/webview_flutter/webview_flutter/lib/webview_flutter.dart +++ b/packages/webview_flutter/webview_flutter/lib/webview_flutter.dart @@ -5,6 +5,8 @@ export 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart' show HttpAuthRequest, + HttpResponseError, + HttpResponseErrorCallback, JavaScriptAlertDialogRequest, JavaScriptConfirmDialogRequest, JavaScriptConsoleMessage, @@ -28,6 +30,8 @@ export 'package:webview_flutter_platform_interface/webview_flutter_platform_inte WebResourceError, WebResourceErrorCallback, WebResourceErrorType, + WebResourceRequest, + WebResourceResponse, WebViewCookie, WebViewCredential, WebViewPermissionResourceType, diff --git a/packages/webview_flutter/webview_flutter/pubspec.yaml b/packages/webview_flutter/webview_flutter/pubspec.yaml index 6307d713f34..c48857425b2 100644 --- a/packages/webview_flutter/webview_flutter/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter description: A Flutter plugin that provides a WebView widget on Android and iOS. repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 4.7.0 +version: 4.8.0 environment: sdk: ^3.2.3 @@ -19,9 +19,9 @@ flutter: dependencies: flutter: sdk: flutter - webview_flutter_android: ^3.15.0 + webview_flutter_android: ^3.16.0 webview_flutter_platform_interface: ^2.10.0 - webview_flutter_wkwebview: ^3.12.0 + webview_flutter_wkwebview: ^3.13.0 dev_dependencies: build_runner: ^2.1.5 diff --git a/packages/webview_flutter/webview_flutter/test/navigation_delegate_test.dart b/packages/webview_flutter/webview_flutter/test/navigation_delegate_test.dart index c2bc6f230fa..64f5b16170d 100644 --- a/packages/webview_flutter/webview_flutter/test/navigation_delegate_test.dart +++ b/packages/webview_flutter/webview_flutter/test/navigation_delegate_test.dart @@ -99,6 +99,18 @@ void main() { verify(delegate.platform.setOnHttpAuthRequest(onHttpAuthRequest)); }); + + test('onHttpError', () async { + WebViewPlatform.instance = TestWebViewPlatform(); + + void onHttpError(HttpResponseError error) {} + + final NavigationDelegate delegate = NavigationDelegate( + onHttpError: onHttpError, + ); + + verify(delegate.platform.setOnHttpError(onHttpError)); + }); }); } diff --git a/packages/webview_flutter/webview_flutter/test/webview_flutter_export_test.dart b/packages/webview_flutter/webview_flutter/test/webview_flutter_export_test.dart index cf3dd5c89d3..907cc474cc8 100644 --- a/packages/webview_flutter/webview_flutter/test/webview_flutter_export_test.dart +++ b/packages/webview_flutter/webview_flutter/test/webview_flutter_export_test.dart @@ -13,6 +13,8 @@ void main() { 'ensure webview_flutter.dart exports classes from platform interface', () { main_file.HttpAuthRequest; + main_file.HttpResponseError; + main_file.HttpResponseErrorCallback; main_file.JavaScriptConsoleMessage; main_file.JavaScriptLogLevel; main_file.JavaScriptMessage; @@ -34,6 +36,8 @@ void main() { main_file.WebViewCookie; main_file.WebViewCredential; main_file.WebResourceErrorType; + main_file.WebResourceRequest; + main_file.WebResourceResponse; main_file.UrlChange; }, );