diff --git a/packages/webview_flutter/CHANGELOG.md b/packages/webview_flutter/CHANGELOG.md index 17a4eb0b0916..e577b30f53af 100644 --- a/packages/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.3.16 + +* Add support for async NavigationDelegates. Synchronous NavigationDelegates + should still continue to function without any change in behavior. + ## 0.3.15+3 * Re-land support for the v2 Android embedding. This correctly sets the minimum diff --git a/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart b/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart index a5d4d7de66fe..324ab6140de5 100644 --- a/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart +++ b/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart @@ -494,6 +494,123 @@ void main() { final String title = await controller.getTitle(); expect(title, 'Some title'); }); + + group('NavigationDelegate', () { + final String blankPage = ""; + final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' + + base64Encode(const Utf8Encoder().convert(blankPage)); + + testWidgets('can allow requests', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageLoads = + StreamController.broadcast(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: blankPageEncoded, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + navigationDelegate: (NavigationRequest request) { + return (request.url.contains('youtube.com')) + ? NavigationDecision.prevent + : NavigationDecision.navigate; + }, + onPageFinished: (String url) => pageLoads.add(url), + ), + ), + ); + + await pageLoads.stream.first; // Wait for initial page load. + final WebViewController controller = await controllerCompleter.future; + await controller + .evaluateJavascript('location.href = "https://www.google.com/"'); + + await pageLoads.stream.first; // Wait for the next page load. + final String currentUrl = await controller.currentUrl(); + expect(currentUrl, 'https://www.google.com/'); + }); + + testWidgets('can block requests', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageLoads = + StreamController.broadcast(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: blankPageEncoded, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + navigationDelegate: (NavigationRequest request) { + return (request.url.contains('youtube.com')) + ? NavigationDecision.prevent + : NavigationDecision.navigate; + }, + onPageFinished: (String url) => pageLoads.add(url), + ), + ), + ); + + await pageLoads.stream.first; // Wait for initial page load. + final WebViewController controller = await controllerCompleter.future; + await controller + .evaluateJavascript('location.href = "https://www.youtube.com/"'); + + // There should never be any second page load, since our new URL is + // blocked. Still wait for a potential page change for some time in order + // to give the test a chance to fail. + await pageLoads.stream.first + .timeout(const Duration(milliseconds: 500), onTimeout: () => null); + final String currentUrl = await controller.currentUrl(); + expect(currentUrl, isNot(contains('youtube.com'))); + }); + + testWidgets('supports asynchronous decisions', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageLoads = + StreamController.broadcast(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: blankPageEncoded, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + navigationDelegate: (NavigationRequest request) async { + NavigationDecision decision = NavigationDecision.prevent; + decision = await Future.delayed( + const Duration(milliseconds: 10), + () => NavigationDecision.navigate); + return decision; + }, + onPageFinished: (String url) => pageLoads.add(url), + ), + ), + ); + + await pageLoads.stream.first; // Wait for initial page load. + final WebViewController controller = await controllerCompleter.future; + await controller + .evaluateJavascript('location.href = "https://www.google.com"'); + + await pageLoads.stream.first; // Wait for second page to load. + final String currentUrl = await controller.currentUrl(); + expect(currentUrl, 'https://www.google.com/'); + }); + }); } // JavaScript booleans evaluate to different string values on Android and iOS. diff --git a/packages/webview_flutter/lib/platform_interface.dart b/packages/webview_flutter/lib/platform_interface.dart index 7e82bae91138..cf6a2fd933f7 100644 --- a/packages/webview_flutter/lib/platform_interface.dart +++ b/packages/webview_flutter/lib/platform_interface.dart @@ -21,7 +21,7 @@ abstract class WebViewPlatformCallbacksHandler { /// Invoked by [WebViewPlatformController] when a navigation request is pending. /// /// If true is returned the navigation is allowed, otherwise it is blocked. - bool onNavigationRequest({String url, bool isForMainFrame}); + FutureOr onNavigationRequest({String url, bool isForMainFrame}); /// Invoked by [WebViewPlatformController] when a page has finished loading. void onPageFinished(String url); diff --git a/packages/webview_flutter/lib/src/webview_method_channel.dart b/packages/webview_flutter/lib/src/webview_method_channel.dart index c2949cc77a2a..2bb7470a1145 100644 --- a/packages/webview_flutter/lib/src/webview_method_channel.dart +++ b/packages/webview_flutter/lib/src/webview_method_channel.dart @@ -31,7 +31,7 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController { _platformCallbacksHandler.onJavaScriptChannelMessage(channel, message); return true; case 'navigationRequest': - return _platformCallbacksHandler.onNavigationRequest( + return await _platformCallbacksHandler.onNavigationRequest( url: call.arguments['url'], isForMainFrame: call.arguments['isForMainFrame'], ); diff --git a/packages/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/lib/webview_flutter.dart index cd5ca46701d7..11541d322dfb 100644 --- a/packages/webview_flutter/lib/webview_flutter.dart +++ b/packages/webview_flutter/lib/webview_flutter.dart @@ -67,7 +67,8 @@ enum NavigationDecision { /// `navigation` should be handled. /// /// See also: [WebView.navigationDelegate]. -typedef NavigationDecision NavigationDelegate(NavigationRequest navigation); +typedef FutureOr NavigationDelegate( + NavigationRequest navigation); /// Signature for when a [WebView] has finished loading a page. typedef void PageFinishedCallback(String url); @@ -439,11 +440,12 @@ class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { } @override - bool onNavigationRequest({String url, bool isForMainFrame}) { + FutureOr onNavigationRequest({String url, bool isForMainFrame}) async { final NavigationRequest request = NavigationRequest._(url: url, isForMainFrame: isForMainFrame); final bool allowNavigation = _widget.navigationDelegate == null || - _widget.navigationDelegate(request) == NavigationDecision.navigate; + await _widget.navigationDelegate(request) == + NavigationDecision.navigate; return allowNavigation; } diff --git a/packages/webview_flutter/pubspec.yaml b/packages/webview_flutter/pubspec.yaml index 4d4df2676f03..4883f049f759 100644 --- a/packages/webview_flutter/pubspec.yaml +++ b/packages/webview_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: webview_flutter description: A Flutter plugin that provides a WebView widget on Android and iOS. -version: 0.3.15+3 +version: 0.3.16 author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter