[webview_flutter] Add support for onPageStarted event (#2295)
* Added support for webView's 'onPageStarted' event
diff --git a/packages/webview_flutter/CHANGELOG.md b/packages/webview_flutter/CHANGELOG.md
index c90205d..1d4ce00 100644
--- a/packages/webview_flutter/CHANGELOG.md
+++ b/packages/webview_flutter/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.3.18
+
+* Add support for onPageStarted event.
+
## 0.3.17
* Fix pedantic lint errors. Added missing documentation and awaited some futures
diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java
index 37ec1c9..b660a72 100644
--- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java
+++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java
@@ -5,6 +5,7 @@
package io.flutter.plugins.webviewflutter;
import android.annotation.TargetApi;
+import android.graphics.Bitmap;
import android.os.Build;
import android.util.Log;
import android.view.KeyEvent;
@@ -66,6 +67,12 @@
return true;
}
+ private void onPageStarted(WebView view, String url) {
+ Map<String, Object> args = new HashMap<>();
+ args.put("url", url);
+ methodChannel.invokeMethod("onPageStarted", args);
+ }
+
private void onPageFinished(WebView view, String url) {
Map<String, Object> args = new HashMap<>();
args.put("url", url);
@@ -107,6 +114,11 @@
}
@Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ FlutterWebViewClient.this.onPageStarted(view, url);
+ }
+
+ @Override
public void onPageFinished(WebView view, String url) {
FlutterWebViewClient.this.onPageFinished(view, url);
}
@@ -133,6 +145,11 @@
}
@Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ FlutterWebViewClient.this.onPageStarted(view, url);
+ }
+
+ @Override
public void onPageFinished(WebView view, String url) {
FlutterWebViewClient.this.onPageFinished(view, url);
}
diff --git a/packages/webview_flutter/example/lib/main.dart b/packages/webview_flutter/example/lib/main.dart
index 4016e10..20520d1 100644
--- a/packages/webview_flutter/example/lib/main.dart
+++ b/packages/webview_flutter/example/lib/main.dart
@@ -68,6 +68,9 @@
print('allowing navigation to $request');
return NavigationDecision.navigate;
},
+ onPageStarted: (String url) {
+ print('Page started loading: $url');
+ },
onPageFinished: (String url) {
print('Page finished loading: $url');
},
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 324ab61..373e65c 100644
--- a/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart
+++ b/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart
@@ -62,6 +62,7 @@
testWidgets('loadUrl with headers', (WidgetTester tester) async {
final Completer<WebViewController> controllerCompleter =
Completer<WebViewController>();
+ final StreamController<String> pageStarts = StreamController<String>();
final StreamController<String> pageLoads = StreamController<String>();
await tester.pumpWidget(
Directionality(
@@ -73,6 +74,9 @@
controllerCompleter.complete(controller);
},
javascriptMode: JavascriptMode.unrestricted,
+ onPageStarted: (String url) {
+ pageStarts.add(url);
+ },
onPageFinished: (String url) {
pageLoads.add(url);
},
@@ -88,7 +92,9 @@
final String currentUrl = await controller.currentUrl();
expect(currentUrl, 'https://flutter-header-echo.herokuapp.com/');
+ await pageStarts.stream.firstWhere((String url) => url == currentUrl);
await pageLoads.stream.firstWhere((String url) => url == currentUrl);
+
final String content = await controller
.evaluateJavascript('document.documentElement.innerText');
expect(content.contains('flutter_test_header'), isTrue);
@@ -97,6 +103,7 @@
testWidgets('JavaScriptChannel', (WidgetTester tester) async {
final Completer<WebViewController> controllerCompleter =
Completer<WebViewController>();
+ final Completer<void> pageStarted = Completer<void>();
final Completer<void> pageLoaded = Completer<void>();
final List<String> messagesReceived = <String>[];
await tester.pumpWidget(
@@ -121,6 +128,9 @@
},
),
].toSet(),
+ onPageStarted: (String url) {
+ pageStarted.complete(null);
+ },
onPageFinished: (String url) {
pageLoaded.complete(null);
},
@@ -128,6 +138,7 @@
),
);
final WebViewController controller = await controllerCompleter.future;
+ await pageStarted.future;
await pageLoaded.future;
expect(messagesReceived, isEmpty);
@@ -155,6 +166,7 @@
final String resizeTestBase64 =
base64Encode(const Utf8Encoder().convert(resizeTest));
final Completer<void> resizeCompleter = Completer<void>();
+ final Completer<void> pageStarted = Completer<void>();
final Completer<void> pageLoaded = Completer<void>();
final Completer<WebViewController> controllerCompleter =
Completer<WebViewController>();
@@ -176,6 +188,9 @@
},
),
].toSet(),
+ onPageStarted: (String url) {
+ pageStarted.complete(null);
+ },
onPageFinished: (String url) {
pageLoaded.complete(null);
},
@@ -198,6 +213,7 @@
);
await controllerCompleter.future;
+ await pageStarted.future;
await pageLoaded.future;
expect(resizeCompleter.isCompleted, false);
@@ -343,6 +359,7 @@
testWidgets('Auto media playback', (WidgetTester tester) async {
Completer<WebViewController> controllerCompleter =
Completer<WebViewController>();
+ Completer<void> pageStarted = Completer<void>();
Completer<void> pageLoaded = Completer<void>();
await tester.pumpWidget(
@@ -355,6 +372,9 @@
controllerCompleter.complete(controller);
},
javascriptMode: JavascriptMode.unrestricted,
+ onPageStarted: (String url) {
+ pageStarted.complete(null);
+ },
onPageFinished: (String url) {
pageLoaded.complete(null);
},
@@ -363,12 +383,14 @@
),
);
WebViewController controller = await controllerCompleter.future;
+ await pageStarted.future;
await pageLoaded.future;
String isPaused = await controller.evaluateJavascript('isPaused();');
expect(isPaused, _webviewBool(false));
controllerCompleter = Completer<WebViewController>();
+ pageStarted = Completer<void>();
pageLoaded = Completer<void>();
// We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy
@@ -382,6 +404,9 @@
controllerCompleter.complete(controller);
},
javascriptMode: JavascriptMode.unrestricted,
+ onPageStarted: (String url) {
+ pageStarted.complete(null);
+ },
onPageFinished: (String url) {
pageLoaded.complete(null);
},
@@ -392,6 +417,7 @@
);
controller = await controllerCompleter.future;
+ await pageStarted.future;
await pageLoaded.future;
isPaused = await controller.evaluateJavascript('isPaused();');
@@ -402,6 +428,7 @@
(WidgetTester tester) async {
final Completer<WebViewController> controllerCompleter =
Completer<WebViewController>();
+ Completer<void> pageStarted = Completer<void>();
Completer<void> pageLoaded = Completer<void>();
final GlobalKey key = GlobalKey();
@@ -415,6 +442,9 @@
controllerCompleter.complete(controller);
},
javascriptMode: JavascriptMode.unrestricted,
+ onPageStarted: (String url) {
+ pageStarted.complete(null);
+ },
onPageFinished: (String url) {
pageLoaded.complete(null);
},
@@ -423,11 +453,13 @@
),
);
final WebViewController controller = await controllerCompleter.future;
+ await pageStarted.future;
await pageLoaded.future;
String isPaused = await controller.evaluateJavascript('isPaused();');
expect(isPaused, _webviewBool(false));
+ pageStarted = Completer<void>();
pageLoaded = Completer<void>();
await tester.pumpWidget(
@@ -440,6 +472,9 @@
controllerCompleter.complete(controller);
},
javascriptMode: JavascriptMode.unrestricted,
+ onPageStarted: (String url) {
+ pageStarted.complete(null);
+ },
onPageFinished: (String url) {
pageLoaded.complete(null);
},
@@ -451,6 +486,7 @@
await controller.reload();
+ await pageStarted.future;
await pageLoaded.future;
isPaused = await controller.evaluateJavascript('isPaused();');
@@ -469,6 +505,7 @@
''';
final String getTitleTestBase64 =
base64Encode(const Utf8Encoder().convert(getTitleTest));
+ final Completer<void> pageStarted = Completer<void>();
final Completer<void> pageLoaded = Completer<void>();
final Completer<WebViewController> controllerCompleter =
Completer<WebViewController>();
@@ -481,6 +518,9 @@
onWebViewCreated: (WebViewController controller) {
controllerCompleter.complete(controller);
},
+ onPageStarted: (String url) {
+ pageStarted.complete(null);
+ },
onPageFinished: (String url) {
pageLoaded.complete(null);
},
@@ -489,6 +529,7 @@
);
final WebViewController controller = await controllerCompleter.future;
+ await pageStarted.future;
await pageLoaded.future;
final String title = await controller.getTitle();
diff --git a/packages/webview_flutter/ios/Classes/FLTWKNavigationDelegate.m b/packages/webview_flutter/ios/Classes/FLTWKNavigationDelegate.m
index abcca0a..3e9d276 100644
--- a/packages/webview_flutter/ios/Classes/FLTWKNavigationDelegate.m
+++ b/packages/webview_flutter/ios/Classes/FLTWKNavigationDelegate.m
@@ -16,6 +16,12 @@
return self;
}
+#pragma mark - WKNavigationDelegate conformance
+
+- (void)webView:(WKWebView*)webView didStartProvisionalNavigation:(WKNavigation*)navigation {
+ [_methodChannel invokeMethod:@"onPageStarted" arguments:@{@"url" : webView.URL.absoluteString}];
+}
+
- (void)webView:(WKWebView*)webView
decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
diff --git a/packages/webview_flutter/lib/platform_interface.dart b/packages/webview_flutter/lib/platform_interface.dart
index 3646869..a3af47a 100644
--- a/packages/webview_flutter/lib/platform_interface.dart
+++ b/packages/webview_flutter/lib/platform_interface.dart
@@ -23,6 +23,9 @@
/// If true is returned the navigation is allowed, otherwise it is blocked.
FutureOr<bool> onNavigationRequest({String url, bool isForMainFrame});
+ /// Invoked by [WebViewPlatformController] when a page has started loading.
+ void onPageStarted(String url);
+
/// 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 ba5b958..ad5a81e 100644
--- a/packages/webview_flutter/lib/src/webview_method_channel.dart
+++ b/packages/webview_flutter/lib/src/webview_method_channel.dart
@@ -40,6 +40,9 @@
case 'onPageFinished':
_platformCallbacksHandler.onPageFinished(call.arguments['url']);
return null;
+ case 'onPageStarted':
+ _platformCallbacksHandler.onPageStarted(call.arguments['url']);
+ return null;
}
throw MissingPluginException(
'${call.method} was invoked but has no handler');
diff --git a/packages/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/lib/webview_flutter.dart
index c17f8b9..a57e2e1 100644
--- a/packages/webview_flutter/lib/webview_flutter.dart
+++ b/packages/webview_flutter/lib/webview_flutter.dart
@@ -73,6 +73,9 @@
typedef FutureOr<NavigationDecision> NavigationDelegate(
NavigationRequest navigation);
+/// Signature for when a [WebView] has started loading a page.
+typedef void PageStartedCallback(String url);
+
/// Signature for when a [WebView] has finished loading a page.
typedef void PageFinishedCallback(String url);
@@ -142,6 +145,7 @@
this.javascriptChannels,
this.navigationDelegate,
this.gestureRecognizers,
+ this.onPageStarted,
this.onPageFinished,
this.debuggingEnabled = false,
this.userAgent,
@@ -257,6 +261,9 @@
/// * When a navigationDelegate is set HTTP requests do not include the HTTP referer header.
final NavigationDelegate navigationDelegate;
+ /// Invoked when a page starts loading.
+ final PageStartedCallback onPageStarted;
+
/// Invoked when a page has finished loading.
///
/// This is invoked only for the main frame.
@@ -453,6 +460,13 @@
}
@override
+ void onPageStarted(String url) {
+ if (_widget.onPageStarted != null) {
+ _widget.onPageStarted(url);
+ }
+ }
+
+ @override
void onPageFinished(String url) {
if (_widget.onPageFinished != null) {
_widget.onPageFinished(url);
diff --git a/packages/webview_flutter/test/webview_flutter_test.dart b/packages/webview_flutter/test/webview_flutter_test.dart
index 728686f..1772df3 100644
--- a/packages/webview_flutter/test/webview_flutter_test.dart
+++ b/packages/webview_flutter/test/webview_flutter_test.dart
@@ -601,6 +601,63 @@
expect(ttsMessagesReceived, <String>['Hello', 'World']);
});
+ group('$PageStartedCallback', () {
+ testWidgets('onPageStarted is not null', (WidgetTester tester) async {
+ String returnedUrl;
+
+ await tester.pumpWidget(WebView(
+ initialUrl: 'https://youtube.com',
+ onPageStarted: (String url) {
+ returnedUrl = url;
+ },
+ ));
+
+ final FakePlatformWebView platformWebView =
+ fakePlatformViewsController.lastCreatedView;
+
+ platformWebView.fakeOnPageStartedCallback();
+
+ expect(platformWebView.currentUrl, returnedUrl);
+ });
+
+ testWidgets('onPageStarted is null', (WidgetTester tester) async {
+ await tester.pumpWidget(const WebView(
+ initialUrl: 'https://youtube.com',
+ onPageStarted: null,
+ ));
+
+ final FakePlatformWebView platformWebView =
+ fakePlatformViewsController.lastCreatedView;
+
+ // The platform side will always invoke a call for onPageStarted. This is
+ // to test that it does not crash on a null callback.
+ platformWebView.fakeOnPageStartedCallback();
+ });
+
+ testWidgets('onPageStarted changed', (WidgetTester tester) async {
+ String returnedUrl;
+
+ await tester.pumpWidget(WebView(
+ initialUrl: 'https://youtube.com',
+ onPageStarted: (String url) {},
+ ));
+
+ await tester.pumpWidget(WebView(
+ initialUrl: 'https://youtube.com',
+ onPageStarted: (String url) {
+ returnedUrl = url;
+ },
+ ));
+
+ final FakePlatformWebView platformWebView =
+ fakePlatformViewsController.lastCreatedView;
+
+ platformWebView.fakeOnPageStartedCallback();
+
+ expect(platformWebView.currentUrl, returnedUrl);
+ });
+ });
+
group('$PageFinishedCallback', () {
testWidgets('onPageFinished is not null', (WidgetTester tester) async {
String returnedUrl;
@@ -968,6 +1025,24 @@
});
}
+ void fakeOnPageStartedCallback() {
+ final StandardMethodCodec codec = const StandardMethodCodec();
+
+ final ByteData data = codec.encodeMethodCall(MethodCall(
+ 'onPageStarted',
+ <dynamic, dynamic>{'url': currentUrl},
+ ));
+
+ // TODO(hterkelsen): Remove this when defaultBinaryMessages is in stable.
+ // https://github.com/flutter/flutter/issues/33446
+ // ignore: deprecated_member_use
+ BinaryMessages.handlePlatformMessage(
+ channel.name,
+ data,
+ (ByteData data) {},
+ );
+ }
+
void fakeOnPageFinishedCallback() {
final StandardMethodCodec codec = const StandardMethodCodec();