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

[webview_flutter] Add async NavigationDelegates #2257

Merged
merged 1 commit into from
Nov 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
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
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