Finding Detached DOM Nodes with Chromium Heap Snapshots

This article provides a comprehensive guide on how web developers can identify and resolve memory leaks caused by detached DOM nodes using the Chromium web browser's Memory panel. It covers the essential techniques for capturing accurate heap snapshots, utilizing the three-snapshot method to isolate leaks, filtering snapshot data specifically for detached elements, and analyzing the retainers tree to pinpoint the exact JavaScript references preventing garbage collection.

Taking a Baseline Heap Snapshot

A detached DOM node is an element that has been removed from the Document Object Model (DOM) tree, meaning it is no longer visible on the page, yet remains in memory because a JavaScript object still references it. To find these nodes, you must first capture the current state of your application's memory.

  1. Open the Chromium Developer Tools by pressing F12 or Ctrl+Shift+I (Cmd+Option+I on Mac).
  2. Navigate to the Memory panel.
  3. Under the "Select profiling type" section, choose Heap snapshot.
  4. Click the Take snapshot button at the bottom.

Chromium automatically runs garbage collection before taking the snapshot to ensure that only active, reachable objects are recorded. Once complete, the snapshot will appear in the left-hand sidebar and automatically open in the "Summary" view.

Filtering for Detached DOM Nodes

Once you have captured a heap snapshot, you can immediately search for detached elements that are lingering in memory.

  1. Ensure the view dropdown at the top of the snapshot pane is set to Summary.
  2. Locate the Class filter input box at the top of the panel.
  3. Type Detached into the filter box.

The panel will filter the memory constructors to show only detached elements, such as Detached HTMLDivElement, Detached HTMLButtonElement, or Detached Text. Expanding any of these constructors will reveal the individual nodes currently held in memory.

The Three-Snapshot Technique

Sometimes, a single snapshot contains too much noise to determine if an action is actively causing a leak. The three-snapshot technique is a precise method to isolate detached DOM nodes created by specific user interactions.

  1. Load your web page and take Snapshot 1 to serve as your baseline.
  2. Perform a specific action on your page that creates and then removes DOM elements (for example, opening and closing a modal window).
  3. Take Snapshot 2.
  4. Perform the exact same action a second time (open and close the modal again).
  5. Take Snapshot 3.

To analyze the results, click on Snapshot 3. Change the perspective dropdown at the top (which defaults to "All objects") to Objects allocated between Snapshot 1 and Snapshot 2.

Because you performed the action twice, any temporary memory from the first action should have been overwritten or garbage collected by the time the third snapshot was taken. The objects remaining in this filtered view—especially when combined with the Detached class filter—are almost certainly memory leaks triggered by that specific user interaction.

Analyzing the Retainers Tree

Finding a detached DOM node is only half the battle; you must also find the JavaScript code keeping it alive. You can do this using the Retainers pane located at the bottom half of the Memory panel.

  1. Click on an individual detached node under one of the expanded Detached constructors in the upper pane.
  2. Look at the Retainers tree at the bottom. This tree shows the path of references from the detached node back to the root object (usually the Window), which explains why the garbage collector cannot delete it.

As you inspect the nodes in the upper and lower panes, pay attention to the color-coding provided by Chromium:

  • Yellow Highlight: Nodes highlighted in yellow have a direct reference from your JavaScript code (e.g., an event listener, a global variable, or a closure). These are the root causes of the leak.
  • Red Highlight: Nodes highlighted in red do not have direct JavaScript references. They are only alive because they are part of a DOM tree anchored by a yellow node.

Focus your efforts on the yellow nodes. Expand the Retainers tree for a yellow node to see the exact variable name and the file line number holding the reference.

Resolving the Leak

Once the Retainers tree reveals the source of the reference, navigate to that location in your source code. The most common culprits for detached DOM nodes include:

  • Unremoved Event Listeners: Removing a DOM node without first calling removeEventListener if the listener is bound to an external scope.
  • Global Variables or Caches: Storing a reference to a DOM element in a global object or state manager and failing to set it to null when the element is removed from the page.
  • Closures: Asynchronous callbacks (like setTimeout or fetch) or closures that inadvertently capture a DOM element reference in their lexical scope.

After updating your code to sever these references when elements are destroyed, repeat the snapshot process to verify that the Detached constructors no longer appear.