Skip to content

feat(inspector): attach Chrome DevTools to Web Worker isolates#386

Open
edusperoni wants to merge 1 commit into
mainfrom
feat/worker-debug
Open

feat(inspector): attach Chrome DevTools to Web Worker isolates#386
edusperoni wants to merge 1 commit into
mainfrom
feat/worker-debug

Conversation

@edusperoni

@edusperoni edusperoni commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Workers were invisible to the debugger: the inspector only served the main isolate and even dropped worker console output on the floor. DevTools discovers extra isolates through the Target domain with flat-session multiplexing, so implement that surface in the runtime:

  • Add WorkerInspectorClient: a per-worker V8Inspector + session that lives entirely on the worker's thread. Incoming CDP messages queue through a dedicated CFRunLoopSource on the worker's runloop; while paused at a breakpoint a nested loop on the worker thread pumps the same queue without re-entering the runloop, so postMessage deliveries stay queued during a pause (matching Chrome's semantics).
  • Turn JsV8InspectorClient into the root session + router: handle Target.setAutoAttach natively (announce workers via Target.attachedToTarget, detach on death), route messages carrying a top-level sessionId to the worker's thread directly from the socket thread, and make the frontend sender thread-safe. Routing off the socket thread means a worker stays debuggable while the main isolate is paused, and vice versa.
  • Debugger.pause for a busy worker uses RequestInterrupt keyed by workerId, so a late-firing interrupt after worker death is a no-op. This also fixes the main-session fast path, which used to interrupt the main isolate for a pause aimed at any session.
  • Serve Network.loadNetworkResource and IO.read/IO.close on the socket thread for any session (sessionId echoed), so worker source maps load too.
  • Route worker console.* to the worker's own inspector and deliver through V8ConsoleMessageStorage::addMessage instead of the live-only runtime agent path: messages logged before the frontend attaches (or before Runtime.enable reaches a session) are stored and replayed as console history. Applies to the main isolate as well.
  • Name execution contexts ("main" / worker script url) so the DevTools console context selector rows are labeled and selectable.
  • Worker lifecycle: inspector is created on the worker thread before RunModule (debug builds only), terminate() kicks a paused worker out of its nested pause loop, teardown unregisters the target before the isolate is disposed, and frontend reconnects reset worker sessions.

waitForDebuggerOnStart (pause new workers before their first line) is left as future work; workers currently announce waitingForDebugger: false.

Summary by CodeRabbit

  • New Features
    • Worker threads are now visible and debuggable in Chrome DevTools as child targets, enabling inspection of worker isolate execution, state, and console output
    • DevTools seamlessly discovers and attaches to worker targets, providing unified debugging across main and worker threads

Workers were invisible to the debugger: the inspector only served the
main isolate and even dropped worker console output on the floor.
DevTools discovers extra isolates through the Target domain with
flat-session multiplexing, so implement that surface in the runtime:

- Add WorkerInspectorClient: a per-worker V8Inspector + session that
  lives entirely on the worker's thread. Incoming CDP messages queue
  through a dedicated CFRunLoopSource on the worker's runloop; while
  paused at a breakpoint a nested loop on the worker thread pumps the
  same queue without re-entering the runloop, so postMessage deliveries
  stay queued during a pause (matching Chrome's semantics).
- Turn JsV8InspectorClient into the root session + router: handle
  Target.setAutoAttach natively (announce workers via
  Target.attachedToTarget, detach on death), route messages carrying a
  top-level sessionId to the worker's thread directly from the socket
  thread, and make the frontend sender thread-safe. Routing off the
  socket thread means a worker stays debuggable while the main isolate
  is paused, and vice versa.
- Debugger.pause for a busy worker uses RequestInterrupt keyed by
  workerId, so a late-firing interrupt after worker death is a no-op.
  This also fixes the main-session fast path, which used to interrupt
  the main isolate for a pause aimed at any session.
- Serve Network.loadNetworkResource and IO.read/IO.close on the socket
  thread for any session (sessionId echoed), so worker source maps
  load too.
- Route worker console.* to the worker's own inspector and deliver
  through V8ConsoleMessageStorage::addMessage instead of the live-only
  runtime agent path: messages logged before the frontend attaches (or
  before Runtime.enable reaches a session) are stored and replayed as
  console history. Applies to the main isolate as well.
- Name execution contexts ("main" / worker script url) so the DevTools
  console context selector rows are labeled and selectable.
- Worker lifecycle: inspector is created on the worker thread before
  RunModule (debug builds only), terminate() kicks a paused worker out
  of its nested pause loop, teardown unregisters the target before the
  isolate is disposed, and frontend reconnects reset worker sessions.

waitForDebuggerOnStart (pause new workers before their first line) is
left as future work; workers currently announce waitingForDebugger:
false.
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8e9edac6-9a8d-41b1-8a21-5f885a4e1ec1

📥 Commits

Reviewing files that changed from the base of the PR and between 91ce499 and b0f3439.

📒 Files selected for processing (8)
  • NativeScript/inspector/JsV8InspectorClient.h
  • NativeScript/inspector/JsV8InspectorClient.mm
  • NativeScript/inspector/WorkerInspectorClient.h
  • NativeScript/inspector/WorkerInspectorClient.mm
  • NativeScript/runtime/DataWrapper.h
  • NativeScript/runtime/Worker.mm
  • NativeScript/runtime/WorkerWrapper.mm
  • v8ios.xcodeproj/project.pbxproj

📝 Walkthrough

Walkthrough

This PR extends NativeScript's V8 Inspector to support worker thread debugging. It adds session-aware message routing, worker-scoped resource delivery, and a new WorkerInspectorClient type that bridges worker isolates with Chrome DevTools, enabling workers to appear as child inspection targets.

Changes

Worker Inspector Support

Layer / File(s) Summary
Main Inspector Client API & Threading Foundation
NativeScript/inspector/JsV8InspectorClient.h
Introduces singleton accessor GetInstance(), public worker-target lifecycle APIs (RegisterWorkerTarget, UnregisterWorkerTarget, SchedulePauseInWorker), concurrency guards (std::mutex), and worker-target bookkeeping state.
Session Routing & Worker Target Discovery
NativeScript/inspector/JsV8InspectorClient.mm
Fast-parses incoming DevTools messages to extract sessionId and routes session-bearing messages to worker targets via RouteToWorker. Implements Target domain semantics (Target.setAutoAttach, etc.) and cleans worker state on frontend disconnect.
Session-Aware Network Resource Handling
NativeScript/inspector/JsV8InspectorClient.mm
Updates HandleLoadNetworkResource, HandleIORead, HandleIOClose to accept and preserve sessionId, manages HTTP resource streams under resourceStreamsMutex_, and routes responses back to the correct session.
Frontend Messaging & Worker Console Integration
NativeScript/inspector/JsV8InspectorClient.mm
Centralizes outbound messaging through thread-safe SendToFrontend helper. Forwards worker console logs to corresponding WorkerInspectorClient and stores main-isolate console messages for replay.
WorkerInspectorClient Type & Initialization
NativeScript/inspector/WorkerInspectorClient.h, NativeScript/inspector/WorkerInspectorClient.mm
Defines new WorkerInspectorClient class: initializes V8 Inspector/session/context for a worker isolate, sets up CFRunLoop message draining with semaphore/mutex synchronization, and tracks worker identifiers for session routing.
Worker Protocol Dispatch & Pause Management
NativeScript/inspector/WorkerInspectorClient.mm
Implements message queue dispatch with session reset coordination, pause-loop message pumping, isolate interrupt-based pause scheduling, protocol response wrapping with sessionId, and worker console message storage.
Worker Runtime Lifecycle Integration
NativeScript/runtime/DataWrapper.h, NativeScript/runtime/Worker.mm, NativeScript/runtime/WorkerWrapper.mm
Adds public CreateInspector() / DestroyInspector() APIs to WorkerWrapper. Inspector is created at worker startup before script execution, destroyed during cleanup, and notifies inspector of termination to unblock nested pause loops under inspectorMutex_.
Xcode Build System Integration
v8ios.xcodeproj/project.pbxproj
Registers WorkerInspectorClient.h and .mm as headers and sources in the build, adds them to the inspector group, and includes them in the appropriate build phases.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • NativeScript/ios#378: Triggers V8 interrupt on Debugger.pause; directly coordinates with this PR's pause interrupt handling in WorkerInspectorClient.
  • NativeScript/ios#385: Extends source-map and DevTools resource implementations; this PR builds on the same stream/resource logic with sessionId-aware routing.

Suggested reviewers

  • NathanWalker

Poem

🐰 The workers leap through DevTools' gaze,
Each thread now speaks in Chrome's own phrase,
With messages routed and sessions blessed,
The inspector awakens—paused threads find rest!
Sessions wrapped, consoles dance free—
Debugging nirvana for threading spree! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: enabling Chrome DevTools attachment to Web Worker isolates through new inspector infrastructure.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant