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

[webview_flutter] Send history events #2379

Closed
wants to merge 1 commit into from
Closed
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
4 changes: 4 additions & 0 deletions packages/webview_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.0.3

* Add support for Navigation History observation.

## 1.0.2

* Android Code Inspection and Clean up.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,12 @@ private void notifyOnNavigationRequest(
}
}

private void onUpdateVisitedHistory(String url) {
HashMap<String, Object> args = new HashMap<>();
args.put("url", url);
methodChannel.invokeMethod("onUpdateVisitedHistory", args);
}

// This method attempts to avoid using WebViewClientCompat due to bug
// https://bugs.chromium.org/p/chromium/issues/detail?id=925887. Also, see
// https://github.com/flutter/flutter/issues/29446.
Expand All @@ -163,6 +169,12 @@ WebViewClient createWebViewClient(boolean hasNavigationDelegate) {

private WebViewClient internalCreateWebViewClient() {
return new WebViewClient() {
@Override
public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
FlutterWebViewClient.this.onUpdateVisitedHistory(url);
super.doUpdateVisitedHistory(view, url, isReload);
}

@TargetApi(Build.VERSION_CODES.N)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
Expand Down Expand Up @@ -204,6 +216,12 @@ public void onUnhandledKeyEvent(WebView view, KeyEvent event) {

private WebViewClientCompat internalCreateWebViewClientCompat() {
return new WebViewClientCompat() {
@Override
public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
FlutterWebViewClient.this.onUpdateVisitedHistory(url);
super.doUpdateVisitedHistory(view, url, isReload);
}

@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
return FlutterWebViewClient.this.shouldOverrideUrlLoading(view, request);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1008,6 +1008,60 @@ void main() {
},
skip: !Platform.isAndroid,
);

testWidgets('onUpdateVisitedHistory triggers when the URL changes',
(WidgetTester tester) async {
final String onUpdateVisitedHistoryTest = '''
<!DOCTYPE html><html>
<head><title>onUpdateVisitedHistory test</title>
<script type="text/javascript">
function changeHash() {
window.location.href = 'https://www.google.com/';
}
</script>
</head>
<body onload="changeHash();" bgColor="blue">
</body>
</html>
''';
final String onUpdateVisitedHistoryTestBase64 =
base64Encode(const Utf8Encoder().convert(onUpdateVisitedHistoryTest));
final Completer<String> onUpdateVisitedHistoryCompleter =
Completer<String>();
final GlobalKey key = GlobalKey();

final String initialUrl =
'data:text/html;charset=utf-8;base64,$onUpdateVisitedHistoryTestBase64';

final WebView webView = WebView(
key: key,
initialUrl: initialUrl,
javascriptMode: JavascriptMode.unrestricted,
onUpdateVisitedHistory: (String newUrl) {
if (newUrl == initialUrl) {
return;
}
onUpdateVisitedHistoryCompleter.complete(newUrl);
});

await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Column(
children: <Widget>[
SizedBox(
width: 200,
height: 200,
child: webView,
),
],
),
),
);

final String nextUrl = await onUpdateVisitedHistoryCompleter.future;
expect(nextUrl, 'https://www.google.com/');
});
}

// JavaScript booleans evaluate to different string values on Android and iOS.
Expand Down
3 changes: 3 additions & 0 deletions packages/webview_flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ class _WebViewExampleState extends State<WebViewExample> {
print('Page finished loading: $url');
},
gestureNavigationEnabled: true,
onUpdateVisitedHistory: (String url) {
print('Update visited history: $url');
},
);
}),
floatingActionButton: favoriteButton(),
Expand Down
16 changes: 16 additions & 0 deletions packages/webview_flutter/ios/Classes/FLTWKHistoryDelegate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import <Flutter/Flutter.h>
#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface FLTWKHistoryDelegate : NSObject
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class and its methods need declaration comments, per Google style.

- (instancetype)initWithWebView:(WKWebView *)webView channel:(FlutterMethodChannel *)channel;
- (void)stopObserving:(WKWebView *)webView;
@end

NS_ASSUME_NONNULL_END
41 changes: 41 additions & 0 deletions packages/webview_flutter/ios/Classes/FLTWKHistoryDelegate.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "FLTWKHistoryDelegate.h"

NSString *const keyPath = @"URL";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this should be static


@implementation FLTWKHistoryDelegate {
FlutterMethodChannel *_methodChannel;
}

- (instancetype)initWithWebView:(id)webView channel:(FlutterMethodChannel *)channel {
self = [super init];
if (self) {
_methodChannel = channel;
[webView addObserver:self
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this feature based on KVO rather than WKNavigationDelegate?

forKeyPath:keyPath
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:nil];
}

return self;
}

- (void)stopObserving:(WKWebView *)webView {
[webView removeObserver:self forKeyPath:keyPath];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([change[NSKeyValueChangeNewKey] isKindOfClass:[NSURL class]]) {
NSURL *newUrl = change[NSKeyValueChangeNewKey];
[_methodChannel invokeMethod:@"onUpdateVisitedHistory"
arguments:@{@"url" : [newUrl absoluteString]}];
}
}

@end
7 changes: 7 additions & 0 deletions packages/webview_flutter/ios/Classes/FlutterWebView.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

#import "FlutterWebView.h"
#import "FLTWKHistoryDelegate.h"
#import "FLTWKNavigationDelegate.h"
#import "JavaScriptChannelHandler.h"

