How WebSocket DevTools Integrates as a Custom Panel in Chrome DevTools

The law-chain-hot/websocket-devtools extension registers a DevTools page in its manifest, loads a bootstrap script that conditionally calls chrome.devtools.panels.create(), and renders a React UI that establishes a runtime port to capture WebSocket traffic in real time.

The law-chain-hot/websocket-devtools repository demonstrates the standard pattern for integrating as a custom panel within the Chrome DevTools interface. By declaring a DevTools page in the extension manifest and leveraging the chrome.devtools.panels API, the extension injects a dedicated "WebSocket DevTools" tab that communicates with the inspected page through a background script bridge.

Manifest Declaration for DevTools Pages

Chrome extensions must declare a DevTools page entry point to trigger custom panel registration. In src/manifest.json, the extension specifies the HTML file that Chrome loads automatically whenever DevTools opens for a tab.

// src/manifest.json
"devtools_page": "src/devtools/devtools.html"

This declaration instructs Chrome to load src/devtools/devtools.html in an invisible DevTools context, which then executes the bootstrap logic required to create the custom panel.

Conditional Panel Registration

When DevTools opens, Chrome loads src/devtools/devtools.html, which executes src/devtools/devtools.js. This script verifies the extension's enabled state via storage before registering the panel, ensuring the UI only appears when the user has activated the extension.

// src/devtools/devtools.js
chrome.storage.local.get(["websocket-proxy-enabled"], result => {
  const enabled = result["websocket-proxy-enabled"] !== false; // default enabled
  if (enabled) {
    chrome.devtools.panels.create(
      "WebSocket DevTools",                // panel title
      "icons/icon.svg",                    // panel icon
      "src/devtools/panel.html",           // panel UI
      panel => { /* optional callbacks */ }
    );
  }
});

The chrome.devtools.panels.create() method accepts four parameters: the display title, an icon path, the HTML page to load within the panel, and an optional callback that executes once the panel is created. This call adds the "WebSocket DevTools" tab to the DevTools UI immediately upon invocation.

Bootstrapping the React Panel Interface

Once the panel is registered, Chrome loads src/devtools/panel.html into the new tab. This file mounts a React application that handles the complex UI rendering and WebSocket inspection logic.

<!-- src/devtools/panel.html -->
<script type="module" src="panel.jsx"></script>

The separation between devtools.js (bootstrap) and panel.jsx (UI) ensures that heavy React dependencies load only when the user actually opens the WebSocket DevTools tab, keeping DevTools startup performance optimal.

Runtime Port Communication Architecture

Custom DevTools panels cannot directly access the inspected page due to Chrome's security sandboxing. Instead, src/devtools/panel.jsx establishes a persistent connection to the background script, which mediates all communication with the content script injected into the target tab.

Panel-to-Background Handshake

Upon mounting, the React component opens a named runtime port and transmits the inspected tab's ID. This initialization sequence allows the background script to correlate incoming WebSocket events with the correct DevTools instance.

// src/devtools/panel.jsx
const port = chrome.runtime.connect({ name: "devtools" });
port.postMessage({ type: "init", tabId });
window._wsInspectorPort = port;

The chrome.runtime.connect() call creates a long-lived port object. By storing this port on the window object, the component ensures that event handlers throughout the React application can reference the same communication channel to send commands or receive updates.

Background Message Routing

The background script listens for these connections in src/background/background.js. When it receives the initialization message containing the tab ID, it registers the port association and prepares to forward WebSocket events captured by the content script.

// src/background/background.js
chrome.runtime.onConnect.addListener(port => {
  if (port.name === "devtools") {
    port.onMessage.addListener(msg => {
      if (msg.type === "init") {
        // store tabId, associate with this port, forward events later
      }
    });
  }
});

This architecture enables the panel to receive real-time WebSocket frame data, connection status changes, and control responses without polling or repeated message passing overhead.

Content Script Data Capture

While the panel manages the display layer, src/content/content.js performs the actual WebSocket interception. This script hooks the WebSocket constructor in the inspected page's main world, captures all message events, and transmits them to the background script. The background script then routes these events to the appropriate panel instance based on the tab ID established during the initialization handshake, completing the data flow from page to DevTools UI.

Summary

  • The extension declares "devtools_page": "src/devtools/devtools.html" in src/manifest.json to trigger loading whenever Chrome DevTools opens.
  • src/devtools/devtools.js conditionally registers the custom panel using chrome.devtools.panels.create() after verifying the websocket-proxy-enabled storage setting.
  • The panel UI renders via React in src/devtools/panel.jsx, which is loaded by src/devtools/panel.html only when the user activates the tab.
  • chrome.runtime.connect({ name: "devtools" }) establishes a persistent port between the panel and src/background/background.js.
  • The background script coordinates with src/content/content.js to intercept WebSocket traffic and forward it to the correct panel instance based on the inspected tab ID.

Frequently Asked Questions

What manifest key is required to add a custom panel to Chrome DevTools?

Chrome extensions must include the devtools_page key in manifest.json, pointing to an HTML file that loads the bootstrap script. In law-chain-hot/websocket-devtools, this is set to src/devtools/devtools.html, which executes the panel registration logic when DevTools opens for any tab.

How does the WebSocket DevTools panel communicate with the inspected page?

The panel does not communicate directly with the inspected page. Instead, it opens a runtime port to the background script using chrome.runtime.connect({name: "devtools"}). The background script then coordinates with a content script injected into the page, which intercepts WebSocket activity and forwards events back through the same port to update the panel UI in real time.

Can the WebSocket DevTools panel be disabled without uninstalling the extension?

Yes. The extension checks chrome.storage.local for a "websocket-proxy-enabled" key before calling chrome.devtools.panels.create(). If the value is explicitly set to false, the panel registration is skipped, effectively disabling the custom tab while keeping the extension installed and its content scripts inactive.

Which Chrome APIs are used to create and manage the custom DevTools panel?

The integration relies on three primary Chrome APIs: chrome.devtools.panels.create() to register the UI tab, chrome.runtime.connect() to establish persistent communication between the panel and background script, and chrome.storage.local to persist user preferences regarding panel visibility.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →