Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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/webview_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 4.1.0

* Adds support to track URL changes. See `NavigationDelegate(onUrlChange)`.

## 4.0.5

* Updates links for the merge of flutter/plugins into flutter/packages.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,32 @@ Future<void> main() async {
final String? currentUrl = await controller.currentUrl();
expect(currentUrl, secondaryUrl);
});

testWidgets('can receive url changes', (WidgetTester tester) async {
final Completer<void> pageLoaded = Completer<void>();

final WebViewController controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(NavigationDelegate(
onPageFinished: (_) => pageLoaded.complete(),
))
..loadRequest(Uri.parse(blankPageEncoded));

await tester.pumpWidget(WebViewWidget(controller: controller));

await pageLoaded.future;

final Completer<String> urlChangeCompleter = Completer<String>();
await controller.setNavigationDelegate(NavigationDelegate(
onUrlChange: (UrlChange change) {
urlChangeCompleter.complete(change.url);
},
));

await controller.runJavaScript('location.href = "$primaryUrl"');

await expectLater(urlChangeCompleter.future, completion(primaryUrl));
});
});

testWidgets('target _blank opens in same window',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ Page resource error:
debugPrint('allowing navigation to ${request.url}');
return NavigationDecision.navigate;
},
onUrlChange: (UrlChange change) {
debugPrint('url change to ${change.url}');
},
),
)
..addJavaScriptChannel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,12 @@ flutter:
- assets/sample_video.mp4
- assets/www/index.html
- assets/www/styles/style.css

