mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
Wasm: support displaying objects in debugger
This commit is contained in:
parent
7b3905246b
commit
9a9e7561b7
|
@ -22,5 +22,24 @@ public enum VariableType {
|
|||
DOUBLE,
|
||||
OBJECT,
|
||||
ADDRESS,
|
||||
UNDEFINED
|
||||
UNDEFINED;
|
||||
|
||||
public FieldType asFieldType() {
|
||||
switch (this) {
|
||||
case INT:
|
||||
return FieldType.INT;
|
||||
case LONG:
|
||||
return FieldType.LONG;
|
||||
case FLOAT:
|
||||
return FieldType.FLOAT;
|
||||
case DOUBLE:
|
||||
return FieldType.DOUBLE;
|
||||
case OBJECT:
|
||||
return FieldType.OBJECT;
|
||||
case ADDRESS:
|
||||
return FieldType.ADDRESS;
|
||||
default:
|
||||
return FieldType.UNDEFINED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -714,7 +714,16 @@ public class WasmClassGenerator {
|
|||
debug.writeArray(indexes.get(itemType), data.start);
|
||||
} else if (data.type instanceof ValueType.Object) {
|
||||
var className = ((ValueType.Object) data.type).getClassName();
|
||||
if (isManagedClass(className)) {
|
||||
if (className.equals("java.lang.Class")) {
|
||||
int headerSize = 8;
|
||||
debug.startClass(className, indexes.get(ValueType.object("java.lang.Object")), data.start, 60);
|
||||
debug.instanceField("size", 8, FieldType.INT);
|
||||
debug.instanceField("flags", 12, FieldType.INT);
|
||||
debug.instanceField("name", 24, FieldType.OBJECT);
|
||||
debug.instanceField("itemType", 32, FieldType.OBJECT);
|
||||
debug.instanceField("parent", 56, FieldType.OBJECT);
|
||||
debug.endClass();
|
||||
} else if (isManagedClass(className)) {
|
||||
var parent = data.cls.getParent() != null
|
||||
? indexes.get(ValueType.object(data.cls.getParent()))
|
||||
: -1;
|
||||
|
|
|
@ -140,8 +140,8 @@ public class Promise<T> {
|
|||
if (state == State.PENDING || state == State.WAITING_PROMISE) {
|
||||
if (thenList == null) {
|
||||
thenList = new ArrayList<>();
|
||||
thenList.add(new Then<>(f, result, false));
|
||||
}
|
||||
thenList.add(new Then<>(f, result, false));
|
||||
} else {
|
||||
passValue(f, result);
|
||||
}
|
||||
|
@ -160,8 +160,8 @@ public class Promise<T> {
|
|||
if (state == State.PENDING || state == State.WAITING_PROMISE) {
|
||||
if (thenList == null) {
|
||||
thenList = new ArrayList<>();
|
||||
thenList.add(new Then<>(f, result, true));
|
||||
}
|
||||
thenList.add(new Then<>(f, result, true));
|
||||
} else if (state == State.COMPLETED) {
|
||||
passValueAsync(f, result);
|
||||
}
|
||||
|
@ -173,8 +173,8 @@ public class Promise<T> {
|
|||
if (state == State.PENDING || state == State.WAITING_PROMISE) {
|
||||
if (catchList == null) {
|
||||
catchList = new ArrayList<>();
|
||||
catchList.add(new Catch(f, result));
|
||||
}
|
||||
catchList.add(new Catch(f, result));
|
||||
} else if (state == State.ERRORED) {
|
||||
passError(f, result);
|
||||
}
|
||||
|
@ -266,9 +266,9 @@ public class Promise<T> {
|
|||
this.value = value;
|
||||
|
||||
if (thenList != null) {
|
||||
List<Then<T>> list = thenList;
|
||||
var list = thenList;
|
||||
thenList = null;
|
||||
for (Then<T> then : list) {
|
||||
for (var then : list) {
|
||||
if (then.promise) {
|
||||
passValueAsync((Function<T, Promise<Object>>) then.f, (Promise<Object>) then.target);
|
||||
} else {
|
||||
|
|
|
@ -47,6 +47,7 @@ import org.teavm.debugging.javascript.JavaScriptDebugger;
|
|||
import org.teavm.debugging.javascript.JavaScriptDebuggerListener;
|
||||
import org.teavm.debugging.javascript.JavaScriptLocation;
|
||||
import org.teavm.debugging.javascript.JavaScriptScript;
|
||||
import org.teavm.debugging.javascript.JavaScriptValue;
|
||||
import org.teavm.debugging.javascript.JavaScriptVariable;
|
||||
import org.teavm.model.MethodReference;
|
||||
import org.teavm.model.ValueType;
|
||||
|
@ -516,7 +517,7 @@ public class Debugger {
|
|||
for (Map.Entry<String, ? extends JavaScriptVariable> entry : jsVariables.entrySet()) {
|
||||
JavaScriptVariable jsVar = entry.getValue();
|
||||
String[] names = mapVariable(entry.getKey(), jsFrame.getLocation());
|
||||
Value value = new Value(this, debugInformation, jsVar.getValue());
|
||||
Value value = new JsValueImpl(this, debugInformation, jsVar.getValue());
|
||||
for (String name : names) {
|
||||
if (name == null) {
|
||||
name = "js:" + jsVar.getName();
|
||||
|
@ -543,9 +544,15 @@ public class Debugger {
|
|||
var variable = prop.get("value");
|
||||
return variable != null ? variable.getValue() : null;
|
||||
})
|
||||
.thenAsync(value -> {
|
||||
.thenAsync((JavaScriptValue value) -> {
|
||||
if (value != null) {
|
||||
var varValue = new Value(this, debugInfo, value);
|
||||
var repr = value.getSimpleRepresentation();
|
||||
if (repr.endsWith("n")) {
|
||||
repr = repr.substring(0, repr.length() - 1);
|
||||
}
|
||||
var longValue = Long.parseLong(repr);
|
||||
var varValue = new WasmValueImpl(this, debugInfo,
|
||||
range.variable().type().asFieldType(), jsFrame, longValue);
|
||||
var variable = new Variable(range.variable().name(), varValue);
|
||||
vars.put(variable.getName(), variable);
|
||||
}
|
||||
|
|
120
core/src/main/java/org/teavm/debugging/JsValueImpl.java
Normal file
120
core/src/main/java/org/teavm/debugging/JsValueImpl.java
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright 2022 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.debugging;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.teavm.common.Promise;
|
||||
import org.teavm.debugging.information.DebugInformation;
|
||||
import org.teavm.debugging.javascript.JavaScriptValue;
|
||||
import org.teavm.debugging.javascript.JavaScriptVariable;
|
||||
|
||||
class JsValueImpl extends Value {
|
||||
private DebugInformation debugInformation;
|
||||
private JavaScriptValue jsValue;
|
||||
|
||||
JsValueImpl(Debugger debugger, DebugInformation debugInformation, JavaScriptValue jsValue) {
|
||||
super(debugger);
|
||||
this.debugInformation = debugInformation;
|
||||
this.jsValue = jsValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Promise<String> getRepresentation() {
|
||||
return jsValue.getRepresentation();
|
||||
}
|
||||
|
||||
@Override
|
||||
Promise<String> prepareType() {
|
||||
return jsValue.getClassName().then(className -> {
|
||||
if (className.startsWith("a/")) {
|
||||
className = className.substring(2);
|
||||
String origClassName = className;
|
||||
int degree = 0;
|
||||
while (className.endsWith("[]")) {
|
||||
className = className.substring(0, className.length() - 2);
|
||||
++degree;
|
||||
}
|
||||
String javaClassName = debugInformation.getClassNameByJsName(className);
|
||||
if (javaClassName != null) {
|
||||
if (degree > 0) {
|
||||
StringBuilder sb = new StringBuilder(javaClassName);
|
||||
for (int i = 0; i < degree; ++i) {
|
||||
sb.append("[]");
|
||||
}
|
||||
javaClassName = sb.toString();
|
||||
}
|
||||
className = javaClassName;
|
||||
} else {
|
||||
className = origClassName;
|
||||
}
|
||||
}
|
||||
return className;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
Promise<Map<String, Variable>> prepareProperties() {
|
||||
return jsValue.getProperties().thenAsync(jsVariables -> {
|
||||
return getType().thenAsync(className -> {
|
||||
if (!className.startsWith("@") && className.endsWith("[]") && jsVariables.containsKey("data")) {
|
||||
return jsVariables.get("data").getValue().getProperties()
|
||||
.then(arrayData -> fillArray(arrayData));
|
||||
}
|
||||
var vars = new HashMap<String, Variable>();
|
||||
for (var entry : jsVariables.entrySet()) {
|
||||
var jsVar = entry.getValue();
|
||||
String name;
|
||||
name = debugger.mapField(className, entry.getKey());
|
||||
if (name == null) {
|
||||
continue;
|
||||
}
|
||||
var value = new JsValueImpl(debugger, debugInformation, jsVar.getValue());
|
||||
vars.put(name, new Variable(name, value));
|
||||
}
|
||||
return Promise.of(vars);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Variable> fillArray(Map<String, ? extends JavaScriptVariable> jsVariables) {
|
||||
var vars = new HashMap<String, Variable>();
|
||||
for (var entry : jsVariables.entrySet()) {
|
||||
var jsVar = entry.getValue();
|
||||
if (!isNumeric(entry.getKey())) {
|
||||
continue;
|
||||
}
|
||||
Value value = new JsValueImpl(debugger, debugInformation, jsVar.getValue());
|
||||
vars.put(entry.getKey(), new Variable(entry.getKey(), value));
|
||||
}
|
||||
return vars;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Promise<Boolean> hasInnerStructure() {
|
||||
return getType().then(value -> !value.equals("long") && jsValue.hasInnerStructure());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Promise<String> getInstanceId() {
|
||||
return getType().then(value -> value.equals("long") ? null : jsValue.getInstanceId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaScriptValue getOriginalValue() {
|
||||
return jsValue;
|
||||
}
|
||||
}
|
|
@ -15,35 +15,22 @@
|
|||
*/
|
||||
package org.teavm.debugging;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.teavm.backend.wasm.debug.info.DebugInfo;
|
||||
import org.teavm.common.Promise;
|
||||
import org.teavm.debugging.information.DebugInformation;
|
||||
import org.teavm.debugging.javascript.JavaScriptValue;
|
||||
import org.teavm.debugging.javascript.JavaScriptVariable;
|
||||
|
||||
public class Value {
|
||||
private Debugger debugger;
|
||||
private DebugInformation debugInformation;
|
||||
public abstract class Value {
|
||||
Debugger debugger;
|
||||
private DebugInfo wasmDebugInfo;
|
||||
private JavaScriptValue jsValue;
|
||||
private Promise<Map<String, Variable>> properties;
|
||||
private Promise<String> type;
|
||||
|
||||
Value(Debugger debugger, DebugInformation debugInformation, JavaScriptValue jsValue) {
|
||||
Value(Debugger debugger) {
|
||||
this.debugger = debugger;
|
||||
this.debugInformation = debugInformation;
|
||||
this.jsValue = jsValue;
|
||||
}
|
||||
|
||||
Value(Debugger debugger, DebugInfo wasmDebugInfo, JavaScriptValue jsValue) {
|
||||
this.debugger = debugger;
|
||||
this.wasmDebugInfo = wasmDebugInfo;
|
||||
this.jsValue = jsValue;
|
||||
}
|
||||
|
||||
private static boolean isNumeric(String str) {
|
||||
static boolean isNumeric(String str) {
|
||||
for (int i = 0; i < str.length(); ++i) {
|
||||
char c = str.charAt(i);
|
||||
if (c < '0' || c > '9') {
|
||||
|
@ -53,89 +40,29 @@ public class Value {
|
|||
return true;
|
||||
}
|
||||
|
||||
public Promise<String> getRepresentation() {
|
||||
return jsValue.getRepresentation();
|
||||
}
|
||||
public abstract Promise<String> getRepresentation();
|
||||
|
||||
public Promise<String> getType() {
|
||||
if (type == null) {
|
||||
type = jsValue.getClassName().then(className -> {
|
||||
if (className.startsWith("a/")) {
|
||||
className = className.substring(2);
|
||||
String origClassName = className;
|
||||
int degree = 0;
|
||||
while (className.endsWith("[]")) {
|
||||
className = className.substring(0, className.length() - 2);
|
||||
++degree;
|
||||
}
|
||||
String javaClassName = debugInformation.getClassNameByJsName(className);
|
||||
if (javaClassName != null) {
|
||||
if (degree > 0) {
|
||||
StringBuilder sb = new StringBuilder(javaClassName);
|
||||
for (int i = 0; i < degree; ++i) {
|
||||
sb.append("[]");
|
||||
}
|
||||
javaClassName = sb.toString();
|
||||
}
|
||||
className = javaClassName;
|
||||
} else {
|
||||
className = origClassName;
|
||||
}
|
||||
}
|
||||
return className;
|
||||
});
|
||||
type = prepareType();
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
abstract Promise<String> prepareType();
|
||||
|
||||
public Promise<Map<String, Variable>> getProperties() {
|
||||
if (properties == null) {
|
||||
properties = jsValue.getProperties().thenAsync(jsVariables -> {
|
||||
return getType().thenAsync(className -> {
|
||||
if (!className.startsWith("@") && className.endsWith("[]") && jsVariables.containsKey("data")) {
|
||||
return jsVariables.get("data").getValue().getProperties()
|
||||
.then(arrayData -> fillArray(arrayData));
|
||||
}
|
||||
Map<String, Variable> vars = new HashMap<>();
|
||||
for (Map.Entry<String, ? extends JavaScriptVariable> entry : jsVariables.entrySet()) {
|
||||
JavaScriptVariable jsVar = entry.getValue();
|
||||
String name;
|
||||
name = debugger.mapField(className, entry.getKey());
|
||||
if (name == null) {
|
||||
continue;
|
||||
}
|
||||
Value value = new Value(debugger, debugInformation, jsVar.getValue());
|
||||
vars.put(name, new Variable(name, value));
|
||||
}
|
||||
return Promise.of(vars);
|
||||
});
|
||||
});
|
||||
properties = prepareProperties();
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
private Map<String, Variable> fillArray(Map<String, ? extends JavaScriptVariable> jsVariables) {
|
||||
Map<String, Variable> vars = new HashMap<>();
|
||||
for (Map.Entry<String, ? extends JavaScriptVariable> entry : jsVariables.entrySet()) {
|
||||
JavaScriptVariable jsVar = entry.getValue();
|
||||
if (!isNumeric(entry.getKey())) {
|
||||
continue;
|
||||
}
|
||||
Value value = new Value(debugger, debugInformation, jsVar.getValue());
|
||||
vars.put(entry.getKey(), new Variable(entry.getKey(), value));
|
||||
}
|
||||
return vars;
|
||||
}
|
||||
abstract Promise<Map<String, Variable>> prepareProperties();
|
||||
|
||||
public Promise<Boolean> hasInnerStructure() {
|
||||
return getType().then(value -> !value.equals("long") && jsValue.hasInnerStructure());
|
||||
}
|
||||
public abstract Promise<Boolean> hasInnerStructure();
|
||||
|
||||
public Promise<String> getInstanceId() {
|
||||
return getType().then(value -> value.equals("long") ? null : jsValue.getInstanceId());
|
||||
}
|
||||
public abstract Promise<String> getInstanceId();
|
||||
|
||||
public JavaScriptValue getOriginalValue() {
|
||||
return jsValue;
|
||||
}
|
||||
public abstract JavaScriptValue getOriginalValue();
|
||||
}
|
||||
|
|
503
core/src/main/java/org/teavm/debugging/WasmValueImpl.java
Normal file
503
core/src/main/java/org/teavm/debugging/WasmValueImpl.java
Normal file
|
@ -0,0 +1,503 @@
|
|||
/*
|
||||
* Copyright 2022 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.debugging;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import org.teavm.backend.wasm.debug.info.ArrayLayout;
|
||||
import org.teavm.backend.wasm.debug.info.ClassLayout;
|
||||
import org.teavm.backend.wasm.debug.info.DebugInfo;
|
||||
import org.teavm.backend.wasm.debug.info.FieldType;
|
||||
import org.teavm.backend.wasm.debug.info.InterfaceLayout;
|
||||
import org.teavm.backend.wasm.debug.info.PrimitiveLayout;
|
||||
import org.teavm.backend.wasm.debug.info.TypeLayout;
|
||||
import org.teavm.common.Promise;
|
||||
import org.teavm.debugging.javascript.JavaScriptCallFrame;
|
||||
import org.teavm.debugging.javascript.JavaScriptValue;
|
||||
import org.teavm.model.PrimitiveType;
|
||||
|
||||
class WasmValueImpl extends Value {
|
||||
private static final String CLASS_PROP = "__class";
|
||||
private static final String ADDRESS_PROP = "__address";
|
||||
private DebugInfo debugInfo;
|
||||
private FieldType type;
|
||||
private JavaScriptCallFrame callFrame;
|
||||
private long longValue;
|
||||
private Promise<TypeLayout> calculatedType;
|
||||
|
||||
WasmValueImpl(Debugger debugger, DebugInfo debugInfo, FieldType type, JavaScriptCallFrame callFrame,
|
||||
long longValue) {
|
||||
super(debugger);
|
||||
this.debugInfo = debugInfo;
|
||||
this.type = type;
|
||||
this.callFrame = callFrame;
|
||||
this.longValue = longValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Promise<String> getRepresentation() {
|
||||
switch (type) {
|
||||
case BOOLEAN:
|
||||
return Promise.of(longValue != 0 ? "true" : "false");
|
||||
case BYTE:
|
||||
return Promise.of(Byte.toString((byte) longValue));
|
||||
case SHORT:
|
||||
return Promise.of(Short.toString((short) longValue));
|
||||
case CHAR: {
|
||||
var sb = new StringBuilder("'");
|
||||
appendChar(sb, (char) longValue);
|
||||
sb.append("'");
|
||||
return Promise.of(sb.toString());
|
||||
}
|
||||
case INT:
|
||||
return Promise.of(Integer.toString((int) longValue));
|
||||
case LONG:
|
||||
return Promise.of(Long.toString(longValue));
|
||||
case FLOAT:
|
||||
return Promise.of(Float.toString(Float.intBitsToFloat((int) longValue)));
|
||||
case DOUBLE:
|
||||
return Promise.of(Double.toString(Double.longBitsToDouble(longValue)));
|
||||
case OBJECT:
|
||||
return buildObjectRepresentation();
|
||||
case ADDRESS:
|
||||
return Promise.of("0x" + Integer.toHexString((int) longValue));
|
||||
default:
|
||||
return Promise.of("undefined");
|
||||
}
|
||||
}
|
||||
|
||||
private void appendChar(StringBuilder sb, char c) {
|
||||
switch (c) {
|
||||
case '\n':
|
||||
sb.append("\\n");
|
||||
break;
|
||||
case '\r':
|
||||
sb.append("\\r");
|
||||
break;
|
||||
case '\t':
|
||||
sb.append("\\t");
|
||||
break;
|
||||
case '\b':
|
||||
sb.append("\\b");
|
||||
break;
|
||||
case '\f':
|
||||
sb.append("\\f");
|
||||
break;
|
||||
case '\'':
|
||||
sb.append("\\\'");
|
||||
break;
|
||||
case '\"':
|
||||
sb.append("\\\"");
|
||||
break;
|
||||
case '\\':
|
||||
sb.append("\\\\");
|
||||
break;
|
||||
default:
|
||||
if (c < 32) {
|
||||
sb.append("\\u00").append(Character.forDigit(c / 16, 16))
|
||||
.append(Character.forDigit(c % 16, 16));
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private Promise<String> buildObjectRepresentation() {
|
||||
if (longValue == 0) {
|
||||
return Promise.of("null");
|
||||
}
|
||||
return getCalculatedType().thenAsync(cls -> {
|
||||
if (cls == null) {
|
||||
return Promise.of("error");
|
||||
}
|
||||
return typeRepresentation(cls, (int) longValue);
|
||||
});
|
||||
}
|
||||
|
||||
private Promise<String> typeRepresentation(TypeLayout type, int address) {
|
||||
switch (type.kind()) {
|
||||
case CLASS:
|
||||
return objectRepresentation((ClassLayout) type, address);
|
||||
case ARRAY:
|
||||
return arrayRepresentation((ArrayLayout) type, address);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return Promise.of(classToString(type));
|
||||
}
|
||||
|
||||
private Promise<String> objectRepresentation(ClassLayout cls, int address) {
|
||||
if (cls.classRef().fullName().equals("java.lang.String")) {
|
||||
var stringRepr = decodeString(cls, address);
|
||||
if (stringRepr != null) {
|
||||
return stringRepr.then(result -> result != null ? result : classToString(cls));
|
||||
}
|
||||
} else if (cls.classRef().fullName().equals("java.lang.Class")) {
|
||||
var stringRepr = decodeClass(address);
|
||||
if (stringRepr != null) {
|
||||
return Promise.of(stringRepr);
|
||||
}
|
||||
}
|
||||
return Promise.of(classToString(cls));
|
||||
}
|
||||
|
||||
private Promise<String> arrayRepresentation(ArrayLayout arrayType, int address) {
|
||||
return callFrame.getMemory(address + 8, 4).then(data -> {
|
||||
if (data == null) {
|
||||
return classToString(arrayType);
|
||||
}
|
||||
var length = readInt(data, 0);
|
||||
return classToString(arrayType.elementType()) + "[" + length + "]";
|
||||
});
|
||||
}
|
||||
|
||||
private Promise<String> decodeString(ClassLayout cls, int address) {
|
||||
for (var field : cls.instanceFields()) {
|
||||
if (field.name().equals("characters") && field.type() == FieldType.OBJECT) {
|
||||
return callFrame.getMemory(address + field.address(), 4).thenAsync(data -> {
|
||||
var charsAddress = readInt(data, 0);
|
||||
return decodeChars(charsAddress);
|
||||
});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Promise<String> decodeChars(int address) {
|
||||
return callFrame.getMemory(address, 12).thenAsync(data -> {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
var classPtr = readInt(data, 0) << 3;
|
||||
var type = debugInfo.classLayoutInfo().find(classPtr);
|
||||
if (!(type instanceof ArrayLayout)) {
|
||||
return null;
|
||||
}
|
||||
var elementType = ((ArrayLayout) type).elementType();
|
||||
if (!(elementType instanceof PrimitiveLayout)) {
|
||||
return null;
|
||||
}
|
||||
var primitiveType = ((PrimitiveLayout) elementType).primitiveType();
|
||||
if (primitiveType != PrimitiveType.CHARACTER) {
|
||||
return null;
|
||||
}
|
||||
var length = readInt(data, 8);
|
||||
return callFrame.getMemory(address + 12, length * 2).then(charsData -> {
|
||||
if (charsData == null) {
|
||||
return null;
|
||||
}
|
||||
var sb = new StringBuilder("\"");
|
||||
for (var i = 0; i < length; ++i) {
|
||||
appendChar(sb, (char) readShort(charsData, i * 2));
|
||||
}
|
||||
sb.append("\"");
|
||||
return sb.toString();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private String decodeClass(int address) {
|
||||
var type = debugInfo.classLayoutInfo().find(address);
|
||||
return type != null ? classToString(type) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
Promise<String> prepareType() {
|
||||
switch (type) {
|
||||
case BOOLEAN:
|
||||
return Promise.of("boolean");
|
||||
case BYTE:
|
||||
return Promise.of("byte");
|
||||
case SHORT:
|
||||
return Promise.of("short");
|
||||
case CHAR:
|
||||
return Promise.of("char");
|
||||
case INT:
|
||||
return Promise.of("int");
|
||||
case LONG:
|
||||
return Promise.of("long");
|
||||
case FLOAT:
|
||||
return Promise.of("float");
|
||||
case DOUBLE:
|
||||
return Promise.of("double");
|
||||
case ADDRESS:
|
||||
return Promise.of("address");
|
||||
case OBJECT:
|
||||
return fetchObjectType();
|
||||
default:
|
||||
return Promise.of("undefined");
|
||||
}
|
||||
}
|
||||
|
||||
private Promise<TypeLayout> getCalculatedType() {
|
||||
if (calculatedType == null) {
|
||||
calculatedType = callFrame.getMemory((int) longValue, 4).then(data -> {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
var header = readInt(data, 0);
|
||||
var classPtr = header << 3;
|
||||
var classes = debugInfo.classLayoutInfo();
|
||||
if (classes == null) {
|
||||
return null;
|
||||
}
|
||||
return classes.find(classPtr);
|
||||
});
|
||||
}
|
||||
return calculatedType;
|
||||
}
|
||||
|
||||
private Promise<String> fetchObjectType() {
|
||||
if (longValue == 0) {
|
||||
return Promise.of("null");
|
||||
}
|
||||
return getCalculatedType().then(cls -> {
|
||||
if (cls == null) {
|
||||
return "error";
|
||||
}
|
||||
return classToString(cls);
|
||||
});
|
||||
}
|
||||
|
||||
private String classToString(TypeLayout type) {
|
||||
switch (type.kind()) {
|
||||
case PRIMITIVE:
|
||||
switch (((PrimitiveLayout) type).primitiveType()) {
|
||||
case BOOLEAN:
|
||||
return "boolean";
|
||||
case BYTE:
|
||||
return "byte";
|
||||
case SHORT:
|
||||
return "short";
|
||||
case CHARACTER:
|
||||
return "char";
|
||||
case INTEGER:
|
||||
return "int";
|
||||
case LONG:
|
||||
return "long";
|
||||
case FLOAT:
|
||||
return "float";
|
||||
case DOUBLE:
|
||||
return "double";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case CLASS:
|
||||
return ((ClassLayout) type).classRef().fullName();
|
||||
case INTERFACE:
|
||||
return ((InterfaceLayout) type).classRef().fullName();
|
||||
case ARRAY:
|
||||
return classToString(((ArrayLayout) type).elementType()) + "[]";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
@Override
|
||||
Promise<Map<String, Variable>> prepareProperties() {
|
||||
return getCalculatedType().thenAsync(cls -> {
|
||||
if (cls != null) {
|
||||
switch (cls.kind()) {
|
||||
case CLASS:
|
||||
return fetchObjectProperties((ClassLayout) cls);
|
||||
case ARRAY:
|
||||
return fetchArrayProperties((ArrayLayout) cls);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Promise.of(Collections.emptyMap());
|
||||
});
|
||||
}
|
||||
|
||||
private Promise<Map<String, Variable>> fetchObjectProperties(ClassLayout cls) {
|
||||
if (longValue == 0) {
|
||||
return Promise.of(Collections.emptyMap());
|
||||
}
|
||||
return callFrame.getMemory((int) longValue, cls.size()).then(data -> {
|
||||
if (data == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
var properties = new LinkedHashMap<String, Variable>();
|
||||
for (var field : cls.instanceFields()) {
|
||||
long longValue;
|
||||
switch (field.type()) {
|
||||
case BOOLEAN:
|
||||
case BYTE:
|
||||
longValue = data[field.address()];
|
||||
break;
|
||||
case SHORT:
|
||||
case CHAR:
|
||||
longValue = readShort(data, field.address());
|
||||
break;
|
||||
case INT:
|
||||
case FLOAT:
|
||||
case ADDRESS:
|
||||
case OBJECT:
|
||||
longValue = readInt(data, field.address());
|
||||
break;
|
||||
case LONG:
|
||||
case DOUBLE:
|
||||
longValue = readLong(data, field.address());
|
||||
break;
|
||||
default:
|
||||
longValue = 0;
|
||||
break;
|
||||
}
|
||||
var value = new WasmValueImpl(debugger, debugInfo, field.type(), callFrame, longValue);
|
||||
properties.put(field.name(), new Variable(field.name(), value));
|
||||
}
|
||||
addCommonProperties(properties, cls);
|
||||
return properties;
|
||||
});
|
||||
}
|
||||
|
||||
private Promise<Map<String, Variable>> fetchArrayProperties(ArrayLayout type) {
|
||||
if (longValue == 0) {
|
||||
return Promise.of(Collections.emptyMap());
|
||||
}
|
||||
return callFrame.getMemory((int) longValue + 8, 4).thenAsync(data -> {
|
||||
if (data == null) {
|
||||
return Promise.of(Collections.emptyMap());
|
||||
}
|
||||
var length = readInt(data, 0);
|
||||
var offset = 12;
|
||||
int itemSize;
|
||||
FieldType elementType;
|
||||
if (type.elementType() instanceof PrimitiveLayout) {
|
||||
switch (((PrimitiveLayout) type.elementType()).primitiveType()) {
|
||||
case BOOLEAN:
|
||||
elementType = FieldType.BOOLEAN;
|
||||
itemSize = 0;
|
||||
break;
|
||||
case BYTE:
|
||||
elementType = FieldType.BYTE;
|
||||
itemSize = 0;
|
||||
break;
|
||||
case SHORT:
|
||||
elementType = FieldType.SHORT;
|
||||
itemSize = 1;
|
||||
break;
|
||||
case CHARACTER:
|
||||
elementType = FieldType.CHAR;
|
||||
itemSize = 1;
|
||||
break;
|
||||
case INTEGER:
|
||||
elementType = FieldType.INT;
|
||||
itemSize = 2;
|
||||
break;
|
||||
case LONG:
|
||||
elementType = FieldType.LONG;
|
||||
itemSize = 3;
|
||||
break;
|
||||
case FLOAT:
|
||||
elementType = FieldType.FLOAT;
|
||||
itemSize = 2;
|
||||
break;
|
||||
case DOUBLE:
|
||||
elementType = FieldType.DOUBLE;
|
||||
offset = 16;
|
||||
itemSize = 3;
|
||||
break;
|
||||
default:
|
||||
itemSize = 1;
|
||||
elementType = FieldType.UNDEFINED;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
elementType = FieldType.OBJECT;
|
||||
itemSize = 2;
|
||||
}
|
||||
return callFrame.getMemory((int) longValue + offset, length << itemSize).then(arrayData -> {
|
||||
var properties = new LinkedHashMap<String, Variable>();
|
||||
for (var i = 0; i < length; ++i) {
|
||||
var name = String.valueOf(i);
|
||||
long longValue;
|
||||
switch (itemSize) {
|
||||
case 0:
|
||||
longValue = arrayData[i];
|
||||
break;
|
||||
case 1:
|
||||
longValue = readShort(arrayData, i * 2);
|
||||
break;
|
||||
case 2:
|
||||
longValue = readInt(arrayData, i * 4);
|
||||
break;
|
||||
default:
|
||||
longValue = readLong(arrayData, i * 8);
|
||||
break;
|
||||
}
|
||||
var value = new WasmValueImpl(debugger, debugInfo, elementType, callFrame, longValue);
|
||||
properties.put(name, new Variable(name, value));
|
||||
}
|
||||
properties.put("length", new Variable("length", new WasmValueImpl(debugger, debugInfo,
|
||||
FieldType.INT, callFrame, length)));
|
||||
addCommonProperties(properties, type);
|
||||
return properties;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void addCommonProperties(Map<String, Variable> properties, TypeLayout cls) {
|
||||
properties.put(CLASS_PROP, new Variable(CLASS_PROP, new WasmValueImpl(debugger, debugInfo,
|
||||
FieldType.OBJECT, callFrame, cls.address())));
|
||||
properties.put(ADDRESS_PROP, new Variable(ADDRESS_PROP, new WasmValueImpl(debugger, debugInfo,
|
||||
FieldType.ADDRESS, callFrame, longValue)));
|
||||
}
|
||||
|
||||
private int readInt(byte[] data, int offset) {
|
||||
return (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8)
|
||||
| ((data[offset + 2] & 0xFF) << 16) | ((data[offset + 3] & 0xFF) << 24);
|
||||
}
|
||||
|
||||
private int readShort(byte[] data, int offset) {
|
||||
return (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8);
|
||||
}
|
||||
|
||||
private long readLong(byte[] data, int offset) {
|
||||
return (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8)
|
||||
| ((data[offset + 2] & 0xFF) << 16) | ((data[offset + 3] & 0xFF) << 24)
|
||||
| ((data[offset + 4] & 0xFFL) << 32) | ((data[offset + 5] & 0xFF) << 40)
|
||||
| ((data[offset + 6] & 0xFFL) << 48) | ((data[offset + 7] & 0xFF) << 56);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Promise<Boolean> hasInnerStructure() {
|
||||
return getCalculatedType().then(type -> {
|
||||
switch (type.kind()) {
|
||||
case CLASS:
|
||||
case ARRAY:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Promise<String> getInstanceId() {
|
||||
return Promise.of(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaScriptValue getOriginalValue() {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -28,4 +28,6 @@ public interface JavaScriptCallFrame {
|
|||
JavaScriptValue getThisVariable();
|
||||
|
||||
JavaScriptValue getClosureVariable();
|
||||
|
||||
Promise<byte[]> getMemory(int address, int count);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ import java.util.Map;
|
|||
import org.teavm.common.Promise;
|
||||
|
||||
public interface JavaScriptValue {
|
||||
String getSimpleRepresentation();
|
||||
|
||||
Promise<String> getRepresentation();
|
||||
|
||||
Promise<String> getClassName();
|
||||
|
|
|
@ -15,10 +15,12 @@
|
|||
*/
|
||||
package org.teavm.chromerdp;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.IntNode;
|
||||
import com.fasterxml.jackson.databind.node.NullNode;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
|
@ -108,6 +110,25 @@ public class ChromeRDPDebugger extends BaseChromeRDPDebugger implements JavaScri
|
|||
});
|
||||
}
|
||||
|
||||
private Promise<Void> injectWasmFunctions(int contextId) {
|
||||
return enableRuntime()
|
||||
.thenAsync(v -> {
|
||||
var compileParams = new CompileScriptCommand();
|
||||
compileParams.expression = ""
|
||||
+ "$dbg_memory = function(buffer, offset, count) { return btoa("
|
||||
+ "String.fromCharCode.apply(null, new Uint8Array(buffer, offset, count))) };\n";
|
||||
compileParams.sourceURL = "file://fake-wasm";
|
||||
compileParams.persistScript = true;
|
||||
compileParams.executionContextId = contextId;
|
||||
return callMethodAsync("Runtime.compileScript", CompileScriptResponse.class, compileParams);
|
||||
})
|
||||
.thenAsync(response -> {
|
||||
var runParams = new RunScriptCommand();
|
||||
runParams.scriptId = response.scriptId;
|
||||
return callMethodAsync("Runtime.runScript", void.class, runParams);
|
||||
});
|
||||
}
|
||||
|
||||
private Promise<Void> enableRuntime() {
|
||||
if (runtimeEnabledPromise == null) {
|
||||
runtimeEnabledPromise = callMethodAsync("Runtime.enable", void.class, null);
|
||||
|
@ -191,7 +212,7 @@ public class ChromeRDPDebugger extends BaseChromeRDPDebugger implements JavaScri
|
|||
}
|
||||
var script = new ChromeRDPScript(this, params.getScriptId(), language, params.getUrl());
|
||||
scripts.put(script.getId(), script);
|
||||
if (params.getUrl().equals("file://fake")) {
|
||||
if (params.getUrl().startsWith("file://fake")) {
|
||||
return Promise.VOID;
|
||||
}
|
||||
for (var listener : getListeners()) {
|
||||
|
@ -199,6 +220,8 @@ public class ChromeRDPDebugger extends BaseChromeRDPDebugger implements JavaScri
|
|||
}
|
||||
if (language == JavaScriptLanguage.JS) {
|
||||
return injectFunctions(params.getExecutionContextId());
|
||||
} else if (language == JavaScriptLanguage.WASM) {
|
||||
return injectWasmFunctions(params.getExecutionContextId());
|
||||
}
|
||||
return Promise.VOID;
|
||||
}
|
||||
|
@ -359,7 +382,7 @@ public class ChromeRDPDebugger extends BaseChromeRDPDebugger implements JavaScri
|
|||
}
|
||||
|
||||
Promise<List<RDPLocalVariable>> getScope(String scopeId) {
|
||||
GetPropertiesCommand params = new GetPropertiesCommand();
|
||||
var params = new GetPropertiesCommand();
|
||||
params.setObjectId(scopeId);
|
||||
params.setOwnProperties(true);
|
||||
|
||||
|
@ -392,6 +415,20 @@ public class ChromeRDPDebugger extends BaseChromeRDPDebugger implements JavaScri
|
|||
});
|
||||
}
|
||||
|
||||
Promise<List<RDPLocalVariable>> getSpecialScope(String scopeId) {
|
||||
var params = new GetPropertiesCommand();
|
||||
params.setObjectId(scopeId);
|
||||
params.setOwnProperties(false);
|
||||
|
||||
return callMethodAsync("Runtime.getProperties", GetPropertiesResponse.class, params)
|
||||
.then(response -> {
|
||||
if (response == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return parseProperties(scopeId, response.getResult(), null);
|
||||
});
|
||||
}
|
||||
|
||||
Promise<String> getClassName(String objectId) {
|
||||
CallFunctionCommand params = new CallFunctionCommand();
|
||||
CallArgumentDTO arg = new CallArgumentDTO();
|
||||
|
@ -407,6 +444,34 @@ public class ChromeRDPDebugger extends BaseChromeRDPDebugger implements JavaScri
|
|||
});
|
||||
}
|
||||
|
||||
Promise<byte[]> getMemory(String objectId, int start, int count) {
|
||||
var params = new CallFunctionCommand();
|
||||
params.setObjectId(objectId);
|
||||
params.setArguments(new CallArgumentDTO[] { objArg(objectId), intArg(start), intArg(count) });
|
||||
params.setFunctionDeclaration("$dbg_memory");
|
||||
|
||||
return callMethodAsync("Runtime.callFunctionOn", CallFunctionResponse.class, params)
|
||||
.then(response -> {
|
||||
var result = response != null ? response.getResult() : null;
|
||||
if (result.getValue() == null) {
|
||||
return null;
|
||||
}
|
||||
return Base64.getDecoder().decode(result.getValue().textValue());
|
||||
});
|
||||
}
|
||||
|
||||
private CallArgumentDTO objArg(String objectId) {
|
||||
var arg = new CallArgumentDTO();
|
||||
arg.setObjectId(objectId);
|
||||
return arg;
|
||||
}
|
||||
|
||||
private CallArgumentDTO intArg(int value) {
|
||||
var arg = new CallArgumentDTO();
|
||||
arg.setValue(new IntNode(value));
|
||||
return arg;
|
||||
}
|
||||
|
||||
Promise<String> getRepresentation(String objectId) {
|
||||
CallFunctionCommand params = new CallFunctionCommand();
|
||||
CallArgumentDTO arg = new CallArgumentDTO();
|
||||
|
@ -495,6 +560,7 @@ public class ChromeRDPDebugger extends BaseChromeRDPDebugger implements JavaScri
|
|||
String scopeId = null;
|
||||
RDPValue thisObject = null;
|
||||
RDPValue closure = null;
|
||||
RDPValue module = null;
|
||||
for (ScopeDTO scope : dto.getScopeChain()) {
|
||||
switch (scope.getType()) {
|
||||
case "local":
|
||||
|
@ -508,10 +574,14 @@ public class ChromeRDPDebugger extends BaseChromeRDPDebugger implements JavaScri
|
|||
thisObject = new RDPValue(this, scope.getObject().getDescription(), scope.getObject().getType(),
|
||||
scope.getObject().getObjectId(), true);
|
||||
break;
|
||||
case "module":
|
||||
module = new RDPValue(this, scope.getObject().getDescription(), scope.getObject().getType(),
|
||||
scope.getObject().getObjectId(), true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new RDPCallFrame(this, dto.getCallFrameId(), map(dto.getLocation()), scopeId,
|
||||
thisObject, closure);
|
||||
thisObject, module, closure);
|
||||
}
|
||||
|
||||
private JavaScriptLocation map(LocationDTO dto) {
|
||||
|
|
|
@ -29,16 +29,19 @@ class RDPCallFrame implements JavaScriptCallFrame {
|
|||
private JavaScriptLocation location;
|
||||
private Promise<Map<String, ? extends JavaScriptVariable>> variables;
|
||||
private JavaScriptValue thisObject;
|
||||
private JavaScriptValue moduleObject;
|
||||
private Promise<JavaScriptValue> memoryObject;
|
||||
private JavaScriptValue closure;
|
||||
private String scopeId;
|
||||
|
||||
RDPCallFrame(ChromeRDPDebugger debugger, String chromeId, JavaScriptLocation location, String scopeId,
|
||||
JavaScriptValue thisObject, JavaScriptValue closure) {
|
||||
JavaScriptValue thisObject, JavaScriptValue moduleObject, JavaScriptValue closure) {
|
||||
this.debugger = debugger;
|
||||
this.chromeId = chromeId;
|
||||
this.location = location;
|
||||
this.scopeId = scopeId;
|
||||
this.thisObject = thisObject;
|
||||
this.moduleObject = moduleObject;
|
||||
this.closure = closure;
|
||||
}
|
||||
|
||||
|
@ -73,4 +76,34 @@ class RDPCallFrame implements JavaScriptCallFrame {
|
|||
public JavaScriptValue getClosureVariable() {
|
||||
return closure;
|
||||
}
|
||||
|
||||
private Promise<JavaScriptValue> getMemoryObject() {
|
||||
if (memoryObject == null) {
|
||||
memoryObject = getPropertyIfExists(moduleObject, "memories")
|
||||
.thenAsync(x -> getPropertyIfExists(x, "$memory"))
|
||||
.thenAsync(x -> getPropertyIfExists(x, "buffer"));
|
||||
}
|
||||
return memoryObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Promise<byte[]> getMemory(int address, int count) {
|
||||
return getMemoryObject().thenAsync(buf -> buf != null
|
||||
? debugger.getMemory(buf.getInstanceId(), address, count)
|
||||
: null);
|
||||
}
|
||||
|
||||
private Promise<JavaScriptValue> getPropertyIfExists(JavaScriptValue value, String name) {
|
||||
if (value == null) {
|
||||
return Promise.of(null);
|
||||
}
|
||||
return debugger.getSpecialScope(value.getInstanceId()).then(properties -> {
|
||||
for (var property : properties) {
|
||||
if (property.getName().equals(name)) {
|
||||
return property.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,11 @@ class RDPValue implements JavaScriptValue {
|
|||
defaultRepresentation = representation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSimpleRepresentation() {
|
||||
return defaultRepresentation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Promise<String> getRepresentation() {
|
||||
if (representation == null) {
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2022 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.chromerdp.data;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ExceptionDetailsDTO {
|
||||
public int exceptionId;
|
||||
public String text;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright 2022 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.chromerdp.messages;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.teavm.chromerdp.data.ExceptionDetailsDTO;
|
||||
import org.teavm.chromerdp.data.RemoteObjectDTO;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class RunScriptResponse {
|
||||
public RemoteObjectDTO result;
|
||||
public ExceptionDetailsDTO exceptionDetails;
|
||||
}
|
Loading…
Reference in New Issue
Block a user