diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index d2d7c841466..77f6d08e648 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.6.0 + +* Adds onReceivedHttpError WebViewClient callback to support `PlatformNavigationDelegate.onHttpError`. + ## 3.5.3 * Bumps gradle from 7.2.2 to 8.0.0. diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java index 31e91a44dab..7436f77736d 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java @@ -309,6 +309,58 @@ ArrayList toList() { } } + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class WebResourceResponseData { + private @NonNull Long statusCode; + + public @NonNull Long getStatusCode() { + return statusCode; + } + + public void setStatusCode(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"statusCode\" is null."); + } + this.statusCode = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + WebResourceResponseData() {} + + public static final class Builder { + + private @Nullable Long statusCode; + + public @NonNull Builder setStatusCode(@NonNull Long setterArg) { + this.statusCode = setterArg; + return this; + } + + public @NonNull WebResourceResponseData build() { + WebResourceResponseData pigeonReturn = new WebResourceResponseData(); + pigeonReturn.setStatusCode(statusCode); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(statusCode); + return toListResult; + } + + static @NonNull WebResourceResponseData fromList(@NonNull ArrayList list) { + WebResourceResponseData pigeonResult = new WebResourceResponseData(); + Object statusCode = list.get(0); + pigeonResult.setStatusCode( + (statusCode == null) + ? null + : ((statusCode instanceof Integer) ? (Integer) statusCode : (Long) statusCode)); + return pigeonResult; + } + } + /** Generated class from Pigeon that represents data sent in messages. */ public static final class WebResourceErrorData { private @NonNull Long errorCode; @@ -2075,6 +2127,8 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { return WebResourceErrorData.fromList((ArrayList) readValue(buffer)); case (byte) 129: return WebResourceRequestData.fromList((ArrayList) readValue(buffer)); + case (byte) 130: + return WebResourceResponseData.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); } @@ -2088,6 +2142,9 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { } else if (value instanceof WebResourceRequestData) { stream.write(129); writeValue(stream, ((WebResourceRequestData) value).toList()); + } else if (value instanceof WebResourceResponseData) { + stream.write(130); + writeValue(stream, ((WebResourceResponseData) value).toList()); } else { super.writeValue(stream, value); } @@ -2142,6 +2199,23 @@ public void onPageFinished( channelReply -> callback.reply(null)); } + public void onReceivedHttpError( + @NonNull Long instanceIdArg, + @NonNull Long webViewInstanceIdArg, + @NonNull WebResourceRequestData requestArg, + @NonNull WebResourceResponseData responseArg, + @NonNull Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedHttpError", + getCodec()); + channel.send( + new ArrayList( + Arrays.asList(instanceIdArg, webViewInstanceIdArg, requestArg, responseArg)), + channelReply -> callback.reply(null)); + } + public void onReceivedRequestError( @NonNull Long instanceIdArg, @NonNull Long webViewInstanceIdArg, diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java index 4dee9c0ad3f..9040d4fb63c 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java @@ -8,6 +8,7 @@ import android.os.Build; import android.webkit.WebResourceError; import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; import android.webkit.WebView; import android.webkit.WebViewClient; import androidx.annotation.NonNull; @@ -69,6 +70,16 @@ static GeneratedAndroidWebView.WebResourceRequestData createWebResourceRequestDa return requestData.build(); } + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + static GeneratedAndroidWebView.WebResourceResponseData createWebResourceResponseData( + WebResourceResponse response) { + final GeneratedAndroidWebView.WebResourceResponseData.Builder responseData = + new GeneratedAndroidWebView.WebResourceResponseData.Builder() + .setStatusCode((long) response.getStatusCode()); + + return responseData.build(); + } + /** * Creates a Flutter api that sends messages to Dart. * @@ -109,6 +120,25 @@ public void onPageFinished( onPageFinished(getIdentifierForClient(webViewClient), webViewIdentifier, urlArg, callback); } + /** Passes arguments from {@link WebViewClient#onReceivedHttpError} to Dart. */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public void onReceivedHttpError( + @NonNull WebViewClient webViewClient, + @NonNull WebView webView, + @NonNull WebResourceRequest request, + @NonNull WebResourceResponse response, + @NonNull Reply callback) { + webViewFlutterApi.create(webView, reply -> {}); + + final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView); + onReceivedHttpError( + getIdentifierForClient(webViewClient), + webViewIdentifier, + createWebResourceRequestData(request), + createWebResourceResponseData(response), + callback); + } + /** * Passes arguments from {@link WebViewClient#onReceivedError(WebView, WebResourceRequest, * WebResourceError)} to Dart. diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java index 35ae02a7625..123debc1473 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java @@ -11,6 +11,7 @@ import android.view.KeyEvent; import android.webkit.WebResourceError; import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; import android.webkit.WebView; import android.webkit.WebViewClient; import androidx.annotation.NonNull; @@ -54,6 +55,14 @@ public void onPageFinished(@NonNull WebView view, @NonNull String url) { flutterApi.onPageFinished(this, view, url, reply -> {}); } + @Override + public void onReceivedHttpError( + @NonNull WebView view, + @NonNull WebResourceRequest request, + @NonNull WebResourceResponse response) { + flutterApi.onReceivedHttpError(this, view, request, response, reply -> {}); + } + @Override public void onReceivedError( @NonNull WebView view, @@ -130,6 +139,15 @@ public void onPageFinished(@NonNull WebView view, @NonNull String url) { flutterApi.onPageFinished(this, view, url, reply -> {}); } + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @Override + public void onReceivedHttpError( + @NonNull WebView view, + @NonNull WebResourceRequest request, + @NonNull WebResourceResponse response) { + flutterApi.onReceivedHttpError(this, view, request, response, reply -> {}); + } + // This method is only called when the WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR feature is // enabled. The deprecated method is called when a device doesn't support this. @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) diff --git a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart index 188687b6231..795841f9d4e 100644 --- a/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart @@ -859,6 +859,74 @@ Future main() async { await pageFinishCompleter.future; }); + testWidgets('onHttpError', (WidgetTester tester) async { + final Completer errorCompleter = + Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + )..setOnHttpError((HttpResponseError error) { + errorCompleter.complete(error); + }), + ) + ..loadRequest( + LoadRequestParams(uri: Uri.parse('$prefixUrl/favicon.ico')), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + final HttpResponseError error = await errorCompleter.future; + + expect(error, isNotNull); + expect(error.statusCode, 404); + }); + + testWidgets('onHttpError is not called when no HTTP error is received', + (WidgetTester tester) async { + final Completer errorCompleter = + Completer(); + final Completer pageFinishCompleter = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + PlatformNavigationDelegate( + const PlatformNavigationDelegateCreationParams(), + ) + ..setOnPageFinished((_) => pageFinishCompleter.complete()) + ..setOnHttpError((HttpResponseError error) { + errorCompleter.complete(error); + }), + ) + ..loadRequest( + LoadRequestParams(uri: Uri.parse('https://www.google.com')), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + expect(errorCompleter.future, doesNotComplete); + await pageFinishCompleter.future; + }); + testWidgets('can block requests', (WidgetTester tester) async { Completer pageLoaded = Completer(); diff --git a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart index b8c9f7bc8cb..bf18cf57ece 100644 --- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart @@ -107,6 +107,9 @@ class _WebViewExampleState extends State { ..setOnPageFinished((String url) { debugPrint('Page finished loading: $url'); }) + ..setOnHttpError((HttpResponseError error) { + debugPrint('Http error occured on page: ${error.statusCode}'); + }) ..setOnWebResourceError((WebResourceError error) { debugPrint(''' Page resource error: diff --git a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml index 8a190ff22eb..3b93278f706 100644 --- a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ - webview_flutter_platform_interface: ^2.1.0 + webview_flutter_platform_interface: ^2.2.0 dev_dependencies: espresso: ^0.2.0 diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart index 0d716220b05..5cbd350d76d 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_proxy.dart @@ -41,6 +41,11 @@ class AndroidWebViewProxy { final android_webview.WebViewClient Function({ 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.WebResourceResponse response, + )? onReceivedHttpError, void Function( android_webview.WebView webView, android_webview.WebResourceRequest request, diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart index 8ab89d4b875..5ba5b17b11f 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart @@ -673,6 +673,7 @@ class WebViewClient extends JavaObject { WebViewClient({ this.onPageStarted, this.onPageFinished, + this.onReceivedHttpError, this.onReceivedRequestError, @Deprecated('Only called on Android version < 23.') this.onReceivedError, this.requestLoading, @@ -693,6 +694,7 @@ class WebViewClient extends JavaObject { WebViewClient.detached({ this.onPageStarted, this.onPageFinished, + this.onReceivedHttpError, this.onReceivedRequestError, @Deprecated('Only called on Android version < 23.') this.onReceivedError, this.requestLoading, @@ -804,6 +806,15 @@ class WebViewClient extends JavaObject { /// reflect the state of the DOM at this point. final void Function(WebView webView, String url)? onPageFinished; + /// Notify the host application that an HTTP error has been received from the + /// server while loading a resource. + /// + /// HTTP errors have status codes >= 400. This callback will be called for any + /// resource (iframe, image, etc.), not just for the main page. Thus, it is + /// recommended to perform minimum required work in this callback. + final void Function(WebView webView, WebResourceRequest request, + WebResourceResponse response)? onReceivedHttpError; + /// Report web resource loading error to the host application. /// /// These errors usually indicate inability to connect to the server. Note @@ -869,6 +880,7 @@ class WebViewClient extends JavaObject { return WebViewClient.detached( onPageStarted: onPageStarted, onPageFinished: onPageFinished, + onReceivedHttpError: onReceivedHttpError, onReceivedRequestError: onReceivedRequestError, onReceivedError: onReceivedError, requestLoading: requestLoading, @@ -1091,6 +1103,19 @@ class WebResourceRequest { final Map requestHeaders; } +/// Encapsulates information about the web resource response. +/// +/// See [WebViewClient.onReceivedHttpError]. +class WebResourceResponse { + /// Constructs a [WebResourceResponse]. + WebResourceResponse({ + required this.statusCode, + }); + + /// The HTTP status code associated with the response. + final int statusCode; +} + /// Encapsulates information about errors occurred during loading of web resources. /// /// See [WebViewClient.onReceivedRequestError]. diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart index f5a4564802a..4fa310cee5e 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.g.dart @@ -100,6 +100,27 @@ class WebResourceRequestData { } } +class WebResourceResponseData { + WebResourceResponseData({ + required this.statusCode, + }); + + int statusCode; + + Object encode() { + return [ + statusCode, + ]; + } + + static WebResourceResponseData decode(Object result) { + result as List; + return WebResourceResponseData( + statusCode: result[0]! as int, + ); + } +} + class WebResourceErrorData { WebResourceErrorData({ required this.errorCode, @@ -1478,6 +1499,9 @@ class _WebViewClientFlutterApiCodec extends StandardMessageCodec { } else if (value is WebResourceRequestData) { buffer.putUint8(129); writeValue(buffer, value.encode()); + } else if (value is WebResourceResponseData) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -1490,6 +1514,8 @@ class _WebViewClientFlutterApiCodec extends StandardMessageCodec { return WebResourceErrorData.decode(readValue(buffer)!); case 129: return WebResourceRequestData.decode(readValue(buffer)!); + case 130: + return WebResourceResponseData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -1503,6 +1529,9 @@ abstract class WebViewClientFlutterApi { void onPageFinished(int instanceId, int webViewInstanceId, String url); + void onReceivedHttpError(int instanceId, int webViewInstanceId, + WebResourceRequestData request, WebResourceResponseData response); + void onReceivedRequestError(int instanceId, int webViewInstanceId, WebResourceRequestData request, WebResourceErrorData error); @@ -1569,6 +1598,38 @@ abstract class WebViewClientFlutterApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedHttpError', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedHttpError was null.'); + final List args = (message as List?)!; + final int? arg_instanceId = (args[0] as int?); + assert(arg_instanceId != null, + 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedHttpError was null, expected non-null int.'); + final int? arg_webViewInstanceId = (args[1] as int?); + assert(arg_webViewInstanceId != null, + 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedHttpError was null, expected non-null int.'); + final WebResourceRequestData? arg_request = + (args[2] as WebResourceRequestData?); + assert(arg_request != null, + 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedHttpError was null, expected non-null WebResourceRequestData.'); + final WebResourceResponseData? arg_response = + (args[3] as WebResourceResponseData?); + assert(arg_response != null, + 'Argument for dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedHttpError was null, expected non-null WebResourceResponseData.'); + api.onReceivedHttpError(arg_instanceId!, arg_webViewInstanceId!, + arg_request!, arg_response!); + return; + }); + } + } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WebViewClientFlutterApi.onReceivedRequestError', 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 0bc2c84c409..df4526bd8b8 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 @@ -27,6 +27,13 @@ WebResourceRequest _toWebResourceRequest(WebResourceRequestData data) { ); } +/// Converts [WebResourceResponseData] to [WebResourceResponse] +WebResourceResponse _toWebResourceResponse(WebResourceResponseData data) { + return WebResourceResponse( + statusCode: data.statusCode, + ); +} + /// Converts [WebResourceErrorData] to [WebResourceError]. WebResourceError _toWebResourceError(WebResourceErrorData data) { return WebResourceError( @@ -647,6 +654,34 @@ class WebViewClientFlutterApiImpl extends WebViewClientFlutterApi { } } + @override + void onReceivedHttpError( + int instanceId, + int webViewInstanceId, + WebResourceRequestData request, + WebResourceResponseData response, + ) { + final WebViewClient? instance = instanceManager + .getInstanceWithWeakReference(instanceId) as WebViewClient?; + final WebView? webViewInstance = instanceManager + .getInstanceWithWeakReference(webViewInstanceId) as WebView?; + assert( + instance != null, + 'InstanceManager does not contain an WebViewClient with instanceId: $instanceId', + ); + assert( + webViewInstance != null, + 'InstanceManager does not contain an WebView with instanceId: $webViewInstanceId', + ); + if (instance!.onReceivedHttpError != null) { + instance.onReceivedHttpError!( + webViewInstance!, + _toWebResourceRequest(request), + _toWebResourceResponse(response), + ); + } + } + @override void onReceivedError( int instanceId, diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart index 88a4bca84ab..b041dbd7dce 100644 --- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_controller.dart @@ -672,6 +672,16 @@ class AndroidWebResourceError extends WebResourceError { } } +/// Error returned in `AndroidNavigationDelegate.setOnHttpError` when an HTTP +/// response error has been received. +@immutable +class AndroidHttpResponseError extends HttpResponseError { + /// Creates a new [AndroidHttpResponseError]. + const AndroidHttpResponseError._({ + required super.statusCode, + }); +} + /// Object specifying creation parameters for creating a [AndroidNavigationDelegate]. /// /// When adding additional fields make sure they can be null or have a default @@ -740,6 +750,16 @@ class AndroidNavigationDelegate extends PlatformNavigationDelegate { callback(url); } }, + onReceivedHttpError: ( + android_webview.WebView webView, + android_webview.WebResourceRequest request, + android_webview.WebResourceResponse response, + ) { + if (weakThis.target?._onHttpError != null) { + weakThis.target!._onHttpError!( + HttpResponseError(statusCode: response.statusCode)); + } + }, onReceivedRequestError: ( android_webview.WebView webView, android_webview.WebResourceRequest request, @@ -847,6 +867,7 @@ class AndroidNavigationDelegate extends PlatformNavigationDelegate { PageEventCallback? _onPageFinished; PageEventCallback? _onPageStarted; + HttpResponseErrorCallback? _onHttpError; ProgressCallback? _onProgress; WebResourceErrorCallback? _onWebResourceError; NavigationRequestCallback? _onNavigationRequest; @@ -919,6 +940,13 @@ class AndroidNavigationDelegate extends PlatformNavigationDelegate { _onPageFinished = onPageFinished; } + @override + Future setOnHttpError( + HttpResponseErrorCallback onHttpError, + ) async { + _onHttpError = onHttpError; + } + @override Future setOnProgress( ProgressCallback onProgress, diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart index 1d232b24179..532099e8086 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart @@ -81,6 +81,14 @@ class WebResourceRequestData { Map requestHeaders; } +class WebResourceResponseData { + WebResourceResponseData( + this.statusCode, + ); + + int statusCode; +} + class WebResourceErrorData { WebResourceErrorData(this.errorCode, this.description); @@ -270,6 +278,13 @@ abstract class WebViewClientFlutterApi { void onPageFinished(int instanceId, int webViewInstanceId, String url); + void onReceivedHttpError( + int instanceId, + int webViewInstanceId, + WebResourceRequestData request, + WebResourceResponseData response, + ); + void onReceivedRequestError( int instanceId, int webViewInstanceId, diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 1802f7e32d8..4a61f3aaf35 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 3.5.3 +version: 3.6.0 environment: sdk: ">=2.18.0 <4.0.0" @@ -20,7 +20,7 @@ flutter: dependencies: flutter: sdk: flutter - webview_flutter_platform_interface: ^2.1.0 + webview_flutter_platform_interface: ^2.2.0 dev_dependencies: build_runner: ^2.1.4 diff --git a/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart index 1801589f885..edc711a0ec4 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart @@ -55,6 +55,29 @@ void main() { expect(callbackUrl, 'https://www.google.com'); }); + test('onHttpError from onReceivedHttpError', () { + final AndroidNavigationDelegate androidNavigationDelegate = + AndroidNavigationDelegate(_buildCreationParams()); + + late final HttpResponseError callbackError; + androidNavigationDelegate.setOnHttpError( + (HttpResponseError htppError) => callbackError = htppError); + + CapturingWebViewClient.lastCreatedDelegate.onReceivedHttpError!( + android_webview.WebView.detached(), + android_webview.WebResourceRequest( + url: 'https://www.google.com', + isForMainFrame: false, + isRedirect: true, + hasGesture: true, + method: 'GET', + requestHeaders: {'X-Mock': 'mocking'}, + ), + android_webview.WebResourceResponse(statusCode: 401)); + + expect(callbackError.statusCode, 401); + }); + test('onWebResourceError from onReceivedRequestError', () { final AndroidNavigationDelegate androidNavigationDelegate = AndroidNavigationDelegate(_buildCreationParams()); @@ -488,6 +511,7 @@ class CapturingWebViewClient extends android_webview.WebViewClient { CapturingWebViewClient({ super.onPageFinished, super.onPageStarted, + super.onReceivedHttpError, super.onReceivedError, super.onReceivedRequestError, super.requestLoading, diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart index f224ff1d1e5..89f66f67736 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.dart @@ -87,6 +87,11 @@ void main() { onPageFinished, void Function(android_webview.WebView webView, String url)? onPageStarted, + void Function( + android_webview.WebView webView, + android_webview.WebResourceRequest request, + android_webview.WebResourceResponse response)? + onReceivedHttpError, @Deprecated('Only called on Android version < 23.') void Function( android_webview.WebView webView, diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart index e0c637efdd1..faf810215ae 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_controller_test.mocks.dart @@ -323,6 +323,16 @@ class MockAndroidNavigationDelegate extends _i1.Mock returnValueForMissingStub: _i9.Future.value(), ) as _i9.Future); @override + _i9.Future setOnHttpError(_i3.HttpResponseErrorCallback? onHttpError) => + (super.noSuchMethod( + Invocation.method( + #setOnHttpError, + [onHttpError], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override _i9.Future setOnProgress(_i3.ProgressCallback? onProgress) => (super.noSuchMethod( Invocation.method( @@ -776,6 +786,11 @@ class MockAndroidWebViewProxy extends _i1.Mock String, String, )? onReceivedError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceResponse, + )? onReceivedHttpError, void Function( _i2.WebView, _i2.WebResourceRequest, @@ -811,6 +826,11 @@ class MockAndroidWebViewProxy extends _i1.Mock String, String, )? onReceivedError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceResponse, + )? onReceivedHttpError, void Function( _i2.WebView, _i2.WebResourceRequest, @@ -849,6 +869,11 @@ class MockAndroidWebViewProxy extends _i1.Mock String, String, )? onReceivedError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceResponse, + )? onReceivedHttpError, void Function( _i2.WebView, _i2.WebResourceRequest, @@ -887,6 +912,11 @@ class MockAndroidWebViewProxy extends _i1.Mock String, String, )? onReceivedError, + void Function( + _i2.WebView, + _i2.WebResourceRequest, + _i2.WebResourceResponse, + )? onReceivedHttpError, void Function( _i2.WebView, _i2.WebResourceRequest, diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart index 71c30b6e903..828e00e08f4 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart @@ -634,6 +634,37 @@ void main() { expect(result, [mockWebView, 'https://www.google.com']); }); + test('onReceivedHttpError', () { + late final List result; + when(mockWebViewClient.onReceivedHttpError).thenReturn( + ( + WebView webView, + WebResourceRequest request, + WebResourceResponse response, + ) { + result = [webView, request, response]; + }, + ); + + flutterApi.onReceivedHttpError( + mockWebViewClientInstanceId, + mockWebViewInstanceId, + WebResourceRequestData( + url: 'https://www.google.com', + isForMainFrame: true, + hasGesture: true, + method: 'GET', + isRedirect: false, + requestHeaders: {}, + ), + WebResourceResponseData( + statusCode: 401, + ), + ); + + expect(result, [mockWebView, isNotNull, isNotNull]); + }); + test('onReceivedRequestError', () { late final List result; when(mockWebViewClient.onReceivedRequestError).thenReturn( diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md index dc83b548e65..9b959214215 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md @@ -1,3 +1,8 @@ +## 3.4.0 + +* Adds `decidePolicyForNavigationResponse` to internal WKNavigationDelegate to support the + `onHttpError` callback. + ## 3.3.0 * Adds support for `PlatformNavigationDelegate.onUrlChange`. diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart index 42b55556782..dc734c155db 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/integration_test/webview_flutter_test.dart @@ -919,6 +919,78 @@ Future main() async { }, ); + testWidgets('onHttpError', (WidgetTester tester) async { + final Completer errorCompleter = + Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + WebKitWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + WebKitNavigationDelegate( + const WebKitNavigationDelegateCreationParams(), + )..setOnHttpError((HttpResponseError error) { + errorCompleter.complete(error); + }), + ) + ..loadRequest( + LoadRequestParams(uri: Uri.parse('$prefixUrl/favicon.ico')), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + final HttpResponseError error = await errorCompleter.future; + + expect(error, isNotNull); + expect(error.statusCode, 404); + }); + + testWidgets('onHttpError is not called when no HTTP error is received', + (WidgetTester tester) async { + final Completer errorCompleter = + Completer(); + final Completer pageFinishCompleter = Completer(); + + final PlatformWebViewController controller = PlatformWebViewController( + WebKitWebViewControllerCreationParams(), + ) + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setPlatformNavigationDelegate( + WebKitNavigationDelegate( + const WebKitNavigationDelegateCreationParams(), + ) + ..setOnPageFinished((_) => pageFinishCompleter.complete()) + ..setOnHttpError((HttpResponseError error) { + errorCompleter.complete(error); + }), + ) + ..loadRequest( + LoadRequestParams( + uri: Uri.parse( + 'https://www.google.nl', + ), + ), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + expect(errorCompleter.future, doesNotComplete); + await pageFinishCompleter.future; + }); + testWidgets('can block requests', (WidgetTester tester) async { Completer pageLoaded = Completer(); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart index a793c2ca00e..2b7054c8ccd 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/lib/main.dart @@ -108,6 +108,9 @@ class _WebViewExampleState extends State { ..setOnPageFinished((String url) { debugPrint('Page finished loading: $url'); }) + ..setOnHttpError((HttpResponseError error) { + debugPrint('Error occured on page: ${error.statusCode}'); + }) ..setOnWebResourceError((WebResourceError error) { debugPrint(''' Page resource error: diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml index b1ee0822395..2c5e7efd6f5 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: flutter: sdk: flutter path_provider: ^2.0.6 - webview_flutter_platform_interface: ^2.1.0 + webview_flutter_platform_interface: ^2.2.0 webview_flutter_wkwebview: # When depending on this package from a real application you should use: # webview_flutter: ^x.y.z diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.h index d2e9bdd71ce..90e70393f0e 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.h @@ -103,6 +103,25 @@ extern FWFWKNavigationActionData *FWFWKNavigationActionDataFromNavigationAction( */ extern FWFNSUrlRequestData *FWFNSUrlRequestDataFromNSURLRequest(NSURLRequest *request); +/** + * Converts a WKNavigationResponse to an FWFWKNavigationResponseData. + * + * @param response The object containing information to create a WKNavigationResponseData. + * + * @return A FWFWKNavigationResponseData. + */ +extern FWFWKNavigationResponseData *FWFWKNavigationResponseDataFromNativeNavigationResponse( + WKNavigationResponse *response); +/** + * Converts a NSURLResponse to an FWFNSHttpUrlResponseData. + * + * @param response The object containing information to create a WKNavigationActionData. + * + * @return A FWFNSHttpUrlResponseData. + */ +extern FWFNSHttpUrlResponseData *FWFNSHttpUrlResponseDataFromNativeNSURLResponse( + NSURLResponse *response); + /** * Converts a WKFrameInfo to an FWFWKFrameInfoData. * @@ -122,6 +141,16 @@ extern FWFWKFrameInfoData *FWFWKFrameInfoDataFromWKFrameInfo(WKFrameInfo *info); extern WKNavigationActionPolicy FWFWKNavigationActionPolicyFromEnumData( FWFWKNavigationActionPolicyEnumData *data); +/** + * Converts an FWFWKNavigationResponsePolicyEnumData to a WKNavigationResponsePolicy. + * + * @param data The data object containing information to create a WKNavigationResponsePolicy. + * + * @return A WKNavigationResponsePolicy or -1 if data could not be converted. + */ +extern WKNavigationResponsePolicy FWFNativeWKNavigationResponsePolicyFromEnumData( + FWFWKNavigationResponsePolicyEnumData *data); + /** * Converts a NSError to an FWFNSErrorData. * diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.m index 5fbbf2e21a7..67acb1317af 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.m @@ -175,6 +175,24 @@ WKAudiovisualMediaTypes FWFWKAudiovisualMediaTypeFromEnumData( allHttpHeaderFields:request.allHTTPHeaderFields ? request.allHTTPHeaderFields : @{}]; } +FWFWKNavigationResponseData *FWFWKNavigationResponseDataFromNativeNavigationResponse( + WKNavigationResponse *response) { + return [FWFWKNavigationResponseData + makeWithResponse:FWFNSHttpUrlResponseDataFromNativeNSURLResponse(response.response) + forMainFrame:@(response.forMainFrame)]; +} + +/// Cast the NSURLResponse object to NSHTTPURLResponse. +/// +/// NSURLResponse doesn't contain the status code so it must be cast to NSHTTPURLResponse. +/// This cast will always succeed because the NSURLResponse object actually is an instance of +/// NSHTTPURLResponse. See: +/// https://developer.apple.com/documentation/foundation/nsurlresponse#overview +FWFNSHttpUrlResponseData *FWFNSHttpUrlResponseDataFromNativeNSURLResponse(NSURLResponse *response) { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + return [FWFNSHttpUrlResponseData makeWithStatusCode:@(httpResponse.statusCode)]; +} + FWFWKFrameInfoData *FWFWKFrameInfoDataFromWKFrameInfo(WKFrameInfo *info) { return [FWFWKFrameInfoData makeWithIsMainFrame:@(info.isMainFrame)]; } @@ -191,6 +209,18 @@ WKNavigationActionPolicy FWFWKNavigationActionPolicyFromEnumData( return -1; } +WKNavigationResponsePolicy FWFNativeWKNavigationResponsePolicyFromEnumData( + FWFWKNavigationResponsePolicyEnumData *data) { + switch (data.value) { + case FWFWKNavigationResponsePolicyEnumAllow: + return WKNavigationResponsePolicyAllow; + case FWFWKNavigationResponsePolicyEnumCancel: + return WKNavigationResponsePolicyCancel; + } + + return -1; +} + FWFNSErrorData *FWFNSErrorDataFromNSError(NSError *error) { return [FWFNSErrorData makeWithCode:@(error.code) domain:error.domain diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.h b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.h index 1eb95ff890a..1da02681673 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.h @@ -87,6 +87,14 @@ typedef NS_ENUM(NSUInteger, FWFWKNavigationActionPolicyEnum) { FWFWKNavigationActionPolicyEnumCancel = 1, }; +/// Mirror of WKNavigationResponsePolicy. +/// +/// See https://developer.apple.com/documentation/webkit/wknavigationactionpolicy?language=objc. +typedef NS_ENUM(NSUInteger, FWFWKNavigationResponsePolicyEnum) { + FWFWKNavigationResponsePolicyEnumAllow = 0, + FWFWKNavigationResponsePolicyEnumCancel = 1, +}; + /// Mirror of NSHTTPCookiePropertyKey. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookiepropertykey. @@ -151,10 +159,13 @@ typedef NS_ENUM(NSUInteger, FWFWKNavigationType) { @class FWFWKAudiovisualMediaTypeEnumData; @class FWFWKWebsiteDataTypeEnumData; @class FWFWKNavigationActionPolicyEnumData; +@class FWFWKNavigationResponsePolicyEnumData; @class FWFNSHttpCookiePropertyKeyEnumData; @class FWFNSUrlRequestData; +@class FWFNSHttpUrlResponseData; @class FWFWKUserScriptData; @class FWFWKNavigationActionData; +@class FWFWKNavigationResponseData; @class FWFWKFrameInfoData; @class FWFNSErrorData; @class FWFWKScriptMessageData; @@ -203,6 +214,13 @@ typedef NS_ENUM(NSUInteger, FWFWKNavigationType) { @property(nonatomic, assign) FWFWKNavigationActionPolicyEnum value; @end +@interface FWFWKNavigationResponsePolicyEnumData : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithValue:(FWFWKNavigationResponsePolicyEnum)value; +@property(nonatomic, assign) FWFWKNavigationResponsePolicyEnum value; +@end + @interface FWFNSHttpCookiePropertyKeyEnumData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; @@ -226,6 +244,16 @@ typedef NS_ENUM(NSUInteger, FWFWKNavigationType) { @property(nonatomic, strong) NSDictionary *allHttpHeaderFields; @end +/// Mirror of NSURLResponse. +/// +/// See https://developer.apple.com/documentation/foundation/nshttpurlresponse?language=objc. +@interface FWFNSHttpUrlResponseData : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithStatusCode:(NSNumber *)statusCode; +@property(nonatomic, strong) NSNumber *statusCode; +@end + /// Mirror of WKUserScript. /// /// See https://developer.apple.com/documentation/webkit/wkuserscript?language=objc. @@ -254,6 +282,18 @@ typedef NS_ENUM(NSUInteger, FWFWKNavigationType) { @property(nonatomic, assign) FWFWKNavigationType navigationType; @end +/// Mirror of WKNavigationResponse. +/// +/// See https://developer.apple.com/documentation/webkit/wknavigationresponse. +@interface FWFWKNavigationResponseData : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithResponse:(FWFNSHttpUrlResponseData *)response + forMainFrame:(NSNumber *)forMainFrame; +@property(nonatomic, strong) FWFNSHttpUrlResponseData *response; +@property(nonatomic, strong) NSNumber *forMainFrame; +@end + /// Mirror of WKFrameInfo. /// /// See https://developer.apple.com/documentation/webkit/wkframeinfo?language=objc. @@ -545,6 +585,16 @@ NSObject *FWFWKNavigationDelegateFlutterApiGetCodec(void); (void (^)(FWFWKNavigationActionPolicyEnumData *_Nullable, FlutterError *_Nullable))completion; +- (void) + decidePolicyForNavigationResponseForDelegateWithIdentifier:(NSNumber *)identifier + webViewIdentifier:(NSNumber *)webViewIdentifier + navigationResponse: + (FWFWKNavigationResponseData *)navigationResponse + completion: + (void (^)( + FWFWKNavigationResponsePolicyEnumData + *_Nullable, + FlutterError *_Nullable))completion; - (void)didFailNavigationForDelegateWithIdentifier:(NSNumber *)identifier webViewIdentifier:(NSNumber *)webViewIdentifier error:(FWFNSErrorData *)error diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.m index 610bc020b32..e98706b5345 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.m @@ -60,6 +60,12 @@ + (nullable FWFWKNavigationActionPolicyEnumData *)nullableFromList:(NSArray *)li - (NSArray *)toList; @end +@interface FWFWKNavigationResponsePolicyEnumData () ++ (FWFWKNavigationResponsePolicyEnumData *)fromList:(NSArray *)list; ++ (nullable FWFWKNavigationResponsePolicyEnumData *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + @interface FWFNSHttpCookiePropertyKeyEnumData () + (FWFNSHttpCookiePropertyKeyEnumData *)fromList:(NSArray *)list; + (nullable FWFNSHttpCookiePropertyKeyEnumData *)nullableFromList:(NSArray *)list; @@ -72,6 +78,12 @@ + (nullable FWFNSUrlRequestData *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end +@interface FWFNSHttpUrlResponseData () ++ (FWFNSHttpUrlResponseData *)fromList:(NSArray *)list; ++ (nullable FWFNSHttpUrlResponseData *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + @interface FWFWKUserScriptData () + (FWFWKUserScriptData *)fromList:(NSArray *)list; + (nullable FWFWKUserScriptData *)nullableFromList:(NSArray *)list; @@ -84,6 +96,12 @@ + (nullable FWFWKNavigationActionData *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end +@interface FWFWKNavigationResponseData () ++ (FWFWKNavigationResponseData *)fromList:(NSArray *)list; ++ (nullable FWFWKNavigationResponseData *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + @interface FWFWKFrameInfoData () + (FWFWKFrameInfoData *)fromList:(NSArray *)list; + (nullable FWFWKFrameInfoData *)nullableFromList:(NSArray *)list; @@ -248,6 +266,29 @@ - (NSArray *)toList { } @end +@implementation FWFWKNavigationResponsePolicyEnumData ++ (instancetype)makeWithValue:(FWFWKNavigationResponsePolicyEnum)value { + FWFWKNavigationResponsePolicyEnumData *pigeonResult = + [[FWFWKNavigationResponsePolicyEnumData alloc] init]; + pigeonResult.value = value; + return pigeonResult; +} ++ (FWFWKNavigationResponsePolicyEnumData *)fromList:(NSArray *)list { + FWFWKNavigationResponsePolicyEnumData *pigeonResult = + [[FWFWKNavigationResponsePolicyEnumData alloc] init]; + pigeonResult.value = [GetNullableObjectAtIndex(list, 0) integerValue]; + return pigeonResult; +} ++ (nullable FWFWKNavigationResponsePolicyEnumData *)nullableFromList:(NSArray *)list { + return (list) ? [FWFWKNavigationResponsePolicyEnumData fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + @(self.value), + ]; +} +@end + @implementation FWFNSHttpCookiePropertyKeyEnumData + (instancetype)makeWithValue:(FWFNSHttpCookiePropertyKeyEnum)value { FWFNSHttpCookiePropertyKeyEnumData *pigeonResult = @@ -306,6 +347,28 @@ - (NSArray *)toList { } @end +@implementation FWFNSHttpUrlResponseData ++ (instancetype)makeWithStatusCode:(NSNumber *)statusCode { + FWFNSHttpUrlResponseData *pigeonResult = [[FWFNSHttpUrlResponseData alloc] init]; + pigeonResult.statusCode = statusCode; + return pigeonResult; +} ++ (FWFNSHttpUrlResponseData *)fromList:(NSArray *)list { + FWFNSHttpUrlResponseData *pigeonResult = [[FWFNSHttpUrlResponseData alloc] init]; + pigeonResult.statusCode = GetNullableObjectAtIndex(list, 0); + NSAssert(pigeonResult.statusCode != nil, @""); + return pigeonResult; +} ++ (nullable FWFNSHttpUrlResponseData *)nullableFromList:(NSArray *)list { + return (list) ? [FWFNSHttpUrlResponseData fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + (self.statusCode ?: [NSNull null]), + ]; +} +@end + @implementation FWFWKUserScriptData + (instancetype)makeWithSource:(NSString *)source injectionTime:(nullable FWFWKUserScriptInjectionTimeEnumData *)injectionTime @@ -370,6 +433,34 @@ - (NSArray *)toList { } @end +@implementation FWFWKNavigationResponseData ++ (instancetype)makeWithResponse:(FWFNSHttpUrlResponseData *)response + forMainFrame:(NSNumber *)forMainFrame { + FWFWKNavigationResponseData *pigeonResult = [[FWFWKNavigationResponseData alloc] init]; + pigeonResult.response = response; + pigeonResult.forMainFrame = forMainFrame; + return pigeonResult; +} ++ (FWFWKNavigationResponseData *)fromList:(NSArray *)list { + FWFWKNavigationResponseData *pigeonResult = [[FWFWKNavigationResponseData alloc] init]; + pigeonResult.response = + [FWFNSHttpUrlResponseData nullableFromList:(GetNullableObjectAtIndex(list, 0))]; + NSAssert(pigeonResult.response != nil, @""); + pigeonResult.forMainFrame = GetNullableObjectAtIndex(list, 1); + NSAssert(pigeonResult.forMainFrame != nil, @""); + return pigeonResult; +} ++ (nullable FWFWKNavigationResponseData *)nullableFromList:(NSArray *)list { + return (list) ? [FWFWKNavigationResponseData fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + (self.response ? [self.response toList] : [NSNull null]), + (self.forMainFrame ?: [NSNull null]), + ]; +} +@end + @implementation FWFWKFrameInfoData + (instancetype)makeWithIsMainFrame:(NSNumber *)isMainFrame { FWFWKFrameInfoData *pigeonResult = [[FWFWKFrameInfoData alloc] init]; @@ -1390,13 +1481,19 @@ - (nullable id)readValueOfType:(UInt8)type { case 128: return [FWFNSErrorData fromList:[self readValue]]; case 129: - return [FWFNSUrlRequestData fromList:[self readValue]]; + return [FWFNSHttpUrlResponseData fromList:[self readValue]]; case 130: - return [FWFWKFrameInfoData fromList:[self readValue]]; + return [FWFNSUrlRequestData fromList:[self readValue]]; case 131: - return [FWFWKNavigationActionData fromList:[self readValue]]; + return [FWFWKFrameInfoData fromList:[self readValue]]; case 132: + return [FWFWKNavigationActionData fromList:[self readValue]]; + case 133: return [FWFWKNavigationActionPolicyEnumData fromList:[self readValue]]; + case 134: + return [FWFWKNavigationResponseData fromList:[self readValue]]; + case 135: + return [FWFWKNavigationResponsePolicyEnumData fromList:[self readValue]]; default: return [super readValueOfType:type]; } @@ -1410,18 +1507,27 @@ - (void)writeValue:(id)value { if ([value isKindOfClass:[FWFNSErrorData class]]) { [self writeByte:128]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFNSUrlRequestData class]]) { + } else if ([value isKindOfClass:[FWFNSHttpUrlResponseData class]]) { [self writeByte:129]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKFrameInfoData class]]) { + } else if ([value isKindOfClass:[FWFNSUrlRequestData class]]) { [self writeByte:130]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKNavigationActionData class]]) { + } else if ([value isKindOfClass:[FWFWKFrameInfoData class]]) { [self writeByte:131]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKNavigationActionPolicyEnumData class]]) { + } else if ([value isKindOfClass:[FWFWKNavigationActionData class]]) { [self writeByte:132]; [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[FWFWKNavigationActionPolicyEnumData class]]) { + [self writeByte:133]; + [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[FWFWKNavigationResponseData class]]) { + [self writeByte:134]; + [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[FWFWKNavigationResponsePolicyEnumData class]]) { + [self writeByte:135]; + [self writeValue:[value toList]]; } else { [super writeValue:value]; } @@ -1521,6 +1627,30 @@ - (void)decidePolicyForNavigationActionForDelegateWithIdentifier:(NSNumber *)arg completion(output, nil); }]; } +- (void) + decidePolicyForNavigationResponseForDelegateWithIdentifier:(NSNumber *)arg_identifier + webViewIdentifier:(NSNumber *)arg_webViewIdentifier + navigationResponse:(FWFWKNavigationResponseData *) + arg_navigationResponse + completion: + (void (^)( + FWFWKNavigationResponsePolicyEnumData + *_Nullable, + FlutterError *_Nullable))completion { + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName: + @"dev.flutter.pigeon.WKNavigationDelegateFlutterApi.decidePolicyForNavigationResponse" + binaryMessenger:self.binaryMessenger + codec:FWFWKNavigationDelegateFlutterApiGetCodec()]; + [channel sendMessage:@[ + arg_identifier ?: [NSNull null], arg_webViewIdentifier ?: [NSNull null], + arg_navigationResponse ?: [NSNull null] + ] + reply:^(id reply) { + FWFWKNavigationResponsePolicyEnumData *output = reply; + completion(output, nil); + }]; +} - (void)didFailNavigationForDelegateWithIdentifier:(NSNumber *)arg_identifier webViewIdentifier:(NSNumber *)arg_webViewIdentifier error:(FWFNSErrorData *)arg_error @@ -1816,28 +1946,34 @@ - (nullable id)readValueOfType:(UInt8)type { case 130: return [FWFNSHttpCookiePropertyKeyEnumData fromList:[self readValue]]; case 131: - return [FWFNSKeyValueChangeKeyEnumData fromList:[self readValue]]; + return [FWFNSHttpUrlResponseData fromList:[self readValue]]; case 132: - return [FWFNSKeyValueObservingOptionsEnumData fromList:[self readValue]]; + return [FWFNSKeyValueChangeKeyEnumData fromList:[self readValue]]; case 133: - return [FWFNSUrlRequestData fromList:[self readValue]]; + return [FWFNSKeyValueObservingOptionsEnumData fromList:[self readValue]]; case 134: - return [FWFObjectOrIdentifier fromList:[self readValue]]; + return [FWFNSUrlRequestData fromList:[self readValue]]; case 135: - return [FWFWKAudiovisualMediaTypeEnumData fromList:[self readValue]]; + return [FWFObjectOrIdentifier fromList:[self readValue]]; case 136: - return [FWFWKFrameInfoData fromList:[self readValue]]; + return [FWFWKAudiovisualMediaTypeEnumData fromList:[self readValue]]; case 137: - return [FWFWKNavigationActionData fromList:[self readValue]]; + return [FWFWKFrameInfoData fromList:[self readValue]]; case 138: - return [FWFWKNavigationActionPolicyEnumData fromList:[self readValue]]; + return [FWFWKNavigationActionData fromList:[self readValue]]; case 139: - return [FWFWKScriptMessageData fromList:[self readValue]]; + return [FWFWKNavigationActionPolicyEnumData fromList:[self readValue]]; case 140: - return [FWFWKUserScriptData fromList:[self readValue]]; + return [FWFWKNavigationResponseData fromList:[self readValue]]; case 141: - return [FWFWKUserScriptInjectionTimeEnumData fromList:[self readValue]]; + return [FWFWKNavigationResponsePolicyEnumData fromList:[self readValue]]; case 142: + return [FWFWKScriptMessageData fromList:[self readValue]]; + case 143: + return [FWFWKUserScriptData fromList:[self readValue]]; + case 144: + return [FWFWKUserScriptInjectionTimeEnumData fromList:[self readValue]]; + case 145: return [FWFWKWebsiteDataTypeEnumData fromList:[self readValue]]; default: return [super readValueOfType:type]; @@ -1858,42 +1994,51 @@ - (void)writeValue:(id)value { } else if ([value isKindOfClass:[FWFNSHttpCookiePropertyKeyEnumData class]]) { [self writeByte:130]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFNSKeyValueChangeKeyEnumData class]]) { + } else if ([value isKindOfClass:[FWFNSHttpUrlResponseData class]]) { [self writeByte:131]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFNSKeyValueObservingOptionsEnumData class]]) { + } else if ([value isKindOfClass:[FWFNSKeyValueChangeKeyEnumData class]]) { [self writeByte:132]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFNSUrlRequestData class]]) { + } else if ([value isKindOfClass:[FWFNSKeyValueObservingOptionsEnumData class]]) { [self writeByte:133]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFObjectOrIdentifier class]]) { + } else if ([value isKindOfClass:[FWFNSUrlRequestData class]]) { [self writeByte:134]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKAudiovisualMediaTypeEnumData class]]) { + } else if ([value isKindOfClass:[FWFObjectOrIdentifier class]]) { [self writeByte:135]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKFrameInfoData class]]) { + } else if ([value isKindOfClass:[FWFWKAudiovisualMediaTypeEnumData class]]) { [self writeByte:136]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKNavigationActionData class]]) { + } else if ([value isKindOfClass:[FWFWKFrameInfoData class]]) { [self writeByte:137]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKNavigationActionPolicyEnumData class]]) { + } else if ([value isKindOfClass:[FWFWKNavigationActionData class]]) { [self writeByte:138]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKScriptMessageData class]]) { + } else if ([value isKindOfClass:[FWFWKNavigationActionPolicyEnumData class]]) { [self writeByte:139]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKUserScriptData class]]) { + } else if ([value isKindOfClass:[FWFWKNavigationResponseData class]]) { [self writeByte:140]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKUserScriptInjectionTimeEnumData class]]) { + } else if ([value isKindOfClass:[FWFWKNavigationResponsePolicyEnumData class]]) { [self writeByte:141]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[FWFWKWebsiteDataTypeEnumData class]]) { + } else if ([value isKindOfClass:[FWFWKScriptMessageData class]]) { [self writeByte:142]; [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[FWFWKUserScriptData class]]) { + [self writeByte:143]; + [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[FWFWKUserScriptInjectionTimeEnumData class]]) { + [self writeByte:144]; + [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[FWFWKWebsiteDataTypeEnumData class]]) { + [self writeByte:145]; + [self writeValue:[value toList]]; } else { [super writeValue:value]; } diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.m index d9cdfd98025..6144059c1ec 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFNavigationDelegateHostApi.m @@ -69,6 +69,24 @@ - (void)didStartProvisionalNavigationForDelegate:(FWFNavigationDelegate *)instan completion:completion]; } +- (void)decidePolicyForNavigationResponseForDelegate:(FWFNavigationDelegate *)instance + webView:(WKWebView *)webView + navigationResponse:(WKNavigationResponse *)navigationResponse + completion: + (void (^)( + FWFWKNavigationResponsePolicyEnumData *_Nullable, + FlutterError *_Nullable))completion { + NSNumber *webViewIdentifier = + @([self.instanceManager identifierWithStrongReferenceForInstance:webView]); + FWFWKNavigationResponseData *navigationResponseData = + FWFWKNavigationResponseDataFromNativeNavigationResponse(navigationResponse); + [self decidePolicyForNavigationResponseForDelegateWithIdentifier: + @([self identifierForDelegate:instance]) + webViewIdentifier:webViewIdentifier + navigationResponse:navigationResponseData + completion:completion]; +} + - (void)didFailNavigationForDelegate:(FWFNavigationDelegate *)instance webView:(WKWebView *)webView error:(NSError *)error @@ -152,6 +170,22 @@ - (void)webView:(WKWebView *)webView }]; } +- (void)webView:(WKWebView *)webView + decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse + decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler { + [self.navigationDelegateAPI + decidePolicyForNavigationResponseForDelegate:self + webView:webView + navigationResponse:navigationResponse + completion:^(FWFWKNavigationResponsePolicyEnumData *policy, + FlutterError *error) { + NSAssert(!error, @"%@", error); + decisionHandler( + FWFNativeWKNavigationResponsePolicyFromEnumData( + policy)); + }]; +} + - (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error { diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.g.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.g.dart index c44e52a7148..2158f70920e 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.g.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/common/web_kit.g.dart @@ -82,6 +82,14 @@ enum WKNavigationActionPolicyEnum { cancel, } +/// Mirror of WKNavigationResponsePolicy. +/// +/// See https://developer.apple.com/documentation/webkit/wknavigationactionpolicy?language=objc. +enum WKNavigationResponsePolicyEnum { + allow, + cancel, +} + /// Mirror of NSHTTPCookiePropertyKey. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookiepropertykey. @@ -264,6 +272,27 @@ class WKNavigationActionPolicyEnumData { } } +class WKNavigationResponsePolicyEnumData { + WKNavigationResponsePolicyEnumData({ + required this.value, + }); + + WKNavigationResponsePolicyEnum value; + + Object encode() { + return [ + value.index, + ]; + } + + static WKNavigationResponsePolicyEnumData decode(Object result) { + result as List; + return WKNavigationResponsePolicyEnumData( + value: WKNavigationResponsePolicyEnum.values[result[0]! as int], + ); + } +} + class NSHttpCookiePropertyKeyEnumData { NSHttpCookiePropertyKeyEnumData({ required this.value, @@ -325,6 +354,30 @@ class NSUrlRequestData { } } +/// Mirror of NSURLResponse. +/// +/// See https://developer.apple.com/documentation/foundation/nshttpurlresponse?language=objc. +class NSHttpUrlResponseData { + NSHttpUrlResponseData({ + required this.statusCode, + }); + + int statusCode; + + Object encode() { + return [ + statusCode, + ]; + } + + static NSHttpUrlResponseData decode(Object result) { + result as List; + return NSHttpUrlResponseData( + statusCode: result[0]! as int, + ); + } +} + /// Mirror of WKUserScript. /// /// See https://developer.apple.com/documentation/webkit/wkuserscript?language=objc. @@ -396,6 +449,35 @@ class WKNavigationActionData { } } +/// Mirror of WKNavigationResponse. +/// +/// See https://developer.apple.com/documentation/webkit/wknavigationresponse. +class WKNavigationResponseData { + WKNavigationResponseData({ + required this.response, + required this.forMainFrame, + }); + + NSHttpUrlResponseData response; + + bool forMainFrame; + + Object encode() { + return [ + response.encode(), + forMainFrame, + ]; + } + + static WKNavigationResponseData decode(Object result) { + result as List; + return WKNavigationResponseData( + response: NSHttpUrlResponseData.decode(result[0]! as List), + forMainFrame: result[1]! as bool, + ); + } +} + /// Mirror of WKFrameInfo. /// /// See https://developer.apple.com/documentation/webkit/wkframeinfo?language=objc. @@ -1389,18 +1471,27 @@ class _WKNavigationDelegateFlutterApiCodec extends StandardMessageCodec { if (value is NSErrorData) { buffer.putUint8(128); writeValue(buffer, value.encode()); - } else if (value is NSUrlRequestData) { + } else if (value is NSHttpUrlResponseData) { buffer.putUint8(129); writeValue(buffer, value.encode()); - } else if (value is WKFrameInfoData) { + } else if (value is NSUrlRequestData) { buffer.putUint8(130); writeValue(buffer, value.encode()); - } else if (value is WKNavigationActionData) { + } else if (value is WKFrameInfoData) { buffer.putUint8(131); writeValue(buffer, value.encode()); - } else if (value is WKNavigationActionPolicyEnumData) { + } else if (value is WKNavigationActionData) { buffer.putUint8(132); writeValue(buffer, value.encode()); + } else if (value is WKNavigationActionPolicyEnumData) { + buffer.putUint8(133); + writeValue(buffer, value.encode()); + } else if (value is WKNavigationResponseData) { + buffer.putUint8(134); + writeValue(buffer, value.encode()); + } else if (value is WKNavigationResponsePolicyEnumData) { + buffer.putUint8(135); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -1412,13 +1503,19 @@ class _WKNavigationDelegateFlutterApiCodec extends StandardMessageCodec { case 128: return NSErrorData.decode(readValue(buffer)!); case 129: - return NSUrlRequestData.decode(readValue(buffer)!); + return NSHttpUrlResponseData.decode(readValue(buffer)!); case 130: - return WKFrameInfoData.decode(readValue(buffer)!); + return NSUrlRequestData.decode(readValue(buffer)!); case 131: - return WKNavigationActionData.decode(readValue(buffer)!); + return WKFrameInfoData.decode(readValue(buffer)!); case 132: + return WKNavigationActionData.decode(readValue(buffer)!); + case 133: return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!); + case 134: + return WKNavigationResponseData.decode(readValue(buffer)!); + case 135: + return WKNavigationResponsePolicyEnumData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -1442,6 +1539,11 @@ abstract class WKNavigationDelegateFlutterApi { int webViewIdentifier, WKNavigationActionData navigationAction); + Future decidePolicyForNavigationResponse( + int identifier, + int webViewIdentifier, + WKNavigationResponseData navigationResponse); + void didFailNavigation( int identifier, int webViewIdentifier, NSErrorData error); @@ -1532,6 +1634,35 @@ abstract class WKNavigationDelegateFlutterApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.WKNavigationDelegateFlutterApi.decidePolicyForNavigationResponse', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.decidePolicyForNavigationResponse was null.'); + final List args = (message as List?)!; + final int? arg_identifier = (args[0] as int?); + assert(arg_identifier != null, + 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.decidePolicyForNavigationResponse was null, expected non-null int.'); + final int? arg_webViewIdentifier = (args[1] as int?); + assert(arg_webViewIdentifier != null, + 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.decidePolicyForNavigationResponse was null, expected non-null int.'); + final WKNavigationResponseData? arg_navigationResponse = + (args[2] as WKNavigationResponseData?); + assert(arg_navigationResponse != null, + 'Argument for dev.flutter.pigeon.WKNavigationDelegateFlutterApi.decidePolicyForNavigationResponse was null, expected non-null WKNavigationResponseData.'); + final WKNavigationResponsePolicyEnumData output = + await api.decidePolicyForNavigationResponse(arg_identifier!, + arg_webViewIdentifier!, arg_navigationResponse!); + return output; + }); + } + } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.WKNavigationDelegateFlutterApi.didFailNavigation', @@ -1840,42 +1971,51 @@ class _WKWebViewHostApiCodec extends StandardMessageCodec { } else if (value is NSHttpCookiePropertyKeyEnumData) { buffer.putUint8(130); writeValue(buffer, value.encode()); - } else if (value is NSKeyValueChangeKeyEnumData) { + } else if (value is NSHttpUrlResponseData) { buffer.putUint8(131); writeValue(buffer, value.encode()); - } else if (value is NSKeyValueObservingOptionsEnumData) { + } else if (value is NSKeyValueChangeKeyEnumData) { buffer.putUint8(132); writeValue(buffer, value.encode()); - } else if (value is NSUrlRequestData) { + } else if (value is NSKeyValueObservingOptionsEnumData) { buffer.putUint8(133); writeValue(buffer, value.encode()); - } else if (value is ObjectOrIdentifier) { + } else if (value is NSUrlRequestData) { buffer.putUint8(134); writeValue(buffer, value.encode()); - } else if (value is WKAudiovisualMediaTypeEnumData) { + } else if (value is ObjectOrIdentifier) { buffer.putUint8(135); writeValue(buffer, value.encode()); - } else if (value is WKFrameInfoData) { + } else if (value is WKAudiovisualMediaTypeEnumData) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is WKNavigationActionData) { + } else if (value is WKFrameInfoData) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is WKNavigationActionPolicyEnumData) { + } else if (value is WKNavigationActionData) { buffer.putUint8(138); writeValue(buffer, value.encode()); - } else if (value is WKScriptMessageData) { + } else if (value is WKNavigationActionPolicyEnumData) { buffer.putUint8(139); writeValue(buffer, value.encode()); - } else if (value is WKUserScriptData) { + } else if (value is WKNavigationResponseData) { buffer.putUint8(140); writeValue(buffer, value.encode()); - } else if (value is WKUserScriptInjectionTimeEnumData) { + } else if (value is WKNavigationResponsePolicyEnumData) { buffer.putUint8(141); writeValue(buffer, value.encode()); - } else if (value is WKWebsiteDataTypeEnumData) { + } else if (value is WKScriptMessageData) { buffer.putUint8(142); writeValue(buffer, value.encode()); + } else if (value is WKUserScriptData) { + buffer.putUint8(143); + writeValue(buffer, value.encode()); + } else if (value is WKUserScriptInjectionTimeEnumData) { + buffer.putUint8(144); + writeValue(buffer, value.encode()); + } else if (value is WKWebsiteDataTypeEnumData) { + buffer.putUint8(145); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -1891,28 +2031,34 @@ class _WKWebViewHostApiCodec extends StandardMessageCodec { case 130: return NSHttpCookiePropertyKeyEnumData.decode(readValue(buffer)!); case 131: - return NSKeyValueChangeKeyEnumData.decode(readValue(buffer)!); + return NSHttpUrlResponseData.decode(readValue(buffer)!); case 132: - return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!); + return NSKeyValueChangeKeyEnumData.decode(readValue(buffer)!); case 133: - return NSUrlRequestData.decode(readValue(buffer)!); + return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!); case 134: - return ObjectOrIdentifier.decode(readValue(buffer)!); + return NSUrlRequestData.decode(readValue(buffer)!); case 135: - return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!); + return ObjectOrIdentifier.decode(readValue(buffer)!); case 136: - return WKFrameInfoData.decode(readValue(buffer)!); + return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!); case 137: - return WKNavigationActionData.decode(readValue(buffer)!); + return WKFrameInfoData.decode(readValue(buffer)!); case 138: - return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!); + return WKNavigationActionData.decode(readValue(buffer)!); case 139: - return WKScriptMessageData.decode(readValue(buffer)!); + return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!); case 140: - return WKUserScriptData.decode(readValue(buffer)!); + return WKNavigationResponseData.decode(readValue(buffer)!); case 141: - return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!); + return WKNavigationResponsePolicyEnumData.decode(readValue(buffer)!); case 142: + return WKScriptMessageData.decode(readValue(buffer)!); + case 143: + return WKUserScriptData.decode(readValue(buffer)!); + case 144: + return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!); + case 145: return WKWebsiteDataTypeEnumData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation.dart index a9bbdc5f104..2f08f0d1e08 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/foundation/foundation.dart @@ -199,6 +199,20 @@ class NSUrlRequest { final Map allHttpHeaderFields; } +/// A URL load request that is independent of protocol or URL scheme. +/// +/// Wraps [NSHttpUrlResponse](https://developer.apple.com/documentation/foundation/nshttpurlresponse?language=objc). +@immutable +class NSHttpUrlResponse { + /// Constructs an [NSHttpUrlResponse]. + const NSHttpUrlResponse({ + required this.statusCode, + }); + + /// The response’s HTTP status code. + final int statusCode; +} + /// Information about an error condition. /// /// Wraps [NSError](https://developer.apple.com/documentation/foundation/nserror?language=objc). diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit.dart index 467fa8735d6..0834709b93b 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit.dart @@ -96,6 +96,21 @@ enum WKNavigationActionPolicy { cancel, } +/// Indicate whether to allow or cancel navigation to a webpage. +/// +/// Wraps [WKNavigationResponsePolicy](https://developer.apple.com/documentation/webkit/wknavigationresponsepolicy?language=objc). +enum WKNavigationResponsePolicy { + /// Allow navigation to continue. + /// + /// See https://developer.apple.com/documentation/webkit/wknavigationresponsepolicy/wknavigationresponsepolicyallow?language=objc. + allow, + + /// Cancel navigation. + /// + /// See https://developer.apple.com/documentation/webkit/wknavigationresponsepolicy/wknavigationresponsepolicycancel?language=objc. + cancel, +} + /// Possible error values that WebKit APIs can return. /// /// See https://developer.apple.com/documentation/webkit/wkerrorcode. @@ -162,6 +177,24 @@ class WKNavigationAction { final WKNavigationType navigationType; } +/// An object that contains information about a response to a navigation request. +/// +/// Wraps [WKNavigationResponse](https://developer.apple.com/documentation/webkit/wknavigationresponse?language=objc). +@immutable +class WKNavigationResponse { + /// Constructs a [WKNavigationResponse]. + const WKNavigationResponse({ + required this.response, + required this.forMainFrame, + }); + + /// The URL request object associated with the navigation action. + final NSHttpUrlResponse response; + + /// The frame in which to display the new content. + final bool forMainFrame; +} + /// An object that contains information about a frame on a webpage. /// /// An instance of this class is a transient, data-only object; it does not @@ -776,6 +809,7 @@ class WKNavigationDelegate extends NSObject { this.didFinishNavigation, this.didStartProvisionalNavigation, this.decidePolicyForNavigationAction, + this.decidePolicyForNavigationResponse, this.didFailNavigation, this.didFailProvisionalNavigation, this.webViewWebContentProcessDidTerminate, @@ -801,6 +835,7 @@ class WKNavigationDelegate extends NSObject { this.didFinishNavigation, this.didStartProvisionalNavigation, this.decidePolicyForNavigationAction, + this.decidePolicyForNavigationResponse, this.didFailNavigation, this.didFailProvisionalNavigation, this.webViewWebContentProcessDidTerminate, @@ -834,6 +869,14 @@ class WKNavigationDelegate extends NSObject { WKNavigationAction navigationAction, )? decidePolicyForNavigationAction; + /// Called when permission is needed to navigate to new content. + /// + /// {@macro webview_flutter_wkwebview.foundation.callbacks} + final Future Function( + WKWebView webView, + WKNavigationResponse navigationResponse, + )? decidePolicyForNavigationResponse; + /// Called when an error occurred during navigation. /// /// {@macro webview_flutter_wkwebview.foundation.callbacks} @@ -856,6 +899,7 @@ class WKNavigationDelegate extends NSObject { didFinishNavigation: didFinishNavigation, didStartProvisionalNavigation: didStartProvisionalNavigation, decidePolicyForNavigationAction: decidePolicyForNavigationAction, + decidePolicyForNavigationResponse: decidePolicyForNavigationResponse, didFailNavigation: didFailNavigation, didFailProvisionalNavigation: didFailProvisionalNavigation, webViewWebContentProcessDidTerminate: diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart index 7cd29da3e71..5b358f94f23 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/web_kit/web_kit_api_impls.dart @@ -73,6 +73,16 @@ extension _WKNavigationActionPolicyConverter on WKNavigationActionPolicy { } } +extension _WKNavigationResponsePolicyConverter on WKNavigationResponsePolicy { + WKNavigationResponsePolicyEnumData toWKNavigationResponsePolicyEnumData() { + return WKNavigationResponsePolicyEnumData( + value: WKNavigationResponsePolicyEnum.values.firstWhere( + (WKNavigationResponsePolicyEnum element) => element.name == name, + ), + ); + } +} + extension _NSHttpCookiePropertyKeyConverter on NSHttpCookiePropertyKey { NSHttpCookiePropertyKeyEnumData toNSHttpCookiePropertyKeyEnumData() { late final NSHttpCookiePropertyKeyEnum value; @@ -176,6 +186,13 @@ extension _NavigationActionDataConverter on WKNavigationActionData { } } +extension _NavigationResponseDataConverter on WKNavigationResponseData { + WKNavigationResponse toNavigationResponse() { + return WKNavigationResponse( + response: response.toNSUrlResponse(), forMainFrame: forMainFrame); + } +} + extension _WKFrameInfoDataConverter on WKFrameInfoData { WKFrameInfo toWKFrameInfo() { return WKFrameInfo(isMainFrame: isMainFrame); @@ -193,6 +210,12 @@ extension _NSUrlRequestDataConverter on NSUrlRequestData { } } +extension _NSUrlResponseDataConverter on NSHttpUrlResponseData { + NSHttpUrlResponse toNSUrlResponse() { + return NSHttpUrlResponse(statusCode: statusCode); + } +} + extension _WKNSErrorDataConverter on NSErrorData { NSError toNSError() { return NSError( @@ -844,6 +867,31 @@ class WKNavigationDelegateFlutterApiImpl ); } + @override + Future decidePolicyForNavigationResponse( + int identifier, + int webViewIdentifier, + WKNavigationResponseData navigationResponse, + ) async { + final Future Function( + WKWebView, + WKNavigationResponse navigationResponse, + )? function = _getDelegate(identifier).decidePolicyForNavigationResponse; + + if (function == null) { + return WKNavigationResponsePolicyEnumData( + value: WKNavigationResponsePolicyEnum.allow, + ); + } + + final WKNavigationResponsePolicy policy = await function( + instanceManager.getInstanceWithWeakReference(webViewIdentifier)! + as WKWebView, + navigationResponse.toNavigationResponse(), + ); + return policy.toWKNavigationResponsePolicyEnumData(); + } + @override void webViewWebContentProcessDidTerminate( int identifier, diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart index e25fdf1e354..e052a28c6a8 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_proxy.dart @@ -67,6 +67,10 @@ class WebKitProxy { WKWebView webView, WKNavigationAction navigationAction, )? decidePolicyForNavigationAction, + Future Function( + WKWebView webView, + WKNavigationResponse navigationResponse, + )? decidePolicyForNavigationResponse, void Function(WKWebView webView, NSError error)? didFailNavigation, void Function(WKWebView webView, NSError error)? didFailProvisionalNavigation, diff --git a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart index 9ca4744440e..a57e67ff9b8 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/lib/src/webkit_webview_controller.dart @@ -640,6 +640,16 @@ class WebKitNavigationDelegate extends PlatformNavigationDelegate { weakThis.target!._onPageStarted!(url ?? ''); } }, + decidePolicyForNavigationResponse: + (WKWebView webView, WKNavigationResponse response) async { + if (weakThis.target?._onHttpError != null && + response.response.statusCode >= 400) { + weakThis.target!._onHttpError!( + HttpResponseError(statusCode: response.response.statusCode)); + } + + return WKNavigationResponsePolicy.allow; + }, decidePolicyForNavigationAction: ( WKWebView webView, WKNavigationAction action, @@ -713,6 +723,7 @@ class WebKitNavigationDelegate extends PlatformNavigationDelegate { PageEventCallback? _onPageFinished; PageEventCallback? _onPageStarted; + HttpResponseErrorCallback? _onHttpError; ProgressCallback? _onProgress; WebResourceErrorCallback? _onWebResourceError; NavigationRequestCallback? _onNavigationRequest; @@ -728,6 +739,11 @@ class WebKitNavigationDelegate extends PlatformNavigationDelegate { _onPageStarted = onPageStarted; } + @override + Future setOnHttpError(HttpResponseErrorCallback onHttpError) async { + _onHttpError = onHttpError; + } + @override Future setOnProgress(ProgressCallback onProgress) async { _onProgress = onProgress; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart b/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart index 8dcbc2abd00..4e5e12850c3 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart @@ -131,6 +131,20 @@ class WKNavigationActionPolicyEnumData { late WKNavigationActionPolicyEnum value; } +/// Mirror of WKNavigationResponsePolicy. +/// +/// See https://developer.apple.com/documentation/webkit/wknavigationactionpolicy?language=objc. +enum WKNavigationResponsePolicyEnum { + allow, + cancel, +} + +// TODO(bparrishMines): Enums need be wrapped in a data class because thay can't +// be used as primitive arguments. See https://github.com/flutter/flutter/issues/87307 +class WKNavigationResponsePolicyEnumData { + late WKNavigationResponsePolicyEnum value; +} + /// Mirror of NSHTTPCookiePropertyKey. /// /// See https://developer.apple.com/documentation/foundation/nshttpcookiepropertykey. @@ -203,6 +217,13 @@ class NSUrlRequestData { late Map allHttpHeaderFields; } +/// Mirror of NSURLResponse. +/// +/// See https://developer.apple.com/documentation/foundation/nshttpurlresponse?language=objc. +class NSHttpUrlResponseData { + late int statusCode; +} + /// Mirror of WKUserScript. /// /// See https://developer.apple.com/documentation/webkit/wkuserscript?language=objc. @@ -221,6 +242,14 @@ class WKNavigationActionData { late WKNavigationType navigationType; } +/// Mirror of WKNavigationResponse. +/// +/// See https://developer.apple.com/documentation/webkit/wknavigationresponse. +class WKNavigationResponseData { + late NSHttpUrlResponseData response; + late bool forMainFrame; +} + /// Mirror of WKFrameInfo. /// /// See https://developer.apple.com/documentation/webkit/wkframeinfo?language=objc. @@ -478,6 +507,16 @@ abstract class WKNavigationDelegateFlutterApi { WKNavigationActionData navigationAction, ); + @ObjCSelector( + 'decidePolicyForNavigationResponseForDelegateWithIdentifier:webViewIdentifier:navigationResponse:', + ) + @async + WKNavigationResponsePolicyEnumData decidePolicyForNavigationResponse( + int identifier, + int webViewIdentifier, + WKNavigationResponseData navigationResponse, + ); + @ObjCSelector( 'didFailNavigationForDelegateWithIdentifier:webViewIdentifier:error:', ) diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml index 4c5c7484c5c..15725ae16c7 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_wkwebview description: A Flutter plugin that provides a WebView widget based on Apple's WKWebView control. repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter_wkwebview issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 3.3.0 +version: 3.4.0 environment: sdk: ">=2.18.0 <4.0.0" @@ -20,7 +20,7 @@ dependencies: flutter: sdk: flutter path: ^1.8.0 - webview_flutter_platform_interface: ^2.1.0 + webview_flutter_platform_interface: ^2.2.0 dev_dependencies: build_runner: ^2.1.5 diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.g.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.g.dart index a9b27a0d731..2bcc752a298 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.g.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/common/test_web_kit.g.dart @@ -977,42 +977,51 @@ class _TestWKWebViewHostApiCodec extends StandardMessageCodec { } else if (value is NSHttpCookiePropertyKeyEnumData) { buffer.putUint8(130); writeValue(buffer, value.encode()); - } else if (value is NSKeyValueChangeKeyEnumData) { + } else if (value is NSHttpUrlResponseData) { buffer.putUint8(131); writeValue(buffer, value.encode()); - } else if (value is NSKeyValueObservingOptionsEnumData) { + } else if (value is NSKeyValueChangeKeyEnumData) { buffer.putUint8(132); writeValue(buffer, value.encode()); - } else if (value is NSUrlRequestData) { + } else if (value is NSKeyValueObservingOptionsEnumData) { buffer.putUint8(133); writeValue(buffer, value.encode()); - } else if (value is ObjectOrIdentifier) { + } else if (value is NSUrlRequestData) { buffer.putUint8(134); writeValue(buffer, value.encode()); - } else if (value is WKAudiovisualMediaTypeEnumData) { + } else if (value is ObjectOrIdentifier) { buffer.putUint8(135); writeValue(buffer, value.encode()); - } else if (value is WKFrameInfoData) { + } else if (value is WKAudiovisualMediaTypeEnumData) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is WKNavigationActionData) { + } else if (value is WKFrameInfoData) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is WKNavigationActionPolicyEnumData) { + } else if (value is WKNavigationActionData) { buffer.putUint8(138); writeValue(buffer, value.encode()); - } else if (value is WKScriptMessageData) { + } else if (value is WKNavigationActionPolicyEnumData) { buffer.putUint8(139); writeValue(buffer, value.encode()); - } else if (value is WKUserScriptData) { + } else if (value is WKNavigationResponseData) { buffer.putUint8(140); writeValue(buffer, value.encode()); - } else if (value is WKUserScriptInjectionTimeEnumData) { + } else if (value is WKNavigationResponsePolicyEnumData) { buffer.putUint8(141); writeValue(buffer, value.encode()); - } else if (value is WKWebsiteDataTypeEnumData) { + } else if (value is WKScriptMessageData) { buffer.putUint8(142); writeValue(buffer, value.encode()); + } else if (value is WKUserScriptData) { + buffer.putUint8(143); + writeValue(buffer, value.encode()); + } else if (value is WKUserScriptInjectionTimeEnumData) { + buffer.putUint8(144); + writeValue(buffer, value.encode()); + } else if (value is WKWebsiteDataTypeEnumData) { + buffer.putUint8(145); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -1028,28 +1037,34 @@ class _TestWKWebViewHostApiCodec extends StandardMessageCodec { case 130: return NSHttpCookiePropertyKeyEnumData.decode(readValue(buffer)!); case 131: - return NSKeyValueChangeKeyEnumData.decode(readValue(buffer)!); + return NSHttpUrlResponseData.decode(readValue(buffer)!); case 132: - return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!); + return NSKeyValueChangeKeyEnumData.decode(readValue(buffer)!); case 133: - return NSUrlRequestData.decode(readValue(buffer)!); + return NSKeyValueObservingOptionsEnumData.decode(readValue(buffer)!); case 134: - return ObjectOrIdentifier.decode(readValue(buffer)!); + return NSUrlRequestData.decode(readValue(buffer)!); case 135: - return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!); + return ObjectOrIdentifier.decode(readValue(buffer)!); case 136: - return WKFrameInfoData.decode(readValue(buffer)!); + return WKAudiovisualMediaTypeEnumData.decode(readValue(buffer)!); case 137: - return WKNavigationActionData.decode(readValue(buffer)!); + return WKFrameInfoData.decode(readValue(buffer)!); case 138: - return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!); + return WKNavigationActionData.decode(readValue(buffer)!); case 139: - return WKScriptMessageData.decode(readValue(buffer)!); + return WKNavigationActionPolicyEnumData.decode(readValue(buffer)!); case 140: - return WKUserScriptData.decode(readValue(buffer)!); + return WKNavigationResponseData.decode(readValue(buffer)!); case 141: - return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!); + return WKNavigationResponsePolicyEnumData.decode(readValue(buffer)!); case 142: + return WKScriptMessageData.decode(readValue(buffer)!); + case 143: + return WKUserScriptData.decode(readValue(buffer)!); + case 144: + return WKUserScriptInjectionTimeEnumData.decode(readValue(buffer)!); + case 145: return WKWebsiteDataTypeEnumData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart index dd007869f0e..a7c667dfc52 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/src/web_kit/web_kit_test.dart @@ -584,6 +584,34 @@ void main() { expect(policyData.value, WKNavigationActionPolicyEnum.cancel); }); + test('decidePolicyForNavigationResponse', () async { + WebKitFlutterApis.instance = WebKitFlutterApis( + instanceManager: instanceManager, + ); + + navigationDelegate = WKNavigationDelegate( + instanceManager: instanceManager, + decidePolicyForNavigationResponse: ( + WKWebView webView, + WKNavigationResponse navigationAction, + ) async { + return WKNavigationResponsePolicy.cancel; + }, + ); + + final WKNavigationResponsePolicyEnumData policyData = + await WebKitFlutterApis.instance.navigationDelegate + .decidePolicyForNavigationResponse( + instanceManager.getIdentifier(navigationDelegate)!, + instanceManager.getIdentifier(webView)!, + WKNavigationResponseData( + response: NSHttpUrlResponseData(statusCode: 401), + forMainFrame: true), + ); + + expect(policyData.value, WKNavigationResponsePolicyEnum.cancel); + }); + test('didFailNavigation', () async { final Completer> argsCompleter = Completer>(); diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_navigation_delegate_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_navigation_delegate_test.dart index 9a654558b6c..84db11269c7 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_navigation_delegate_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_navigation_delegate_test.dart @@ -75,6 +75,33 @@ void main() { expect(callbackUrl, 'https://www.google.com'); }); + test('setOnHttpError from decidePolicyForNavigationResponse', () { + final WebKitNavigationDelegate webKitDelgate = WebKitNavigationDelegate( + const WebKitNavigationDelegateCreationParams( + webKitProxy: WebKitProxy( + createNavigationDelegate: CapturingNavigationDelegate.new, + createUIDelegate: CapturingUIDelegate.new, + ), + ), + ); + + late final HttpResponseError callbackError; + void onHttpError(HttpResponseError error) { + callbackError = error; + } + + webKitDelgate.setOnHttpError(onHttpError); + + CapturingNavigationDelegate + .lastCreatedDelegate.decidePolicyForNavigationResponse!( + WKWebView.detached(), + const WKNavigationResponse( + response: NSHttpUrlResponse(statusCode: 401), forMainFrame: true), + ); + + expect(callbackError.statusCode, 401); + }); + test('onWebResourceError from didFailNavigation', () { final WebKitNavigationDelegate webKitDelegate = WebKitNavigationDelegate( const WebKitNavigationDelegateCreationParams( @@ -244,6 +271,7 @@ class CapturingNavigationDelegate extends WKNavigationDelegate { CapturingNavigationDelegate({ super.didFinishNavigation, super.didStartProvisionalNavigation, + super.decidePolicyForNavigationResponse, super.didFailNavigation, super.didFailProvisionalNavigation, super.decidePolicyForNavigationAction, diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart index dc7085bc52e..8d8a3357ddc 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_controller_test.dart @@ -1060,6 +1060,7 @@ class CapturingNavigationDelegate extends WKNavigationDelegate { super.didFailNavigation, super.didFailProvisionalNavigation, super.decidePolicyForNavigationAction, + super.decidePolicyForNavigationResponse, super.webViewWebContentProcessDidTerminate, }) : super.detached() { lastCreatedDelegate = this;