From 86c465c9e253f9da720e0a6b9a55cd4a4c9dd66b Mon Sep 17 00:00:00 2001 From: Alistair Smith Date: Thu, 12 Feb 2026 17:55:37 -0800 Subject: [PATCH 1/8] try a third time --- Source/JavaScriptCore/debugger/Debugger.cpp | 33 +++++++++++++++++++ Source/JavaScriptCore/debugger/Debugger.h | 2 ++ .../debugger/DebuggerCallFrame.cpp | 9 ++++- Source/JavaScriptCore/runtime/VMTraps.cpp | 25 ++++++++++++++ Source/cmake/WebKitCompilerFlags.cmake | 8 +++-- 5 files changed, 74 insertions(+), 3 deletions(-) diff --git a/Source/JavaScriptCore/debugger/Debugger.cpp b/Source/JavaScriptCore/debugger/Debugger.cpp index 1aa6438cedc5..f3e2bff6f99b 100644 --- a/Source/JavaScriptCore/debugger/Debugger.cpp +++ b/Source/JavaScriptCore/debugger/Debugger.cpp @@ -28,6 +28,7 @@ #include "HeapIterationScope.h" #include "JSCInlines.h" #include "JSGenerator.h" +#include "JSModuleRecord.h" #include "MarkedSpaceInlines.h" #include "Microtask.h" #include "VMEntryScopeInlines.h" @@ -165,6 +166,38 @@ Debugger::~Debugger() void Debugger::attach(JSGlobalObject* globalObject) { + if (globalObject->debugger() == this) { +#if USE(BUN_JSC_ADDITIONS) + // Debugger was pre-attached (e.g., SIGUSR1 runtime inspector activation) + // before any observers were registered. sourceParsed() was a no-op during + // the initial attach() because canDispatchFunctionToObservers() returned + // false. Replay sourceParsed events now that observers have been added + // (via Debugger.enable → addObserver). + if (canDispatchFunctionToObservers()) { + UncheckedKeyHashSet> sourceProviders; + { + JSLockHolder locker(m_vm); + HeapIterationScope iterationScope(m_vm.heap); + m_vm.heap.objectSpace().forEachLiveCell(iterationScope, [&] (HeapCell* heapCell, HeapCell::Kind kind) { + if (isJSCellKind(kind)) { + auto* cell = static_cast(heapCell); + if (auto* function = jsDynamicCast(cell)) { + if (function->scope()->globalObject() == globalObject && function->executable()->isFunctionExecutable() && !function->isHostOrBuiltinFunction()) + sourceProviders.add(jsCast(function->executable())->source().provider()); + } else if (auto* moduleRecord = jsDynamicCast(cell)) { + if (auto* provider = moduleRecord->sourceCode().provider()) + sourceProviders.add(provider); + } + } + return IterationStatus::Continue; + }); + } + for (auto& sourceProvider : sourceProviders) + sourceParsed(globalObject, sourceProvider.get(), -1, nullString()); + } +#endif + return; + } ASSERT(!globalObject->debugger()); globalObject->setDebugger(this); m_globalObjects.add(globalObject); diff --git a/Source/JavaScriptCore/debugger/Debugger.h b/Source/JavaScriptCore/debugger/Debugger.h index 2f664e697202..3fb21094effc 100644 --- a/Source/JavaScriptCore/debugger/Debugger.h +++ b/Source/JavaScriptCore/debugger/Debugger.h @@ -161,6 +161,8 @@ class Debugger : public DoublyLinkedListNode { void registerCodeBlock(CodeBlock*); void forEachRegisteredCodeBlock(NOESCAPE const Function&); + bool isPauseAtNextOpportunitySet() const { return m_pauseAtNextOpportunity; } + void didCreateNativeExecutable(NativeExecutable&); void willCallNativeExecutable(CallFrame*); diff --git a/Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp b/Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp index 10be94d63a3f..5d3176d4a9de 100644 --- a/Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp +++ b/Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp @@ -152,7 +152,14 @@ DebuggerScope* DebuggerCallFrame::scope(VM& vm) CodeBlock* codeBlock = m_validMachineFrame->isNativeCalleeFrame() ? nullptr : m_validMachineFrame->codeBlock(); if (isTailDeleted()) scope = m_shadowChickenFrame.scope; - else if (codeBlock && codeBlock->scopeRegister().isValid()) + // Code compiled without CodeGenerationMode::Debugger may have its scope + // register repurposed by the DFG (DFGStackLayoutPhase invalidates it when + // needsScopeRegister() is false). Reading the scope register from such a + // frame crashes because the slot contains stale data (e.g., a VirtualRegister + // offset instead of a JSScope pointer). ShadowChicken has the same guard. + // Falls through to callee->scope() which is always valid. + else if (codeBlock && codeBlock->scopeRegister().isValid() + && codeBlock->wasCompiledWithDebuggingOpcodes()) scope = m_validMachineFrame->scope(codeBlock->scopeRegister().offset()); else if (JSCallee* callee = jsDynamicCast(m_validMachineFrame->jsCallee())) scope = callee->scope(); diff --git a/Source/JavaScriptCore/runtime/VMTraps.cpp b/Source/JavaScriptCore/runtime/VMTraps.cpp index 13a32c3df2b8..394934bd5e49 100644 --- a/Source/JavaScriptCore/runtime/VMTraps.cpp +++ b/Source/JavaScriptCore/runtime/VMTraps.cpp @@ -29,6 +29,7 @@ #include "CallFrameInlines.h" #include "CodeBlock.h" #include "CodeBlockSet.h" +#include "Debugger.h" #include "DFGCommonData.h" #include "ExceptionHelpers.h" #include "HeapInlines.h" @@ -46,6 +47,11 @@ #include #include +#if USE(BUN_JSC_ADDITIONS) +extern "C" __attribute__((weak)) void Bun__drainQueuedCDPMessages(JSC::VM&); +extern "C" __attribute__((weak)) bool Bun__shouldBreakAfterMessageDrain(JSC::VM&); +#endif + namespace JSC { #if ENABLE(SIGNAL_BASED_VM_TRAPS) @@ -474,9 +480,28 @@ bool VMTraps::handleTraps(VMTraps::BitField mask) bool didHandleTrap = false; while (needHandling(mask)) { auto event = takeTopPriorityTrap(mask); + if (event == NoEvent) + break; switch (event) { case NeedDebuggerBreak: invalidateCodeBlocksOnStack(vm.topCallFrame); +#if USE(BUN_JSC_ADDITIONS) + // Drain queued CDP messages. If a command like Debugger.pause + // is dispatched, it sets m_javaScriptPauseScheduled on the agent. + if (Bun__drainQueuedCDPMessages) + Bun__drainQueuedCDPMessages(vm); + // Only enter breakProgram() if a pause was actually requested + // (bootstrap, Debugger.pause command, breakpoint). For plain + // message delivery, the drain above is sufficient. + if (!Bun__shouldBreakAfterMessageDrain || Bun__shouldBreakAfterMessageDrain(vm)) { + if (vm.topCallFrame) { + if (auto* globalObject = vm.topCallFrame->lexicalGlobalObject(vm)) { + if (auto* debugger = globalObject->debugger()) + debugger->breakProgram(); + } + } + } +#endif didHandleTrap = true; break; diff --git a/Source/cmake/WebKitCompilerFlags.cmake b/Source/cmake/WebKitCompilerFlags.cmake index c94cb1924ac5..0d071d3bd30f 100644 --- a/Source/cmake/WebKitCompilerFlags.cmake +++ b/Source/cmake/WebKitCompilerFlags.cmake @@ -265,7 +265,9 @@ if (COMPILER_IS_GCC_OR_CLANG) -Wl,-U,_WTFTimer__secondsUntilTimer -Wl,-U,_WTFTimer__cancel -Wl,-U,_Bun__errorInstance__finalize - -Wl,-U,_Bun__reportUnhandledError) + -Wl,-U,_Bun__reportUnhandledError + -Wl,-U,_Bun__drainQueuedCDPMessages + -Wl,-U,_Bun__shouldBreakAfterMessageDrain) else() WEBKIT_PREPEND_GLOBAL_COMPILER_FLAGS(-Wl,-u,_WTFTimer__create -Wl,-u,_WTFTimer__update @@ -274,7 +276,9 @@ if (COMPILER_IS_GCC_OR_CLANG) -Wl,-u,_WTFTimer__secondsUntilTimer -Wl,-u,_WTFTimer__cancel -Wl,-u,_Bun__errorInstance__finalize - -Wl,-u,_Bun__reportUnhandledError) + -Wl,-u,_Bun__reportUnhandledError + -Wl,-u,_Bun__drainQueuedCDPMessages + -Wl,-u,_Bun__shouldBreakAfterMessageDrain) endif() endif () From 9284eb5bb1bb9de3d2f1309cb4693f748448cef6 Mon Sep 17 00:00:00 2001 From: Alistair Smith Date: Thu, 12 Feb 2026 18:01:53 -0800 Subject: [PATCH 2/8] cleanup and make a notifySourceParsedForExistingCode() --- Source/JavaScriptCore/debugger/Debugger.cpp | 69 +++++++-------------- Source/JavaScriptCore/debugger/Debugger.h | 2 + 2 files changed, 26 insertions(+), 45 deletions(-) diff --git a/Source/JavaScriptCore/debugger/Debugger.cpp b/Source/JavaScriptCore/debugger/Debugger.cpp index f3e2bff6f99b..c4cd697ab797 100644 --- a/Source/JavaScriptCore/debugger/Debugger.cpp +++ b/Source/JavaScriptCore/debugger/Debugger.cpp @@ -28,7 +28,6 @@ #include "HeapIterationScope.h" #include "JSCInlines.h" #include "JSGenerator.h" -#include "JSModuleRecord.h" #include "MarkedSpaceInlines.h" #include "Microtask.h" #include "VMEntryScopeInlines.h" @@ -164,37 +163,33 @@ Debugger::~Debugger() globalObject->setDebugger(nullptr); } +void Debugger::notifySourceParsedForExistingCode(JSGlobalObject* globalObject) +{ + // Enumerate all CodeBlocks to find source providers for scripts that were + // already loaded before the debugger attached. Fires sourceParsed for each + // one, notifying observers (e.g., InspectorDebuggerAgent sends scriptParsed). + UncheckedKeyHashSet> sourceProviders; + { + JSLockHolder locker(m_vm); + m_vm.heap.forEachCodeBlock([&](CodeBlock* codeBlock) { + if (codeBlock->globalObject() == globalObject) { + if (auto* provider = codeBlock->ownerExecutable()->source().provider()) + sourceProviders.add(provider); + } + }); + } + for (auto& sourceProvider : sourceProviders) + sourceParsed(globalObject, sourceProvider.get(), -1, nullString()); +} + void Debugger::attach(JSGlobalObject* globalObject) { if (globalObject->debugger() == this) { #if USE(BUN_JSC_ADDITIONS) - // Debugger was pre-attached (e.g., SIGUSR1 runtime inspector activation) - // before any observers were registered. sourceParsed() was a no-op during - // the initial attach() because canDispatchFunctionToObservers() returned - // false. Replay sourceParsed events now that observers have been added - // (via Debugger.enable → addObserver). - if (canDispatchFunctionToObservers()) { - UncheckedKeyHashSet> sourceProviders; - { - JSLockHolder locker(m_vm); - HeapIterationScope iterationScope(m_vm.heap); - m_vm.heap.objectSpace().forEachLiveCell(iterationScope, [&] (HeapCell* heapCell, HeapCell::Kind kind) { - if (isJSCellKind(kind)) { - auto* cell = static_cast(heapCell); - if (auto* function = jsDynamicCast(cell)) { - if (function->scope()->globalObject() == globalObject && function->executable()->isFunctionExecutable() && !function->isHostOrBuiltinFunction()) - sourceProviders.add(jsCast(function->executable())->source().provider()); - } else if (auto* moduleRecord = jsDynamicCast(cell)) { - if (auto* provider = moduleRecord->sourceCode().provider()) - sourceProviders.add(provider); - } - } - return IterationStatus::Continue; - }); - } - for (auto& sourceProvider : sourceProviders) - sourceParsed(globalObject, sourceProvider.get(), -1, nullString()); - } + // Debugger was pre-attached before observers were registered. + // Replay sourceParsed events now that observers exist. + if (canDispatchFunctionToObservers()) + notifySourceParsedForExistingCode(globalObject); #endif return; } @@ -205,23 +200,7 @@ void Debugger::attach(JSGlobalObject* globalObject) m_vm.setShouldBuildPCToCodeOriginMapping(); // Call `sourceParsed` after iterating because it will execute JavaScript in Web Inspector. - UncheckedKeyHashSet> sourceProviders; - { - JSLockHolder locker(m_vm); - HeapIterationScope iterationScope(m_vm.heap); - m_vm.heap.objectSpace().forEachLiveCell(iterationScope, [&] (HeapCell* heapCell, HeapCell::Kind kind) { - if (isJSCellKind(kind)) { - auto* cell = static_cast(heapCell); - if (auto* function = jsDynamicCast(cell)) { - if (function->scope()->globalObject() == globalObject && function->executable()->isFunctionExecutable() && !function->isHostOrBuiltinFunction()) - sourceProviders.add(jsCast(function->executable())->source().provider()); - } - } - return IterationStatus::Continue; - }); - } - for (auto& sourceProvider : sourceProviders) - sourceParsed(globalObject, sourceProvider.get(), -1, nullString()); + notifySourceParsedForExistingCode(globalObject); } void Debugger::detach(JSGlobalObject* globalObject, ReasonForDetach reason) diff --git a/Source/JavaScriptCore/debugger/Debugger.h b/Source/JavaScriptCore/debugger/Debugger.h index 3fb21094effc..8f44c5366b53 100644 --- a/Source/JavaScriptCore/debugger/Debugger.h +++ b/Source/JavaScriptCore/debugger/Debugger.h @@ -142,6 +142,8 @@ class Debugger : public DoublyLinkedListNode { JS_EXPORT_PRIVATE virtual void sourceParsed(JSGlobalObject*, SourceProvider*, int errorLineNumber, const WTF::String& errorMessage); + void notifySourceParsedForExistingCode(JSGlobalObject*); + void exception(JSGlobalObject*, CallFrame*, JSValue exceptionValue, bool hasCatchHandler); void atStatement(CallFrame*); void atExpression(CallFrame*); From f87ee89f3a5afba385f599b708de9baf3df71d91 Mon Sep 17 00:00:00 2001 From: Alistair Smith Date: Thu, 12 Feb 2026 19:11:18 -0800 Subject: [PATCH 3/8] debugger: skip heap walk in notifySourceParsedForExistingCode when no observers --- Source/JavaScriptCore/debugger/Debugger.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/JavaScriptCore/debugger/Debugger.cpp b/Source/JavaScriptCore/debugger/Debugger.cpp index c4cd697ab797..f3b8fecb1896 100644 --- a/Source/JavaScriptCore/debugger/Debugger.cpp +++ b/Source/JavaScriptCore/debugger/Debugger.cpp @@ -165,6 +165,11 @@ Debugger::~Debugger() void Debugger::notifySourceParsedForExistingCode(JSGlobalObject* globalObject) { + // Avoid heap walk when no observers are registered — sourceParsed will + // early-return for every entry anyway. + if (!canDispatchFunctionToObservers()) + return; + // Enumerate all CodeBlocks to find source providers for scripts that were // already loaded before the debugger attached. Fires sourceParsed for each // one, notifying observers (e.g., InspectorDebuggerAgent sends scriptParsed). From 37ae99ef3231dff3babb224ff075495733c23fb3 Mon Sep 17 00:00:00 2001 From: Alistair Smith Date: Fri, 13 Feb 2026 13:29:39 -0800 Subject: [PATCH 4/8] fix: stop SignalSender when polling traps are enabled to prevent crash on jettisoned code --- Source/JavaScriptCore/runtime/VMTraps.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Source/JavaScriptCore/runtime/VMTraps.cpp b/Source/JavaScriptCore/runtime/VMTraps.cpp index 394934bd5e49..4236cd7d2c20 100644 --- a/Source/JavaScriptCore/runtime/VMTraps.cpp +++ b/Source/JavaScriptCore/runtime/VMTraps.cpp @@ -302,6 +302,15 @@ class VMTraps::SignalSender final : public ThreadSafeRefCounted Date: Fri, 13 Feb 2026 14:02:06 -0800 Subject: [PATCH 5/8] guard isPauseAtNextOpportunitySet with USE(BUN_JSC_ADDITIONS) --- Source/JavaScriptCore/debugger/Debugger.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/JavaScriptCore/debugger/Debugger.h b/Source/JavaScriptCore/debugger/Debugger.h index 8f44c5366b53..0ad627c4097f 100644 --- a/Source/JavaScriptCore/debugger/Debugger.h +++ b/Source/JavaScriptCore/debugger/Debugger.h @@ -163,7 +163,9 @@ class Debugger : public DoublyLinkedListNode { void registerCodeBlock(CodeBlock*); void forEachRegisteredCodeBlock(NOESCAPE const Function&); +#if USE(BUN_JSC_ADDITIONS) bool isPauseAtNextOpportunitySet() const { return m_pauseAtNextOpportunity; } +#endif void didCreateNativeExecutable(NativeExecutable&); void willCallNativeExecutable(CallFrame*); From 40d3a6318c6c57233a53ad92ca6a7a966c9c90fe Mon Sep 17 00:00:00 2001 From: Alistair Smith Date: Fri, 13 Feb 2026 15:15:49 -0800 Subject: [PATCH 6/8] bisect: revert VMTraps.cpp to PR 159 version (keep Debugger changes) --- Source/JavaScriptCore/runtime/VMTraps.cpp | 41 ++++++----------------- Source/cmake/WebKitCompilerFlags.cmake | 8 ++--- 2 files changed, 13 insertions(+), 36 deletions(-) diff --git a/Source/JavaScriptCore/runtime/VMTraps.cpp b/Source/JavaScriptCore/runtime/VMTraps.cpp index 4236cd7d2c20..264e7f7ebd85 100644 --- a/Source/JavaScriptCore/runtime/VMTraps.cpp +++ b/Source/JavaScriptCore/runtime/VMTraps.cpp @@ -29,7 +29,6 @@ #include "CallFrameInlines.h" #include "CodeBlock.h" #include "CodeBlockSet.h" -#include "Debugger.h" #include "DFGCommonData.h" #include "ExceptionHelpers.h" #include "HeapInlines.h" @@ -47,11 +46,6 @@ #include #include -#if USE(BUN_JSC_ADDITIONS) -extern "C" __attribute__((weak)) void Bun__drainQueuedCDPMessages(JSC::VM&); -extern "C" __attribute__((weak)) bool Bun__shouldBreakAfterMessageDrain(JSC::VM&); -#endif - namespace JSC { #if ENABLE(SIGNAL_BASED_VM_TRAPS) @@ -302,15 +296,6 @@ class VMTraps::SignalSender final : public ThreadSafeRefCountedlexicalGlobalObject(vm)) { - if (auto* debugger = globalObject->debugger()) - debugger->breakProgram(); - } + // If a debugger is attached and wants to pause, call breakProgram() + // to immediately enter the pause loop. This is needed because baseline + // JIT doesn't have debug hooks that call pauseIfNeeded(). + // breakProgram() is safe here because we're on the JS thread and + // NOT inside a StopTheWorld callback (NeedDebuggerBreak is processed + // after NeedStopTheWorld in the same handleTraps call, but the STW + // has already resumed by then). + if (vm.topCallFrame) { + if (auto* globalObject = vm.topCallFrame->lexicalGlobalObject(vm)) { + if (auto* debugger = globalObject->debugger()) + debugger->breakProgram(); } } -#endif didHandleTrap = true; break; diff --git a/Source/cmake/WebKitCompilerFlags.cmake b/Source/cmake/WebKitCompilerFlags.cmake index 0d071d3bd30f..c94cb1924ac5 100644 --- a/Source/cmake/WebKitCompilerFlags.cmake +++ b/Source/cmake/WebKitCompilerFlags.cmake @@ -265,9 +265,7 @@ if (COMPILER_IS_GCC_OR_CLANG) -Wl,-U,_WTFTimer__secondsUntilTimer -Wl,-U,_WTFTimer__cancel -Wl,-U,_Bun__errorInstance__finalize - -Wl,-U,_Bun__reportUnhandledError - -Wl,-U,_Bun__drainQueuedCDPMessages - -Wl,-U,_Bun__shouldBreakAfterMessageDrain) + -Wl,-U,_Bun__reportUnhandledError) else() WEBKIT_PREPEND_GLOBAL_COMPILER_FLAGS(-Wl,-u,_WTFTimer__create -Wl,-u,_WTFTimer__update @@ -276,9 +274,7 @@ if (COMPILER_IS_GCC_OR_CLANG) -Wl,-u,_WTFTimer__secondsUntilTimer -Wl,-u,_WTFTimer__cancel -Wl,-u,_Bun__errorInstance__finalize - -Wl,-u,_Bun__reportUnhandledError - -Wl,-u,_Bun__drainQueuedCDPMessages - -Wl,-u,_Bun__shouldBreakAfterMessageDrain) + -Wl,-u,_Bun__reportUnhandledError) endif() endif () From 608dcd0c4d5c49e70de13f6fcacbd561e2f05a1e Mon Sep 17 00:00:00 2001 From: Alistair Smith Date: Fri, 13 Feb 2026 19:08:23 -0800 Subject: [PATCH 7/8] restore NeedDebuggerBreak CDP drain handler and linker flags --- Source/JavaScriptCore/runtime/VMTraps.cpp | 32 +++++++++++++++-------- Source/cmake/WebKitCompilerFlags.cmake | 8 ++++-- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/Source/JavaScriptCore/runtime/VMTraps.cpp b/Source/JavaScriptCore/runtime/VMTraps.cpp index 264e7f7ebd85..394934bd5e49 100644 --- a/Source/JavaScriptCore/runtime/VMTraps.cpp +++ b/Source/JavaScriptCore/runtime/VMTraps.cpp @@ -29,6 +29,7 @@ #include "CallFrameInlines.h" #include "CodeBlock.h" #include "CodeBlockSet.h" +#include "Debugger.h" #include "DFGCommonData.h" #include "ExceptionHelpers.h" #include "HeapInlines.h" @@ -46,6 +47,11 @@ #include #include +#if USE(BUN_JSC_ADDITIONS) +extern "C" __attribute__((weak)) void Bun__drainQueuedCDPMessages(JSC::VM&); +extern "C" __attribute__((weak)) bool Bun__shouldBreakAfterMessageDrain(JSC::VM&); +#endif + namespace JSC { #if ENABLE(SIGNAL_BASED_VM_TRAPS) @@ -479,19 +485,23 @@ bool VMTraps::handleTraps(VMTraps::BitField mask) switch (event) { case NeedDebuggerBreak: invalidateCodeBlocksOnStack(vm.topCallFrame); - // If a debugger is attached and wants to pause, call breakProgram() - // to immediately enter the pause loop. This is needed because baseline - // JIT doesn't have debug hooks that call pauseIfNeeded(). - // breakProgram() is safe here because we're on the JS thread and - // NOT inside a StopTheWorld callback (NeedDebuggerBreak is processed - // after NeedStopTheWorld in the same handleTraps call, but the STW - // has already resumed by then). - if (vm.topCallFrame) { - if (auto* globalObject = vm.topCallFrame->lexicalGlobalObject(vm)) { - if (auto* debugger = globalObject->debugger()) - debugger->breakProgram(); +#if USE(BUN_JSC_ADDITIONS) + // Drain queued CDP messages. If a command like Debugger.pause + // is dispatched, it sets m_javaScriptPauseScheduled on the agent. + if (Bun__drainQueuedCDPMessages) + Bun__drainQueuedCDPMessages(vm); + // Only enter breakProgram() if a pause was actually requested + // (bootstrap, Debugger.pause command, breakpoint). For plain + // message delivery, the drain above is sufficient. + if (!Bun__shouldBreakAfterMessageDrain || Bun__shouldBreakAfterMessageDrain(vm)) { + if (vm.topCallFrame) { + if (auto* globalObject = vm.topCallFrame->lexicalGlobalObject(vm)) { + if (auto* debugger = globalObject->debugger()) + debugger->breakProgram(); + } } } +#endif didHandleTrap = true; break; diff --git a/Source/cmake/WebKitCompilerFlags.cmake b/Source/cmake/WebKitCompilerFlags.cmake index c94cb1924ac5..0d071d3bd30f 100644 --- a/Source/cmake/WebKitCompilerFlags.cmake +++ b/Source/cmake/WebKitCompilerFlags.cmake @@ -265,7 +265,9 @@ if (COMPILER_IS_GCC_OR_CLANG) -Wl,-U,_WTFTimer__secondsUntilTimer -Wl,-U,_WTFTimer__cancel -Wl,-U,_Bun__errorInstance__finalize - -Wl,-U,_Bun__reportUnhandledError) + -Wl,-U,_Bun__reportUnhandledError + -Wl,-U,_Bun__drainQueuedCDPMessages + -Wl,-U,_Bun__shouldBreakAfterMessageDrain) else() WEBKIT_PREPEND_GLOBAL_COMPILER_FLAGS(-Wl,-u,_WTFTimer__create -Wl,-u,_WTFTimer__update @@ -274,7 +276,9 @@ if (COMPILER_IS_GCC_OR_CLANG) -Wl,-u,_WTFTimer__secondsUntilTimer -Wl,-u,_WTFTimer__cancel -Wl,-u,_Bun__errorInstance__finalize - -Wl,-u,_Bun__reportUnhandledError) + -Wl,-u,_Bun__reportUnhandledError + -Wl,-u,_Bun__drainQueuedCDPMessages + -Wl,-u,_Bun__shouldBreakAfterMessageDrain) endif() endif () From 802b48b3666b5ece5c78aee5c18137c78fe24ecf Mon Sep 17 00:00:00 2001 From: Alistair Smith Date: Mon, 16 Feb 2026 18:36:59 -0800 Subject: [PATCH 8/8] fix: only tear down agents on last frontend disconnect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, disconnectFrontend unconditionally called willDestroyFrontendAndBackend which detached the debugger and disabled all agents — even when other frontends were still connected. This caused CDP responses to be lost on client reconnection. Move the agent teardown after the hasFrontends() check so it only runs when the last frontend disconnects. --- .../inspector/JSGlobalObjectInspectorController.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp b/Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp index 803c4e3acbbc..1ce34b3f736a 100644 --- a/Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp +++ b/Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp @@ -149,9 +149,6 @@ void JSGlobalObjectInspectorController::connectFrontend(FrontendChannel& fronten void JSGlobalObjectInspectorController::disconnectFrontend(FrontendChannel& frontendChannel) { - // FIXME: change this to notify agents which frontend has disconnected (by id). - m_agents.willDestroyFrontendAndBackend(DisconnectReason::InspectorDestroyed); - m_frontendRouter->disconnectFrontend(frontendChannel); m_isAutomaticInspection = false; @@ -161,6 +158,8 @@ void JSGlobalObjectInspectorController::disconnectFrontend(FrontendChannel& fron if (!disconnectedLastFrontend) return; + m_agents.willDestroyFrontendAndBackend(DisconnectReason::InspectorDestroyed); + #if ENABLE(INSPECTOR_ALTERNATE_DISPATCHERS) if (m_augmentingClient) m_augmentingClient->inspectorDisconnected();