mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2025-01-03 05:44:10 -08:00
Make debugging API asynchronous
This commit is contained in:
parent
66126856a2
commit
75295f50e5
31
core/src/main/java/org/teavm/common/CompletablePromise.java
Normal file
31
core/src/main/java/org/teavm/common/CompletablePromise.java
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 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.common;
|
||||||
|
|
||||||
|
public class CompletablePromise<T> extends Promise<T> {
|
||||||
|
public CompletablePromise() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void complete(T value) {
|
||||||
|
super.complete(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void completeWithError(Throwable e) {
|
||||||
|
super.completeWithError(e);
|
||||||
|
}
|
||||||
|
}
|
319
core/src/main/java/org/teavm/common/Promise.java
Normal file
319
core/src/main/java/org/teavm/common/Promise.java
Normal file
|
@ -0,0 +1,319 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 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.common;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class Promise<T> {
|
||||||
|
public static final Promise<Void> VOID = Promise.of(null);
|
||||||
|
|
||||||
|
private T value;
|
||||||
|
private Promise<T> promise;
|
||||||
|
private Throwable error;
|
||||||
|
private State state = State.PENDING;
|
||||||
|
private List<Then<T>> thenList;
|
||||||
|
private List<Catch> catchList;
|
||||||
|
|
||||||
|
Promise() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Promise<T> of(T value) {
|
||||||
|
Promise<T> promise = new Promise<>();
|
||||||
|
promise.complete(value);
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Promise<?> error(Throwable e) {
|
||||||
|
Promise<?> promise = new Promise<>();
|
||||||
|
promise.completeWithError(e);
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Promise<Void> allVoid(Collection<Promise<Void>> promises) {
|
||||||
|
if (promises.isEmpty()) {
|
||||||
|
return Promise.VOID;
|
||||||
|
}
|
||||||
|
AllVoidFunction all = new AllVoidFunction(promises.size());
|
||||||
|
|
||||||
|
for (Promise<?> promise : promises) {
|
||||||
|
promise.then(all.thenF).catchError(all.catchF);
|
||||||
|
}
|
||||||
|
|
||||||
|
return all.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static <T> Promise<List<T>> all(Collection<Promise<T>> promises) {
|
||||||
|
if (promises.isEmpty()) {
|
||||||
|
return Promise.of(Collections.emptyList());
|
||||||
|
}
|
||||||
|
AllFunction<T> all = new AllFunction<>(promises.size());
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (Promise<T> promise : promises) {
|
||||||
|
promise.then(all.thenF(i++)).catchError(all.catchF);
|
||||||
|
}
|
||||||
|
|
||||||
|
return all.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class AllVoidFunction {
|
||||||
|
Promise<Void> result = new Promise<>();
|
||||||
|
int count;
|
||||||
|
boolean error;
|
||||||
|
|
||||||
|
AllVoidFunction(int count) {
|
||||||
|
this.result = result;
|
||||||
|
this.count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
Function<Object, Void> thenF = v -> {
|
||||||
|
if (!error && --count == 0) {
|
||||||
|
result.complete(null);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
Function<Throwable, Void> catchF = e -> {
|
||||||
|
if (!error) {
|
||||||
|
error = true;
|
||||||
|
result.completeWithError(e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static class AllFunction<T> {
|
||||||
|
Promise<List<T>> result = new Promise<>();
|
||||||
|
List<T> list = new ArrayList<>();
|
||||||
|
int count;
|
||||||
|
boolean error;
|
||||||
|
|
||||||
|
AllFunction(int count) {
|
||||||
|
this.result = result;
|
||||||
|
this.count = count;
|
||||||
|
list.addAll(Collections.nCopies(count, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
Function<T, Void> thenF(int index) {
|
||||||
|
return v -> {
|
||||||
|
if (!error) {
|
||||||
|
list.set(index, v);
|
||||||
|
if (--count == 0) {
|
||||||
|
result.complete(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Function<Throwable, Void> catchF = e -> {
|
||||||
|
if (!error) {
|
||||||
|
error = true;
|
||||||
|
result.completeWithError(e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public <S> Promise<S> then(Function<? super T, S> f) {
|
||||||
|
Promise<S> result = new Promise<>();
|
||||||
|
if (state == State.PENDING || state == State.WAITING_PROMISE) {
|
||||||
|
if (thenList == null) {
|
||||||
|
thenList = new ArrayList<>();
|
||||||
|
thenList.add(new Then<>(f, result, false));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
passValue(f, result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Promise<Void> thenVoid(Consumer<T> f) {
|
||||||
|
return then(r -> {
|
||||||
|
f.accept(r);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public <S> Promise<S> thenAsync(Function<T, Promise<S>> f) {
|
||||||
|
Promise<S> result = new Promise<>();
|
||||||
|
if (state == State.PENDING || state == State.WAITING_PROMISE) {
|
||||||
|
if (thenList == null) {
|
||||||
|
thenList = new ArrayList<>();
|
||||||
|
thenList.add(new Then<>(f, result, true));
|
||||||
|
}
|
||||||
|
} else if (state == State.COMPLETED) {
|
||||||
|
passValueAsync(f, result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <S> Promise<S> catchError(Function<Throwable, S> f) {
|
||||||
|
Promise<S> result = new Promise<>();
|
||||||
|
if (state == State.PENDING || state == State.WAITING_PROMISE) {
|
||||||
|
if (catchList == null) {
|
||||||
|
catchList = new ArrayList<>();
|
||||||
|
catchList.add(new Catch(f, result));
|
||||||
|
}
|
||||||
|
} else if (state == State.ERRORED) {
|
||||||
|
passError(f, result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Promise<Void> catchVoid(Consumer<Throwable> f) {
|
||||||
|
return catchError(e -> {
|
||||||
|
f.accept(e);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
<S> void passValue(Function<? super T, S> f, Promise<? super S> target) {
|
||||||
|
if (state == State.COMPLETED) {
|
||||||
|
S next;
|
||||||
|
try {
|
||||||
|
next = f.apply(value);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
target.completeWithError(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
target.complete(next);
|
||||||
|
} else {
|
||||||
|
target.completeWithError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<S> void passValueAsync(Function<T, Promise<S>> f, Promise<S> target) {
|
||||||
|
if (state == State.COMPLETED) {
|
||||||
|
target.completeAsync(f.apply(value));
|
||||||
|
} else {
|
||||||
|
target.completeWithError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<S> void passError(Function<Throwable, S> f, Promise<? super S> target) {
|
||||||
|
S next;
|
||||||
|
try {
|
||||||
|
next = f.apply(error);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
target.completeWithError(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
target.complete(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
void complete(T value) {
|
||||||
|
if (state != State.PENDING) {
|
||||||
|
throw new IllegalStateException("Already completed");
|
||||||
|
}
|
||||||
|
completeImpl(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void completeAsync(Promise<T> value) {
|
||||||
|
if (state != State.PENDING) {
|
||||||
|
throw new IllegalStateException("Already completed");
|
||||||
|
}
|
||||||
|
state = State.WAITING_PROMISE;
|
||||||
|
|
||||||
|
value
|
||||||
|
.then(result -> {
|
||||||
|
completeImpl(result);
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.catchError(e -> {
|
||||||
|
completeWithErrorImpl(e);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void completeImpl(T value) {
|
||||||
|
state = State.COMPLETED;
|
||||||
|
this.value = value;
|
||||||
|
|
||||||
|
if (thenList != null) {
|
||||||
|
List<Then<T>> list = thenList;
|
||||||
|
thenList = null;
|
||||||
|
for (Then<T> then : list) {
|
||||||
|
if (then.promise) {
|
||||||
|
passValueAsync((Function<T, Promise<Object>>) then.f, (Promise<Object>) then.target);
|
||||||
|
} else {
|
||||||
|
passValue(then.f, (Promise<Object>) then.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catchList = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void completeWithError(Throwable e) {
|
||||||
|
if (state != State.PENDING) {
|
||||||
|
throw new IllegalStateException("Already completed");
|
||||||
|
}
|
||||||
|
completeWithErrorImpl(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void completeWithErrorImpl(Throwable e) {
|
||||||
|
state = State.ERRORED;
|
||||||
|
this.error = e;
|
||||||
|
|
||||||
|
if (catchList != null) {
|
||||||
|
List<Catch> list = catchList;
|
||||||
|
thenList = null;
|
||||||
|
for (Catch c : list) {
|
||||||
|
passError(c.f, (Promise<Object>) c.target);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
thenList = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
PENDING,
|
||||||
|
WAITING_PROMISE,
|
||||||
|
COMPLETED,
|
||||||
|
ERRORED
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Then<T> {
|
||||||
|
Function<? super T, ?> f;
|
||||||
|
Promise<?> target;
|
||||||
|
boolean promise;
|
||||||
|
|
||||||
|
Then(Function<? super T, ?> f, Promise<?> target, boolean promise) {
|
||||||
|
this.f = f;
|
||||||
|
this.target = target;
|
||||||
|
this.promise = promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Catch {
|
||||||
|
Function<Throwable, ?> f;
|
||||||
|
Promise<?> target;
|
||||||
|
|
||||||
|
Catch(Function<Throwable, ?> f, Promise<?> target) {
|
||||||
|
this.f = f;
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,8 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package org.teavm.debugging;
|
package org.teavm.debugging;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import org.teavm.common.Promise;
|
||||||
|
import org.teavm.debugging.information.DebugInformation;
|
||||||
import org.teavm.debugging.information.SourceLocation;
|
import org.teavm.debugging.information.SourceLocation;
|
||||||
import org.teavm.debugging.javascript.JavaScriptCallFrame;
|
import org.teavm.debugging.javascript.JavaScriptCallFrame;
|
||||||
import org.teavm.debugging.javascript.JavaScriptLocation;
|
import org.teavm.debugging.javascript.JavaScriptLocation;
|
||||||
|
@ -27,15 +28,16 @@ public class CallFrame {
|
||||||
private JavaScriptCallFrame originalCallFrame;
|
private JavaScriptCallFrame originalCallFrame;
|
||||||
private SourceLocation location;
|
private SourceLocation location;
|
||||||
private MethodReference method;
|
private MethodReference method;
|
||||||
private Map<String, Variable> variables;
|
private Promise<Map<String, Variable>> variables;
|
||||||
|
private DebugInformation debugInformation;
|
||||||
|
|
||||||
CallFrame(Debugger debugger, JavaScriptCallFrame originalFrame, SourceLocation location, MethodReference method,
|
CallFrame(Debugger debugger, JavaScriptCallFrame originalFrame, SourceLocation location, MethodReference method,
|
||||||
Map<String, Variable> variables) {
|
DebugInformation debugInformation) {
|
||||||
this.debugger = debugger;
|
this.debugger = debugger;
|
||||||
this.originalCallFrame = originalFrame;
|
this.originalCallFrame = originalFrame;
|
||||||
this.location = location;
|
this.location = location;
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.variables = Collections.unmodifiableMap(variables);
|
this.debugInformation = debugInformation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Debugger getDebugger() {
|
public Debugger getDebugger() {
|
||||||
|
@ -58,7 +60,10 @@ public class CallFrame {
|
||||||
return method;
|
return method;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Variable> getVariables() {
|
public Promise<Map<String, Variable>> getVariables() {
|
||||||
|
if (variables == null) {
|
||||||
|
variables = debugger.createVariables(originalCallFrame, debugInformation);
|
||||||
|
}
|
||||||
return variables;
|
return variables;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,28 +15,45 @@
|
||||||
*/
|
*/
|
||||||
package org.teavm.debugging;
|
package org.teavm.debugging;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.Collection;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.Collections;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.HashMap;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.HashSet;
|
||||||
import org.teavm.debugging.information.*;
|
import java.util.LinkedHashSet;
|
||||||
import org.teavm.debugging.javascript.*;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.teavm.common.Promise;
|
||||||
|
import org.teavm.debugging.information.DebugInformation;
|
||||||
|
import org.teavm.debugging.information.DebugInformationProvider;
|
||||||
|
import org.teavm.debugging.information.DebuggerCallSite;
|
||||||
|
import org.teavm.debugging.information.DebuggerCallSiteVisitor;
|
||||||
|
import org.teavm.debugging.information.DebuggerStaticCallSite;
|
||||||
|
import org.teavm.debugging.information.DebuggerVirtualCallSite;
|
||||||
|
import org.teavm.debugging.information.GeneratedLocation;
|
||||||
|
import org.teavm.debugging.information.SourceLocation;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptBreakpoint;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptCallFrame;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptDebugger;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptDebuggerListener;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptLocation;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptVariable;
|
||||||
import org.teavm.model.MethodReference;
|
import org.teavm.model.MethodReference;
|
||||||
|
|
||||||
public class Debugger {
|
public class Debugger {
|
||||||
private static final Object dummyObject = new Object();
|
private Set<DebuggerListener> listeners = new LinkedHashSet<>();
|
||||||
private ConcurrentMap<DebuggerListener, Object> listeners = new ConcurrentHashMap<>();
|
|
||||||
private JavaScriptDebugger javaScriptDebugger;
|
private JavaScriptDebugger javaScriptDebugger;
|
||||||
private DebugInformationProvider debugInformationProvider;
|
private DebugInformationProvider debugInformationProvider;
|
||||||
private BlockingQueue<JavaScriptBreakpoint> temporaryBreakpoints = new LinkedBlockingQueue<>();
|
private List<JavaScriptBreakpoint> temporaryBreakpoints = new ArrayList<>();
|
||||||
private ConcurrentMap<String, DebugInformation> debugInformationMap = new ConcurrentHashMap<>();
|
private Map<String, DebugInformation> debugInformationMap = new HashMap<>();
|
||||||
private ConcurrentMap<String, ConcurrentMap<DebugInformation, Object>> debugInformationFileMap =
|
private Map<String, Set<DebugInformation>> debugInformationFileMap = new HashMap<>();
|
||||||
new ConcurrentHashMap<>();
|
private Map<DebugInformation, String> scriptMap = new HashMap<>();
|
||||||
private ConcurrentMap<DebugInformation, String> scriptMap = new ConcurrentHashMap<>();
|
private Map<JavaScriptBreakpoint, Breakpoint> breakpointMap = new HashMap<>();
|
||||||
private final ConcurrentMap<JavaScriptBreakpoint, Breakpoint> breakpointMap = new ConcurrentHashMap<>();
|
private Set<Breakpoint> breakpoints = new LinkedHashSet<>();
|
||||||
private final ConcurrentMap<Breakpoint, Object> breakpoints = new ConcurrentHashMap<>();
|
private Set<? extends Breakpoint> readonlyBreakpoints = Collections.unmodifiableSet(breakpoints);
|
||||||
private volatile CallFrame[] callStack;
|
private CallFrame[] callStack;
|
||||||
|
private Set<String> scriptNames = new LinkedHashSet<>();
|
||||||
|
|
||||||
public Debugger(JavaScriptDebugger javaScriptDebugger, DebugInformationProvider debugInformationProvider) {
|
public Debugger(JavaScriptDebugger javaScriptDebugger, DebugInformationProvider debugInformationProvider) {
|
||||||
this.javaScriptDebugger = javaScriptDebugger;
|
this.javaScriptDebugger = javaScriptDebugger;
|
||||||
|
@ -49,52 +66,46 @@ public class Debugger {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addListener(DebuggerListener listener) {
|
public void addListener(DebuggerListener listener) {
|
||||||
listeners.put(listener, dummyObject);
|
listeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeListener(DebuggerListener listener) {
|
public void removeListener(DebuggerListener listener) {
|
||||||
listeners.remove(listener);
|
listeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void suspend() {
|
public Promise<Void> suspend() {
|
||||||
javaScriptDebugger.suspend();
|
return javaScriptDebugger.suspend();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resume() {
|
public Promise<Void> resume() {
|
||||||
javaScriptDebugger.resume();
|
return javaScriptDebugger.resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stepInto() {
|
public Promise<Void> stepInto() {
|
||||||
step(true);
|
return step(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stepOut() {
|
public Promise<Void> stepOut() {
|
||||||
javaScriptDebugger.stepOut();
|
return javaScriptDebugger.stepOut();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stepOver() {
|
public Promise<Void> stepOver() {
|
||||||
step(false);
|
return step(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void jsStep(boolean enterMethod) {
|
private Promise<Void> jsStep(boolean enterMethod) {
|
||||||
if (enterMethod) {
|
return enterMethod ? javaScriptDebugger.stepInto() : javaScriptDebugger.stepOver();
|
||||||
javaScriptDebugger.stepInto();
|
|
||||||
} else {
|
|
||||||
javaScriptDebugger.stepOver();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void step(boolean enterMethod) {
|
private Promise<Void> step(boolean enterMethod) {
|
||||||
CallFrame[] callStack = getCallStack();
|
CallFrame[] callStack = getCallStack();
|
||||||
if (callStack == null || callStack.length == 0) {
|
if (callStack == null || callStack.length == 0) {
|
||||||
jsStep(enterMethod);
|
return jsStep(enterMethod);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
CallFrame recentFrame = callStack[0];
|
CallFrame recentFrame = callStack[0];
|
||||||
if (recentFrame.getLocation() == null || recentFrame.getLocation().getFileName() == null
|
if (recentFrame.getLocation() == null || recentFrame.getLocation().getFileName() == null
|
||||||
|| recentFrame.getLocation().getLine() < 0) {
|
|| recentFrame.getLocation().getLine() < 0) {
|
||||||
jsStep(enterMethod);
|
return jsStep(enterMethod);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
Set<JavaScriptLocation> successors = new HashSet<>();
|
Set<JavaScriptLocation> successors = new HashSet<>();
|
||||||
for (CallFrame frame : callStack) {
|
for (CallFrame frame : callStack) {
|
||||||
|
@ -120,10 +131,13 @@ public class Debugger {
|
||||||
}
|
}
|
||||||
enterMethod = true;
|
enterMethod = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Promise<Void>> jsBreakpointPromises = new ArrayList<>();
|
||||||
for (JavaScriptLocation successor : successors) {
|
for (JavaScriptLocation successor : successors) {
|
||||||
temporaryBreakpoints.add(javaScriptDebugger.createBreakpoint(successor));
|
jsBreakpointPromises.add(javaScriptDebugger.createBreakpoint(successor)
|
||||||
|
.thenVoid(temporaryBreakpoints::add));
|
||||||
}
|
}
|
||||||
javaScriptDebugger.resume();
|
return Promise.allVoid(jsBreakpointPromises).thenAsync(v -> javaScriptDebugger.resume());
|
||||||
}
|
}
|
||||||
|
|
||||||
static class CallSiteSuccessorFinder implements DebuggerCallSiteVisitor {
|
static class CallSiteSuccessorFinder implements DebuggerCallSiteVisitor {
|
||||||
|
@ -184,37 +198,36 @@ public class Debugger {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<DebugInformation> debugInformationBySource(String sourceFile) {
|
private List<DebugInformation> debugInformationBySource(String sourceFile) {
|
||||||
Map<DebugInformation, Object> list = debugInformationFileMap.get(sourceFile);
|
Set<DebugInformation> list = debugInformationFileMap.get(sourceFile);
|
||||||
return list != null ? new ArrayList<>(list.keySet()) : Collections.emptyList();
|
return list != null ? new ArrayList<>(list) : Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void continueToLocation(SourceLocation location) {
|
public Promise<Void> continueToLocation(SourceLocation location) {
|
||||||
continueToLocation(location.getFileName(), location.getLine());
|
return continueToLocation(location.getFileName(), location.getLine());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void continueToLocation(String fileName, int line) {
|
public Promise<Void> continueToLocation(String fileName, int line) {
|
||||||
if (!javaScriptDebugger.isSuspended()) {
|
if (!javaScriptDebugger.isSuspended()) {
|
||||||
return;
|
return Promise.VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Promise<Void>> promises = new ArrayList<>();
|
||||||
for (DebugInformation debugInformation : debugInformationBySource(fileName)) {
|
for (DebugInformation debugInformation : debugInformationBySource(fileName)) {
|
||||||
Collection<GeneratedLocation> locations = debugInformation.getGeneratedLocations(fileName, line);
|
Collection<GeneratedLocation> locations = debugInformation.getGeneratedLocations(fileName, line);
|
||||||
for (GeneratedLocation location : locations) {
|
for (GeneratedLocation location : locations) {
|
||||||
JavaScriptLocation jsLocation = new JavaScriptLocation(scriptMap.get(debugInformation),
|
JavaScriptLocation jsLocation = new JavaScriptLocation(scriptMap.get(debugInformation),
|
||||||
location.getLine(), location.getColumn());
|
location.getLine(), location.getColumn());
|
||||||
JavaScriptBreakpoint jsBreakpoint = javaScriptDebugger.createBreakpoint(jsLocation);
|
promises.add(javaScriptDebugger.createBreakpoint(jsLocation).thenVoid(temporaryBreakpoints::add));
|
||||||
if (jsBreakpoint != null) {
|
|
||||||
temporaryBreakpoints.add(jsBreakpoint);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return Promise.allVoid(promises).thenAsync(v -> javaScriptDebugger.resume());
|
||||||
javaScriptDebugger.resume();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSuspended() {
|
public boolean isSuspended() {
|
||||||
return javaScriptDebugger.isSuspended();
|
return javaScriptDebugger.isSuspended();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Breakpoint createBreakpoint(String file, int line) {
|
public Promise<Breakpoint> createBreakpoint(String file, int line) {
|
||||||
return createBreakpoint(new SourceLocation(file, line));
|
return createBreakpoint(new SourceLocation(file, line));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,28 +235,30 @@ public class Debugger {
|
||||||
return debugInformationFileMap.keySet();
|
return debugInformationFileMap.keySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Breakpoint createBreakpoint(SourceLocation location) {
|
public Promise<Breakpoint> createBreakpoint(SourceLocation location) {
|
||||||
synchronized (breakpointMap) {
|
|
||||||
Breakpoint breakpoint = new Breakpoint(this, location);
|
Breakpoint breakpoint = new Breakpoint(this, location);
|
||||||
breakpoints.put(breakpoint, dummyObject);
|
breakpoints.add(breakpoint);
|
||||||
updateInternalBreakpoints(breakpoint);
|
return updateInternalBreakpoints(breakpoint).then(v -> {
|
||||||
updateBreakpointStatus(breakpoint, false);
|
updateBreakpointStatus(breakpoint, false);
|
||||||
return breakpoint;
|
return breakpoint;
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<Breakpoint> getBreakpoints() {
|
public Set<? extends Breakpoint> getBreakpoints() {
|
||||||
return new HashSet<>(breakpoints.keySet());
|
return readonlyBreakpoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateInternalBreakpoints(Breakpoint breakpoint) {
|
private Promise<Void> updateInternalBreakpoints(Breakpoint breakpoint) {
|
||||||
if (breakpoint.isDestroyed()) {
|
if (breakpoint.isDestroyed()) {
|
||||||
return;
|
return Promise.VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Promise<Void>> promises = new ArrayList<>();
|
||||||
for (JavaScriptBreakpoint jsBreakpoint : breakpoint.jsBreakpoints) {
|
for (JavaScriptBreakpoint jsBreakpoint : breakpoint.jsBreakpoints) {
|
||||||
breakpointMap.remove(jsBreakpoint);
|
breakpointMap.remove(jsBreakpoint);
|
||||||
jsBreakpoint.destroy();
|
promises.add(jsBreakpoint.destroy());
|
||||||
}
|
}
|
||||||
|
|
||||||
List<JavaScriptBreakpoint> jsBreakpoints = new ArrayList<>();
|
List<JavaScriptBreakpoint> jsBreakpoints = new ArrayList<>();
|
||||||
SourceLocation location = breakpoint.getLocation();
|
SourceLocation location = breakpoint.getLocation();
|
||||||
for (DebugInformation debugInformation : debugInformationBySource(location.getFileName())) {
|
for (DebugInformation debugInformation : debugInformationBySource(location.getFileName())) {
|
||||||
|
@ -251,16 +266,19 @@ public class Debugger {
|
||||||
for (GeneratedLocation genLocation : locations) {
|
for (GeneratedLocation genLocation : locations) {
|
||||||
JavaScriptLocation jsLocation = new JavaScriptLocation(scriptMap.get(debugInformation),
|
JavaScriptLocation jsLocation = new JavaScriptLocation(scriptMap.get(debugInformation),
|
||||||
genLocation.getLine(), genLocation.getColumn());
|
genLocation.getLine(), genLocation.getColumn());
|
||||||
JavaScriptBreakpoint jsBreakpoint = javaScriptDebugger.createBreakpoint(jsLocation);
|
promises.add(javaScriptDebugger.createBreakpoint(jsLocation).thenVoid(jsBreakpoint -> {
|
||||||
jsBreakpoints.add(jsBreakpoint);
|
jsBreakpoints.add(jsBreakpoint);
|
||||||
breakpointMap.put(jsBreakpoint, breakpoint);
|
breakpointMap.put(jsBreakpoint, breakpoint);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
breakpoint.jsBreakpoints = jsBreakpoints;
|
breakpoint.jsBreakpoints = jsBreakpoints;
|
||||||
|
|
||||||
|
return Promise.allVoid(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DebuggerListener[] getListeners() {
|
private DebuggerListener[] getListeners() {
|
||||||
return listeners.keySet().toArray(new DebuggerListener[0]);
|
return listeners.toArray(new DebuggerListener[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateBreakpointStatus(Breakpoint breakpoint, boolean fireEvent) {
|
private void updateBreakpointStatus(Breakpoint breakpoint, boolean fireEvent) {
|
||||||
|
@ -302,9 +320,7 @@ public class Debugger {
|
||||||
MethodReference method = !empty ? debugInformation.getMethodAt(jsFrame.getLocation().getLine(),
|
MethodReference method = !empty ? debugInformation.getMethodAt(jsFrame.getLocation().getLine(),
|
||||||
jsFrame.getLocation().getColumn()) : null;
|
jsFrame.getLocation().getColumn()) : null;
|
||||||
if (!empty || !wasEmpty) {
|
if (!empty || !wasEmpty) {
|
||||||
VariableMap vars = new VariableMap(jsFrame.getVariables(), this, debugInformation,
|
frames.add(new CallFrame(this, jsFrame, loc, method, debugInformation));
|
||||||
jsFrame.getLocation());
|
|
||||||
frames.add(new CallFrame(this, jsFrame, loc, method, vars));
|
|
||||||
}
|
}
|
||||||
wasEmpty = empty;
|
wasEmpty = empty;
|
||||||
}
|
}
|
||||||
|
@ -313,42 +329,56 @@ public class Debugger {
|
||||||
return callStack.clone();
|
return callStack.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Promise<Map<String, Variable>> createVariables(JavaScriptCallFrame jsFrame, DebugInformation debugInformation) {
|
||||||
|
return jsFrame.getVariables().then(jsVariables -> {
|
||||||
|
Map<String, Variable> vars = new HashMap<>();
|
||||||
|
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());
|
||||||
|
for (String name : names) {
|
||||||
|
if (name == null) {
|
||||||
|
name = "js:" + jsVar.getName();
|
||||||
|
}
|
||||||
|
vars.put(name, new Variable(name, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableMap(vars);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void addScript(String name) {
|
private void addScript(String name) {
|
||||||
|
if (!name.isEmpty()) {
|
||||||
|
scriptNames.add(name);
|
||||||
|
}
|
||||||
if (debugInformationMap.containsKey(name)) {
|
if (debugInformationMap.containsKey(name)) {
|
||||||
updateBreakpoints();
|
updateBreakpoints();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DebugInformation debugInfo = debugInformationProvider.getDebugInformation(name);
|
DebugInformation debugInfo = debugInformationProvider.getDebugInformation(name);
|
||||||
if (debugInfo == null) {
|
if (debugInfo == null) {
|
||||||
updateBreakpoints();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (debugInformationMap.putIfAbsent(name, debugInfo) != null) {
|
|
||||||
updateBreakpoints();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
debugInformationMap.put(name, debugInfo);
|
||||||
for (String sourceFile : debugInfo.getFilesNames()) {
|
for (String sourceFile : debugInfo.getFilesNames()) {
|
||||||
ConcurrentMap<DebugInformation, Object> list = debugInformationFileMap.get(sourceFile);
|
Set<DebugInformation> list = debugInformationFileMap.get(sourceFile);
|
||||||
if (list == null) {
|
if (list == null) {
|
||||||
list = new ConcurrentHashMap<>();
|
list = new HashSet<>();
|
||||||
ConcurrentMap<DebugInformation, Object> existing = debugInformationFileMap.putIfAbsent(
|
debugInformationFileMap.put(sourceFile, list);
|
||||||
sourceFile, list);
|
|
||||||
if (existing != null) {
|
|
||||||
list = existing;
|
|
||||||
}
|
}
|
||||||
}
|
list.add(debugInfo);
|
||||||
list.put(debugInfo, dummyObject);
|
|
||||||
}
|
}
|
||||||
scriptMap.put(debugInfo, name);
|
scriptMap.put(debugInfo, name);
|
||||||
updateBreakpoints();
|
updateBreakpoints();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateBreakpoints() {
|
public Set<? extends String> getScriptNames() {
|
||||||
synchronized (breakpointMap) {
|
return scriptNames;
|
||||||
for (Breakpoint breakpoint : breakpoints.keySet()) {
|
|
||||||
updateInternalBreakpoints(breakpoint);
|
|
||||||
updateBreakpointStatus(breakpoint, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateBreakpoints() {
|
||||||
|
for (Breakpoint breakpoint : breakpoints) {
|
||||||
|
updateInternalBreakpoints(breakpoint).thenVoid(v -> updateBreakpointStatus(breakpoint, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,22 +400,33 @@ public class Debugger {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fireResumed() {
|
private void fireResumed() {
|
||||||
List<JavaScriptBreakpoint> temporaryBreakpoints = new ArrayList<>();
|
|
||||||
this.temporaryBreakpoints.drainTo(temporaryBreakpoints);
|
|
||||||
for (JavaScriptBreakpoint jsBreakpoint : temporaryBreakpoints) {
|
|
||||||
jsBreakpoint.destroy();
|
|
||||||
}
|
|
||||||
for (DebuggerListener listener : getListeners()) {
|
for (DebuggerListener listener : getListeners()) {
|
||||||
listener.resumed();
|
listener.resumed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fireAttached() {
|
private void firePaused(JavaScriptBreakpoint breakpoint) {
|
||||||
synchronized (breakpointMap) {
|
List<JavaScriptBreakpoint> temporaryBreakpoints = new ArrayList<>(this.temporaryBreakpoints);
|
||||||
for (Breakpoint breakpoint : breakpoints.keySet()) {
|
this.temporaryBreakpoints.clear();
|
||||||
updateInternalBreakpoints(breakpoint);
|
List<Promise<Void>> promises = new ArrayList<>();
|
||||||
updateBreakpointStatus(breakpoint, false);
|
for (JavaScriptBreakpoint jsBreakpoint : temporaryBreakpoints) {
|
||||||
|
promises.add(jsBreakpoint.destroy());
|
||||||
}
|
}
|
||||||
|
callStack = null;
|
||||||
|
Promise.allVoid(promises).thenVoid(v -> {
|
||||||
|
Breakpoint javaBreakpoint = null;
|
||||||
|
if (breakpoint != null && !temporaryBreakpoints.contains(breakpoint)) {
|
||||||
|
javaBreakpoint = breakpointMap.get(breakpoint);
|
||||||
|
}
|
||||||
|
for (DebuggerListener listener : getListeners()) {
|
||||||
|
listener.paused(javaBreakpoint);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fireAttached() {
|
||||||
|
for (Breakpoint breakpoint : breakpoints) {
|
||||||
|
updateInternalBreakpoints(breakpoint).thenVoid(v -> updateBreakpointStatus(breakpoint, false));
|
||||||
}
|
}
|
||||||
for (DebuggerListener listener : getListeners()) {
|
for (DebuggerListener listener : getListeners()) {
|
||||||
listener.attached();
|
listener.attached();
|
||||||
|
@ -393,7 +434,7 @@ public class Debugger {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fireDetached() {
|
private void fireDetached() {
|
||||||
for (Breakpoint breakpoint : breakpoints.keySet()) {
|
for (Breakpoint breakpoint : breakpoints) {
|
||||||
updateBreakpointStatus(breakpoint, false);
|
updateBreakpointStatus(breakpoint, false);
|
||||||
}
|
}
|
||||||
for (DebuggerListener listener : getListeners()) {
|
for (DebuggerListener listener : getListeners()) {
|
||||||
|
@ -434,14 +475,7 @@ public class Debugger {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void paused(JavaScriptBreakpoint breakpoint) {
|
public void paused(JavaScriptBreakpoint breakpoint) {
|
||||||
callStack = null;
|
firePaused(breakpoint);
|
||||||
Breakpoint javaBreakpoint = null;
|
|
||||||
if (breakpoint != null && !temporaryBreakpoints.contains(breakpoint)) {
|
|
||||||
javaBreakpoint = breakpointMap.get(breakpoint);
|
|
||||||
}
|
|
||||||
for (DebuggerListener listener : getListeners()) {
|
|
||||||
listener.paused(javaBreakpoint);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2014 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.AbstractMap;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import org.teavm.debugging.information.DebugInformation;
|
|
||||||
import org.teavm.debugging.javascript.JavaScriptVariable;
|
|
||||||
|
|
||||||
class PropertyMap extends AbstractMap<String, Variable> {
|
|
||||||
private String className;
|
|
||||||
private AtomicReference<Map<String, Variable>> backingMap = new AtomicReference<>();
|
|
||||||
private Map<String, JavaScriptVariable> jsVariables;
|
|
||||||
private Debugger debugger;
|
|
||||||
private DebugInformation debugInformation;
|
|
||||||
|
|
||||||
public PropertyMap(String className, Map<String, JavaScriptVariable> jsVariables, Debugger debugger,
|
|
||||||
DebugInformation debugInformation) {
|
|
||||||
this.className = className;
|
|
||||||
this.jsVariables = jsVariables;
|
|
||||||
this.debugger = debugger;
|
|
||||||
this.debugInformation = debugInformation;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
updateBackingMap();
|
|
||||||
return backingMap.get().size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Variable get(Object key) {
|
|
||||||
updateBackingMap();
|
|
||||||
return backingMap.get().get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<Entry<String, Variable>> entrySet() {
|
|
||||||
updateBackingMap();
|
|
||||||
return backingMap.get().entrySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateBackingMap() {
|
|
||||||
if (backingMap.get() != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Map<String, Variable> vars = new HashMap<>();
|
|
||||||
for (Map.Entry<String, JavaScriptVariable> entry : jsVariables.entrySet()) {
|
|
||||||
JavaScriptVariable jsVar = entry.getValue();
|
|
||||||
String name;
|
|
||||||
if (className.endsWith("[]")) {
|
|
||||||
if (entry.getKey().equals("data")) {
|
|
||||||
name = entry.getKey();
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else if (isNumeric(entry.getKey())) {
|
|
||||||
name = entry.getKey();
|
|
||||||
} else {
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
backingMap.compareAndSet(null, vars);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isNumeric(String str) {
|
|
||||||
for (int i = 0; i < str.length(); ++i) {
|
|
||||||
char c = str.charAt(i);
|
|
||||||
if (c < '0' || c > '9') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,16 +15,19 @@
|
||||||
*/
|
*/
|
||||||
package org.teavm.debugging;
|
package org.teavm.debugging;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import org.teavm.common.Promise;
|
||||||
import org.teavm.debugging.information.DebugInformation;
|
import org.teavm.debugging.information.DebugInformation;
|
||||||
import org.teavm.debugging.javascript.JavaScriptValue;
|
import org.teavm.debugging.javascript.JavaScriptValue;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptVariable;
|
||||||
|
|
||||||
public class Value {
|
public class Value {
|
||||||
private Debugger debugger;
|
private Debugger debugger;
|
||||||
private DebugInformation debugInformation;
|
private DebugInformation debugInformation;
|
||||||
private JavaScriptValue jsValue;
|
private JavaScriptValue jsValue;
|
||||||
private AtomicReference<PropertyMap> properties = new AtomicReference<>();
|
private Promise<Map<String, Variable>> properties;
|
||||||
|
private Promise<String> type;
|
||||||
|
|
||||||
Value(Debugger debugger, DebugInformation debugInformation, JavaScriptValue jsValue) {
|
Value(Debugger debugger, DebugInformation debugInformation, JavaScriptValue jsValue) {
|
||||||
this.debugger = debugger;
|
this.debugger = debugger;
|
||||||
|
@ -32,30 +35,66 @@ public class Value {
|
||||||
this.jsValue = jsValue;
|
this.jsValue = jsValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRepresentation() {
|
private static boolean isNumeric(String str) {
|
||||||
|
for (int i = 0; i < str.length(); ++i) {
|
||||||
|
char c = str.charAt(i);
|
||||||
|
if (c < '0' || c > '9') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Promise<String> getRepresentation() {
|
||||||
return jsValue.getRepresentation();
|
return jsValue.getRepresentation();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getType() {
|
public Promise<String> getType() {
|
||||||
String className = jsValue.getClassName();
|
if (type == null) {
|
||||||
|
type = jsValue.getClassName().then(className -> {
|
||||||
if (className.startsWith("a/")) {
|
if (className.startsWith("a/")) {
|
||||||
className = className.substring(2);
|
className = className.substring(2);
|
||||||
String javaClassName = debugInformation.getClassNameByJsName(className);
|
String javaClassName = debugInformation.getClassNameByJsName(className);
|
||||||
if (javaClassName != null) {
|
if (javaClassName != null) {
|
||||||
className = javaClassName;
|
className = javaClassName;
|
||||||
}
|
}
|
||||||
} else if (className.startsWith("@")) {
|
|
||||||
className = className.substring(1);
|
|
||||||
}
|
}
|
||||||
return className;
|
return className;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Variable> getProperties() {
|
public Promise<Map<String, Variable>> getProperties() {
|
||||||
if (properties.get() == null) {
|
if (properties == null) {
|
||||||
properties.compareAndSet(null, new PropertyMap(jsValue.getClassName(), jsValue.getProperties(), debugger,
|
properties = jsValue.getProperties().thenAsync(jsVariables -> {
|
||||||
debugInformation));
|
return jsValue.getClassName().then(className -> {
|
||||||
|
Map<String, Variable> vars = new HashMap<>();
|
||||||
|
for (Map.Entry<String, ? extends JavaScriptVariable> entry : jsVariables.entrySet()) {
|
||||||
|
JavaScriptVariable jsVar = entry.getValue();
|
||||||
|
String name;
|
||||||
|
if (className.endsWith("[]")) {
|
||||||
|
if (entry.getKey().equals("data")) {
|
||||||
|
name = entry.getKey();
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
return properties.get();
|
} else if (isNumeric(entry.getKey())) {
|
||||||
|
name = entry.getKey();
|
||||||
|
} else {
|
||||||
|
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 vars;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasInnerStructure() {
|
public boolean hasInnerStructure() {
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2014 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.AbstractMap;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import org.teavm.debugging.information.DebugInformation;
|
|
||||||
import org.teavm.debugging.javascript.JavaScriptLocation;
|
|
||||||
import org.teavm.debugging.javascript.JavaScriptVariable;
|
|
||||||
|
|
||||||
class VariableMap extends AbstractMap<String, Variable> {
|
|
||||||
private AtomicReference<Map<String, Variable>> backingMap = new AtomicReference<>();
|
|
||||||
private Map<String, JavaScriptVariable> jsVariables;
|
|
||||||
private Debugger debugger;
|
|
||||||
private DebugInformation debugInformation;
|
|
||||||
private JavaScriptLocation location;
|
|
||||||
|
|
||||||
public VariableMap(Map<String, JavaScriptVariable> jsVariables, Debugger debugger,
|
|
||||||
DebugInformation debugInformation, JavaScriptLocation location) {
|
|
||||||
this.jsVariables = jsVariables;
|
|
||||||
this.debugger = debugger;
|
|
||||||
this.debugInformation = debugInformation;
|
|
||||||
this.location = location;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<Entry<String, Variable>> entrySet() {
|
|
||||||
updateBackingMap();
|
|
||||||
return backingMap.get().entrySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Variable get(Object key) {
|
|
||||||
updateBackingMap();
|
|
||||||
return backingMap.get().get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
updateBackingMap();
|
|
||||||
return backingMap.get().size();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateBackingMap() {
|
|
||||||
if (backingMap.get() != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Map<String, Variable> vars = new HashMap<>();
|
|
||||||
for (Map.Entry<String, JavaScriptVariable> entry : jsVariables.entrySet()) {
|
|
||||||
JavaScriptVariable jsVar = entry.getValue();
|
|
||||||
String[] names = debugger.mapVariable(entry.getKey(), location);
|
|
||||||
Value value = new Value(debugger, debugInformation, jsVar.getValue());
|
|
||||||
for (String name : names) {
|
|
||||||
if (name == null) {
|
|
||||||
name = "js:" + jsVar.getName();
|
|
||||||
}
|
|
||||||
vars.put(name, new Variable(name, value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
backingMap.compareAndSet(null, vars);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,10 +15,12 @@
|
||||||
*/
|
*/
|
||||||
package org.teavm.debugging.javascript;
|
package org.teavm.debugging.javascript;
|
||||||
|
|
||||||
|
import org.teavm.common.Promise;
|
||||||
|
|
||||||
public interface JavaScriptBreakpoint {
|
public interface JavaScriptBreakpoint {
|
||||||
JavaScriptLocation getLocation();
|
JavaScriptLocation getLocation();
|
||||||
|
|
||||||
boolean isValid();
|
boolean isValid();
|
||||||
|
|
||||||
void destroy();
|
Promise<Void> destroy();
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,14 @@
|
||||||
package org.teavm.debugging.javascript;
|
package org.teavm.debugging.javascript;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import org.teavm.common.Promise;
|
||||||
|
|
||||||
public interface JavaScriptCallFrame {
|
public interface JavaScriptCallFrame {
|
||||||
JavaScriptDebugger getDebugger();
|
JavaScriptDebugger getDebugger();
|
||||||
|
|
||||||
JavaScriptLocation getLocation();
|
JavaScriptLocation getLocation();
|
||||||
|
|
||||||
Map<String, JavaScriptVariable> getVariables();
|
Promise<Map<String, ? extends JavaScriptVariable>> getVariables();
|
||||||
|
|
||||||
JavaScriptValue getThisVariable();
|
JavaScriptValue getThisVariable();
|
||||||
|
|
||||||
|
|
|
@ -15,22 +15,24 @@
|
||||||
*/
|
*/
|
||||||
package org.teavm.debugging.javascript;
|
package org.teavm.debugging.javascript;
|
||||||
|
|
||||||
|
import org.teavm.common.Promise;
|
||||||
|
|
||||||
public interface JavaScriptDebugger {
|
public interface JavaScriptDebugger {
|
||||||
void addListener(JavaScriptDebuggerListener listener);
|
void addListener(JavaScriptDebuggerListener listener);
|
||||||
|
|
||||||
void removeListener(JavaScriptDebuggerListener listener);
|
void removeListener(JavaScriptDebuggerListener listener);
|
||||||
|
|
||||||
void suspend();
|
Promise<Void> suspend();
|
||||||
|
|
||||||
void resume();
|
Promise<Void> resume();
|
||||||
|
|
||||||
void stepInto();
|
Promise<Void> stepInto();
|
||||||
|
|
||||||
void stepOut();
|
Promise<Void> stepOut();
|
||||||
|
|
||||||
void stepOver();
|
Promise<Void> stepOver();
|
||||||
|
|
||||||
void continueToLocation(JavaScriptLocation location);
|
Promise<Void> continueToLocation(JavaScriptLocation location);
|
||||||
|
|
||||||
boolean isSuspended();
|
boolean isSuspended();
|
||||||
|
|
||||||
|
@ -40,5 +42,5 @@ public interface JavaScriptDebugger {
|
||||||
|
|
||||||
JavaScriptCallFrame[] getCallStack();
|
JavaScriptCallFrame[] getCallStack();
|
||||||
|
|
||||||
JavaScriptBreakpoint createBreakpoint(JavaScriptLocation location);
|
Promise<JavaScriptBreakpoint> createBreakpoint(JavaScriptLocation location);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,14 @@
|
||||||
package org.teavm.debugging.javascript;
|
package org.teavm.debugging.javascript;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import org.teavm.common.Promise;
|
||||||
|
|
||||||
public interface JavaScriptValue {
|
public interface JavaScriptValue {
|
||||||
String getRepresentation();
|
Promise<String> getRepresentation();
|
||||||
|
|
||||||
String getClassName();
|
Promise<String> getClassName();
|
||||||
|
|
||||||
Map<String, JavaScriptVariable> getProperties();
|
Promise<Map<String, ? extends JavaScriptVariable>> getProperties();
|
||||||
|
|
||||||
boolean hasInnerStructure();
|
boolean hasInnerStructure();
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,8 @@ public class Scene {
|
||||||
private Body axis;
|
private Body axis;
|
||||||
private Body reel;
|
private Body reel;
|
||||||
private long lastCalculated;
|
private long lastCalculated;
|
||||||
private long startTime;
|
private long relativeTime;
|
||||||
|
private boolean hasUnfinishedComputations;
|
||||||
|
|
||||||
public Scene() {
|
public Scene() {
|
||||||
world = new World(new Vec2(0, -9.8f));
|
world = new World(new Vec2(0, -9.8f));
|
||||||
|
@ -35,7 +36,6 @@ public class Scene {
|
||||||
joinReelToAxis();
|
joinReelToAxis();
|
||||||
initBalls();
|
initBalls();
|
||||||
lastCalculated = System.currentTimeMillis();
|
lastCalculated = System.currentTimeMillis();
|
||||||
startTime = lastCalculated;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initAxis() {
|
private void initAxis() {
|
||||||
|
@ -133,18 +133,27 @@ public class Scene {
|
||||||
|
|
||||||
public void calculate() {
|
public void calculate() {
|
||||||
long currentTime = System.currentTimeMillis();
|
long currentTime = System.currentTimeMillis();
|
||||||
int timeToCalculate = (int) (currentTime - lastCalculated);
|
long timeToCalculate = currentTime - lastCalculated;
|
||||||
long relativeTime = currentTime - startTime;
|
int count = 5;
|
||||||
while (timeToCalculate > 10) {
|
while (timeToCalculate > 10) {
|
||||||
int period = (int) ((relativeTime + 5000) / 10000);
|
int period = (int) ((relativeTime + 5000) / 10000);
|
||||||
reel.applyTorque(period % 2 == 0 ? 8f : -8f);
|
reel.applyTorque(period % 2 == 0 ? 8f : -8f);
|
||||||
world.step(0.01f, 20, 40);
|
world.step(0.01f, 20, 40);
|
||||||
lastCalculated += 10;
|
lastCalculated += 10;
|
||||||
timeToCalculate -= 10;
|
timeToCalculate -= 10;
|
||||||
|
relativeTime += 10;
|
||||||
|
if (count-- == 0) {
|
||||||
|
hasUnfinishedComputations = true;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
hasUnfinishedComputations = false;
|
||||||
|
}
|
||||||
|
|
||||||
public int timeUntilNextStep() {
|
public int timeUntilNextStep() {
|
||||||
|
if (hasUnfinishedComputations) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
return (int) Math.max(0, lastCalculated + 10 - System.currentTimeMillis());
|
return (int) Math.max(0, lastCalculated + 10 - System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,6 @@ public final class BenchmarkStarter {
|
||||||
private static double timeSpentCalculating;
|
private static double timeSpentCalculating;
|
||||||
private static double totalTime;
|
private static double totalTime;
|
||||||
|
|
||||||
|
|
||||||
private BenchmarkStarter() {
|
private BenchmarkStarter() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,20 +17,21 @@ package org.teavm.chromerdp;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.NullNode;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.function.Supplier;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.teavm.chromerdp.data.CallArgumentDTO;
|
import org.teavm.chromerdp.data.CallArgumentDTO;
|
||||||
|
@ -54,32 +55,41 @@ import org.teavm.chromerdp.messages.ScriptParsedNotification;
|
||||||
import org.teavm.chromerdp.messages.SetBreakpointCommand;
|
import org.teavm.chromerdp.messages.SetBreakpointCommand;
|
||||||
import org.teavm.chromerdp.messages.SetBreakpointResponse;
|
import org.teavm.chromerdp.messages.SetBreakpointResponse;
|
||||||
import org.teavm.chromerdp.messages.SuspendedNotification;
|
import org.teavm.chromerdp.messages.SuspendedNotification;
|
||||||
|
import org.teavm.common.CompletablePromise;
|
||||||
|
import org.teavm.common.Promise;
|
||||||
import org.teavm.debugging.javascript.JavaScriptBreakpoint;
|
import org.teavm.debugging.javascript.JavaScriptBreakpoint;
|
||||||
import org.teavm.debugging.javascript.JavaScriptCallFrame;
|
import org.teavm.debugging.javascript.JavaScriptCallFrame;
|
||||||
import org.teavm.debugging.javascript.JavaScriptDebugger;
|
import org.teavm.debugging.javascript.JavaScriptDebugger;
|
||||||
import org.teavm.debugging.javascript.JavaScriptDebuggerListener;
|
import org.teavm.debugging.javascript.JavaScriptDebuggerListener;
|
||||||
import org.teavm.debugging.javascript.JavaScriptLocation;
|
import org.teavm.debugging.javascript.JavaScriptLocation;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptVariable;
|
||||||
|
|
||||||
public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeConsumer {
|
public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeConsumer {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ChromeRDPDebugger.class);
|
private static final Logger logger = LoggerFactory.getLogger(ChromeRDPDebugger.class);
|
||||||
private static final Object dummy = new Object();
|
private static final Promise<Map<String, ? extends JavaScriptVariable>> EMPTY_SCOPE =
|
||||||
private ChromeRDPExchange exchange;
|
Promise.of(Collections.emptyMap());
|
||||||
private ConcurrentMap<JavaScriptDebuggerListener, Object> listeners = new ConcurrentHashMap<>();
|
private volatile ChromeRDPExchange exchange;
|
||||||
private ConcurrentMap<JavaScriptLocation, RDPBreakpoint> breakpointLocationMap = new ConcurrentHashMap<>();
|
private Set<JavaScriptDebuggerListener> listeners = new LinkedHashSet<>();
|
||||||
private ConcurrentMap<RDPBreakpoint, Object> breakpoints = new ConcurrentHashMap<>();
|
private Map<JavaScriptLocation, RDPNativeBreakpoint> breakpointLocationMap = new HashMap<>();
|
||||||
private ConcurrentMap<String, RDPBreakpoint> breakpointsByChromeId = new ConcurrentHashMap<>();
|
private Set<RDPBreakpoint> breakpoints = new LinkedHashSet<>();
|
||||||
|
private Map<String, RDPNativeBreakpoint> breakpointsByChromeId = new HashMap<>();
|
||||||
private volatile RDPCallFrame[] callStack = new RDPCallFrame[0];
|
private volatile RDPCallFrame[] callStack = new RDPCallFrame[0];
|
||||||
private ConcurrentMap<String, String> scripts = new ConcurrentHashMap<>();
|
private Map<String, String> scripts = new HashMap<>();
|
||||||
private ConcurrentMap<String, String> scriptIds = new ConcurrentHashMap<>();
|
private Map<String, String> scriptIds = new HashMap<>();
|
||||||
private boolean suspended;
|
private volatile boolean suspended;
|
||||||
private ObjectMapper mapper = new ObjectMapper();
|
private ObjectMapper mapper = new ObjectMapper();
|
||||||
private ConcurrentMap<Integer, ResponseHandler<Object>> responseHandlers = new ConcurrentHashMap<>();
|
private ConcurrentMap<Integer, ResponseHandler<Object>> responseHandlers = new ConcurrentHashMap<>();
|
||||||
private ConcurrentMap<Integer, CompletableFuture<Object>> futures = new ConcurrentHashMap<>();
|
private ConcurrentMap<Integer, CompletablePromise<Object>> promises = new ConcurrentHashMap<>();
|
||||||
private AtomicInteger messageIdGenerator = new AtomicInteger();
|
private AtomicInteger messageIdGenerator = new AtomicInteger();
|
||||||
private Lock breakpointLock = new ReentrantLock();
|
|
||||||
|
|
||||||
private List<JavaScriptDebuggerListener> getListeners() {
|
private List<JavaScriptDebuggerListener> getListeners() {
|
||||||
return new ArrayList<>(listeners.keySet());
|
return new ArrayList<>(listeners);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Executor executor;
|
||||||
|
|
||||||
|
public ChromeRDPDebugger(Executor executor) {
|
||||||
|
this.executor = executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -92,8 +102,8 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
}
|
}
|
||||||
this.exchange = exchange;
|
this.exchange = exchange;
|
||||||
if (exchange != null) {
|
if (exchange != null) {
|
||||||
for (RDPBreakpoint breakpoint : breakpoints.keySet().toArray(new RDPBreakpoint[0])) {
|
for (RDPBreakpoint breakpoint : breakpoints.toArray(new RDPBreakpoint[0])) {
|
||||||
updateBreakpoint(breakpoint);
|
updateBreakpoint(breakpoint.nativeBreakpoint);
|
||||||
}
|
}
|
||||||
for (JavaScriptDebuggerListener listener : getListeners()) {
|
for (JavaScriptDebuggerListener listener : getListeners()) {
|
||||||
listener.attached();
|
listener.attached();
|
||||||
|
@ -110,70 +120,77 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void injectFunctions(int contextId) {
|
private Promise<Void> injectFunctions(int contextId) {
|
||||||
callMethod("Runtime.enable", void.class, null);
|
return callMethodAsync("Runtime.enable", void.class, null)
|
||||||
|
.thenAsync(v -> {
|
||||||
CompileScriptCommand compileParams = new CompileScriptCommand();
|
CompileScriptCommand compileParams = new CompileScriptCommand();
|
||||||
compileParams.expression = "$dbg_class = function(obj) { return typeof obj === 'object' && obj != null "
|
compileParams.expression = "$dbg_class = function(obj) { return typeof obj === 'object' "
|
||||||
+ "? obj.__teavm_class__() : null };";
|
+ "&& obj !== null && '__teavm_class__' in obj ? obj.__teavm_class__() : null; };\n"
|
||||||
|
+ "$dbg_repr = function(obj) { return typeof obj === 'object' "
|
||||||
|
+ "&& obj !== null && 'toString' in obj ? obj.toString() : null; }\n";
|
||||||
compileParams.sourceURL = "file://fake";
|
compileParams.sourceURL = "file://fake";
|
||||||
compileParams.persistScript = true;
|
compileParams.persistScript = true;
|
||||||
compileParams.executionContextId = contextId;
|
compileParams.executionContextId = contextId;
|
||||||
CompileScriptResponse response = callMethod("Runtime.compileScript", CompileScriptResponse.class,
|
return callMethodAsync("Runtime.compileScript", CompileScriptResponse.class, compileParams);
|
||||||
compileParams);
|
})
|
||||||
|
.thenAsync(response -> {
|
||||||
RunScriptCommand runParams = new RunScriptCommand();
|
RunScriptCommand runParams = new RunScriptCommand();
|
||||||
runParams.scriptId = response.scriptId;
|
runParams.scriptId = response.scriptId;
|
||||||
callMethod("Runtime.runScript", void.class, runParams);
|
return callMethodAsync("Runtime.runScript", void.class, runParams);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private ChromeRDPExchangeListener exchangeListener = this::receiveMessage;
|
private ChromeRDPExchangeListener exchangeListener = messageText -> {
|
||||||
|
callInExecutor(() -> receiveMessage(messageText)
|
||||||
|
.catchError(e -> {
|
||||||
|
logger.error("Error handling message", e);
|
||||||
|
return null;
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
private void receiveMessage(String messageText) {
|
private Promise<Void> receiveMessage(String messageText) {
|
||||||
new Thread(() -> {
|
|
||||||
try {
|
try {
|
||||||
JsonNode jsonMessage = mapper.readTree(messageText);
|
JsonNode jsonMessage = mapper.readTree(messageText);
|
||||||
if (jsonMessage.has("id")) {
|
if (jsonMessage.has("id")) {
|
||||||
Response response = mapper.reader(Response.class).readValue(jsonMessage);
|
Response response = mapper.readerFor(Response.class).readValue(jsonMessage);
|
||||||
if (response.getError() != null) {
|
if (response.getError() != null) {
|
||||||
if (logger.isWarnEnabled()) {
|
if (logger.isWarnEnabled()) {
|
||||||
logger.warn("Error message #{} received from browser: {}", jsonMessage.get("id"),
|
logger.warn("Error message #{} received from browser: {}", jsonMessage.get("id"),
|
||||||
response.getError().toString());
|
response.getError().toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CompletableFuture<Object> future = futures.remove(response.getId());
|
CompletablePromise<Object> promise = promises.remove(response.getId());
|
||||||
try {
|
try {
|
||||||
responseHandlers.remove(response.getId()).received(response.getResult(), future);
|
responseHandlers.remove(response.getId()).received(response.getResult(), promise);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
logger.warn("Error processing message ${}", response.getId(), e);
|
logger.warn("Error processing message ${}", response.getId(), e);
|
||||||
future.completeExceptionally(e);
|
promise.completeWithError(e);
|
||||||
}
|
}
|
||||||
|
return Promise.VOID;
|
||||||
} else {
|
} else {
|
||||||
Message message = mapper.readerFor(Message.class).readValue(messageText);
|
Message message = mapper.readerFor(Message.class).readValue(messageText);
|
||||||
if (message.getMethod() == null) {
|
if (message.getMethod() == null) {
|
||||||
return;
|
return Promise.VOID;
|
||||||
}
|
}
|
||||||
switch (message.getMethod()) {
|
switch (message.getMethod()) {
|
||||||
case "Debugger.paused":
|
case "Debugger.paused":
|
||||||
firePaused(parseJson(SuspendedNotification.class, message.getParams()));
|
return firePaused(parseJson(SuspendedNotification.class, message.getParams()));
|
||||||
break;
|
|
||||||
case "Debugger.resumed":
|
case "Debugger.resumed":
|
||||||
fireResumed();
|
return fireResumed();
|
||||||
break;
|
|
||||||
case "Debugger.scriptParsed":
|
case "Debugger.scriptParsed":
|
||||||
scriptParsed(parseJson(ScriptParsedNotification.class, message.getParams()));
|
return scriptParsed(parseJson(ScriptParsedNotification.class, message.getParams()));
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
return Promise.VOID;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (logger.isErrorEnabled()) {
|
if (logger.isErrorEnabled()) {
|
||||||
logger.error("Error receiving message from Google Chrome", e);
|
logger.error("Error receiving message from Google Chrome", e);
|
||||||
}
|
}
|
||||||
|
return Promise.VOID;
|
||||||
}
|
}
|
||||||
}).start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void firePaused(SuspendedNotification params) {
|
private Promise<Void> firePaused(SuspendedNotification params) {
|
||||||
suspended = true;
|
suspended = true;
|
||||||
CallFrameDTO[] callFrameDTOs = params.getCallFrames();
|
CallFrameDTO[] callFrameDTOs = params.getCallFrames();
|
||||||
RDPCallFrame[] callStack = new RDPCallFrame[callFrameDTOs.length];
|
RDPCallFrame[] callStack = new RDPCallFrame[callFrameDTOs.length];
|
||||||
|
@ -182,41 +199,48 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
}
|
}
|
||||||
this.callStack = callStack;
|
this.callStack = callStack;
|
||||||
|
|
||||||
RDPBreakpoint breakpoint = null;
|
RDPNativeBreakpoint nativeBreakpoint = null;
|
||||||
if (params.getHitBreakpoints() != null && !params.getHitBreakpoints().isEmpty()) {
|
if (params.getHitBreakpoints() != null && !params.getHitBreakpoints().isEmpty()) {
|
||||||
breakpoint = breakpointsByChromeId.get(params.getHitBreakpoints().get(0));
|
nativeBreakpoint = breakpointsByChromeId.get(params.getHitBreakpoints().get(0));
|
||||||
}
|
}
|
||||||
|
RDPBreakpoint breakpoint = !nativeBreakpoint.breakpoints.isEmpty()
|
||||||
|
? nativeBreakpoint.breakpoints.iterator().next()
|
||||||
|
: null;
|
||||||
|
|
||||||
for (JavaScriptDebuggerListener listener : getListeners()) {
|
for (JavaScriptDebuggerListener listener : getListeners()) {
|
||||||
listener.paused(breakpoint);
|
listener.paused(breakpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Promise.VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void fireResumed() {
|
private Promise<Void> fireResumed() {
|
||||||
suspended = false;
|
suspended = false;
|
||||||
callStack = null;
|
callStack = null;
|
||||||
for (JavaScriptDebuggerListener listener : getListeners()) {
|
for (JavaScriptDebuggerListener listener : getListeners()) {
|
||||||
listener.resumed();
|
listener.resumed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Promise.VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void scriptParsed(ScriptParsedNotification params) {
|
private Promise<Void> scriptParsed(ScriptParsedNotification params) {
|
||||||
if (scripts.putIfAbsent(params.getScriptId(), params.getUrl()) != null) {
|
if (scripts.putIfAbsent(params.getScriptId(), params.getUrl()) != null) {
|
||||||
return;
|
return Promise.VOID;
|
||||||
}
|
}
|
||||||
if (params.getUrl().equals("file://fake")) {
|
if (params.getUrl().equals("file://fake")) {
|
||||||
return;
|
return Promise.VOID;
|
||||||
}
|
}
|
||||||
scriptIds.put(params.getUrl(), params.getScriptId());
|
scriptIds.put(params.getUrl(), params.getScriptId());
|
||||||
for (JavaScriptDebuggerListener listener : getListeners()) {
|
for (JavaScriptDebuggerListener listener : getListeners()) {
|
||||||
listener.scriptAdded(params.getUrl());
|
listener.scriptAdded(params.getUrl());
|
||||||
}
|
}
|
||||||
injectFunctions(params.getExecutionContextId());
|
return injectFunctions(params.getExecutionContextId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addListener(JavaScriptDebuggerListener listener) {
|
public void addListener(JavaScriptDebuggerListener listener) {
|
||||||
listeners.put(listener, dummy);
|
listeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -225,35 +249,35 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void suspend() {
|
public Promise<Void> suspend() {
|
||||||
callMethod("Debugger.pause", void.class, null);
|
return callMethodAsync("Debugger.pause", void.class, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resume() {
|
public Promise<Void> resume() {
|
||||||
callMethod("Debugger.resume", void.class, null);
|
return callMethodAsync("Debugger.resume", void.class, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stepInto() {
|
public Promise<Void> stepInto() {
|
||||||
callMethod("Debugger.stepInto", void.class, null);
|
return callMethodAsync("Debugger.stepInto", void.class, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stepOut() {
|
public Promise<Void> stepOut() {
|
||||||
callMethod("Debugger.stepOut", void.class, null);
|
return callMethodAsync("Debugger.stepOut", void.class, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stepOver() {
|
public Promise<Void> stepOver() {
|
||||||
callMethod("Debugger.stepOver", void.class, null);
|
return callMethodAsync("Debugger.stepOver", void.class, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void continueToLocation(JavaScriptLocation location) {
|
public Promise<Void> continueToLocation(JavaScriptLocation location) {
|
||||||
ContinueToLocationCommand params = new ContinueToLocationCommand();
|
ContinueToLocationCommand params = new ContinueToLocationCommand();
|
||||||
params.setLocation(unmap(location));
|
params.setLocation(unmap(location));
|
||||||
callMethod("Debugger.continueToLocation", void.class, params);
|
return callMethodAsync("Debugger.continueToLocation", void.class, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -283,70 +307,75 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JavaScriptBreakpoint createBreakpoint(JavaScriptLocation location) {
|
public Promise<JavaScriptBreakpoint> createBreakpoint(JavaScriptLocation location) {
|
||||||
RDPBreakpoint breakpoint;
|
RDPBreakpoint breakpoint = new RDPBreakpoint(this);
|
||||||
|
breakpoint.nativeBreakpoint = lockNativeBreakpoint(location, breakpoint);
|
||||||
|
CompletablePromise<JavaScriptBreakpoint> result = new CompletablePromise<>();
|
||||||
|
breakpoints.add(breakpoint);
|
||||||
|
breakpoint.nativeBreakpoint.initPromise.thenVoid(v -> result.complete(breakpoint));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise<Void> destroyBreakpoint(RDPBreakpoint breakpoint) {
|
||||||
|
if (breakpoint.nativeBreakpoint == null) {
|
||||||
|
return Promise.VOID;
|
||||||
|
}
|
||||||
|
RDPNativeBreakpoint nativeBreakpoint = breakpoint.nativeBreakpoint;
|
||||||
|
breakpoint.nativeBreakpoint = null;
|
||||||
|
nativeBreakpoint.breakpoints.remove(breakpoint);
|
||||||
|
return releaseNativeBreakpoint(nativeBreakpoint, breakpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RDPNativeBreakpoint lockNativeBreakpoint(JavaScriptLocation location, RDPBreakpoint bp) {
|
||||||
|
RDPNativeBreakpoint breakpoint;
|
||||||
|
|
||||||
breakpointLock.lock();
|
|
||||||
try {
|
|
||||||
breakpoint = breakpointLocationMap.get(location);
|
breakpoint = breakpointLocationMap.get(location);
|
||||||
if (breakpoint == null) {
|
if (breakpoint != null) {
|
||||||
breakpoint = new RDPBreakpoint(this, location);
|
breakpoint.breakpoints.add(bp);
|
||||||
breakpointLocationMap.put(location, breakpoint);
|
|
||||||
updateBreakpoint(breakpoint);
|
|
||||||
}
|
|
||||||
breakpoint.referenceCount.incrementAndGet();
|
|
||||||
breakpoints.put(breakpoint, dummy);
|
|
||||||
} finally {
|
|
||||||
breakpointLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
return breakpoint;
|
return breakpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
void destroyBreakpoint(RDPBreakpoint breakpoint) {
|
breakpoint = new RDPNativeBreakpoint(this, location);
|
||||||
if (breakpoint.referenceCount.decrementAndGet() > 0) {
|
breakpoint.breakpoints.add(bp);
|
||||||
return;
|
breakpointLocationMap.put(location, breakpoint);
|
||||||
|
RDPNativeBreakpoint finalBreakpoint = breakpoint;
|
||||||
|
breakpoint.initPromise = updateBreakpoint(breakpoint).then(v -> {
|
||||||
|
checkBreakpoint(finalBreakpoint);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
return breakpoint;
|
||||||
}
|
}
|
||||||
breakpointLock.lock();
|
|
||||||
try {
|
private Promise<Void> releaseNativeBreakpoint(RDPNativeBreakpoint breakpoint, RDPBreakpoint bp) {
|
||||||
if (breakpoint.referenceCount.get() > 0) {
|
breakpoint.breakpoints.remove(bp);
|
||||||
return;
|
return checkBreakpoint(breakpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Promise<Void> checkBreakpoint(RDPNativeBreakpoint breakpoint) {
|
||||||
|
if (!breakpoint.breakpoints.isEmpty()) {
|
||||||
|
return Promise.VOID;
|
||||||
|
}
|
||||||
|
if (breakpointLocationMap.get(breakpoint.getLocation()) == breakpoint) {
|
||||||
breakpointLocationMap.remove(breakpoint.getLocation());
|
breakpointLocationMap.remove(breakpoint.getLocation());
|
||||||
breakpoints.remove(breakpoint);
|
|
||||||
|
|
||||||
if (breakpoint.chromeId == null) {
|
|
||||||
synchronized (breakpoint.updateMonitor) {
|
|
||||||
while (breakpoint.updating.get()) {
|
|
||||||
try {
|
|
||||||
breakpoint.updateMonitor.wait();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
if (breakpoint.destroyPromise == null) {
|
||||||
}
|
breakpoint.destroyPromise = breakpoint.initPromise.thenAsync(v -> {
|
||||||
}
|
|
||||||
|
|
||||||
if (breakpoint.chromeId != null) {
|
|
||||||
breakpointsByChromeId.remove(breakpoint.chromeId);
|
breakpointsByChromeId.remove(breakpoint.chromeId);
|
||||||
if (logger.isInfoEnabled()) {
|
if (logger.isInfoEnabled()) {
|
||||||
logger.info("Removing breakpoint at {}", breakpoint.getLocation());
|
logger.info("Removing breakpoint at {}", breakpoint.getLocation());
|
||||||
}
|
}
|
||||||
RemoveBreakpointCommand params = new RemoveBreakpointCommand();
|
RemoveBreakpointCommand params = new RemoveBreakpointCommand();
|
||||||
params.setBreakpointId(breakpoint.chromeId);
|
params.setBreakpointId(breakpoint.chromeId);
|
||||||
callMethod("Debugger.removeBreakpoint", void.class, params);
|
return callMethodAsync("Debugger.removeBreakpoint", void.class, params);
|
||||||
}
|
});
|
||||||
breakpoint.debugger = null;
|
breakpoint.debugger = null;
|
||||||
breakpoint.chromeId = null;
|
|
||||||
} finally {
|
|
||||||
breakpointLock.unlock();
|
|
||||||
}
|
}
|
||||||
|
return breakpoint.destroyPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateBreakpoint(final RDPBreakpoint breakpoint) {
|
private Promise<Void> updateBreakpoint(RDPNativeBreakpoint breakpoint) {
|
||||||
if (breakpoint.chromeId != null) {
|
if (breakpoint.chromeId != null) {
|
||||||
return;
|
return Promise.VOID;
|
||||||
}
|
}
|
||||||
SetBreakpointCommand params = new SetBreakpointCommand();
|
SetBreakpointCommand params = new SetBreakpointCommand();
|
||||||
params.setLocation(unmap(breakpoint.getLocation()));
|
params.setLocation(unmap(breakpoint.getLocation()));
|
||||||
|
@ -355,9 +384,8 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
logger.info("Setting breakpoint at {}", breakpoint.getLocation());
|
logger.info("Setting breakpoint at {}", breakpoint.getLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
breakpoint.updating.set(true);
|
return callMethodAsync("Debugger.setBreakpoint", SetBreakpointResponse.class, params)
|
||||||
try {
|
.thenVoid(response -> {
|
||||||
SetBreakpointResponse response = callMethod("Debugger.setBreakpoint", SetBreakpointResponse.class, params);
|
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
breakpoint.chromeId = response.getBreakpointId();
|
breakpoint.chromeId = response.getBreakpointId();
|
||||||
if (breakpoint.chromeId != null) {
|
if (breakpoint.chromeId != null) {
|
||||||
|
@ -369,32 +397,50 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
}
|
}
|
||||||
breakpoint.chromeId = null;
|
breakpoint.chromeId = null;
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
synchronized (breakpoint.updateMonitor) {
|
|
||||||
breakpoint.updating.set(false);
|
|
||||||
breakpoint.updateMonitor.notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for (RDPBreakpoint bp : breakpoint.breakpoints) {
|
||||||
for (JavaScriptDebuggerListener listener : getListeners()) {
|
for (JavaScriptDebuggerListener listener : getListeners()) {
|
||||||
listener.breakpointChanged(breakpoint);
|
listener.breakpointChanged(bp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
List<RDPLocalVariable> getScope(String scopeId) {
|
Promise<List<RDPLocalVariable>> getScope(String scopeId) {
|
||||||
GetPropertiesCommand params = new GetPropertiesCommand();
|
GetPropertiesCommand params = new GetPropertiesCommand();
|
||||||
params.setObjectId(scopeId);
|
params.setObjectId(scopeId);
|
||||||
params.setOwnProperties(true);
|
params.setOwnProperties(true);
|
||||||
|
|
||||||
GetPropertiesResponse response = callMethod("Runtime.getProperties", GetPropertiesResponse.class, params);
|
return callMethodAsync("Runtime.getProperties", GetPropertiesResponse.class, params)
|
||||||
|
.thenAsync(response -> {
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
return Collections.emptyList();
|
return Promise.of(Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseProperties(response.getResult());
|
PropertyDescriptorDTO proto = Arrays.asList(response.getResult()).stream()
|
||||||
|
.filter(p -> p.getName().equals("__proto__"))
|
||||||
|
.findAny()
|
||||||
|
.orElse(null);
|
||||||
|
if (proto == null || proto.getValue() == null || proto.getValue().getObjectId() == null) {
|
||||||
|
return Promise.of(parseProperties(scopeId, response.getResult(), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
String getClassName(String objectId) {
|
GetPropertiesCommand protoParams = new GetPropertiesCommand();
|
||||||
|
protoParams.setObjectId(proto.getValue().getObjectId());
|
||||||
|
protoParams.setOwnProperties(false);
|
||||||
|
|
||||||
|
return callMethodAsync("Runtime.getProperties", GetPropertiesResponse.class, protoParams)
|
||||||
|
.then(protoProperties -> {
|
||||||
|
PropertyDescriptorDTO[] getters = Arrays.asList(protoProperties.getResult()).stream()
|
||||||
|
.filter(p -> p.getGetter() != null && p.getValue() == null
|
||||||
|
&& !p.getName().equals("__proto__"))
|
||||||
|
.toArray(PropertyDescriptorDTO[]::new);
|
||||||
|
return parseProperties(scopeId, response.getResult(), getters);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise<String> getClassName(String objectId) {
|
||||||
CallFunctionCommand params = new CallFunctionCommand();
|
CallFunctionCommand params = new CallFunctionCommand();
|
||||||
CallArgumentDTO arg = new CallArgumentDTO();
|
CallArgumentDTO arg = new CallArgumentDTO();
|
||||||
arg.setObjectId(objectId);
|
arg.setObjectId(objectId);
|
||||||
|
@ -402,12 +448,14 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
params.setArguments(new CallArgumentDTO[] { arg });
|
params.setArguments(new CallArgumentDTO[] { arg });
|
||||||
params.setFunctionDeclaration("$dbg_class");
|
params.setFunctionDeclaration("$dbg_class");
|
||||||
|
|
||||||
CallFunctionResponse response = callMethod("Runtime.callFunctionOn", CallFunctionResponse.class, params);
|
return callMethodAsync("Runtime.callFunctionOn", CallFunctionResponse.class, params)
|
||||||
|
.then(response -> {
|
||||||
RemoteObjectDTO result = response != null ? response.getResult() : null;
|
RemoteObjectDTO result = response != null ? response.getResult() : null;
|
||||||
return result.getValue() != null ? result.getValue().textValue() : null;
|
return result.getValue() != null ? result.getValue().textValue() : null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
String getRepresentation(String objectId) {
|
Promise<String> getRepresentation(String objectId) {
|
||||||
CallFunctionCommand params = new CallFunctionCommand();
|
CallFunctionCommand params = new CallFunctionCommand();
|
||||||
CallArgumentDTO arg = new CallArgumentDTO();
|
CallArgumentDTO arg = new CallArgumentDTO();
|
||||||
arg.setObjectId(objectId);
|
arg.setObjectId(objectId);
|
||||||
|
@ -415,42 +463,81 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
params.setArguments(new CallArgumentDTO[] { arg });
|
params.setArguments(new CallArgumentDTO[] { arg });
|
||||||
params.setFunctionDeclaration("$dbg_repr");
|
params.setFunctionDeclaration("$dbg_repr");
|
||||||
|
|
||||||
CallFunctionResponse response = callMethod("Runtime.callFunctionOn", CallFunctionResponse.class, params);
|
return callMethodAsync("Runtime.callFunctionOn", CallFunctionResponse.class, params)
|
||||||
|
.then(response -> {
|
||||||
RemoteObjectDTO result = response != null ? response.getResult() : null;
|
RemoteObjectDTO result = response != null ? response.getResult() : null;
|
||||||
return result.getValue() != null ? result.getValue().textValue() : null;
|
return result.getValue() != null ? result.getValue().textValue() : null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<RDPLocalVariable> parseProperties(PropertyDescriptorDTO[] properties) {
|
private List<RDPLocalVariable> parseProperties(String scopeId, PropertyDescriptorDTO[] properties,
|
||||||
|
PropertyDescriptorDTO[] getters) {
|
||||||
List<RDPLocalVariable> variables = new ArrayList<>();
|
List<RDPLocalVariable> variables = new ArrayList<>();
|
||||||
if (properties != null) {
|
if (properties != null) {
|
||||||
for (PropertyDescriptorDTO property : properties) {
|
for (PropertyDescriptorDTO property : properties) {
|
||||||
RemoteObjectDTO remoteValue = property.getValue();
|
RemoteObjectDTO remoteValue = property.getValue();
|
||||||
|
RemoteObjectDTO getter = property.getGetter();
|
||||||
RDPValue value;
|
RDPValue value;
|
||||||
if (remoteValue != null && remoteValue.getType() != null) {
|
if (remoteValue != null && remoteValue.getType() != null) {
|
||||||
switch (remoteValue.getType()) {
|
value = mapValue(remoteValue);
|
||||||
case "undefined":
|
} else if (getter != null && getter.getObjectId() != null) {
|
||||||
value = new RDPValue(this, "undefined", "undefined", null, false);
|
value = mapValue(getter);
|
||||||
break;
|
|
||||||
case "object":
|
|
||||||
case "function":
|
|
||||||
value = new RDPValue(this, null, remoteValue.getType(), remoteValue.getObjectId(), true);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
value = new RDPValue(this, remoteValue.getValue().asText(), remoteValue.getType(),
|
|
||||||
remoteValue.getObjectId(), false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
value = new RDPValue(this, "null", "null", "null", false);
|
value = new RDPValue(this, "null", "null", null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
RDPLocalVariable var = new RDPLocalVariable(property.getName(), value);
|
RDPLocalVariable var = new RDPLocalVariable(property.getName(), value);
|
||||||
variables.add(var);
|
variables.add(var);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (getters != null) {
|
||||||
|
for (PropertyDescriptorDTO property : getters) {
|
||||||
|
RDPValue value = new RDPValue(this, "<get>", "@Function", scopeId, true);
|
||||||
|
value.getter = property.getGetter();
|
||||||
|
RDPLocalVariable var = new RDPLocalVariable(property.getName(), value);
|
||||||
|
variables.add(var);
|
||||||
|
}
|
||||||
|
}
|
||||||
return variables;
|
return variables;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Promise<RDPValue> invokeGetter(String functionId, String objectId) {
|
||||||
|
CallFunctionCommand params = new CallFunctionCommand();
|
||||||
|
params.setObjectId(functionId);
|
||||||
|
|
||||||
|
CallArgumentDTO functionArg = new CallArgumentDTO();
|
||||||
|
functionArg.setObjectId(functionId);
|
||||||
|
CallArgumentDTO arg = new CallArgumentDTO();
|
||||||
|
arg.setObjectId(objectId);
|
||||||
|
|
||||||
|
params.setArguments(new CallArgumentDTO[] { arg });
|
||||||
|
params.setFunctionDeclaration("Function.prototype.call");
|
||||||
|
|
||||||
|
return callMethodAsync("Runtime.callFunctionOn", CallFunctionResponse.class, params)
|
||||||
|
.then(response -> {
|
||||||
|
RemoteObjectDTO result = response != null ? response.getResult() : null;
|
||||||
|
return result.getValue() != null ? mapValue(result) : null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
RDPValue mapValue(RemoteObjectDTO remoteValue) {
|
||||||
|
switch (remoteValue.getType()) {
|
||||||
|
case "undefined":
|
||||||
|
return new RDPValue(this, "undefined", "undefined", null, false);
|
||||||
|
case "object":
|
||||||
|
case "function":
|
||||||
|
if (remoteValue.getValue() instanceof NullNode) {
|
||||||
|
return new RDPValue(this, "null", "null", null, false);
|
||||||
|
} else {
|
||||||
|
return new RDPValue(this, null, remoteValue.getType(), remoteValue.getObjectId(),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return new RDPValue(this, remoteValue.getValue().asText(), remoteValue.getType(),
|
||||||
|
remoteValue.getObjectId(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private <T> T parseJson(Class<T> type, JsonNode node) throws IOException {
|
private <T> T parseJson(Class<T> type, JsonNode node) throws IOException {
|
||||||
return mapper.readerFor(type).readValue(node);
|
return mapper.readerFor(type).readValue(node);
|
||||||
}
|
}
|
||||||
|
@ -485,7 +572,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new RDPCallFrame(this, dto.getCallFrameId(), map(dto.getLocation()), new RDPScope(this, scopeId),
|
return new RDPCallFrame(this, dto.getCallFrameId(), map(dto.getLocation()), scopeId,
|
||||||
thisObject, closure);
|
thisObject, closure);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -501,9 +588,9 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
private <R> R callMethod(String method, Class<R> returnType, Object params) {
|
private <R> Promise<R> callMethodAsync(String method, Class<R> returnType, Object params) {
|
||||||
if (exchange == null) {
|
if (exchange == null) {
|
||||||
return null;
|
return Promise.of(null);
|
||||||
}
|
}
|
||||||
Message message = new Message();
|
Message message = new Message();
|
||||||
message.setId(messageIdGenerator.incrementAndGet());
|
message.setId(messageIdGenerator.incrementAndGet());
|
||||||
|
@ -512,7 +599,8 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
message.setParams(mapper.valueToTree(params));
|
message.setParams(mapper.valueToTree(params));
|
||||||
}
|
}
|
||||||
|
|
||||||
CompletableFuture<R> sync = setResponseHandler(message.getId(), (node, out) -> {
|
sendMessage(message);
|
||||||
|
return setResponseHandler(message.getId(), (JsonNode node, CompletablePromise<R> out) -> {
|
||||||
if (node == null) {
|
if (node == null) {
|
||||||
out.complete(null);
|
out.complete(null);
|
||||||
} else {
|
} else {
|
||||||
|
@ -520,41 +608,38 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
||||||
out.complete(response);
|
out.complete(response);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
sendMessage(message);
|
|
||||||
try {
|
|
||||||
return read(sync);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
return null;
|
|
||||||
} catch (TimeoutException e) {
|
|
||||||
logger.warn("Chrome debug protocol: timed out", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private <T> CompletableFuture<T> setResponseHandler(int messageId, ResponseHandler<T> handler) {
|
private <T> Promise<T> setResponseHandler(int messageId, ResponseHandler<T> handler) {
|
||||||
CompletableFuture<T> future = new CompletableFuture<>();
|
CompletablePromise<T> promise = new CompletablePromise<>();
|
||||||
futures.put(messageId, (CompletableFuture<Object>) future);
|
promises.put(messageId, (CompletablePromise<Object>) promise);
|
||||||
responseHandlers.put(messageId, (ResponseHandler<Object>) handler);
|
responseHandlers.put(messageId, (ResponseHandler<Object>) handler);
|
||||||
return future;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ResponseHandler<T> {
|
interface ResponseHandler<T> {
|
||||||
void received(JsonNode node, CompletableFuture<T> out) throws IOException;
|
void received(JsonNode node, CompletablePromise<T> out) throws IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> T read(Future<T> future) throws InterruptedException, TimeoutException {
|
Promise<Map<String, ? extends JavaScriptVariable>> createScope(String id) {
|
||||||
try {
|
if (id == null) {
|
||||||
return future.get(1500, TimeUnit.MILLISECONDS);
|
return EMPTY_SCOPE;
|
||||||
} catch (ExecutionException e) {
|
|
||||||
Throwable cause = e.getCause();
|
|
||||||
if (cause instanceof RuntimeException) {
|
|
||||||
throw (RuntimeException) cause;
|
|
||||||
} else if (cause instanceof Error) {
|
|
||||||
throw (Error) cause;
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException(cause);
|
|
||||||
}
|
}
|
||||||
|
return getScope(id).then(scope -> {
|
||||||
|
Map<String, RDPLocalVariable> newBackingMap = new HashMap<>();
|
||||||
|
for (RDPLocalVariable variable : scope) {
|
||||||
|
newBackingMap.put(variable.getName(), variable);
|
||||||
}
|
}
|
||||||
|
return Collections.unmodifiableMap(newBackingMap);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> Promise<T> callInExecutor(Supplier<Promise<T>> f) {
|
||||||
|
CompletablePromise<T> result = new CompletablePromise<>();
|
||||||
|
executor.execute(() -> {
|
||||||
|
f.get().thenVoid(result::complete).catchVoid(result::completeWithError);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,14 +23,19 @@ import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import org.teavm.common.Promise;
|
||||||
import org.teavm.debugging.Breakpoint;
|
import org.teavm.debugging.Breakpoint;
|
||||||
import org.teavm.debugging.CallFrame;
|
import org.teavm.debugging.CallFrame;
|
||||||
import org.teavm.debugging.Debugger;
|
import org.teavm.debugging.Debugger;
|
||||||
import org.teavm.debugging.DebuggerListener;
|
import org.teavm.debugging.DebuggerListener;
|
||||||
import org.teavm.debugging.Variable;
|
import org.teavm.debugging.Variable;
|
||||||
import org.teavm.debugging.information.URLDebugInformationProvider;
|
import org.teavm.debugging.information.URLDebugInformationProvider;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptLocation;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptVariable;
|
||||||
|
|
||||||
public final class ChromeRDPRunner {
|
public final class ChromeRDPRunner {
|
||||||
private ChromeRDPServer server;
|
private ChromeRDPServer server;
|
||||||
|
@ -38,14 +43,12 @@ public final class ChromeRDPRunner {
|
||||||
private Map<Breakpoint, Integer> breakpointIds = new WeakHashMap<>();
|
private Map<Breakpoint, Integer> breakpointIds = new WeakHashMap<>();
|
||||||
private int currentFrame;
|
private int currentFrame;
|
||||||
private int breakpointIdGen;
|
private int breakpointIdGen;
|
||||||
private volatile Runnable attachListener;
|
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
|
||||||
private volatile Runnable suspendListener;
|
|
||||||
private volatile Runnable resumeListener;
|
|
||||||
|
|
||||||
private ChromeRDPRunner() {
|
private ChromeRDPRunner() {
|
||||||
server = new ChromeRDPServer();
|
server = new ChromeRDPServer();
|
||||||
server.setPort(2357);
|
server.setPort(2357);
|
||||||
ChromeRDPDebugger jsDebugger = new ChromeRDPDebugger();
|
ChromeRDPDebugger jsDebugger = new ChromeRDPDebugger(queue::offer);
|
||||||
server.setExchangeConsumer(jsDebugger);
|
server.setExchangeConsumer(jsDebugger);
|
||||||
|
|
||||||
new Thread(server::start).start();
|
new Thread(server::start).start();
|
||||||
|
@ -61,9 +64,6 @@ public final class ChromeRDPRunner {
|
||||||
private DebuggerListener listener = new DebuggerListener() {
|
private DebuggerListener listener = new DebuggerListener() {
|
||||||
@Override
|
@Override
|
||||||
public void resumed() {
|
public void resumed() {
|
||||||
if (resumeListener != null) {
|
|
||||||
resumeListener.run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -77,9 +77,6 @@ public final class ChromeRDPRunner {
|
||||||
System.out.println("Breakpoint #" + breakpointIds.get(breakpoint) + " hit");
|
System.out.println("Breakpoint #" + breakpointIds.get(breakpoint) + " hit");
|
||||||
}
|
}
|
||||||
currentFrame = 0;
|
currentFrame = 0;
|
||||||
if (suspendListener != null) {
|
|
||||||
suspendListener.run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -88,9 +85,6 @@ public final class ChromeRDPRunner {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attached() {
|
public void attached() {
|
||||||
if (attachListener != null) {
|
|
||||||
attachListener.run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -98,7 +92,7 @@ public final class ChromeRDPRunner {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) {
|
||||||
ChromeRDPRunner runner = new ChromeRDPRunner();
|
ChromeRDPRunner runner = new ChromeRDPRunner();
|
||||||
try {
|
try {
|
||||||
runner.acceptInput();
|
runner.acceptInput();
|
||||||
|
@ -107,145 +101,206 @@ public final class ChromeRDPRunner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void acceptInput() throws IOException, InterruptedException {
|
public void acceptInput() throws InterruptedException {
|
||||||
if (!debugger.isAttached()) {
|
boolean wasAttached = debugger.isAttached();
|
||||||
|
if (!wasAttached) {
|
||||||
System.out.println("Waiting for remote process to attach...");
|
System.out.println("Waiting for remote process to attach...");
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
attachListener = latch::countDown;
|
|
||||||
if (!debugger.isAttached()) {
|
|
||||||
try {
|
|
||||||
latch.await();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
attachListener = null;
|
|
||||||
System.out.println("Attached");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
queue.take().run();
|
||||||
|
if (debugger.isAttached() && !wasAttached) {
|
||||||
|
wasAttached = true;
|
||||||
|
System.out.println("Attached");
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
stdinThread();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
} else if (!debugger.isAttached() && wasAttached) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.offer(() -> {
|
||||||
|
debugger.detach();
|
||||||
|
server.stop();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stdinThread() throws IOException {
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
||||||
loop: while (true) {
|
while (true) {
|
||||||
System.out.print("> ");
|
System.out.print("> ");
|
||||||
String line = reader.readLine();
|
String line = reader.readLine();
|
||||||
if (line == null) {
|
if (line == null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BlockingQueue<Boolean> callbackQueue = new ArrayBlockingQueue<>(1);
|
||||||
|
queue.add(() -> {
|
||||||
|
processSingleCommand(line).then(r -> callbackQueue.offer(r)).catchError(e -> {
|
||||||
|
e.printStackTrace();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
if (!callbackQueue.take()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Promise<Boolean> processSingleCommand(String line) {
|
||||||
line = line.trim();
|
line = line.trim();
|
||||||
String[] parts = Arrays.stream(line.split(" +"))
|
String[] parts = Arrays.stream(line.split(" +"))
|
||||||
.map(String::trim)
|
.map(String::trim)
|
||||||
.filter(s -> !s.isEmpty())
|
.filter(s -> !s.isEmpty())
|
||||||
.toArray(String[]::new);
|
.toArray(String[]::new);
|
||||||
if (parts.length == 0) {
|
if (parts.length == 0) {
|
||||||
continue;
|
return Promise.of(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (parts[0]) {
|
switch (parts[0]) {
|
||||||
case "suspend":
|
case "suspend":
|
||||||
if (debugger.isSuspended()) {
|
if (debugger.isSuspended()) {
|
||||||
System.out.println("Suspend command is only available when program is running");
|
System.out.println("Suspend command is only available when program is running");
|
||||||
|
return Promise.of(true);
|
||||||
} else {
|
} else {
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
return debugger.suspend().then(v -> true);
|
||||||
suspendListener = latch::countDown;
|
|
||||||
debugger.suspend();
|
|
||||||
latch.await();
|
|
||||||
suspendListener = null;
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
case "detach":
|
case "detach":
|
||||||
break loop;
|
return Promise.of(false);
|
||||||
|
|
||||||
case "continue":
|
case "continue":
|
||||||
case "cont":
|
case "cont":
|
||||||
case "c":
|
case "c":
|
||||||
suspended(parts, resumeCommand);
|
return suspended(parts, resumeCommand);
|
||||||
break;
|
|
||||||
|
|
||||||
case "breakpoint":
|
case "breakpoint":
|
||||||
case "break":
|
case "break":
|
||||||
case "br":
|
case "br":
|
||||||
case "bp":
|
case "bp":
|
||||||
breakpointCommand.execute(parts);
|
return breakpointCommand.execute(parts).then(v -> true);
|
||||||
break;
|
|
||||||
|
|
||||||
case "backtrace":
|
case "backtrace":
|
||||||
case "bt":
|
case "bt":
|
||||||
suspended(parts, backtraceCommand);
|
return suspended(parts, backtraceCommand);
|
||||||
break;
|
|
||||||
|
|
||||||
case "frame":
|
case "frame":
|
||||||
case "fr":
|
case "fr":
|
||||||
case "f":
|
case "f":
|
||||||
suspended(parts, frameCommand);
|
return suspended(parts, frameCommand);
|
||||||
break;
|
|
||||||
|
|
||||||
case "step":
|
case "step":
|
||||||
case "s":
|
case "s":
|
||||||
suspended(parts, stepCommand);
|
return suspended(parts, stepCommand);
|
||||||
break;
|
|
||||||
|
|
||||||
case "next":
|
case "next":
|
||||||
case "n":
|
case "n":
|
||||||
suspended(parts, nextCommand);
|
return suspended(parts, nextCommand);
|
||||||
break;
|
|
||||||
|
|
||||||
case "out":
|
case "out":
|
||||||
case "o":
|
case "o":
|
||||||
suspended(parts, outCommand);
|
return suspended(parts, outCommand);
|
||||||
break;
|
|
||||||
|
|
||||||
case "info":
|
case "info":
|
||||||
suspended(parts, infoCommand);
|
return suspended(parts, infoCommand);
|
||||||
break;
|
|
||||||
|
case "print":
|
||||||
|
case "p":
|
||||||
|
return suspended(parts, printCommand);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
System.out.println("Unknown command");
|
System.out.println("Unknown command");
|
||||||
|
return Promise.of(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debugger.detach();
|
private Promise<Boolean> suspended(String[] arguments, Command command) {
|
||||||
server.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void suspended(String[] arguments, Command command) throws InterruptedException {
|
|
||||||
if (!debugger.isSuspended()) {
|
if (!debugger.isSuspended()) {
|
||||||
System.out.println("This command is only available when remote process is suspended");
|
System.out.println("This command is only available when remote process is suspended");
|
||||||
return;
|
return Promise.of(true);
|
||||||
}
|
}
|
||||||
command.execute(arguments);
|
return command.execute(arguments).then(v -> true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Command resumeCommand = args -> {
|
private Command resumeCommand = args -> debugger.resume();
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
resumeListener = latch::countDown;
|
|
||||||
debugger.resume();
|
|
||||||
latch.await();
|
|
||||||
resumeListener = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
private Command breakpointCommand = args -> {
|
private Command breakpointCommand = args -> {
|
||||||
if (args.length != 3) {
|
if (args.length != 3 && args.length != 3) {
|
||||||
System.out.println("Expected 2 arguments");
|
System.out.println("Expected 2 arguments");
|
||||||
return;
|
return Promise.VOID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 4) {
|
||||||
|
return tryResolveJsBreakpoint(args[1], Integer.parseInt(args[2]), Integer.parseInt(args[3]));
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] fileNames = resolveFileName(args[1]);
|
String[] fileNames = resolveFileName(args[1]);
|
||||||
if (fileNames.length == 0) {
|
if (fileNames.length == 0) {
|
||||||
System.out.println("Unknown file: " + args[1]);
|
return tryResolveJsBreakpoint(args[1], Integer.parseInt(args[2]),
|
||||||
return;
|
args.length == 3 ? 1 : Integer.parseInt(args[3]));
|
||||||
} else if (fileNames.length > 1) {
|
} else if (fileNames.length > 1) {
|
||||||
System.out.println("Ambiguous file name: " + args[1] + ". Possible names are: "
|
System.out.println("Ambiguous file name: " + args[1] + ". Possible names are: "
|
||||||
+ Arrays.toString(fileNames));
|
+ Arrays.toString(fileNames));
|
||||||
return;
|
return Promise.VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
Breakpoint bp = debugger.createBreakpoint(fileNames[0], Integer.parseInt(args[2]));
|
return debugger.createBreakpoint(fileNames[0], Integer.parseInt(args[2])).thenVoid(bp -> {
|
||||||
int id = breakpointIdGen++;
|
int id = breakpointIdGen++;
|
||||||
breakpointIds.put(bp, id);
|
breakpointIds.put(bp, id);
|
||||||
System.out.println("Breakpoint #" + id + " was set at " + bp.getLocation());
|
System.out.println("Breakpoint #" + id + " was set at " + bp.getLocation());
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private Promise<Void> tryResolveJsBreakpoint(String fileName, int lineNumber, int columnNumber) {
|
||||||
|
String[] fileNames = resolveJsFileName(fileName);
|
||||||
|
if (fileNames.length == 0) {
|
||||||
|
System.out.println("Unknown file: " + fileName);
|
||||||
|
return Promise.VOID;
|
||||||
|
} else if (fileNames.length > 1) {
|
||||||
|
System.out.println("Ambiguous file name: " + fileName + ". Possible names are: "
|
||||||
|
+ Arrays.toString(fileNames));
|
||||||
|
return Promise.VOID;
|
||||||
|
}
|
||||||
|
|
||||||
|
JavaScriptLocation location = new JavaScriptLocation(fileNames[0], lineNumber - 1, columnNumber - 1);
|
||||||
|
return debugger.getJavaScriptDebugger().createBreakpoint(location).thenVoid(bp -> {
|
||||||
|
System.out.println("Native breakpoint was set at " + bp.getLocation());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] resolveJsFileName(String fileName) {
|
||||||
|
if (debugger.getScriptNames().contains(fileName)) {
|
||||||
|
return new String[] { fileName };
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] result = debugger.getScriptNames().stream()
|
||||||
|
.filter(f -> f.endsWith(fileName) && isPrecededByPathSeparator(f, fileName))
|
||||||
|
.toArray(String[]::new);
|
||||||
|
if (result.length == 1) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return debugger.getSourceFiles().stream()
|
||||||
|
.filter(f -> {
|
||||||
|
int index = f.lastIndexOf('.');
|
||||||
|
if (index <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String nameWithoutExt = f.substring(0, index);
|
||||||
|
return nameWithoutExt.endsWith(fileName) && isPrecededByPathSeparator(nameWithoutExt, fileName);
|
||||||
|
})
|
||||||
|
.toArray(String[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
private String[] resolveFileName(String fileName) {
|
private String[] resolveFileName(String fileName) {
|
||||||
if (debugger.getSourceFiles().contains(fileName)) {
|
if (debugger.getSourceFiles().contains(fileName)) {
|
||||||
return new String[] { fileName };
|
return new String[] { fileName };
|
||||||
|
@ -294,20 +349,22 @@ public final class ChromeRDPRunner {
|
||||||
}
|
}
|
||||||
System.out.println(sb.toString());
|
System.out.println(sb.toString());
|
||||||
}
|
}
|
||||||
|
return Promise.VOID;
|
||||||
};
|
};
|
||||||
|
|
||||||
private Command frameCommand = args -> {
|
private Command frameCommand = args -> {
|
||||||
if (args.length != 2) {
|
if (args.length != 2) {
|
||||||
System.out.println("Expected 1 argument");
|
System.out.println("Expected 1 argument");
|
||||||
return;
|
return Promise.VOID;
|
||||||
}
|
}
|
||||||
int index = Integer.parseInt(args[1]);
|
int index = Integer.parseInt(args[1]);
|
||||||
int max = debugger.getCallStack().length - 1;
|
int max = debugger.getCallStack().length - 1;
|
||||||
if (index < 0 || index > max) {
|
if (index < 0 || index > max) {
|
||||||
System.out.println("Given frame index is outside of valid range 0.." + max);
|
System.out.println("Given frame index is outside of valid range 0.." + max);
|
||||||
return;
|
return Promise.VOID;
|
||||||
}
|
}
|
||||||
currentFrame = index;
|
currentFrame = index;
|
||||||
|
return Promise.VOID;
|
||||||
};
|
};
|
||||||
|
|
||||||
private Command stepCommand = args -> debugger.stepInto();
|
private Command stepCommand = args -> debugger.stepInto();
|
||||||
|
@ -319,7 +376,7 @@ public final class ChromeRDPRunner {
|
||||||
private Command infoCommand = args -> {
|
private Command infoCommand = args -> {
|
||||||
if (args.length != 2) {
|
if (args.length != 2) {
|
||||||
System.out.println("Expected 1 argument");
|
System.out.println("Expected 1 argument");
|
||||||
return;
|
return Promise.VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (args[1]) {
|
switch (args[1]) {
|
||||||
|
@ -331,25 +388,119 @@ public final class ChromeRDPRunner {
|
||||||
int id = breakpointIds.get(breakpoint);
|
int id = breakpointIds.get(breakpoint);
|
||||||
System.out.println(" #" + id + ": " + breakpoint.getLocation());
|
System.out.println(" #" + id + ": " + breakpoint.getLocation());
|
||||||
}
|
}
|
||||||
break;
|
return Promise.VOID;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "variables": {
|
case "variables": {
|
||||||
CallFrame frame = debugger.getCallStack()[currentFrame];
|
CallFrame frame = debugger.getCallStack()[currentFrame];
|
||||||
for (Variable var : frame.getVariables().values().stream()
|
return printScope(frame.getVariables());
|
||||||
.sorted(Comparator.comparing(Variable::getName))
|
|
||||||
.collect(Collectors.toList())) {
|
|
||||||
System.out.println(" " + var.getName() + ": " + var.getValue().getType());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
System.out.println("Invalid argument");
|
System.out.println("Invalid argument");
|
||||||
|
return Promise.VOID;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private Command printCommand = args -> {
|
||||||
|
if (args.length != 2) {
|
||||||
|
System.out.println("Expected 1 argument");
|
||||||
|
return Promise.VOID;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] path = args[1].split("\\.");
|
||||||
|
return followPath(path, 0, debugger.getCallStack()[currentFrame].getVariables());
|
||||||
|
};
|
||||||
|
|
||||||
|
private Promise<Void> followPath(String[] path, int index, Promise<Map<String, Variable>> scope) {
|
||||||
|
String elem = path[index];
|
||||||
|
return scope.thenAsync(map -> {
|
||||||
|
Variable var = map.get(elem);
|
||||||
|
if (var != null) {
|
||||||
|
if (index == path.length - 1) {
|
||||||
|
return variableToString(var)
|
||||||
|
.thenVoid(str -> System.out.println(str))
|
||||||
|
.thenAsync(v -> var.getValue().getType().thenAsync(type -> type.startsWith("@")
|
||||||
|
? printJsScope(var.getValue().getOriginalValue().getProperties())
|
||||||
|
: printScope(var.getValue().getProperties())));
|
||||||
|
} else {
|
||||||
|
return var.getValue().getType().thenAsync(type -> type.startsWith("@")
|
||||||
|
? followJsPath(path, index + 1, var.getValue().getOriginalValue().getProperties())
|
||||||
|
: followPath(path, index + 1, var.getValue().getProperties()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println("Invalid path specified");
|
||||||
|
return Promise.VOID;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Promise<Void> followJsPath(String[] path, int index,
|
||||||
|
Promise<Map<String, ? extends JavaScriptVariable>> scope) {
|
||||||
|
String elem = path[index];
|
||||||
|
return scope.thenAsync(map -> {
|
||||||
|
JavaScriptVariable var = map.get(elem);
|
||||||
|
if (var != null) {
|
||||||
|
if (index == path.length - 1) {
|
||||||
|
return jsVariableToString(var)
|
||||||
|
.thenVoid(str -> System.out.println(str))
|
||||||
|
.thenAsync(v -> printJsScope(var.getValue().getProperties()));
|
||||||
|
} else {
|
||||||
|
return followJsPath(path, index + 1, var.getValue().getProperties());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println("Invalid path specified");
|
||||||
|
return Promise.VOID;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Promise<Void> printScope(Promise<Map<String, Variable>> scope) {
|
||||||
|
return scope
|
||||||
|
.then(vars -> vars.values())
|
||||||
|
.then(vars -> vars.stream()
|
||||||
|
.sorted(Comparator.comparing(Variable::getName))
|
||||||
|
.map(this::variableToString)
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
)
|
||||||
|
.thenAsync(Promise::all)
|
||||||
|
.thenVoid(vars -> {
|
||||||
|
for (String var : vars) {
|
||||||
|
System.out.println(" " + var);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Promise<Void> printJsScope(Promise<Map<String, ? extends JavaScriptVariable>> scope) {
|
||||||
|
return scope
|
||||||
|
.then(vars -> vars.values())
|
||||||
|
.then(vars -> vars.stream()
|
||||||
|
.sorted(Comparator.comparing(JavaScriptVariable::getName))
|
||||||
|
.map(this::jsVariableToString)
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
)
|
||||||
|
.thenAsync(Promise::all)
|
||||||
|
.thenVoid(vars -> {
|
||||||
|
for (String var : vars) {
|
||||||
|
System.out.println(" " + var);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Promise<String> variableToString(Variable variable) {
|
||||||
|
return variable.getValue().getType()
|
||||||
|
.thenAsync(type -> variable.getValue().getRepresentation()
|
||||||
|
.then(repr -> variable.getName() + ": " + type + " (" + repr + ")"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Promise<String> jsVariableToString(JavaScriptVariable variable) {
|
||||||
|
return variable.getValue().getClassName()
|
||||||
|
.thenAsync(type -> variable.getValue().getRepresentation()
|
||||||
|
.then(repr -> variable.getName() + ": " + type + " (" + repr + ")"));
|
||||||
|
}
|
||||||
|
|
||||||
private interface Command {
|
private interface Command {
|
||||||
void execute(String[] args) throws InterruptedException;
|
Promise<Void> execute(String[] args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,38 +15,35 @@
|
||||||
*/
|
*/
|
||||||
package org.teavm.chromerdp;
|
package org.teavm.chromerdp;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import org.teavm.common.Promise;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import org.teavm.debugging.javascript.JavaScriptBreakpoint;
|
import org.teavm.debugging.javascript.JavaScriptBreakpoint;
|
||||||
import org.teavm.debugging.javascript.JavaScriptLocation;
|
import org.teavm.debugging.javascript.JavaScriptLocation;
|
||||||
|
|
||||||
class RDPBreakpoint implements JavaScriptBreakpoint {
|
class RDPBreakpoint implements JavaScriptBreakpoint {
|
||||||
volatile String chromeId;
|
|
||||||
ChromeRDPDebugger debugger;
|
ChromeRDPDebugger debugger;
|
||||||
private JavaScriptLocation location;
|
RDPNativeBreakpoint nativeBreakpoint;
|
||||||
AtomicInteger referenceCount = new AtomicInteger();
|
Promise<Void> destroyPromise;
|
||||||
final Object updateMonitor = new Object();
|
|
||||||
AtomicBoolean updating = new AtomicBoolean(true);
|
|
||||||
|
|
||||||
RDPBreakpoint(ChromeRDPDebugger debugger, JavaScriptLocation location) {
|
RDPBreakpoint(ChromeRDPDebugger debugger) {
|
||||||
this.debugger = debugger;
|
this.debugger = debugger;
|
||||||
this.location = location;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JavaScriptLocation getLocation() {
|
public JavaScriptLocation getLocation() {
|
||||||
return location;
|
return nativeBreakpoint.getLocation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroy() {
|
public Promise<Void> destroy() {
|
||||||
if (debugger != null) {
|
if (destroyPromise == null) {
|
||||||
debugger.destroyBreakpoint(this);
|
destroyPromise = debugger.destroyBreakpoint(this);
|
||||||
|
debugger = null;
|
||||||
}
|
}
|
||||||
|
return destroyPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid() {
|
public boolean isValid() {
|
||||||
return chromeId != null && debugger != null && debugger.isAttached();
|
return nativeBreakpoint != null && nativeBreakpoint.isValid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,25 +15,29 @@
|
||||||
*/
|
*/
|
||||||
package org.teavm.chromerdp;
|
package org.teavm.chromerdp;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.teavm.debugging.javascript.*;
|
import org.teavm.common.Promise;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptCallFrame;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptDebugger;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptLocation;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptValue;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptVariable;
|
||||||
|
|
||||||
class RDPCallFrame implements JavaScriptCallFrame {
|
class RDPCallFrame implements JavaScriptCallFrame {
|
||||||
private JavaScriptDebugger debugger;
|
private ChromeRDPDebugger debugger;
|
||||||
private String chromeId;
|
private String chromeId;
|
||||||
private JavaScriptLocation location;
|
private JavaScriptLocation location;
|
||||||
private Map<String, JavaScriptVariable> variables;
|
private Promise<Map<String, ? extends JavaScriptVariable>> variables;
|
||||||
private JavaScriptValue thisObject;
|
private JavaScriptValue thisObject;
|
||||||
private JavaScriptValue closure;
|
private JavaScriptValue closure;
|
||||||
|
private String scopeId;
|
||||||
|
|
||||||
RDPCallFrame(JavaScriptDebugger debugger, String chromeId, JavaScriptLocation location,
|
RDPCallFrame(ChromeRDPDebugger debugger, String chromeId, JavaScriptLocation location, String scopeId,
|
||||||
Map<String, ? extends JavaScriptVariable> variables, JavaScriptValue thisObject,
|
JavaScriptValue thisObject, JavaScriptValue closure) {
|
||||||
JavaScriptValue closure) {
|
|
||||||
this.debugger = debugger;
|
this.debugger = debugger;
|
||||||
this.chromeId = chromeId;
|
this.chromeId = chromeId;
|
||||||
this.location = location;
|
this.location = location;
|
||||||
this.variables = Collections.unmodifiableMap(variables);
|
this.scopeId = scopeId;
|
||||||
this.thisObject = thisObject;
|
this.thisObject = thisObject;
|
||||||
this.closure = closure;
|
this.closure = closure;
|
||||||
}
|
}
|
||||||
|
@ -48,7 +52,10 @@ class RDPCallFrame implements JavaScriptCallFrame {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, JavaScriptVariable> getVariables() {
|
public Promise<Map<String, ? extends JavaScriptVariable>> getVariables() {
|
||||||
|
if (variables == null) {
|
||||||
|
variables = debugger.createScope(scopeId);
|
||||||
|
}
|
||||||
return variables;
|
return variables;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 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;
|
||||||
|
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.teavm.common.Promise;
|
||||||
|
import org.teavm.debugging.javascript.JavaScriptLocation;
|
||||||
|
|
||||||
|
class RDPNativeBreakpoint {
|
||||||
|
volatile String chromeId;
|
||||||
|
ChromeRDPDebugger debugger;
|
||||||
|
private JavaScriptLocation location;
|
||||||
|
Promise<Void> initPromise;
|
||||||
|
Promise<Void> destroyPromise;
|
||||||
|
Set<RDPBreakpoint> breakpoints = new LinkedHashSet<>();
|
||||||
|
|
||||||
|
RDPNativeBreakpoint(ChromeRDPDebugger debugger, JavaScriptLocation location) {
|
||||||
|
this.debugger = debugger;
|
||||||
|
this.location = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JavaScriptLocation getLocation() {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValid() {
|
||||||
|
return chromeId != null && debugger != null && debugger.isAttached();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,64 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2014 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;
|
|
||||||
|
|
||||||
import java.util.AbstractMap;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
class RDPScope extends AbstractMap<String, RDPLocalVariable> {
|
|
||||||
private AtomicReference<Map<String, RDPLocalVariable>> backingMap = new AtomicReference<>();
|
|
||||||
private ChromeRDPDebugger debugger;
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
RDPScope(ChromeRDPDebugger debugger, String id) {
|
|
||||||
this.debugger = debugger;
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<Entry<String, RDPLocalVariable>> entrySet() {
|
|
||||||
initBackingMap();
|
|
||||||
return backingMap.get().entrySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
initBackingMap();
|
|
||||||
return backingMap.get().size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RDPLocalVariable get(Object key) {
|
|
||||||
initBackingMap();
|
|
||||||
return backingMap.get().get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initBackingMap() {
|
|
||||||
if (backingMap.get() != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Map<String, RDPLocalVariable> newBackingMap = new HashMap<>();
|
|
||||||
if (id != null) {
|
|
||||||
for (RDPLocalVariable variable : debugger.getScope(id)) {
|
|
||||||
newBackingMap.put(variable.getName(), variable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
backingMap.compareAndSet(null, newBackingMap);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,56 +15,77 @@
|
||||||
*/
|
*/
|
||||||
package org.teavm.chromerdp;
|
package org.teavm.chromerdp;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import org.teavm.chromerdp.data.RemoteObjectDTO;
|
||||||
|
import org.teavm.common.Promise;
|
||||||
import org.teavm.debugging.javascript.JavaScriptValue;
|
import org.teavm.debugging.javascript.JavaScriptValue;
|
||||||
import org.teavm.debugging.javascript.JavaScriptVariable;
|
import org.teavm.debugging.javascript.JavaScriptVariable;
|
||||||
|
|
||||||
class RDPValue implements JavaScriptValue {
|
class RDPValue implements JavaScriptValue {
|
||||||
private AtomicReference<String> representation = new AtomicReference<>();
|
|
||||||
private AtomicReference<String> className = new AtomicReference<>();
|
|
||||||
private String typeName;
|
|
||||||
private ChromeRDPDebugger debugger;
|
private ChromeRDPDebugger debugger;
|
||||||
private String objectId;
|
private String objectId;
|
||||||
private Map<String, ? extends JavaScriptVariable> properties;
|
private Promise<Map<String, ? extends JavaScriptVariable>> properties;
|
||||||
private boolean innerStructure;
|
private boolean innerStructure;
|
||||||
|
private Promise<String> className;
|
||||||
|
private Promise<String> representation;
|
||||||
|
private final String defaultRepresentation;
|
||||||
|
private final String typeName;
|
||||||
|
RemoteObjectDTO getter;
|
||||||
|
|
||||||
RDPValue(ChromeRDPDebugger debugger, String representation, String typeName, String objectId,
|
RDPValue(ChromeRDPDebugger debugger, String representation, String typeName, String objectId,
|
||||||
boolean innerStructure) {
|
boolean innerStructure) {
|
||||||
this.representation.set(representation == null && objectId == null ? "" : representation);
|
|
||||||
this.typeName = typeName;
|
|
||||||
this.debugger = debugger;
|
this.debugger = debugger;
|
||||||
this.objectId = objectId;
|
this.objectId = objectId;
|
||||||
this.innerStructure = innerStructure;
|
this.innerStructure = innerStructure;
|
||||||
properties = objectId != null ? new RDPScope(debugger, objectId) : Collections.emptyMap();
|
this.typeName = typeName;
|
||||||
|
defaultRepresentation = representation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getRepresentation() {
|
public Promise<String> getRepresentation() {
|
||||||
if (representation.get() == null) {
|
if (representation == null) {
|
||||||
representation.compareAndSet(null, debugger.getRepresentation(objectId));
|
|
||||||
}
|
|
||||||
return representation.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getClassName() {
|
|
||||||
if (className.get() == null) {
|
|
||||||
if (objectId != null) {
|
if (objectId != null) {
|
||||||
String computedClassName = debugger.getClassName(objectId);
|
representation = defaultRepresentation != null
|
||||||
className.compareAndSet(null, computedClassName != null ? computedClassName : "@Object");
|
? Promise.of(defaultRepresentation)
|
||||||
|
: debugger.getRepresentation(objectId);
|
||||||
} else {
|
} else {
|
||||||
className.compareAndSet(null, "@" + typeName);
|
representation = Promise.of(defaultRepresentation != null ? defaultRepresentation : "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return className.get();
|
return representation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, JavaScriptVariable> getProperties() {
|
public Promise<String> getClassName() {
|
||||||
return (Map<String, JavaScriptVariable>) properties;
|
if (className == null) {
|
||||||
|
if (objectId == null) {
|
||||||
|
className = Promise.of("@" + typeName);
|
||||||
|
} else {
|
||||||
|
className = debugger.getClassName(objectId).then(c -> c != null ? c : "@Object");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return className;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Promise<Map<String, ? extends JavaScriptVariable>> getProperties() {
|
||||||
|
if (properties == null) {
|
||||||
|
if (getter == null) {
|
||||||
|
properties = debugger.createScope(objectId);
|
||||||
|
} else {
|
||||||
|
properties = debugger.invokeGetter(getter.getObjectId(), objectId).then(value -> {
|
||||||
|
if (value == null) {
|
||||||
|
value = new RDPValue(debugger, "null", "null", null, false);
|
||||||
|
}
|
||||||
|
Map<String, RDPLocalVariable> map = new HashMap<>();
|
||||||
|
map.put("<value>", new RDPLocalVariable("<value>", value));
|
||||||
|
map.put("<function>", new RDPLocalVariable("<function>", debugger.mapValue(getter)));
|
||||||
|
return map;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -16,11 +16,13 @@
|
||||||
package org.teavm.chromerdp.data;
|
package org.teavm.chromerdp.data;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public class PropertyDescriptorDTO {
|
public class PropertyDescriptorDTO {
|
||||||
private String name;
|
private String name;
|
||||||
private RemoteObjectDTO value;
|
private RemoteObjectDTO value;
|
||||||
|
private RemoteObjectDTO getter;
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
|
@ -37,4 +39,14 @@ public class PropertyDescriptorDTO {
|
||||||
public void setValue(RemoteObjectDTO value) {
|
public void setValue(RemoteObjectDTO value) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonProperty("get")
|
||||||
|
public RemoteObjectDTO getGetter() {
|
||||||
|
return getter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty("get")
|
||||||
|
public void setGetter(RemoteObjectDTO getter) {
|
||||||
|
this.getter = getter;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,10 @@
|
||||||
package org.teavm.idea.debug;
|
package org.teavm.idea.debug;
|
||||||
|
|
||||||
import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType;
|
import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType;
|
||||||
import com.intellij.execution.process.ProcessHandler;
|
|
||||||
import com.intellij.execution.ui.ExecutionConsole;
|
import com.intellij.execution.ui.ExecutionConsole;
|
||||||
import com.intellij.icons.AllIcons;
|
import com.intellij.icons.AllIcons;
|
||||||
|
import com.intellij.openapi.application.Application;
|
||||||
|
import com.intellij.openapi.application.ApplicationManager;
|
||||||
import com.intellij.openapi.extensions.ExtensionPoint;
|
import com.intellij.openapi.extensions.ExtensionPoint;
|
||||||
import com.intellij.openapi.extensions.Extensions;
|
import com.intellij.openapi.extensions.Extensions;
|
||||||
import com.intellij.openapi.util.Key;
|
import com.intellij.openapi.util.Key;
|
||||||
|
@ -105,7 +106,8 @@ public class TeaVMDebugProcess extends XDebugProcess {
|
||||||
private Debugger initDebugger() {
|
private Debugger initDebugger() {
|
||||||
debugServer = new ChromeRDPServer();
|
debugServer = new ChromeRDPServer();
|
||||||
debugServer.setPort(port);
|
debugServer.setPort(port);
|
||||||
ChromeRDPDebugger chromeDebugger = new ChromeRDPDebugger();
|
Application application = ApplicationManager.getApplication();
|
||||||
|
ChromeRDPDebugger chromeDebugger = new ChromeRDPDebugger(application::invokeLater);
|
||||||
debugServer.setExchangeConsumer(chromeDebugger);
|
debugServer.setExchangeConsumer(chromeDebugger);
|
||||||
editorsProvider = new TeaVMDebuggerEditorsProvider();
|
editorsProvider = new TeaVMDebuggerEditorsProvider();
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,6 @@ public class TeaVMLineBreakpointHandler<B extends XLineBreakpoint<?>> extends XB
|
||||||
private ProjectFileIndex fileIndex;
|
private ProjectFileIndex fileIndex;
|
||||||
private TeaVMDebugProcess debugProcess;
|
private TeaVMDebugProcess debugProcess;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public TeaVMLineBreakpointHandler(Class<? extends XBreakpointType<B, ?>> breakpointType,
|
public TeaVMLineBreakpointHandler(Class<? extends XBreakpointType<B, ?>> breakpointType,
|
||||||
Project project, Debugger innerDebugger, TeaVMDebugProcess debugProcess) {
|
Project project, Debugger innerDebugger, TeaVMDebugProcess debugProcess) {
|
||||||
super(breakpointType);
|
super(breakpointType);
|
||||||
|
@ -63,10 +62,11 @@ public class TeaVMLineBreakpointHandler<B extends XLineBreakpoint<?>> extends XB
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Breakpoint innerBreakpoint = innerDebugger.createBreakpoint(path, breakpoint.getLine() + 1);
|
innerDebugger.createBreakpoint(path, breakpoint.getLine() + 1).thenVoid(innerBreakpoint -> {
|
||||||
breakpoint.putUserData(TeaVMDebugProcess.INNER_BREAKPOINT_KEY, innerBreakpoint);
|
breakpoint.putUserData(TeaVMDebugProcess.INNER_BREAKPOINT_KEY, innerBreakpoint);
|
||||||
debugProcess.breakpointMap.put(innerBreakpoint, breakpoint);
|
debugProcess.breakpointMap.put(innerBreakpoint, breakpoint);
|
||||||
debugProcess.updateBreakpointStatus(innerBreakpoint);
|
debugProcess.updateBreakpointStatus(innerBreakpoint);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|
|
@ -21,10 +21,10 @@ import com.intellij.xdebugger.frame.XNamedValue;
|
||||||
import com.intellij.xdebugger.frame.XValueChildrenList;
|
import com.intellij.xdebugger.frame.XValueChildrenList;
|
||||||
import com.intellij.xdebugger.frame.XValueNode;
|
import com.intellij.xdebugger.frame.XValueNode;
|
||||||
import com.intellij.xdebugger.frame.XValuePlace;
|
import com.intellij.xdebugger.frame.XValuePlace;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.teavm.debugging.javascript.JavaScriptValue;
|
import org.teavm.debugging.javascript.JavaScriptValue;
|
||||||
import org.teavm.debugging.javascript.JavaScriptVariable;
|
|
||||||
|
|
||||||
public class TeaVMOriginalValue extends XNamedValue {
|
public class TeaVMOriginalValue extends XNamedValue {
|
||||||
private boolean root;
|
private boolean root;
|
||||||
|
@ -39,20 +39,25 @@ public class TeaVMOriginalValue extends XNamedValue {
|
||||||
@Override
|
@Override
|
||||||
public void computePresentation(@NotNull XValueNode node, @NotNull XValuePlace place) {
|
public void computePresentation(@NotNull XValueNode node, @NotNull XValuePlace place) {
|
||||||
Icon icon = root ? PlatformIcons.VARIABLE_ICON : PlatformIcons.FIELD_ICON;
|
Icon icon = root ? PlatformIcons.VARIABLE_ICON : PlatformIcons.FIELD_ICON;
|
||||||
String representation = innerValue.getRepresentation();
|
innerValue.getRepresentation().thenVoid(representation -> {
|
||||||
if (representation == null) {
|
innerValue.getClassName().thenVoid(className -> {
|
||||||
representation = "null";
|
String nonNullRepr = representation != null ? representation : "null";
|
||||||
|
node.setPresentation(icon, className.substring(1), nonNullRepr, innerValue.hasInnerStructure());
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
node.setPresentation(icon, innerValue.getClassName(), representation, !innerValue.getProperties().isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void computeChildren(@NotNull XCompositeNode node) {
|
public void computeChildren(@NotNull XCompositeNode node) {
|
||||||
|
innerValue.getProperties().then(properties -> properties.values().stream()
|
||||||
|
.map(variable -> new TeaVMOriginalValue(variable.getName(), false, variable.getValue()))
|
||||||
|
.collect(Collectors.toList()))
|
||||||
|
.thenVoid(values -> {
|
||||||
XValueChildrenList children = new XValueChildrenList();
|
XValueChildrenList children = new XValueChildrenList();
|
||||||
for (JavaScriptVariable variable : innerValue.getProperties().values()) {
|
for (TeaVMOriginalValue value : values) {
|
||||||
children.add(new TeaVMOriginalValue(variable.getName(), false, variable.getValue()));
|
children.add(value);
|
||||||
}
|
}
|
||||||
node.addChildren(children, true);
|
node.addChildren(children, true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,11 @@ import com.intellij.xdebugger.frame.XCompositeNode;
|
||||||
import com.intellij.xdebugger.frame.XNamedValue;
|
import com.intellij.xdebugger.frame.XNamedValue;
|
||||||
import com.intellij.xdebugger.frame.XStackFrame;
|
import com.intellij.xdebugger.frame.XStackFrame;
|
||||||
import com.intellij.xdebugger.frame.XValueChildrenList;
|
import com.intellij.xdebugger.frame.XValueChildrenList;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.teavm.common.Promise;
|
||||||
import org.teavm.debugging.CallFrame;
|
import org.teavm.debugging.CallFrame;
|
||||||
import org.teavm.debugging.Value;
|
import org.teavm.debugging.Value;
|
||||||
import org.teavm.debugging.Variable;
|
import org.teavm.debugging.Variable;
|
||||||
|
@ -74,16 +77,33 @@ class TeaVMStackFrame extends XStackFrame {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void computeChildren(@NotNull XCompositeNode node) {
|
public void computeChildren(@NotNull XCompositeNode node) {
|
||||||
XValueChildrenList children = new XValueChildrenList();
|
computeChildrenImpl(node, innerFrame.getVariables(), true);
|
||||||
for (Variable variable : innerFrame.getVariables().values()) {
|
|
||||||
children.add(createValueNode(variable.getName(), true, variable.getValue()));
|
|
||||||
}
|
|
||||||
node.addChildren(children, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static XNamedValue createValueNode(String name, boolean root, Value value) {
|
static void computeChildrenImpl(XCompositeNode node, Promise<Map<String, Variable>> variablesPromise,
|
||||||
return !value.getType().startsWith("@")
|
boolean root) {
|
||||||
|
variablesPromise.then(variables -> variables.values()
|
||||||
|
.stream()
|
||||||
|
.map(var -> createValueNode(var.getName(), root, var.getValue()))
|
||||||
|
.collect(Collectors.toList()))
|
||||||
|
.thenAsync(Promise::all)
|
||||||
|
.thenVoid(values -> {
|
||||||
|
XValueChildrenList children = new XValueChildrenList();
|
||||||
|
for (XNamedValue value : values) {
|
||||||
|
children.add(value);
|
||||||
|
}
|
||||||
|
node.addChildren(children, true);
|
||||||
|
})
|
||||||
|
.catchError(e -> {
|
||||||
|
node.setErrorMessage("Error occurred calculating scope: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static Promise<XNamedValue> createValueNode(String name, boolean root, Value value) {
|
||||||
|
return value.getType().then(type -> !type.startsWith("@")
|
||||||
? new TeaVMValue(name, root, value)
|
? new TeaVMValue(name, root, value)
|
||||||
: new TeaVMOriginalValue(name, root, value.getOriginalValue());
|
: new TeaVMOriginalValue(name, root, value.getOriginalValue()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,13 +18,15 @@ package org.teavm.idea.debug;
|
||||||
import com.intellij.util.PlatformIcons;
|
import com.intellij.util.PlatformIcons;
|
||||||
import com.intellij.xdebugger.frame.XCompositeNode;
|
import com.intellij.xdebugger.frame.XCompositeNode;
|
||||||
import com.intellij.xdebugger.frame.XNamedValue;
|
import com.intellij.xdebugger.frame.XNamedValue;
|
||||||
import com.intellij.xdebugger.frame.XValueChildrenList;
|
|
||||||
import com.intellij.xdebugger.frame.XValueNode;
|
import com.intellij.xdebugger.frame.XValueNode;
|
||||||
import com.intellij.xdebugger.frame.XValuePlace;
|
import com.intellij.xdebugger.frame.XValuePlace;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.teavm.common.Promise;
|
||||||
import org.teavm.debugging.Value;
|
import org.teavm.debugging.Value;
|
||||||
import org.teavm.debugging.Variable;
|
import org.teavm.debugging.Variable;
|
||||||
|
|
||||||
|
@ -41,41 +43,58 @@ public class TeaVMValue extends XNamedValue {
|
||||||
@Override
|
@Override
|
||||||
public void computePresentation(@NotNull XValueNode node, @NotNull XValuePlace place) {
|
public void computePresentation(@NotNull XValueNode node, @NotNull XValuePlace place) {
|
||||||
Icon icon = root ? PlatformIcons.VARIABLE_ICON : PlatformIcons.FIELD_ICON;
|
Icon icon = root ? PlatformIcons.VARIABLE_ICON : PlatformIcons.FIELD_ICON;
|
||||||
String representation = innerValue.getRepresentation();
|
innerValue.getRepresentation()
|
||||||
if (representation == null) {
|
.then(representation -> representation != null ? representation : "null")
|
||||||
representation = "null";
|
.thenVoid(representation -> {
|
||||||
|
innerValue.getType().thenVoid(type -> {
|
||||||
|
if (Objects.equals(type, "java.lang.String")) {
|
||||||
|
getStringRepresentation().thenVoid(str -> node.setPresentation(icon, type, str, true));
|
||||||
|
} else {
|
||||||
|
node.setPresentation(icon, type, representation, innerValue.hasInnerStructure());
|
||||||
}
|
}
|
||||||
if (Objects.equals(innerValue.getType(), "java.lang.String")) {
|
});
|
||||||
representation = getStringRepresentation();
|
});
|
||||||
}
|
|
||||||
node.setPresentation(icon, innerValue.getType(), representation, !innerValue.getProperties().isEmpty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getStringRepresentation() {
|
private Promise<String> getStringRepresentation() {
|
||||||
Variable charactersProperty = innerValue.getProperties().get("characters");
|
return innerValue.getProperties().thenAsync(properties -> {
|
||||||
if (charactersProperty != null) {
|
Variable charactersProperty = properties.get("characters");
|
||||||
Variable dataProperty = charactersProperty.getValue().getProperties().get("data");
|
if (charactersProperty == null) {
|
||||||
if (dataProperty != null) {
|
return errorString();
|
||||||
Value dataValue = dataProperty.getValue();
|
}
|
||||||
int[] indexes = dataValue.getProperties().keySet().stream()
|
return charactersProperty.getValue().getProperties().thenAsync(charsProperties -> {
|
||||||
|
Variable dataProperty = charsProperties.get("data");
|
||||||
|
if (dataProperty == null) {
|
||||||
|
return errorString();
|
||||||
|
}
|
||||||
|
return dataProperty.getValue().getProperties().thenAsync(dataValueProperties -> {
|
||||||
|
int[] indexes = dataValueProperties.keySet().stream()
|
||||||
.filter(t -> isDigits(t))
|
.filter(t -> isDigits(t))
|
||||||
.mapToInt(Integer::parseInt)
|
.mapToInt(Integer::parseInt)
|
||||||
.toArray();
|
.toArray();
|
||||||
int maxIndex = Math.min(Arrays.stream(indexes).max().orElse(-1) + 1, 256);
|
int maxIndex = Math.min(Arrays.stream(indexes).max().orElse(-1) + 1, 256);
|
||||||
char[] chars = new char[maxIndex];
|
char[] chars = new char[maxIndex];
|
||||||
|
List<Promise<Void>> promises = new ArrayList<>();
|
||||||
for (int i = 0; i < maxIndex; ++i) {
|
for (int i = 0; i < maxIndex; ++i) {
|
||||||
Variable charProperty = dataValue.getProperties().get(Integer.toString(i));
|
Variable charProperty = dataValueProperties.get(Integer.toString(i));
|
||||||
if (charProperty != null) {
|
if (charProperty != null) {
|
||||||
String charRepr = charProperty.getValue().getRepresentation();
|
int index = i;
|
||||||
|
promises.add(charProperty.getValue().getRepresentation().thenVoid(charRepr -> {
|
||||||
if (isDigits(charRepr)) {
|
if (isDigits(charRepr)) {
|
||||||
chars[i] = (char) Integer.parseInt(charRepr);
|
chars[index] = (char) Integer.parseInt(charRepr);
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Promise.allVoid(promises).thenAsync(v -> Promise.of(new String(chars)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return new String(chars);
|
|
||||||
}
|
private Promise<String> errorString() {
|
||||||
}
|
return Promise.of("<could not calculate string value>");
|
||||||
return "<could not calculate string value>";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isDigits(String str) {
|
private static boolean isDigits(String str) {
|
||||||
|
@ -90,10 +109,6 @@ public class TeaVMValue extends XNamedValue {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void computeChildren(@NotNull XCompositeNode node) {
|
public void computeChildren(@NotNull XCompositeNode node) {
|
||||||
XValueChildrenList children = new XValueChildrenList();
|
TeaVMStackFrame.computeChildrenImpl(node, innerValue.getProperties(), false);
|
||||||
for (Variable variable : innerValue.getProperties().values()) {
|
|
||||||
children.add(TeaVMStackFrame.createValueNode(variable.getName(), false, variable.getValue()));
|
|
||||||
}
|
|
||||||
node.addChildren(children, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user