Wasm: working on Chrome RDP debugger

This commit is contained in:
Alexey Andreev 2022-11-29 18:14:13 +01:00
parent d1feec7ae6
commit 9d3927e196
12 changed files with 480 additions and 177 deletions

View File

@ -101,7 +101,6 @@ public class DebugInfoParser {
private Promise<Void> parseSection(int limit) {
return readString("Error reading custom section name").thenAsync(name -> {
var sectionParser = sectionParsers.get(name);
System.out.println("Section '" + name + "': " + (sectionParser != null ? "parsed" : "unknown"));
if (sectionParser != null) {
return readBytes(limit - pos, "Error reading section '" + name + "' content")
.thenVoid(sectionParser::parse);

View File

@ -320,6 +320,17 @@ public class Promise<T> {
}
}
public static void runNow(Runnable runnable) {
var queue = processing.get();
if (queue == null) {
runnable.run();
} else {
processing.remove();
runnable.run();
processing.set(queue);
}
}
enum State {
PENDING,
WAITING_PROMISE,

View File

@ -0,0 +1,196 @@
/*
* Copyright 2022 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.chromerdp;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.teavm.chromerdp.data.Message;
import org.teavm.chromerdp.data.Response;
import org.teavm.common.CompletablePromise;
import org.teavm.common.Promise;
import org.teavm.debugging.javascript.JavaScriptDebuggerListener;
public abstract class BaseChromeRDPDebugger implements ChromeRDPExchangeConsumer {
protected static final Logger logger = LoggerFactory.getLogger(BaseChromeRDPDebugger.class);
private ChromeRDPExchange exchange;
protected Set<JavaScriptDebuggerListener> listeners = new LinkedHashSet<>();
protected ObjectMapper mapper = new ObjectMapper();
private ConcurrentMap<Integer, ChromeRDPDebugger.ResponseHandler<Object>>
responseHandlers = new ConcurrentHashMap<>();
private ConcurrentMap<Integer, CompletablePromise<Object>> promises = new ConcurrentHashMap<>();
private int messageIdGenerator;
protected List<JavaScriptDebuggerListener> getListeners() {
return new ArrayList<>(listeners);
}
private Executor executor;
public BaseChromeRDPDebugger(Executor executor) {
this.executor = executor;
}
@Override
public void setExchange(ChromeRDPExchange exchange) {
if (this.exchange == exchange) {
return;
}
if (this.exchange != null) {
this.exchange.removeListener(exchangeListener);
}
this.exchange = exchange;
if (exchange != null) {
onAttach();
for (var listener : getListeners()) {
listener.attached();
}
} else {
onDetach();
for (var listener : getListeners()) {
listener.detached();
}
}
if (this.exchange != null) {
this.exchange.addListener(exchangeListener);
}
}
private ChromeRDPExchangeListener exchangeListener = messageText -> {
callInExecutor(() -> receiveMessage(messageText)
.catchError(e -> {
logger.error("Error handling message", e);
return null;
}));
};
private Promise<Void> receiveMessage(String messageText) {
try {
var jsonMessage = mapper.readTree(messageText);
if (jsonMessage.has("id")) {
var response = parseJson(Response.class, jsonMessage);
if (response.getError() != null) {
if (logger.isWarnEnabled()) {
logger.warn("Error message #{} received from browser: {}", jsonMessage.get("id"),
response.getError().toString());
}
}
var promise = promises.remove(response.getId());
try {
responseHandlers.remove(response.getId()).received(response.getResult(), promise);
} catch (RuntimeException e) {
logger.warn("Error processing message ${}", response.getId(), e);
promise.completeWithError(e);
}
return Promise.VOID;
} else {
var message = parseJson(Message.class, jsonMessage);
if (message.getMethod() == null) {
return Promise.VOID;
}
return handleMessage(message);
}
} catch (Exception e) {
if (logger.isErrorEnabled()) {
logger.error("Error receiving message from Google Chrome", e);
}
return Promise.VOID;
}
}
public void detach() {
if (exchange != null) {
exchange.disconnect();
}
}
public boolean isAttached() {
return exchange != null;
}
protected abstract void onAttach();
protected abstract void onDetach();
protected abstract Promise<Void> handleMessage(Message message) throws IOException;
private <T> Promise<T> callInExecutor(Supplier<Promise<T>> f) {
var result = new CompletablePromise<T>();
executor.execute(() -> {
f.get().thenVoid(result::complete).catchVoid(result::completeWithError);
});
return result;
}
protected <T> T parseJson(Class<T> type, JsonNode node) throws IOException {
return mapper.readerFor(type).readValue(node);
}
protected void sendMessage(Message message) {
if (exchange == null) {
return;
}
try {
exchange.send(mapper.writer().writeValueAsString(message));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected <R> Promise<R> callMethodAsync(String method, Class<R> returnType, Object params) {
if (!isAttached()) {
return Promise.of(null);
}
var message = new Message();
message.setId(++messageIdGenerator);
message.setMethod(method);
if (params != null) {
message.setParams(mapper.valueToTree(params));
}
sendMessage(message);
return setResponseHandler(message.getId(), (JsonNode node, CompletablePromise<R> out) -> {
if (node == null) {
out.complete(null);
} else {
R response = returnType != void.class ? mapper.readerFor(returnType).readValue(node) : null;
out.complete(response);
}
});
}
@SuppressWarnings("unchecked")
private <T> Promise<T> setResponseHandler(int messageId, ResponseHandler<T> handler) {
CompletablePromise<T> promise = new CompletablePromise<>();
promises.put(messageId, (CompletablePromise<Object>) promise);
responseHandlers.put(messageId, (ResponseHandler<Object>) handler);
return promise;
}
interface ResponseHandler<T> {
void received(JsonNode node, CompletablePromise<T> out) throws IOException;
}
}

View File

@ -15,8 +15,6 @@
*/
package org.teavm.chromerdp;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.NullNode;
import java.io.IOException;
import java.util.ArrayList;
@ -27,20 +25,13 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.teavm.chromerdp.data.CallArgumentDTO;
import org.teavm.chromerdp.data.CallFrameDTO;
import org.teavm.chromerdp.data.LocationDTO;
import org.teavm.chromerdp.data.Message;
import org.teavm.chromerdp.data.PropertyDescriptorDTO;
import org.teavm.chromerdp.data.RemoteObjectDTO;
import org.teavm.chromerdp.data.Response;
import org.teavm.chromerdp.data.ScopeDTO;
import org.teavm.chromerdp.messages.CallFunctionCommand;
import org.teavm.chromerdp.messages.CallFunctionResponse;
@ -64,12 +55,9 @@ import org.teavm.debugging.javascript.JavaScriptDebuggerListener;
import org.teavm.debugging.javascript.JavaScriptLocation;
import org.teavm.debugging.javascript.JavaScriptVariable;
public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeConsumer {
private static final Logger logger = LoggerFactory.getLogger(ChromeRDPDebugger.class);
public class ChromeRDPDebugger extends BaseChromeRDPDebugger implements JavaScriptDebugger {
private static final Promise<Map<String, ? extends JavaScriptVariable>> EMPTY_SCOPE =
Promise.of(Collections.emptyMap());
private volatile ChromeRDPExchange exchange;
private Set<JavaScriptDebuggerListener> listeners = new LinkedHashSet<>();
private Map<JavaScriptLocation, RDPNativeBreakpoint> breakpointLocationMap = new HashMap<>();
private Set<RDPBreakpoint> breakpoints = new LinkedHashSet<>();
private Map<String, RDPNativeBreakpoint> breakpointsByChromeId = new HashMap<>();
@ -77,48 +65,23 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
private Map<String, String> scripts = new HashMap<>();
private Map<String, String> scriptIds = new HashMap<>();
private volatile boolean suspended;
private ObjectMapper mapper = new ObjectMapper();
private ConcurrentMap<Integer, ResponseHandler<Object>> responseHandlers = new ConcurrentHashMap<>();
private ConcurrentMap<Integer, CompletablePromise<Object>> promises = new ConcurrentHashMap<>();
private AtomicInteger messageIdGenerator = new AtomicInteger();
private Promise<Void> runtimeEnabledPromise;
private List<JavaScriptDebuggerListener> getListeners() {
return new ArrayList<>(listeners);
}
private Executor executor;
public ChromeRDPDebugger(Executor executor) {
this.executor = executor;
super(executor);
}
@Override
public void setExchange(ChromeRDPExchange exchange) {
if (this.exchange == exchange) {
return;
}
if (this.exchange != null) {
this.exchange.removeListener(exchangeListener);
}
this.exchange = exchange;
if (exchange != null) {
protected void onAttach() {
for (RDPBreakpoint breakpoint : breakpoints.toArray(new RDPBreakpoint[0])) {
updateBreakpoint(breakpoint.nativeBreakpoint);
}
for (JavaScriptDebuggerListener listener : getListeners()) {
listener.attached();
}
} else {
@Override
protected void onDetach() {
suspended = false;
callStack = null;
for (JavaScriptDebuggerListener listener : getListeners()) {
listener.detached();
}
}
if (this.exchange != null) {
this.exchange.addListener(exchangeListener);
}
}
private Promise<Void> injectFunctions(int contextId) {
@ -148,38 +111,8 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
return runtimeEnabledPromise;
}
private ChromeRDPExchangeListener exchangeListener = messageText -> {
callInExecutor(() -> receiveMessage(messageText)
.catchError(e -> {
logger.error("Error handling message", e);
return null;
}));
};
private Promise<Void> receiveMessage(String messageText) {
try {
JsonNode jsonMessage = mapper.readTree(messageText);
if (jsonMessage.has("id")) {
Response response = mapper.readerFor(Response.class).readValue(jsonMessage);
if (response.getError() != null) {
if (logger.isWarnEnabled()) {
logger.warn("Error message #{} received from browser: {}", jsonMessage.get("id"),
response.getError().toString());
}
}
CompletablePromise<Object> promise = promises.remove(response.getId());
try {
responseHandlers.remove(response.getId()).received(response.getResult(), promise);
} catch (RuntimeException e) {
logger.warn("Error processing message ${}", response.getId(), e);
promise.completeWithError(e);
}
return Promise.VOID;
} else {
Message message = mapper.readerFor(Message.class).readValue(messageText);
if (message.getMethod() == null) {
return Promise.VOID;
}
@Override
protected Promise<Void> handleMessage(Message message) throws IOException {
switch (message.getMethod()) {
case "Debugger.paused":
return firePaused(parseJson(SuspendedNotification.class, message.getParams()));
@ -190,13 +123,6 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
}
return Promise.VOID;
}
} catch (Exception e) {
if (logger.isErrorEnabled()) {
logger.error("Error receiving message from Google Chrome", e);
}
return Promise.VOID;
}
}
private Promise<Void> firePaused(SuspendedNotification params) {
suspended = true;
@ -293,24 +219,12 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
@Override
public boolean isSuspended() {
return exchange != null && suspended;
}
@Override
public boolean isAttached() {
return exchange != null;
}
@Override
public void detach() {
if (exchange != null) {
exchange.disconnect();
}
return isAttached() && suspended;
}
@Override
public JavaScriptCallFrame[] getCallStack() {
if (exchange == null) {
if (!isAttached()) {
return null;
}
JavaScriptCallFrame[] callStack = this.callStack;
@ -321,7 +235,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
public Promise<JavaScriptBreakpoint> createBreakpoint(JavaScriptLocation location) {
RDPBreakpoint breakpoint = new RDPBreakpoint(this);
breakpoint.nativeBreakpoint = lockNativeBreakpoint(location, breakpoint);
CompletablePromise<JavaScriptBreakpoint> result = new CompletablePromise<>();
var result = new CompletablePromise<JavaScriptBreakpoint>();
breakpoints.add(breakpoint);
breakpoint.nativeBreakpoint.initPromise.thenVoid(v -> result.complete(breakpoint));
return result;
@ -550,21 +464,6 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
}
}
private <T> T parseJson(Class<T> type, JsonNode node) throws IOException {
return mapper.readerFor(type).readValue(node);
}
private void sendMessage(Message message) {
if (exchange == null) {
return;
}
try {
exchange.send(mapper.writer().writeValueAsString(message));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private RDPCallFrame map(CallFrameDTO dto) {
String scopeId = null;
RDPValue thisObject = null;
@ -600,40 +499,6 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
return dto;
}
private <R> Promise<R> callMethodAsync(String method, Class<R> returnType, Object params) {
if (exchange == null) {
return Promise.of(null);
}
Message message = new Message();
message.setId(messageIdGenerator.incrementAndGet());
message.setMethod(method);
if (params != null) {
message.setParams(mapper.valueToTree(params));
}
sendMessage(message);
return setResponseHandler(message.getId(), (JsonNode node, CompletablePromise<R> out) -> {
if (node == null) {
out.complete(null);
} else {
R response = returnType != void.class ? mapper.readerFor(returnType).readValue(node) : null;
out.complete(response);
}
});
}
@SuppressWarnings("unchecked")
private <T> Promise<T> setResponseHandler(int messageId, ResponseHandler<T> handler) {
CompletablePromise<T> promise = new CompletablePromise<>();
promises.put(messageId, (CompletablePromise<Object>) promise);
responseHandlers.put(messageId, (ResponseHandler<Object>) handler);
return promise;
}
interface ResponseHandler<T> {
void received(JsonNode node, CompletablePromise<T> out) throws IOException;
}
Promise<Map<String, ? extends JavaScriptVariable>> createScope(String id) {
if (id == null) {
return EMPTY_SCOPE;
@ -646,12 +511,4 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
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;
}
}

View File

@ -27,6 +27,7 @@ import javax.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.websocket.jsr356.server.ContainerDefaultConfigurator;
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
public class ChromeRDPServer {
@ -80,6 +81,7 @@ public class ChromeRDPServer {
private class RPDEndpointConfig implements ServerEndpointConfig {
private Map<String, Object> userProperties = new HashMap<>();
private ContainerDefaultConfigurator configurator = new ContainerDefaultConfigurator();
public RPDEndpointConfig() {
userProperties.put("chrome.rdp", exchangeConsumer);
@ -102,7 +104,7 @@ public class ChromeRDPServer {
@Override
public Configurator getConfigurator() {
return null;
return configurator;
}
@Override

View File

@ -0,0 +1,127 @@
/*
* Copyright 2022 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.chromerdp;
import java.io.IOException;
import java.util.Base64;
import java.util.Objects;
import java.util.concurrent.Executor;
import org.teavm.backend.wasm.debug.parser.DebugInfoParser;
import org.teavm.backend.wasm.debug.parser.DebugInfoReader;
import org.teavm.chromerdp.data.Message;
import org.teavm.chromerdp.messages.GetScriptSourceCommand;
import org.teavm.chromerdp.messages.ScriptParsedNotification;
import org.teavm.chromerdp.messages.ScriptSource;
import org.teavm.common.CompletablePromise;
import org.teavm.common.Promise;
public class WasmChromeRDPDebugger extends BaseChromeRDPDebugger {
public WasmChromeRDPDebugger(Executor executor) {
super(executor);
}
@Override
protected void onAttach() {
}
@Override
protected void onDetach() {
}
@Override
protected Promise<Void> handleMessage(Message message) throws IOException {
switch (message.getMethod()) {
case "Debugger.scriptParsed":
return scriptParsed(parseJson(ScriptParsedNotification.class, message.getParams()));
}
return Promise.VOID;
}
private Promise<Void> scriptParsed(ScriptParsedNotification params) {
if (Objects.equals(params.getScriptLanguage(), "WebAssembly")) {
var callArgs = new GetScriptSourceCommand();
callArgs.scriptId = params.getScriptId();
return callMethodAsync("Debugger.getScriptSource", ScriptSource.class, callArgs)
.thenVoid(source -> parseWasm(source, params.getUrl()));
}
return Promise.VOID;
}
private void parseWasm(ScriptSource source, String url) {
if (source.bytecode == null) {
return;
}
var bytes = Base64.getDecoder().decode(source.bytecode);
var reader = new DebugInfoReaderImpl(bytes);
var debugInfoParser = new DebugInfoParser(reader);
Promise.runNow(() -> {
debugInfoParser.parse().catchVoid(Throwable::printStackTrace);
reader.complete();
});
var lineInfo = debugInfoParser.getLineInfo();
if (lineInfo != null) {
System.out.println("Debug information found in script: " + url);
}
}
private static class DebugInfoReaderImpl implements DebugInfoReader {
private byte[] data;
private int ptr;
private CompletablePromise<Integer> promise;
private byte[] target;
private int offset;
private int count;
DebugInfoReaderImpl(byte[] data) {
this.data = data;
}
@Override
public Promise<Integer> skip(int amount) {
promise = new CompletablePromise<>();
count = amount;
return promise;
}
@Override
public Promise<Integer> read(byte[] buffer, int offset, int count) {
promise = new CompletablePromise<>();
this.target = buffer;
this.offset = offset;
this.count = count;
return promise;
}
private void complete() {
while (promise != null) {
var p = promise;
count = Math.min(count, data.length - ptr);
promise = null;
if (target != null) {
System.arraycopy(data, ptr, target, offset, count);
target = null;
}
ptr += count;
if (count == 0) {
count = -1;
}
p.complete(count);
}
}
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2022 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.chromerdp;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class WasmChromeRDPRunner {
private ChromeRDPServer server;
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
private WasmChromeRDPRunner() {
server = new ChromeRDPServer();
server.setPort(2357);
var wasmDebugger = new WasmChromeRDPDebugger(queue::offer);
server.setExchangeConsumer(wasmDebugger);
new Thread(server::start).start();
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
System.err.println("Uncaught exception in thread " + t);
e.printStackTrace();
});
}
public static void main(String[] args) {
var runner = new WasmChromeRDPRunner();
try {
while (true) {
runner.run();
}
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
}
public void run() throws InterruptedException {
while (true) {
queue.take().run();
}
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright 2022 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.chromerdp.messages;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class GetScriptSourceCommand {
public String scriptId;
}

View File

@ -22,6 +22,7 @@ public class ScriptParsedNotification {
private String scriptId;
private String url;
private int executionContextId;
private String scriptLanguage;
public String getScriptId() {
return scriptId;
@ -46,4 +47,12 @@ public class ScriptParsedNotification {
public void setExecutionContextId(int executionContextId) {
this.executionContextId = executionContextId;
}
public String getScriptLanguage() {
return scriptLanguage;
}
public void setScriptLanguage(String scriptLanguage) {
this.scriptLanguage = scriptLanguage;
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright 2022 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.chromerdp.messages;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ScriptSource {
public String scriptSource;
public String bytecode;
}

View File

@ -15,7 +15,7 @@ class DebuggerAgent {
}
attach() {
chrome.debugger.attach(this.debuggee, "1.2", () => {
chrome.debugger.attach(this.debuggee, "1.3", () => {
this.attachedToDebugger = true;
chrome.debugger.sendCommand(this.debuggee, "Debugger.enable", {}, () => this.connectToServer());
});
@ -102,7 +102,7 @@ class DebuggerAgent {
DebuggerAgent.MAX_MESSAGE_SIZE = 65534;
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.action.onClicked.addListener(tab => {
chrome.storage.sync.get({ port: 2357 }, items => attachDebugger(tab, items.port));
});

View File

@ -1,19 +1,19 @@
{
"manifest_version": 2,
"manifest_version": 3,
"name": "TeaVM debugger agent",
"description": "TeaVM debugger agent, that sends RDP commands over WebSocket",
"version": "0.6.0",
"version": "0.7.0",
"permissions" : ["debugger", "activeTab", "tabs", "storage", "*://*/*"],
"permissions" : ["debugger", "activeTab", "tabs", "storage", "scripting"],
"browser_action" : {
"action" : {
"default_icon": "teavm-16.png",
"default_title ": "Connect to TeaVM debugger"
},
"background": {
"scripts": ["main.js"]
"service_worker": "main.js"
},
"content_scripts": [