# FOR TESTING ONLY. DO NOT MERGE.
dependency_overrides:
webview_flutter_android:
path: ../../../webview_flutter/webview_flutter_android
webview_flutter_platform_interface:
path: ../../../webview_flutter/webview_flutter_platform_interface
webview_flutter_wkwebview:
path: ../../../webview_flutter/webview_flutter_wkwebview
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,7 @@ class FakeNavigationDelegate extends PlatformNavigationDelegate {
Future<void> setOnWebResourceError(
WebResourceErrorCallback onWebResourceError,
) async {}

@override
Future<void> setOnUrlChange(UrlChangeCallback onUrlChange) async {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,26 @@ import 'webview_controller.dart';
/// ```
class NavigationDelegate {
/// Constructs a [NavigationDelegate].
///
/// {@template webview_fluttter.navigation_delegate}
/// `onUrlChange`: invoked when the underlying web view changes to a new url.
/// {@endtemplate}
NavigationDelegate({
FutureOr<NavigationDecision> Function(NavigationRequest request)?
onNavigationRequest,
void Function(String url)? onPageStarted,
void Function(String url)? onPageFinished,
void Function(int progress)? onProgress,
void Function(WebResourceError error)? onWebResourceError,
void Function(UrlChange change)? onUrlChange,
}) : this.fromPlatformCreationParams(
const PlatformNavigationDelegateCreationParams(),
onNavigationRequest: onNavigationRequest,
onPageStarted: onPageStarted,
onPageFinished: onPageFinished,
onProgress: onProgress,
onWebResourceError: onWebResourceError,
onUrlChange: onUrlChange,
);

/// Constructs a [NavigationDelegate] from creation params for a specific
Expand Down Expand Up @@ -81,6 +87,8 @@ class NavigationDelegate {
/// );
/// ```
/// {@endtemplate}
///
/// {@macro webview_fluttter.navigation_delegate}
NavigationDelegate.fromPlatformCreationParams(
PlatformNavigationDelegateCreationParams params, {
FutureOr<NavigationDecision> Function(NavigationRequest request)?
Expand All @@ -89,23 +97,28 @@ class NavigationDelegate {
void Function(String url)? onPageFinished,
void Function(int progress)? onProgress,
void Function(WebResourceError error)? onWebResourceError,
void Function(UrlChange change)? onUrlChange,
}) : this.fromPlatform(
PlatformNavigationDelegate(params),
onNavigationRequest: onNavigationRequest,
onPageStarted: onPageStarted,
onPageFinished: onPageFinished,
onProgress: onProgress,
onWebResourceError: onWebResourceError,
onUrlChange: onUrlChange,
);

/// Constructs a [NavigationDelegate] from a specific platform implementation.
///
/// {@macro webview_fluttter.navigation_delegate}
NavigationDelegate.fromPlatform(
this.platform, {
this.onNavigationRequest,
this.onPageStarted,
this.onPageFinished,
this.onProgress,
this.onWebResourceError,
void Function(UrlChange change)? onUrlChange,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was not added as a field of the clas because there is a chance that the field can be different from the actual value. For example, a user may set this value and then call NavigationDelegate.platform.setOnUrlChange:

final NavigationDelegate delegate = NavigationDelegate(onUrlChange: (_) {});
delegate.platform.setUrlChange((_) {});

This is true for all the other callbacks, but changing them would be a breaking change.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is true for all the other callbacks, but changing them would be a breaking change.

Couldn't we replace the final fields with getters, and have the getters return the platform version of the field, to preserve API compatibility while removing the potential for them to become out of sync?

(Doesn't need to be in this PR.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mvanbeusekom suggested something similar by making the callback method setters in PlatformNavigationDelegate fields instead of methods. I decided against it initially because using a method allowed us to throw an UnimplementedError for unsupported callback methods. But we could add getters to the interface as a mix of both solutions.

This wouldn't be possible in this PR since it is something we would have to add to the platform interface first anyways.

}) {
if (onNavigationRequest != null) {
platform.setOnNavigationRequest(onNavigationRequest!);
Expand All @@ -122,6 +135,9 @@ class NavigationDelegate {
if (onWebResourceError != null) {
platform.setOnWebResourceError(onWebResourceError!);
}
if (onUrlChange != null) {
platform.setOnUrlChange(onUrlChange);
}
}

/// Implementation of [PlatformNavigationDelegate] for the current platform.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export 'package:webview_flutter_platform_interface/webview_flutter_platform_inte
PlatformWebViewCookieManagerCreationParams,
PlatformWebViewWidgetCreationParams,
ProgressCallback,
UrlChange,
WebResourceError,
WebResourceErrorCallback,
WebResourceErrorType,
Expand Down
11 changes: 10 additions & 1 deletion packages/webview_flutter/webview_flutter/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: webview_flutter
description: A Flutter plugin that provides a WebView widget on Android and iOS.
repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
version: 4.0.5
version: 4.1.0

environment:
sdk: ">=2.17.0 <3.0.0"
Expand Down Expand Up @@ -31,3 +31,12 @@ dev_dependencies:
sdk: flutter
mockito: ^5.3.2
plugin_platform_interface: ^2.1.3

# FOR TESTING ONLY. DO NOT MERGE.
dependency_overrides:
webview_flutter_android:
path: ../../webview_flutter/webview_flutter_android
webview_flutter_platform_interface:
path: ../../webview_flutter/webview_flutter_platform_interface
webview_flutter_wkwebview:
path: ../../webview_flutter/webview_flutter_wkwebview
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ void main() {

verify(delegate.platform.setOnWebResourceError(onWebResourceError));
});

test('onUrlChange', () async {
WebViewPlatform.instance = TestWebViewPlatform();

void onUrlChange(UrlChange change) {}

final NavigationDelegate delegate = NavigationDelegate(
onUrlChange: onUrlChange,
);

verify(delegate.platform.setOnUrlChange(onUrlChange));
});
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,4 +228,14 @@ class MockPlatformNavigationDelegate extends _i1.Mock
returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i8.Future<void>.value(),
) as _i8.Future<void>);
@override
_i8.Future<void> setOnUrlChange(_i3.UrlChangeCallback? onUrlChange) =>
(super.noSuchMethod(
Invocation.method(
#setOnUrlChange,
[onUrlChange],
),
returnValue: _i8.Future<void>.value(),
returnValueForMissingStub: _i8.Future<void>.value(),
) as _i8.Future<void>);
}
Original file line number Diff line number Diff line change
Expand Up @@ -414,4 +414,14 @@ class MockPlatformNavigationDelegate extends _i1.Mock
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i5.Future<void> setOnUrlChange(_i6.UrlChangeCallback? onUrlChange) =>
(super.noSuchMethod(
Invocation.method(
#setOnUrlChange,
[onUrlChange],
),
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ void main() {
main_file.WebViewCookie;
// ignore: unnecessary_statements
main_file.WebResourceErrorType;
// ignore: unnecessary_statements
main_file.UrlChange;
});
});
}
4 changes: 4 additions & 0 deletions packages/webview_flutter/webview_flutter_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 3.4.0

* Adds support for `PlatformNavigationDelegate.onUrlChange`.

## 3.3.1

* Updates links for the merge of flutter/plugins into flutter/packages.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2352,6 +2352,25 @@ public void urlLoading(
callback.reply(null);
});
}

public void doUpdateVisitedHistory(
@NonNull Long instanceIdArg,
@NonNull Long webViewInstanceIdArg,
@NonNull String urlArg,
@NonNull Boolean isReloadArg,
Reply<Void> callback) {
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.WebViewClientFlutterApi.doUpdateVisitedHistory",
getCodec());
channel.send(
new ArrayList<Object>(
Arrays.asList(instanceIdArg, webViewInstanceIdArg, urlArg, isReloadArg)),
channelReply -> {
callback.reply(null);
});
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
public interface DownloadListenerHostApi {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) {
return returnValueForShouldOverrideUrlLoading;
}

@Override
public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
flutterApi.doUpdateVisitedHistory(this, view, url, isReload, reply -> {});
}

@Override
public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
// Deliberately empty. Occasionally the webview will mark events as having failed to be
Expand Down Expand Up @@ -146,6 +151,11 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) {
return returnValueForShouldOverrideUrlLoading;
}

@Override
public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
flutterApi.doUpdateVisitedHistory(this, view, url, isReload, reply -> {});
}

@Override
public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
// Deliberately empty. Occasionally the webview will mark events as having failed to be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,12 @@ public WebViewClient createWebViewClient(WebViewClientFlutterApiImpl flutterApi)

verify(mockWebViewClient).setReturnValueForShouldOverrideUrlLoading(false);
}

@Test
public void doUpdateVisitedHistory() {
webViewClient.doUpdateVisitedHistory(mockWebView, "https://www.google.com", true);
verify(mockFlutterApi)
.doUpdateVisitedHistory(
eq(webViewClient), eq(mockWebView), eq("https://www.google.com"), eq(true), any());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,42 @@ Future<void> main() async {
final String? currentUrl = await controller.currentUrl();
expect(currentUrl, secondaryUrl);
});

testWidgets('can receive url changes', (WidgetTester tester) async {
final Completer<void> pageLoaded = Completer<void>();

final PlatformNavigationDelegate navigationDelegate =
PlatformNavigationDelegate(
const PlatformNavigationDelegateCreationParams(),
)..setOnPageFinished((_) => pageLoaded.complete());

final PlatformWebViewController controller = PlatformWebViewController(
const PlatformWebViewControllerCreationParams(),
)
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setPlatformNavigationDelegate(navigationDelegate)
..loadRequest(LoadRequestParams(uri: Uri.parse(blankPageEncoded)));

await tester.pumpWidget(Builder(
builder: (BuildContext context) {
return PlatformWebViewWidget(
PlatformWebViewWidgetCreationParams(controller: controller),
).build(context);
},
));

await pageLoaded.future;
await navigationDelegate.setOnPageFinished((_) {});

final Completer<String> urlChangeCompleter = Completer<String>();
await navigationDelegate.setOnUrlChange((UrlChange change) {
urlChangeCompleter.complete(change.url);
});

await controller.runJavaScript('location.href = "$primaryUrl"');

await expectLater(urlChangeCompleter.future, completion(primaryUrl));
});
});

testWidgets('target _blank opens in same window',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ Page resource error:
}
debugPrint('allowing navigation to ${request.url}');
return NavigationDecision.navigate;
})
..setOnUrlChange((UrlChange change) {
debugPrint('url change to ${change.url}');
}),
)
..addJavaScriptChannel(JavaScriptChannelParams(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,11 @@ flutter:
- assets/sample_video.mp4
- assets/www/index.html
- assets/www/styles/style.css


# FOR TESTING ONLY. DO NOT MERGE.
dependency_overrides:
webview_flutter_android:
path: ../../../webview_flutter/webview_flutter_android
webview_flutter_platform_interface:
path: ../../../webview_flutter/webview_flutter_platform_interface
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ class AndroidWebViewProxy {
)?
requestLoading,
void Function(android_webview.WebView webView, String url)? urlLoading,
void Function(android_webview.WebView webView, String url, bool isReload)?
doUpdateVisitedHistory,
}) createAndroidWebViewClient;

/// Constructs a [android_webview.FlutterAssetManager].
Expand Down
Loading