From df36e278acea1a152527a5e4ba4aeacb949e94d3 Mon Sep 17 00:00:00 2001 From: Peter Kanev Date: Fri, 19 May 2017 17:08:18 +0300 Subject: [PATCH] implement debug-brk functionality support --- .../src/main/java/com/tns/RuntimeHelper.java | 24 +++- .../java/com/tns/AndroidJsV8Inspector.java | 118 ++++++++++++------ runtime/src/main/jni/JsV8InspectorClient.cpp | 7 ++ runtime/src/main/jni/JsV8InspectorClient.h | 1 + .../main/jni/com_tns_AndroidJsV8Inspector.cpp | 4 + .../src/main/java/com/tns/RuntimeHelper.java | 19 ++- 6 files changed, 130 insertions(+), 43 deletions(-) diff --git a/build-artifacts/project-template-gradle/src/main/java/com/tns/RuntimeHelper.java b/build-artifacts/project-template-gradle/src/main/java/com/tns/RuntimeHelper.java index 627c72e42..7d634794b 100644 --- a/build-artifacts/project-template-gradle/src/main/java/com/tns/RuntimeHelper.java +++ b/build-artifacts/project-template-gradle/src/main/java/com/tns/RuntimeHelper.java @@ -6,6 +6,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.os.Build; import android.util.Log; + import java.io.IOException; public final class RuntimeHelper { @@ -28,7 +29,7 @@ private static boolean hasErrorIntent(Application app) { try { java.lang.Class ErrReport = java.lang.Class.forName("com.tns.ErrorReport"); java.lang.reflect.Field field = ErrReport.getDeclaredField("ERROR_FILE_NAME"); - fileName = (String)field.get(null); + fileName = (String) field.get(null); } catch (Exception e) { return false; } @@ -97,7 +98,7 @@ public static Runtime initRuntime(Application app) { logger.write("Extracting snapshot blob"); } - aE.extractAssets(app, "snapshots/" + Build.CPU_ABI, outputDir, extractPolicy); + aE.extractAssets(app, "snapshots/" + Build.CPU_ABI, outputDir, extractPolicy); } extractPolicy.setAssetsThumb(app); @@ -131,17 +132,32 @@ public static Runtime initRuntime(Application app) { runtime = Runtime.initializeRuntimeWithConfiguration(config); if (isDebuggable) { try { - v8Inspector = new AndroidJsV8Inspector(app, logger); + v8Inspector = new AndroidJsV8Inspector(app.getFilesDir().getAbsolutePath(), app.getPackageName()); v8Inspector.start(); // the following snippet is used as means to notify the VSCode extension // debugger that the debugger agent has started - File debugBreakFile = new File("/data/local/tmp", app.getPackageName() + "-debugger-started"); + File debuggerStartedFile = new File("/data/local/tmp", app.getPackageName() + "-debugger-started"); + if (debuggerStartedFile.exists() && !debuggerStartedFile.isDirectory() && debuggerStartedFile.length() == 0) { + java.io.FileWriter fileWriter = new java.io.FileWriter(debuggerStartedFile); + fileWriter.write("started"); + fileWriter.close(); + } + + // check if --debug-brk flag has been set. If positive: + // write to the file to invalidate the flag + // inform the v8Inspector to pause the main thread + File debugBreakFile = new File("/data/local/tmp", app.getPackageName() + "-debugbreak"); + boolean shouldBreak = false; if (debugBreakFile.exists() && !debugBreakFile.isDirectory() && debugBreakFile.length() == 0) { java.io.FileWriter fileWriter = new java.io.FileWriter(debugBreakFile); fileWriter.write("started"); fileWriter.close(); + + shouldBreak = true; } + + v8Inspector.waitForDebugger(shouldBreak); } catch (IOException e) { e.printStackTrace(); } diff --git a/runtime/src/main/java/com/tns/AndroidJsV8Inspector.java b/runtime/src/main/java/com/tns/AndroidJsV8Inspector.java index 8d0a69c2e..0ff226c63 100644 --- a/runtime/src/main/java/com/tns/AndroidJsV8Inspector.java +++ b/runtime/src/main/java/com/tns/AndroidJsV8Inspector.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; import fi.iki.elonen.NanoHTTPD; import fi.iki.elonen.NanoWSD; @@ -26,23 +27,32 @@ class AndroidJsV8Inspector { private static boolean DEBUG_LOG_ENABLED = false; private JsV8InspectorServer server; - private Context context; - private static String applicationDir; + private static String ApplicationDir; + private String packageName; protected native final void init(); protected native final void connect(Object connection); + private native void scheduleBreak(); + protected static native void disconnect(); protected native final void dispatchMessage(String message); private Handler mainHandler; + + private final Object debugBrkLock; + + private static AtomicBoolean DebugInitialized = new AtomicBoolean(false); + private LinkedBlockingQueue inspectorMessages = new LinkedBlockingQueue(); + private LinkedBlockingQueue pendingInspectorMessages = new LinkedBlockingQueue(); - AndroidJsV8Inspector(Context context, Logger logger) { - this.context = context; - applicationDir = context.getFilesDir().getAbsolutePath(); + AndroidJsV8Inspector(String filesDir, String packageName) { + ApplicationDir = filesDir; + this.packageName = packageName; + this.debugBrkLock = new Object(); } public void start() throws IOException { @@ -51,7 +61,7 @@ public void start() throws IOException { mainHandler = currentRuntime.getHandler(); - this.server = new JsV8InspectorServer(this.context.getPackageName() + "-inspectorServer"); + this.server = new JsV8InspectorServer(this.packageName + "-inspectorServer"); this.server.start(-1); if (DEBUG_LOG_ENABLED) { @@ -82,9 +92,7 @@ private static void sendToDevToolsConsole(Object connection, String message, Str String sendingText = consoleMessage.toString(); AndroidJsV8Inspector.send(connection, sendingText); - } catch (JSONException e) { - e.printStackTrace(); - } catch (IOException e) { + } catch (JSONException | IOException e) { e.printStackTrace(); } } @@ -102,11 +110,11 @@ private static String getInspectorMessage(Object connection) { @RuntimeCallable public static Pair[] getPageResources() { // necessary to align the data dir returned by context (emulator) and that used by the v8 inspector - if (applicationDir.startsWith("/data/user/0/")) { - applicationDir = applicationDir.replaceFirst("/data/user/0/", "/data/data/"); + if (ApplicationDir.startsWith("/data/user/0/")) { + ApplicationDir = ApplicationDir.replaceFirst("/data/user/0/", "/data/data/"); } - String dataDir = applicationDir; + String dataDir = ApplicationDir; File rootFilesDir = new File(dataDir, "app"); @@ -168,8 +176,41 @@ private static String getMimeType(String url) { return type; } - class JsV8InspectorServer extends NanoWSD { - public JsV8InspectorServer(String name) { + // pause the main thread for 30 seconds (30 * 1000 ms) + // allowing the devtools frontend to establish connection with the inspector + protected void waitForDebugger(boolean shouldBreak) { + if (shouldBreak) { + synchronized (this.debugBrkLock) { + try { + this.debugBrkLock.wait(1000 * 30); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + AndroidJsV8Inspector.DebugInitialized.getAndSet(true); + this.processDebugBreak(); + } + } + } else { + AndroidJsV8Inspector.DebugInitialized.getAndSet(true); + } + } + + // process all messages coming front the frontend necessary to initialize the inspector backend + // schedule a debug line break at first convenience + private void processDebugBreak() { + processDebugBreakMessages(); + scheduleBreak(); + } + + private void processDebugBreakMessages() { + while (!pendingInspectorMessages.isEmpty()) { + String inspectorMessage = pendingInspectorMessages.poll(); + dispatchMessage(inspectorMessage); + } + } + + private class JsV8InspectorServer extends NanoWSD { + JsV8InspectorServer(String name) { super(name); } @@ -187,9 +228,8 @@ protected WebSocket openWebSocket(IHTTPSession handshake) { } } - class JsV8InspectorWebSocket extends NanoWSD.WebSocket { - - public JsV8InspectorWebSocket(NanoHTTPD.IHTTPSession handshakeRequest) { + private class JsV8InspectorWebSocket extends NanoWSD.WebSocket { + JsV8InspectorWebSocket(NanoHTTPD.IHTTPSession handshakeRequest) { super(handshakeRequest); } @@ -199,16 +239,7 @@ protected void onOpen() { Log.d("V8Inspector", "onOpen: ThreadID: " + Thread.currentThread().getId()); } - mainHandler.post(new Runnable() { - @Override - public void run() { - if (DEBUG_LOG_ENABLED) { - Log.d("V8Inspector", "Connecting. threadID : " + Thread.currentThread().getId()); - } - - connect(JsV8InspectorWebSocket.this); - } - }); + connect(JsV8InspectorWebSocket.this); } @Override @@ -236,16 +267,30 @@ protected void onMessage(final NanoWSD.WebSocketFrame message) { inspectorMessages.offer(message.getTextPayload()); - mainHandler.post(new Runnable() { - @Override - public void run() { - String nextMessage = inspectorMessages.poll(); - while (nextMessage != null) { - dispatchMessage(nextMessage); - nextMessage = inspectorMessages.poll(); + if (!AndroidJsV8Inspector.DebugInitialized.get()) { + String nextMessage = inspectorMessages.poll(); + while (nextMessage != null) { + pendingInspectorMessages.offer(nextMessage); + nextMessage = inspectorMessages.poll(); + } + + if (message.getTextPayload().contains("Debugger.enable")) { + synchronized (debugBrkLock) { + debugBrkLock.notify(); } } - }); + } else { + mainHandler.postAtFrontOfQueue(new Runnable() { + @Override + public void run() { + String nextMessage = inspectorMessages.poll(); + while (nextMessage != null) { + dispatchMessage(nextMessage); + nextMessage = inspectorMessages.poll(); + } + } + }); + } } @Override @@ -259,8 +304,7 @@ public void send(String payload) throws IOException { public String getInspectorMessage() { try { - String message = inspectorMessages.take(); - return message; + return inspectorMessages.take(); } catch (InterruptedException e) { e.printStackTrace(); } diff --git a/runtime/src/main/jni/JsV8InspectorClient.cpp b/runtime/src/main/jni/JsV8InspectorClient.cpp index 2fbabb31d..d1d350c27 100644 --- a/runtime/src/main/jni/JsV8InspectorClient.cpp +++ b/runtime/src/main/jni/JsV8InspectorClient.cpp @@ -43,6 +43,13 @@ void JsV8InspectorClient::connect(jobject connection) { this->isConnected = true; } +void JsV8InspectorClient::scheduleBreak() { + Isolate::Scope isolate_scope(isolate_); + v8::HandleScope handleScope(isolate_); + + this->session_->schedulePauseOnNextStatement(v8_inspector::StringView(), v8_inspector::StringView()); +} + void JsV8InspectorClient::createInspectorSession(v8::Isolate* isolate, const v8::Local& context) { session_ = inspector_->connect(0, this, v8_inspector::StringView()); } diff --git a/runtime/src/main/jni/JsV8InspectorClient.h b/runtime/src/main/jni/JsV8InspectorClient.h index f5bb7b19f..a5edb186f 100644 --- a/runtime/src/main/jni/JsV8InspectorClient.h +++ b/runtime/src/main/jni/JsV8InspectorClient.h @@ -23,6 +23,7 @@ class JsV8InspectorClient : V8InspectorClient, v8_inspector::V8Inspector::Channe void init(); void connect(jobject connection); + void scheduleBreak(); void createInspectorSession(v8::Isolate* isolate, const v8::Local& context); void disconnect(); void dispatchMessage(const std::string& message); diff --git a/runtime/src/main/jni/com_tns_AndroidJsV8Inspector.cpp b/runtime/src/main/jni/com_tns_AndroidJsV8Inspector.cpp index 3172730d5..c4944e9be 100644 --- a/runtime/src/main/jni/com_tns_AndroidJsV8Inspector.cpp +++ b/runtime/src/main/jni/com_tns_AndroidJsV8Inspector.cpp @@ -15,6 +15,10 @@ JNIEXPORT extern "C" void Java_com_tns_AndroidJsV8Inspector_connect(JNIEnv* env, JsV8InspectorClient::GetInstance()->connect(connection); } +JNIEXPORT extern "C" void Java_com_tns_AndroidJsV8Inspector_scheduleBreak(JNIEnv* env, jobject instance) { + JsV8InspectorClient::GetInstance()->scheduleBreak(); +} + JNIEXPORT extern "C" void Java_com_tns_AndroidJsV8Inspector_disconnect(JNIEnv* env, jobject instance) { JsV8InspectorClient::GetInstance()->disconnect(); } diff --git a/test-app/app/src/main/java/com/tns/RuntimeHelper.java b/test-app/app/src/main/java/com/tns/RuntimeHelper.java index 8b4101e92..7d634794b 100644 --- a/test-app/app/src/main/java/com/tns/RuntimeHelper.java +++ b/test-app/app/src/main/java/com/tns/RuntimeHelper.java @@ -132,17 +132,32 @@ public static Runtime initRuntime(Application app) { runtime = Runtime.initializeRuntimeWithConfiguration(config); if (isDebuggable) { try { - v8Inspector = new AndroidJsV8Inspector(app, logger); + v8Inspector = new AndroidJsV8Inspector(app.getFilesDir().getAbsolutePath(), app.getPackageName()); v8Inspector.start(); // the following snippet is used as means to notify the VSCode extension // debugger that the debugger agent has started - File debugBreakFile = new File("/data/local/tmp", app.getPackageName() + "-debugger-started"); + File debuggerStartedFile = new File("/data/local/tmp", app.getPackageName() + "-debugger-started"); + if (debuggerStartedFile.exists() && !debuggerStartedFile.isDirectory() && debuggerStartedFile.length() == 0) { + java.io.FileWriter fileWriter = new java.io.FileWriter(debuggerStartedFile); + fileWriter.write("started"); + fileWriter.close(); + } + + // check if --debug-brk flag has been set. If positive: + // write to the file to invalidate the flag + // inform the v8Inspector to pause the main thread + File debugBreakFile = new File("/data/local/tmp", app.getPackageName() + "-debugbreak"); + boolean shouldBreak = false; if (debugBreakFile.exists() && !debugBreakFile.isDirectory() && debugBreakFile.length() == 0) { java.io.FileWriter fileWriter = new java.io.FileWriter(debugBreakFile); fileWriter.write("started"); fileWriter.close(); + + shouldBreak = true; } + + v8Inspector.waitForDebugger(shouldBreak); } catch (IOException e) { e.printStackTrace(); }