DevTools Bridge Protocol
This code connects the DevTools backend agent to the frontend store through a Bridge backed by a transport wall. The bridge carries named events in both directions, while the backend agent and frontend store attach handlers for the events they understand.
It exists to keep renderer-specific work on the backend side and expose a stable stream of messages to the frontend. The same boundary also carries tree updates, selections, inspection requests, editing requests, capability notifications, and shutdown.
Sources: packages/react-devtools-shared/src/bridge.js:317-529, packages/react-devtools-shared/src/devtools/store.js:307-376, packages/react-devtools-shared/src/backend/agent.js:287-368, packages/react-devtools-shared/src/bridge.js:404-431
Core concepts
Bridge
A bridge is the typed event boundary that listens to a wall, dispatches incoming events, queues outgoing messages, and rejects use after shutdown.
Sources: packages/react-devtools-shared/src/bridge.js:317-529, packages/react-devtools-shared/src/bridge.js:327-342, packages/react-devtools-shared/src/bridge.js:452-458
Wall
A wall is the transport supplied to Bridge; its listen method installs the incoming-message handler and must return an unlisten function.
Sources: packages/react-devtools-shared/src/bridge.js:327-342
Agent
The backend agent is the backend-side event router: its constructor registers frontend request names against agent methods and then announces that the backend is initialized.
Sources: packages/react-devtools-shared/src/backend/agent.js:287-368
Store
The frontend store is the state owner for the DevTools tree and subscribes to bridge events such as operations, selectElement, and backendInitialized.
Sources: packages/react-devtools-shared/src/devtools/store.js:183-2585, packages/react-devtools-shared/src/devtools/store.js:307-376
Renderer interface
A renderer interface is the renderer-specific object stored by renderer ID so the agent can ask the matching renderer for host-instance IDs, display names, styles, or other renderer operations.
Sources: packages/react-devtools-shared/src/backend/agent.js:370-372, packages/react-devtools-shared/src/backend/agent.js:443-453, packages/react-devtools-shared/src/backend/agent.js:455-530, packages/react-devtools-shared/src/backend/agent.js:532-541
Tree operation
A tree operation is a numeric protocol instruction that describes a change to the DevTools element tree; the shown protocol defines add, remove, reorder, metadata, and related Suspense operation codes.
Sources: packages/react-devtools-shared/src/constants.js:20, packages/react-devtools-shared/src/constants.js:21, packages/react-devtools-shared/src/constants.js:22, packages/react-devtools-shared/src/constants.js:27
How messages cross the bridge
The source excerpts do not include the declarations of BackendEvents and FrontendEvents, so the reliable distinction here is direction: events registered by the backend Agent are frontend-to-backend requests, while events registered by the frontend Store are backend-to-frontend notifications.
| Direction | Message examples | Receiver behavior |
|---|---|---|
| Frontend → backend | inspectElement, overrideProps, overrideState | Agent registers each name with a method. |
| Frontend → backend | getBackendVersion, getBridgeProtocol, startProfiling | Agent exposes request handlers through the same bridge. |
| Backend → frontend | operations, selectElement, backendInitialized | Store registers listeners for tree and lifecycle updates. |
| Backend → frontend | unsupportedRendererVersion | Store registers a compatibility listener. |
The backend registrations are visible in Agent's constructor, including inspection, override, profiling, selection, and shutdown requests; the frontend registrations are visible in Store's constructor, including operations, selectElement, and backendInitialized.
Bridge.send first validates that the bridge is alive and that the event name is a non-empty string, then appends the event and payload to _messageQueue. The bridge schedules only one pending flush at a time, using the test scheduler when present and otherwise queueMicrotask, so messages created in the same turn can be sent together.
The batching comments describe a BATCH_DURATION window: a newly received message is queued, a zero-delay future flush allows same-tick messages to coalesce, and messages arriving after a recent flush wait for the already scheduled flush rather than creating another one.
Evidence
The sequence shows the explicit calls present in the constructors: the wall is listened to, both sides register bridge listeners, and the agent sends backendInitialized after setup.
Shutdown is also a protocol event. shutdown emits locally, queues an outgoing shutdown message, marks the bridge as shut down, removes listeners, unlistens from the wall, and synchronously flushes remaining outgoing messages.
Sources: packages/react-devtools-shared/src/backend/agent.js:287-368, packages/react-devtools-shared/src/devtools/store.js:307-376, packages/react-devtools-shared/src/bridge.js:366-402, packages/react-devtools-shared/src/bridge.js:317-529, packages/react-devtools-shared/src/bridge.js:327-342, packages/react-devtools-shared/src/bridge.js:404-431
How tree changes become store state
The protocol represents tree changes with numeric operation codes. The three operations central to the requested tree flow are these:
| Code | Meaning | Store consequence |
|---|---|---|
TREE_OPERATION_ADD | Add a tree element. | The element must become addressable by its ID and connected to the tree. |
TREE_OPERATION_REMOVE | Remove a tree element. | The element and its tree relationship must no longer be available. |
TREE_OPERATION_REORDER_CHILDREN | Change child order. | The parent’s child-ID ordering must change without changing the element IDs. |
The constants establish the numeric values 1, 2, and 3; the excerpts do not show the onBridgeOperations implementation, so they do not establish the exact positional encoding of each flat operations array.
What the store state makes explicit is the destination of decoding: _idToElement maps element IDs to mutable elements, _roots stores root IDs, _rootIDToRendererID associates roots with renderer IDs, and _ownersMap indexes ownership relationships.
The store also maintains _weightAcrossRoots, exposed as numElements, so tree navigation can treat the element tree as a weighted visible sequence. getElementAtIndex walks roots and then child arrays, using each child’s weight, or 1 when that child is collapsed, to locate the element at a visible index.
A decoded add or reorder therefore matters beyond the immediate map update: later navigation reads children, root membership, parent relationships, and weights. getIndexOfElementID walks from an element to its root and adds the weights of preceding siblings and roots.
Evidence
- operations-messagepackages/react-devtools-shared/src/devtools/store.js:307
- bridgepackages/react-devtools-shared/src/bridge.js:317
- bridgepackages/react-devtools-shared/src/devtools/store.js:307
- storepackages/react-devtools-shared/src/devtools/store.js:183
- storepackages/react-devtools-shared/src/devtools/store.js:307
- element-mappackages/react-devtools-shared/src/devtools/store.js:183
- element-mappackages/react-devtools-shared/src/devtools/store.js:704
- rootspackages/react-devtools-shared/src/devtools/store.js:183
- rootspackages/react-devtools-shared/src/devtools/store.js:612
The dataflow is grounded at the subscription boundary and at the store’s visible indexes; the exact flat-array parser is outside the shown excerpts.
Sources: packages/react-devtools-shared/src/constants.js:20, packages/react-devtools-shared/src/constants.js:21, packages/react-devtools-shared/src/constants.js:22, packages/react-devtools-shared/src/devtools/store.js:307-376, packages/react-devtools-shared/src/devtools/store.js:183-2585, packages/react-devtools-shared/src/devtools/store.js:526-528, packages/react-devtools-shared/src/devtools/store.js:612-697, packages/react-devtools-shared/src/devtools/store.js:794-857
How requests reach a renderer
The agent routes a frontend request by registering the message name with an agent method. inspectElement and overrideProps are explicitly registered in the constructor, but their method bodies are not included in the pack. Therefore, the excerpts prove the first routing hop, not the complete implementation of either request.
Renderer-specific routing is explicit for host-instance lookup. getIDForHostInstance iterates over _rendererInterfaces; in non-DOM environments it asks each renderer for an element or Suspense ID, while in the DOM it asks each renderer for its nearest mounted DOM node and chooses the best match.
Once a renderer ID is known, getInstanceAndStyle indexes _rendererInterfaces by that ID and calls renderer.getInstanceAndStyle(id), returning null with a warning when the ID is invalid. getComponentNameForHostInstance follows the same pattern: it first obtains the element-and-renderer match, then calls getDisplayNameForElementID on the selected renderer.
Renderer interfaces enter this routing table through registerRendererInterface, which stores the interface under its renderer ID, propagates the current trace-updates setting, and restores a tracked selection path when the renderer matches the persisted selection.
For selection, selectNode converts a host instance into an {id, rendererID} match and sends selectElement with the element ID, or null when no React match exists. The store listens for selectElement, so selection feedback crosses back into frontend state through the same bridge.
Sources: packages/react-devtools-shared/src/backend/agent.js:287-368, packages/react-devtools-shared/src/backend/agent.js:455-530, packages/react-devtools-shared/src/backend/agent.js:443-453, packages/react-devtools-shared/src/backend/agent.js:532-541, packages/react-devtools-shared/src/backend/agent.js:951-966, packages/react-devtools-shared/src/backend/agent.js:936-949, packages/react-devtools-shared/src/devtools/store.js:307-376
How it connects
The bridge and agent belong to the shared DevTools layer that sits between renderer integrations and frontend views. The backend initializes the agent with a BackendBridge, while the frontend initializes the store with a FrontendBridge; both are the same conceptual message boundary viewed from opposite sides.
Renderer attachment connects an injected renderer interface to the agent, then flushes pending initial operation codes to the frontend after the store and renderer interface are connected. This is the hand-off that makes the tree-operation stream useful to the store.
For the surrounding architecture, start with React DevTools Extension for the browser extension’s page-to-panel bridge, and DevTools Profiler & Commit Data for the profiler’s use of tree operations and commit data. The broader repository placement is described in Repository Map.
Sources: packages/react-devtools-shared/src/backend/agent.js:287-368, packages/react-devtools-shared/src/devtools/store.js:307-376, packages/react-devtools-shared/src/backend/index.js:27-35, packages/react-devtools-shared/src/constants.js:22
Key takeaways
Bridgequeues outgoing messages and schedules one flush, allowing same-turn messages to batch within the documentedBATCH_DURATIONbehavior.Agentregisters frontend request names such asinspectElementandoverrideProps;Storeregisters backend notifications such asoperationsandselectElement.- The shown tree constants define add, remove, and reorder operation codes, but the flat-array parser itself is not included in the excerpts.
- The store keeps mutable elements in
_idToElement, roots in_roots, and uses weights and child arrays for visible tree indexing. - Renderer routing uses a renderer ID to select an entry from
_rendererInterfaces; host-instance lookup and style lookup demonstrate that route directly.
Sources: packages/react-devtools-shared/src/bridge.js:317-529, packages/react-devtools-shared/src/bridge.js:366-402, packages/react-devtools-shared/src/backend/agent.js:287-368, packages/react-devtools-shared/src/devtools/store.js:307-376, packages/react-devtools-shared/src/constants.js:20, packages/react-devtools-shared/src/constants.js:21, packages/react-devtools-shared/src/constants.js:22, packages/react-devtools-shared/src/devtools/store.js:183-2585, packages/react-devtools-shared/src/devtools/store.js:612-697, packages/react-devtools-shared/src/devtools/store.js:794-857, packages/react-devtools-shared/src/backend/agent.js:443-453, packages/react-devtools-shared/src/backend/agent.js:455-530