aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bun.js/bindings/BunInspector.cpp269
-rw-r--r--src/bun.js/bindings/BunInspector.h53
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp19
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.h2
-rw-r--r--src/bun.js/javascript.zig27
5 files changed, 241 insertions, 129 deletions
diff --git a/src/bun.js/bindings/BunInspector.cpp b/src/bun.js/bindings/BunInspector.cpp
index 4790c5a60..2543442d0 100644
--- a/src/bun.js/bindings/BunInspector.cpp
+++ b/src/bun.js/bindings/BunInspector.cpp
@@ -1,13 +1,21 @@
#include "BunInspector.h"
#include <JavaScriptCore/Heap.h>
#include <JavaScriptCore/JSGlobalObject.h>
-#include "JSGlobalObjectInspectorController.h"
#include <JavaScriptCore/JSGlobalObjectDebugger.h>
namespace Zig {
WTF_MAKE_ISO_ALLOCATED_IMPL(BunInspector);
+extern "C" void Bun__waitForDebuggerToStart();
+extern "C" void Bun__debuggerIsReady();
+
+static BunInspector* inspectorFromGlobal(Zig::GlobalObject* globalObject)
+{
+ RELEASE_ASSERT(globalObject->bunInspectorPtr);
+ return reinterpret_cast<BunInspector*>(globalObject->bunInspectorPtr);
+}
+
class BunInspectorConnection {
public:
@@ -85,25 +93,53 @@ void BunInspector::sendMessageToFrontend(const String& message)
}
}
- auto utf8Message = out.utf8();
- if (this->m_pendingMessages.size() > 0) {
- this->m_pendingMessages.append(WTFMove(utf8Message));
- return;
+ {
+ LockHolder locker(this->m_pendingMessagesLock);
+ if (!this->hasSentWelcomeMessage) {
+ this->hasSentWelcomeMessage = true;
+ auto welcomeMessage = makeString(
+ "{ \"method\": \"Runtime.executionContextCreated\", \"params\":{\"context\":{\"id\":"_s,
+ this->scriptExecutionContext()->identifier(),
+ ",\"origin\":\"\",\"name\":\""_s,
+ this->identifier(),
+ "\",\"uniqueId\":\"1234\",\"auxData\":{\"isDefault\":true}}}}"_s);
+ this->m_pendingMessages.append(WTFMove(welcomeMessage.isolatedCopy()));
+ }
+
+ this->m_pendingMessages.append(WTFMove(out.isolatedCopy()));
}
+ us_wakeup_loop((us_loop_t*)this->loop);
+}
- std::string_view view { utf8Message.data(), utf8Message.length() };
- if (!this->server->publish("BunInspectorConnection", view, uWS::OpCode::TEXT, false)) {
- this->m_pendingMessages.append(WTFMove(utf8Message));
+void BunInspector::drainIncomingMessages()
+{
+ LockHolder locker(this->m_incomingMessagesLock);
+ size_t size = this->m_incomingMessages.size();
+ while (size > 0) {
+ auto& message = this->m_incomingMessages.first();
+ this->sendMessageToTargetBackend(message);
+ this->m_incomingMessages.removeFirst();
+ size = this->m_incomingMessages.size();
}
}
+void BunInspector::didParseSource(SourceID id, const Debugger::Script& script)
+{
+}
+
void BunInspector::drainOutgoingMessages()
{
+ if (this->server->numSubscribers("BunInspectorConnection") == 0) {
+ return;
+ }
+
+ LockHolder locker(this->m_pendingMessagesLock);
size_t size = this->m_pendingMessages.size();
while (size > 0) {
auto& message = this->m_pendingMessages.first();
- std::string_view view { message.data(), message.length() };
+ auto utf8 = message.utf8();
+ std::string_view view { utf8.data(), utf8.length() };
if (!this->server->publish("BunInspectorConnection", view, uWS::OpCode::TEXT, false)) {
return;
}
@@ -114,28 +150,16 @@ void BunInspector::drainOutgoingMessages()
extern "C" void Bun__tickWhileWaitingForDebugger(JSC::JSGlobalObject* globalObject);
-RefPtr<BunInspector> BunInspector::startWebSocketServer(
- WebCore::ScriptExecutionContext& context,
- WTF::String hostname,
- uint16_t port,
- WTF::Function<void(RefPtr<BunInspector>, bool success)>&& callback)
+void BunInspector::startServer(WTF::String hostname, uint16_t port, WTF::URL url, WTF::String title)
{
- context.ensureURL();
- auto url = context.url();
- auto identifier = url.fileSystemPath();
-
- auto title = makeString(
- url.fileSystemPath(),
- " (Bun "_s, Bun__version, ")"_s);
-
- auto* globalObject = context.jsGlobalObject();
-
uWS::App* app = new uWS::App();
- RefPtr<BunInspector> inspector = adoptRef(*new BunInspector(&context, app, WTFMove(identifier)));
+ this->server = app;
+ this->loop = uWS::Loop::get();
+
auto host = hostname.utf8();
// https://chromedevtools.github.io/devtools-protocol/ GET /json or /json/list
- app->get("/json", [hostname, port, url, title = title, inspector](auto* res, auto* /*req*/) {
+ app->get("/json", [hostname, port, url, title = title, inspector = this](auto* res, auto* /*req*/) {
auto identifier = inspector->identifier();
auto jsonString = makeString(
"[ {\"faviconUrl\": \"https://bun.sh/favicon.svg\", \"description\": \"\", \"devtoolsFrontendUrl\": \"devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws="_s,
@@ -182,20 +206,16 @@ RefPtr<BunInspector> BunInspector::startWebSocketServer(
.sendPingsAutomatically = true,
/* Handlers */
.upgrade = nullptr,
- .open = [inspector](auto* ws) {
- BunInspectorConnection** connectionPtr = ws->getUserData();
- *connectionPtr = new BunInspectorConnection(inspector);
- ws->subscribe("BunInspectorConnection");
- BunInspectorConnection* connection = *connectionPtr;
- inspector->connect(Inspector::FrontendChannel::ConnectionType::Local);
- auto* debugger = reinterpret_cast<Inspector::JSGlobalObjectDebugger*>(inspector->globalObject()->inspectorController().debugger());
- debugger->runWhilePausedCallback = [](JSC::JSGlobalObject& globalObject, bool& isPaused) {
- while (isPaused) {
- Bun__tickWhileWaitingForDebugger(&globalObject);
- }
- }; },
+ .open = [inspector = this](auto* ws) {
+ BunInspectorConnection** connectionPtr = ws->getUserData();
+ *connectionPtr = new BunInspectorConnection(inspector);
- .message = [inspector](auto* ws, std::string_view message, uWS::OpCode opCode) {
+
+ ws->subscribe("BunInspectorConnection");
+ BunInspectorConnection* connection = *connectionPtr;
+ Bun__debuggerIsReady(); },
+
+ .message = [inspector = this](auto* ws, std::string_view message, uWS::OpCode opCode) {
if (opCode == uWS::OpCode::TEXT) {
if (!inspector) {
ws->close();
@@ -204,27 +224,14 @@ RefPtr<BunInspector> BunInspector::startWebSocketServer(
BunInspectorConnection** connectionPtr = ws->getUserData();
BunInspectorConnection* connection = *connectionPtr;
- // if (!connection->hasSentWelcomeMessage) {
- // connection->hasSentWelcomeMessage = true;
- // auto welcomeMessage = makeString(
- // "{ \"method\": \"Runtime.executionContextCreated\", \"params\":{\"context\":{\"id\":"_s,
- // connection->inspector->scriptExecutionContext()->identifier(),
- // ",\"origin\":\"\",\"name\":\""_s,
- // connection->inspector->identifier(),
- // "\",\"uniqueId\":\"1234\",\"auxData\":{\"isDefault\":true}}}}"_s
- // ).utf8();
- // std::string_view view { welcomeMessage.data(), welcomeMessage.length() };
- // if (! ws->send(
- // view,
- // uWS::OpCode::TEXT,
- // false,
- // false
- // )) {
- // connection->m_messages.append(WTFMove(welcomeMessage));
- // }
- // }
-
- inspector->dispatchToBackend(message);
+
+
+
+
+ connection->inspector->dispatchToBackend(message);
+ connection->inspector->drainOutgoingMessages();
+
+
} },
.drain = [](auto* ws) {
@@ -246,7 +253,6 @@ RefPtr<BunInspector> BunInspector::startWebSocketServer(
}
connection->m_messages.removeFirst();
}
-
connection->inspector->drainOutgoingMessages(); },
.ping = [](auto* /*ws*/, std::string_view) {
/* Not implemented yet */ },
@@ -271,14 +277,54 @@ RefPtr<BunInspector> BunInspector::startWebSocketServer(
res->write(req->getUrl());
res->end(" was not found");
})
- .listen(std::string(host.data(), host.length()), port, [inspector, callback = WTFMove(callback)](auto* listen_socket) {
- if (listen_socket) {
- callback(inspector, true);
- } else {
- callback(inspector, false);
- }
+ .listen(std::string(host.data(), host.length()), port, [inspector = this](auto* listen_socket) {
+ inspector->loop->addPostHandler(inspector, [inspector = inspector](uWS::Loop* loop) {
+ inspector->drainOutgoingMessages();
+ });
+ WebCore::ScriptExecutionContext::postTaskTo(
+ inspector->scriptExecutionContext()->identifier(),
+ [inspector = inspector](WebCore::ScriptExecutionContext& ctx) mutable {
+ inspector->readyToStartDebugger();
+ });
+
+ inspector->loop->run();
});
- ;
+}
+
+void BunInspector::readyToStartDebugger()
+{
+ this->ensureDebugger();
+
+ auto& inspectorController = globalObject()->inspectorController();
+ auto* debugger = inspectorController.debugger();
+ debugger->addObserver(*this);
+ debugger->schedulePauseAtNextOpportunity();
+}
+
+BunInspector* BunInspector::startWebSocketServer(
+ Zig::GlobalObject* globalObject,
+ WebCore::ScriptExecutionContext& context,
+ WTF::String hostname,
+ uint16_t port,
+ WTF::Function<void(BunInspector*, bool success)>&& callback)
+{
+ context.ensureURL();
+ auto url = context.url();
+ auto identifier = url.fileSystemPath();
+
+ auto title = makeString(
+ url.fileSystemPath(),
+ " (Bun "_s, Bun__version, ")"_s);
+
+ auto* inspector = new BunInspector(&context, nullptr, WTFMove(identifier));
+ reinterpret_cast<Zig::GlobalObject*>(globalObject)->bunInspectorPtr = inspector;
+ auto backgroundThreadFunction = [inspector = inspector, hostname = hostname.isolatedCopy(), port = port, url = WTFMove(url), title = WTFMove(title)]() -> void {
+ inspector->startServer(hostname, port, url, WTFMove(title));
+ };
+ WTF::Thread::create("BunInspector", WTFMove(backgroundThreadFunction))->detach();
+
+ callback(inspector, true);
+ Bun__waitForDebuggerToStart();
return inspector;
}
@@ -287,55 +333,17 @@ void BunInspector::dispatchToBackend(std::string_view message)
{
WTF::CString data { message.data(), message.length() };
WTF::String msg = WTF::String::fromUTF8(data.data(), data.length());
- auto jsonObject = WTF::JSONImpl::Value::parseJSON(msg);
- // if (auto object = jsonObject->asObject()) {
- // auto method = object->getString("method"_s);
-
- // if (method == "Profiler.enable"_s || method == "Runtime.runIfWaitingForDebugger"_s || method == "Debugger.setAsyncCallStackDepth"_s || method == "Debugger.setBlackboxPatterns"_s) {
-
- // if (auto id = object.get()->getInteger("id"_s)) {
- // auto response = makeString(
- // "{\"id\":"_s,
- // id.value(),
- // "\"result\":{}}"_s);
-
- // sendMessageToFrontend(response);
- // return;
- // }
- // } else if (method == "Runtime.getHeapUsage"_s) {
-
- // if (auto id = object.get()->getInteger("id"_s)) {
- // auto& heap = globalObject()->vm().heap;
- // int usedSize = heap.size();
- // int totalSize = heap.capacity();
-
- // auto response = makeString(
- // "{\"id\":"_s,
- // id.value(),
- // "\"result\":{ "_s,
- // "\"usedSize\": "_s, usedSize, "\"totalSize\":"_s, totalSize, "}}"_s);
-
- // sendMessageToFrontend(response);
- // return;
- // }
- // } else if (method == "Runtime.getIsolateId"_s) {
-
- // if (auto id = object.get()->getInteger("id"_s)) {
- // auto& heap = globalObject()->vm().heap;
- // int usedSize = heap.size();
- // int totalSize = heap.capacity();
-
- // auto response = makeString(
- // "{\"id\":"_s,
- // id.value(),
- // "\"result\": \"123\"}"_s);
-
- // sendMessageToFrontend(response);
- // return;
- // }
- // }
- // }
- globalObject()->inspectorController().backendDispatcher().dispatch(msg);
+ bool needsTask = true;
+ {
+ LockHolder incomingMessagesLock(this->m_incomingMessagesLock);
+ needsTask = this->m_incomingMessages.isEmpty();
+ this->m_incomingMessages.append(WTFMove(msg.isolatedCopy()));
+ }
+ WebCore::ScriptExecutionContext::postTaskTo(
+ scriptExecutionContext()->identifier(),
+ [inspector = this](WebCore::ScriptExecutionContext& ctx) mutable {
+ inspector->drainIncomingMessages();
+ });
}
void BunInspector::sendMessageToTargetBackend(const WTF::String& message)
@@ -353,4 +361,31 @@ void BunInspector::disconnect()
globalObject()->inspectorController().disconnectFrontend(*this);
}
+void BunInspector::didPause(JSGlobalObject* jsGlobalObject, DebuggerCallFrame& callframe, JSValue exceptionOrCaughtValue)
+{
+ printf("didPause\n");
+}
+void BunInspector::didContinue()
+{
+ printf("didContinue\n");
+}
+
+void BunInspector::waitForMessages()
+{
+ this->m_incomingMessagesLock.lock();
+}
+
+void BunInspector::ensureDebugger()
+{
+ this->connect(Inspector::FrontendChannel::ConnectionType::Local);
+
+ auto* debugger = reinterpret_cast<Inspector::JSGlobalObjectDebugger*>(this->globalObject()->inspectorController().debugger());
+ debugger->runWhilePausedCallback = [](JSC::JSGlobalObject& globalObject, bool& isResumed) {
+ auto* inspector = inspectorFromGlobal(reinterpret_cast<Zig::GlobalObject*>(&globalObject));
+ while (!isResumed) {
+ inspector->drainIncomingMessages();
+ }
+ };
+}
+
} // namespace Zig \ No newline at end of file
diff --git a/src/bun.js/bindings/BunInspector.h b/src/bun.js/bindings/BunInspector.h
index c02e810cb..824493076 100644
--- a/src/bun.js/bindings/BunInspector.h
+++ b/src/bun.js/bindings/BunInspector.h
@@ -6,14 +6,16 @@
#include <JavaScriptCore/InspectorFrontendChannel.h>
#include "ContextDestructionObserver.h"
#include <wtf/RefPtr.h>
+#include <JavaScriptCore/Debugger.h>
#include <wtf/Deque.h>
+#include "JSGlobalObjectInspectorController.h"
namespace Zig {
using namespace JSC;
using namespace WebCore;
-class BunInspector final : public RefCounted<BunInspector>, ::Inspector::InspectorTarget, ::Inspector::FrontendChannel, public WebCore::ContextDestructionObserver {
+class BunInspector final : public RefCounted<BunInspector>, ::Inspector::InspectorTarget, ::Inspector::FrontendChannel, public WebCore::ContextDestructionObserver, JSC::Debugger::Observer {
public:
WTF_MAKE_ISO_ALLOCATED(BunInspector);
BunInspector(ScriptExecutionContext* context, uWS::App* server, WTF::String&& identifier)
@@ -30,18 +32,44 @@ public:
server->close();
}
- using RefCounted::deref;
- using RefCounted::ref;
-
bool isProvisional() const override { return false; }
String identifier() const override { return m_identifier; }
Inspector::InspectorTargetType type() const override { return Inspector::InspectorTargetType::DedicatedWorker; }
+ GlobalObject* globalObject() { return static_cast<GlobalObject*>(scriptExecutionContext()->jsGlobalObject()); }
+
+ void startServer(WTF::String hostname, uint16_t port, WTF::URL url, WTF::String title);
+
+ Lock m_mutex;
+
+ void ensureDebugger();
+ JSC::Debugger* debugger() { return globalObject()->inspectorController().debugger(); }
+
+ void didPause(JSGlobalObject*, DebuggerCallFrame&, JSValue /* exceptionOrCaughtValue */) override;
+ void didContinue() override;
+ void didParseSource(SourceID, const Debugger::Script&) override;
+ void failedToParseSource(const String& /* url */, const String& /* data */, int /* firstLine */, int /* errorLine */, const String& /* errorMessage */) override {}
+
+ void didCreateNativeExecutable(NativeExecutable&) override {}
+ void willCallNativeExecutable(CallFrame*) override {}
- static RefPtr<BunInspector> startWebSocketServer(
+ void willEnter(CallFrame*) override {}
+
+ void didQueueMicrotask(JSGlobalObject*, MicrotaskIdentifier) override {}
+ void willRunMicrotask(JSGlobalObject*, MicrotaskIdentifier) override {}
+ void didRunMicrotask(JSGlobalObject*, MicrotaskIdentifier) override {}
+
+ void applyBreakpoints(CodeBlock*) override {}
+ void breakpointActionLog(JSGlobalObject*, const String& /* data */) override {}
+ void breakpointActionSound(BreakpointActionID) override {}
+ void breakpointActionProbe(JSGlobalObject*, BreakpointActionID, unsigned /* batchId */, unsigned /* sampleId */, JSValue /* result */) override {}
+ void didDeferBreakpointPause(BreakpointID) override {}
+
+ static BunInspector* startWebSocketServer(
+ Zig::GlobalObject* globalObject,
WebCore::ScriptExecutionContext& ctx,
WTF::String hostname,
uint16_t port,
- WTF::Function<void(RefPtr<BunInspector>, bool success)>&& callback);
+ WTF::Function<void(BunInspector*, bool success)>&& callback);
// Connection management.
void connect(Inspector::FrontendChannel::ConnectionType) override;
@@ -53,15 +81,24 @@ public:
Inspector::FrontendChannel::ConnectionType connectionType() const override { return Inspector::FrontendChannel::ConnectionType::Remote; }
int connectionCounter = 0;
+ bool hasSentWelcomeMessage = false;
void drainOutgoingMessages();
+ void drainIncomingMessages();
+ void waitForMessages();
+
+ void readyToStartDebugger();
private:
void dispatchToBackend(std::string_view message);
WTF::String m_identifier;
+ WTF::Lock m_pendingMessagesLock;
uWS::App* server;
- Deque<WTF::CString> m_pendingMessages;
- GlobalObject* globalObject() { return static_cast<GlobalObject*>(scriptExecutionContext()->jsGlobalObject()); }
+ uWS::Loop* loop;
+ Deque<WTF::String> m_pendingMessages;
+
+ Deque<WTF::String> m_incomingMessages;
+ WTF::Lock m_incomingMessagesLock;
};
} \ No newline at end of file
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index 237e789e8..a89484b1a 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -245,14 +245,14 @@ extern "C" bool JSGlobalObject__startRemoteInspector(Zig::GlobalObject* globalOb
#if !ENABLE(REMOTE_INSPECTOR)
return false;
#else
- globalObject->setInspectable(true);
bool didSucceed = false;
// This function calls immediately.
auto inspector = BunInspector::startWebSocketServer(
+ globalObject,
*globalObject->scriptExecutionContext(),
WTF::String::fromUTF8(host),
- port, [port, &didSucceed](RefPtr<BunInspector> inspector, bool success) {
+ port, [port, &didSucceed](BunInspector* inspector, bool success) {
didSucceed = success;
if (success) {
@@ -284,6 +284,10 @@ extern "C" JSC__JSGlobalObject* Zig__GlobalObject__create(JSClassRef* globalObje
if (count > 0) {
globalObject->installAPIGlobals(globalObjectClass, count, vm);
}
+ if (inspector != 0) {
+ globalObject->setInspectable(true);
+ globalObject->inspectorController().debugger();
+ }
JSC::gcProtect(globalObject);
vm.ref();
return globalObject;
@@ -549,6 +553,9 @@ WebCore::ScriptExecutionContext* GlobalObject::scriptExecutionContext() const
void GlobalObject::reportUncaughtExceptionAtEventLoop(JSGlobalObject* globalObject,
JSC::Exception* exception)
{
+ if (globalObject->hasDebugger()) {
+ globalObject->debugger()->exception(globalObject, nullptr, exception, false);
+ }
Bun__reportUnhandledError(globalObject, JSValue::encode(JSValue(exception)));
}
@@ -560,10 +567,16 @@ void GlobalObject::promiseRejectionTracker(JSGlobalObject* obj, JSC::JSPromise*
// Do this in C++ for now
auto* globalObj = reinterpret_cast<GlobalObject*>(obj);
+
switch (operation) {
- case JSPromiseRejectionOperation::Reject:
+ case JSPromiseRejectionOperation::Reject: {
+ if (obj->hasDebugger()) {
+ obj->debugger()->exception(obj, nullptr, promise->result(), false);
+ }
+
globalObj->m_aboutToBeNotifiedRejectedPromises.append(JSC::Strong<JSPromise>(obj->vm(), promise));
break;
+ }
case JSPromiseRejectionOperation::Handle:
globalObj->m_aboutToBeNotifiedRejectedPromises.removeFirstMatching([&](Strong<JSPromise>& unhandledPromise) {
return unhandledPromise.get() == promise;
diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h
index 66853c909..c848ee397 100644
--- a/src/bun.js/bindings/ZigGlobalObject.h
+++ b/src/bun.js/bindings/ZigGlobalObject.h
@@ -403,6 +403,8 @@ public:
void* napiInstanceDataFinalizer = nullptr;
void* napiInstanceDataFinalizerHint = nullptr;
+ void* bunInspectorPtr = nullptr;
+
#include "ZigGeneratedClasses+lazyStructureHeader.h"
private:
diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig
index cacac0213..e9515b0ff 100644
--- a/src/bun.js/javascript.zig
+++ b/src/bun.js/javascript.zig
@@ -256,6 +256,28 @@ pub export fn Bun__drainMicrotasks() void {
JSC.VirtualMachine.get().eventLoop().tick();
}
+const WaitForDebugger = struct {
+ var is_debugger_ready = std.atomic.Atomic(u32).init(0);
+ pub fn ready() void {
+ is_debugger_ready.store(1, .Monotonic);
+ std.Thread.Futex.wake(&is_debugger_ready, 1);
+ }
+
+ pub fn wait() void {
+ while (is_debugger_ready.load(.Monotonic) == 0) {
+ std.Thread.Futex.wait(&is_debugger_ready, 1);
+ }
+ }
+};
+
+pub export fn Bun__waitForDebuggerToStart() void {
+ WaitForDebugger.wait();
+}
+
+pub export fn Bun__debuggerIsReady() void {
+ WaitForDebugger.ready();
+}
+
export fn Bun__readOriginTimer(vm: *JSC.VirtualMachine) u64 {
return vm.origin_timer.read();
}
@@ -2793,6 +2815,9 @@ pub fn NewHotReloader(comptime Ctx: type, comptime EventLoopType: type, comptime
}
comptime {
- if (!JSC.is_bindgen)
+ if (!JSC.is_bindgen) {
_ = Bun__getMainPath;
+ _ = Bun__waitForDebuggerToStart;
+ _ = Bun__debuggerIsReady;
+ }
}