Merge branch 'v4_webview' of github.com:flutter/plugins into v4_webview_android_navigation_delegate
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart
index a9d8d12..8aa5f7d 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart
@@ -128,10 +128,9 @@
class WebViewHostApiImpl extends WebViewHostApi {
/// Constructs a [WebViewHostApiImpl].
WebViewHostApiImpl({
- BinaryMessenger? binaryMessenger,
+ super.binaryMessenger,
InstanceManager? instanceManager,
- }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager,
- super(binaryMessenger: binaryMessenger);
+ }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager;
/// Maintains instances stored to communicate with java objects.
final InstanceManager instanceManager;
@@ -342,10 +341,9 @@
class WebSettingsHostApiImpl extends WebSettingsHostApi {
/// Constructs a [WebSettingsHostApiImpl].
WebSettingsHostApiImpl({
- BinaryMessenger? binaryMessenger,
+ super.binaryMessenger,
InstanceManager? instanceManager,
- }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager,
- super(binaryMessenger: binaryMessenger);
+ }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager;
/// Maintains instances stored to communicate with java objects.
final InstanceManager instanceManager;
@@ -484,10 +482,9 @@
class JavaScriptChannelHostApiImpl extends JavaScriptChannelHostApi {
/// Constructs a [JavaScriptChannelHostApiImpl].
JavaScriptChannelHostApiImpl({
- BinaryMessenger? binaryMessenger,
+ super.binaryMessenger,
InstanceManager? instanceManager,
- }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager,
- super(binaryMessenger: binaryMessenger);
+ }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager;
/// Maintains instances stored to communicate with java objects.
final InstanceManager instanceManager;
@@ -529,10 +526,9 @@
class WebViewClientHostApiImpl extends WebViewClientHostApi {
/// Constructs a [WebViewClientHostApiImpl].
WebViewClientHostApiImpl({
- BinaryMessenger? binaryMessenger,
+ super.binaryMessenger,
InstanceManager? instanceManager,
- }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager,
- super(binaryMessenger: binaryMessenger);
+ }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager;
/// Maintains instances stored to communicate with java objects.
final InstanceManager instanceManager;
@@ -717,10 +713,9 @@
class DownloadListenerHostApiImpl extends DownloadListenerHostApi {
/// Constructs a [DownloadListenerHostApiImpl].
DownloadListenerHostApiImpl({
- BinaryMessenger? binaryMessenger,
+ super.binaryMessenger,
InstanceManager? instanceManager,
- }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager,
- super(binaryMessenger: binaryMessenger);
+ }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager;
/// Maintains instances stored to communicate with java objects.
final InstanceManager instanceManager;
@@ -772,10 +767,9 @@
class WebChromeClientHostApiImpl extends WebChromeClientHostApi {
/// Constructs a [WebChromeClientHostApiImpl].
WebChromeClientHostApiImpl({
- BinaryMessenger? binaryMessenger,
+ super.binaryMessenger,
InstanceManager? instanceManager,
- }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager,
- super(binaryMessenger: binaryMessenger);
+ }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager;
/// Maintains instances stored to communicate with java objects.
final InstanceManager instanceManager;
@@ -822,10 +816,9 @@
class WebStorageHostApiImpl extends WebStorageHostApi {
/// Constructs a [WebStorageHostApiImpl].
WebStorageHostApiImpl({
- BinaryMessenger? binaryMessenger,
+ super.binaryMessenger,
InstanceManager? instanceManager,
- }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager,
- super(binaryMessenger: binaryMessenger);
+ }) : instanceManager = instanceManager ?? JavaObject.globalInstanceManager;
/// Maintains instances stored to communicate with java objects.
final InstanceManager instanceManager;
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_navigation_delegate.dart b/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_navigation_delegate.dart
new file mode 100644
index 0000000..0d2630f
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_navigation_delegate.dart
@@ -0,0 +1,279 @@
+// Copyright 2013 The Flutter 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 'dart:async';
+
+import 'package:flutter/foundation.dart';
+import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart';
+
+import '../../android_webview.dart' as android_webview;
+import 'android_proxy.dart';
+
+/// Signature for the `loadUrl` callback responsible for loading the [url]
+/// after a navigation request has been approved.
+typedef LoadUrlCallback = Future<void> Function(
+ String url, Map<String, String>? headers);
+
+/// Error returned in `WebView.onWebResourceError` when a web resource loading error has occurred.
+class AndroidWebResourceError extends WebResourceError {
+ /// Creates a new [AndroidWebResourceError].
+ AndroidWebResourceError._({
+ required super.errorCode,
+ required super.description,
+ this.failingUrl,
+ }) : super(
+ errorType: _errorCodeToErrorType(errorCode),
+ );
+
+ /// Gets the URL for which the failing resource request was made.
+ final String? failingUrl;
+
+ static WebResourceErrorType? _errorCodeToErrorType(int errorCode) {
+ switch (errorCode) {
+ case android_webview.WebViewClient.errorAuthentication:
+ return WebResourceErrorType.authentication;
+ case android_webview.WebViewClient.errorBadUrl:
+ return WebResourceErrorType.badUrl;
+ case android_webview.WebViewClient.errorConnect:
+ return WebResourceErrorType.connect;
+ case android_webview.WebViewClient.errorFailedSslHandshake:
+ return WebResourceErrorType.failedSslHandshake;
+ case android_webview.WebViewClient.errorFile:
+ return WebResourceErrorType.file;
+ case android_webview.WebViewClient.errorFileNotFound:
+ return WebResourceErrorType.fileNotFound;
+ case android_webview.WebViewClient.errorHostLookup:
+ return WebResourceErrorType.hostLookup;
+ case android_webview.WebViewClient.errorIO:
+ return WebResourceErrorType.io;
+ case android_webview.WebViewClient.errorProxyAuthentication:
+ return WebResourceErrorType.proxyAuthentication;
+ case android_webview.WebViewClient.errorRedirectLoop:
+ return WebResourceErrorType.redirectLoop;
+ case android_webview.WebViewClient.errorTimeout:
+ return WebResourceErrorType.timeout;
+ case android_webview.WebViewClient.errorTooManyRequests:
+ return WebResourceErrorType.tooManyRequests;
+ case android_webview.WebViewClient.errorUnknown:
+ return WebResourceErrorType.unknown;
+ case android_webview.WebViewClient.errorUnsafeResource:
+ return WebResourceErrorType.unsafeResource;
+ case android_webview.WebViewClient.errorUnsupportedAuthScheme:
+ return WebResourceErrorType.unsupportedAuthScheme;
+ case android_webview.WebViewClient.errorUnsupportedScheme:
+ return WebResourceErrorType.unsupportedScheme;
+ }
+
+ throw ArgumentError(
+ 'Could not find a WebResourceErrorType for errorCode: $errorCode',
+ );
+ }
+}
+
+/// Object specifying creation parameters for creating a [AndroidNavigationDelegate].
+///
+/// When adding additional fields make sure they can be null or have a default
+/// value to avoid breaking changes. See [PlatformNavigationDelegateCreationParams] for
+/// more information.
+@immutable
+class AndroidNavigationDelegateCreationParams
+ extends PlatformNavigationDelegateCreationParams {
+ /// Creates a new [AndroidNavigationDelegateCreationParams] instance.
+ const AndroidNavigationDelegateCreationParams._({
+ this.loadUrl,
+ @visibleForTesting this.androidWebViewProxy = const AndroidWebViewProxy(),
+ }) : super();
+
+ /// Creates a [AndroidNavigationDelegateCreationParams] instance based on [PlatformNavigationDelegateCreationParams].
+ factory AndroidNavigationDelegateCreationParams.fromPlatformNavigationDelegateCreationParams(
+ // Recommended placeholder to prevent being broken by platform interface.
+ // ignore: avoid_unused_constructor_parameters
+ PlatformNavigationDelegateCreationParams params, {
+ LoadUrlCallback? loadUrl,
+ @visibleForTesting
+ AndroidWebViewProxy androidWebViewProxy = const AndroidWebViewProxy(),
+ }) {
+ return AndroidNavigationDelegateCreationParams._(
+ loadUrl: loadUrl,
+ androidWebViewProxy: androidWebViewProxy,
+ );
+ }
+
+ /// Callback responsible for loading the [url] after a navigation request is approved.
+ final LoadUrlCallback? loadUrl;
+
+ /// Handles constructing objects and calling static methods for the Android WebView
+ /// native library.
+ @visibleForTesting
+ final AndroidWebViewProxy androidWebViewProxy;
+}
+
+/// A place to register callback methods responsible to handle navigation events
+/// triggered by the [android_webview.WebView].
+class AndroidNavigationDelegate extends PlatformNavigationDelegate {
+ /// Creates a new [AndroidNavigationkDelegate].
+ AndroidNavigationDelegate(PlatformNavigationDelegateCreationParams params)
+ : super.implementation(params is AndroidNavigationDelegateCreationParams
+ ? params
+ : AndroidNavigationDelegateCreationParams
+ .fromPlatformNavigationDelegateCreationParams(params)) {
+ final WeakReference<AndroidNavigationDelegate> weakThis =
+ WeakReference<AndroidNavigationDelegate>(this);
+
+ _webChromeClient = (params as AndroidNavigationDelegateCreationParams)
+ .androidWebViewProxy
+ .createAndroidWebChromeClient(
+ onProgressChanged: (android_webview.WebView webView, int progress) {
+ if (weakThis.target?._onProgress != null) {
+ weakThis.target!._onProgress!(progress);
+ }
+ });
+
+ _webViewClient = params.androidWebViewProxy.createAndroidWebViewClient(
+ onPageFinished: (android_webview.WebView webView, String url) {
+ if (weakThis.target?._onPageFinished != null) {
+ weakThis.target!._onPageFinished!(url);
+ }
+ },
+ onPageStarted: (android_webview.WebView webView, String url) {
+ if (weakThis.target?._onPageStarted != null) {
+ weakThis.target!._onPageStarted!(url);
+ }
+ },
+ onReceivedRequestError: (
+ android_webview.WebView webView,
+ android_webview.WebResourceRequest request,
+ android_webview.WebResourceError error,
+ ) {
+ if (weakThis.target?._onWebResourceError != null &&
+ request.isForMainFrame) {
+ weakThis.target!._onWebResourceError!(AndroidWebResourceError._(
+ errorCode: error.errorCode,
+ description: error.description,
+ failingUrl: request.url,
+ ));
+ }
+ },
+ onReceivedError: (
+ android_webview.WebView webView,
+ int errorCode,
+ String description,
+ String failingUrl,
+ ) {
+ if (weakThis.target?._onWebResourceError != null) {
+ weakThis.target!._onWebResourceError!(AndroidWebResourceError._(
+ errorCode: errorCode,
+ description: description,
+ failingUrl: failingUrl,
+ ));
+ }
+ },
+ requestLoading: (
+ android_webview.WebView webView,
+ android_webview.WebResourceRequest request,
+ ) {
+ if (weakThis.target != null) {
+ weakThis.target!._handleNavigation(
+ request.url,
+ request.requestHeaders,
+ );
+ }
+ },
+ urlLoading: (
+ android_webview.WebView webView,
+ String url,
+ ) {
+ if (weakThis.target != null) {
+ weakThis.target!._handleNavigation(url, <String, String>{});
+ }
+ },
+ );
+ }
+
+ late final android_webview.WebChromeClient _webChromeClient;
+
+ /// Gets the native [android_webview.WebChromeClient] that is bridged by this [AndroidNavigationDelegate].
+ ///
+ /// Used by the [AndroidWebViewController] to set the `android_webview.WebView.setWebChromeClient`.
+ android_webview.WebChromeClient get androidWebChromeClient =>
+ _webChromeClient;
+
+ late final android_webview.WebViewClient _webViewClient;
+
+ /// Gets the native [android_webview.WebViewClient] that is bridged by this [AndroidNavigationDelegate].
+ ///
+ /// Used by the [AndroidWebViewController] to set the `android_webview.WebView.setWebViewClient`.
+ android_webview.WebViewClient get androidWebViewClient => _webViewClient;
+
+ PageEventCallback? _onPageFinished;
+ PageEventCallback? _onPageStarted;
+ ProgressCallback? _onProgress;
+ WebResourceErrorCallback? _onWebResourceError;
+ NavigationRequestCallback? _onNavigationRequest;
+
+ AndroidNavigationDelegateCreationParams get _androidParams =>
+ params as AndroidNavigationDelegateCreationParams;
+
+ bool get _handlesNavigation =>
+ _onNavigationRequest != null && _androidParams.loadUrl != null;
+
+ void _handleNavigation(String url, Map<String, String> headers) {
+ if (!_handlesNavigation) {
+ return;
+ }
+
+ final FutureOr<NavigationDecision> returnValue = _onNavigationRequest!(
+ NavigationRequest(
+ isMainFrame: true,
+ url: url,
+ ),
+ );
+
+ if (returnValue is NavigationDecision &&
+ returnValue == NavigationDecision.navigate) {
+ _androidParams.loadUrl!(url, headers);
+ } else if (returnValue is Future<NavigationDecision>) {
+ returnValue.then((NavigationDecision shouldLoadUrl) {
+ if (shouldLoadUrl == NavigationDecision.navigate) {
+ _androidParams.loadUrl!(url, headers);
+ }
+ });
+ }
+ }
+
+ @override
+ Future<void> setOnNavigationRequest(
+ NavigationRequestCallback onNavigationRequest,
+ ) async {
+ _onNavigationRequest = onNavigationRequest;
+ }
+
+ @override
+ Future<void> setOnPageStarted(
+ PageEventCallback onPageStarted,
+ ) async {
+ _onPageStarted = onPageStarted;
+ }
+
+ @override
+ Future<void> setOnPageFinished(
+ PageEventCallback onPageFinished,
+ ) async {
+ _onPageFinished = onPageFinished;
+ }
+
+ @override
+ Future<void> setOnProgress(
+ ProgressCallback onProgress,
+ ) async {
+ _onProgress = onProgress;
+ }
+
+ @override
+ Future<void> setOnWebResourceError(
+ WebResourceErrorCallback onWebResourceError,
+ ) async {
+ _onWebResourceError = onWebResourceError;
+ }
+}
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_proxy.dart b/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_proxy.dart
new file mode 100644
index 0000000..757397a
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_proxy.dart
@@ -0,0 +1,55 @@
+// Copyright 2013 The Flutter 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 '../../android_webview.dart' as android_webview;
+
+/// Handles constructing objects and calling static methods for the Android
+/// WebView native library.
+///
+/// This class provides dependency injection for the implementations of the
+/// platform interface classes. Improving the ease of unit testing and/or
+/// overriding the underlying Android WebView classes.
+///
+/// By default each function calls the default constructor of the WebView class
+/// it intends to return.
+class AndroidWebViewProxy {
+ /// Constructs a [AndroidWebViewProxy].
+ const AndroidWebViewProxy({
+ this.createAndroidWebChromeClient = android_webview.WebChromeClient.new,
+ this.createAndroidWebViewClient = android_webview.WebViewClient.new,
+ });
+
+ /// Constructs a [android_webview.WebChromeClient].
+ final android_webview.WebChromeClient Function({
+ void Function(android_webview.WebView webView, int progress)?
+ onProgressChanged,
+ }) createAndroidWebChromeClient;
+
+ /// Constructs a [android_webview.WebViewClient].
+ final android_webview.WebViewClient Function({
+ bool shouldOverrideUrlLoading,
+ void Function(android_webview.WebView webView, String url)? onPageStarted,
+ void Function(android_webview.WebView webView, String url)? onPageFinished,
+ void Function(
+ android_webview.WebView webView,
+ android_webview.WebResourceRequest request,
+ android_webview.WebResourceError error,
+ )?
+ onReceivedRequestError,
+ @Deprecated('Only called on Android version < 23.')
+ void Function(
+ android_webview.WebView webView,
+ int errorCode,
+ String description,
+ String failingUrl,
+ )?
+ onReceivedError,
+ void Function(
+ android_webview.WebView webView,
+ android_webview.WebResourceRequest request,
+ )?
+ requestLoading,
+ void Function(android_webview.WebView webView, String url)? urlLoading,
+ }) createAndroidWebViewClient;
+}
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_webview_cookie_manager.dart b/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_webview_cookie_manager.dart
index 9a64a62..127b7e4 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_webview_cookie_manager.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/v4/src/android_webview_cookie_manager.dart
@@ -44,10 +44,10 @@
/// with the default [AndroidWebViewCookieManager] constructor.
@visibleForTesting
AndroidWebViewCookieManager.fromNativeApi(
- AndroidWebViewCookieManagerCreationParams params, {
+ AndroidWebViewCookieManagerCreationParams super.params, {
required CookieManager cookieManager,
}) : _cookieManager = cookieManager,
- super.implementation(params);
+ super.implementation();
final CookieManager _cookieManager;
diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
index d1fa1b8..4cb99b5 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
@@ -16,7 +16,7 @@
class WebViewAndroidWidget extends StatefulWidget {
/// Constructs a [WebViewAndroidWidget].
const WebViewAndroidWidget({
- Key? key,
+ super.key,
required this.creationParams,
required this.useHybridComposition,
required this.callbacksHandler,
@@ -26,7 +26,7 @@
@visibleForTesting
this.flutterAssetManager = const android_webview.FlutterAssetManager(),
@visibleForTesting this.webStorage,
- }) : super(key: key);
+ });
/// Initial parameters used to setup the WebView.
final CreationParams creationParams;
@@ -570,10 +570,9 @@
extends android_webview.JavaScriptChannel {
/// Creates a [WebViewAndroidJavaScriptChannel].
WebViewAndroidJavaScriptChannel(
- String channelName,
+ super.channelName,
this.javascriptChannelRegistry,
) : super(
- channelName,
postMessage: withWeakRefenceTo(
javascriptChannelRegistry,
(WeakReference<JavascriptChannelRegistry> weakReference) {
diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
index 8f7cd9b..8384b7c 100644
--- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
@@ -6,7 +6,7 @@
publish_to: none
environment:
- sdk: ">=2.14.0 <3.0.0"
+ sdk: ">=2.17.0 <3.0.0"
flutter: ">=3.0.0"
flutter:
diff --git a/packages/webview_flutter/webview_flutter_android/test/v4/android_navigation_delegate_test.dart b/packages/webview_flutter/webview_flutter_android/test/v4/android_navigation_delegate_test.dart
new file mode 100644
index 0000000..dfde021
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/test/v4/android_navigation_delegate_test.dart
@@ -0,0 +1,396 @@
+// Copyright 2013 The Flutter 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 'dart:async';
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:webview_flutter_android/src/android_webview.dart'
+ as android_webview;
+import 'package:webview_flutter_android/src/v4/src/android_navigation_delegate.dart';
+import 'package:webview_flutter_android/src/v4/src/android_proxy.dart';
+import 'package:webview_flutter_platform_interface/v4/webview_flutter_platform_interface.dart';
+
+void main() {
+ group('AndroidNavigationDelegate', () {
+ test('onPageFinished', () {
+ final AndroidNavigationDelegate webKitDelegate =
+ AndroidNavigationDelegate(_buildCreationParams());
+
+ late final String callbackUrl;
+ webKitDelegate.setOnPageFinished((String url) => callbackUrl = url);
+
+ CapturingWebViewClient.lastCreatedDelegate.onPageFinished!(
+ android_webview.WebView.detached(),
+ 'https://www.google.com',
+ );
+
+ expect(callbackUrl, 'https://www.google.com');
+ });
+
+ test('onPageStarted', () {
+ final AndroidNavigationDelegate webKitDelegate =
+ AndroidNavigationDelegate(_buildCreationParams());
+
+ late final String callbackUrl;
+ webKitDelegate.setOnPageStarted((String url) => callbackUrl = url);
+
+ CapturingWebViewClient.lastCreatedDelegate.onPageStarted!(
+ android_webview.WebView.detached(),
+ 'https://www.google.com',
+ );
+
+ expect(callbackUrl, 'https://www.google.com');
+ });
+
+ test('onWebResourceError from onReceivedRequestError', () {
+ final AndroidNavigationDelegate webKitDelegate =
+ AndroidNavigationDelegate(_buildCreationParams());
+
+ late final WebResourceError callbackError;
+ webKitDelegate.setOnWebResourceError(
+ (WebResourceError error) => callbackError = error);
+
+ CapturingWebViewClient.lastCreatedDelegate.onReceivedRequestError!(
+ android_webview.WebView.detached(),
+ android_webview.WebResourceRequest(
+ url: 'https://www.google.com',
+ isForMainFrame: true,
+ isRedirect: true,
+ hasGesture: true,
+ method: 'GET',
+ requestHeaders: <String, String>{'X-Mock': 'mocking'},
+ ),
+ android_webview.WebResourceError(
+ errorCode: android_webview.WebViewClient.errorFileNotFound,
+ description: 'Page not found.',
+ ),
+ );
+
+ expect(callbackError.errorCode,
+ android_webview.WebViewClient.errorFileNotFound);
+ expect(callbackError.description, 'Page not found.');
+ expect(callbackError.errorType, WebResourceErrorType.fileNotFound);
+ });
+
+ test('onWebResourceError from onRequestError', () {
+ final AndroidNavigationDelegate webKitDelegate =
+ AndroidNavigationDelegate(_buildCreationParams());
+
+ late final WebResourceError callbackError;
+ webKitDelegate.setOnWebResourceError(
+ (WebResourceError error) => callbackError = error);
+
+ CapturingWebViewClient.lastCreatedDelegate.onReceivedError!(
+ android_webview.WebView.detached(),
+ android_webview.WebViewClient.errorFileNotFound,
+ 'Page not found.',
+ 'https://www.google.com',
+ );
+
+ expect(callbackError.errorCode,
+ android_webview.WebViewClient.errorFileNotFound);
+ expect(callbackError.description, 'Page not found.');
+ expect(callbackError.errorType, WebResourceErrorType.fileNotFound);
+ });
+
+ test(
+ 'onNavigationRequest from requestLoading should not be called when loadUrlCallback is not specified',
+ () {
+ final AndroidNavigationDelegate webKitDelegate =
+ AndroidNavigationDelegate(_buildCreationParams());
+
+ NavigationRequest? callbackNavigationRequest;
+ webKitDelegate
+ .setOnNavigationRequest((NavigationRequest navigationRequest) {
+ callbackNavigationRequest = navigationRequest;
+ return NavigationDecision.prevent;
+ });
+
+ CapturingWebViewClient.lastCreatedDelegate.requestLoading!(
+ android_webview.WebView.detached(),
+ android_webview.WebResourceRequest(
+ url: 'https://www.google.com',
+ isForMainFrame: true,
+ isRedirect: true,
+ hasGesture: true,
+ method: 'GET',
+ requestHeaders: <String, String>{'X-Mock': 'mocking'},
+ ),
+ );
+
+ expect(callbackNavigationRequest, isNull);
+ });
+
+ test(
+ 'onLoadUrl from requestLoading should not be called when navigationRequestCallback is not specified',
+ () {
+ final Completer<void> completer = Completer<void>();
+ AndroidNavigationDelegate(_buildCreationParams(loadUrlCallback: (
+ String url,
+ Map<String, String>? headers,
+ ) {
+ completer.complete();
+ return completer.future;
+ }));
+
+ CapturingWebViewClient.lastCreatedDelegate.requestLoading!(
+ android_webview.WebView.detached(),
+ android_webview.WebResourceRequest(
+ url: 'https://www.google.com',
+ isForMainFrame: true,
+ isRedirect: true,
+ hasGesture: true,
+ method: 'GET',
+ requestHeaders: <String, String>{'X-Mock': 'mocking'},
+ ),
+ );
+
+ expect(completer.isCompleted, false);
+ });
+
+ test(
+ 'onLoadUrl from requestLoading should not be called when onNavigationRequestCallback returns NavigationDecision.prevent',
+ () {
+ final Completer<void> completer = Completer<void>();
+ final AndroidNavigationDelegate webKitDelegate =
+ AndroidNavigationDelegate(_buildCreationParams(loadUrlCallback: (
+ String url,
+ Map<String, String>? headers,
+ ) {
+ completer.complete();
+ return completer.future;
+ }));
+
+ late final NavigationRequest callbackNavigationRequest;
+ webKitDelegate
+ .setOnNavigationRequest((NavigationRequest navigationRequest) {
+ callbackNavigationRequest = navigationRequest;
+ return NavigationDecision.prevent;
+ });
+
+ CapturingWebViewClient.lastCreatedDelegate.requestLoading!(
+ android_webview.WebView.detached(),
+ android_webview.WebResourceRequest(
+ url: 'https://www.google.com',
+ isForMainFrame: true,
+ isRedirect: true,
+ hasGesture: true,
+ method: 'GET',
+ requestHeaders: <String, String>{'X-Mock': 'mocking'},
+ ),
+ );
+
+ expect(callbackNavigationRequest.isMainFrame, true);
+ expect(callbackNavigationRequest.url, 'https://www.google.com');
+ expect(completer.isCompleted, false);
+ });
+
+ test(
+ 'onLoadUrl from requestLoading should complete when onNavigationRequestCallback returns NavigationDecision.navigate',
+ () {
+ final Completer<void> completer = Completer<void>();
+ late final String loadUrlCallbackUrl;
+ late final Map<String, String>? loadUrlCallbackHeaders;
+ final AndroidNavigationDelegate webKitDelegate =
+ AndroidNavigationDelegate(_buildCreationParams(loadUrlCallback: (
+ String url,
+ Map<String, String>? headers,
+ ) {
+ loadUrlCallbackUrl = url;
+ loadUrlCallbackHeaders = headers;
+ completer.complete();
+ return completer.future;
+ }));
+
+ late final NavigationRequest callbackNavigationRequest;
+ webKitDelegate
+ .setOnNavigationRequest((NavigationRequest navigationRequest) {
+ callbackNavigationRequest = navigationRequest;
+ return NavigationDecision.navigate;
+ });
+
+ CapturingWebViewClient.lastCreatedDelegate.requestLoading!(
+ android_webview.WebView.detached(),
+ android_webview.WebResourceRequest(
+ url: 'https://www.google.com',
+ isForMainFrame: true,
+ isRedirect: true,
+ hasGesture: true,
+ method: 'GET',
+ requestHeaders: <String, String>{'X-Mock': 'mocking'},
+ ),
+ );
+
+ expect(loadUrlCallbackUrl, 'https://www.google.com');
+ expect(loadUrlCallbackHeaders, <String, String>{'X-Mock': 'mocking'});
+ expect(callbackNavigationRequest.isMainFrame, true);
+ expect(callbackNavigationRequest.url, 'https://www.google.com');
+ expect(completer.isCompleted, true);
+ });
+
+ test(
+ 'onNavigationRequest from urlLoading should not be called when loadUrlCallback is not specified',
+ () {
+ final AndroidNavigationDelegate webKitDelegate =
+ AndroidNavigationDelegate(_buildCreationParams());
+
+ NavigationRequest? callbackNavigationRequest;
+ webKitDelegate
+ .setOnNavigationRequest((NavigationRequest navigationRequest) {
+ callbackNavigationRequest = navigationRequest;
+ return NavigationDecision.prevent;
+ });
+
+ CapturingWebViewClient.lastCreatedDelegate.urlLoading!(
+ android_webview.WebView.detached(),
+ 'https://www.google.com',
+ );
+
+ expect(callbackNavigationRequest, isNull);
+ });
+
+ test(
+ 'onLoadUrl from urlLoading should not be called when navigationRequestCallback is not specified',
+ () {
+ final Completer<void> completer = Completer<void>();
+ AndroidNavigationDelegate(_buildCreationParams(loadUrlCallback: (
+ String url,
+ Map<String, String>? headers,
+ ) {
+ completer.complete();
+ return completer.future;
+ }));
+
+ CapturingWebViewClient.lastCreatedDelegate.urlLoading!(
+ android_webview.WebView.detached(),
+ 'https://www.google.com',
+ );
+
+ expect(completer.isCompleted, false);
+ });
+
+ test(
+ 'onLoadUrl from urlLoading should not be called when onNavigationRequestCallback returns NavigationDecision.prevent',
+ () {
+ final Completer<void> completer = Completer<void>();
+ final AndroidNavigationDelegate webKitDelegate =
+ AndroidNavigationDelegate(_buildCreationParams(loadUrlCallback: (
+ String url,
+ Map<String, String>? headers,
+ ) {
+ completer.complete();
+ return completer.future;
+ }));
+
+ late final NavigationRequest callbackNavigationRequest;
+ webKitDelegate
+ .setOnNavigationRequest((NavigationRequest navigationRequest) {
+ callbackNavigationRequest = navigationRequest;
+ return NavigationDecision.prevent;
+ });
+
+ CapturingWebViewClient.lastCreatedDelegate.urlLoading!(
+ android_webview.WebView.detached(),
+ 'https://www.google.com',
+ );
+
+ expect(callbackNavigationRequest.isMainFrame, true);
+ expect(callbackNavigationRequest.url, 'https://www.google.com');
+ expect(completer.isCompleted, false);
+ });
+
+ test(
+ 'onLoadUrl from urlLoading should complete when onNavigationRequestCallback returns NavigationDecision.navigate',
+ () {
+ final Completer<void> completer = Completer<void>();
+ late final String loadUrlCallbackUrl;
+ late final Map<String, String>? loadUrlCallbackHeaders;
+ final AndroidNavigationDelegate webKitDelegate =
+ AndroidNavigationDelegate(_buildCreationParams(loadUrlCallback: (
+ String url,
+ Map<String, String>? headers,
+ ) {
+ loadUrlCallbackUrl = url;
+ loadUrlCallbackHeaders = headers;
+ completer.complete();
+ return completer.future;
+ }));
+
+ late final NavigationRequest callbackNavigationRequest;
+ webKitDelegate
+ .setOnNavigationRequest((NavigationRequest navigationRequest) {
+ callbackNavigationRequest = navigationRequest;
+ return NavigationDecision.navigate;
+ });
+
+ CapturingWebViewClient.lastCreatedDelegate.urlLoading!(
+ android_webview.WebView.detached(),
+ 'https://www.google.com',
+ );
+
+ expect(loadUrlCallbackUrl, 'https://www.google.com');
+ expect(loadUrlCallbackHeaders, <String, String>{});
+ expect(callbackNavigationRequest.isMainFrame, true);
+ expect(callbackNavigationRequest.url, 'https://www.google.com');
+ expect(completer.isCompleted, true);
+ });
+
+ test('setOnProgress', () {
+ final AndroidNavigationDelegate webKitDelegate =
+ AndroidNavigationDelegate(_buildCreationParams());
+
+ late final int callbackProgress;
+ webKitDelegate
+ .setOnProgress((int progress) => callbackProgress = progress);
+
+ CapturingWebChromeClient.lastCreatedDelegate.onProgressChanged!(
+ android_webview.WebView.detached(),
+ 42,
+ );
+
+ expect(callbackProgress, 42);
+ });
+ });
+}
+
+AndroidNavigationDelegateCreationParams _buildCreationParams({
+ LoadUrlCallback? loadUrlCallback,
+}) {
+ return AndroidNavigationDelegateCreationParams
+ .fromPlatformNavigationDelegateCreationParams(
+ const PlatformNavigationDelegateCreationParams(),
+ loadUrl: loadUrlCallback,
+ androidWebViewProxy: const AndroidWebViewProxy(
+ createAndroidWebChromeClient: CapturingWebChromeClient.new,
+ createAndroidWebViewClient: CapturingWebViewClient.new,
+ ),
+ );
+}
+
+// Records the last created instance of itself.
+class CapturingWebViewClient extends android_webview.WebViewClient {
+ CapturingWebViewClient({
+ super.onPageFinished,
+ super.onPageStarted,
+ super.onReceivedError,
+ super.onReceivedRequestError,
+ super.requestLoading,
+ super.shouldOverrideUrlLoading,
+ super.urlLoading,
+ }) : super.detached() {
+ lastCreatedDelegate = this;
+ }
+ static CapturingWebViewClient lastCreatedDelegate = CapturingWebViewClient();
+}
+
+// Records the last created instance of itself.
+class CapturingWebChromeClient extends android_webview.WebChromeClient {
+ CapturingWebChromeClient({
+ super.onProgressChanged,
+ }) : super.detached() {
+ lastCreatedDelegate = this;
+ }
+ static CapturingWebChromeClient lastCreatedDelegate =
+ CapturingWebChromeClient();
+}