From 0cb0bd469b971c87f2276a7c628f1effc640d03a Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Mon, 28 Jul 2014 23:54:33 +0400 Subject: [PATCH] Begin implementation of google chrome debugger backend --- pom.xml | 25 ++- teavm-chrome-rdp/.gitignore | 4 + teavm-chrome-rdp/pom.xml | 88 ++++++++ .../teavm/chromerpd/ChromeRDPDebugger.java | 202 ++++++++++++++++++ .../org/teavm/chromerpd/RDPCallFrame.java | 42 ++++ .../teavm/chromerpd/data/CallFrameDTO.java | 41 ++++ .../org/teavm/chromerpd/data/LocationDTO.java | 50 +++++ .../org/teavm/chromerpd/data/Message.java | 43 ++++ .../messages/ContinueToLocationCommand.java | 34 +++ .../messages/ScriptParsedNotification.java | 41 ++++ .../messages/SuspendedNotification.java | 53 +++++ .../org/teavm/debugging/DebugInformation.java | 10 +- .../debugging/DebugInformationProvider.java | 24 +++ .../java/org/teavm/debugging/Debugger.java | 78 +++++-- .../teavm/debugging/JavaScriptBreakpoint.java | 2 +- .../teavm/debugging/JavaScriptCallFrame.java | 2 +- .../teavm/debugging/JavaScriptDebugger.java | 4 +- .../debugging/JavaScriptDebuggerListener.java | 2 + .../teavm/debugging/JavaScriptLocation.java | 44 ++++ .../URLDebugInformationProvider.java | 44 ++++ 20 files changed, 810 insertions(+), 23 deletions(-) create mode 100644 teavm-chrome-rdp/.gitignore create mode 100644 teavm-chrome-rdp/pom.xml create mode 100644 teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/ChromeRDPDebugger.java create mode 100644 teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/RDPCallFrame.java create mode 100644 teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/data/CallFrameDTO.java create mode 100644 teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/data/LocationDTO.java create mode 100644 teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/data/Message.java create mode 100644 teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/messages/ContinueToLocationCommand.java create mode 100644 teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/messages/ScriptParsedNotification.java create mode 100644 teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/messages/SuspendedNotification.java create mode 100644 teavm-core/src/main/java/org/teavm/debugging/DebugInformationProvider.java create mode 100644 teavm-core/src/main/java/org/teavm/debugging/JavaScriptLocation.java create mode 100644 teavm-core/src/main/java/org/teavm/debugging/URLDebugInformationProvider.java diff --git a/pom.xml b/pom.xml index 68e4aa6a2..95f370fc7 100644 --- a/pom.xml +++ b/pom.xml @@ -65,8 +65,10 @@ UTF-8 - 0.7.5 https://oss.sonatype.org/content/repositories/snapshots/ + 0.7.5 + 9.2.1.v20140609 + 1.7.7 @@ -79,6 +81,7 @@ teavm-samples teavm-platform teavm-cli + teavm-chrome-rdp @@ -134,6 +137,26 @@ maven-core 3.0 + + javax.websocket + javax.websocket-api + 1.0 + + + org.eclipse.jetty + jetty-server + ${jetty.version} + + + org.eclipse.jetty.websocket + javax-websocket-server-impl + ${jetty.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + diff --git a/teavm-chrome-rdp/.gitignore b/teavm-chrome-rdp/.gitignore new file mode 100644 index 000000000..c708c363d --- /dev/null +++ b/teavm-chrome-rdp/.gitignore @@ -0,0 +1,4 @@ +/target +/.settings +/.classpath +/.project diff --git a/teavm-chrome-rdp/pom.xml b/teavm-chrome-rdp/pom.xml new file mode 100644 index 000000000..580e21dc2 --- /dev/null +++ b/teavm-chrome-rdp/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + + org.teavm + teavm + 0.2-SNAPSHOT + + teavm-chrome-rdp + + TeaVM debugging backend for Google Chrome RDP + TeaVM debugging backend for Google Chrome RDP + + + + org.eclipse.jetty + jetty-server + + + org.eclipse.jetty.websocket + javax-websocket-server-impl + + + org.teavm + teavm-core + ${project.version} + + + org.slf4j + slf4j-api + + + javax.websocket + javax.websocket-api + + + org.eclipse.jetty + jetty-server + + + org.eclipse.jetty.websocket + javax-websocket-server-impl + + + org.slf4j + slf4j-api + + + org.codehaus.jackson + jackson-core-asl + 1.9.13 + + + org.codehaus.jackson + jackson-mapper-asl + 1.9.13 + + + + + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + \ No newline at end of file diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/ChromeRDPDebugger.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/ChromeRDPDebugger.java new file mode 100644 index 000000000..4d9cb2590 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/ChromeRDPDebugger.java @@ -0,0 +1,202 @@ +/* + * 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.chromerpd; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.server.ServerEndpoint; +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.JsonProcessingException; +import org.codehaus.jackson.map.ObjectMapper; +import org.teavm.chromerpd.data.CallFrameDTO; +import org.teavm.chromerpd.data.LocationDTO; +import org.teavm.chromerpd.data.Message; +import org.teavm.chromerpd.messages.ContinueToLocationCommand; +import org.teavm.chromerpd.messages.ScriptParsedNotification; +import org.teavm.chromerpd.messages.SuspendedNotification; +import org.teavm.debugging.*; + +/** + * + * @author Alexey Andreev + */ +@ServerEndpoint("/") +public class ChromeRDPDebugger implements JavaScriptDebugger { + private List listeners = new ArrayList<>(); + private Session session; + private RDPCallFrame[] callStack = new RDPCallFrame[0]; + private Map scripts = new HashMap<>(); + private Map scriptIds = new HashMap<>(); + private boolean suspended; + private ObjectMapper mapper = new ObjectMapper(); + + @OnOpen + public void open(Session session) { + this.session = session; + } + + @OnMessage + public void receive(String messageText) throws IOException { + Message message = mapper.reader(Message.class).readValue(messageText); + switch (message.getMethod()) { + case "Debugger.paused": + firePaused(parseJson(SuspendedNotification.class, message.getParams())); + break; + case "Debugger.resumed": + fireResumed(); + break; + case "Debugger.scriptParsed": + scriptParsed(parseJson(ScriptParsedNotification.class, message.getParams())); + break; + } + } + + private T parseJson(Class type, JsonNode node) throws IOException { + return mapper.reader(type).readValue(node); + } + + private void firePaused(SuspendedNotification params) { + suspended = true; + CallFrameDTO[] callFrameDTOs = params.getCallFrames(); + RDPCallFrame[] callFrames = new RDPCallFrame[callFrameDTOs.length]; + for (int i = 0; i < callFrames.length; ++i) { + callFrames[i] = map(callFrameDTOs[i]); + } + for (JavaScriptDebuggerListener listener : listeners) { + listener.paused(); + } + } + + RDPCallFrame map(CallFrameDTO dto) { + return new RDPCallFrame(dto.getCallFrameId(), map(dto.getLocation())); + } + + JavaScriptLocation map(LocationDTO dto) { + return new JavaScriptLocation(scripts.get(dto.getScriptId()), dto.getLineNumber(), dto.getColumnNumber()); + } + + private void fireResumed() { + suspended = false; + for (JavaScriptDebuggerListener listener : listeners) { + listener.resumed(); + } + } + + private void scriptParsed(ScriptParsedNotification params) { + if (scripts.containsKey(params.getScriptId())) { + return; + } + scripts.put(params.getScriptId(), params.getUrl()); + scriptIds.put(params.getUrl(), params.getScriptId()); + for (JavaScriptDebuggerListener listener : listeners) { + listener.scriptAdded(params.getUrl()); + } + } + + @Override + public void addListener(JavaScriptDebuggerListener listener) { + listeners.add(listener); + } + + @Override + public void removeListener(JavaScriptDebuggerListener listener) { + listeners.remove(listener); + } + + private void sendMessage(Message message) { + try { + String messageText = mapper.writer().writeValueAsString(message); + session.getAsyncRemote().sendText(messageText); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void suspend() { + Message message = new Message(); + message.setMethod("Debugger.pause"); + sendMessage(message); + } + + @Override + public void resume() { + Message message = new Message(); + message.setMethod("Debugger.resume"); + sendMessage(message); + } + + @Override + public void stepInto() { + Message message = new Message(); + message.setMethod("Debugger.stepInto"); + sendMessage(message); + } + + @Override + public void stepOut() { + Message message = new Message(); + message.setMethod("Debugger.stepOut"); + sendMessage(message); + } + + @Override + public void stepOver() { + Message message = new Message(); + message.setMethod("Debugger.stepOver"); + sendMessage(message); + } + + @Override + public void continueToLocation(JavaScriptLocation location) { + Message message = new Message(); + message.setMethod("Debugger.continueToLocation"); + ContinueToLocationCommand params = new ContinueToLocationCommand(); + LocationDTO locationDTO = new LocationDTO(); + locationDTO.setScriptId(scriptIds.get(location.getScript())); + locationDTO.setLineNumber(location.getLine()); + locationDTO.setColumnNumber(location.getColumn()); + params.setLocation(locationDTO); + message.setParams(mapper.valueToTree(params)); + sendMessage(message); + } + + @Override + public boolean isSuspended() { + return suspended; + } + + @Override + public JavaScriptCallFrame[] getCallStack() { + return callStack; + } + + @Override + public JavaScriptBreakpoint getCurrentBreakpoint() { + return null; + } + + @Override + public JavaScriptBreakpoint createBreakpoint(JavaScriptLocation location) { + return null; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/RDPCallFrame.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/RDPCallFrame.java new file mode 100644 index 000000000..32cf1f7a1 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/RDPCallFrame.java @@ -0,0 +1,42 @@ +/* + * 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.chromerpd; + +import org.teavm.debugging.JavaScriptCallFrame; +import org.teavm.debugging.JavaScriptLocation; + +/** + * + * @author Alexey Andreev + */ +public class RDPCallFrame implements JavaScriptCallFrame { + private String chromeId; + private JavaScriptLocation location; + + public RDPCallFrame(String chromeId, JavaScriptLocation location) { + this.chromeId = chromeId; + this.location = location; + } + + public String getChromeId() { + return chromeId; + } + + @Override + public JavaScriptLocation getLocation() { + return location; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/data/CallFrameDTO.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/data/CallFrameDTO.java new file mode 100644 index 000000000..a0fc1bb15 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/data/CallFrameDTO.java @@ -0,0 +1,41 @@ +/* + * 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.chromerpd.data; + +/** + * + * @author Alexey Andreev + */ +public class CallFrameDTO { + private String callFrameId; + private LocationDTO location; + + public String getCallFrameId() { + return callFrameId; + } + + public void setCallFrameId(String callFrameId) { + this.callFrameId = callFrameId; + } + + public LocationDTO getLocation() { + return location; + } + + public void setLocation(LocationDTO location) { + this.location = location; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/data/LocationDTO.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/data/LocationDTO.java new file mode 100644 index 000000000..8136468a4 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/data/LocationDTO.java @@ -0,0 +1,50 @@ +/* + * 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.chromerpd.data; + +/** + * + * @author Alexey Andreev + */ +public class LocationDTO { + private int columnNumber; + private int lineNumber; + private String scriptId; + + public int getColumnNumber() { + return columnNumber; + } + + public void setColumnNumber(int columnNumber) { + this.columnNumber = columnNumber; + } + + public int getLineNumber() { + return lineNumber; + } + + public void setLineNumber(int lineNumber) { + this.lineNumber = lineNumber; + } + + public String getScriptId() { + return scriptId; + } + + public void setScriptId(String scriptId) { + this.scriptId = scriptId; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/data/Message.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/data/Message.java new file mode 100644 index 000000000..ff8675522 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/data/Message.java @@ -0,0 +1,43 @@ +/* + * 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.chromerpd.data; + +import org.codehaus.jackson.JsonNode; + +/** + * + * @author Alexey Andreev + */ +public class Message { + private String method; + private JsonNode params; + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public JsonNode getParams() { + return params; + } + + public void setParams(JsonNode params) { + this.params = params; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/messages/ContinueToLocationCommand.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/messages/ContinueToLocationCommand.java new file mode 100644 index 000000000..ce70864dd --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/messages/ContinueToLocationCommand.java @@ -0,0 +1,34 @@ +/* + * 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.chromerpd.messages; + +import org.teavm.chromerpd.data.LocationDTO; + +/** + * + * @author Alexey Andreev + */ +public class ContinueToLocationCommand { + private LocationDTO location; + + public LocationDTO getLocation() { + return location; + } + + public void setLocation(LocationDTO location) { + this.location = location; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/messages/ScriptParsedNotification.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/messages/ScriptParsedNotification.java new file mode 100644 index 000000000..9fab6dcbc --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/messages/ScriptParsedNotification.java @@ -0,0 +1,41 @@ +/* + * 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.chromerpd.messages; + +/** + * + * @author Alexey Andreev + */ +public class ScriptParsedNotification { + private String scriptId; + private String url; + + public String getScriptId() { + return scriptId; + } + + public void setScriptId(String scriptId) { + this.scriptId = scriptId; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/messages/SuspendedNotification.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/messages/SuspendedNotification.java new file mode 100644 index 000000000..a1e990d6e --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerpd/messages/SuspendedNotification.java @@ -0,0 +1,53 @@ +/* + * 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.chromerpd.messages; + +import org.codehaus.jackson.JsonNode; +import org.teavm.chromerpd.data.CallFrameDTO; + +/** + * + * @author Alexey Andreev + */ +public class SuspendedNotification { + private CallFrameDTO[] callFrames; + private String reason; + private JsonNode data; + + public CallFrameDTO[] getCallFrames() { + return callFrames; + } + + public void setCallFrames(CallFrameDTO[] callFrames) { + this.callFrames = callFrames; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + public JsonNode getData() { + return data; + } + + public void setData(JsonNode data) { + this.data = data; + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/DebugInformation.java b/teavm-core/src/main/java/org/teavm/debugging/DebugInformation.java index c9415ac21..9cdf877f5 100644 --- a/teavm-core/src/main/java/org/teavm/debugging/DebugInformation.java +++ b/teavm-core/src/main/java/org/teavm/debugging/DebugInformation.java @@ -41,6 +41,10 @@ public class DebugInformation { Mapping methodMapping; Mapping lineMapping; + public String[] getCoveredSourceFiles() { + return fileNames.clone(); + } + public Collection getGeneratedLocations(String fileName, int line) { Integer fileIndex = fileNameMap.get(fileName); if (fileIndex == null) { @@ -90,6 +94,10 @@ public class DebugInformation { return new MethodReference(className, MethodDescriptor.parse(method)); } + public MethodReference getMethodAt(int line, int column) { + return getMethodAt(new GeneratedLocation(line, column)); + } + private T componentByKey(Mapping mapping, T[] values, GeneratedLocation location) { int keyIndex = indexByKey(mapping, location); int valueIndex = keyIndex >= 0 ? mapping.values[keyIndex] : -1; @@ -106,7 +114,7 @@ public class DebugInformation { writer.write(this); } - public DebugInformation read(InputStream input) throws IOException { + public static DebugInformation read(InputStream input) throws IOException { DebugInformationReader reader = new DebugInformationReader(input); return reader.read(); } diff --git a/teavm-core/src/main/java/org/teavm/debugging/DebugInformationProvider.java b/teavm-core/src/main/java/org/teavm/debugging/DebugInformationProvider.java new file mode 100644 index 000000000..9c8ccafb1 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/DebugInformationProvider.java @@ -0,0 +1,24 @@ +/* + * 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; + +/** + * + * @author Alexey Andreev + */ +public interface DebugInformationProvider { + DebugInformation getDebugInformation(String script); +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/Debugger.java b/teavm-core/src/main/java/org/teavm/debugging/Debugger.java index e77a77920..77ac0f960 100644 --- a/teavm-core/src/main/java/org/teavm/debugging/Debugger.java +++ b/teavm-core/src/main/java/org/teavm/debugging/Debugger.java @@ -27,14 +27,17 @@ import org.teavm.model.MethodReference; public class Debugger { private List listeners = new ArrayList<>(); private JavaScriptDebugger javaScriptDebugger; - private DebugInformation debugInformation; + private DebugInformationProvider debugInformationProvider; private List temporaryJsBreakpoints = new ArrayList<>(); + private Map debugInformationMap = new HashMap<>(); + private Map> debugInformationFileMap = new HashMap<>(); + private Map scriptMap = new HashMap<>(); Map breakpointMap = new HashMap<>(); private CallFrame[] callStack; - public Debugger(JavaScriptDebugger javaScriptDebugger, DebugInformation debugInformation) { + public Debugger(JavaScriptDebugger javaScriptDebugger, DebugInformationProvider debugInformationProvider) { this.javaScriptDebugger = javaScriptDebugger; - this.debugInformation = debugInformation; + this.debugInformationProvider = debugInformationProvider; javaScriptDebugger.addListener(javaScriptListener); } @@ -65,7 +68,6 @@ public class Debugger { if (!javaScriptDebugger.isSuspended()) { return; } - // TODO: lookup all locations that are "out of" the current location and create temporary breakpoints javaScriptDebugger.stepOut(); } @@ -74,18 +76,26 @@ public class Debugger { return; } javaScriptDebugger.stepOver(); - // TODO: lookup all locations that are "after" the current location and create temporary breakpoints + } + + private List debugInformationBySource(String sourceFile) { + List list = debugInformationFileMap.get(sourceFile); + return list != null ? list : Collections.emptyList(); } public void continueToLocation(String fileName, int line) { if (!javaScriptDebugger.isSuspended()) { return; } - Collection locations = debugInformation.getGeneratedLocations(fileName, line); - for (GeneratedLocation location : locations) { - JavaScriptBreakpoint jsBreakpoint = javaScriptDebugger.createBreakpoint(location); - if (jsBreakpoint != null) { - temporaryJsBreakpoints.add(jsBreakpoint); + for (DebugInformation debugInformation : debugInformationBySource(fileName)) { + Collection locations = debugInformation.getGeneratedLocations(fileName, line); + for (GeneratedLocation location : locations) { + JavaScriptLocation jsLocation = new JavaScriptLocation(scriptMap.get(debugInformation), + location.getLine(), location.getColumn()); + JavaScriptBreakpoint jsBreakpoint = javaScriptDebugger.createBreakpoint(jsLocation); + if (jsBreakpoint != null) { + temporaryJsBreakpoints.add(jsBreakpoint); + } } } javaScriptDebugger.resume(); @@ -96,12 +106,16 @@ public class Debugger { } public Breakpoint createBreakpoint(String fileName, int line) { - Collection locations = debugInformation.getGeneratedLocations(fileName, line); List jsBreakpoints = new ArrayList<>(); - for (GeneratedLocation location : locations) { - JavaScriptBreakpoint jsBreakpoint = javaScriptDebugger.createBreakpoint(location); - if (jsBreakpoint != null) { - jsBreakpoints.add(jsBreakpoint); + for (DebugInformation debugInformation : debugInformationBySource(fileName)) { + Collection locations = debugInformation.getGeneratedLocations(fileName, line); + for (GeneratedLocation location : locations) { + JavaScriptLocation jsLocation = new JavaScriptLocation(scriptMap.get(debugInformation), + location.getLine(), location.getColumn()); + JavaScriptBreakpoint jsBreakpoint = javaScriptDebugger.createBreakpoint(jsLocation); + if (jsBreakpoint != null) { + jsBreakpoints.add(jsBreakpoint); + } } } return !jsBreakpoints.isEmpty() ? new Breakpoint(this, jsBreakpoints, fileName, line) : null; @@ -117,9 +131,17 @@ public class Debugger { List frames = new ArrayList<>(); boolean wasEmpty = false; for (JavaScriptCallFrame jsFrame : javaScriptDebugger.getCallStack()) { - SourceLocation loc = debugInformation.getSourceLocation(jsFrame.getLocation()); + DebugInformation debugInformation = debugInformationMap.get(jsFrame.getLocation().getScript()); + SourceLocation loc; + if (debugInformation != null) { + loc = debugInformation.getSourceLocation(jsFrame.getLocation().getLine(), + jsFrame.getLocation().getColumn()); + } else { + loc = null; + } boolean empty = loc == null || (loc.getFileName() == null && loc.getLine() < 0); - MethodReference method = !empty ? debugInformation.getMethodAt(jsFrame.getLocation()) : null; + MethodReference method = !empty ? debugInformation.getMethodAt(jsFrame.getLocation().getLine(), + jsFrame.getLocation().getColumn()) : null; if (!empty || !wasEmpty) { frames.add(new CallFrame(loc, method)); } @@ -130,6 +152,23 @@ public class Debugger { return callStack.clone(); } + private void addScript(String name) { + if (debugInformationMap.containsKey(name)) { + return; + } + DebugInformation debugInfo = debugInformationProvider.getDebugInformation(name); + debugInformationMap.put(name, debugInfo); + for (String sourceFile : debugInfo.getCoveredSourceFiles()) { + List list = debugInformationFileMap.get(sourceFile); + if (list == null) { + list = new ArrayList<>(); + debugInformationFileMap.put(sourceFile, list); + } + list.add(debugInfo); + } + scriptMap.put(debugInfo, name); + } + private JavaScriptDebuggerListener javaScriptListener = new JavaScriptDebuggerListener() { @Override public void resumed() { @@ -148,5 +187,10 @@ public class Debugger { listener.paused(); } } + + @Override + public void scriptAdded(String name) { + addScript(name); + } }; } diff --git a/teavm-core/src/main/java/org/teavm/debugging/JavaScriptBreakpoint.java b/teavm-core/src/main/java/org/teavm/debugging/JavaScriptBreakpoint.java index 5923c7426..f6934836f 100644 --- a/teavm-core/src/main/java/org/teavm/debugging/JavaScriptBreakpoint.java +++ b/teavm-core/src/main/java/org/teavm/debugging/JavaScriptBreakpoint.java @@ -28,7 +28,7 @@ public interface JavaScriptBreakpoint { String getFileName(); - GeneratedLocation getLocation(); + JavaScriptLocation getLocation(); void destroy(); } diff --git a/teavm-core/src/main/java/org/teavm/debugging/JavaScriptCallFrame.java b/teavm-core/src/main/java/org/teavm/debugging/JavaScriptCallFrame.java index 21bfcbbed..a0a6d4c4a 100644 --- a/teavm-core/src/main/java/org/teavm/debugging/JavaScriptCallFrame.java +++ b/teavm-core/src/main/java/org/teavm/debugging/JavaScriptCallFrame.java @@ -20,5 +20,5 @@ package org.teavm.debugging; * @author Alexey Andreev */ public interface JavaScriptCallFrame { - GeneratedLocation getLocation(); + JavaScriptLocation getLocation(); } diff --git a/teavm-core/src/main/java/org/teavm/debugging/JavaScriptDebugger.java b/teavm-core/src/main/java/org/teavm/debugging/JavaScriptDebugger.java index b8114193a..a6cfcfcbc 100644 --- a/teavm-core/src/main/java/org/teavm/debugging/JavaScriptDebugger.java +++ b/teavm-core/src/main/java/org/teavm/debugging/JavaScriptDebugger.java @@ -34,7 +34,7 @@ public interface JavaScriptDebugger { void stepOver(); - void continueToLocation(GeneratedLocation location); + void continueToLocation(JavaScriptLocation location); boolean isSuspended(); @@ -42,5 +42,5 @@ public interface JavaScriptDebugger { JavaScriptBreakpoint getCurrentBreakpoint(); - JavaScriptBreakpoint createBreakpoint(GeneratedLocation location); + JavaScriptBreakpoint createBreakpoint(JavaScriptLocation location); } diff --git a/teavm-core/src/main/java/org/teavm/debugging/JavaScriptDebuggerListener.java b/teavm-core/src/main/java/org/teavm/debugging/JavaScriptDebuggerListener.java index a8b076bba..a7e99a37e 100644 --- a/teavm-core/src/main/java/org/teavm/debugging/JavaScriptDebuggerListener.java +++ b/teavm-core/src/main/java/org/teavm/debugging/JavaScriptDebuggerListener.java @@ -23,4 +23,6 @@ public interface JavaScriptDebuggerListener { void paused(); void resumed(); + + void scriptAdded(String name); } diff --git a/teavm-core/src/main/java/org/teavm/debugging/JavaScriptLocation.java b/teavm-core/src/main/java/org/teavm/debugging/JavaScriptLocation.java new file mode 100644 index 000000000..2cee84c2d --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/JavaScriptLocation.java @@ -0,0 +1,44 @@ +/* + * 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; + +/** + * + * @author Alexey Andreev + */ +public class JavaScriptLocation { + private String script; + private int line; + private int column; + + public JavaScriptLocation(String script, int line, int column) { + this.script = script; + this.line = line; + this.column = column; + } + + public String getScript() { + return script; + } + + public int getLine() { + return line; + } + + public int getColumn() { + return column; + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/URLDebugInformationProvider.java b/teavm-core/src/main/java/org/teavm/debugging/URLDebugInformationProvider.java new file mode 100644 index 000000000..8159a9cc4 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/URLDebugInformationProvider.java @@ -0,0 +1,44 @@ +/* + * 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.io.IOException; +import java.io.InputStream; +import java.net.URL; + +/** + * + * @author Alexey Andreev + */ +public class URLDebugInformationProvider implements DebugInformationProvider { + private String baseURL; + + public URLDebugInformationProvider(String baseURL) { + this.baseURL = baseURL; + } + + @Override + public DebugInformation getDebugInformation(String script) { + try { + URL url = new URL(baseURL + script); + try (InputStream input = url.openStream()) { + return DebugInformation.read(input); + } + } catch (IOException e) { + return null; + } + } +}