Skip to content

[🐛 Bug]: Potential browser-memory leak in precompiled find_element* atoms (document.$wdc_ cache growth), amplified by XPath-heavy lookups #17357

@mykola-mokhnach

Description

@mykola-mokhnach

Description

Reproducible Code

### Summary

While investigating Appium hybrid iOS failures after WebView refresh/restart, we found what appears to be a browser-side memory retention pattern in Selenium precompiled atoms used by remote-debugger integrations.

Specifically, `find_element_fragment` / `find_element` / `find_elements` atom flows serialize returned DOM nodes into element references by storing them in `document.$wdc_` via `tc(...)`/`Xf(...)`-style cache functions. The cache appears append-only unless a specific cached id is looked up again and found stale. In dynamic pages (frequent DOM recreation, refresh, WebContent restarts), this can retain detached nodes and grow memory over long sessions.

This is especially visible with frequent XPath lookups.

### Original report context

- Appium issue: [appium/appium#22178](https://github.com/appium/appium/issues/22178)
- Comment referencing XPath/find-element timeout behavior: [issue comment](https://github.com/appium/appium/issues/22178#issuecomment-4258153148)

### Observed behavior

- Repeated `findElement`/`findElements` on dynamic pages gradually increases browser process memory.
- In long-running sessions (especially real iOS hybrid apps), memory growth correlates with later lookup instability/timeouts.
- XPath usage amplifies the effect because multi-match snapshots can create many cached element ids quickly.

### Why this looks like an atom-level retention issue

From the precompiled atoms path:

1. `find_element*` returns DOM node(s).
2. Result serialization path wraps node(s) as WebDriver element refs.
3. Wrapper stores node references in `document.$wdc_` map (`:wdc:<id> -> node`).
4. Entries are not proactively pruned; stale cleanup is mostly opportunistic (on future dereference of that exact id).

This means detached nodes may remain strongly referenced by the cache and avoid GC.

### Expected behavior

- Element cache should not retain unbounded references to detached DOM nodes.
- Long sessions with repeated find operations should not show monotonic memory growth from atom cache retention.
- Cache management should be bounded and/or sweep stale entries proactively.

### Minimal repro idea

1. Open a page that re-renders or fully refreshes DOM periodically.
2. Run loop:
   - execute `findElements(By.xpath(...))` repeatedly (including broad XPath).
   - optionally trigger periodic refresh/navigation.
3. Observe browser process memory over time.
4. Compare with CSS/id single-match lookups and with reduced lookup frequency.

### Suggested directions

- Add bounded cache policy for atom-side node cache (`$wdc_`) (size limit / LRU).
- Perform periodic stale sweep of cached nodes (detach check with capped batch size).
- Consider cache reset on navigation/refresh boundaries where safe.
- Add diagnostics hook: report cache size / stale ratio for debugging.
- Add stress test to prevent regressions (repeated XPath queries + DOM churn, assert no unbounded growth).

Debugging Logs

### Notes

- This report is not claiming XPath evaluator internals are leaking by themselves.
- The likely hotspot is element-reference cache retention in atom serialization of found nodes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-needs-triagingA Selenium member will evaluate this soon!C-nodejsJavaScript BindingsD-safariI-defectSomething is not working as intendedOS-mac

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions