diff options
Diffstat (limited to 'src/bun.js/bindings')
22 files changed, 673 insertions, 46 deletions
diff --git a/src/bun.js/bindings/AsyncContextFrame.cpp b/src/bun.js/bindings/AsyncContextFrame.cpp index 326350664..2a103a8d1 100644 --- a/src/bun.js/bindings/AsyncContextFrame.cpp +++ b/src/bun.js/bindings/AsyncContextFrame.cpp @@ -81,27 +81,27 @@ extern "C" EncodedJSValue AsyncContextFrame__withAsyncContextIfNeeded(JSGlobalOb restoreAsyncContext = asyncContextData->getInternalField(0); \ asyncContextData->putInternalField(vm, 0, wrapper->context.get()); \ } \ - auto result = JSC::call(__VA_ARGS__); \ + auto result = JSC::profiledCall(__VA_ARGS__); \ if (asyncContextData) { \ asyncContextData->putInternalField(vm, 0, restoreAsyncContext); \ } \ return result; -JSValue AsyncContextFrame::call(JSGlobalObject* global, JSValue functionObject, const ArgList& args, ASCIILiteral errorMessage) -{ - ASYNCCONTEXTFRAME_CALL_IMPL(global, functionObject, args, errorMessage); -} -JSValue AsyncContextFrame::call(JSGlobalObject* global, JSValue functionObject, JSValue thisValue, const ArgList& args, ASCIILiteral errorMessage) -{ - ASYNCCONTEXTFRAME_CALL_IMPL(global, functionObject, thisValue, args, errorMessage); -} +// JSValue AsyncContextFrame::call(JSGlobalObject* global, JSValue functionObject, const ArgList& args, ASCIILiteral errorMessage) +// { +// ASYNCCONTEXTFRAME_CALL_IMPL(global, ProfilingReason::API, functionObject, args, errorMessage); +// } +// JSValue AsyncContextFrame::call(JSGlobalObject* global, JSValue functionObject, JSValue thisValue, const ArgList& args, ASCIILiteral errorMessage) +// { +// ASYNCCONTEXTFRAME_CALL_IMPL(global, ProfilingReason::API, functionObject, thisValue, args, errorMessage); +// } JSValue AsyncContextFrame::call(JSGlobalObject* global, JSValue functionObject, JSValue thisValue, const ArgList& args) { - ASYNCCONTEXTFRAME_CALL_IMPL(global, functionObject, JSC::getCallData(functionObject), thisValue, args); + ASYNCCONTEXTFRAME_CALL_IMPL(global, ProfilingReason::API, functionObject, JSC::getCallData(functionObject), thisValue, args); } JSValue AsyncContextFrame::call(JSGlobalObject* global, JSValue functionObject, JSValue thisValue, const ArgList& args, NakedPtr<Exception>& returnedException) { - ASYNCCONTEXTFRAME_CALL_IMPL(global, functionObject, JSC::getCallData(functionObject), thisValue, args, returnedException); + ASYNCCONTEXTFRAME_CALL_IMPL(global, ProfilingReason::API, functionObject, JSC::getCallData(functionObject), thisValue, args, returnedException); } #undef ASYNCCONTEXTFRAME_CALL_IMPL diff --git a/src/bun.js/bindings/AsyncContextFrame.h b/src/bun.js/bindings/AsyncContextFrame.h index 52ca0d160..f5ddf4ce0 100644 --- a/src/bun.js/bindings/AsyncContextFrame.h +++ b/src/bun.js/bindings/AsyncContextFrame.h @@ -20,8 +20,8 @@ public: // The following is JSC::call but // - it unwraps AsyncContextFrame // - does not take a CallData, because JSC::getCallData(AsyncContextFrame) -> not callable - static JSC::JSValue call(JSC::JSGlobalObject*, JSC::JSValue functionObject, const JSC::ArgList&, ASCIILiteral errorMessage); - static JSC::JSValue call(JSC::JSGlobalObject*, JSC::JSValue functionObject, JSC::JSValue thisValue, const JSC::ArgList&, ASCIILiteral errorMessage); + // static JSC::JSValue call(JSC::JSGlobalObject*, JSC::JSValue functionObject, const JSC::ArgList&, ASCIILiteral errorMessage); + // static JSC::JSValue call(JSC::JSGlobalObject*, JSC::JSValue functionObject, JSC::JSValue thisValue, const JSC::ArgList&, ASCIILiteral errorMessage); static JSC::JSValue call(JSC::JSGlobalObject*, JSC::JSValue functionObject, JSC::JSValue thisValue, const JSC::ArgList&); static JSC::JSValue call(JSC::JSGlobalObject*, JSC::JSValue functionObject, JSC::JSValue thisValue, const JSC::ArgList&, NakedPtr<JSC::Exception>& returnedException); diff --git a/src/bun.js/bindings/BunDebugger.cpp b/src/bun.js/bindings/BunDebugger.cpp new file mode 100644 index 000000000..9d14a75b7 --- /dev/null +++ b/src/bun.js/bindings/BunDebugger.cpp @@ -0,0 +1,530 @@ +#include "root.h" +#include <uws/src/App.h> + +#include <JavaScriptCore/InspectorFrontendChannel.h> +#include <JavaScriptCore/JSGlobalObjectDebuggable.h> +#include <JavaScriptCore/JSGlobalObjectDebugger.h> +#include <JavaScriptCore/Debugger.h> +#include "ScriptExecutionContext.h" +#include "Strong.h" +#include "debug-helpers.h" + +extern "C" void Bun__tickWhilePaused(bool*); + +namespace Bun { +using namespace JSC; +using namespace WebCore; + +class BunInspectorConnection; + +static WebCore::ScriptExecutionContext* debuggerScriptExecutionContext = nullptr; +static WTF::Lock inspectorConnectionsLock = WTF::Lock(); +static WTF::HashMap<ScriptExecutionContextIdentifier, Vector<BunInspectorConnection*, 8>>* inspectorConnections = nullptr; + +enum class ConnectionStatus : int32_t { + Pending = 0, + Connected = 1, + Disconnecting = 2, + Disconnected = 3, +}; + +class BunInspectorConnection : public Inspector::FrontendChannel { + +public: + BunInspectorConnection(ScriptExecutionContext& scriptExecutionContext, JSC::JSGlobalObject* globalObject) + : Inspector::FrontendChannel() + , globalObject(globalObject) + , scriptExecutionContextIdentifier(scriptExecutionContext.identifier()) + { + } + + ~BunInspectorConnection() + { + } + + static BunInspectorConnection* create(ScriptExecutionContext& scriptExecutionContext, JSC::JSGlobalObject* globalObject) + { + return new BunInspectorConnection(scriptExecutionContext, globalObject); + } + + ConnectionType connectionType() const override + { + return ConnectionType::Remote; + } + + void connect() + { + switch (this->status) { + case ConnectionStatus::Disconnected: + case ConnectionStatus::Disconnecting: { + return; + } + default: { + break; + } + } + + if (this->jsWaitForMessageFromInspectorLock.isLocked()) + this->jsWaitForMessageFromInspectorLock.unlockFairly(); + + ScriptExecutionContext::ensureOnContextThread(scriptExecutionContextIdentifier, [connection = this](ScriptExecutionContext& context) { + switch (connection->status) { + case ConnectionStatus::Pending: { + connection->status = ConnectionStatus::Connected; + auto* globalObject = context.jsGlobalObject(); + globalObject->setInspectable(true); + + auto& inspector = globalObject->inspectorDebuggable(); + inspector.setInspectable(true); + + inspector.connect(*connection); + + Inspector::JSGlobalObjectDebugger* debugger = reinterpret_cast<Inspector::JSGlobalObjectDebugger*>(globalObject->debugger()); + if (debugger) { + debugger->runWhilePausedCallback = [](JSC::JSGlobalObject& globalObject, bool& isDoneProcessingEvents) -> void { + BunInspectorConnection::runWhilePaused(globalObject, isDoneProcessingEvents); + }; + } + + connection->receiveMessagesOnInspectorThread(context, reinterpret_cast<Zig::GlobalObject*>(globalObject)); + + break; + } + default: { + break; + } + } + }); + } + + void disconnect() + { + if (jsWaitForMessageFromInspectorLock.isLocked()) + jsWaitForMessageFromInspectorLock.unlockFairly(); + + switch (this->status) { + case ConnectionStatus::Disconnected: { + return; + } + default: { + break; + } + } + + ScriptExecutionContext::ensureOnContextThread(scriptExecutionContextIdentifier, [connection = this](ScriptExecutionContext& context) { + if (connection->status == ConnectionStatus::Disconnected) + return; + + connection->status = ConnectionStatus::Disconnected; + connection->inspector().disconnect(*connection); + }); + } + + JSC::JSGlobalObjectDebuggable& inspector() + { + return globalObject->inspectorDebuggable(); + } + + void sendMessageToFrontend(const String& message) override + { + if (message.length() == 0) + return; + + this->sendMessageToDebuggerThread(message.isolatedCopy()); + } + + static void runWhilePaused(JSGlobalObject& globalObject, bool& isDoneProcessingEvents) + { + Zig::GlobalObject* global = reinterpret_cast<Zig::GlobalObject*>(&globalObject); + Vector<BunInspectorConnection*, 8> connections; + { + WTF::LockHolder locker(inspectorConnectionsLock); + connections.appendVector(inspectorConnections->get(global->scriptExecutionContext()->identifier())); + } + + for (auto* connection : connections) { + if (connection->status == ConnectionStatus::Pending) { + connection->connect(); + } + + if (connection->status != ConnectionStatus::Disconnected) { + connection->receiveMessagesOnInspectorThread(*global->scriptExecutionContext(), global); + } + } + + // for (auto* connection : connections) { + // if (connection->status == ConnectionStatus::Connected) { + // connection->jsWaitForMessageFromInspectorLock.lock(); + // } + // } + + if (connections.size() == 1) { + while (!isDoneProcessingEvents) { + auto* connection = connections[0]; + if (connection->status == ConnectionStatus::Disconnected || connection->status == ConnectionStatus::Disconnecting) { + if (global->debugger() && global->debugger()->isPaused()) { + global->debugger()->continueProgram(); + } + break; + } + connection->receiveMessagesOnInspectorThread(*global->scriptExecutionContext(), global); + } + } else { + while (!isDoneProcessingEvents) { + size_t closedCount = 0; + for (auto* connection : connections) { + closedCount += connection->status == ConnectionStatus::Disconnected || connection->status == ConnectionStatus::Disconnecting; + connection->receiveMessagesOnInspectorThread(*global->scriptExecutionContext(), global); + if (isDoneProcessingEvents) + break; + } + + if (closedCount == connections.size() && global->debugger() && !isDoneProcessingEvents) { + global->debugger()->continueProgram(); + continue; + } + } + } + } + + void receiveMessagesOnInspectorThread(ScriptExecutionContext& context, Zig::GlobalObject* globalObject) + { + this->jsThreadMessageScheduledCount.store(0); + WTF::Vector<WTF::String, 12> messages; + + { + WTF::LockHolder locker(jsThreadMessagesLock); + this->jsThreadMessages.swap(messages); + } + + auto& dispatcher = globalObject->inspectorDebuggable(); + Inspector::JSGlobalObjectDebugger* debugger = reinterpret_cast<Inspector::JSGlobalObjectDebugger*>(globalObject->debugger()); + + if (!debugger) { + for (auto message : messages) { + dispatcher.dispatchMessageFromRemote(WTFMove(message)); + + debugger = reinterpret_cast<Inspector::JSGlobalObjectDebugger*>(globalObject->debugger()); + if (debugger) { + debugger->runWhilePausedCallback = [](JSC::JSGlobalObject& globalObject, bool& isDoneProcessingEvents) -> void { + runWhilePaused(globalObject, isDoneProcessingEvents); + }; + } + } + } else { + for (auto message : messages) { + dispatcher.dispatchMessageFromRemote(WTFMove(message)); + } + } + + messages.clear(); + } + + void receiveMessagesOnDebuggerThread(ScriptExecutionContext& context, Zig::GlobalObject* debuggerGlobalObject) + { + debuggerThreadMessageScheduledCount.store(0); + WTF::Vector<WTF::String, 12> messages; + + { + WTF::LockHolder locker(debuggerThreadMessagesLock); + this->debuggerThreadMessages.swap(messages); + } + + JSFunction* onMessageFn = jsCast<JSFunction*>(jsBunDebuggerOnMessageFunction->m_cell.get()); + MarkedArgumentBuffer arguments; + arguments.ensureCapacity(messages.size()); + auto& vm = debuggerGlobalObject->vm(); + + for (auto& message : messages) { + arguments.append(jsString(vm, message)); + } + + messages.clear(); + + JSC::call(debuggerGlobalObject, onMessageFn, arguments, "BunInspectorConnection::receiveMessagesOnDebuggerThread - onMessageFn"_s); + } + + void sendMessageToDebuggerThread(WTF::String&& inputMessage) + { + { + WTF::LockHolder locker(debuggerThreadMessagesLock); + debuggerThreadMessages.append(inputMessage); + } + + if (this->debuggerThreadMessageScheduledCount++ == 0) { + debuggerScriptExecutionContext->postTaskConcurrently([connection = this](ScriptExecutionContext& context) { + connection->receiveMessagesOnDebuggerThread(context, reinterpret_cast<Zig::GlobalObject*>(context.jsGlobalObject())); + }); + } + } + + void sendMessageToInspectorFromDebuggerThread(const WTF::String& inputMessage) + { + { + WTF::LockHolder locker(jsThreadMessagesLock); + jsThreadMessages.append(inputMessage); + } + + if (this->jsWaitForMessageFromInspectorLock.isLocked()) { + this->jsWaitForMessageFromInspectorLock.unlock(); + } else if (this->jsThreadMessageScheduledCount++ == 0) { + ScriptExecutionContext::postTaskTo(scriptExecutionContextIdentifier, [connection = this](ScriptExecutionContext& context) { + connection->receiveMessagesOnInspectorThread(context, reinterpret_cast<Zig::GlobalObject*>(context.jsGlobalObject())); + }); + } + } + + WTF::Vector<WTF::String, 12> debuggerThreadMessages; + WTF::Lock debuggerThreadMessagesLock = WTF::Lock(); + std::atomic<uint32_t> debuggerThreadMessageScheduledCount { 0 }; + + WTF::Vector<WTF::String, 12> jsThreadMessages; + WTF::Lock jsThreadMessagesLock = WTF::Lock(); + std::atomic<uint32_t> jsThreadMessageScheduledCount { 0 }; + + JSC::JSGlobalObject* globalObject; + ScriptExecutionContextIdentifier scriptExecutionContextIdentifier; + Bun::StrongRef* jsBunDebuggerOnMessageFunction = nullptr; + + WTF::Lock jsWaitForMessageFromInspectorLock; + std::atomic<ConnectionStatus> status = ConnectionStatus::Pending; +}; + +JSC_DECLARE_HOST_FUNCTION(jsFunctionSend); +JSC_DECLARE_HOST_FUNCTION(jsFunctionDisconnect); + +class JSBunInspectorConnection final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + static constexpr bool needsDestruction = false; + + static JSBunInspectorConnection* create(JSC::VM& vm, JSC::Structure* structure, BunInspectorConnection* connection) + { + JSBunInspectorConnection* ptr = new (NotNull, JSC::allocateCell<JSBunInspectorConnection>(vm)) JSBunInspectorConnection(vm, structure, connection); + ptr->finishCreation(vm); + return ptr; + } + + DECLARE_EXPORT_INFO; + template<typename, SubspaceAccess mode> + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl<JSBunInspectorConnection, WebCore::UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForBunInspectorConnection.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForBunInspectorConnection = std::forward<decltype(space)>(space); }, + [](auto& spaces) { return spaces.m_subspaceForBunInspectorConnection.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForBunInspectorConnection = std::forward<decltype(space)>(space); }); + } + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info(), JSC::NonArray, 2); + } + + BunInspectorConnection* connection() + { + return m_connection; + } + +private: + JSBunInspectorConnection(JSC::VM& vm, JSC::Structure* structure, BunInspectorConnection* connection) + : Base(vm, structure) + , m_connection(connection) + { + } + + void finishCreation(JSC::VM& vm) + { + Base::finishCreation(vm); + } + + BunInspectorConnection* m_connection; +}; + +JSC_DEFINE_HOST_FUNCTION(jsFunctionSend, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* jsConnection = jsDynamicCast<JSBunInspectorConnection*>(callFrame->thisValue()); + auto message = callFrame->uncheckedArgument(0).toWTFString(globalObject).isolatedCopy(); + + if (!jsConnection) + return JSValue::encode(jsUndefined()); + + jsConnection->connection()->sendMessageToInspectorFromDebuggerThread(message); + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsFunctionDisconnect, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto* jsConnection = jsDynamicCast<JSBunInspectorConnection*>(callFrame->thisValue()); + if (!jsConnection) + return JSValue::encode(jsUndefined()); + + auto& connection = *jsConnection->connection(); + + if (connection.status == ConnectionStatus::Connected || connection.status == ConnectionStatus::Pending) { + connection.status = ConnectionStatus::Disconnecting; + connection.disconnect(); + if (connection.jsWaitForMessageFromInspectorLock.isLocked()) + connection.jsWaitForMessageFromInspectorLock.unlockFairly(); + } + + return JSValue::encode(jsUndefined()); +} + +const JSC::ClassInfo JSBunInspectorConnection::s_info = { "BunInspectorConnection"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSBunInspectorConnection) }; + +extern "C" unsigned int Bun__createJSDebugger(Zig::GlobalObject* globalObject) +{ + { + WTF::LockHolder locker(inspectorConnectionsLock); + if (inspectorConnections == nullptr) { + inspectorConnections = new WTF::HashMap<ScriptExecutionContextIdentifier, Vector<BunInspectorConnection*, 8>>(); + } + + inspectorConnections->add(globalObject->scriptExecutionContext()->identifier(), Vector<BunInspectorConnection*, 8>()); + } + + return static_cast<unsigned int>(globalObject->scriptExecutionContext()->identifier()); +} +extern "C" void Bun__tickWhilePaused(bool*); + +extern "C" void Bun__ensureDebugger(ScriptExecutionContextIdentifier scriptId, bool pauseOnStart) +{ + + auto* globalObject = ScriptExecutionContext::getScriptExecutionContext(scriptId)->jsGlobalObject(); + globalObject->setInspectable(true); + + auto& inspector = globalObject->inspectorDebuggable(); + inspector.setInspectable(true); + + Inspector::JSGlobalObjectDebugger* debugger = reinterpret_cast<Inspector::JSGlobalObjectDebugger*>(globalObject->debugger()); + if (debugger) { + debugger->runWhilePausedCallback = [](JSC::JSGlobalObject& globalObject, bool& isDoneProcessingEvents) -> void { + BunInspectorConnection::runWhilePaused(globalObject, isDoneProcessingEvents); + }; + } + + if (pauseOnStart) + inspector.pauseWaitingForAutomaticInspection(); +} + +JSC_DEFINE_HOST_FUNCTION(jsFunctionCreateConnection, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + auto* debuggerGlobalObject = jsDynamicCast<Zig::GlobalObject*>(globalObject); + if (!debuggerGlobalObject) + return JSValue::encode(jsUndefined()); + + ScriptExecutionContext* targetContext = ScriptExecutionContext::getScriptExecutionContext(static_cast<ScriptExecutionContextIdentifier>(callFrame->argument(0).toUInt32(globalObject))); + JSFunction* onMessageFn = jsCast<JSFunction*>(callFrame->argument(1).toObject(globalObject)); + + if (!targetContext || !onMessageFn) + return JSValue::encode(jsUndefined()); + + auto& vm = globalObject->vm(); + auto connection = BunInspectorConnection::create( + *targetContext, + targetContext->jsGlobalObject()); + + { + WTF::LockHolder locker(inspectorConnectionsLock); + auto connections = inspectorConnections->get(targetContext->identifier()); + connections.append(connection); + inspectorConnections->set(targetContext->identifier(), connections); + } + connection->jsBunDebuggerOnMessageFunction = new Bun::StrongRef(vm, onMessageFn); + connection->connect(); + + return JSValue::encode(JSBunInspectorConnection::create(vm, JSBunInspectorConnection::createStructure(vm, globalObject, globalObject->objectPrototype()), connection)); +} + +extern "C" BunString Bun__startJSDebuggerThread(Zig::GlobalObject* debuggerGlobalObject, ScriptExecutionContextIdentifier scriptId, BunString* portOrPathString) +{ + if (!debuggerScriptExecutionContext) + debuggerScriptExecutionContext = debuggerGlobalObject->scriptExecutionContext(); + JSC::VM& vm = debuggerGlobalObject->vm(); + JSValue defaultValue = debuggerGlobalObject->internalModuleRegistry()->requireId(debuggerGlobalObject, vm, InternalModuleRegistry::Field::InternalDebugger); + JSFunction* debuggerDefaultFn = jsCast<JSFunction*>(defaultValue.asCell()); + + MarkedArgumentBuffer arguments; + + arguments.append(jsNumber(static_cast<unsigned int>(scriptId))); + arguments.append(Bun::toJS(debuggerGlobalObject, *portOrPathString)); + arguments.append(JSFunction::create(vm, debuggerGlobalObject, 1, String(), jsFunctionCreateConnection, ImplementationVisibility::Public)); + arguments.append(JSFunction::create(vm, debuggerGlobalObject, 1, String("send"_s), jsFunctionSend, ImplementationVisibility::Public)); + arguments.append(JSFunction::create(vm, debuggerGlobalObject, 0, String("disconnect"_s), jsFunctionDisconnect, ImplementationVisibility::Public)); + + JSValue serverURLValue = JSC::call(debuggerGlobalObject, debuggerDefaultFn, arguments, "Bun__initJSDebuggerThread - debuggerDefaultFn"_s); + + if (serverURLValue.isUndefinedOrNull()) + return BunStringEmpty; + + return Bun::toStringRef(debuggerGlobalObject, serverURLValue); +} + +enum class AsyncCallTypeUint8 : uint8_t { + DOMTimer = 1, + EventListener = 2, + PostMessage = 3, + RequestAnimationFrame = 4, + Microtask = 5, +}; + +static Inspector::InspectorDebuggerAgent::AsyncCallType getCallType(AsyncCallTypeUint8 callType) +{ + Inspector::InspectorDebuggerAgent::AsyncCallType type; + switch (callType) { + case AsyncCallTypeUint8::DOMTimer: + return Inspector::InspectorDebuggerAgent::AsyncCallType::DOMTimer; + case AsyncCallTypeUint8::EventListener: + return Inspector::InspectorDebuggerAgent::AsyncCallType::EventListener; + case AsyncCallTypeUint8::PostMessage: + return Inspector::InspectorDebuggerAgent::AsyncCallType::PostMessage; + case AsyncCallTypeUint8::RequestAnimationFrame: + return Inspector::InspectorDebuggerAgent::AsyncCallType::RequestAnimationFrame; + case AsyncCallTypeUint8::Microtask: + return Inspector::InspectorDebuggerAgent::AsyncCallType::Microtask; + default: + RELEASE_ASSERT_NOT_REACHED(); + } +} + +extern "C" void Debugger__didScheduleAsyncCall(JSGlobalObject* globalObject, AsyncCallTypeUint8 callType, uint64_t callbackId, bool singleShot) +{ + auto* agent = debuggerAgent(globalObject); + if (!agent) + return; + + agent->didScheduleAsyncCall(globalObject, getCallType(callType), callbackId, singleShot); +} + +extern "C" void Debugger__didCancelAsyncCall(JSGlobalObject* globalObject, AsyncCallTypeUint8 callType, uint64_t callbackId) +{ + auto* agent = debuggerAgent(globalObject); + if (!agent) + return; + + agent->didCancelAsyncCall(getCallType(callType), callbackId); +} + +extern "C" void Debugger__didDispatchAsyncCall(JSGlobalObject* globalObject, AsyncCallTypeUint8 callType, uint64_t callbackId) +{ + auto* agent = debuggerAgent(globalObject); + if (!agent) + return; + + agent->didDispatchAsyncCall(getCallType(callType), callbackId); +} + +extern "C" void Debugger__willDispatchAsyncCall(JSGlobalObject* globalObject, AsyncCallTypeUint8 callType, uint64_t callbackId) +{ + auto* agent = debuggerAgent(globalObject); + if (!agent) + return; + + agent->willDispatchAsyncCall(getCallType(callType), callbackId); +} +} diff --git a/src/bun.js/bindings/BunInspector.cpp b/src/bun.js/bindings/BunInspector.cpp index ccb0a702d..76920e398 100644 --- a/src/bun.js/bindings/BunInspector.cpp +++ b/src/bun.js/bindings/BunInspector.cpp @@ -3,6 +3,10 @@ #include <JavaScriptCore/InspectorFrontendChannel.h> #include <JavaScriptCore/JSGlobalObjectDebuggable.h> +#include <JavaScriptCore/JSGlobalObjectDebugger.h> +#include <JavaScriptCore/Debugger.h> + +extern "C" void Bun__tickWhilePaused(bool*); namespace Bun { using namespace JSC; @@ -31,6 +35,13 @@ public: { this->globalObject = globalObject; this->globalObject->inspectorDebuggable().connect(*this); + + Inspector::JSGlobalObjectDebugger* debugger = reinterpret_cast<Inspector::JSGlobalObjectDebugger*>(this->globalObject->debugger()); + if (debugger) { + debugger->runWhilePausedCallback = [](JSC::JSGlobalObject& globalObject, bool& isPaused) -> void { + Bun__tickWhilePaused(&isPaused); + }; + } } void onClose() @@ -57,6 +68,13 @@ public: void onMessage(std::string_view message) { WTF::String messageString = WTF::String::fromUTF8(message.data(), message.length()); + Inspector::JSGlobalObjectDebugger* debugger = reinterpret_cast<Inspector::JSGlobalObjectDebugger*>(this->globalObject->debugger()); + if (debugger) { + debugger->runWhilePausedCallback = [](JSC::JSGlobalObject& globalObject, bool& done) -> void { + Inspector::JSGlobalObjectDebugger* debugger = reinterpret_cast<Inspector::JSGlobalObjectDebugger*>(globalObject.debugger()); + Bun__tickWhilePaused(&done); + }; + } this->globalObject->inspectorDebuggable().dispatchMessageFromRemote(WTFMove(messageString)); } @@ -84,6 +102,7 @@ public: using BunInspectorConnectionNoSSL = BunInspectorConnection<false>; using SSLBunInspectorConnection = BunInspectorConnection<true>; + template<bool isSSL> static void addInspector(void* app, JSC::JSGlobalObject* globalObject) { diff --git a/src/bun.js/bindings/Debugger.zig b/src/bun.js/bindings/Debugger.zig new file mode 100644 index 000000000..3d9fc450c --- /dev/null +++ b/src/bun.js/bindings/Debugger.zig @@ -0,0 +1,33 @@ +const bun = @import("root").bun; +const JSC = bun.JSC; + +pub const Debugger = struct { + pub const AsyncCallType = enum(u8) { + DOMTimer = 1, + EventListener = 2, + PostMessage = 3, + RequestAnimationFrame = 4, + Microtask = 5, + }; + extern fn Debugger__didScheduleAsyncCall(*JSC.JSGlobalObject, AsyncCallType, u64, bool) void; + extern fn Debugger__didCancelAsyncCall(*JSC.JSGlobalObject, AsyncCallType, u64) void; + extern fn Debugger__didDispatchAsyncCall(*JSC.JSGlobalObject, AsyncCallType, u64) void; + extern fn Debugger__willDispatchAsyncCall(*JSC.JSGlobalObject, AsyncCallType, u64) void; + + pub fn didScheduleAsyncCall(globalObject: *JSC.JSGlobalObject, call: AsyncCallType, id: u64, single_shot: bool) void { + JSC.markBinding(@src()); + Debugger__didScheduleAsyncCall(globalObject, call, id, single_shot); + } + pub fn didCancelAsyncCall(globalObject: *JSC.JSGlobalObject, call: AsyncCallType, id: u64) void { + JSC.markBinding(@src()); + Debugger__didCancelAsyncCall(globalObject, call, id); + } + pub fn didDispatchAsyncCall(globalObject: *JSC.JSGlobalObject, call: AsyncCallType, id: u64) void { + JSC.markBinding(@src()); + Debugger__didDispatchAsyncCall(globalObject, call, id); + } + pub fn willDispatchAsyncCall(globalObject: *JSC.JSGlobalObject, call: AsyncCallType, id: u64) void { + JSC.markBinding(@src()); + Debugger__willDispatchAsyncCall(globalObject, call, id); + } +}; diff --git a/src/bun.js/bindings/InternalModuleRegistry.cpp b/src/bun.js/bindings/InternalModuleRegistry.cpp index 8323560d6..8dbf42a02 100644 --- a/src/bun.js/bindings/InternalModuleRegistry.cpp +++ b/src/bun.js/bindings/InternalModuleRegistry.cpp @@ -160,4 +160,4 @@ JSC_DEFINE_HOST_FUNCTION(InternalModuleRegistry::jsCreateInternalModuleById, (JS } // namespace Bun #undef INTERNAL_MODULE_REGISTRY_GENERATE_ -#undef INTERNAL_MODULE_REGISTRY_GENERATE +#undef INTERNAL_MODULE_REGISTRY_GENERATE
\ No newline at end of file diff --git a/src/bun.js/bindings/JSBundlerPlugin.cpp b/src/bun.js/bindings/JSBundlerPlugin.cpp index ec3933574..6ae266df7 100644 --- a/src/bun.js/bindings/JSBundlerPlugin.cpp +++ b/src/bun.js/bindings/JSBundlerPlugin.cpp @@ -404,7 +404,7 @@ extern "C" EncodedJSValue JSBundlerPlugin__runSetupFunction( arguments.append(JSValue::decode(encodedConfig)); auto* lexicalGlobalObject = jsCast<JSFunction*>(JSValue::decode(encodedSetupFunction))->globalObject(); - auto result = JSC::call(lexicalGlobalObject, setupFunction, callData, plugin, arguments); + auto result = call(lexicalGlobalObject, setupFunction, callData, plugin, arguments); if (UNLIKELY(scope.exception())) { auto exception = scope.exception(); scope.clearException(); diff --git a/src/bun.js/bindings/JSReadableHelper.cpp b/src/bun.js/bindings/JSReadableHelper.cpp index 3a4de4637..0c459f329 100644 --- a/src/bun.js/bindings/JSReadableHelper.cpp +++ b/src/bun.js/bindings/JSReadableHelper.cpp @@ -38,7 +38,7 @@ static bool callRead(JSValue stream, JSFunction* read, JSC::MarkedArgumentBuffer { WTF::NakedPtr<JSC::Exception> exceptionPtr; JSC::CallData callData = JSC::getCallData(read); - JSValue ret = JSC::call(lexicalGlobalObject, read, callData, JSValue(stream), WTFMove(args), exceptionPtr); + JSValue ret = call(lexicalGlobalObject, read, callData, JSValue(stream), WTFMove(args), exceptionPtr); if (auto* exception = exceptionPtr.get()) { JSC::Identifier errorEventName = JSC::Identifier::fromString(vm, "error"_s); if (emitter.hasEventListeners(errorEventName)) { diff --git a/src/bun.js/bindings/ModuleLoader.cpp b/src/bun.js/bindings/ModuleLoader.cpp index 2c8b95612..4e2de9294 100644 --- a/src/bun.js/bindings/ModuleLoader.cpp +++ b/src/bun.js/bindings/ModuleLoader.cpp @@ -359,7 +359,7 @@ static JSValue handleVirtualModuleResult( arguments.append(jsUndefined()); arguments.append(pendingModule); ASSERT(!arguments.hasOverflowed()); - JSC::call(globalObject, performPromiseThenFunction, callData, jsUndefined(), arguments); + JSC::profiledCall(globalObject, ProfilingReason::Microtask, performPromiseThenFunction, callData, jsUndefined(), arguments); return internalPromise; } default: { diff --git a/src/bun.js/bindings/ScriptExecutionContext.cpp b/src/bun.js/bindings/ScriptExecutionContext.cpp index eab41d584..2113c9f64 100644 --- a/src/bun.js/bindings/ScriptExecutionContext.cpp +++ b/src/bun.js/bindings/ScriptExecutionContext.cpp @@ -114,12 +114,17 @@ void ScriptExecutionContext::willDestroyDestructionObserver(ContextDestructionOb m_destructionObservers.remove(&observer); } +bool ScriptExecutionContext::isJSExecutionForbidden() +{ + return !m_vm || m_vm->executionForbidden(); +} + extern "C" void* Bun__getVM(); bool ScriptExecutionContext::isContextThread() { auto clientData = WebCore::clientData(vm()); - return clientData->bunVM == Bun__getVM(); + return clientData && clientData->bunVM == Bun__getVM(); } bool ScriptExecutionContext::ensureOnContextThread(ScriptExecutionContextIdentifier identifier, Function<void(ScriptExecutionContext&)>&& task) diff --git a/src/bun.js/bindings/ScriptExecutionContext.h b/src/bun.js/bindings/ScriptExecutionContext.h index b32435240..6f10dced7 100644 --- a/src/bun.js/bindings/ScriptExecutionContext.h +++ b/src/bun.js/bindings/ScriptExecutionContext.h @@ -129,7 +129,7 @@ public: bool isContextThread(); bool isDocument() { return false; } bool isWorkerGlobalScope() { return true; } - bool isJSExecutionForbidden() { return false; } + bool isJSExecutionForbidden(); void reportException(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL, JSC::Exception* exception, RefPtr<void*>&&, CachedScript* = nullptr, bool = false) { } diff --git a/src/bun.js/bindings/Strong.cpp b/src/bun.js/bindings/Strong.cpp index 8ec63e318..045b4484a 100644 --- a/src/bun.js/bindings/Strong.cpp +++ b/src/bun.js/bindings/Strong.cpp @@ -1,28 +1,9 @@ #include "root.h" #include <JavaScriptCore/StrongInlines.h> #include "BunClientData.h" - +#include "Strong.h" namespace Bun { -// We tried to pool these -// But it was very complicated -class StrongRef { - WTF_MAKE_ISO_ALLOCATED(StrongRef); - -public: - StrongRef(JSC::VM& vm, JSC::JSValue value) - : m_cell(vm, value) - { - } - - StrongRef() - : m_cell() - { - } - - JSC::Strong<JSC::Unknown> m_cell; -}; - WTF_MAKE_ISO_ALLOCATED_IMPL(StrongRef); } diff --git a/src/bun.js/bindings/Strong.h b/src/bun.js/bindings/Strong.h new file mode 100644 index 000000000..f39d1c611 --- /dev/null +++ b/src/bun.js/bindings/Strong.h @@ -0,0 +1,27 @@ +#pragma once + +#include "root.h" +#include "JavaScriptCore/Strong.h" + +namespace Bun { + +// We tried to pool these +// But it was very complicated +class StrongRef { + WTF_MAKE_ISO_ALLOCATED(StrongRef); + +public: + StrongRef(JSC::VM& vm, JSC::JSValue value) + : m_cell(vm, value) + { + } + + StrongRef() + : m_cell() + { + } + + JSC::Strong<JSC::Unknown> m_cell; +}; + +}
\ No newline at end of file diff --git a/src/bun.js/bindings/ZigConsoleClient.cpp b/src/bun.js/bindings/ZigConsoleClient.cpp index a98c246de..6fb364d93 100644 --- a/src/bun.js/bindings/ZigConsoleClient.cpp +++ b/src/bun.js/bindings/ZigConsoleClient.cpp @@ -8,6 +8,12 @@ #include "ZigConsoleClient.h" #include "wtf/text/WTFString.h" +#undef ENABLE_INSPECTOR_ALTERNATE_DISPATCHERS + +#include "JavaScriptCore/JSGlobalObjectInspectorController.h" +#include "JavaScriptCore/JSGlobalObjectDebuggable.h" +#include "JavaScriptCore/ConsoleClient.h" + #include "GCDefferalContext.h" using ScriptArguments = Inspector::ScriptArguments; @@ -24,6 +30,11 @@ void Zig::ConsoleClient::messageWithTypeAndLevel(MessageType type, MessageLevel JSC::JSGlobalObject* globalObject, Ref<ScriptArguments>&& arguments) { + if (globalObject->inspectable()) { + if (auto* client = globalObject->inspectorController().consoleClient().get()) { + client->messageWithTypeAndLevel(type, level, globalObject, arguments.copyRef()); + } + } JSC::VM& vm = globalObject->vm(); auto args = arguments.ptr(); JSC__JSValue jsArgs[255]; diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 3de7d3daa..baa1ddda7 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -3863,7 +3863,7 @@ EncodedJSValue GlobalObject::assignToStream(JSValue stream, JSValue controller) arguments.append(stream); arguments.append(controller); - auto result = JSC::call(this, function, callData, JSC::jsUndefined(), arguments); + auto result = call(this, function, callData, JSC::jsUndefined(), arguments); if (scope.exception()) return JSC::JSValue::encode(scope.exception()); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 1d82cd0f3..0535b1e8f 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -398,6 +398,7 @@ public: mutable WriteBarrier<Unknown> m_JSWebSocketSetterValue; mutable WriteBarrier<Unknown> m_JSWorkerSetterValue; + mutable WriteBarrier<Unknown> m_JSBunDebuggerValue; mutable WriteBarrier<JSFunction> m_thenables[promiseFunctionsSize + 1]; Structure* memoryFootprintStructure() diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index f7998c83c..e1d6ba526 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -252,7 +252,8 @@ static void handlePromise(PromiseType* promise, JSC__JSGlobalObject* globalObjec arguments.append(jsUndefined()); arguments.append(JSValue::decode(ctx)); ASSERT(!arguments.hasOverflowed()); - JSC::call(globalThis, performPromiseThenFunction, callData, jsUndefined(), arguments); + // async context tracking is handled by performPromiseThenFunction internally. + JSC::profiledCall(globalThis, JSC::ProfilingReason::Microtask, performPromiseThenFunction, callData, jsUndefined(), arguments); } else { promise->then(globalThis, resolverFunction, rejecterFunction); } @@ -1770,7 +1771,7 @@ extern "C" JSC__JSValue JSObjectCallAsFunctionReturnValue(JSContextRef ctx, JSC_ return JSC::JSValue::encode(JSC::JSValue()); NakedPtr<JSC::Exception> returnedException = nullptr; - auto result = JSC::call(globalObject, jsObject, callData, jsThisObject, argList, returnedException); + auto result = JSC::profiledCall(globalObject, ProfilingReason::API, jsObject, callData, jsThisObject, argList, returnedException); if (asyncContextData) { asyncContextData->putInternalField(vm, 0, restoreAsyncContext); @@ -1811,7 +1812,7 @@ JSC__JSValue JSObjectCallAsFunctionReturnValueHoldingAPILock(JSContextRef ctx, J return JSC::JSValue::encode(JSC::JSValue()); NakedPtr<JSC::Exception> returnedException = nullptr; - auto result = JSC::call(globalObject, jsObject, callData, jsThisObject, argList, returnedException); + auto result = call(globalObject, jsObject, callData, jsThisObject, argList, returnedException); if (returnedException.get()) { return JSC::JSValue::encode(JSC::JSValue(returnedException.get())); diff --git a/src/bun.js/bindings/debug-helpers.h b/src/bun.js/bindings/debug-helpers.h new file mode 100644 index 000000000..a0fc99868 --- /dev/null +++ b/src/bun.js/bindings/debug-helpers.h @@ -0,0 +1,18 @@ +#include "root.h" + +#include "JavaScriptCore/InspectorDebuggerAgent.h" + +namespace JSC { +Inspector::InspectorDebuggerAgent* debuggerAgent(JSC::JSGlobalObject* globalObject) +{ + if (LIKELY(!globalObject->hasDebugger())) { + return nullptr; + } + + if (auto* debugger = globalObject->debugger()) { + return dynamicDowncast<Inspector::InspectorDebuggerAgent>(debugger->client()); + } + + return nullptr; +} +}
\ No newline at end of file diff --git a/src/bun.js/bindings/napi.cpp b/src/bun.js/bindings/napi.cpp index c27bcf533..d532e5444 100644 --- a/src/bun.js/bindings/napi.cpp +++ b/src/bun.js/bindings/napi.cpp @@ -1918,7 +1918,7 @@ extern "C" napi_status napi_call_function(napi_env env, napi_value recv_napi, if (thisValue.isEmpty()) { thisValue = JSC::jsUndefined(); } - JSC::JSValue result = JSC::call(globalObject, funcValue, callData, thisValue, args); + JSC::JSValue result = call(globalObject, funcValue, callData, thisValue, args); if (result_ptr) { *result_ptr = toNapi(result); diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h index a81b84577..4c09df6a5 100644 --- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h @@ -41,7 +41,7 @@ public: std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMockWithImplementationCleanupData; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForProcessObject; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForInternalModuleRegistry; - + std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForBunInspectorConnection; #include "ZigGeneratedClasses+DOMClientIsoSubspaces.h" /* --- bun --- */ diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h index c67112388..2b834cf3c 100644 --- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h @@ -41,6 +41,7 @@ public: std::unique_ptr<IsoSubspace> m_subspaceForMockWithImplementationCleanupData; std::unique_ptr<IsoSubspace> m_subspaceForProcessObject; std::unique_ptr<IsoSubspace> m_subspaceForInternalModuleRegistry; + std::unique_ptr<IsoSubspace> m_subspaceForBunInspectorConnection; #include "ZigGeneratedClasses+DOMIsoSubspaces.h" /*-- BUN --*/ diff --git a/src/bun.js/bindings/webcore/EventEmitter.cpp b/src/bun.js/bindings/webcore/EventEmitter.cpp index 0e273042b..de0be2c89 100644 --- a/src/bun.js/bindings/webcore/EventEmitter.cpp +++ b/src/bun.js/bindings/webcore/EventEmitter.cpp @@ -234,7 +234,7 @@ void EventEmitter::innerInvokeEventListeners(const Identifier& eventType, Simple continue; WTF::NakedPtr<JSC::Exception> exceptionPtr; - JSC::call(lexicalGlobalObject, jsFunction, callData, thisValue, arguments, exceptionPtr); + call(lexicalGlobalObject, jsFunction, callData, thisValue, arguments, exceptionPtr); auto* exception = exceptionPtr.get(); if (UNLIKELY(exception)) { |