From 386bd2097e7ebfb473348ad6d3e0063d86102412 Mon Sep 17 00:00:00 2001 From: Lorenzo Pichilli Date: Sat, 8 Oct 2022 18:57:29 +0200 Subject: [PATCH] added loadSimulatedRequest iOS webview method --- CHANGELOG.md | 2 +- .../in_app_webview/load_url.dart | 49 ++++++++++++++++++- .../InAppWebView/WebViewChannelDelegate.swift | 17 +++++++ .../WebViewChannelDelegateMethods.swift | 1 + ios/Classes/Types/URLResponse.swift | 8 +++ .../in_app_webview_controller.dart | 32 +++++++++++- 6 files changed, 105 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45cafba1..9983b993 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - Added `PrintJobController` to manage print jobs - Added `WebAuthenticationSession` for iOS - Added `FindInteractionController` for Android and iOS -- Added `pauseAllMediaPlayback`, `setAllMediaPlaybackSuspended`, `closeAllMediaPresentations`, `requestMediaPlaybackState`, `isInFullscreen`, `getCameraCaptureState`, `setCameraCaptureState`, `getMicrophoneCaptureState`, `setMicrophoneCaptureState` WebView controller methods +- Added `pauseAllMediaPlayback`, `setAllMediaPlaybackSuspended`, `closeAllMediaPresentations`, `requestMediaPlaybackState`, `isInFullscreen`, `getCameraCaptureState`, `setCameraCaptureState`, `getMicrophoneCaptureState`, `setMicrophoneCaptureState`, `loadSimulatedRequest` WebView controller methods - Added `underPageBackgroundColor`, `isTextInteractionEnabled`, `isSiteSpecificQuirksModeEnabled`, `upgradeKnownHostsToHTTPS`, `forceDarkStrategy`, `willSuppressErrorPage`, `algorithmicDarkeningAllowed`, `requestedWithHeaderMode`, `enterpriseAuthenticationAppLinkPolicyEnabled`, `isElementFullscreenEnabled`, `isFindInteractionEnabled`, `minimumViewportInset`, `maximumViewportInset` WebView settings - Added `onCameraCaptureStateChanged`, `onMicrophoneCaptureStateChanged` WebView events - Added support for `onPermissionRequest` event on iOS 15.0+ diff --git a/example/integration_test/in_app_webview/load_url.dart b/example/integration_test/in_app_webview/load_url.dart index 2b230aca..53b3c8f4 100644 --- a/example/integration_test/in_app_webview/load_url.dart +++ b/example/integration_test/in_app_webview/load_url.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; @@ -8,7 +9,7 @@ import 'package:flutter_test/flutter_test.dart'; import '../constants.dart'; void loadUrl() { - final shouldSkip = kIsWeb ? false : + final shouldSkip1 = kIsWeb ? false : ![ TargetPlatform.android, TargetPlatform.iOS, @@ -49,5 +50,49 @@ void loadUrl() { await controller.loadUrl( urlRequest: URLRequest(url: TEST_CROSS_PLATFORM_URL_1)); expect(await loadedUrl.future, TEST_CROSS_PLATFORM_URL_1.toString()); - }, skip: shouldSkip); + }, skip: shouldSkip1); + + final shouldSkip2 = kIsWeb ? false : + ![ + TargetPlatform.iOS, + TargetPlatform.macOS, + ].contains(defaultTargetPlatform); + + testWidgets('loadSimulatedRequest', (WidgetTester tester) async { + final Completer controllerCompleter = Completer(); + final Completer firstUrlLoad = Completer(); + final Completer loadedUrl = Completer(); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: InAppWebView( + key: GlobalKey(), + initialUrlRequest: + URLRequest(url: initialUrl), + onWebViewCreated: (controller) { + controllerCompleter.complete(controller); + }, + onLoadStop: (controller, url) { + if (url.toString() == initialUrl.toString() && !firstUrlLoad.isCompleted) { + firstUrlLoad.complete(url.toString()); + } else if (url.toString() == TEST_CROSS_PLATFORM_URL_1.toString() && !loadedUrl.isCompleted) { + loadedUrl.complete(url.toString()); + } + }, + ), + ), + ); + final InAppWebViewController controller = + await controllerCompleter.future; + expect(await firstUrlLoad.future, initialUrl.toString()); + + final htmlCode = "

Hello

