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

Commit 02a610e

Browse files
committed
[webview_flutter] Send history events
Send URL change events when the user navigates in the Browser. This also works for JavaScript Navigation that does not actually make any request.
1 parent 24c042e commit 02a610e

File tree

12 files changed

+206
-1
lines changed

12 files changed

+206
-1
lines changed

packages/webview_flutter/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.3.19
2+
3+
* Add support for Navigation History observation.
4+
15
## 0.3.18
26

37
* Add support for onPageStarted event.

packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ private void notifyOnNavigationRequest(
9292
}
9393
}
9494

95+
private void onUpdateVisitedHistory(String url) {
96+
HashMap<String, Object> args = new HashMap<>();
97+
args.put("url", url);
98+
methodChannel.invokeMethod("onUpdateVisitedHistory", args);
99+
}
100+
95101
// This method attempts to avoid using WebViewClientCompat due to bug
96102
// https://bugs.chromium.org/p/chromium/issues/detail?id=925887. Also, see
97103
// https://github.com/flutter/flutter/issues/29446.
@@ -107,6 +113,12 @@ WebViewClient createWebViewClient(boolean hasNavigationDelegate) {
107113

108114
private WebViewClient internalCreateWebViewClient() {
109115
return new WebViewClient() {
116+
@Override
117+
public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
118+
FlutterWebViewClient.this.onUpdateVisitedHistory(url);
119+
super.doUpdateVisitedHistory(view, url, isReload);
120+
}
121+
110122
@TargetApi(Build.VERSION_CODES.N)
111123
@Override
112124
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
@@ -134,6 +146,12 @@ public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
134146

135147
private WebViewClientCompat internalCreateWebViewClientCompat() {
136148
return new WebViewClientCompat() {
149+
@Override
150+
public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
151+
FlutterWebViewClient.this.onUpdateVisitedHistory(url);
152+
super.doUpdateVisitedHistory(view, url, isReload);
153+
}
154+
137155
@Override
138156
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
139157
return FlutterWebViewClient.this.shouldOverrideUrlLoading(view, request);

packages/webview_flutter/example/lib/main.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ class _WebViewExampleState extends State<WebViewExample> {
7474
onPageFinished: (String url) {
7575
print('Page finished loading: $url');
7676
},
77+
onUpdateVisitedHistory: (String url) {
78+
print('Update visited history: $url');
79+
},
7780
);
7881
}),
7982
floatingActionButton: favoriteButton(),

packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,60 @@ void main() {
652652
expect(currentUrl, 'https://www.google.com/');
653653
});
654654
});
655+
656+
testWidgets('onUpdateVisitedHistory triggers when the URL changes',
657+
(WidgetTester tester) async {
658+
final String onUpdateVisitedHistoryTest = '''
659+
<!DOCTYPE html><html>
660+
<head><title>onUpdateVisitedHistory test</title>
661+
<script type="text/javascript">
662+
function changeHash() {
663+
window.location.href = 'https://www.google.com/';
664+
}
665+
</script>
666+
</head>
667+
<body onload="changeHash();" bgColor="blue">
668+
</body>
669+
</html>
670+
''';
671+
final String onUpdateVisitedHistoryTestBase64 =
672+
base64Encode(const Utf8Encoder().convert(onUpdateVisitedHistoryTest));
673+
final Completer<String> onUpdateVisitedHistoryCompleter =
674+
Completer<String>();
675+
final GlobalKey key = GlobalKey();
676+
677+
final String initialUrl =
678+
'data:text/html;charset=utf-8;base64,$onUpdateVisitedHistoryTestBase64';
679+
680+
final WebView webView = WebView(
681+
key: key,
682+
initialUrl: initialUrl,
683+
javascriptMode: JavascriptMode.unrestricted,
684+
onUpdateVisitedHistory: (String newUrl) {
685+
if (newUrl == initialUrl) {
686+
return;
687+
}
688+
onUpdateVisitedHistoryCompleter.complete(newUrl);
689+
});
690+
691+
await tester.pumpWidget(
692+
Directionality(
693+
textDirection: TextDirection.ltr,
694+
child: Column(
695+
children: <Widget>[
696+
SizedBox(
697+
width: 200,
698+
height: 200,
699+
child: webView,
700+
),
701+
],
702+
),
703+
),
704+
);
705+
706+
final String nextUrl = await onUpdateVisitedHistoryCompleter.future;
707+
expect(nextUrl, 'https://www.google.com/');
708+
});
655709
}
656710

