Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
[webview_flutter] Add async NavigationDelegates (flutter#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.
  • Loading branch information
Michael Klimushyn authored Nov 14, 2019
commit d22502af8f0f9fde5f8cefee95cea5a9bc9bd094
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 <[email protected]>
homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter

Expand Down