From 6c5045b1b7736d6a6d9d9471a78962674f3e7e1a Mon Sep 17 00:00:00 2001 From: EunchulJeon Date: Fri, 19 Jan 2024 06:22:14 +0900 Subject: [PATCH] [webview_flutter_wkwebview] Add javascript panel interface for wkwebview (#5795) * There are cases where Web calls System Popup with javascript on webview_flutter * At this time, the message comes in the WKUIDelegate part in iOS. * https://developer.apple.com/documentation/webkit/wkuidelegate/1537406-webview * https://developer.apple.com/documentation/webkit/wkuidelegate/1536489-webview * Related issue: https://github.com/flutter/flutter/issues/30358#issuecomment-1645347616 * Related Interface PR: https://github.com/flutter/packages/pull/5670 * The PR that contains all changes can be found at https://github.com/flutter/packages/pull/4704 --- .../webview_flutter_wkwebview/CHANGELOG.md | 4 + .../webview_flutter_test.dart | 92 +++++++++++++ .../example/lib/main.dart | 120 +++++++++++++++++ .../example/pubspec.yaml | 2 +- .../ios/Classes/FWFDataConverters.m | 6 +- .../ios/Classes/FWFGeneratedWebKitApis.h | 24 +++- .../ios/Classes/FWFGeneratedWebKitApis.m | 98 +++++++++++++- .../ios/Classes/FWFUIDelegateHostApi.m | 91 +++++++++++++ .../lib/src/common/web_kit.g.dart | 122 ++++++++++++++++++ .../lib/src/web_kit/web_kit.dart | 33 ++++- .../lib/src/web_kit/web_kit_api_impls.dart | 32 ++++- .../lib/src/webkit_proxy.dart | 13 ++ .../lib/src/webkit_webview_controller.dart | 68 ++++++++++ .../pigeons/web_kit.dart | 35 +++++ .../webview_flutter_wkwebview/pubspec.yaml | 4 +- .../legacy/web_kit_webview_widget_test.dart | 6 +- .../test/src/web_kit/web_kit_test.dart | 22 +++- .../test/webkit_navigation_delegate_test.dart | 7 +- .../test/webkit_webview_controller_test.dart | 111 +++++++++++++++- .../test/webkit_webview_widget_test.dart | 3 + 20 files changed, 872 insertions(+), 21 deletions(-) diff --git a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md index 5a97e3e88f49..0d899a59188f 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_wkwebview/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.11.0 + +* Adds support to show JavaScript dialog. See `PlatformWebViewController.setOnJavaScriptAlertDialog`, `PlatformWebViewController.setOnJavaScriptConfirmDialog` and `PlatformWebViewController.setOnJavaScriptTextInputDialog`. + ## 3.10.3 * Adds a check that throws an `ArgumentError` when `WebKitWebViewController.addJavaScriptChannel` 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 89ecad8d0e12..9d95849d7baa 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 @@ -1303,6 +1303,98 @@ Future main() async { }, ); + testWidgets('can receive JavaScript alert dialogs', + (WidgetTester tester) async { + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + + final Completer alertMessage = Completer(); + unawaited(controller.setOnJavaScriptAlertDialog( + (JavaScriptAlertDialogRequest request) async { + alertMessage.complete(request.message); + }, + )); + + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + unawaited( + controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await controller.runJavaScript('alert("alert message")'); + await expectLater(alertMessage.future, completion('alert message')); + }); + + testWidgets('can receive JavaScript confirm dialogs', + (WidgetTester tester) async { + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + + final Completer confirmMessage = Completer(); + unawaited(controller.setOnJavaScriptConfirmDialog( + (JavaScriptConfirmDialogRequest request) async { + confirmMessage.complete(request.message); + return true; + }, + )); + + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + unawaited( + controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + await controller.runJavaScript('confirm("confirm message")'); + await expectLater(confirmMessage.future, completion('confirm message')); + }); + + testWidgets('can receive JavaScript prompt dialogs', + (WidgetTester tester) async { + final PlatformWebViewController controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + ); + + unawaited(controller.setOnJavaScriptTextInputDialog( + (JavaScriptTextInputDialogRequest request) async { + return 'return message'; + }, + )); + + unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted)); + unawaited( + controller.loadRequest(LoadRequestParams(uri: Uri.parse(primaryUrl))), + ); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: controller), + ).build(context); + }, + )); + + final Object promptResponse = await controller.runJavaScriptReturningResult( + 'prompt("input message", "default text")', + ); + expect(promptResponse, 'return message'); + }); + group('Logging', () { testWidgets('can receive console log messages', (WidgetTester tester) async { 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 cf8063717910..116f4bb058f9 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,38 @@ const String kLogExamplePage = ''' '''; +const String kAlertTestPage = ''' + + + + + + + +

Click the following button to see the effect

+
+ + + +
+ + +'''; + class WebViewExample extends StatefulWidget { const WebViewExample({super.key, this.cookieManager}); @@ -297,6 +329,7 @@ enum MenuOptions { setCookie, logExample, basicAuthentication, + javaScriptAlert, } class SampleMenu extends StatelessWidget { @@ -348,6 +381,8 @@ class SampleMenu extends StatelessWidget { _onLogExample(); case MenuOptions.basicAuthentication: _promptForUrl(context); + case MenuOptions.javaScriptAlert: + _onJavaScriptAlertExample(context); } }, itemBuilder: (BuildContext context) => >[ @@ -412,6 +447,10 @@ class SampleMenu extends StatelessWidget { value: MenuOptions.basicAuthentication, child: Text('Basic Authentication Example'), ), + const PopupMenuItem( + value: MenuOptions.javaScriptAlert, + child: Text('JavaScript Alert Example'), + ), ], ); } @@ -536,6 +575,28 @@ class SampleMenu extends StatelessWidget { return webViewController.loadHtmlString(kTransparentBackgroundPage); } + Future _onJavaScriptAlertExample(BuildContext context) { + webViewController.setOnJavaScriptAlertDialog( + (JavaScriptAlertDialogRequest request) async { + await _showAlert(context, request.message); + }); + + webViewController.setOnJavaScriptConfirmDialog( + (JavaScriptConfirmDialogRequest request) async { + final bool result = await _showConfirm(context, request.message); + return result; + }); + + webViewController.setOnJavaScriptTextInputDialog( + (JavaScriptTextInputDialogRequest request) async { + final String result = + await _showTextInput(context, request.message, request.defaultText); + return result; + }); + + return webViewController.loadHtmlString(kAlertTestPage); + } + Widget _getCookieList(String cookies) { if (cookies == '""') { return Container(); @@ -605,6 +666,65 @@ class SampleMenu extends StatelessWidget { }, ); } + + Future _showAlert(BuildContext context, String message) async { + return showDialog( + context: context, + builder: (BuildContext ctx) { + return AlertDialog( + content: Text(message), + actions: [ + TextButton( + onPressed: () { + Navigator.of(ctx).pop(); + }, + child: const Text('OK')) + ], + ); + }); + } + + Future _showConfirm(BuildContext context, String message) async { + return await showDialog( + context: context, + builder: (BuildContext ctx) { + return AlertDialog( + content: Text(message), + actions: [ + TextButton( + onPressed: () { + Navigator.of(ctx).pop(false); + }, + child: const Text('Cancel')), + TextButton( + onPressed: () { + Navigator.of(ctx).pop(true); + }, + child: const Text('OK')), + ], + ); + }) ?? + false; + } + + Future _showTextInput( + BuildContext context, String message, String? defaultText) async { + return await showDialog( + context: context, + builder: (BuildContext ctx) { + return AlertDialog( + content: Text(message), + actions: [ + TextButton( + onPressed: () { + Navigator.of(ctx).pop('Text test'); + }, + child: const Text('Enter')), + ], + ); + }) ?? + ''; + } } class NavigationControls extends StatelessWidget { diff --git a/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/example/pubspec.yaml index 900520fe61a5..5d323b737c07 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.7.0 + webview_flutter_platform_interface: ^2.9.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.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.m index a4dda8bf4210..9a5cc86fe951 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFDataConverters.m @@ -167,7 +167,7 @@ WKAudiovisualMediaTypes FWFNativeWKAudiovisualMediaTypeFromEnumData( FWFNSUrlRequestData *FWFNSUrlRequestDataFromNativeNSURLRequest(NSURLRequest *request) { return [FWFNSUrlRequestData - makeWithUrl:request.URL.absoluteString + makeWithUrl:request.URL.absoluteString == nil ? @"" : request.URL.absoluteString httpMethod:request.HTTPMethod httpBody:request.HTTPBody ? [FlutterStandardTypedData typedDataWithBytes:request.HTTPBody] @@ -176,7 +176,9 @@ WKAudiovisualMediaTypes FWFNativeWKAudiovisualMediaTypeFromEnumData( } FWFWKFrameInfoData *FWFWKFrameInfoDataFromNativeWKFrameInfo(WKFrameInfo *info) { - return [FWFWKFrameInfoData makeWithIsMainFrame:info.isMainFrame]; + return [FWFWKFrameInfoData + makeWithIsMainFrame:info.isMainFrame + request:FWFNSUrlRequestDataFromNativeNSURLRequest(info.request)]; } WKNavigationActionPolicy FWFNativeWKNavigationActionPolicyFromEnumData( 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 1e312a2d2a47..a0524d05429d 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.h +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.h @@ -464,8 +464,9 @@ typedef NS_ENUM(NSUInteger, FWFNSUrlCredentialPersistence) { @interface FWFWKFrameInfoData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithIsMainFrame:(BOOL)isMainFrame; ++ (instancetype)makeWithIsMainFrame:(BOOL)isMainFrame request:(FWFNSUrlRequestData *)request; @property(nonatomic, assign) BOOL isMainFrame; +@property(nonatomic, strong) FWFNSUrlRequestData *request; @end /// Mirror of NSError. @@ -949,6 +950,27 @@ NSObject *FWFWKUIDelegateFlutterApiGetCodec(void); (void (^)( FWFWKPermissionDecisionData *_Nullable, FlutterError *_Nullable))completion; +/// Callback to Dart function `WKUIDelegate.runJavaScriptAlertPanel`. +- (void)runJavaScriptAlertPanelForDelegateWithIdentifier:(NSInteger)identifier + message:(NSString *)message + frame:(FWFWKFrameInfoData *)frame + completion: + (void (^)(FlutterError *_Nullable))completion; +/// Callback to Dart function `WKUIDelegate.runJavaScriptConfirmPanel`. +- (void)runJavaScriptConfirmPanelForDelegateWithIdentifier:(NSInteger)identifier + message:(NSString *)message + frame:(FWFWKFrameInfoData *)frame + completion: + (void (^)(NSNumber *_Nullable, + FlutterError *_Nullable))completion; +/// Callback to Dart function `WKUIDelegate.runJavaScriptTextInputPanel`. +- (void)runJavaScriptTextInputPanelForDelegateWithIdentifier:(NSInteger)identifier + prompt:(NSString *)prompt + defaultText:(NSString *)defaultText + frame:(FWFWKFrameInfoData *)frame + completion: + (void (^)(NSString *_Nullable, + FlutterError *_Nullable))completion; @end /// The codec used by FWFWKHttpCookieStoreHostApi. 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 5d8017832b02..c9cd99620f2c 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFGeneratedWebKitApis.m @@ -609,14 +609,16 @@ - (NSArray *)toList { @end @implementation FWFWKFrameInfoData -+ (instancetype)makeWithIsMainFrame:(BOOL)isMainFrame { ++ (instancetype)makeWithIsMainFrame:(BOOL)isMainFrame request:(FWFNSUrlRequestData *)request { FWFWKFrameInfoData *pigeonResult = [[FWFWKFrameInfoData alloc] init]; pigeonResult.isMainFrame = isMainFrame; + pigeonResult.request = request; return pigeonResult; } + (FWFWKFrameInfoData *)fromList:(NSArray *)list { FWFWKFrameInfoData *pigeonResult = [[FWFWKFrameInfoData alloc] init]; pigeonResult.isMainFrame = [GetNullableObjectAtIndex(list, 0) boolValue]; + pigeonResult.request = [FWFNSUrlRequestData nullableFromList:(GetNullableObjectAtIndex(list, 1))]; return pigeonResult; } + (nullable FWFWKFrameInfoData *)nullableFromList:(NSArray *)list { @@ -625,6 +627,7 @@ + (nullable FWFWKFrameInfoData *)nullableFromList:(NSArray *)list { - (NSArray *)toList { return @[ @(self.isMainFrame), + (self.request ? [self.request toList] : [NSNull null]), ]; } @end @@ -3098,6 +3101,99 @@ - (void)requestMediaCapturePermissionForDelegateWithIdentifier:(NSInteger)arg_id } }]; } +- (void)runJavaScriptAlertPanelForDelegateWithIdentifier:(NSInteger)arg_identifier + message:(NSString *)arg_message + frame:(FWFWKFrameInfoData *)arg_frame + completion: + (void (^)(FlutterError *_Nullable))completion { + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi." + @"runJavaScriptAlertPanel" + binaryMessenger:self.binaryMessenger + codec:FWFWKUIDelegateFlutterApiGetCodec()]; + [channel + sendMessage:@[ @(arg_identifier), arg_message ?: [NSNull null], arg_frame ?: [NSNull null] ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion([FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + completion(nil); + } + } else { + completion([FlutterError errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); + } + }]; +} +- (void)runJavaScriptConfirmPanelForDelegateWithIdentifier:(NSInteger)arg_identifier + message:(NSString *)arg_message + frame:(FWFWKFrameInfoData *)arg_frame + completion: + (void (^)(NSNumber *_Nullable, + FlutterError *_Nullable))completion { + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi." + @"runJavaScriptConfirmPanel" + binaryMessenger:self.binaryMessenger + codec:FWFWKUIDelegateFlutterApiGetCodec()]; + [channel + sendMessage:@[ @(arg_identifier), arg_message ?: [NSNull null], arg_frame ?: [NSNull null] ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion(nil, [FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + NSNumber *output = reply[0] == [NSNull null] ? nil : reply[0]; + completion(output, nil); + } + } else { + completion(nil, + [FlutterError errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); + } + }]; +} +- (void)runJavaScriptTextInputPanelForDelegateWithIdentifier:(NSInteger)arg_identifier + prompt:(NSString *)arg_prompt + defaultText:(NSString *)arg_defaultText + frame:(FWFWKFrameInfoData *)arg_frame + completion:(void (^)(NSString *_Nullable, + FlutterError *_Nullable)) + completion { + FlutterBasicMessageChannel *channel = [FlutterBasicMessageChannel + messageChannelWithName:@"dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi." + @"runJavaScriptTextInputPanel" + binaryMessenger:self.binaryMessenger + codec:FWFWKUIDelegateFlutterApiGetCodec()]; + [channel sendMessage:@[ + @(arg_identifier), arg_prompt ?: [NSNull null], arg_defaultText ?: [NSNull null], + arg_frame ?: [NSNull null] + ] + reply:^(NSArray *reply) { + if (reply != nil) { + if (reply.count > 1) { + completion(nil, [FlutterError errorWithCode:reply[0] + message:reply[1] + details:reply[2]]); + } else { + NSString *output = reply[0] == [NSNull null] ? nil : reply[0]; + completion(output, nil); + } + } else { + completion(nil, [FlutterError + errorWithCode:@"channel-error" + message:@"Unable to establish connection on channel." + details:@""]); + } + }]; +} @end @interface FWFWKHttpCookieStoreHostApiCodecReader : FlutterStandardReader diff --git a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIDelegateHostApi.m b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIDelegateHostApi.m index 879d85dd9d42..f9d78eaea06a 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIDelegateHostApi.m +++ b/packages/webview_flutter/webview_flutter_wkwebview/ios/Classes/FWFUIDelegateHostApi.m @@ -88,6 +88,63 @@ - (void)requestMediaCapturePermissionForDelegateWithIdentifier:(FWFUIDelegate *) decision)); }]; } + +- (void)runJavaScriptAlertPanelForDelegateWithIdentifier:(FWFUIDelegate *)instance + message:(NSString *)message + frame:(WKFrameInfo *)frame + completionHandler:(void (^)(void))completionHandler { + [self runJavaScriptAlertPanelForDelegateWithIdentifier:[self identifierForDelegate:instance] + message:message + frame:FWFWKFrameInfoDataFromNativeWKFrameInfo( + frame) + completion:^(FlutterError *error) { + NSAssert(!error, @"%@", error); + completionHandler(); + }]; +} + +- (void)runJavaScriptConfirmPanelForDelegateWithIdentifier:(FWFUIDelegate *)instance + message:(NSString *)message + frame:(WKFrameInfo *)frame + completionHandler:(void (^)(BOOL))completionHandler { + [self runJavaScriptConfirmPanelForDelegateWithIdentifier:[self identifierForDelegate:instance] + message:message + frame:FWFWKFrameInfoDataFromNativeWKFrameInfo( + frame) + completion:^(NSNumber *isConfirmed, + FlutterError *error) { + NSAssert(!error, @"%@", error); + if (error) { + completionHandler(NO); + } else { + completionHandler(isConfirmed.boolValue); + } + }]; +} + +- (void)runJavaScriptTextInputPanelForDelegateWithIdentifier:(FWFUIDelegate *)instance + prompt:(NSString *)prompt + defaultText:(NSString *)defaultText + frame:(WKFrameInfo *)frame + completionHandler: + (void (^)(NSString *_Nullable))completionHandler { + [self + runJavaScriptTextInputPanelForDelegateWithIdentifier:[self identifierForDelegate:instance] + prompt:prompt + defaultText:defaultText + frame:FWFWKFrameInfoDataFromNativeWKFrameInfo( + frame) + completion:^(NSString *inputText, + FlutterError *error) { + NSAssert(!error, @"%@", error); + if (error) { + completionHandler(nil); + } else { + completionHandler(inputText); + } + }]; +} + @end @implementation FWFUIDelegate @@ -131,6 +188,39 @@ - (void)webView:(WKWebView *)webView decisionHandler(decision); }]; } + +- (void)webView:(WKWebView *)webView + runJavaScriptAlertPanelWithMessage:(NSString *)message + initiatedByFrame:(WKFrameInfo *)frame + completionHandler:(void (^)(void))completionHandler { + [self.UIDelegateAPI runJavaScriptAlertPanelForDelegateWithIdentifier:self + message:message + frame:frame + completionHandler:completionHandler]; +} + +- (void)webView:(WKWebView *)webView + runJavaScriptConfirmPanelWithMessage:(NSString *)message + initiatedByFrame:(WKFrameInfo *)frame + completionHandler:(void (^)(BOOL))completionHandler { + [self.UIDelegateAPI runJavaScriptConfirmPanelForDelegateWithIdentifier:self + message:message + frame:frame + completionHandler:completionHandler]; +} + +- (void)webView:(WKWebView *)webView + runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt + defaultText:(NSString *)defaultText + initiatedByFrame:(WKFrameInfo *)frame + completionHandler:(void (^)(NSString *_Nullable))completionHandler { + [self.UIDelegateAPI runJavaScriptTextInputPanelForDelegateWithIdentifier:self + prompt:prompt + defaultText:defaultText + frame:frame + completionHandler:completionHandler]; +} + @end @interface FWFUIDelegateHostApiImpl () @@ -161,4 +251,5 @@ - (void)createWithIdentifier:(NSInteger)identifier error:(FlutterError *_Nullabl instanceManager:self.instanceManager]; [self.instanceManager addDartCreatedInstance:uIDelegate withIdentifier:identifier]; } + @end 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 1c632203f611..f814bd0f4fdf 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 @@ -560,13 +560,17 @@ class WKNavigationActionData { class WKFrameInfoData { WKFrameInfoData({ required this.isMainFrame, + required this.request, }); bool isMainFrame; + NSUrlRequestData request; + Object encode() { return [ isMainFrame, + request.encode(), ]; } @@ -574,6 +578,7 @@ class WKFrameInfoData { result as List; return WKFrameInfoData( isMainFrame: result[0]! as bool, + request: NSUrlRequestData.decode(result[1]! as List), ); } } @@ -2919,6 +2924,18 @@ abstract class WKUIDelegateFlutterApi { WKFrameInfoData frame, WKMediaCaptureTypeData type); + /// Callback to Dart function `WKUIDelegate.runJavaScriptAlertPanel`. + Future runJavaScriptAlertPanel( + int identifier, String message, WKFrameInfoData frame); + + /// Callback to Dart function `WKUIDelegate.runJavaScriptConfirmPanel`. + Future runJavaScriptConfirmPanel( + int identifier, String message, WKFrameInfoData frame); + + /// Callback to Dart function `WKUIDelegate.runJavaScriptTextInputPanel`. + Future runJavaScriptTextInputPanel( + int identifier, String prompt, String defaultText, WKFrameInfoData frame); + static void setup(WKUIDelegateFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -3002,6 +3019,111 @@ abstract class WKUIDelegateFlutterApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi.runJavaScriptAlertPanel', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi.runJavaScriptAlertPanel 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.webview_flutter_wkwebview.WKUIDelegateFlutterApi.runJavaScriptAlertPanel was null, expected non-null int.'); + final String? arg_message = (args[1] as String?); + assert(arg_message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi.runJavaScriptAlertPanel was null, expected non-null String.'); + final WKFrameInfoData? arg_frame = (args[2] as WKFrameInfoData?); + assert(arg_frame != null, + 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi.runJavaScriptAlertPanel was null, expected non-null WKFrameInfoData.'); + try { + await api.runJavaScriptAlertPanel( + arg_identifier!, arg_message!, arg_frame!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi.runJavaScriptConfirmPanel', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi.runJavaScriptConfirmPanel 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.webview_flutter_wkwebview.WKUIDelegateFlutterApi.runJavaScriptConfirmPanel was null, expected non-null int.'); + final String? arg_message = (args[1] as String?); + assert(arg_message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi.runJavaScriptConfirmPanel was null, expected non-null String.'); + final WKFrameInfoData? arg_frame = (args[2] as WKFrameInfoData?); + assert(arg_frame != null, + 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi.runJavaScriptConfirmPanel was null, expected non-null WKFrameInfoData.'); + try { + final bool output = await api.runJavaScriptConfirmPanel( + arg_identifier!, arg_message!, arg_frame!); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi.runJavaScriptTextInputPanel', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi.runJavaScriptTextInputPanel 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.webview_flutter_wkwebview.WKUIDelegateFlutterApi.runJavaScriptTextInputPanel was null, expected non-null int.'); + final String? arg_prompt = (args[1] as String?); + assert(arg_prompt != null, + 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi.runJavaScriptTextInputPanel was null, expected non-null String.'); + final String? arg_defaultText = (args[2] as String?); + assert(arg_defaultText != null, + 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi.runJavaScriptTextInputPanel was null, expected non-null String.'); + final WKFrameInfoData? arg_frame = (args[3] as WKFrameInfoData?); + assert(arg_frame != null, + 'Argument for dev.flutter.pigeon.webview_flutter_wkwebview.WKUIDelegateFlutterApi.runJavaScriptTextInputPanel was null, expected non-null WKFrameInfoData.'); + try { + final String output = await api.runJavaScriptTextInputPanel( + arg_identifier!, arg_prompt!, arg_defaultText!, arg_frame!); + return wrapResponse(result: output); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } } } 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 d157b3c65892..fb99be0940ef 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 @@ -172,10 +172,16 @@ class WKNavigationAction { @immutable class WKFrameInfo { /// Construct a [WKFrameInfo]. - const WKFrameInfo({required this.isMainFrame}); + const WKFrameInfo({ + required this.isMainFrame, + required this.request, + }); /// Indicates whether the frame is the web site's main frame or a subframe. final bool isMainFrame; + + /// The URL request object associated with the navigation action. + final NSUrlRequest request; } /// A script that the web view injects into a webpage. @@ -728,6 +734,9 @@ class WKUIDelegate extends NSObject { WKUIDelegate({ this.onCreateWebView, this.requestMediaCapturePermission, + this.runJavaScriptAlertDialog, + this.runJavaScriptConfirmDialog, + this.runJavaScriptTextInputDialog, super.observeValue, super.binaryMessenger, super.instanceManager, @@ -749,6 +758,9 @@ class WKUIDelegate extends NSObject { WKUIDelegate.detached({ this.onCreateWebView, this.requestMediaCapturePermission, + this.runJavaScriptAlertDialog, + this.runJavaScriptConfirmDialog, + this.runJavaScriptTextInputDialog, super.observeValue, super.binaryMessenger, super.instanceManager, @@ -780,11 +792,30 @@ class WKUIDelegate extends NSObject { WKMediaCaptureType type, )? requestMediaCapturePermission; + /// Notifies the host application that the web page + /// wants to display a JavaScript alert() dialog. + final Future Function(String message, WKFrameInfo frame)? + runJavaScriptAlertDialog; + + /// Notifies the host application that the web page + /// wants to display a JavaScript confirm() dialog. + final Future Function(String message, WKFrameInfo frame)? + runJavaScriptConfirmDialog; + + /// Notifies the host application that the web page + /// wants to display a JavaScript prompt() dialog. + final Future Function( + String prompt, String defaultText, WKFrameInfo frame)? + runJavaScriptTextInputDialog; + @override WKUIDelegate copy() { return WKUIDelegate.detached( onCreateWebView: onCreateWebView, requestMediaCapturePermission: requestMediaCapturePermission, + runJavaScriptAlertDialog: runJavaScriptAlertDialog, + runJavaScriptConfirmDialog: runJavaScriptConfirmDialog, + runJavaScriptTextInputDialog: runJavaScriptTextInputDialog, observeValue: observeValue, binaryMessenger: _uiDelegateApi.binaryMessenger, instanceManager: _uiDelegateApi.instanceManager, 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 de28939bbb9c..9c94bcb58f89 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 @@ -153,7 +153,10 @@ extension _NavigationActionDataConverter on WKNavigationActionData { extension _WKFrameInfoDataConverter on WKFrameInfoData { WKFrameInfo toWKFrameInfo() { - return WKFrameInfo(isMainFrame: isMainFrame); + return WKFrameInfo( + isMainFrame: isMainFrame, + request: request.toNSUrlRequest(), + ); } } @@ -741,6 +744,33 @@ class WKUIDelegateFlutterApiImpl extends WKUIDelegateFlutterApi { return WKPermissionDecisionData(value: decision); } + + @override + Future runJavaScriptAlertPanel( + int identifier, String message, WKFrameInfoData frame) { + final WKUIDelegate instance = + instanceManager.getInstanceWithWeakReference(identifier)!; + return instance.runJavaScriptAlertDialog! + .call(message, frame.toWKFrameInfo()); + } + + @override + Future runJavaScriptConfirmPanel( + int identifier, String message, WKFrameInfoData frame) { + final WKUIDelegate instance = + instanceManager.getInstanceWithWeakReference(identifier)!; + return instance.runJavaScriptConfirmDialog! + .call(message, frame.toWKFrameInfo()); + } + + @override + Future runJavaScriptTextInputPanel(int identifier, String prompt, + String defaultText, WKFrameInfoData frame) { + final WKUIDelegate instance = + instanceManager.getInstanceWithWeakReference(identifier)!; + return instance.runJavaScriptTextInputDialog! + .call(prompt, defaultText, frame.toWKFrameInfo()); + } } /// Host api implementation for [WKNavigationDelegate]. 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 4b0c4ccd993a..3a21bc982b06 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 @@ -95,6 +95,19 @@ class WebKitProxy { WKFrameInfo frame, WKMediaCaptureType type, )? requestMediaCapturePermission, + Future Function( + String message, + WKFrameInfo frame, + )? runJavaScriptAlertDialog, + Future Function( + String message, + WKFrameInfo frame, + )? runJavaScriptConfirmDialog, + Future Function( + String prompt, + String defaultText, + WKFrameInfo frame, + )? runJavaScriptTextInputDialog, InstanceManager? instanceManager, }) createUIDelegate; } 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 357ab94acdd1..66b04c5f309d 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 @@ -220,6 +220,46 @@ class WebKitWebViewController extends PlatformWebViewController { return decisionCompleter.future; } }, + runJavaScriptAlertDialog: (String message, WKFrameInfo frame) async { + final Future Function(JavaScriptAlertDialogRequest request)? + callback = weakThis.target?._onJavaScriptAlertDialog; + if (callback != null) { + final JavaScriptAlertDialogRequest request = + JavaScriptAlertDialogRequest( + message: message, url: frame.request.url); + await callback.call(request); + return; + } + }, + runJavaScriptConfirmDialog: (String message, WKFrameInfo frame) async { + final Future Function(JavaScriptConfirmDialogRequest request)? + callback = weakThis.target?._onJavaScriptConfirmDialog; + if (callback != null) { + final JavaScriptConfirmDialogRequest request = + JavaScriptConfirmDialogRequest( + message: message, url: frame.request.url); + final bool result = await callback.call(request); + return result; + } + + return false; + }, + runJavaScriptTextInputDialog: + (String prompt, String defaultText, WKFrameInfo frame) async { + final Future Function(JavaScriptTextInputDialogRequest request)? + callback = weakThis.target?._onJavaScriptTextInputDialog; + if (callback != null) { + final JavaScriptTextInputDialogRequest request = + JavaScriptTextInputDialogRequest( + message: prompt, + url: frame.request.url, + defaultText: defaultText); + final String result = await callback.call(request); + return result; + } + + return ''; + }, ); _webView.setUIDelegate(_uiDelegate); @@ -274,6 +314,13 @@ class WebKitWebViewController extends PlatformWebViewController { void Function(JavaScriptConsoleMessage)? _onConsoleMessageCallback; void Function(PlatformWebViewPermissionRequest)? _onPermissionRequestCallback; + Future Function(JavaScriptAlertDialogRequest request)? + _onJavaScriptAlertDialog; + Future Function(JavaScriptConfirmDialogRequest request)? + _onJavaScriptConfirmDialog; + Future Function(JavaScriptTextInputDialogRequest request)? + _onJavaScriptTextInputDialog; + WebKitWebViewControllerCreationParams get _webKitParams => params as WebKitWebViewControllerCreationParams; @@ -680,6 +727,27 @@ window.addEventListener("error", function(e) { return (await _webView.evaluateJavaScript('navigator.userAgent;') as String?)!; } + + @override + Future setOnJavaScriptAlertDialog( + Future Function(JavaScriptAlertDialogRequest request) + onJavaScriptAlertDialog) async { + _onJavaScriptAlertDialog = onJavaScriptAlertDialog; + } + + @override + Future setOnJavaScriptConfirmDialog( + Future Function(JavaScriptConfirmDialogRequest request) + onJavaScriptConfirmDialog) async { + _onJavaScriptConfirmDialog = onJavaScriptConfirmDialog; + } + + @override + Future setOnJavaScriptTextInputDialog( + Future Function(JavaScriptTextInputDialogRequest request) + onJavaScriptTextInputDialog) async { + _onJavaScriptTextInputDialog = onJavaScriptTextInputDialog; + } } /// An implementation of [JavaScriptChannelParams] with the WebKit api. 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 c4894692f57e..d5fe530e9734 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/pigeons/web_kit.dart @@ -343,6 +343,7 @@ class WKNavigationActionData { /// See https://developer.apple.com/documentation/webkit/wkframeinfo?language=objc. class WKFrameInfoData { late bool isMainFrame; + late NSUrlRequestData request; } /// Mirror of NSError. @@ -805,6 +806,40 @@ abstract class WKUIDelegateFlutterApi { WKFrameInfoData frame, WKMediaCaptureTypeData type, ); + + /// Callback to Dart function `WKUIDelegate.runJavaScriptAlertPanel`. + @ObjCSelector( + 'runJavaScriptAlertPanelForDelegateWithIdentifier:message:frame:', + ) + @async + void runJavaScriptAlertPanel( + int identifier, + String message, + WKFrameInfoData frame, + ); + + /// Callback to Dart function `WKUIDelegate.runJavaScriptConfirmPanel`. + @ObjCSelector( + 'runJavaScriptConfirmPanelForDelegateWithIdentifier:message:frame:', + ) + @async + bool runJavaScriptConfirmPanel( + int identifier, + String message, + WKFrameInfoData frame, + ); + + /// Callback to Dart function `WKUIDelegate.runJavaScriptTextInputPanel`. + @ObjCSelector( + 'runJavaScriptTextInputPanelForDelegateWithIdentifier:prompt:defaultText:frame:', + ) + @async + String runJavaScriptTextInputPanel( + int identifier, + String prompt, + String defaultText, + WKFrameInfoData frame, + ); } /// Mirror of WKHttpCookieStore. diff --git a/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml b/packages/webview_flutter/webview_flutter_wkwebview/pubspec.yaml index 572f1d8b4919..2f14ca17b174 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.10.3 +version: 3.11.0 environment: sdk: ^3.2.3 @@ -20,7 +20,7 @@ dependencies: flutter: sdk: flutter path: ^1.8.0 - webview_flutter_platform_interface: ^2.7.0 + webview_flutter_platform_interface: ^2.9.0 dev_dependencies: build_runner: ^2.1.5 diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.dart index ebeb3d922b36..8c421193d187 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/legacy/web_kit_webview_widget_test.dart @@ -148,7 +148,7 @@ void main() { mockWebViewConfiguration, const WKNavigationAction( request: request, - targetFrame: WKFrameInfo(isMainFrame: false), + targetFrame: WKFrameInfo(isMainFrame: false, request: request), navigationType: WKNavigationType.linkActivated, ), ); @@ -1166,7 +1166,9 @@ void main() { mockWebView, const WKNavigationAction( request: NSUrlRequest(url: 'https://google.com'), - targetFrame: WKFrameInfo(isMainFrame: false), + targetFrame: WKFrameInfo( + isMainFrame: false, + request: NSUrlRequest(url: 'https://google.com')), navigationType: WKNavigationType.linkActivated, ), ), 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 2c95a52e1b93..e0eeb941cce9 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 @@ -585,7 +585,12 @@ void main() { url: 'url', allHttpHeaderFields: {}, ), - targetFrame: WKFrameInfoData(isMainFrame: false), + targetFrame: WKFrameInfoData( + isMainFrame: false, + request: NSUrlRequestData( + url: 'url', + allHttpHeaderFields: {}, + )), navigationType: WKNavigationType.linkActivated, ), ); @@ -1004,7 +1009,12 @@ void main() { url: 'url', allHttpHeaderFields: {}, ), - targetFrame: WKFrameInfoData(isMainFrame: false), + targetFrame: WKFrameInfoData( + isMainFrame: false, + request: NSUrlRequestData( + url: 'url', + allHttpHeaderFields: {}, + )), navigationType: WKNavigationType.linkActivated, ), ); @@ -1063,7 +1073,8 @@ void main() { const WKSecurityOrigin origin = WKSecurityOrigin(host: 'host', port: 12, protocol: 'protocol'); - const WKFrameInfo frame = WKFrameInfo(isMainFrame: false); + const WKFrameInfo frame = + WKFrameInfo(isMainFrame: false, request: NSUrlRequest(url: 'url')); const WKMediaCaptureType type = WKMediaCaptureType.microphone; flutterApi.requestMediaCapturePermission( @@ -1074,7 +1085,10 @@ void main() { port: origin.port, protocol: origin.protocol, ), - WKFrameInfoData(isMainFrame: frame.isMainFrame), + WKFrameInfoData( + isMainFrame: frame.isMainFrame, + request: NSUrlRequestData( + url: 'url', allHttpHeaderFields: {})), WKMediaCaptureTypeData(value: type), ); 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 63d432c6ac35..171e4056feed 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 @@ -205,7 +205,9 @@ void main() { WKWebView.detached(), const WKNavigationAction( request: NSUrlRequest(url: 'https://www.google.com'), - targetFrame: WKFrameInfo(isMainFrame: false), + targetFrame: WKFrameInfo( + isMainFrame: false, + request: NSUrlRequest(url: 'https://google.com')), navigationType: WKNavigationType.linkActivated, ), ), @@ -278,6 +280,9 @@ class CapturingUIDelegate extends WKUIDelegate { CapturingUIDelegate({ super.onCreateWebView, super.requestMediaCapturePermission, + super.runJavaScriptAlertDialog, + super.runJavaScriptConfirmDialog, + super.runJavaScriptTextInputDialog, super.instanceManager, }) : super.detached() { lastCreatedDelegate = this; 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 79f34f378dee..5bbb36d8a0ee 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 @@ -91,13 +91,29 @@ void main() { WKFrameInfo frame, WKMediaCaptureType type, )? requestMediaCapturePermission, + Future Function( + String message, + WKFrameInfo frame, + )? runJavaScriptAlertDialog, + Future Function( + String message, + WKFrameInfo frame, + )? runJavaScriptConfirmDialog, + Future Function( + String prompt, + String defaultText, + WKFrameInfo frame, + )? runJavaScriptTextInputDialog, InstanceManager? instanceManager, }) { return uiDelegate ?? CapturingUIDelegate( - onCreateWebView: onCreateWebView, - requestMediaCapturePermission: requestMediaCapturePermission, - ); + onCreateWebView: onCreateWebView, + requestMediaCapturePermission: + requestMediaCapturePermission, + runJavaScriptAlertDialog: runJavaScriptAlertDialog, + runJavaScriptConfirmDialog: runJavaScriptConfirmDialog, + runJavaScriptTextInputDialog: runJavaScriptTextInputDialog); }, createScriptMessageHandler: WKScriptMessageHandler.detached, ), @@ -999,7 +1015,9 @@ void main() { WKWebViewConfiguration.detached(), const WKNavigationAction( request: request, - targetFrame: WKFrameInfo(isMainFrame: false), + targetFrame: WKFrameInfo( + isMainFrame: false, + request: NSUrlRequest(url: 'https://google.com')), navigationType: WKNavigationType.linkActivated, ), ); @@ -1218,7 +1236,9 @@ void main() { CapturingUIDelegate.lastCreatedDelegate, WKWebView.detached(), const WKSecurityOrigin(host: '', port: 0, protocol: ''), - const WKFrameInfo(isMainFrame: false), + const WKFrameInfo( + isMainFrame: false, + request: NSUrlRequest(url: 'https://google.com')), WKMediaCaptureType.microphone, ); @@ -1228,6 +1248,84 @@ void main() { expect(decision, WKPermissionDecision.grant); }); + group('JavaScript Dialog', () { + test('setOnJavaScriptAlertDialog', () async { + final WebKitWebViewController controller = createControllerWithMocks(); + late final String message; + await controller.setOnJavaScriptAlertDialog( + (JavaScriptAlertDialogRequest request) async { + message = request.message; + return; + }); + + const String callbackMessage = 'Message'; + final Future Function(String message, WKFrameInfo frame) + onJavaScriptAlertDialog = + CapturingUIDelegate.lastCreatedDelegate.runJavaScriptAlertDialog!; + await onJavaScriptAlertDialog( + callbackMessage, + const WKFrameInfo( + isMainFrame: false, + request: NSUrlRequest(url: 'https://google.com'))); + + expect(message, callbackMessage); + }); + + test('setOnJavaScriptConfirmDialog', () async { + final WebKitWebViewController controller = createControllerWithMocks(); + late final String message; + const bool callbackReturnValue = true; + await controller.setOnJavaScriptConfirmDialog( + (JavaScriptConfirmDialogRequest request) async { + message = request.message; + return callbackReturnValue; + }); + + const String callbackMessage = 'Message'; + final Future Function(String message, WKFrameInfo frame) + onJavaScriptConfirmDialog = + CapturingUIDelegate.lastCreatedDelegate.runJavaScriptConfirmDialog!; + final bool returnValue = await onJavaScriptConfirmDialog( + callbackMessage, + const WKFrameInfo( + isMainFrame: false, + request: NSUrlRequest(url: 'https://google.com'))); + + expect(message, callbackMessage); + expect(returnValue, callbackReturnValue); + }); + + test('setOnJavaScriptTextInputDialog', () async { + final WebKitWebViewController controller = createControllerWithMocks(); + late final String message; + late final String? defaultText; + const String callbackReturnValue = 'Return Value'; + await controller.setOnJavaScriptTextInputDialog( + (JavaScriptTextInputDialogRequest request) async { + message = request.message; + defaultText = request.defaultText; + return callbackReturnValue; + }); + + const String callbackMessage = 'Message'; + const String callbackDefaultText = 'Default Text'; + final Future Function( + String prompt, String defaultText, WKFrameInfo frame) + onJavaScriptTextInputDialog = CapturingUIDelegate + .lastCreatedDelegate.runJavaScriptTextInputDialog!; + final String returnValue = await onJavaScriptTextInputDialog( + callbackMessage, + callbackDefaultText, + const WKFrameInfo( + isMainFrame: false, + request: NSUrlRequest(url: 'https://google.com'))); + + expect(message, callbackMessage); + expect(defaultText, callbackDefaultText); + expect(returnValue, callbackReturnValue); + }); + }); + test('inspectable', () async { final MockWKWebView mockWebView = MockWKWebView(); @@ -1419,6 +1517,9 @@ class CapturingUIDelegate extends WKUIDelegate { CapturingUIDelegate({ super.onCreateWebView, super.requestMediaCapturePermission, + super.runJavaScriptAlertDialog, + super.runJavaScriptConfirmDialog, + super.runJavaScriptTextInputDialog, super.instanceManager, }) : super.detached() { lastCreatedDelegate = this; diff --git a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.dart b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.dart index f7bd373b35bb..63151ebadef2 100644 --- a/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.dart +++ b/packages/webview_flutter/webview_flutter_wkwebview/test/webkit_webview_widget_test.dart @@ -195,6 +195,9 @@ WebKitWebViewController createTestWebViewController( }, createUIDelegate: ({ dynamic onCreateWebView, dynamic requestMediaCapturePermission, + dynamic runJavaScriptAlertDialog, + dynamic runJavaScriptConfirmDialog, + dynamic runJavaScriptTextInputDialog, InstanceManager? instanceManager, }) { final MockWKUIDelegate mockWKUIDelegate = MockWKUIDelegate();