Skip to content

Commit eac45de

Browse files
authored
[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].*
1 parent 95b9959 commit eac45de

23 files changed

+1064
-11
lines changed

packages/webview_flutter/webview_flutter_android/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 3.11.0
2+
3+
* Adds support to register a callback to receive JavaScript console messages. See `AndroidWebViewController.onConsoleMessage`.
4+
15
## 3.10.1
26

37
* Bumps androidx.annotation:annotation from 1.5.0 to 1.7.0.

packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java

Lines changed: 259 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,62 @@ private FileChooserMode(final int index) {
9494
}
9595
}
9696

97+
/**
98+
* Indicates the type of message logged to the console.
99+
*
100+
* <p>See https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel.
101+
*/
102+
public enum ConsoleMessageLevel {
103+
/**
104+
* Indicates a message is logged for debugging.
105+
*
106+
* <p>See
107+
* https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#DEBUG.
108+
*/
109+
DEBUG(0),
110+
/**
111+
* Indicates a message is provided as an error.
112+
*
113+
* <p>See
114+
* https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#ERROR.
115+
*/
116+
ERROR(1),
117+
/**
118+
* Indicates a message is provided as a basic log message.
119+
*
120+
* <p>See
121+
* https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#LOG.
122+
*/
123+
LOG(2),
124+
/**
125+
* Indicates a message is provided as a tip.
126+
*
127+
* <p>See
128+
* https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#TIP.
129+
*/
130+
TIP(3),
131+
/**
132+
* Indicates a message is provided as a warning.
133+
*
134+
* <p>See
135+
* https://developer.android.com/reference/android/webkit/ConsoleMessage.MessageLevel#WARNING.
136+
*/
137+
WARNING(4),
138+
/**
139+
* Indicates a message with an unknown level.
140+
*
141+
* <p>This does not represent an actual value provided by the platform and only indicates a
142+
* value was provided that isn't currently supported.
143+
*/
144+
UNKNOWN(5);
145+
146+
final int index;
147+
148+
private ConsoleMessageLevel(final int index) {
149+
this.index = index;
150+
}
151+
}
152+
97153
/** Generated class from Pigeon that represents data sent in messages. */
98154
public static final class WebResourceRequestData {
99155
private @NonNull String url;
@@ -409,6 +465,136 @@ ArrayList<Object> toList() {
409465
}
410466
}
411467

