Skip to content

Commit

Permalink
[webview_flutter_android] Adds Android implementation to override con…
Browse files Browse the repository at this point in the history
…sole 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].*
  • Loading branch information
mvanbeusekom authored Sep 29, 2023
1 parent 95b9959 commit eac45de
Show file tree
Hide file tree
Showing 23 changed files with 1,064 additions and 11 deletions.
4 changes: 4 additions & 0 deletions packages/webview_flutter/webview_flutter_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,62 @@ private FileChooserMode(final int index) {
}
}

/**
* 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;
Expand Down Expand Up @@ -409,6 +465,136 @@ ArrayList<Object> toList() {
}
}

/**
* 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);
Expand Down Expand Up @@ -2401,6 +2587,9 @@ public interface WebChromeClientHostApi {
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();
Expand Down Expand Up @@ -2463,6 +2652,33 @@ static void setup(
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. */
Expand Down Expand Up @@ -2536,6 +2752,34 @@ static void setup(
}
}
}

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;
Expand All @@ -2551,7 +2795,7 @@ public interface Reply<T> {
}
/** The codec used by WebChromeClientFlutterApi. */
static @NonNull MessageCodec<Object> getCodec() {
return new StandardMessageCodec();
return WebChromeClientFlutterApiCodec.INSTANCE;
}

public void onProgressChanged(
Expand Down Expand Up @@ -2656,6 +2900,20 @@ public void onGeolocationPermissionsHidePrompt(
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -27,6 +28,24 @@ public class WebChromeClientFlutterApiImpl extends WebChromeClientFlutterApi {
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.
*
Expand Down Expand Up @@ -149,6 +168,25 @@ public void onHideCustomView(
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) {
Expand Down
Loading

0 comments on commit eac45de

Please sign in to comment.