Skip to content

Commit

Permalink
[webview_flutter] Add async NavigationDelegates (flutter#2257)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Michael Klimushyn authored and Park Sung Min committed Dec 17, 2019
1 parent c145851 commit d096ad4
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 6 deletions.
5 changes: 5 additions & 0 deletions packages/webview_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
117 changes: 117 additions & 0 deletions packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,123 @@ void main() {
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.
Expand Down
2 changes: 1 addition & 1 deletion packages/webview_flutter/lib/platform_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool> onNavigationRequest({String url, bool isForMainFrame});

/// Invoked by [WebViewPlatformController] when a page has finished loading.
void onPageFinished(String url);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
);
Expand Down
8 changes: 5 additions & 3 deletions packages/webview_flutter/lib/webview_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ enum NavigationDecision {
/// `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);
Expand Down Expand Up @@ -439,11 +440,12 @@ class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler {
}

@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;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/webview_flutter/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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

Expand Down

0 comments on commit d096ad4

Please sign in to comment.