468+
/**
469+
* Represents a JavaScript console message from WebCore.
470+
*
471+
* <p>See https://developer.android.com/reference/android/webkit/ConsoleMessage
472+
*
473+
* <p>Generated class from Pigeon that represents data sent in messages.
474+
*/
475+
public static final class ConsoleMessage {
476+
private @NonNull Long lineNumber;
477+
478+
public @NonNull Long getLineNumber() {
479+
return lineNumber;
480+
}
481+
482+
public void setLineNumber(@NonNull Long setterArg) {
483+
if (setterArg == null) {
484+
throw new IllegalStateException("Nonnull field \"lineNumber\" is null.");
485+
}
486+
this.lineNumber = setterArg;
487+
}
488+
489+
private @NonNull String message;
490+
491+
public @NonNull String getMessage() {
492+
return message;
493+
}
494+
495+
public void setMessage(@NonNull String setterArg) {
496+
if (setterArg == null) {
497+
throw new IllegalStateException("Nonnull field \"message\" is null.");
498+
}
499+
this.message = setterArg;
500+
}
501+
502+
private @NonNull ConsoleMessageLevel level;
503+
504+
public @NonNull ConsoleMessageLevel getLevel() {
505+
return level;
506+
}
507+
508+
public void setLevel(@NonNull ConsoleMessageLevel setterArg) {
509+
if (setterArg == null) {
510+
throw new IllegalStateException("Nonnull field \"level\" is null.");
511+
}
512+
this.level = setterArg;
513+
}
514+
515+
private @NonNull String sourceId;
516+
517+
public @NonNull String getSourceId() {
518+
return sourceId;
519+
}
520+
521+
public void setSourceId(@NonNull String setterArg) {
522+
if (setterArg == null) {
523+
throw new IllegalStateException("Nonnull field \"sourceId\" is null.");
524+
}
525+
this.sourceId = setterArg;
526+
}
527+
528+
/** Constructor is non-public to enforce null safety; use Builder. */
529+
ConsoleMessage() {}
530+
531+
public static final class Builder {
532+
533+
private @Nullable Long lineNumber;
534+
535+
public @NonNull Builder setLineNumber(@NonNull Long setterArg) {
536+
this.lineNumber = setterArg;
537+
return this;
538+
}
539+
540+
private @Nullable String message;
541+
542+
public @NonNull Builder setMessage(@NonNull String setterArg) {
543+
this.message = setterArg;
544+
return this;
545+
}
546+
547+
private @Nullable ConsoleMessageLevel level;
548+
549+
public @NonNull Builder setLevel(@NonNull ConsoleMessageLevel setterArg) {
550+
this.level = setterArg;
551+
return this;
552+
}
553+
554+
private @Nullable String sourceId;
555+
556+
public @NonNull Builder setSourceId(@NonNull String setterArg) {
557+
this.sourceId = setterArg;
558+
return this;
559+
}
560+
561+
public @NonNull ConsoleMessage build() {
562+
ConsoleMessage pigeonReturn = new ConsoleMessage();
563+
pigeonReturn.setLineNumber(lineNumber);
564+
pigeonReturn.setMessage(message);
565+
pigeonReturn.setLevel(level);
566+
pigeonReturn.setSourceId(sourceId);
567+
return pigeonReturn;
568+
}
569+
}
570+
571+
@NonNull
572+
ArrayList<Object> toList() {
573+
ArrayList<Object> toListResult = new ArrayList<Object>(4);
574+
toListResult.add(lineNumber);
575+
toListResult.add(message);
576+
toListResult.add(level == null ? null : level.index);
577+
toListResult.add(sourceId);
578+
return toListResult;
579+
}
580+
581+
static @NonNull ConsoleMessage fromList(@NonNull ArrayList<Object> list) {
582+
ConsoleMessage pigeonResult = new ConsoleMessage();
583+
Object lineNumber = list.get(0);
584+
pigeonResult.setLineNumber(
585+
(lineNumber == null)
586+
? null
587+
: ((lineNumber instanceof Integer) ? (Integer) lineNumber : (Long) lineNumber));
588+
Object message = list.get(1);
589+
pigeonResult.setMessage((String) message);
590+
Object level = list.get(2);
591+
pigeonResult.setLevel(ConsoleMessageLevel.values()[(int) level]);
592+
Object sourceId = list.get(3);
593+
pigeonResult.setSourceId((String) sourceId);
594+
return pigeonResult;
595+
}
596+
}
597+
412598
public interface Result<T> {
413599
@SuppressWarnings("UnknownNullness")
414600
void success(T result);
@@ -2401,6 +2587,9 @@ public interface WebChromeClientHostApi {
24012587
void setSynchronousReturnValueForOnShowFileChooser(
24022588
@NonNull Long instanceId, @NonNull Boolean value);
24032589

2590+
void setSynchronousReturnValueForOnConsoleMessage(
2591+
@NonNull Long instanceId, @NonNull Boolean value);
2592+
24042593
/** The codec used by WebChromeClientHostApi. */
24052594
static @NonNull MessageCodec<Object> getCodec() {
24062595
return new StandardMessageCodec();
@@ -2463,6 +2652,33 @@ static void setup(
24632652
channel.setMessageHandler(null);
24642653
}
24652654
}
2655+
{
2656+
BasicMessageChannel<Object> channel =
2657+
new BasicMessageChannel<>(
2658+
binaryMessenger,
2659+
"dev.flutter.pigeon.webview_flutter_android.WebChromeClientHostApi.setSynchronousReturnValueForOnConsoleMessage",
2660+
getCodec());
2661+
if (api != null) {
2662+
channel.setMessageHandler(
2663+
(message, reply) -> {
2664+
ArrayList<Object> wrapped = new ArrayList<Object>();
2665+
ArrayList<Object> args = (ArrayList<Object>) message;
2666+
Number instanceIdArg = (Number) args.get(0);
2667+
Boolean valueArg = (Boolean) args.get(1);
2668+
try {
2669+
api.setSynchronousReturnValueForOnConsoleMessage(
2670+
(instanceIdArg == null) ? null : instanceIdArg.longValue(), valueArg);
2671+
wrapped.add(0, null);
2672+
} catch (Throwable exception) {
2673+
ArrayList<Object> wrappedError = wrapError(exception);
2674+
wrapped = wrappedError;
2675+
}
2676+
reply.reply(wrapped);
2677+
});
2678+
} else {
2679+
channel.setMessageHandler(null);
2680+
}
2681+
}
24662682
}
24672683
}
24682684
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
@@ -2536,6 +2752,34 @@ static void setup(
25362752
}
25372753
}
25382754
}
2755+
2756+
private static class WebChromeClientFlutterApiCodec extends StandardMessageCodec {
2757+
public static final WebChromeClientFlutterApiCodec INSTANCE =
2758+
new WebChromeClientFlutterApiCodec();
2759+
2760+
private WebChromeClientFlutterApiCodec() {}
2761+
2762+
@Override
2763+
protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) {
2764+
switch (type) {
2765+
case (byte) 128:
2766+
return ConsoleMessage.fromList((ArrayList<Object>) readValue(buffer));
2767+
default:
2768+
return super.readValueOfType(type, buffer);
2769+
}
2770+
}
2771+
2772+
@Override
2773+
protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) {
2774+
if (value instanceof ConsoleMessage) {
2775+
stream.write(128);
2776+
writeValue(stream, ((ConsoleMessage) value).toList());
2777+
} else {
2778+
super.writeValue(stream, value);
2779+
}
2780+
}
2781+
}
2782+
25392783
/** Generated class from Pigeon that represents Flutter messages that can be called from Java. */
25402784
public static class WebChromeClientFlutterApi {
25412785
private final @NonNull BinaryMessenger binaryMessenger;
@@ -2551,7 +2795,7 @@ public interface Reply<T> {
25512795
}
25522796
/** The codec used by WebChromeClientFlutterApi. */
25532797
static @NonNull MessageCodec<Object> getCodec() {
2554-
return new StandardMessageCodec();
2798+
return WebChromeClientFlutterApiCodec.INSTANCE;
25552799
}
25562800

25572801
public void onProgressChanged(
@@ -2656,6 +2900,20 @@ public void onGeolocationPermissionsHidePrompt(
26562900
new ArrayList<Object>(Collections.singletonList(identifierArg)),
26572901
channelReply -> callback.reply(null));
26582902
}
2903+
/** Callback to Dart function `WebChromeClient.onConsoleMessage`. */
2904+
public void onConsoleMessage(
2905+
@NonNull Long instanceIdArg,
2906+
@NonNull ConsoleMessage messageArg,
2907+
@NonNull Reply<Void> callback) {
2908+
BasicMessageChannel<Object> channel =
2909+
new BasicMessageChannel<>(
2910+
binaryMessenger,
2911+
"dev.flutter.pigeon.webview_flutter_android.WebChromeClientFlutterApi.onConsoleMessage",
2912+
getCodec());
2913+
channel.send(
2914+
new ArrayList<Object>(Arrays.asList(instanceIdArg, messageArg)),
2915+
channelReply -> callback.reply(null));
2916+
}
26592917
}
26602918
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
26612919
public interface WebStorageHostApi {

packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import android.os.Build;
88
import android.view.View;
9+
import android.webkit.ConsoleMessage;
910
import android.webkit.GeolocationPermissions;
1011
import android.webkit.PermissionRequest;
1112
import android.webkit.WebChromeClient;
@@ -27,6 +28,24 @@ public class WebChromeClientFlutterApiImpl extends WebChromeClientFlutterApi {
2728
private final InstanceManager instanceManager;
2829
private final WebViewFlutterApiImpl webViewFlutterApi;
2930

31+
private static GeneratedAndroidWebView.ConsoleMessageLevel toConsoleMessageLevel(
32+
ConsoleMessage.MessageLevel level) {
33+
switch (level) {
34+
case TIP:
35+
return GeneratedAndroidWebView.ConsoleMessageLevel.TIP;
36+
case LOG:
37+
return GeneratedAndroidWebView.ConsoleMessageLevel.LOG;
38+
case WARNING:
39+
return GeneratedAndroidWebView.ConsoleMessageLevel.WARNING;
40+
case ERROR:
41+
return GeneratedAndroidWebView.ConsoleMessageLevel.ERROR;
42+
case DEBUG:
43+
return GeneratedAndroidWebView.ConsoleMessageLevel.DEBUG;
44+
}
45+
46+
return GeneratedAndroidWebView.ConsoleMessageLevel.UNKNOWN;
47+
}
48+
3049
/**
3150
* Creates a Flutter api that sends messages to Dart.
3251
*
@@ -149,6 +168,25 @@ public void onHideCustomView(
149168
callback);
150169
}
151170

171+
/**
172+
* Sends a message to Dart to call `WebChromeClient.onConsoleMessage` on the Dart object
173+
* representing `instance`.
174+
*/
175+
public void onConsoleMessage(
176+
@NonNull WebChromeClient instance,
177+
@NonNull ConsoleMessage message,
178+
@NonNull Reply<Void> callback) {
179+
super.onConsoleMessage(
180+
Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(instance)),
181+
new GeneratedAndroidWebView.ConsoleMessage.Builder()
182+
.setLineNumber((long) message.lineNumber())
183+
.setMessage(message.message())
184+
.setLevel(toConsoleMessageLevel(message.messageLevel()))
185+
.setSourceId(message.sourceId())
186+
.build(),
187+
callback);
188+
}
189+
152190
private long getIdentifierForClient(WebChromeClient webChromeClient) {
153191
final Long identifier = instanceManager.getIdentifierForStrongReference(webChromeClient);
154192
if (identifier == null) {

0 commit comments

Comments
 (0)