Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit d22502a

Browse files
author
Michael Klimushyn
authored
[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.
1 parent 4a94503 commit d22502a

File tree

6 files changed

+130
-6
lines changed

6 files changed

+130
-6
lines changed

packages/webview_flutter/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 0.3.16
2+
3+
* Add support for async NavigationDelegates. Synchronous NavigationDelegates
4+
should still continue to function without any change in behavior.
5+
16
## 0.3.15+3
27

38
* Re-land support for the v2 Android embedding. This correctly sets the minimum

packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,123 @@ void main() {
494494
final String title = await controller.getTitle();
495495
expect(title, 'Some title');
496496
});
497+
498+
group('NavigationDelegate', () {
499+
final String blankPage = "<!DOCTYPE html><head></head><body></body></html>";
500+
final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' +
501+
base64Encode(const Utf8Encoder().convert(blankPage));
502+
503+
testWidgets('can allow requests', (WidgetTester tester) async {
504+
final Completer<WebViewController> controllerCompleter =
505+
Completer<WebViewController>();
506+
final StreamController<String> pageLoads =
507+
StreamController<String>.broadcast();
508+
await tester.pumpWidget(
509+
Directionality(
510+
textDirection: TextDirection.ltr,
511+
child: WebView(
512+
key: GlobalKey(),
513+
initialUrl: blankPageEncoded,
514+
onWebViewCreated: (WebViewController controller) {
515+
controllerCompleter.complete(controller);
516+
},
517+
javascriptMode: JavascriptMode.unrestricted,
518+
navigationDelegate: (NavigationRequest request) {
519+
return (request.url.contains('youtube.com'))
520+
? NavigationDecision.prevent
521+
: NavigationDecision.navigate;
522+
},
523+
onPageFinished: (String url) => pageLoads.add(url),
524+
),
525+
),
526+
);
527+
528+
await pageLoads.stream.first; // Wait for initial page load.
529+
final WebViewController controller = await controllerCompleter.future;
530+
await controller
531+
.evaluateJavascript('location.href = "https://www.google.com/"');
532+
533+
await pageLoads.stream.first; // Wait for the next page load.
534+
final String currentUrl = await controller.currentUrl();
535+
expect(currentUrl, 'https://www.google.com/');
536+
});
537+
538+
testWidgets('can block requests', (WidgetTester tester) async {
539+
final Completer<WebViewController> controllerCompleter =
540+
Completer<WebViewController>();
541+
final StreamController<String> pageLoads =
542+
StreamController<String>.broadcast();
543+
await tester.pumpWidget(
544+
Directionality(
545+
textDirection: TextDirection.ltr,
546+
child: WebView(
547+
key: GlobalKey(),
548+
initialUrl: blankPageEncoded,
549+
onWebViewCreated: (WebViewController controller) {
550+
controllerCompleter.complete(controller);
551+
},
552+
javascriptMode: JavascriptMode.unrestricted,
553+
navigationDelegate: (NavigationRequest request) {
554+
return (request.url.contains('youtube.com'))
555+
? NavigationDecision.prevent
556+
: NavigationDecision.navigate;
557+
},
558+
onPageFinished: (String url) => pageLoads.add(url),
559+
),
560+
),
561+
);
562+
563+
await pageLoads.stream.first; // Wait for initial page load.
564+
final WebViewController controller = await controllerCompleter.future;
565+
await controller
566+
.evaluateJavascript('location.href = "https://www.youtube.com/"');
567+
568+
// There should never be any second page load, since our new URL is
569+
// blocked. Still wait for a potential page change for some time in order
570+
// to give the test a chance to fail.
571+
await pageLoads.stream.first
572+
.timeout(const Duration(milliseconds: 500), onTimeout: () => null);
573+
final String currentUrl = await controller.currentUrl();
574+
expect(currentUrl, isNot(contains('youtube.com')));
575+
});
576+
577+
testWidgets('supports asynchronous decisions', (WidgetTester tester) async {
578+
final Completer<WebViewController> controllerCompleter =
579+
Completer<WebViewController>();
580+
final StreamController<String> pageLoads =
581+
StreamController<String>.broadcast();
582+
await tester.pumpWidget(
583+
Directionality(
584+
textDirection: TextDirection.ltr,
585+
child: WebView(
586+
key: GlobalKey(),
587+
initialUrl: blankPageEncoded,
588+
onWebViewCreated: (WebViewController controller) {
589+
controllerCompleter.complete(controller);
590+
},
591+
javascriptMode: JavascriptMode.unrestricted,
592+
navigationDelegate: (NavigationRequest request) async {
593+
NavigationDecision decision = NavigationDecision.prevent;
594+
decision = await Future<NavigationDecision>.delayed(
595+
const Duration(milliseconds: 10),
596+
() => NavigationDecision.navigate);
597+
return decision;
598+
},
599+
onPageFinished: (String url) => pageLoads.add(url),
600+
),
601+
),
602+
);
603+
604+
await pageLoads.stream.first; // Wait for initial page load.
605+
final WebViewController controller = await controllerCompleter.future;
606+
await controller
607+
.evaluateJavascript('location.href = "https://www.google.com"');
608+
609+
await pageLoads.stream.first; // Wait for second page to load.
610+
final String currentUrl = await controller.currentUrl();
611+
expect(currentUrl, 'https://www.google.com/');
612+
});
613+
});
497614
}
498615

499616
// JavaScript booleans evaluate to different string values on Android and iOS.

packages/webview_flutter/lib/platform_interface.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ abstract class WebViewPlatformCallbacksHandler {
2121
/// Invoked by [WebViewPlatformController] when a navigation request is pending.
2222
///
2323
/// If true is returned the navigation is allowed, otherwise it is blocked.
24-
bool onNavigationRequest({String url, bool isForMainFrame});
24+
FutureOr<bool> onNavigationRequest({String url, bool isForMainFrame});
2525

2626
/// Invoked by [WebViewPlatformController] when a page has finished loading.
2727
void onPageFinished(String url);

packages/webview_flutter/lib/src/webview_method_channel.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController {
3131
_platformCallbacksHandler.onJavaScriptChannelMessage(channel, message);
3232
return true;
3333
case 'navigationRequest':
34-
return _platformCallbacksHandler.onNavigationRequest(
34+
return await _platformCallbacksHandler.onNavigationRequest(
3535
url: call.arguments['url'],
3636
isForMainFrame: call.arguments['isForMainFrame'],
3737
);

packages/webview_flutter/lib/webview_flutter.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ enum NavigationDecision {
6767
/// `navigation` should be handled.
6868
///
6969
/// See also: [WebView.navigationDelegate].
70-
typedef NavigationDecision NavigationDelegate(NavigationRequest navigation);
70+
typedef FutureOr<NavigationDecision> NavigationDelegate(
71+
NavigationRequest navigation);
7172

7273
/// Signature for when a [WebView] has finished loading a page.
7374
typedef void PageFinishedCallback(String url);
@@ -439,11 +440,12 @@ class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler {
439440
}
440441

441442
@override
442-
bool onNavigationRequest({String url, bool isForMainFrame}) {
443+
FutureOr<bool> onNavigationRequest({String url, bool isForMainFrame}) async {
443444
final NavigationRequest request =
444445
NavigationRequest._(url: url, isForMainFrame: isForMainFrame);
445446
final bool allowNavigation = _widget.navigationDelegate == null ||
446-
_widget.navigationDelegate(request) == NavigationDecision.navigate;
447+
await _widget.navigationDelegate(request) ==
448+
NavigationDecision.navigate;
447449
return allowNavigation;
448450
}
449451

packages/webview_flutter/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: webview_flutter
22
description: A Flutter plugin that provides a WebView widget on Android and iOS.
3-
version: 0.3.15+3
3+
version: 0.3.16
44
author: Flutter Team <[email protected]>
55
homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter
66

0 commit comments

Comments
 (0)