From eaab52e1b46f6224e44982c647ff2d967f881a02 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Wed, 15 Jun 2022 19:33:34 +0200 Subject: [PATCH 1/2] Fixes bug when onNavigationRequestCallback returns false --- .../webview_flutter_android/CHANGELOG.md | 4 + .../lib/webview_android_widget.dart | 8 +- .../webview_flutter_android/pubspec.yaml | 2 +- .../test/webview_android_widget_test.dart | 192 ++++++++++++++++++ .../webview_android_widget_test.mocks.dart | 30 +++ 5 files changed, 231 insertions(+), 5 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 66393c88cbb3..67c633fda18a 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 f1b130c7e365..ff6265dbad00 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 @@ class WebViewAndroidWebViewClient extends android_webview.WebViewClient { if (returnValue is bool && returnValue) { loadUrl!(url, {}); - } else { - (returnValue as Future).then((bool shouldLoadUrl) { + } else if (returnValue is Future) { + returnValue.then((bool shouldLoadUrl) { if (shouldLoadUrl) { loadUrl!(url, {}); } @@ -677,8 +677,8 @@ class WebViewAndroidWebViewClient extends android_webview.WebViewClient { if (returnValue is bool && returnValue) { loadUrl!(request.url, {}); - } else { - (returnValue as Future).then((bool shouldLoadUrl) { + } else if (returnValue is Future) { + returnValue.then((bool shouldLoadUrl) { if (shouldLoadUrl) { loadUrl!(request.url, {}); } diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 53f25c723fda..759e9d73b050 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android 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 a987f1cf548d..7863e4a12367 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 @@ import 'webview_android_widget_test.mocks.dart'; android_webview.WebSettings, android_webview.WebStorage, android_webview.WebView, + android_webview.WebResourceRequest, WebViewAndroidDownloadListener, WebViewAndroidJavaScriptChannel, WebViewAndroidWebChromeClient, @@ -843,4 +845,194 @@ void main() { verify(mockPlatformHostApi.setWebContentsDebuggingEnabled(false)); }); }); + + group('WebViewAndroidWebViewClient', () { + test( + 'urlLoading should call loadUrl when onNavigationRequestCallback returns true', + () { + final Completer completer = Completer(); + final WebViewAndroidWebViewClient webViewClient = + WebViewAndroidWebViewClient.handlesNavigation( + onPageStartedCallback: (_) {}, + onPageFinishedCallback: (_) {}, + onWebResourceErrorCallback: (_) {}, + onNavigationRequestCallback: ({ + required bool isForMainFrame, + required String url, + }) => + true, + loadUrl: (String url, Map? 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 completer = Completer(); + final WebViewAndroidWebViewClient webViewClient = + WebViewAndroidWebViewClient.handlesNavigation( + onPageStartedCallback: (_) {}, + onPageFinishedCallback: (_) {}, + onWebResourceErrorCallback: (_) {}, + onNavigationRequestCallback: ({ + required bool isForMainFrame, + required String url, + }) => + Future.value(true), + loadUrl: (String url, Map? headers) async { + completer.complete(); + }); + + webViewClient.urlLoading(MockWebView(), 'https://flutter.dev'); + await completer.future; + expect(completer.isCompleted, isTrue); + }); + + 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? 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.value(false), + loadUrl: (String url, Map? 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 completer = Completer(); + 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? 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 completer = Completer(); + 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.value(true), + loadUrl: (String url, Map? headers) async { + expect(url, 'https://flutter.dev'); + completer.complete(); + }); + + webViewClient.requestLoading(MockWebView(), mockRequest); + await completer.future; + expect(completer.isCompleted, isTrue); + }); + + test( + 'requestLoading should not call onLoadUrlCallback 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? headers) { + fail( + 'loadUrl should not be called if onNavigationRequestCallback returns false.'); + }); + + webViewClient.requestLoading(MockWebView(), mockRequest); + }); + + test( + 'requestLoading should not call onLoadUrlCallback 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.value(false), + loadUrl: (String url, Map? 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 3385e7998ba9..78e60cac1b8e 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 @@ class MockWebView extends _i1.Mock implements _i2.WebView { returnValueForMissingStub: Future.value()) as _i4.Future); } +/// 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 get requestHeaders => + (super.noSuchMethod(Invocation.getter(#requestHeaders), + returnValue: {}) as Map); +} + /// A class which mocks [WebViewAndroidDownloadListener]. /// /// See the documentation for Mockito's code generation for more information. From c06313dcdee7d30ccf3cd08ba8ed06b343b3f119 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Wed, 15 Jun 2022 20:51:04 +0200 Subject: [PATCH 2/2] Improve expectation in unit test --- .../test/webview_android_widget_test.dart | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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 7863e4a12367..2432b35a4814 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 @@ -888,8 +888,7 @@ void main() { }); webViewClient.urlLoading(MockWebView(), 'https://flutter.dev'); - await completer.future; - expect(completer.isCompleted, isTrue); + expect(completer.future, completes); }); test( @@ -983,12 +982,11 @@ void main() { }); webViewClient.requestLoading(MockWebView(), mockRequest); - await completer.future; - expect(completer.isCompleted, isTrue); + expect(completer.future, completes); }); test( - 'requestLoading should not call onLoadUrlCallback when onNavigationRequestCallback returns false', + 'requestLoading should not call loadUrl when onNavigationRequestCallback returns false', () { final MockWebResourceRequest mockRequest = MockWebResourceRequest(); when(mockRequest.isForMainFrame).thenReturn(true); @@ -1012,7 +1010,7 @@ void main() { }); test( - 'requestLoading should not call onLoadUrlCallback when onNavigationRequestCallback returns a Future false', + 'requestLoading should not call loadUrl when onNavigationRequestCallback returns a Future false', () { final MockWebResourceRequest mockRequest = MockWebResourceRequest(); when(mockRequest.isForMainFrame).thenReturn(true);