Expand Down Expand Up @@ -64,6 +65,7 @@ @implementation FLTWebViewController {
// The set of registered JavaScript channel names.
NSMutableSet* _javaScriptChannelNames;
FLTWKNavigationDelegate* _navigationDelegate;
FLTWKHistoryDelegate* _historyDelegate;
}

- (instancetype)initWithFrame:(CGRect)frame
Expand Down Expand Up @@ -95,6 +97,7 @@ - (instancetype)initWithFrame:(CGRect)frame
_navigationDelegate = [[FLTWKNavigationDelegate alloc] initWithChannel:_channel];
_webView.UIDelegate = self;
_webView.navigationDelegate = _navigationDelegate;
_historyDelegate = [[FLTWKHistoryDelegate alloc] initWithWebView:_webView channel:_channel];
__weak __typeof__(self) weakSelf = self;
[_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
[weakSelf onMethodCall:call result:result];
Expand All @@ -119,6 +122,10 @@ - (instancetype)initWithFrame:(CGRect)frame
return self;
}

- (void)dealloc {
[_historyDelegate stopObserving:_webView];
}

- (UIView*)view {
return _webView;
}
Expand Down
3 changes: 3 additions & 0 deletions packages/webview_flutter/lib/platform_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ abstract class WebViewPlatformCallbacksHandler {

/// Report web resource loading error to the host application.
void onWebResourceError(WebResourceError error);

/// Invoked by [WebViewPlatformController] when the URL has changed.
void onUpdateVisitedHistory(String url);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming here seems very Android-centric; is there a reason not to call it something more generic like onUrlChanged?

}

/// Possible error type categorizations used by [WebResourceError].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController {
),
);
return null;
case 'onUpdateVisitedHistory':
_platformCallbacksHandler.onUpdateVisitedHistory(call.arguments['url']);
return null;
}

throw MissingPluginException(
Expand Down
17 changes: 17 additions & 0 deletions packages/webview_flutter/lib/webview_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ typedef void PageFinishedCallback(String url);
/// Signature for when a [WebView] has failed to load a resource.
typedef void WebResourceErrorCallback(WebResourceError error);

/// Signature for when a [WebView] had a history update.
typedef void PageUpdateVisitedHistoryCallback(String url);

/// Specifies possible restrictions on automatic media playback.
///
/// This is typically used in [WebView.initialMediaPlaybackPolicy].
Expand Down Expand Up @@ -214,6 +217,7 @@ class WebView extends StatefulWidget {
this.onPageStarted,
this.onPageFinished,
this.onWebResourceError,
this.onUpdateVisitedHistory,
this.debuggingEnabled = false,
this.gestureNavigationEnabled = false,
this.userAgent,
Expand Down Expand Up @@ -350,6 +354,12 @@ class WebView extends StatefulWidget {
/// the main page.
final WebResourceErrorCallback onWebResourceError;

/// Invoked when the URL in the browser changed.
///
/// This is always triggered when the URL changes. Also when this is done via
/// JavaScript and no [onPageStarted] or [onPageFinished] events would be fired.
final PageUpdateVisitedHistoryCallback onUpdateVisitedHistory;

/// Controls whether WebView debugging is enabled.
///
/// Setting this to true enables [WebView debugging on Android](https://developers.google.com/web/tools/chrome-devtools/remote-debugging/).
Expand Down Expand Up @@ -560,6 +570,13 @@ class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler {
}
}

@override
void onUpdateVisitedHistory(String url) {
if (_widget.onUpdateVisitedHistory != null) {
_widget.onUpdateVisitedHistory(url);
}
}

void _updateJavascriptChannelsFromSet(Set<JavascriptChannel> channels) {
_javascriptChannels.clear();
if (channels == null) {
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: 1.0.2
version: 1.0.3
homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter

environment:
Expand Down
34 changes: 34 additions & 0 deletions packages/webview_flutter/test/webview_flutter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,22 @@ void main() {

expect(platformWebView.userAgent, 'UA');
});
testWidgets('onUpdateVisitedHistory', (WidgetTester tester) async {
String visitedUrl;

await tester.pumpWidget(WebView(
initialUrl: 'https://youtube.com',
onUpdateVisitedHistory: (String url) {
visitedUrl = url;
}));

final FakePlatformWebView platformWebView =
fakePlatformViewsController.lastCreatedView;

platformWebView.fakeOnUpdateVisitedHistoryCallback('https://google.com');

expect(visitedUrl, 'https://google.com');
});
}

class FakePlatformWebView {
Expand Down Expand Up @@ -1052,6 +1068,24 @@ class FakePlatformWebView {
);
}

void fakeOnUpdateVisitedHistoryCallback(String nextUrl) {
final StandardMethodCodec codec = const StandardMethodCodec();

final ByteData data = codec.encodeMethodCall(MethodCall(
'onUpdateVisitedHistory',
<dynamic, dynamic>{'url': nextUrl},
));

// TODO(hterkelsen): Remove this when defaultBinaryMessages is in stable.
// https://github.com/flutter/flutter/issues/33446
// ignore: deprecated_member_use
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this is obsolete.

BinaryMessages.handlePlatformMessage(
channel.name,
data,
(ByteData data) {},
);
}

void _loadUrl(String url) {
history = history.sublist(0, currentPosition + 1);
history.add(url);
Expand Down