[webview_flutter_android] Adds Android implementation to override console log (#4702)
Adds the Android implementation for registering a JavaScript console callback. This will allow developers to receive JavaScript console messages in a Dart callback.
This PR contains the `webview_flutter_android` specific changes from PR #4541.
Related issue: flutter/flutter#32908
*If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*
diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
index 4608d1c..4fb48de 100644
--- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 3.11.0
+
+* Adds support to register a callback to receive JavaScript console messages. See `AndroidWebViewController.onConsoleMessage`.
+
## 3.10.1
* Bumps androidx.annotation:annotation from 1.5.0 to 1.7.0.
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java
index 567e201..d4cfc69 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
@@ -94,6 +94,62 @@
}
}
+ /**
+ * Indicates the type of message logged to the console.
+ *
+ * <p>See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel.
+ */
+ public enum ConsoleMessageLevel {
+ /**
+ * Indicates a message is logged for debugging.
+ *
+ * <p>See
+ * https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#DEBUG.
+ */
+ DEBUG(0),
+ /**
+ * Indicates a message is provided as an error.
+ *
+ * <p>See
+ * https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#ERROR.
+ */
+ ERROR(1),
+ /**
+ * Indicates a message is provided as a basic log message.
+ *
+ * <p>See
+ * https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#LOG.
+ */
+ LOG(2),
+ /**
+ * Indicates a message is provided as a tip.
+ *
+ * <p>See
+ * https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#TIP.
+ */
+ TIP(3),
+ /**
+ * Indicates a message is provided as a warning.
+ *
+ * <p>See
+ * https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#WARNING.
+ */
+ WARNING(4),
+ /**
+ * Indicates a message with an unknown level.
+ *
+ * <p>This does not represent an actual value provided by the platform and only indicates a
+ * value was provided that isn't currently supported.
+ */
+ UNKNOWN(5);
+
+ final int index;
+
+ private ConsoleMessageLevel(final int index) {
+ this.index = index;
+ }
+ }
+
/** Generated class from Pigeon that represents data sent in messages. */
public static final class WebResourceRequestData {
private @NonNull String url;
@@ -409,6 +465,136 @@
}
}
+ /**
+ * Represents a JavaScript console message from WebCore.
+ *
+ * <p>See https://developer.android.com/reference/android/webkit/ConsoleMessage
+ *
+ * <p>Generated class from Pigeon that represents data sent in messages.
+ */
+ public static final class ConsoleMessage {
+ private @NonNull Long lineNumber;
+
+ public @NonNull Long getLineNumber() {
+ return lineNumber;
+ }
+
+ public void setLineNumber(@NonNull Long setterArg) {
+ if (setterArg == null) {
+ throw new IllegalStateException("Nonnull field \"lineNumber\" is null.");
+ }
+ this.lineNumber = setterArg;
+ }
+
+ private @NonNull String message;
+
+ public @NonNull String getMessage() {
+ return message;
+ }
+
+ public void setMessage(@NonNull String setterArg) {
+ if (setterArg == null) {
+ throw new IllegalStateException("Nonnull field \"message\" is null.");
+ }
+ this.message = setterArg;
+ }
+
+ private @NonNull ConsoleMessageLevel level;
+
+ public @NonNull ConsoleMessageLevel getLevel() {
+ return level;
+ }
+
+ public void setLevel(@NonNull ConsoleMessageLevel setterArg) {
+ if (setterArg == null) {
+ throw new IllegalStateException("Nonnull field \"level\" is null.");
+ }
+ this.level = setterArg;
+ }
+
+ private @NonNull String sourceId;
+
+ public @NonNull String getSourceId() {
+ return sourceId;
+ }
+
+ public void setSourceId(@NonNull String setterArg) {
+ if (setterArg == null) {
+ throw new IllegalStateException("Nonnull field \"sourceId\" is null.");
+ }
+ this.sourceId = setterArg;
+ }
+
+ /** Constructor is non-public to enforce null safety; use Builder. */
+ ConsoleMessage() {}
+
+ public static final class Builder {
+
+ private @Nullable Long lineNumber;
+
+ public @NonNull Builder setLineNumber(@NonNull Long setterArg) {
+ this.lineNumber = setterArg;
+ return this;
+ }
+
+ private @Nullable String message;
+
+ public @NonNull Builder setMessage(@NonNull String setterArg) {
+ this.message = setterArg;
+ return this;
+ }
+
+ private @Nullable ConsoleMessageLevel level;
+
+ public @NonNull Builder setLevel(@NonNull ConsoleMessageLevel setterArg) {
+ this.level = setterArg;
+ return this;
+ }
+
+ private @Nullable String sourceId;
+
+ public @NonNull Builder setSourceId(@NonNull String setterArg) {
+ this.sourceId = setterArg;
+ return this;
+ }
+
+ public @NonNull ConsoleMessage build() {
+ ConsoleMessage pigeonReturn = new ConsoleMessage();
+ pigeonReturn.setLineNumber(lineNumber);
+ pigeonReturn.setMessage(message);
+ pigeonReturn.setLevel(level);
+ pigeonReturn.setSourceId(sourceId);
+ return pigeonReturn;
+ }
+ }
+
+ @NonNull
+ ArrayList<Object> toList() {
+ ArrayList<Object> toListResult = new ArrayList<Object>(4);
+ toListResult.add(lineNumber);
+ toListResult.add(message);
+ toListResult.add(level == null ? null : level.index);
+ toListResult.add(sourceId);
+ return toListResult;
+ }
+
+ static @NonNull ConsoleMessage fromList(@NonNull ArrayList<Object> list) {
+ ConsoleMessage pigeonResult = new ConsoleMessage();
+ Object lineNumber = list.get(0);
+ pigeonResult.setLineNumber(
+ (lineNumber == null)
+ ? null
+ : ((lineNumber instanceof Integer) ? (Integer) lineNumber : (Long) lineNumber));
+ Object message = list.get(1);
+ pigeonResult.setMessage((String) message);
+ Object level = list.get(2);
+ pigeonResult.setLevel(ConsoleMessageLevel.values()[(int) level]);
+ Object sourceId = list.get(3);
+ pigeonResult.setSourceId((String) sourceId);
+ return pigeonResult;
+ }
+ }
+
public interface Result<T> {
@SuppressWarnings("UnknownNullness")
void success(T result);
@@ -2401,6 +2587,9 @@
void setSynchronousReturnValueForOnShowFileChooser(
@NonNull Long instanceId, @NonNull Boolean value);
+ void setSynchronousReturnValueForOnConsoleMessage(
+ @NonNull Long instanceId, @NonNull Boolean value);
+
/** The codec used by WebChromeClientHostApi. */
static @NonNull MessageCodec<Object> getCodec() {
return new StandardMessageCodec();
@@ -2463,6 +2652,33 @@
channel.setMessageHandler(null);
}
}
+ {
+ BasicMessageChannel<Object> channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnConsoleMessage",
+ 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.setSynchronousReturnValueForOnConsoleMessage(
+ (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. */
@@ -2536,6 +2752,34 @@
}
}
}
+
+ private static class WebChromeClientFlutterApiCodec extends StandardMessageCodec {
+ public static final WebChromeClientFlutterApiCodec INSTANCE =
+ new WebChromeClientFlutterApiCodec();
+
+ private WebChromeClientFlutterApiCodec() {}
+
+ @Override
+ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) {
+ switch (type) {
+ case (byte) 128:
+ return ConsoleMessage.fromList((ArrayList<Object>) readValue(buffer));
+ default:
+ return super.readValueOfType(type, buffer);
+ }
+ }
+
+ @Override
+ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) {
+ if (value instanceof ConsoleMessage) {
+ stream.write(128);
+ writeValue(stream, ((ConsoleMessage) value).toList());
+ } else {
+ super.writeValue(stream, value);
+ }
+ }
+ }
+
/** Generated class from Pigeon that represents Flutter messages that can be called from Java. */
public static class WebChromeClientFlutterApi {
private final @NonNull BinaryMessenger binaryMessenger;
@@ -2551,7 +2795,7 @@
}
/** The codec used by WebChromeClientFlutterApi. */
static @NonNull MessageCodec<Object> getCodec() {
- return new StandardMessageCodec();
+ return WebChromeClientFlutterApiCodec.INSTANCE;
}
public void onProgressChanged(
@@ -2656,6 +2900,20 @@
new ArrayList<Object>(Collections.singletonList(identifierArg)),
channelReply -> callback.reply(null));
}
+ /** Callback to Dart function `WebChromeClient.onConsoleMessage`. */
+ public void onConsoleMessage(
+ @NonNull Long instanceIdArg,
+ @NonNull ConsoleMessage messageArg,
+ @NonNull Reply<Void> callback) {
+ BasicMessageChannel<Object> channel =
+ new BasicMessageChannel<>(
+ binaryMessenger,
+ "dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onConsoleMessage",
+ getCodec());
+ channel.send(
+ new ArrayList<Object>(Arrays.asList(instanceIdArg, messageArg)),
+ channelReply -> callback.reply(null));
+ }
}
/** 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 f5097b6..b383dfd 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
@@ -6,6 +6,7 @@
import android.os.Build;
import android.view.View;
+import android.webkit.ConsoleMessage;
import android.webkit.GeolocationPermissions;
import android.webkit.PermissionRequest;
import android.webkit.WebChromeClient;
@@ -27,6 +28,24 @@
private final InstanceManager instanceManager;
private final WebViewFlutterApiImpl webViewFlutterApi;
+ private static GeneratedAndroidWebView.ConsoleMessageLevel toConsoleMessageLevel(
+ ConsoleMessage.MessageLevel level) {
+ switch (level) {
+ case TIP:
+ return GeneratedAndroidWebView.ConsoleMessageLevel.TIP;
+ case LOG:
+ return GeneratedAndroidWebView.ConsoleMessageLevel.LOG;
+ case WARNING:
+ return GeneratedAndroidWebView.ConsoleMessageLevel.WARNING;
+ case ERROR:
+ return GeneratedAndroidWebView.ConsoleMessageLevel.ERROR;
+ case DEBUG:
+ return GeneratedAndroidWebView.ConsoleMessageLevel.DEBUG;
+ }
+
+ return GeneratedAndroidWebView.ConsoleMessageLevel.UNKNOWN;
+ }
+
/**
* Creates a Flutter api that sends messages to Dart.
*
@@ -149,6 +168,25 @@
callback);
}
+ /**
+ * Sends a message to Dart to call `WebChromeClient.onConsoleMessage` on the Dart object
+ * representing `instance`.
+ */
+ public void onConsoleMessage(
+ @NonNull WebChromeClient instance,
+ @NonNull ConsoleMessage message,
+ @NonNull Reply<Void> callback) {
+ super.onConsoleMessage(
+ Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(instance)),
+ new GeneratedAndroidWebView.ConsoleMessage.Builder()
+ .setLineNumber((long) message.lineNumber())
+ .setMessage(message.message())
+ .setLevel(toConsoleMessageLevel(message.messageLevel()))
+ .setSourceId(message.sourceId())
+ .build(),
+ 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 635c6c3..cb382d5 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
@@ -9,6 +9,7 @@
import android.os.Build;
import android.os.Message;
import android.view.View;
+import android.webkit.ConsoleMessage;
import android.webkit.GeolocationPermissions;
import android.webkit.PermissionRequest;
import android.webkit.ValueCallback;
@@ -40,6 +41,7 @@
public static class WebChromeClientImpl extends SecureWebChromeClient {
private final WebChromeClientFlutterApiImpl flutterApi;
private boolean returnValueForOnShowFileChooser = false;
+ private boolean returnValueForOnConsoleMessage = false;
/**
* Creates a {@link WebChromeClient} that passes arguments of callbacks methods to Dart.
@@ -107,10 +109,21 @@
flutterApi.onPermissionRequest(this, request, reply -> {});
}
+ @Override
+ public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
+ flutterApi.onConsoleMessage(this, consoleMessage, reply -> {});
+ return returnValueForOnConsoleMessage;
+ }
+
/** Sets return value for {@link #onShowFileChooser}. */
public void setReturnValueForOnShowFileChooser(boolean value) {
returnValueForOnShowFileChooser = value;
}
+
+ /** Sets return value for {@link #onConsoleMessage}. */
+ public void setReturnValueForOnConsoleMessage(boolean value) {
+ returnValueForOnConsoleMessage = value;
+ }
}
/**
@@ -246,4 +259,12 @@
Objects.requireNonNull(instanceManager.getInstance(instanceId));
webChromeClient.setReturnValueForOnShowFileChooser(value);
}
+
+ @Override
+ public void setSynchronousReturnValueForOnConsoleMessage(
+ @NonNull Long instanceId, @NonNull Boolean value) {
+ final WebChromeClientImpl webChromeClient =
+ Objects.requireNonNull(instanceManager.getInstance(instanceId));
+ webChromeClient.setReturnValueForOnConsoleMessage(value);
+ }
}
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java
index 4e09b2e..ef49dde 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java
@@ -16,6 +16,7 @@
import android.net.Uri;
import android.os.Message;
import android.view.View;
+import android.webkit.ConsoleMessage;
import android.webkit.GeolocationPermissions;
import android.webkit.PermissionRequest;
import android.webkit.WebChromeClient;
@@ -162,4 +163,17 @@
webChromeClient.onGeolocationPermissionsHidePrompt();
verify(mockFlutterApi).onGeolocationPermissionsHidePrompt(eq(webChromeClient), any());
}
+
+ @Test
+ public void onConsoleMessage() {
+ webChromeClient.onConsoleMessage(
+ new ConsoleMessage("message", "sourceId", 23, ConsoleMessage.MessageLevel.ERROR));
+ verify(mockFlutterApi).onConsoleMessage(eq(webChromeClient), any(), any());
+ }
+
+ @Test
+ public void setReturnValueForOnConsoleMessage() {
+ webChromeClient.setReturnValueForOnConsoleMessage(true);
+ assertTrue(webChromeClient.onConsoleMessage(null));
+ }
}
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 0e805dd..017e696 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
@@ -1310,6 +1310,47 @@
);
},
);
+
+ group('Logging', () {
+ testWidgets('can receive console log messages',
+ (WidgetTester tester) async {
+ const String testPage = '''
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <title>WebResourceError test</title>
+ </head>
+ <body onload="console.debug('Debug message')">
+ <p>Test page</p>
+ </body>
+ </html>
+ ''';
+
+ final Completer<String> debugMessageReceived = Completer<String>();
+ final PlatformWebViewController controller = PlatformWebViewController(
+ const PlatformWebViewControllerCreationParams(),
+ );
+ unawaited(controller.setJavaScriptMode(JavaScriptMode.unrestricted));
+
+ await controller.setOnConsoleMessage((JavaScriptConsoleMessage message) {
+ debugMessageReceived
+ .complete('${message.level.name}:${message.message}');
+ });
+
+ await controller.loadHtmlString(testPage);
+
+ await tester.pumpWidget(Builder(
+ builder: (BuildContext context) {
+ return PlatformWebViewWidget(
+ PlatformWebViewWidgetCreationParams(controller: controller),
+ ).build(context);
+ },
+ ));
+
+ await expectLater(
+ debugMessageReceived.future, completion('debug:Debug message'));
+ });
+ });
}
/// Returns the value used for the HTTP User-Agent: request header in subsequent HTTP requests.
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 781a98f..92a8fe2 100644
--- a/packages/webview_flutter/webview_flutter_android/example/lib/main.dart
+++ b/packages/webview_flutter/webview_flutter_android/example/lib/main.dart
@@ -73,6 +73,40 @@
</html>
''';
+const String kLogExamplePage = '''
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<title>Load file or HTML string example</title>
+</head>
+<body onload="console.log('Logging that the page is loading.')">
+
+<h1>Local demo page</h1>
+<p>
+ This page is used to test the forwarding of console logs to Dart.
+</p>
+
+<style>
+ .btn-group button {
+ padding: 24px; 24px;
+ display: block;
+ width: 25%;
+ margin: 5px 0px 0px 0px;
+ }
+</style>
+
+<div class="btn-group">
+ <button onclick="console.error('This is an error message.')">Error</button>
+ <button onclick="console.warn('This is a warning message.')">Warning</button>
+ <button onclick="console.info('This is a info message.')">Info</button>
+ <button onclick="console.debug('This is a debug message.')">Debug</button>
+ <button onclick="console.log('This is a log message.')">Log</button>
+</div>
+
+</body>
+</html>
+''';
+
class WebViewExample extends StatefulWidget {
const WebViewExample({super.key, this.cookieManager});
@@ -202,6 +236,7 @@
transparentBackground,
setCookie,
videoExample,
+ logExample,
}
class SampleMenu extends StatelessWidget {
@@ -265,6 +300,9 @@
case MenuOptions.videoExample:
_onVideoExample(context);
break;
+ case MenuOptions.logExample:
+ _onLogExample();
+ break;
}
},
itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[
@@ -322,6 +360,10 @@
child: Text('Transparent background example'),
),
const PopupMenuItem<MenuOptions>(
+ value: MenuOptions.logExample,
+ child: Text('Log example'),
+ ),
+ const PopupMenuItem<MenuOptions>(
value: MenuOptions.videoExample,
child: Text('Video example'),
),
@@ -497,6 +539,16 @@
return indexFile.path;
}
+
+ Future<void> _onLogExample() {
+ webViewController
+ .setOnConsoleMessage((JavaScriptConsoleMessage consoleMessage) {
+ debugPrint(
+ '== JS == ${consoleMessage.level.name}: ${consoleMessage.message}');
+ });
+
+ return webViewController.loadHtmlString(kLogExamplePage);
+ }
}
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 286b847..acba200 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 @@
# 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.4.0
+ webview_flutter_platform_interface: ^2.6.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 508a31b..f3d00eb 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
@@ -44,6 +44,9 @@
onGeolocationPermissionsShowPrompt,
void Function(android_webview.WebChromeClient instance)?
onGeolocationPermissionsHidePrompt,
+ void Function(android_webview.WebChromeClient instance,
+ android_webview.ConsoleMessage message)?
+ onConsoleMessage,
void Function(
android_webview.WebChromeClient instance,
android_webview.View view,
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 5ed2fc0..d63f2c7 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
@@ -12,7 +12,8 @@
import 'android_webview_api_impls.dart';
import 'instance_manager.dart';
-export 'android_webview_api_impls.dart' show FileChooserMode;
+export 'android_webview_api_impls.dart'
+ show ConsoleMessage, ConsoleMessageLevel, FileChooserMode;
/// Root of the Java class hierarchy.
///
@@ -1047,6 +1048,7 @@
this.onGeolocationPermissionsHidePrompt,
this.onShowCustomView,
this.onHideCustomView,
+ this.onConsoleMessage,
@visibleForTesting super.binaryMessenger,
@visibleForTesting super.instanceManager,
}) : super.detached() {
@@ -1068,6 +1070,7 @@
this.onGeolocationPermissionsHidePrompt,
this.onShowCustomView,
this.onHideCustomView,
+ this.onConsoleMessage,
super.binaryMessenger,
super.instanceManager,
}) : super.detached();
@@ -1121,6 +1124,10 @@
/// mode.
final HideCustomViewCallback? onHideCustomView;
+ /// Report a JavaScript console message to the host application.
+ final void Function(WebChromeClient instance, ConsoleMessage message)?
+ onConsoleMessage;
+
/// Sets the required synchronous return value for the Java method,
/// `WebChromeClient.onShowFileChooser(...)`.
///
@@ -1150,6 +1157,33 @@
);
}
+ /// Sets the required synchronous return value for the Java method,
+ /// `WebChromeClient.onShowFileChooser(...)`.
+ ///
+ /// The Java method, `WebChromeClient.onConsoleMessage(...)`, 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 [onConsoleMessage] to be nonnull.
+ ///
+ /// Defaults to false.
+ Future<void> setSynchronousReturnValueForOnConsoleMessage(
+ bool value,
+ ) {
+ if (value && onConsoleMessage == null) {
+ throw StateError(
+ 'Setting this to true requires `onConsoleMessage` to be nonnull.',
+ );
+ }
+ return api.setSynchronousReturnValueForOnConsoleMessageFromInstance(
+ this,
+ value,
+ );
+ }
+
@override
WebChromeClient copy() {
return WebChromeClient.detached(
@@ -1160,6 +1194,7 @@
onGeolocationPermissionsHidePrompt: onGeolocationPermissionsHidePrompt,
onShowCustomView: onShowCustomView,
onHideCustomView: onHideCustomView,
+ onConsoleMessage: onConsoleMessage,
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 99e1e7f..87f75f1 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
@@ -32,6 +32,42 @@
save,
}
+/// Indicates the type of message logged to the console.
+///
+/// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel.
+enum ConsoleMessageLevel {
+ /// Indicates a message is logged for debugging.
+ ///
+ /// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#DEBUG.
+ debug,
+
+ /// Indicates a message is provided as an error.
+ ///
+ /// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#ERROR.
+ error,
+
+ /// Indicates a message is provided as a basic log message.
+ ///
+ /// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#LOG.
+ log,
+
+ /// Indicates a message is provided as a tip.
+ ///
+ /// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#TIP.
+ tip,
+
+ /// Indicates a message is provided as a warning.
+ ///
+ /// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#WARNING.
+ warning,
+
+ /// Indicates a message with an unknown level.
+ ///
+ /// This does not represent an actual value provided by the platform and only
+ /// indicates a value was provided that isn't currently supported.
+ unknown,
+}
+
class WebResourceRequestData {
WebResourceRequestData({
required this.url,
@@ -131,6 +167,45 @@
}
}
+/// Represents a JavaScript console message from WebCore.
+///
+/// See https://developer.android.com/reference/android/webkit/ConsoleMessage
+class ConsoleMessage {
+ ConsoleMessage({
+ required this.lineNumber,
+ required this.message,
+ required this.level,
+ required this.sourceId,
+ });
+
+ int lineNumber;
+
+ String message;
+
+ ConsoleMessageLevel level;
+
+ String sourceId;
+
+ Object encode() {
+ return <Object?>[
+ lineNumber,
+ message,
+ level.index,
+ sourceId,
+ ];
+ }
+
+ static ConsoleMessage decode(Object result) {
+ result as List<Object?>;
+ return ConsoleMessage(
+ lineNumber: result[0]! as int,
+ message: result[1]! as String,
+ level: ConsoleMessageLevel.values[result[2]! as int],
+ sourceId: result[3]! as String,
+ );
+ }
+}
+
/// Host API for managing the native `InstanceManager`.
class InstanceManagerHostApi {
/// Constructor for [InstanceManagerHostApi]. The [binaryMessenger] named argument is
@@ -1943,6 +2018,30 @@
return;
}
}
+
+ Future<void> setSynchronousReturnValueForOnConsoleMessage(
+ int arg_instanceId, bool arg_value) async {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnConsoleMessage',
+ codec,
+ binaryMessenger: _binaryMessenger);
+ final List<Object?>? replyList = await channel
+ .send(<Object?>[arg_instanceId, arg_value]) as List<Object?>?;
+ 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 {
@@ -2012,8 +2111,31 @@
}
}
+class _WebChromeClientFlutterApiCodec extends StandardMessageCodec {
+ const _WebChromeClientFlutterApiCodec();
+ @override
+ void writeValue(WriteBuffer buffer, Object? value) {
+ if (value is ConsoleMessage) {
+ buffer.putUint8(128);
+ writeValue(buffer, value.encode());
+ } else {
+ super.writeValue(buffer, value);
+ }
+ }
+
+ @override
+ Object? readValueOfType(int type, ReadBuffer buffer) {
+ switch (type) {
+ case 128:
+ return ConsoleMessage.decode(readValue(buffer)!);
+ default:
+ return super.readValueOfType(type, buffer);
+ }
+ }
+}
+
abstract class WebChromeClientFlutterApi {
- static const MessageCodec<Object?> codec = StandardMessageCodec();
+ static const MessageCodec<Object?> codec = _WebChromeClientFlutterApiCodec();
void onProgressChanged(int instanceId, int webViewInstanceId, int progress);
@@ -2037,6 +2159,9 @@
/// Callback to Dart function `WebChromeClient.onGeolocationPermissionsHidePrompt`.
void onGeolocationPermissionsHidePrompt(int identifier);
+ /// Callback to Dart function `WebChromeClient.onConsoleMessage`.
+ void onConsoleMessage(int instanceId, ConsoleMessage message);
+
static void setup(WebChromeClientFlutterApi? api,
{BinaryMessenger? binaryMessenger}) {
{
@@ -2210,6 +2335,29 @@
});
}
}
+ {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onConsoleMessage',
+ 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.onConsoleMessage was null.');
+ final List<Object?> args = (message as List<Object?>?)!;
+ final int? arg_instanceId = (args[0] as int?);
+ assert(arg_instanceId != null,
+ 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onConsoleMessage was null, expected non-null int.');
+ final ConsoleMessage? arg_message = (args[1] as ConsoleMessage?);
+ assert(arg_message != null,
+ 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onConsoleMessage was null, expected non-null ConsoleMessage.');
+ api.onConsoleMessage(arg_instanceId!, arg_message!);
+ return;
+ });
+ }
+ }
}
}
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 2c773fd..c9191e0 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
@@ -11,7 +11,8 @@
import 'android_webview.g.dart';
import 'instance_manager.dart';
-export 'android_webview.g.dart' show FileChooserMode;
+export 'android_webview.g.dart'
+ show ConsoleMessage, ConsoleMessageLevel, FileChooserMode;
/// Converts [WebResourceRequestData] to [WebResourceRequest]
WebResourceRequest _toWebResourceRequest(WebResourceRequestData data) {
@@ -892,6 +893,17 @@
value,
);
}
+
+ /// Helper method to convert instances ids to objects.
+ Future<void> setSynchronousReturnValueForOnConsoleMessageFromInstance(
+ WebChromeClient instance,
+ bool value,
+ ) {
+ return setSynchronousReturnValueForOnConsoleMessage(
+ instanceManager.getIdentifier(instance)!,
+ value,
+ );
+ }
}
/// Flutter api implementation for [DownloadListener].
@@ -1017,6 +1029,13 @@
);
}
}
+
+ @override
+ void onConsoleMessage(int instanceId, ConsoleMessage message) {
+ final WebChromeClient instance =
+ instanceManager.getInstanceWithWeakReference(instanceId)!;
+ instance.onConsoleMessage?.call(instance, message);
+ }
}
/// 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 bc1da74..d0559b4 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
@@ -13,6 +13,7 @@
import 'android_proxy.dart';
import 'android_webview.dart' as android_webview;
+import 'android_webview_api_impls.dart';
import 'instance_manager.dart';
import 'platform_views_service_proxy.dart';
import 'weak_reference_utils.dart';
@@ -188,6 +189,42 @@
};
},
),
+ onConsoleMessage: withWeakReferenceTo(
+ this,
+ (WeakReference<AndroidWebViewController> weakReference) {
+ return (android_webview.WebChromeClient webChromeClient,
+ android_webview.ConsoleMessage consoleMessage) async {
+ final void Function(JavaScriptConsoleMessage)? callback =
+ weakReference.target?._onConsoleLogCallback;
+ if (callback != null) {
+ JavaScriptLogLevel logLevel;
+ switch (consoleMessage.level) {
+ // Android maps `console.debug` to `MessageLevel.TIP`, it seems
+ // `MessageLevel.DEBUG` if not being used.
+ case ConsoleMessageLevel.debug:
+ case ConsoleMessageLevel.tip:
+ logLevel = JavaScriptLogLevel.debug;
+ break;
+ case ConsoleMessageLevel.error:
+ logLevel = JavaScriptLogLevel.error;
+ break;
+ case ConsoleMessageLevel.warning:
+ logLevel = JavaScriptLogLevel.warning;
+ break;
+ case ConsoleMessageLevel.unknown:
+ case ConsoleMessageLevel.log:
+ logLevel = JavaScriptLogLevel.log;
+ break;
+ }
+
+ callback(JavaScriptConsoleMessage(
+ level: logLevel,
+ message: consoleMessage.message,
+ ));
+ }
+ };
+ },
+ ),
onPermissionRequest: withWeakReferenceTo(
this,
(WeakReference<AndroidWebViewController> weakReference) {
@@ -255,6 +292,8 @@
void Function(PlatformWebViewPermissionRequest)? _onPermissionRequestCallback;
+ void Function(JavaScriptConsoleMessage consoleMessage)? _onConsoleLogCallback;
+
/// Whether to enable the platform's webview content debugging tools.
///
/// Defaults to false.
@@ -566,6 +605,18 @@
_onShowCustomWidgetCallback = onShowCustomWidget;
_onHideCustomWidgetCallback = onHideCustomWidget;
}
+
+ /// Sets a callback that notifies the host application of any log messages
+ /// written to the JavaScript console.
+ @override
+ Future<void> setOnConsoleMessage(
+ void Function(JavaScriptConsoleMessage consoleMessage)
+ onConsoleMessage) async {
+ _onConsoleLogCallback = onConsoleMessage;
+
+ return _webChromeClient.setSynchronousReturnValueForOnConsoleMessage(
+ _onConsoleLogCallback != null);
+ }
}
/// 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 c19a2b2..3676053 100644
--- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
+++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
@@ -57,6 +57,42 @@
save,
}
+/// Indicates the type of message logged to the console.
+///
+/// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel.
+enum ConsoleMessageLevel {
+ /// Indicates a message is logged for debugging.
+ ///
+ /// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#DEBUG.
+ debug,
+
+ /// Indicates a message is provided as an error.
+ ///
+ /// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#ERROR.
+ error,
+
+ /// Indicates a message is provided as a basic log message.
+ ///
+ /// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#LOG.
+ log,
+
+ /// Indicates a message is provided as a tip.
+ ///
+ /// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#TIP.
+ tip,
+
+ /// Indicates a message is provided as a warning.
+ ///
+ /// See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#WARNING.
+ warning,
+
+ /// Indicates a message with an unknown level.
+ ///
+ /// This does not represent an actual value provided by the platform and only
+ /// indicates a value was provided that isn't currently supported.
+ unknown,
+}
+
class WebResourceRequestData {
WebResourceRequestData(
this.url,
@@ -89,6 +125,16 @@
int y;
}
+/// Represents a JavaScript console message from WebCore.
+///
+/// See https://developer.android.com/reference/android/webkit/ConsoleMessage
+class ConsoleMessage {
+ late int lineNumber;
+ late String message;
+ late ConsoleMessageLevel level;
+ late String sourceId;
+}
+
/// Handles methods calls to the native Java Object class.
///
/// Also handles calls to remove the reference to an instance with `dispose`.
@@ -337,6 +383,11 @@
int instanceId,
bool value,
);
+
+ void setSynchronousReturnValueForOnConsoleMessage(
+ int instanceId,
+ bool value,
+ );
}
@HostApi(dartHostTestHandler: 'TestAssetManagerHostApi')
@@ -379,6 +430,9 @@
/// Callback to Dart function `WebChromeClient.onGeolocationPermissionsHidePrompt`.
void onGeolocationPermissionsHidePrompt(int identifier);
+
+ /// Callback to Dart function `WebChromeClient.onConsoleMessage`.
+ void onConsoleMessage(int instanceId, ConsoleMessage message);
}
@HostApi(dartHostTestHandler: 'TestWebStorageHostApi')
diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
index 21acae2..8242abd 100644
--- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
@@ -2,7 +2,7 @@
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.10.1
+version: 3.11.0
environment:
sdk: ">=2.19.0 <4.0.0"
@@ -20,7 +20,7 @@
dependencies:
flutter:
sdk: flutter
- webview_flutter_platform_interface: ^2.4.0
+ webview_flutter_platform_interface: ^2.6.0
dev_dependencies:
build_runner: ^2.1.4
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_navigation_delegate_test.dart
index 0693ef5..269123e 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
@@ -520,6 +520,7 @@
super.onShowCustomView,
super.onHideCustomView,
super.onPermissionRequest,
+ super.onConsoleMessage,
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 c350be6..3f3bf73 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
@@ -73,6 +73,9 @@
android_webview.CustomViewCallback callback)?
onShowCustomView,
void Function(android_webview.WebChromeClient instance)? onHideCustomView,
+ void Function(android_webview.WebChromeClient instance,
+ android_webview.ConsoleMessage message)?
+ onConsoleMessage,
})? createWebChromeClient,
android_webview.WebView? mockWebView,
android_webview.WebViewClient? mockWebViewClient,
@@ -111,6 +114,9 @@
onShowCustomView,
void Function(android_webview.WebChromeClient instance)?
onHideCustomView,
+ void Function(android_webview.WebChromeClient instance,
+ android_webview.ConsoleMessage message)?
+ onConsoleMessage,
}) =>
MockWebChromeClient(),
createAndroidWebView: () => nonNullMockWebView,
@@ -606,6 +612,7 @@
dynamic onPermissionRequest,
dynamic onShowCustomView,
dynamic onHideCustomView,
+ dynamic onConsoleMessage,
}) {
onShowFileChooserCallback = onShowFileChooser!;
return mockWebChromeClient;
@@ -676,6 +683,7 @@
dynamic onPermissionRequest,
dynamic onShowCustomView,
dynamic onHideCustomView,
+ dynamic onConsoleMessage,
}) {
onGeoPermissionHandle = onGeolocationPermissionsShowPrompt!;
onGeoPermissionHidePromptHandle = onGeolocationPermissionsHidePrompt!;
@@ -750,6 +758,7 @@
onShowCustomView,
void Function(android_webview.WebChromeClient instance)?
onHideCustomView,
+ dynamic onConsoleMessage,
}) {
onShowCustomViewHandle = onShowCustomView!;
onHideCustomViewHandle = onHideCustomView!;
@@ -802,6 +811,7 @@
)? onPermissionRequest,
dynamic onShowCustomView,
dynamic onHideCustomView,
+ dynamic onConsoleMessage,
}) {
onPermissionRequestCallback = onPermissionRequest!;
return mockWebChromeClient;
@@ -856,6 +866,7 @@
)? onPermissionRequest,
dynamic onShowCustomView,
dynamic onHideCustomView,
+ dynamic onConsoleMessage,
}) {
onPermissionRequestCallback = onPermissionRequest!;
return mockWebChromeClient;
@@ -881,6 +892,104 @@
expect(callbackCalled, isFalse);
});
+ test('setOnConsoleLogCallback', () async {
+ late final void Function(
+ android_webview.WebChromeClient instance,
+ android_webview.ConsoleMessage message,
+ ) onConsoleMessageCallback;
+
+ final MockWebChromeClient mockWebChromeClient = MockWebChromeClient();
+ final AndroidWebViewController controller = createControllerWithMocks(
+ createWebChromeClient: ({
+ dynamic onProgressChanged,
+ dynamic onShowFileChooser,
+ dynamic onGeolocationPermissionsShowPrompt,
+ dynamic onGeolocationPermissionsHidePrompt,
+ dynamic onPermissionRequest,
+ dynamic onShowCustomView,
+ dynamic onHideCustomView,
+ void Function(
+ android_webview.WebChromeClient,
+ android_webview.ConsoleMessage,
+ )? onConsoleMessage,
+ }) {
+ onConsoleMessageCallback = onConsoleMessage!;
+ return mockWebChromeClient;
+ },
+ );
+
+ final Map<String, JavaScriptLogLevel> logs =
+ <String, JavaScriptLogLevel>{};
+ await controller.setOnConsoleMessage(
+ (JavaScriptConsoleMessage message) async {
+ logs[message.message] = message.level;
+ },
+ );
+
+ onConsoleMessageCallback(
+ mockWebChromeClient,
+ ConsoleMessage(
+ lineNumber: 42,
+ message: 'Debug message',
+ level: ConsoleMessageLevel.debug,
+ sourceId: 'source',
+ ),
+ );
+ onConsoleMessageCallback(
+ mockWebChromeClient,
+ ConsoleMessage(
+ lineNumber: 42,
+ message: 'Error message',
+ level: ConsoleMessageLevel.error,
+ sourceId: 'source',
+ ),
+ );
+ onConsoleMessageCallback(
+ mockWebChromeClient,
+ ConsoleMessage(
+ lineNumber: 42,
+ message: 'Log message',
+ level: ConsoleMessageLevel.log,
+ sourceId: 'source',
+ ),
+ );
+ onConsoleMessageCallback(
+ mockWebChromeClient,
+ ConsoleMessage(
+ lineNumber: 42,
+ message: 'Tip message',
+ level: ConsoleMessageLevel.tip,
+ sourceId: 'source',
+ ),
+ );
+ onConsoleMessageCallback(
+ mockWebChromeClient,
+ ConsoleMessage(
+ lineNumber: 42,
+ message: 'Warning message',
+ level: ConsoleMessageLevel.warning,
+ sourceId: 'source',
+ ),
+ );
+ onConsoleMessageCallback(
+ mockWebChromeClient,
+ ConsoleMessage(
+ lineNumber: 42,
+ message: 'Unknown message',
+ level: ConsoleMessageLevel.unknown,
+ sourceId: 'source',
+ ),
+ );
+
+ expect(logs.length, 6);
+ expect(logs['Debug message'], JavaScriptLogLevel.debug);
+ expect(logs['Error message'], JavaScriptLogLevel.error);
+ expect(logs['Log message'], JavaScriptLogLevel.log);
+ expect(logs['Tip message'], JavaScriptLogLevel.debug);
+ expect(logs['Warning message'], JavaScriptLogLevel.warning);
+ expect(logs['Unknown message'], JavaScriptLogLevel.log);
+ });
+
test('runJavaScript', () async {
final MockWebView mockWebView = MockWebView();
final AndroidWebViewController controller = createControllerWithMocks(
@@ -1334,6 +1443,7 @@
android_webview.CustomViewCallback callback)?
onShowCustomView,
dynamic onHideCustomView,
+ dynamic onConsoleMessage,
}) {
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 b6d6e2c..98d7879 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
@@ -739,8 +739,8 @@
) as _i9.Future<void>);
@override
_i9.Future<void> setCustomWidgetCallbacks({
- _i8.OnShowCustomWidgetCallback? onShowCustomWidget,
- _i8.OnHideCustomWidgetCallback? onHideCustomWidget,
+ required _i8.OnShowCustomWidgetCallback? onShowCustomWidget,
+ required _i8.OnHideCustomWidgetCallback? onHideCustomWidget,
}) =>
(super.noSuchMethod(
Invocation.method(
@@ -754,6 +754,26 @@
returnValue: _i9.Future<void>.value(),
returnValueForMissingStub: _i9.Future<void>.value(),
) as _i9.Future<void>);
+ @override
+ _i9.Future<void> setOnConsoleMessage(
+ void Function(_i3.JavaScriptConsoleMessage)? onConsoleMessage) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #setOnConsoleMessage,
+ [onConsoleMessage],
+ ),
+ returnValue: _i9.Future<void>.value(),
+ returnValueForMissingStub: _i9.Future<void>.value(),
+ ) as _i9.Future<void>);
+ @override
+ _i9.Future<String?> getUserAgent() => (super.noSuchMethod(
+ Invocation.method(
+ #getUserAgent,
+ [],
+ ),
+ returnValue: _i9.Future<String?>.value(),
+ returnValueForMissingStub: _i9.Future<String?>.value(),
+ ) as _i9.Future<String?>);
}
/// A class which mocks [AndroidWebViewProxy].
@@ -775,6 +795,10 @@
) as _i2.WebView Function());
@override
_i2.WebChromeClient Function({
+ void Function(
+ _i2.WebChromeClient,
+ _i2.ConsoleMessage,
+ )? onConsoleMessage,
void Function(_i2.WebChromeClient)? onGeolocationPermissionsHidePrompt,
_i9.Future<void> Function(
String,
@@ -801,6 +825,10 @@
}) get createAndroidWebChromeClient => (super.noSuchMethod(
Invocation.getter(#createAndroidWebChromeClient),
returnValue: ({
+ void Function(
+ _i2.WebChromeClient,
+ _i2.ConsoleMessage,
+ )? onConsoleMessage,
void Function(_i2.WebChromeClient)?
onGeolocationPermissionsHidePrompt,
_i9.Future<void> Function(
@@ -831,6 +859,10 @@
Invocation.getter(#createAndroidWebChromeClient),
),
returnValueForMissingStub: ({
+ void Function(
+ _i2.WebChromeClient,
+ _i2.ConsoleMessage,
+ )? onConsoleMessage,
void Function(_i2.WebChromeClient)?
onGeolocationPermissionsHidePrompt,
_i9.Future<void> Function(
@@ -861,6 +893,10 @@
Invocation.getter(#createAndroidWebChromeClient),
),
) as _i2.WebChromeClient Function({
+ void Function(
+ _i2.WebChromeClient,
+ _i2.ConsoleMessage,
+ )? onConsoleMessage,
void Function(_i2.WebChromeClient)? onGeolocationPermissionsHidePrompt,
_i9.Future<void> Function(
String,
@@ -1780,6 +1816,16 @@
returnValueForMissingStub: _i9.Future<void>.value(),
) as _i9.Future<void>);
@override
+ _i9.Future<void> setSynchronousReturnValueForOnConsoleMessage(bool? value) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #setSynchronousReturnValueForOnConsoleMessage,
+ [value],
+ ),
+ returnValue: _i9.Future<void>.value(),
+ returnValueForMissingStub: _i9.Future<void>.value(),
+ ) as _i9.Future<void>);
+ @override
_i2.WebChromeClient copy() => (super.noSuchMethod(
Invocation.method(
#copy,
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.mocks.dart
index b23bcaa..5213793 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_cookie_manager_test.mocks.dart
@@ -475,8 +475,8 @@
) as _i5.Future<void>);
@override
_i5.Future<void> setCustomWidgetCallbacks({
- _i6.OnShowCustomWidgetCallback? onShowCustomWidget,
- _i6.OnHideCustomWidgetCallback? onHideCustomWidget,
+ required _i6.OnShowCustomWidgetCallback? onShowCustomWidget,
+ required _i6.OnHideCustomWidgetCallback? onHideCustomWidget,
}) =>
(super.noSuchMethod(
Invocation.method(
@@ -490,6 +490,25 @@
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
+ @override
+ _i5.Future<void> setOnConsoleMessage(
+ void Function(_i3.JavaScriptConsoleMessage)? onConsoleMessage) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #setOnConsoleMessage,
+ [onConsoleMessage],
+ ),
+ returnValue: _i5.Future<void>.value(),
+ returnValueForMissingStub: _i5.Future<void>.value(),
+ ) as _i5.Future<void>);
+ @override
+ _i5.Future<String?> getUserAgent() => (super.noSuchMethod(
+ Invocation.method(
+ #getUserAgent,
+ [],
+ ),
+ returnValue: _i5.Future<String?>.value(),
+ ) as _i5.Future<String?>);
}
/// A class which mocks [TestInstanceManagerHostApi].
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart
index da03052..2629d56 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart
@@ -1120,6 +1120,81 @@
expect(callbackParameters, <Object?>[instance]);
});
+ test('onConsoleMessage', () async {
+ late final List<Object> result;
+ when(mockWebChromeClient.onConsoleMessage).thenReturn(
+ (WebChromeClient instance, ConsoleMessage message) {
+ result = <Object>[instance, message];
+ },
+ );
+
+ final ConsoleMessage message = ConsoleMessage(
+ lineNumber: 0,
+ message: 'message',
+ level: ConsoleMessageLevel.error,
+ sourceId: 'sourceId',
+ );
+
+ flutterApi.onConsoleMessage(
+ mockWebChromeClientInstanceId,
+ message,
+ );
+ expect(result[0], mockWebChromeClient);
+ expect(result[1], message);
+ });
+
+ test('setSynchronousReturnValueForOnConsoleMessage', () {
+ final MockTestWebChromeClientHostApi mockHostApi =
+ MockTestWebChromeClientHostApi();
+ TestWebChromeClientHostApi.setup(mockHostApi);
+
+ WebChromeClient.api =
+ WebChromeClientHostApiImpl(instanceManager: instanceManager);
+
+ final WebChromeClient webChromeClient = WebChromeClient.detached();
+ instanceManager.addHostCreatedInstance(webChromeClient, 2);
+
+ webChromeClient.setSynchronousReturnValueForOnConsoleMessage(false);
+
+ verify(
+ mockHostApi.setSynchronousReturnValueForOnConsoleMessage(2, false),
+ );
+ });
+
+ test(
+ 'setSynchronousReturnValueForOnConsoleMessage throws StateError when onConsoleMessage is null',
+ () {
+ final MockTestWebChromeClientHostApi mockHostApi =
+ MockTestWebChromeClientHostApi();
+ TestWebChromeClientHostApi.setup(mockHostApi);
+
+ WebChromeClient.api =
+ WebChromeClientHostApiImpl(instanceManager: instanceManager);
+
+ final WebChromeClient clientWithNullCallback =
+ WebChromeClient.detached();
+ instanceManager.addHostCreatedInstance(clientWithNullCallback, 2);
+
+ expect(
+ () => clientWithNullCallback
+ .setSynchronousReturnValueForOnConsoleMessage(true),
+ throwsStateError,
+ );
+
+ final WebChromeClient clientWithNonnullCallback =
+ WebChromeClient.detached(
+ onConsoleMessage: (_, __) async {},
+ );
+ instanceManager.addHostCreatedInstance(clientWithNonnullCallback, 3);
+
+ clientWithNonnullCallback
+ .setSynchronousReturnValueForOnConsoleMessage(true);
+
+ verify(
+ mockHostApi.setSynchronousReturnValueForOnConsoleMessage(3, true),
+ );
+ });
+
test('copy', () {
expect(WebChromeClient.detached().copy(), isA<WebChromeClient>());
});
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 fecf814..717cc14 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
@@ -480,6 +480,21 @@
),
returnValueForMissingStub: null,
);
+ @override
+ void setSynchronousReturnValueForOnConsoleMessage(
+ int? instanceId,
+ bool? value,
+ ) =>
+ super.noSuchMethod(
+ Invocation.method(
+ #setSynchronousReturnValueForOnConsoleMessage,
+ [
+ instanceId,
+ value,
+ ],
+ ),
+ returnValueForMissingStub: null,
+ );
}
/// A class which mocks [TestWebSettingsHostApi].
@@ -1181,6 +1196,16 @@
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
+ _i5.Future<void> setSynchronousReturnValueForOnConsoleMessage(bool? value) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #setSynchronousReturnValueForOnConsoleMessage,
+ [value],
+ ),
+ returnValue: _i5.Future<void>.value(),
+ returnValueForMissingStub: _i5.Future<void>.value(),
+ ) as _i5.Future<void>);
+ @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 c527038..150ea5a 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
@@ -780,6 +780,16 @@
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
+ _i5.Future<void> setSynchronousReturnValueForOnConsoleMessage(bool? value) =>
+ (super.noSuchMethod(
+ Invocation.method(
+ #setSynchronousReturnValueForOnConsoleMessage,
+ [value],
+ ),
+ returnValue: _i5.Future<void>.value(),
+ returnValueForMissingStub: _i5.Future<void>.value(),
+ ) as _i5.Future<void>);
+ @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 b88ea8a..a92c054 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
@@ -1516,6 +1516,8 @@
void setSynchronousReturnValueForOnShowFileChooser(
int instanceId, bool value);
+ void setSynchronousReturnValueForOnConsoleMessage(int instanceId, bool value);
+
static void setup(TestWebChromeClientHostApi? api,
{BinaryMessenger? binaryMessenger}) {
{
@@ -1568,6 +1570,33 @@
});
}
}
+ {
+ final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+ 'dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnConsoleMessage',
+ codec,
+ binaryMessenger: binaryMessenger);
+ if (api == null) {
+ _testBinaryMessengerBinding!.defaultBinaryMessenger
+ .setMockDecodedMessageHandler<Object?>(channel, null);
+ } else {
+ _testBinaryMessengerBinding!.defaultBinaryMessenger
+ .setMockDecodedMessageHandler<Object?>(channel,
+ (Object? message) async {
+ assert(message != null,
+ 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnConsoleMessage was null.');
+ final List<Object?> args = (message as List<Object?>?)!;
+ final int? arg_instanceId = (args[0] as int?);
+ assert(arg_instanceId != null,
+ 'Argument for dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnConsoleMessage 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.setSynchronousReturnValueForOnConsoleMessage was null, expected non-null bool.');
+ api.setSynchronousReturnValueForOnConsoleMessage(
+ arg_instanceId!, arg_value!);
+ return <Object?>[];
+ });
+ }
+ }
}
}