657711
// JavaScript booleans evaluate to different string values on Android and iOS.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2019 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#import <Flutter/Flutter.h>
6+
#import <Foundation/Foundation.h>
7+
#import <WebKit/WebKit.h>
8+
9+
NS_ASSUME_NONNULL_BEGIN
10+
11+
@interface FLTWKHistoryDelegate : NSObject
12+
- (instancetype)initWithWebView:(WKWebView *)webView channel:(FlutterMethodChannel *)channel;
13+
- (void)stopObserving:(WKWebView *)webView;
14+
@end
15+
16+
NS_ASSUME_NONNULL_END
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2019 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#import "FLTWKHistoryDelegate.h"
6+
7+
NSString *const keyPath = @"URL";
8+
9+
@implementation FLTWKHistoryDelegate {
10+
FlutterMethodChannel *_methodChannel;
11+
}
12+
13+
- (instancetype)initWithWebView:(id)webView channel:(FlutterMethodChannel *)channel {
14+
self = [super init];
15+
if (self) {
16+
_methodChannel = channel;
17+
[webView addObserver:self
18+
forKeyPath:keyPath
19+
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
20+
context:nil];
21+
}
22+
23+
return self;
24+
}
25+
26+
- (void)stopObserving:(WKWebView *)webView {
27+
[webView removeObserver:self forKeyPath:keyPath];
28+
}
29+
30+
- (void)observeValueForKeyPath:(NSString *)keyPath
31+
ofObject:(id)object
32+
change:(NSDictionary *)change
33+
context:(void *)context {
34+
if ([keyPath isEqualToString:keyPath] &&
35+
[change[NSKeyValueChangeNewKey] isKindOfClass:[NSURL class]]) {
36+
NSURL *newUrl = change[NSKeyValueChangeNewKey];
37+
[_methodChannel invokeMethod:@"onUpdateVisitedHistory"
38+
arguments:@{@"url" : [newUrl absoluteString]}];
39+
}
40+
}
41+
42+
@end

packages/webview_flutter/ios/Classes/FlutterWebView.m

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
#import "FlutterWebView.h"
6+
#import "FLTWKHistoryDelegate.h"
67
#import "FLTWKNavigationDelegate.h"
78
#import "JavaScriptChannelHandler.h"
89

@@ -42,6 +43,7 @@ @implementation FLTWebViewController {
4243
// The set of registered JavaScript channel names.
4344
NSMutableSet* _javaScriptChannelNames;
4445
FLTWKNavigationDelegate* _navigationDelegate;
46+
FLTWKHistoryDelegate* _historyDelegate;
4547
}
4648

4749
- (instancetype)initWithFrame:(CGRect)frame
@@ -72,6 +74,7 @@ - (instancetype)initWithFrame:(CGRect)frame
7274
_webView = [[WKWebView alloc] initWithFrame:frame configuration:configuration];
7375
_navigationDelegate = [[FLTWKNavigationDelegate alloc] initWithChannel:_channel];
7476
_webView.navigationDelegate = _navigationDelegate;
77+
_historyDelegate = [[FLTWKHistoryDelegate alloc] initWithWebView:_webView channel:_channel];
7578
__weak __typeof__(self) weakSelf = self;
7679
[_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
7780
[weakSelf onMethodCall:call result:result];
@@ -89,6 +92,12 @@ - (instancetype)initWithFrame:(CGRect)frame
8992
return self;
9093
}
9194

95+
- (void)dealloc {
96+
if (_historyDelegate != nil) {
97+
[_historyDelegate stopObserving:_webView];
98+
}
99+
}
100+
92101
- (UIView*)view {
93102
return _webView;
94103
}

packages/webview_flutter/lib/platform_interface.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ abstract class WebViewPlatformCallbacksHandler {
2828

2929
/// Invoked by [WebViewPlatformController] when a page has finished loading.
3030
void onPageFinished(String url);
31+
32+
/// Invoked by [WebViewPlatformController] when the url has changed.
33+
void onUpdateVisitedHistory(String url);
3134
}
3235

3336
/// Interface for talking to the webview's platform implementation.

packages/webview_flutter/lib/src/webview_method_channel.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController {
4343
case 'onPageStarted':
4444
_platformCallbacksHandler.onPageStarted(call.arguments['url']);
4545
return null;
46+
case 'onUpdateVisitedHistory':
47+
_platformCallbacksHandler.onUpdateVisitedHistory(call.arguments['url']);
48+
return null;
4649
}
4750
throw MissingPluginException(
4851
'${call.method} was invoked but has no handler');

packages/webview_flutter/lib/webview_flutter.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ typedef void PageStartedCallback(String url);
7979
/// Signature for when a [WebView] has finished loading a page.
8080
typedef void PageFinishedCallback(String url);
8181

82+
/// Signature for when an Android [WebView] had a history update.
83+
typedef void PageUpdateVisitedHistoryCallback(String url);
84+
8285
/// Specifies possible restrictions on automatic media playback.
8386
///
8487
/// This is typically used in [WebView.initialMediaPlaybackPolicy].
@@ -147,6 +150,7 @@ class WebView extends StatefulWidget {
147150
this.gestureRecognizers,
148151
this.onPageStarted,
149152
this.onPageFinished,
153+
this.onUpdateVisitedHistory,
150154
this.debuggingEnabled = false,
151155
this.userAgent,
152156
this.initialMediaPlaybackPolicy =
@@ -276,6 +280,14 @@ class WebView extends StatefulWidget {
276280
/// [WebViewController.evaluateJavascript] can assume this.
277281
final PageFinishedCallback onPageFinished;
278282

283+
/// Invoked when the URL in the browser changed.
284+
///
285+
/// While it is possible to see all URL changes in the [navigationDelegate]
286+
/// in iOS. This is not the case for Android when JavaScript updates the URL.
287+
/// So when you need an update as soon as the browser is on a different URL,
288+
/// use this callback.
289+
final PageUpdateVisitedHistoryCallback onUpdateVisitedHistory;
290+
279291
/// Controls whether WebView debugging is enabled.
280292
///
281293
/// Setting this to true enables [WebView debugging on Android](https://developers.google.com/web/tools/chrome-devtools/remote-debugging/).
@@ -473,6 +485,13 @@ class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler {
473485
}
474486
}
475487

488+
@override
489+
void onUpdateVisitedHistory(String url) {
490+
if (_widget.onUpdateVisitedHistory != null) {
491+
_widget.onUpdateVisitedHistory(url);
492+
}
493+
}
494+
476495
void _updateJavascriptChannelsFromSet(Set<JavascriptChannel> channels) {
477496
_javascriptChannels.clear();
478497
if (channels == null) {

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.18
3+
version: 0.3.19
44
homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter
55

66
environment:

packages/webview_flutter/test/webview_flutter_test.dart

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,22 @@ void main() {
887887

888888
expect(platformWebView.userAgent, 'UA');
889889
});
890+
testWidgets('onUpdateVisitedHistory', (WidgetTester tester) async {
891+
String visitedUrl;
892+
893+
await tester.pumpWidget(WebView(
894+
initialUrl: 'https://youtube.com',
895+
onUpdateVisitedHistory: (String url) {
896+
visitedUrl = url;
897+
}));
898+
899+
final FakePlatformWebView platformWebView =
900+
fakePlatformViewsController.lastCreatedView;
901+
902+
platformWebView.fakeOnUpdateVisitedHistoryCallback('https://google.com');
903+
904+
expect(visitedUrl, 'https://google.com');
905+
});
890906
}
891907

892908
class FakePlatformWebView {
@@ -1061,6 +1077,24 @@ class FakePlatformWebView {
10611077
);
10621078
}
10631079

1080+
void fakeOnUpdateVisitedHistoryCallback(String nextUrl) {
1081+
final StandardMethodCodec codec = const StandardMethodCodec();
1082+
1083+
final ByteData data = codec.encodeMethodCall(MethodCall(
1084+
'onUpdateVisitedHistory',
1085+
<dynamic, dynamic>{'url': nextUrl},
1086+
));
1087+
1088+
// TODO(hterkelsen): Remove this when defaultBinaryMessages is in stable.
1089+
// https://github.com/flutter/flutter/issues/33446
1090+
// ignore: deprecated_member_use
1091+
BinaryMessages.handlePlatformMessage(
1092+
channel.name,
1093+
data,
1094+
(ByteData data) {},
1095+
);
1096+
}
1097+
10641098
void _loadUrl(String url) {
10651099
history = history.sublist(0, currentPosition + 1);
10661100
history.add(url);

0 commit comments

Comments
 (0)