diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index ab74abceaf63..3250aff7b70e 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.14.0 + +* Adds support to show JavaScript dialog. See `AndroidWebViewController.setOnJavaScriptAlertDialog`, `AndroidWebViewController.setOnJavaScriptConfirmDialog` and `AndroidWebViewController.setOnJavaScriptTextInputDialog`. + ## 3.13.2 * Fixes new lint warnings. 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 703c81a31da3..42a215afe88f 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 @@ -2643,6 +2643,12 @@ void setSynchronousReturnValueForOnShowFileChooser( void setSynchronousReturnValueForOnConsoleMessage( @NonNull Long instanceId, @NonNull Boolean value); + void setSynchronousReturnValueForOnJsAlert(@NonNull Long instanceId, @NonNull Boolean value); + + void setSynchronousReturnValueForOnJsConfirm(@NonNull Long instanceId, @NonNull Boolean value); + + void setSynchronousReturnValueForOnJsPrompt(@NonNull Long instanceId, @NonNull Boolean value); + /** The codec used by WebChromeClientHostApi. */ static @NonNull MessageCodec getCodec() { return new StandardMessageCodec(); @@ -2732,6 +2738,87 @@ static void setup( channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsAlert", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number instanceIdArg = (Number) args.get(0); + Boolean valueArg = (Boolean) args.get(1); + try { + api.setSynchronousReturnValueForOnJsAlert( + (instanceIdArg == null) ? null : instanceIdArg.longValue(), valueArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsConfirm", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number instanceIdArg = (Number) args.get(0); + Boolean valueArg = (Boolean) args.get(1); + try { + api.setSynchronousReturnValueForOnJsConfirm( + (instanceIdArg == null) ? null : instanceIdArg.longValue(), valueArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsPrompt", + getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Number instanceIdArg = (Number) args.get(0); + Boolean valueArg = (Boolean) args.get(1); + try { + api.setSynchronousReturnValueForOnJsPrompt( + (instanceIdArg == null) ? null : instanceIdArg.longValue(), valueArg); + wrapped.add(0, null); + } catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } } } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ @@ -2967,6 +3054,60 @@ public void onConsoleMessage( new ArrayList(Arrays.asList(instanceIdArg, messageArg)), channelReply -> callback.reply(null)); } + + public void onJsAlert( + @NonNull Long instanceIdArg, + @NonNull String urlArg, + @NonNull String messageArg, + @NonNull Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onJsAlert", + getCodec()); + channel.send( + new ArrayList(Arrays.asList(instanceIdArg, urlArg, messageArg)), + channelReply -> callback.reply(null)); + } + + public void onJsConfirm( + @NonNull Long instanceIdArg, + @NonNull String urlArg, + @NonNull String messageArg, + @NonNull Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onJsConfirm", + getCodec()); + channel.send( + new ArrayList(Arrays.asList(instanceIdArg, urlArg, messageArg)), + channelReply -> { + @SuppressWarnings("ConstantConditions") + Boolean output = (Boolean) channelReply; + callback.reply(output); + }); + } + + public void onJsPrompt( + @NonNull Long instanceIdArg, + @NonNull String urlArg, + @NonNull String messageArg, + @NonNull String defaultValueArg, + @NonNull Reply callback) { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, + "dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onJsPrompt", + getCodec()); + channel.send( + new ArrayList(Arrays.asList(instanceIdArg, urlArg, messageArg, defaultValueArg)), + channelReply -> { + @SuppressWarnings("ConstantConditions") + String output = (String) channelReply; + callback.reply(output); + }); + } } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface WebStorageHostApi { diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java index b383dfdba11e..d5601670630c 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java @@ -187,6 +187,56 @@ public void onConsoleMessage( callback); } + /** + * Sends a message to Dart to call `WebChromeClient.onJsAlert` on the Dart object representing + * `instance`. + */ + public void onJsAlert( + @NonNull WebChromeClient instance, + @NonNull String url, + @NonNull String message, + @NonNull WebChromeClientFlutterApi.Reply callback) { + super.onJsAlert( + Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(instance)), + url, + message, + callback); + } + + /** + * Sends a message to Dart to call `WebChromeClient.onJsConfirm` on the Dart object representing + * `instance`. + */ + public void onJsConfirm( + @NonNull WebChromeClient instance, + @NonNull String url, + @NonNull String message, + @NonNull WebChromeClientFlutterApi.Reply callback) { + super.onJsConfirm( + Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(instance)), + url, + message, + callback); + } + + /** + * Sends a message to Dart to call `WebChromeClient.onJsPrompt` on the Dart object representing + * `instance`. + */ + public void onJsPrompt( + @NonNull WebChromeClient instance, + @NonNull String url, + @NonNull String message, + @NonNull String defaultValue, + @NonNull WebChromeClientFlutterApi.Reply callback) { + super.onJsPrompt( + Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(instance)), + url, + message, + defaultValue, + callback); + } + private long getIdentifierForClient(WebChromeClient webChromeClient) { final Long identifier = instanceManager.getIdentifierForStrongReference(webChromeClient); if (identifier == null) { diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java index cb382d51f2b1..3f230b7b794f 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java @@ -11,6 +11,8 @@ import android.view.View; import android.webkit.ConsoleMessage; import android.webkit.GeolocationPermissions; +import android.webkit.JsPromptResult; +import android.webkit.JsResult; import android.webkit.PermissionRequest; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; @@ -43,6 +45,10 @@ public static class WebChromeClientImpl extends SecureWebChromeClient { private boolean returnValueForOnShowFileChooser = false; private boolean returnValueForOnConsoleMessage = false; + private boolean returnValueForOnJsAlert = false; + private boolean returnValueForOnJsConfirm = false; + private boolean returnValueForOnJsPrompt = false; + /** * Creates a {@link WebChromeClient} that passes arguments of callbacks methods to Dart. * @@ -124,6 +130,77 @@ public void setReturnValueForOnShowFileChooser(boolean value) { public void setReturnValueForOnConsoleMessage(boolean value) { returnValueForOnConsoleMessage = value; } + + public void setReturnValueForOnJsAlert(boolean value) { + returnValueForOnJsAlert = value; + } + + public void setReturnValueForOnJsConfirm(boolean value) { + returnValueForOnJsConfirm = value; + } + + public void setReturnValueForOnJsPrompt(boolean value) { + returnValueForOnJsPrompt = value; + } + + @Override + public boolean onJsAlert(WebView view, String url, String message, JsResult result) { + if (returnValueForOnJsAlert) { + flutterApi.onJsAlert( + this, + url, + message, + reply -> { + result.confirm(); + }); + return true; + } else { + return false; + } + } + + @Override + public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { + if (returnValueForOnJsConfirm) { + flutterApi.onJsConfirm( + this, + url, + message, + reply -> { + if (reply) { + result.confirm(); + } else { + result.cancel(); + } + }); + return true; + } else { + return false; + } + } + + @Override + public boolean onJsPrompt( + WebView view, String url, String message, String defaultValue, JsPromptResult result) { + if (returnValueForOnJsPrompt) { + flutterApi.onJsPrompt( + this, + url, + message, + defaultValue, + reply -> { + @Nullable String inputMessage = reply; + if (inputMessage != null) { + result.confirm(inputMessage); + } else { + result.cancel(); + } + }); + return true; + } else { + return false; + } + } } /** @@ -267,4 +344,28 @@ public void setSynchronousReturnValueForOnConsoleMessage( Objects.requireNonNull(instanceManager.getInstance(instanceId)); webChromeClient.setReturnValueForOnConsoleMessage(value); } + + @Override + public void setSynchronousReturnValueForOnJsAlert( + @NonNull Long instanceId, @NonNull Boolean value) { + final WebChromeClientImpl webChromeClient = + Objects.requireNonNull(instanceManager.getInstance(instanceId)); + webChromeClient.setReturnValueForOnJsAlert(value); + } + + @Override + public void setSynchronousReturnValueForOnJsConfirm( + @NonNull Long instanceId, @NonNull Boolean value) { + final WebChromeClientImpl webChromeClient = + Objects.requireNonNull(instanceManager.getInstance(instanceId)); + webChromeClient.setReturnValueForOnJsConfirm(value); + } + + @Override + public void setSynchronousReturnValueForOnJsPrompt( + @NonNull Long instanceId, @NonNull Boolean value) { + final WebChromeClientImpl webChromeClient = + Objects.requireNonNull(instanceManager.getInstance(instanceId)); + webChromeClient.setReturnValueForOnJsPrompt(value); + } } 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 9ed263e8dc6c..ae509680d571 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 @@ -1397,6 +1397,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_android/example/lib/main.dart b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart index 582a48ac92ba..addccb00cde6 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,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}); @@ -287,6 +319,7 @@ enum MenuOptions { videoExample, logExample, basicAuthentication, + javaScriptAlert, } class SampleMenu extends StatelessWidget { @@ -340,6 +373,8 @@ class SampleMenu extends StatelessWidget { _onLogExample(); case MenuOptions.basicAuthentication: _promptForUrl(context); + case MenuOptions.javaScriptAlert: + _onJavaScriptAlertExample(context); } }, itemBuilder: (BuildContext context) => >[ @@ -408,6 +443,10 @@ class SampleMenu extends StatelessWidget { value: MenuOptions.basicAuthentication, child: Text('Basic Authentication Example'), ), + const PopupMenuItem( + value: MenuOptions.javaScriptAlert, + child: Text('JavaScript Alert Example'), + ), ], ); } @@ -556,6 +595,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(); @@ -623,6 +684,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_android/example/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml index 337a70d06a33..e708e3dcaf84 100644 --- a/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/example/pubspec.yaml @@ -17,7 +17,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.7.0 + webview_flutter_platform_interface: ^2.9.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 339ce9fed2b8..90af244aa4c3 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 @@ -53,6 +53,10 @@ class AndroidWebViewProxy { android_webview.CustomViewCallback callback)? onShowCustomView, void Function(android_webview.WebChromeClient instance)? onHideCustomView, + Future Function(String url, String message)? onJsAlert, + Future Function(String url, String message)? onJsConfirm, + Future Function(String url, String message, String defaultValue)? + onJsPrompt, }) createAndroidWebChromeClient; /// Constructs a [android_webview.WebViewClient]. 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 f18c66b02e30..8cf4a43dc105 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 @@ -1065,6 +1065,9 @@ class WebChromeClient extends JavaObject { this.onShowCustomView, this.onHideCustomView, this.onConsoleMessage, + this.onJsAlert, + this.onJsConfirm, + this.onJsPrompt, @visibleForTesting super.binaryMessenger, @visibleForTesting super.instanceManager, }) : super.detached() { @@ -1087,6 +1090,9 @@ class WebChromeClient extends JavaObject { this.onShowCustomView, this.onHideCustomView, this.onConsoleMessage, + this.onJsAlert, + this.onJsConfirm, + this.onJsPrompt, super.binaryMessenger, super.instanceManager, }) : super.detached(); @@ -1144,6 +1150,19 @@ class WebChromeClient extends JavaObject { final void Function(WebChromeClient instance, ConsoleMessage message)? onConsoleMessage; + /// Notify the host application that the web page wants to display a + /// JavaScript alert() dialog. + final Future Function(String url, String message)? onJsAlert; + + /// Notify the host application that the web page wants to display a + /// JavaScript confirm() dialog. + final Future Function(String url, String message)? onJsConfirm; + + /// Notify the host application that the web page wants to display a + /// JavaScript prompt() dialog. + final Future Function( + String url, String message, String defaultValue)? onJsPrompt; + /// Sets the required synchronous return value for the Java method, /// `WebChromeClient.onShowFileChooser(...)`. /// @@ -1200,6 +1219,78 @@ class WebChromeClient extends JavaObject { ); } + /// Sets the required synchronous return value for the Java method, + /// `WebChromeClient.onJsAlert(...)`. + /// + /// The Java method, `WebChromeClient.onJsAlert(...)`, requires + /// a boolean to be returned and this method sets the returned value for all + /// calls to the Java method. + /// + /// Setting this to true indicates that the client is handling all console + /// messages. + /// + /// Requires [onJsAlert] to be nonnull. + /// + /// Defaults to false. + Future setSynchronousReturnValueForOnJsAlert( + bool value, + ) { + if (value && onJsAlert == null) { + throw StateError( + 'Setting this to true requires `onJsAlert` to be nonnull.', + ); + } + return api.setSynchronousReturnValueForOnJsAlertFromInstance(this, value); + } + + /// Sets the required synchronous return value for the Java method, + /// `WebChromeClient.onJsConfirm(...)`. + /// + /// The Java method, `WebChromeClient.onJsConfirm(...)`, requires + /// a boolean to be returned and this method sets the returned value for all + /// calls to the Java method. + /// + /// Setting this to true indicates that the client is handling all console + /// messages. + /// + /// Requires [onJsConfirm] to be nonnull. + /// + /// Defaults to false. + Future setSynchronousReturnValueForOnJsConfirm( + bool value, + ) { + if (value && onJsConfirm == null) { + throw StateError( + 'Setting this to true requires `onJsConfirm` to be nonnull.', + ); + } + return api.setSynchronousReturnValueForOnJsConfirmFromInstance(this, value); + } + + /// Sets the required synchronous return value for the Java method, + /// `WebChromeClient.onJsPrompt(...)`. + /// + /// The Java method, `WebChromeClient.onJsPrompt(...)`, requires + /// a boolean to be returned and this method sets the returned value for all + /// calls to the Java method. + /// + /// Setting this to true indicates that the client is handling all console + /// messages. + /// + /// Requires [onJsPrompt] to be nonnull. + /// + /// Defaults to false. + Future setSynchronousReturnValueForOnJsPrompt( + bool value, + ) { + if (value && onJsPrompt == null) { + throw StateError( + 'Setting this to true requires `onJsPrompt` to be nonnull.', + ); + } + return api.setSynchronousReturnValueForOnJsPromptFromInstance(this, value); + } + @override WebChromeClient copy() { return WebChromeClient.detached( @@ -1211,6 +1302,9 @@ class WebChromeClient extends JavaObject { onShowCustomView: onShowCustomView, onHideCustomView: onHideCustomView, onConsoleMessage: onConsoleMessage, + onJsAlert: onJsAlert, + onJsConfirm: onJsConfirm, + onJsPrompt: onJsPrompt, binaryMessenger: _api.binaryMessenger, instanceManager: _api.instanceManager, ); 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 6911ed651dae..d2a872406af5 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 @@ -2106,6 +2106,78 @@ class WebChromeClientHostApi { return; } } + + Future setSynchronousReturnValueForOnJsAlert( + int arg_instanceId, bool arg_value) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsAlert', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel + .send([arg_instanceId, arg_value]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } + + Future setSynchronousReturnValueForOnJsConfirm( + int arg_instanceId, bool arg_value) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsConfirm', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel + .send([arg_instanceId, arg_value]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } + + Future setSynchronousReturnValueForOnJsPrompt( + int arg_instanceId, bool arg_value) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsPrompt', + codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel + .send([arg_instanceId, arg_value]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } } class FlutterAssetManagerHostApi { @@ -2226,6 +2298,13 @@ abstract class WebChromeClientFlutterApi { /// Callback to Dart function `WebChromeClient.onConsoleMessage`. void onConsoleMessage(int instanceId, ConsoleMessage message); + Future onJsAlert(int instanceId, String url, String message); + + Future onJsConfirm(int instanceId, String url, String message); + + Future onJsPrompt( + int instanceId, String url, String message, String defaultValue); + static void setup(WebChromeClientFlutterApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -2422,6 +2501,89 @@ abstract class WebChromeClientFlutterApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onJsAlert', + 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_android.WebChromeClientFlutterApi.onJsAlert 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.webview_flutter_android.WebChromeClientFlutterApi.onJsAlert was null, expected non-null int.'); + final String? arg_url = (args[1] as String?); + assert(arg_url != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onJsAlert was null, expected non-null String.'); + final String? arg_message = (args[2] as String?); + assert(arg_message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onJsAlert was null, expected non-null String.'); + await api.onJsAlert(arg_instanceId!, arg_url!, arg_message!); + return; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onJsConfirm', + 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_android.WebChromeClientFlutterApi.onJsConfirm 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.webview_flutter_android.WebChromeClientFlutterApi.onJsConfirm was null, expected non-null int.'); + final String? arg_url = (args[1] as String?); + assert(arg_url != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onJsConfirm was null, expected non-null String.'); + final String? arg_message = (args[2] as String?); + assert(arg_message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onJsConfirm was null, expected non-null String.'); + final bool output = + await api.onJsConfirm(arg_instanceId!, arg_url!, arg_message!); + return output; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onJsPrompt', + 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_android.WebChromeClientFlutterApi.onJsPrompt 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.webview_flutter_android.WebChromeClientFlutterApi.onJsPrompt was null, expected non-null int.'); + final String? arg_url = (args[1] as String?); + assert(arg_url != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onJsPrompt was null, expected non-null String.'); + final String? arg_message = (args[2] as String?); + assert(arg_message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onJsPrompt was null, expected non-null String.'); + final String? arg_defaultValue = (args[3] as String?); + assert(arg_defaultValue != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onJsPrompt was null, expected non-null String.'); + final String output = await api.onJsPrompt( + arg_instanceId!, arg_url!, arg_message!, arg_defaultValue!); + return output; + }); + } + } } } 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 e0e4c9a3b7a1..b30cbc1a3a76 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 @@ -948,6 +948,33 @@ class WebChromeClientHostApiImpl extends WebChromeClientHostApi { value, ); } + + /// Helper method to convert instances ids to objects. + Future setSynchronousReturnValueForOnJsAlertFromInstance( + WebChromeClient instance, + bool value, + ) { + return setSynchronousReturnValueForOnJsAlert( + instanceManager.getIdentifier(instance)!, value); + } + + /// Helper method to convert instances ids to objects. + Future setSynchronousReturnValueForOnJsConfirmFromInstance( + WebChromeClient instance, + bool value, + ) { + return setSynchronousReturnValueForOnJsConfirm( + instanceManager.getIdentifier(instance)!, value); + } + + /// Helper method to convert instances ids to objects. + Future setSynchronousReturnValueForOnJsPromptFromInstance( + WebChromeClient instance, + bool value, + ) { + return setSynchronousReturnValueForOnJsPrompt( + instanceManager.getIdentifier(instance)!, value); + } } /// Flutter api implementation for [DownloadListener]. @@ -1080,6 +1107,31 @@ class WebChromeClientFlutterApiImpl extends WebChromeClientFlutterApi { instanceManager.getInstanceWithWeakReference(instanceId)!; instance.onConsoleMessage?.call(instance, message); } + + @override + Future onJsAlert(int instanceId, String url, String message) { + final WebChromeClient instance = + instanceManager.getInstanceWithWeakReference(instanceId)!; + + return instance.onJsAlert!(url, message); + } + + @override + Future onJsConfirm(int instanceId, String url, String message) { + final WebChromeClient instance = + instanceManager.getInstanceWithWeakReference(instanceId)!; + + return instance.onJsConfirm!(url, message); + } + + @override + Future onJsPrompt( + int instanceId, String url, String message, String defaultValue) { + final WebChromeClient instance = + instanceManager.getInstanceWithWeakReference(instanceId)!; + + return instance.onJsPrompt!(url, message, defaultValue); + } } /// Host api implementation for [WebStorage]. 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 3c9d6d086ac7..d4d77667ba90 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 @@ -264,6 +264,49 @@ class AndroidWebViewController extends PlatformWebViewController { }; }, ), + onJsAlert: withWeakReferenceTo(this, + (WeakReference weakReference) { + return (String url, String message) async { + final Future Function(JavaScriptAlertDialogRequest)? callback = + weakReference.target?._onJavaScriptAlert; + if (callback != null) { + final JavaScriptAlertDialogRequest request = + JavaScriptAlertDialogRequest(message: message, url: url); + + await callback.call(request); + } + return; + }; + }), + onJsConfirm: withWeakReferenceTo(this, + (WeakReference weakReference) { + return (String url, String message) async { + final Future Function(JavaScriptConfirmDialogRequest)? callback = + weakReference.target?._onJavaScriptConfirm; + if (callback != null) { + final JavaScriptConfirmDialogRequest request = + JavaScriptConfirmDialogRequest(message: message, url: url); + final bool result = await callback.call(request); + return result; + } + return false; + }; + }), + onJsPrompt: withWeakReferenceTo(this, + (WeakReference weakReference) { + return (String url, String message, String defaultValue) async { + final Future Function(JavaScriptTextInputDialogRequest)? + callback = weakReference.target?._onJavaScriptPrompt; + if (callback != null) { + final JavaScriptTextInputDialogRequest request = + JavaScriptTextInputDialogRequest( + message: message, url: url, defaultText: defaultValue); + final String result = await callback.call(request); + return result; + } + return ''; + }; + }), ); /// The native [android_webview.FlutterAssetManager] allows managing assets. @@ -290,6 +333,13 @@ class AndroidWebViewController extends PlatformWebViewController { void Function(JavaScriptConsoleMessage consoleMessage)? _onConsoleLogCallback; + Future Function(JavaScriptAlertDialogRequest request)? + _onJavaScriptAlert; + Future Function(JavaScriptConfirmDialogRequest request)? + _onJavaScriptConfirm; + Future Function(JavaScriptTextInputDialogRequest request)? + _onJavaScriptPrompt; + /// Whether to enable the platform's webview content debugging tools. /// /// Defaults to false. @@ -616,6 +666,30 @@ class AndroidWebViewController extends PlatformWebViewController { @override Future getUserAgent() => _webView.settings.getUserAgentString(); + + @override + Future setOnJavaScriptAlertDialog( + Future Function(JavaScriptAlertDialogRequest request) + onJavaScriptAlertDialog) async { + _onJavaScriptAlert = onJavaScriptAlertDialog; + return _webChromeClient.setSynchronousReturnValueForOnJsAlert(true); + } + + @override + Future setOnJavaScriptConfirmDialog( + Future Function(JavaScriptConfirmDialogRequest request) + onJavaScriptConfirmDialog) async { + _onJavaScriptConfirm = onJavaScriptConfirmDialog; + return _webChromeClient.setSynchronousReturnValueForOnJsConfirm(true); + } + + @override + Future setOnJavaScriptTextInputDialog( + Future Function(JavaScriptTextInputDialogRequest request) + onJavaScriptTextInputDialog) async { + _onJavaScriptPrompt = onJavaScriptTextInputDialog; + return _webChromeClient.setSynchronousReturnValueForOnJsPrompt(true); + } } /// Android implementation of [PlatformWebViewPermissionRequest]. 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 34e8f21bde14..3b30cfbc177c 100644 --- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart +++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart @@ -398,6 +398,21 @@ abstract class WebChromeClientHostApi { int instanceId, bool value, ); + + void setSynchronousReturnValueForOnJsAlert( + int instanceId, + bool value, + ); + + void setSynchronousReturnValueForOnJsConfirm( + int instanceId, + bool value, + ); + + void setSynchronousReturnValueForOnJsPrompt( + int instanceId, + bool value, + ); } @HostApi(dartHostTestHandler: 'TestAssetManagerHostApi') @@ -443,6 +458,16 @@ abstract class WebChromeClientFlutterApi { /// Callback to Dart function `WebChromeClient.onConsoleMessage`. void onConsoleMessage(int instanceId, ConsoleMessage message); + + @async + void onJsAlert(int instanceId, String url, String message); + + @async + bool onJsConfirm(int instanceId, String url, String message); + + @async + String onJsPrompt( + int instanceId, String url, String message, String defaultValue); } @HostApi(dartHostTestHandler: 'TestWebStorageHostApi') diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 91e8347772cb..65ace680cbc3 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.13.2 +version: 3.14.0 environment: sdk: ">=3.0.0 <4.0.0" @@ -20,7 +20,7 @@ flutter: dependencies: flutter: sdk: flutter - webview_flutter_platform_interface: ^2.7.0 + webview_flutter_platform_interface: ^2.9.0 dev_dependencies: build_runner: ^2.1.4 @@ -32,4 +32,4 @@ dev_dependencies: topics: - html - webview - - webview-flutter + - webview-flutter \ No newline at end of file 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 c9a7e8ddcbe9..36681d092164 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 @@ -566,6 +566,9 @@ class CapturingWebChromeClient extends android_webview.WebChromeClient { super.onHideCustomView, super.onPermissionRequest, super.onConsoleMessage, + super.onJsAlert, + super.onJsConfirm, + super.onJsPrompt, super.binaryMessenger, super.instanceManager, }) : super.detached() { 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 eb5f8a89eef5..279a58ac1437 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 @@ -76,6 +76,10 @@ void main() { void Function(android_webview.WebChromeClient instance, android_webview.ConsoleMessage message)? onConsoleMessage, + Future Function(String url, String message)? onJsAlert, + Future Function(String url, String message)? onJsConfirm, + Future Function(String url, String message, String defaultValue)? + onJsPrompt, })? createWebChromeClient, android_webview.WebView? mockWebView, android_webview.WebViewClient? mockWebViewClient, @@ -117,6 +121,13 @@ void main() { void Function(android_webview.WebChromeClient instance, android_webview.ConsoleMessage message)? onConsoleMessage, + Future Function(String url, String message)? + onJsAlert, + Future Function(String url, String message)? + onJsConfirm, + Future Function( + String url, String message, String defaultValue)? + onJsPrompt, }) => MockWebChromeClient(), createAndroidWebView: () => nonNullMockWebView, @@ -619,6 +630,9 @@ void main() { dynamic onShowCustomView, dynamic onHideCustomView, dynamic onConsoleMessage, + dynamic onJsAlert, + dynamic onJsConfirm, + dynamic onJsPrompt, }) { onShowFileChooserCallback = onShowFileChooser!; return mockWebChromeClient; @@ -690,6 +704,9 @@ void main() { dynamic onShowCustomView, dynamic onHideCustomView, dynamic onConsoleMessage, + dynamic onJsAlert, + dynamic onJsConfirm, + dynamic onJsPrompt, }) { onGeoPermissionHandle = onGeolocationPermissionsShowPrompt!; onGeoPermissionHidePromptHandle = onGeolocationPermissionsHidePrompt!; @@ -757,6 +774,9 @@ void main() { dynamic onGeolocationPermissionsShowPrompt, dynamic onGeolocationPermissionsHidePrompt, dynamic onPermissionRequest, + dynamic onJsAlert, + dynamic onJsConfirm, + dynamic onJsPrompt, void Function( android_webview.WebChromeClient instance, android_webview.View view, @@ -818,6 +838,9 @@ void main() { dynamic onShowCustomView, dynamic onHideCustomView, dynamic onConsoleMessage, + dynamic onJsAlert, + dynamic onJsConfirm, + dynamic onJsPrompt, }) { onPermissionRequestCallback = onPermissionRequest!; return mockWebChromeClient; @@ -873,6 +896,9 @@ void main() { dynamic onShowCustomView, dynamic onHideCustomView, dynamic onConsoleMessage, + dynamic onJsAlert, + dynamic onJsConfirm, + dynamic onJsPrompt, }) { onPermissionRequestCallback = onPermissionRequest!; return mockWebChromeClient; @@ -898,6 +924,132 @@ void main() { expect(callbackCalled, isFalse); }); + group('JavaScript Dialog', () { + test('setOnJavaScriptAlertDialog', () async { + late final Future Function(String url, String message) + onJsAlertCallback; + + final MockWebChromeClient mockWebChromeClient = MockWebChromeClient(); + + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: ({ + dynamic onProgressChanged, + dynamic onShowFileChooser, + dynamic onGeolocationPermissionsShowPrompt, + dynamic onGeolocationPermissionsHidePrompt, + dynamic onPermissionRequest, + dynamic onShowCustomView, + dynamic onHideCustomView, + Future Function(String url, String message)? onJsAlert, + dynamic onJsConfirm, + dynamic onJsPrompt, + dynamic onConsoleMessage, + }) { + onJsAlertCallback = onJsAlert!; + return mockWebChromeClient; + }, + ); + + late final String message; + await controller.setOnJavaScriptAlertDialog( + (JavaScriptAlertDialogRequest request) async { + message = request.message; + return; + }); + + const String callbackMessage = 'Message'; + await onJsAlertCallback('', callbackMessage); + expect(message, callbackMessage); + }); + + test('setOnJavaScriptConfirmDialog', () async { + late final Future Function(String url, String message) + onJsConfirmCallback; + + final MockWebChromeClient mockWebChromeClient = MockWebChromeClient(); + + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: ({ + dynamic onProgressChanged, + dynamic onShowFileChooser, + dynamic onGeolocationPermissionsShowPrompt, + dynamic onGeolocationPermissionsHidePrompt, + dynamic onPermissionRequest, + dynamic onShowCustomView, + dynamic onHideCustomView, + dynamic onJsAlert, + Future Function(String url, String message)? onJsConfirm, + dynamic onJsPrompt, + dynamic onConsoleMessage, + }) { + onJsConfirmCallback = onJsConfirm!; + return mockWebChromeClient; + }, + ); + + late final String message; + const bool callbackReturnValue = true; + await controller.setOnJavaScriptConfirmDialog( + (JavaScriptConfirmDialogRequest request) async { + message = request.message; + return callbackReturnValue; + }); + + const String callbackMessage = 'Message'; + final bool returnValue = await onJsConfirmCallback('', callbackMessage); + + expect(message, callbackMessage); + expect(returnValue, callbackReturnValue); + }); + + test('setOnJavaScriptTextInputDialog', () async { + late final Future Function( + String url, String message, String defaultValue) onJsPromptCallback; + final MockWebChromeClient mockWebChromeClient = MockWebChromeClient(); + + final AndroidWebViewController controller = createControllerWithMocks( + createWebChromeClient: ({ + dynamic onProgressChanged, + dynamic onShowFileChooser, + dynamic onGeolocationPermissionsShowPrompt, + dynamic onGeolocationPermissionsHidePrompt, + dynamic onPermissionRequest, + dynamic onShowCustomView, + dynamic onHideCustomView, + dynamic onJsAlert, + dynamic onJsConfirm, + Future Function( + String url, String message, String defaultText)? + onJsPrompt, + dynamic onConsoleMessage, + }) { + onJsPromptCallback = onJsPrompt!; + return mockWebChromeClient; + }, + ); + + 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 String returnValue = + await onJsPromptCallback('', callbackMessage, callbackDefaultText); + + expect(message, callbackMessage); + expect(defaultText, callbackDefaultText); + expect(returnValue, callbackReturnValue); + }); + }); + test('setOnConsoleLogCallback', () async { late final void Function( android_webview.WebChromeClient instance, @@ -914,6 +1066,9 @@ void main() { dynamic onPermissionRequest, dynamic onShowCustomView, dynamic onHideCustomView, + dynamic onJsAlert, + dynamic onJsConfirm, + dynamic onJsPrompt, void Function( android_webview.WebChromeClient, android_webview.ConsoleMessage, @@ -1464,6 +1619,9 @@ void main() { onShowCustomView, dynamic onHideCustomView, dynamic onConsoleMessage, + dynamic onJsAlert, + dynamic onJsConfirm, + dynamic onJsPrompt, }) { onShowCustomViewCallback = onShowCustomView; return mockWebChromeClient; 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 c6248455ab54..d86021bf839f 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 @@ -915,6 +915,19 @@ class MockAndroidWebViewProxy extends _i1.Mock _i2.GeolocationPermissionsCallback, )? onGeolocationPermissionsShowPrompt, void Function(_i2.WebChromeClient)? onHideCustomView, + _i9.Future Function( + String, + String, + )? onJsAlert, + _i9.Future Function( + String, + String, + )? onJsConfirm, + _i9.Future Function( + String, + String, + String, + )? onJsPrompt, void Function( _i2.WebChromeClient, _i2.PermissionRequest, @@ -946,6 +959,19 @@ class MockAndroidWebViewProxy extends _i1.Mock _i2.GeolocationPermissionsCallback, )? onGeolocationPermissionsShowPrompt, void Function(_i2.WebChromeClient)? onHideCustomView, + _i9.Future Function( + String, + String, + )? onJsAlert, + _i9.Future Function( + String, + String, + )? onJsConfirm, + _i9.Future Function( + String, + String, + String, + )? onJsPrompt, void Function( _i2.WebChromeClient, _i2.PermissionRequest, @@ -980,6 +1006,19 @@ class MockAndroidWebViewProxy extends _i1.Mock _i2.GeolocationPermissionsCallback, )? onGeolocationPermissionsShowPrompt, void Function(_i2.WebChromeClient)? onHideCustomView, + _i9.Future Function( + String, + String, + )? onJsAlert, + _i9.Future Function( + String, + String, + )? onJsConfirm, + _i9.Future Function( + String, + String, + String, + )? onJsPrompt, void Function( _i2.WebChromeClient, _i2.PermissionRequest, @@ -1013,6 +1052,19 @@ class MockAndroidWebViewProxy extends _i1.Mock _i2.GeolocationPermissionsCallback, )? onGeolocationPermissionsShowPrompt, void Function(_i2.WebChromeClient)? onHideCustomView, + _i9.Future Function( + String, + String, + )? onJsAlert, + _i9.Future Function( + String, + String, + )? onJsConfirm, + _i9.Future Function( + String, + String, + String, + )? onJsPrompt, void Function( _i2.WebChromeClient, _i2.PermissionRequest, @@ -2030,6 +2082,36 @@ class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { ) as _i9.Future); @override + _i9.Future setSynchronousReturnValueForOnJsAlert(bool? value) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForOnJsAlert, + [value], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + _i9.Future setSynchronousReturnValueForOnJsConfirm(bool? value) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForOnJsConfirm, + [value], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + _i9.Future setSynchronousReturnValueForOnJsPrompt(bool? value) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForOnJsPrompt, + [value], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override _i2.WebChromeClient copy() => (super.noSuchMethod( Invocation.method( #copy, diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart index d4b640d6a08d..4a9ced6a07b8 100644 --- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart @@ -510,6 +510,51 @@ class MockTestWebChromeClientHostApi extends _i1.Mock ), returnValueForMissingStub: null, ); + @override + void setSynchronousReturnValueForOnJsAlert( + int? instanceId, + bool? value, + ) => + super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForOnJsAlert, + [ + instanceId, + value, + ], + ), + returnValueForMissingStub: null, + ); + @override + void setSynchronousReturnValueForOnJsConfirm( + int? instanceId, + bool? value, + ) => + super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForOnJsConfirm, + [ + instanceId, + value, + ], + ), + returnValueForMissingStub: null, + ); + @override + void setSynchronousReturnValueForOnJsPrompt( + int? instanceId, + bool? value, + ) => + super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForOnJsPrompt, + [ + instanceId, + value, + ], + ), + returnValueForMissingStub: null, + ); } /// A class which mocks [TestWebSettingsHostApi]. @@ -1286,6 +1331,36 @@ class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { ) as _i5.Future); @override + _i5.Future setSynchronousReturnValueForOnJsAlert(bool? value) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForOnJsAlert, + [value], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setSynchronousReturnValueForOnJsConfirm(bool? value) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForOnJsConfirm, + [value], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setSynchronousReturnValueForOnJsPrompt(bool? value) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForOnJsPrompt, + [value], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override _i2.WebChromeClient copy() => (super.noSuchMethod( Invocation.method( #copy, diff --git a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart index 24e3fbbbcfb6..f7a11fb9e872 100644 --- a/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart +++ b/packages/webview_flutter/webview_flutter_android/test/legacy/webview_android_widget_test.mocks.dart @@ -871,6 +871,36 @@ class MockWebChromeClient extends _i1.Mock implements _i2.WebChromeClient { ) as _i5.Future); @override + _i5.Future setSynchronousReturnValueForOnJsAlert(bool? value) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForOnJsAlert, + [value], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setSynchronousReturnValueForOnJsConfirm(bool? value) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForOnJsConfirm, + [value], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future setSynchronousReturnValueForOnJsPrompt(bool? value) => + (super.noSuchMethod( + Invocation.method( + #setSynchronousReturnValueForOnJsPrompt, + [value], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override _i2.WebChromeClient copy() => (super.noSuchMethod( Invocation.method( #copy, diff --git a/packages/webview_flutter/webview_flutter_android/test/test_android_webview.g.dart b/packages/webview_flutter/webview_flutter_android/test/test_android_webview.g.dart index da34fcffa9c1..41d66e0320e7 100644 --- a/packages/webview_flutter/webview_flutter_android/test/test_android_webview.g.dart +++ b/packages/webview_flutter/webview_flutter_android/test/test_android_webview.g.dart @@ -1543,6 +1543,12 @@ abstract class TestWebChromeClientHostApi { void setSynchronousReturnValueForOnConsoleMessage(int instanceId, bool value); + void setSynchronousReturnValueForOnJsAlert(int instanceId, bool value); + + void setSynchronousReturnValueForOnJsConfirm(int instanceId, bool value); + + void setSynchronousReturnValueForOnJsPrompt(int instanceId, bool value); + static void setup(TestWebChromeClientHostApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -1622,6 +1628,87 @@ abstract class TestWebChromeClientHostApi { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsAlert', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsAlert 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.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsAlert was null, expected non-null int.'); + final bool? arg_value = (args[1] as bool?); + assert(arg_value != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsAlert was null, expected non-null bool.'); + api.setSynchronousReturnValueForOnJsAlert( + arg_instanceId!, arg_value!); + return []; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsConfirm', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsConfirm 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.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsConfirm was null, expected non-null int.'); + final bool? arg_value = (args[1] as bool?); + assert(arg_value != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsConfirm was null, expected non-null bool.'); + api.setSynchronousReturnValueForOnJsConfirm( + arg_instanceId!, arg_value!); + return []; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsPrompt', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsPrompt 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.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsPrompt was null, expected non-null int.'); + final bool? arg_value = (args[1] as bool?); + assert(arg_value != null, + 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsPrompt was null, expected non-null bool.'); + api.setSynchronousReturnValueForOnJsPrompt( + arg_instanceId!, arg_value!); + return []; + }); + } + } } }