How the Background Script Manages State When the DevTools Panel Closes in WebSocket DevTools

The background script detects panel closure via the onDisconnect event, immediately notifies the content script to reset proxy state, removes the dead port reference from its internal Map, and stops the keep-alive timer when no panels remain active, while preserving captured WebSocket data in memory for future sessions.

The WebSocket DevTools extension (law-chain-hot/websocket-devtools) uses a service-worker-based background script as its central coordination hub. When developers close the Chrome DevTools panel, the background script must perform precise cleanup to prevent memory leaks and ensure content scripts stop forwarding WebSocket events. This article examines the exact mechanisms implemented in src/background/background.js for state persistence and resource cleanup.

Central State Storage in the Background Script

The background script maintains several critical data structures in global scope to coordinate between content scripts and DevTools panels:

  • devtoolsPorts: A Map object tracking active port connections (port → tabId)
  • websocketData: An object containing the connections array and isMonitoring flag
  • keepAliveTimer: A periodic interval timer preventing service worker termination
  • tabUrls: Tracks tab URLs to distinguish navigation events from query-string changes

The DevtoolsPorts Map

When a DevTools panel opens, it creates a long-lived port via chrome.runtime.connect. The background script stores this relationship:

// src/background/background.js
const devtoolsPorts = new Map(); // port → tabId

This mapping enables targeted messaging and lifecycle management for each panel instance.

The Panel Closure Detection and Cleanup Sequence

When a user closes the DevTools panel, the port connection fires an onDisconnect event. The background script executes a three-phase cleanup process defined in the port's disconnect listener at lines 52–91 of src/background/background.js.

Phase 1: Notifying the Content Script

First, the script sends a reset command to the specific tab associated with the closed panel:

port.onDisconnect.addListener(() => {
  if (tabId) {
    chrome.tabs.sendMessage(tabId, { type: "reset-proxy-state" })
      .catch(() => {}); // Ignore errors if tab closed simultaneously
  }
  // ...
});

This reset-proxy-state message instructs the content script (src/content/content.js) to cease intercepting WebSocket traffic and clear any UI-related state.

Phase 2: Removing the Port Reference

Immediately after messaging the tab, the script purges the dead connection from the registry:

devtoolsPorts.delete(port);

This deletion prevents subsequent postMessage calls to a disconnected port, which would throw runtime errors.

Phase 3: Resource Conservation

If no DevTools panels remain open, the background script stops its periodic keep-alive heartbeat:

if (devtoolsPorts.size === 0) stopKeepAlive();

The stopKeepAlive() function clears the keepAliveTimer interval, allowing the service worker to enter idle state and conserve system resources.

Data Persistence Across Panel Sessions

Unlike the ephemeral port connections, the captured WebSocket data persists in the websocketData variable. When a new panel opens on the same tab, the background script sends this existing log via the existing-data message during the initialization handshake. This design allows developers to close and reopen DevTools without losing captured network activity.

Extension Shutdown Handling

For complete extension termination, the background script registers a chrome.runtime.onSuspend listener. This handler clears any remaining intervals and forcibly disconnects surviving ports, ensuring graceful cleanup when Chrome terminates the service worker.

Summary

  • The background script tracks active DevTools panels using a Map called devtoolsPorts that associates ports with tab IDs.
  • When a panel closes, the onDisconnect handler triggers a cleanup sequence: sending reset-proxy-state to the content script, deleting the port reference, and conditionally stopping the keep-alive timer.
  • Captured WebSocket data remains in memory via the websocketData object, allowing new panels to access historical connection logs.
  • The script implements defensive programming with .catch() handlers to manage race conditions where tabs close simultaneously with panels.

Frequently Asked Questions

Does closing the DevTools panel delete the captured WebSocket history?

No. The background script preserves the websocketData object in memory. Only the port reference and tab association are removed. When you reopen DevTools on the same tab, the background script sends the existing connection log via the existing-data message, restoring your session history.

How does the background script prevent service worker termination while panels are active?

The script maintains a keepAliveTimer that periodically pings all connected ports and their associated tabs. The timer only runs while devtoolsPorts.size > 0. When the last panel closes, stopKeepAlive() clears this interval, allowing the service worker to sleep.

What happens if the content script fails to receive the reset message?

The disconnect handler wraps the chrome.tabs.sendMessage call in a .catch() block that silently ignores errors. If the tab closed simultaneously with the DevTools panel, the promise rejection is handled gracefully without crashing the background script.

Where is the port-to-tab mapping initialized?

The mapping occurs in src/background/background.js within the chrome.runtime.onConnect listener. When the DevTools panel sends its initialization message containing the tabId, the background script stores the relationship as devtoolsPorts.set(port, tabId).

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 →