"; + await controller.loadSimulatedRequest( + urlRequest: URLRequest(url: TEST_CROSS_PLATFORM_URL_1), + data: Uint8List.fromList(utf8.encode(htmlCode)) + ); + expect(await loadedUrl.future, TEST_CROSS_PLATFORM_URL_1.toString()); + expect(await controller.evaluateJavascript(source: "document.body").toString().trim(), htmlCode); + }, skip: shouldSkip2); } diff --git a/ios/Classes/InAppWebView/WebViewChannelDelegate.swift b/ios/Classes/InAppWebView/WebViewChannelDelegate.swift index 0af660c7..7b1e6995 100644 --- a/ios/Classes/InAppWebView/WebViewChannelDelegate.swift +++ b/ios/Classes/InAppWebView/WebViewChannelDelegate.swift @@ -640,6 +640,23 @@ public class WebViewChannelDelegate : ChannelDelegate { result(false) } break + case .loadSimulatedRequest: + if let webView = webView, #available(iOS 15.0, *) { + let request = URLRequest.init(fromPluginMap: arguments!["urlRequest"] as! [String:Any?]) + let data = arguments!["data"] as! FlutterStandardTypedData + var response: URLResponse? = nil + if let urlResponse = arguments!["urlResponse"] as? [String:Any?] { + response = URLResponse.init(fromPluginMap: urlResponse) + } + if let response = response { + webView.loadSimulatedRequest(request, response: response, responseData: data.data) + } else { + webView.loadSimulatedRequest(request, responseHTML: String(decoding: data.data, as: UTF8.self)) + } + result(true) + } else { + result(false) + } } } diff --git a/ios/Classes/InAppWebView/WebViewChannelDelegateMethods.swift b/ios/Classes/InAppWebView/WebViewChannelDelegateMethods.swift index 8ba787ed..13efc2cd 100644 --- a/ios/Classes/InAppWebView/WebViewChannelDelegateMethods.swift +++ b/ios/Classes/InAppWebView/WebViewChannelDelegateMethods.swift @@ -86,4 +86,5 @@ public enum WebViewChannelDelegateMethods: String { case setCameraCaptureState = "setCameraCaptureState" case getMicrophoneCaptureState = "getMicrophoneCaptureState" case setMicrophoneCaptureState = "setMicrophoneCaptureState" + case loadSimulatedRequest = "loadSimulatedRequest" } diff --git a/ios/Classes/Types/URLResponse.swift b/ios/Classes/Types/URLResponse.swift index 0717b703..0a6a4835 100644 --- a/ios/Classes/Types/URLResponse.swift +++ b/ios/Classes/Types/URLResponse.swift @@ -8,6 +8,14 @@ import Foundation extension URLResponse { + public convenience init?(fromPluginMap: [String:Any?]) { + let url = URL(string: fromPluginMap["url"] as? String ?? "about:blank")! + let mimeType = fromPluginMap["mimeType"] as? String + let expectedContentLength = fromPluginMap["expectedContentLength"] as? Int64 ?? 0 + let textEncodingName = fromPluginMap["textEncodingName"] as? String + self.init(url: url, mimeType: mimeType, expectedContentLength: Int(expectedContentLength), textEncodingName: textEncodingName) + } + public func toMap () -> [String:Any?] { let httpResponse: HTTPURLResponse? = self as? HTTPURLResponse return [ diff --git a/lib/src/in_app_webview/in_app_webview_controller.dart b/lib/src/in_app_webview/in_app_webview_controller.dart index 7abe8fec..b649f8e5 100644 --- a/lib/src/in_app_webview/in_app_webview_controller.dart +++ b/lib/src/in_app_webview/in_app_webview_controller.dart @@ -1619,7 +1619,7 @@ class InAppWebViewController { ///Loads the given [url] with [postData] (x-www-form-urlencoded) using `POST` method into this WebView. /// - ///Example + ///Example: ///```dart ///var postData = Uint8List.fromList(utf8.encode("firstname=Foo&surname=Bar")); ///controller.postUrl(url: Uri.parse("https://www.example.com/"), postData: postData); @@ -3371,6 +3371,36 @@ class InAppWebViewController { await _channel.invokeMethod('setMicrophoneCaptureState', args); } + ///Loads the web content from the data you provide as if the data were the response to the request. + ///If [urlResponse] is `null`, it loads the web content from the data as an utf8 encoded HTML string as the response to the request. + /// + ///[urlRequest] represents a URL request that specifies the base URL and other loading details the system uses to interpret the data you provide. + /// + ///[urlResponse] represents a response the system uses to interpret the data you provide. + /// + ///[data] represents the data or the utf8 encoded HTML string to use as the contents of the webpage. + /// + ///Example: + ///```dart + ///controller.loadSimulateloadSimulatedRequestdRequest(urlRequest: URLRequest( + /// url: Uri.parse("https://flutter.dev"), + /// ), + /// data: Uint8List.fromList(utf8.encode("

Hello

")) + ///); + ///``` + /// + ///**NOTE for iOS**: available on iOS 15.0+. + /// + ///**Supported Platforms/Implementations**: + ///- iOS ([Official API - WKWebView.loadSimulatedRequest(_:response:responseData:)](https://developer.apple.com/documentation/webkit/wkwebview/3763094-loadsimulatedrequest) and [Official API - WKWebView.loadSimulatedRequest(_:responseHTML:)](https://developer.apple.com/documentation/webkit/wkwebview/3763095-loadsimulatedrequest)). + Future loadSimulatedRequest({required URLRequest urlRequest, required Uint8List data, URLResponse? urlResponse}) async { + Map args = {}; + args.putIfAbsent('urlRequest', () => urlRequest.toMap()); + args.putIfAbsent('data', () => data); + args.putIfAbsent('urlResponse', () => urlResponse?.toMap()); + await _channel.invokeMethod('loadSimulatedRequest', args); + } + ///Returns the iframe `id` attribute used on the Web platform. /// ///**Supported Platforms/Implementations**: