Skip to content

[webview_flutter]Adds support to intercept JavaScript alert/confirm/prompt dialog. #5026

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/webview_flutter/webview_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 4.5.0

* Adds support to intercept JavaScript dialog.
Sees `WebViewController.setOnJavaScriptAlertDialog`,
`WebViewController.setOnJavaScriptConfirmDialog`,
`WebViewController.setOnJavaScriptPromptDialog`.

## 4.4.1

* Exposes `JavaScriptLogLevel` from platform interface.
Expand Down
116 changes: 116 additions & 0 deletions packages/webview_flutter/webview_flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,9 @@ enum MenuOptions {
transparentBackground,
setCookie,
logExample,
jsAlert,
jsConfirm,
jsPrompt,
}

class SampleMenu extends StatelessWidget {
Expand Down Expand Up @@ -302,6 +305,15 @@ class SampleMenu extends StatelessWidget {
case MenuOptions.logExample:
_onLogExample();
break;
case MenuOptions.jsAlert:
_onJavaScriptAlertExample(context);
break;
case MenuOptions.jsConfirm:
_onJavaScriptConfirmExample(context);
break;
case MenuOptions.jsPrompt:
_onJavaScriptPromptExample(context);
break;
}
},
itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[
Expand Down Expand Up @@ -362,6 +374,18 @@ class SampleMenu extends StatelessWidget {
value: MenuOptions.logExample,
child: Text('Log example'),
),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.jsAlert,
child: Text('JavaScript Alert example'),
),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.jsConfirm,
child: Text('JavaScript Confirm example'),
),
const PopupMenuItem<MenuOptions>(
value: MenuOptions.jsPrompt,
child: Text('JavaScript Prompt example'),
),
],
);
}
Expand Down Expand Up @@ -481,6 +505,98 @@ class SampleMenu extends StatelessWidget {
return webViewController.loadHtmlString(kTransparentBackgroundPage);
}

Future<void> _onJavaScriptAlertExample(BuildContext context) {
webViewController.setOnJavaScriptAlertDialog((String message) async {
await showDialog(
context: context,
barrierDismissible: false,
builder: (
BuildContext context,
) {
return AlertDialog(
content: Text(message),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('OK'))
],
);
});
});
return webViewController
.runJavaScript("alert('This is a JavaScript alert dialog');");
}

Future<void> _onJavaScriptConfirmExample(BuildContext context) {
webViewController.setOnJavaScriptConfirmDialog((String message) async {
final bool? result = await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
content: Text(message),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: const Text('OK'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: const Text('Cancel'),
)
],
);
});
debugPrint('result=$result');
return result ?? false;
});
return webViewController
.runJavaScript("confirm('This is a JavaScript confirm dialog');");
}

Future<void> _onJavaScriptPromptExample(BuildContext context) {
final TextEditingController textEditingController = TextEditingController();
webViewController.setOnJavaScriptPromptDialog(
(String message, String? defaultText) async {
textEditingController.text = defaultText ?? '';
final String? result = await showDialog<String>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text(message),
content: TextField(
controller: textEditingController,
),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop(textEditingController.text);
},
child: const Text('OK'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop('');
},
child: const Text('Cancel'),
)
],
);
});
debugPrint('result=$result');
return result ?? '';
});
return webViewController
.runJavaScript("prompt('This is a JavaScript prompt dialog');");
}

Widget _getCookieList(String cookies) {
if (cookies == '""') {
return Container();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,28 @@ class WebViewController {
return platform.setOnConsoleMessage(onConsoleMessage);
}

/// Sets a callback that notifies the host application that the web page
/// wants to display a JavaScript alert() dialog.
Future<void> setOnJavaScriptAlertDialog(
Future<void> Function(String message) onJavaScriptAlertDialog) async {
return platform.setOnJavaScriptAlertDialog(onJavaScriptAlertDialog);
}

/// Sets a callback that notifies the host application that the web page
/// wants to display a JavaScript confirm() dialog.
Future<void> setOnJavaScriptConfirmDialog(
Future<bool> Function(String message) onJavaScriptConfirmDialog) async {
return platform.setOnJavaScriptConfirmDialog(onJavaScriptConfirmDialog);
}

/// Sets a callback that notifies the host application that the web page
/// wants to display a JavaScript prompt() dialog.
Future<void> setOnJavaScriptPromptDialog(
Future<String> Function(String message, String? defaultText)
onJavaScriptPromptDialog) async {
return platform.setOnJavaScriptPromptDialog(onJavaScriptPromptDialog);
}

/// Gets the value used for the HTTP `User-Agent:` request header.
Future<String?> getUserAgent() {
return platform.getUserAgent();
Expand Down
2 changes: 1 addition & 1 deletion packages/webview_flutter/webview_flutter/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: webview_flutter
description: A Flutter plugin that provides a WebView widget on Android and iOS.
repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
version: 4.4.1
version: 4.5.0

environment:
sdk: ">=2.19.0 <4.0.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 3.13.0

* Adds support to intercept JavaScript dialog.
Sees `PlatformWebViewController.setOnJavaScriptAlertDialog`,
`PlatformWebViewController.setOnJavaScriptConfirmDialog`,
`PlatformWebViewController.setOnJavaScriptPromptDialog`.

## 3.12.0

* Adds support for `PlatformWebViewController.getUserAgent`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2620,6 +2620,15 @@ 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<Object> getCodec() {
return new StandardMessageCodec();
Expand Down Expand Up @@ -2709,6 +2718,81 @@ static void setup(
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger, "dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsAlert", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) 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<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger, "dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsConfirm", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) 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<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger, "dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnJsPrompt", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) 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<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
Expand Down Expand Up @@ -2862,6 +2946,57 @@ public void onShowFileChooser(
callback.reply(output);
});
}
public void onJsAlert(
@NonNull Long instanceIdArg,
@NonNull String messageArg,
@NonNull Reply<Void> callback) {
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onJsAlert",
getCodec());
channel.send(
new ArrayList<Object>(
Arrays.asList(instanceIdArg, messageArg)),
channelReply -> callback.reply(null));
}
public void onJsConfirm(
@NonNull Long instanceIdArg,
@NonNull String messageArg,
@NonNull Reply<Boolean> callback) {
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onJsConfirm",
getCodec());
channel.send(
new ArrayList<Object>(
Arrays.asList(instanceIdArg, messageArg)),
channelReply -> {
@SuppressWarnings("ConstantConditions")
Boolean output = (Boolean) channelReply;
callback.reply(output);
});
}
public void onJsPrompt(
@NonNull Long instanceIdArg,
@NonNull String messageArg,
@NonNull String defaultValueArg,
@NonNull Reply<String> callback) {
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger,
"dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onJsPrompt",
getCodec());
channel.send(
new ArrayList<Object>(
Arrays.asList(instanceIdArg, messageArg, defaultValueArg)),
channelReply -> {
@SuppressWarnings("ConstantConditions")
String output = (String) channelReply;
callback.reply(output);
});
}
/** Callback to Dart function `WebChromeClient.onPermissionRequest`. */
public void onPermissionRequest(
@NonNull Long instanceIdArg,
Expand Down
Loading