mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
716 lines
27 KiB
C++
716 lines
27 KiB
C++
#include "root.h"
|
|
|
|
#include "ZigGlobalObject.h"
|
|
|
|
#include <JavaScriptCore/InspectorFrontendChannel.h>
|
|
#include <JavaScriptCore/StopTheWorldCallback.h>
|
|
#include <JavaScriptCore/VMManager.h>
|
|
#include <JavaScriptCore/JSGlobalObjectDebuggable.h>
|
|
#include <JavaScriptCore/JSGlobalObjectDebugger.h>
|
|
#include <JavaScriptCore/Debugger.h>
|
|
#include "ScriptExecutionContext.h"
|
|
#include "debug-helpers.h"
|
|
#include "BunInjectedScriptHost.h"
|
|
#include <JavaScriptCore/JSGlobalObjectInspectorController.h>
|
|
|
|
#include "InspectorLifecycleAgent.h"
|
|
#include "InspectorTestReporterAgent.h"
|
|
#include "InspectorBunFrontendDevServerAgent.h"
|
|
#include "InspectorHTTPServerAgent.h"
|
|
|
|
extern "C" void Bun__tickWhilePaused(bool*);
|
|
extern "C" void Bun__eventLoop__incrementRefConcurrently(void* bunVM, int delta);
|
|
|
|
namespace Bun {
|
|
using namespace JSC;
|
|
using namespace WebCore;
|
|
|
|
class BunInspectorConnection;
|
|
|
|
static WebCore::ScriptExecutionContext* debuggerScriptExecutionContext = nullptr;
|
|
static WTF::Lock inspectorConnectionsLock = WTF::Lock();
|
|
static WTF::UncheckedKeyHashMap<ScriptExecutionContextIdentifier, Vector<BunInspectorConnection*, 8>>* inspectorConnections = nullptr;
|
|
|
|
static bool waitingForConnection = false;
|
|
extern "C" void Debugger__didConnect();
|
|
|
|
class BunJSGlobalObjectDebuggable final : public JSC::JSGlobalObjectDebuggable {
|
|
public:
|
|
using Base = JSC::JSGlobalObjectDebuggable;
|
|
|
|
BunJSGlobalObjectDebuggable(JSC::JSGlobalObject& globalObject)
|
|
: Base(globalObject)
|
|
{
|
|
}
|
|
|
|
~BunJSGlobalObjectDebuggable() final
|
|
{
|
|
}
|
|
|
|
static Ref<BunJSGlobalObjectDebuggable> create(JSGlobalObject& globalObject)
|
|
{
|
|
return adoptRef(*new BunJSGlobalObjectDebuggable(globalObject));
|
|
}
|
|
|
|
void pauseWaitingForAutomaticInspection() override
|
|
{
|
|
}
|
|
void unpauseForResolvedAutomaticInspection() override
|
|
{
|
|
if (waitingForConnection) {
|
|
waitingForConnection = false;
|
|
Debugger__didConnect();
|
|
}
|
|
}
|
|
};
|
|
|
|
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, bool shouldRefEventLoop)
|
|
: Inspector::FrontendChannel()
|
|
, globalObject(globalObject)
|
|
, scriptExecutionContextIdentifier(scriptExecutionContext.identifier())
|
|
, unrefOnDisconnect(shouldRefEventLoop)
|
|
{
|
|
}
|
|
|
|
~BunInspectorConnection()
|
|
{
|
|
}
|
|
|
|
static BunInspectorConnection* create(ScriptExecutionContext& scriptExecutionContext, JSC::JSGlobalObject* globalObject, bool shouldRefEventLoop)
|
|
{
|
|
return new BunInspectorConnection(scriptExecutionContext, globalObject, shouldRefEventLoop);
|
|
}
|
|
|
|
ConnectionType connectionType() const override
|
|
{
|
|
return ConnectionType::Remote;
|
|
}
|
|
|
|
void doConnect(WebCore::ScriptExecutionContext& context)
|
|
{
|
|
this->status = ConnectionStatus::Connected;
|
|
auto* globalObject = context.jsGlobalObject();
|
|
if (this->unrefOnDisconnect) {
|
|
Bun__eventLoop__incrementRefConcurrently(static_cast<Zig::GlobalObject*>(globalObject)->bunVM(), 1);
|
|
}
|
|
globalObject->setInspectable(true);
|
|
auto& inspector = globalObject->inspectorDebuggable();
|
|
inspector.setInspectable(true);
|
|
|
|
static bool hasConnected = false;
|
|
|
|
if (!hasConnected) {
|
|
hasConnected = true;
|
|
globalObject->inspectorController().registerAlternateAgent(
|
|
WTF::makeUniqueRef<Inspector::InspectorLifecycleAgent>(*globalObject));
|
|
globalObject->inspectorController().registerAlternateAgent(
|
|
WTF::makeUniqueRef<Inspector::InspectorTestReporterAgent>(*globalObject));
|
|
globalObject->inspectorController().registerAlternateAgent(
|
|
WTF::makeUniqueRef<Inspector::InspectorBunFrontendDevServerAgent>(*globalObject));
|
|
globalObject->inspectorController().registerAlternateAgent(
|
|
WTF::makeUniqueRef<Inspector::InspectorHTTPServerAgent>(*globalObject));
|
|
}
|
|
|
|
this->hasEverConnected = true;
|
|
globalObject->inspectorController().connectFrontend(*this, true, false); // waitingForConnection
|
|
|
|
Inspector::JSGlobalObjectDebugger* debugger = reinterpret_cast<Inspector::JSGlobalObjectDebugger*>(globalObject->debugger());
|
|
if (debugger) {
|
|
debugger->runWhilePausedCallback = [](JSC::JSGlobalObject& globalObject, bool& isDoneProcessingEvents) -> void {
|
|
BunInspectorConnection::runWhilePaused(globalObject, isDoneProcessingEvents);
|
|
};
|
|
}
|
|
|
|
this->receiveMessagesOnInspectorThread(context, static_cast<Zig::GlobalObject*>(globalObject), false);
|
|
}
|
|
|
|
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->doConnect(context);
|
|
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;
|
|
|
|
// Do not call .disconnect() if we never actually connected.
|
|
if (connection->hasEverConnected) {
|
|
connection->inspector().disconnect(*connection);
|
|
}
|
|
|
|
if (connection->unrefOnDisconnect) {
|
|
connection->unrefOnDisconnect = false;
|
|
Bun__eventLoop__incrementRefConcurrently(static_cast<Zig::GlobalObject*>(context.jsGlobalObject())->bunVM(), -1);
|
|
}
|
|
});
|
|
}
|
|
|
|
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 = static_cast<Zig::GlobalObject*>(&globalObject);
|
|
Vector<BunInspectorConnection*, 8> connections;
|
|
{
|
|
Locker<Lock> locker(inspectorConnectionsLock);
|
|
connections.appendVector(inspectorConnections->get(global->scriptExecutionContext()->identifier()));
|
|
}
|
|
|
|
for (auto* connection : connections) {
|
|
if (connection->status == ConnectionStatus::Pending) {
|
|
connection->connect();
|
|
continue;
|
|
}
|
|
|
|
if (connection->status != ConnectionStatus::Disconnected) {
|
|
connection->receiveMessagesOnInspectorThread(*global->scriptExecutionContext(), global, true);
|
|
}
|
|
}
|
|
|
|
// 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, true);
|
|
}
|
|
} 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, true);
|
|
if (isDoneProcessingEvents)
|
|
break;
|
|
}
|
|
|
|
if (closedCount == connections.size() && global->debugger() && !isDoneProcessingEvents) {
|
|
global->debugger()->continueProgram();
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void receiveMessagesOnInspectorThread(ScriptExecutionContext& context, Zig::GlobalObject* globalObject, bool connectIfNeeded)
|
|
{
|
|
this->jsThreadMessageScheduledCount.store(0);
|
|
WTF::Vector<WTF::String, 12> messages;
|
|
|
|
{
|
|
Locker<Lock> locker(jsThreadMessagesLock);
|
|
this->jsThreadMessages.swap(messages);
|
|
}
|
|
|
|
auto& dispatcher = globalObject->inspectorDebuggable();
|
|
Inspector::JSGlobalObjectDebugger* debugger = reinterpret_cast<Inspector::JSGlobalObjectDebugger*>(globalObject->debugger());
|
|
|
|
if (!debugger) {
|
|
if (connectIfNeeded && this->status == ConnectionStatus::Pending) {
|
|
this->doConnect(context);
|
|
return;
|
|
}
|
|
|
|
for (auto message : messages) {
|
|
dispatcher.dispatchMessageFromRemote(WTF::move(message));
|
|
|
|
if (!debugger) {
|
|
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(WTF::move(message));
|
|
}
|
|
}
|
|
|
|
messages.clear();
|
|
}
|
|
|
|
void receiveMessagesOnDebuggerThread(ScriptExecutionContext& context, Zig::GlobalObject* debuggerGlobalObject)
|
|
{
|
|
debuggerThreadMessageScheduledCount.store(0);
|
|
WTF::Vector<WTF::String, 12> messages;
|
|
|
|
{
|
|
Locker<Lock> locker(debuggerThreadMessagesLock);
|
|
this->debuggerThreadMessages.swap(messages);
|
|
}
|
|
|
|
JSFunction* onMessageFn = jsCast<JSFunction*>(jsBunDebuggerOnMessageFunction.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)
|
|
{
|
|
{
|
|
Locker<Lock> locker(debuggerThreadMessagesLock);
|
|
debuggerThreadMessages.append(inputMessage);
|
|
}
|
|
|
|
if (this->debuggerThreadMessageScheduledCount++ == 0) {
|
|
debuggerScriptExecutionContext->postTaskConcurrently([connection = this](ScriptExecutionContext& context) {
|
|
connection->receiveMessagesOnDebuggerThread(context, static_cast<Zig::GlobalObject*>(context.jsGlobalObject()));
|
|
});
|
|
}
|
|
}
|
|
|
|
void sendMessageToInspectorFromDebuggerThread(Vector<WTF::String, 12>&& inputMessages)
|
|
{
|
|
{
|
|
Locker<Lock> locker(jsThreadMessagesLock);
|
|
jsThreadMessages.appendVector(inputMessages);
|
|
}
|
|
|
|
if (this->jsWaitForMessageFromInspectorLock.isLocked()) {
|
|
this->jsWaitForMessageFromInspectorLock.unlock();
|
|
} else if (this->jsThreadMessageScheduledCount++ == 0) {
|
|
ScriptExecutionContext::postTaskTo(scriptExecutionContextIdentifier, [connection = this](ScriptExecutionContext& context) {
|
|
connection->receiveMessagesOnInspectorThread(context, static_cast<Zig::GlobalObject*>(context.jsGlobalObject()), true);
|
|
});
|
|
}
|
|
}
|
|
|
|
void sendMessageToInspectorFromDebuggerThread(const WTF::String& inputMessage)
|
|
{
|
|
{
|
|
Locker<Lock> 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, static_cast<Zig::GlobalObject*>(context.jsGlobalObject()), true);
|
|
});
|
|
}
|
|
}
|
|
|
|
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;
|
|
JSC::Strong<JSC::Unknown> jsBunDebuggerOnMessageFunction {};
|
|
|
|
WTF::Lock jsWaitForMessageFromInspectorLock;
|
|
std::atomic<ConnectionStatus> status = ConnectionStatus::Pending;
|
|
|
|
bool unrefOnDisconnect = false;
|
|
|
|
bool hasEverConnected = false;
|
|
};
|
|
|
|
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 JSC::DestructionMode needsDestruction = DoesNotNeedDestruction;
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
|
|
if (!jsConnection)
|
|
return JSValue::encode(jsUndefined());
|
|
|
|
if (message.isString()) {
|
|
jsConnection->connection()->sendMessageToInspectorFromDebuggerThread(message.toWTFString(globalObject).isolatedCopy());
|
|
} else if (message.isCell()) {
|
|
auto* array = jsCast<JSArray*>(message.asCell());
|
|
Vector<WTF::String, 12> messages;
|
|
JSC::forEachInArrayLike(globalObject, array, [&](JSC::JSValue value) -> bool {
|
|
messages.append(value.toWTFString(globalObject).isolatedCopy());
|
|
return true;
|
|
});
|
|
jsConnection->connection()->sendMessageToInspectorFromDebuggerThread(WTF::move(messages));
|
|
}
|
|
|
|
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)
|
|
{
|
|
{
|
|
Locker<Lock> locker(inspectorConnectionsLock);
|
|
if (inspectorConnections == nullptr) {
|
|
inspectorConnections = new WTF::UncheckedKeyHashMap<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->m_inspectorController = makeUnique<Inspector::JSGlobalObjectInspectorController>(*globalObject, Bun::BunInjectedScriptHost::create());
|
|
globalObject->m_inspectorDebuggable = BunJSGlobalObjectDebuggable::create(*globalObject);
|
|
globalObject->m_inspectorDebuggable->init();
|
|
|
|
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) {
|
|
waitingForConnection = true;
|
|
}
|
|
}
|
|
|
|
extern "C" void BunDebugger__willHotReload()
|
|
{
|
|
if (debuggerScriptExecutionContext == nullptr) {
|
|
return;
|
|
}
|
|
|
|
debuggerScriptExecutionContext->postTaskConcurrently([](ScriptExecutionContext& context) {
|
|
Locker<Lock> locker(inspectorConnectionsLock);
|
|
for (auto& connections : *inspectorConnections) {
|
|
for (auto* connection : connections.value) {
|
|
connection->sendMessageToFrontend("{\"method\":\"Bun.canReload\"}"_s);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
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)));
|
|
bool shouldRef = !callFrame->argument(1).toBoolean(globalObject);
|
|
JSFunction* onMessageFn = jsCast<JSFunction*>(callFrame->argument(2).toObject(globalObject));
|
|
|
|
if (!targetContext || !onMessageFn)
|
|
return JSValue::encode(jsUndefined());
|
|
|
|
auto& vm = JSC::getVM(globalObject);
|
|
auto connection = BunInspectorConnection::create(
|
|
*targetContext,
|
|
targetContext->jsGlobalObject(), shouldRef);
|
|
|
|
{
|
|
Locker<Lock> locker(inspectorConnectionsLock);
|
|
auto connections = inspectorConnections->get(targetContext->identifier());
|
|
connections.append(connection);
|
|
inspectorConnections->set(targetContext->identifier(), connections);
|
|
}
|
|
connection->jsBunDebuggerOnMessageFunction = { vm, onMessageFn };
|
|
connection->connect();
|
|
|
|
return JSValue::encode(JSBunInspectorConnection::create(vm, JSBunInspectorConnection::createStructure(vm, globalObject, globalObject->objectPrototype()), connection));
|
|
}
|
|
|
|
extern "C" void Bun__startJSDebuggerThread(Zig::GlobalObject* debuggerGlobalObject, ScriptExecutionContextIdentifier scriptId, BunString* portOrPathString, int isAutomatic, bool isUrlServer)
|
|
{
|
|
if (!debuggerScriptExecutionContext)
|
|
debuggerScriptExecutionContext = debuggerGlobalObject->scriptExecutionContext();
|
|
|
|
JSC::VM& vm = debuggerGlobalObject->vm();
|
|
auto scope = DECLARE_CATCH_SCOPE(vm);
|
|
JSValue defaultValue = debuggerGlobalObject->internalModuleRegistry()->requireId(debuggerGlobalObject, vm, InternalModuleRegistry::Field::InternalDebugger);
|
|
scope.assertNoException();
|
|
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, 3, 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));
|
|
arguments.append(jsBoolean(isAutomatic));
|
|
arguments.append(jsBoolean(isUrlServer));
|
|
|
|
JSC::call(debuggerGlobalObject, debuggerDefaultFn, arguments, "Bun__initJSDebuggerThread - debuggerDefaultFn"_s);
|
|
scope.assertNoException();
|
|
}
|
|
|
|
enum class AsyncCallTypeUint8 : uint8_t {
|
|
DOMTimer = 1,
|
|
EventListener = 2,
|
|
PostMessage = 3,
|
|
RequestAnimationFrame = 4,
|
|
Microtask = 5,
|
|
};
|
|
|
|
static Inspector::InspectorDebuggerAgent::AsyncCallType getCallType(AsyncCallTypeUint8 callType)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
// StopTheWorld callback for SIGUSR1 debugger activation.
|
|
// This runs on the main thread at a safe point when VMManager::requestStopAll(JSDebugger) is called.
|
|
//
|
|
// Note: These APIs require the updated oven-sh/WebKit with StopReason::JSDebugger support.
|
|
// Until WebKit is updated, these will cause build errors.
|
|
|
|
extern "C" bool Bun__checkInspectorActivationRequest();
|
|
extern "C" void Bun__activateInspector(JSC::JSGlobalObject*);
|
|
|
|
#include <JavaScriptCore/VMEntryScope.h>
|
|
|
|
// This callback is registered with VMManager::setJSDebuggerCallback() and called
|
|
// when VMManager::requestStopAll(JSDebugger) is invoked. It cannot have C linkage
|
|
// because it returns a C++ type (std::pair).
|
|
//
|
|
// This handles the case where JS is actively executing (including infinite loops).
|
|
// For idle VMs, RuntimeInspector::checkAndActivateInspector handles it via event loop.
|
|
JSC::StopTheWorldStatus Bun__jsDebuggerCallback(JSC::VM& vm, JSC::StopTheWorldEvent event)
|
|
{
|
|
using namespace JSC;
|
|
|
|
// Only handle VMStopped events - this is when all VMs have stopped and we can safely activate
|
|
if (event != StopTheWorldEvent::VMStopped)
|
|
return STW_CONTINUE();
|
|
|
|
// Check if this is a debugger activation request from SIGUSR1
|
|
if (!Bun__checkInspectorActivationRequest())
|
|
return STW_RESUME_ALL();
|
|
|
|
// Activate the inspector/debugger
|
|
// Note: Bun__activateInspector uses thread-local VM, so globalObject param is unused.
|
|
Bun__activateInspector(nullptr);
|
|
|
|
// Fire the debugger break trap - now that the debugger exists, this will work
|
|
vm.notifyNeedDebuggerBreak();
|
|
|
|
return STW_RESUME_ALL();
|
|
}
|
|
|
|
// Zig binding for VMManager::requestStopAll
|
|
// Note: StopReason is a bitmask (uint32_t), not sequential values
|
|
extern "C" void VMManager__requestStopAll(uint32_t reason)
|
|
{
|
|
JSC::VMManager::requestStopAll(static_cast<JSC::VMManager::StopReason>(reason));
|
|
}
|
|
|
|
// Zig binding for VMManager::requestResumeAll
|
|
extern "C" void VMManager__requestResumeAll(uint32_t reason)
|
|
{
|
|
JSC::VMManager::requestResumeAll(static_cast<JSC::VMManager::StopReason>(reason));
|
|
}
|