[webview_flutter] Fixes bug when onNavigationRequestCallback returns false (#5981)
diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
index 66393c8..67c633f 100644
--- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.8.13
+
+* Fixes a bug which causes an exception when the `onNavigationRequestCallback` return `false`.
+
## 2.8.12
* Bumps mockito-inline from 3.11.1 to 4.6.1.
diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
index f1b130c..ff6265d 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
@@ -652,8 +652,8 @@
if (returnValue is bool && returnValue) {
loadUrl!(url, <String, String>{});
- } else {
- (returnValue as Future<bool>).then((bool shouldLoadUrl) {
+ } else if (returnValue is Future<bool>) {
+ returnValue.then((bool shouldLoadUrl) {
if (shouldLoadUrl) {
loadUrl!(url, <String, String>{});
}
@@ -677,8 +677,8 @@
if (returnValue is bool && returnValue) {
loadUrl!(request.url, <String, String>{});
- } else {
- (returnValue as Future<bool>).then((bool shouldLoadUrl) {
+ } else if (returnValue is Future<bool>) {
+ returnValue.then((bool shouldLoadUrl) {
if (shouldLoadUrl) {
loadUrl!(request.url, <String, String>{});
}
diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
index 53f25c7..759e9d7 100644
--- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
@@ -2,7 +2,7 @@
description: A Flutter plugin that provides a WebView widget on Android.
repository: https://github.com/flutter/plugins/tree/main/packages/webview_flutter/webview_flutter_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
-version: 2.8.12
+version: 2.8.13
environment:
sdk: ">=2.14.0 <3.0.0"
diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
index a987f1c..2432b35 100644
--- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/widgets.dart';
@@ -24,6 +25,7 @@
android_webview.WebSettings,
android_webview.WebStorage,
android_webview.WebView,
+ android_webview.WebResourceRequest,
WebViewAndroidDownloadListener,
WebViewAndroidJavaScriptChannel,
WebViewAndroidWebChromeClient,
@@ -843,4 +845,192 @@
verify(mockPlatformHostApi.setWebContentsDebuggingEnabled(false));
});
});
+
+ group('WebViewAndroidWebViewClient', () {
+ test(
+ 'urlLoading should call loadUrl when onNavigationRequestCallback returns true',
+ () {
+ final Completer<void> completer = Completer<void>();
+ final WebViewAndroidWebViewClient webViewClient =
+ WebViewAndroidWebViewClient.handlesNavigation(
+ onPageStartedCallback: (_) {},
+ onPageFinishedCallback: (_) {},
+ onWebResourceErrorCallback: (_) {},
+ onNavigationRequestCallback: ({
+ required bool isForMainFrame,
+ required String url,
+ }) =>
+ true,
+ loadUrl: (String url, Map<String, String>? headers) async {
+ completer.complete();
+ });
+
+ webViewClient.urlLoading(MockWebView(), 'https://flutter.dev');
+ expect(completer.isCompleted, isTrue);
+ });
+
+ test(
+ 'urlLoading should call loadUrl when onNavigationRequestCallback returns a Future true',
+ () async {
+ final Completer<void> completer = Completer<void>();
+ final WebViewAndroidWebViewClient webViewClient =
+ WebViewAndroidWebViewClient.handlesNavigation(
+ onPageStartedCallback: (_) {},
+ onPageFinishedCallback: (_) {},
+ onWebResourceErrorCallback: (_) {},
+ onNavigationRequestCallback: ({
+ required bool isForMainFrame,
+ required String url,
+ }) =>
+ Future<bool>.value(true),
+ loadUrl: (String url, Map<String, String>? headers) async {
+ completer.complete();
+ });
+
+ webViewClient.urlLoading(MockWebView(), 'https://flutter.dev');
+ expect(completer.future, completes);
+ });
+
+ test(
+ 'urlLoading should not call laodUrl when onNavigationRequestCallback returns false',
+ () async {
+ final WebViewAndroidWebViewClient webViewClient =
+ WebViewAndroidWebViewClient.handlesNavigation(
+ onPageStartedCallback: (_) {},
+ onPageFinishedCallback: (_) {},
+ onWebResourceErrorCallback: (_) {},
+ onNavigationRequestCallback: ({
+ required bool isForMainFrame,
+ required String url,
+ }) =>
+ false,
+ loadUrl: (String url, Map<String, String>? headers) async {
+ fail(
+ 'loadUrl should not be called if onNavigationRequestCallback returns false.');
+ });
+
+ webViewClient.urlLoading(MockWebView(), 'https://flutter.dev');
+ });
+
+ test(
+ 'urlLoading should not call loadUrl when onNavigationRequestCallback returns a Future false',
+ () {
+ final WebViewAndroidWebViewClient webViewClient =
+ WebViewAndroidWebViewClient.handlesNavigation(
+ onPageStartedCallback: (_) {},
+ onPageFinishedCallback: (_) {},
+ onWebResourceErrorCallback: (_) {},
+ onNavigationRequestCallback: ({
+ required bool isForMainFrame,
+ required String url,
+ }) =>
+ Future<bool>.value(false),
+ loadUrl: (String url, Map<String, String>? headers) async {
+ fail(
+ 'loadUrl should not be called if onNavigationRequestCallback returns false.');
+ });
+
+ webViewClient.urlLoading(MockWebView(), 'https://flutter.dev');
+ });
+
+ test(
+ 'requestLoading should call loadUrl when onNavigationRequestCallback returns true',
+ () {
+ final Completer<void> completer = Completer<void>();
+ final MockWebResourceRequest mockRequest = MockWebResourceRequest();
+ when(mockRequest.isForMainFrame).thenReturn(true);
+ when(mockRequest.url).thenReturn('https://flutter.dev');
+ final WebViewAndroidWebViewClient webViewClient =
+ WebViewAndroidWebViewClient.handlesNavigation(
+ onPageStartedCallback: (_) {},
+ onPageFinishedCallback: (_) {},
+ onWebResourceErrorCallback: (_) {},
+ onNavigationRequestCallback: ({
+ required bool isForMainFrame,
+ required String url,
+ }) =>
+ true,
+ loadUrl: (String url, Map<String, String>? headers) async {
+ expect(url, 'https://flutter.dev');
+ completer.complete();
+ });
+
+ webViewClient.requestLoading(MockWebView(), mockRequest);
+ expect(completer.isCompleted, isTrue);
+ });
+
+ test(
+ 'requestLoading should call loadUrl when onNavigationRequestCallback returns a Future true',
+ () async {
+ final Completer<void> completer = Completer<void>();
+ final MockWebResourceRequest mockRequest = MockWebResourceRequest();
+ when(mockRequest.isForMainFrame).thenReturn(true);
+ when(mockRequest.url).thenReturn('https://flutter.dev');
+ final WebViewAndroidWebViewClient webViewClient =
+ WebViewAndroidWebViewClient.handlesNavigation(
+ onPageStartedCallback: (_) {},
+ onPageFinishedCallback: (_) {},
+ onWebResourceErrorCallback: (_) {},
+ onNavigationRequestCallback: ({
+ required bool isForMainFrame,
+ required String url,
+ }) =>
+ Future<bool>.value(true),
+ loadUrl: (String url, Map<String, String>? headers) async {
+ expect(url, 'https://flutter.dev');
+ completer.complete();
+ });
+
+ webViewClient.requestLoading(MockWebView(), mockRequest);
+ expect(completer.future, completes);
+ });
+
+ test(
+ 'requestLoading should not call loadUrl when onNavigationRequestCallback returns false',
+ () {
+ final MockWebResourceRequest mockRequest = MockWebResourceRequest();
+ when(mockRequest.isForMainFrame).thenReturn(true);
+ when(mockRequest.url).thenReturn('https://flutter.dev');
+ final WebViewAndroidWebViewClient webViewClient =
+ WebViewAndroidWebViewClient.handlesNavigation(
+ onPageStartedCallback: (_) {},
+ onPageFinishedCallback: (_) {},
+ onWebResourceErrorCallback: (_) {},
+ onNavigationRequestCallback: ({
+ required bool isForMainFrame,
+ required String url,
+ }) =>
+ false,
+ loadUrl: (String url, Map<String, String>? headers) {
+ fail(
+ 'loadUrl should not be called if onNavigationRequestCallback returns false.');
+ });
+
+ webViewClient.requestLoading(MockWebView(), mockRequest);
+ });
+
+ test(
+ 'requestLoading should not call loadUrl when onNavigationRequestCallback returns a Future false',
+ () {
+ final MockWebResourceRequest mockRequest = MockWebResourceRequest();
+ when(mockRequest.isForMainFrame).thenReturn(true);
+ when(mockRequest.url).thenReturn('https://flutter.dev');
+ final WebViewAndroidWebViewClient webViewClient =
+ WebViewAndroidWebViewClient.handlesNavigation(
+ onPageStartedCallback: (_) {},
+ onPageFinishedCallback: (_) {},
+ onWebResourceErrorCallback: (_) {},
+ onNavigationRequestCallback: ({
+ required bool isForMainFrame,
+ required String url,
+ }) =>
+ Future<bool>.value(false),
+ loadUrl: (String url, Map<String, String>? headers) {
+ fail(
+ 'loadUrl should not be called if onNavigationRequestCallback returns false.');
+ });
+
+ webViewClient.requestLoading(MockWebView(), mockRequest);
+ });
+ });
}
diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart
index 3385e79..78e60ca 100644
--- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart
@@ -286,6 +286,36 @@
returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
}
+/// A class which mocks [WebResourceRequest].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockWebResourceRequest extends _i1.Mock
+ implements _i2.WebResourceRequest {
+ MockWebResourceRequest() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ String get url =>
+ (super.noSuchMethod(Invocation.getter(#url), returnValue: '') as String);
+ @override
+ bool get isForMainFrame => (super
+ .noSuchMethod(Invocation.getter(#isForMainFrame), returnValue: false)
+ as bool);
+ @override
+ bool get hasGesture =>
+ (super.noSuchMethod(Invocation.getter(#hasGesture), returnValue: false)
+ as bool);
+ @override
+ String get method =>
+ (super.noSuchMethod(Invocation.getter(#method), returnValue: '')
+ as String);
+ @override
+ Map<String, String> get requestHeaders =>
+ (super.noSuchMethod(Invocation.getter(#requestHeaders),
+ returnValue: <String, String>{}) as Map<String, String>);
+}
+
/// A class which mocks [WebViewAndroidDownloadListener].
///
/// See the documentation for Mockito's code generation for more information.