[webview_flutter] Add async NavigationDelegates (#2257)
Previously all navigation decisions needed to be made synchronously in
Dart. The platform layers are already waiting for the MethodChannel
callback, so just changed the return type to be
`FutureOr<NavigationDecision>'. This is a change to the method signature
of `WebViewPlatformCallbacksHandler#onNavigationRequest`, but that
interface appears to only be meant to be implemented internally to the
plugin.
diff --git a/packages/webview_flutter/CHANGELOG.md b/packages/webview_flutter/CHANGELOG.md
index 17a4eb0..e577b30 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 a5d4d7d..324ab61 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 @@
final String title = await controller.getTitle();
expect(title, 'Some title');
});
+
+ group('NavigationDelegate', () {
+ final String blankPage = "<!DOCTYPE html><head></head><body></body></html>";
+ final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' +
+ base64Encode(const Utf8Encoder().convert(blankPage));
+
+ testWidgets('can allow requests', (WidgetTester tester) async {
+ final Completer<WebViewController> controllerCompleter =
+ Completer<WebViewController>();
+ final StreamController<String> pageLoads =
+ StreamController<String>.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<WebViewController> controllerCompleter =
+ Completer<WebViewController>();
+ final StreamController<String> pageLoads =
+ StreamController<String>.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<WebViewController> controllerCompleter =
+ Completer<WebViewController>();
+ final StreamController<String> pageLoads =
+ StreamController<String>.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<NavigationDecision>.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 7e82bae..cf6a2fd 100644
--- a/packages/webview_flutter/lib/platform_interface.dart
+++ b/packages/webview_flutter/lib/platform_interface.dart
@@ -21,7 +21,7 @@
/// 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<bool> 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 c2949cc..2bb7470 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 @@
_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 cd5ca46..11541d3 100644
--- a/packages/webview_flutter/lib/webview_flutter.dart
+++ b/packages/webview_flutter/lib/webview_flutter.dart
@@ -67,7 +67,8 @@
/// `navigation` should be handled.
///
/// See also: [WebView.navigationDelegate].
-typedef NavigationDecision NavigationDelegate(NavigationRequest navigation);
+typedef FutureOr<NavigationDecision> NavigationDelegate(
+ NavigationRequest navigation);
/// Signature for when a [WebView] has finished loading a page.
typedef void PageFinishedCallback(String url);
@@ -439,11 +440,12 @@
}
@override
- bool onNavigationRequest({String url, bool isForMainFrame}) {
+ FutureOr<bool> 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 4d4df26..4883f04 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 <flutter-dev@googlegroups.com>
homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter