Skip to content

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

Open
edusperoni wants to merge 2 commits into
mainfrom
feat/worker-debug
Open

feat(inspector): attach Chrome DevTools to Web Worker isolates#386
edusperoni wants to merge 2 commits 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 visible and debuggable in Chrome DevTools as child targets, with session-aware routing so messages, network/resource fetches, and console logs map to the correct worker/front-end session.
    • Workers auto-attach/discover for unified debugging and support pause requests from the frontend.
  • Bug Fixes

    • Improved thread-safety and reliable pause/resume/teardown across main and worker runtimes.
  • Chores

    • Build configuration updated to include worker inspector sources.

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: 8759c6d7-268c-4e74-bc67-36a99eea5190

📥 Commits

Reviewing files that changed from the base of the PR and between b0f3439 and 026e407.

📒 Files selected for processing (1)
  • NativeScript/inspector/JsV8InspectorClient.mm
🚧 Files skipped from review as they are similar to previous changes (1)
  • NativeScript/inspector/JsV8InspectorClient.mm

📝 Walkthrough

Walkthrough

Adds worker-target DevTools support: main inspector routes session-scoped messages, a WorkerInspectorClient owns per-worker V8 inspector sessions and pause loops, network/IO handlers become session-aware, console logs are forwarded per-worker, and worker inspectors are created/destroyed during worker lifecycle.

Changes

Worker Inspector Support

Layer / File(s) Summary
Main Inspector Client API & Threading Foundation
NativeScript/inspector/JsV8InspectorClient.h, NativeScript/inspector/JsV8InspectorClient.mm
Introduces singleton GetInstance(), SendToFrontend, worker-target registration/pause APIs, mutex-protected sender/resource/worker maps, and instance initialization.
Session Routing & Worker Target Discovery
NativeScript/inspector/JsV8InspectorClient.mm
Fast-parses frontend messages to extract sessionId/method/id, routes session-bearing messages to worker targets via RouteToWorker, handles Target domain auto-attach/discovery semantics, and resets worker state on disconnect.
Session-Aware Network Resource Handling
NativeScript/inspector/JsV8InspectorClient.mm
HandleLoadNetworkResource, HandleIORead, HandleIOClose accept sessionId, use resourceStreamsMutex_ for stream lifecycle, and send session-scoped replies/errors.
Frontend Messaging & Worker Console Integration
NativeScript/inspector/JsV8InspectorClient.mm
Adds SendToFrontend (sender mutex + source-map rewrite), forwards worker isolate console logs to WorkerInspectorClient, and stores main-isolate console messages for replay/history.
WorkerInspectorClient Type & Initialization
NativeScript/inspector/WorkerInspectorClient.h, NativeScript/inspector/WorkerInspectorClient.mm
New WorkerInspectorClient: per-worker IDs/session, CFRunLoop source and semaphore for draining incoming CDP messages, V8Inspector/session/context initialization, and thread-safe incoming queue.
Worker Protocol Dispatch & Pause Management
NativeScript/inspector/WorkerInspectorClient.mm
Dispatch pipeline with session reset coordination, nested pause-loop pumping, isolate-interrupt pause scheduling, wrapping outgoing messages with sessionId, and worker console message storage.
Worker Runtime Lifecycle Integration
NativeScript/runtime/DataWrapper.h, NativeScript/runtime/Worker.mm, NativeScript/runtime/WorkerWrapper.mm
Adds CreateInspector()/DestroyInspector() to WorkerWrapper, creates the inspector before running the worker module, notifies inspector on termination to break pause loops, and destroys inspector during cleanup under inspectorMutex_.
Xcode Build System Integration
v8ios.xcodeproj/project.pbxproj
Adds WorkerInspectorClient.h and WorkerInspectorClient.mm to project file references, build phases, and the inspector group.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • NativeScript/ios#385: Modifies DevTools resource IO handling and stream bookkeeping; closely related to the session-aware resource changes here.
  • NativeScript/ios#378: Adds V8 interrupt behavior for Debugger.pause; related to the pause-scheduling/interrupt logic in worker handling.

Suggested reviewers

  • NathanWalker

Poem

🐰 I hop through threads with tiny paws,

I wrap your sessions, mind the pause,
Workers whisper to DevTools bright,
Messages routed, consoles light,
Debugging carrots—what a sight!

🚥 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 directly and accurately describes the main change: adding Chrome DevTools support for Web Worker isolates as separate targets with per-worker inspector sessions.
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.


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