diff options
-rw-r--r-- | src/bun.js/bindings/BunInspector.cpp | 269 | ||||
-rw-r--r-- | src/bun.js/bindings/BunInspector.h | 53 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.cpp | 19 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.h | 2 | ||||
-rw-r--r-- | src/bun.js/javascript.zig | 27 |
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; + } } |