Skip to content

fix(dom): reduce transient allocation during serialize (PER-10135)#2349

Open
rishigupta1599 wants to merge 1 commit into
masterfrom
fix/per-10135-dom-serialize-memory
Open

fix(dom): reduce transient allocation during serialize (PER-10135)#2349
rishigupta1599 wants to merge 1 commit into
masterfrom
fix/per-10135-dom-serialize-memory

Conversation

@rishigupta1599

Copy link
Copy Markdown
Contributor

Context — PER-10135

Customer (RS Components / LCNC, Selenium SDK) sees intermittent renderer OOM / browser disconnects during PercyDOM.serialize on long (~50 min) sessions, both Chrome and Firefox.

Root cause (investigated + reproduced)

Not a memory leak. A single serialize produces a large transient allocation burst (full DOM clone + full HTML string + string copies + JSON.stringify/bridge marshalling). On a customer SPA already near the renderer/VM memory ceiling (telemetry p50 heap ~1885 MB) in a constrained Automate VM, that one-shot burst intermittently tips the renderer over.

  • Reproduced deterministically only under a hard memory cap (docker --memory): identical 45k-node/315 MB page survives at 2 GB (heap sawtooths & recovers) but crashes on the first serialize at 1.1 GB. Roomy hosts can't repro it (--max-old-space-size doesn't bound a renderer) — which is why earlier attempts failed.
  • Leak ruled out: 100 serializes on one tab → post-GC heap flat (~0.0005 MB/snapshot), live DOM node count constant (90,004 → 90,004), heap-object graph +0.06% (noise).

What this PR changes (safe, output-preserving)

Two reductions to Percy's per-snapshot transient footprint:

  1. serialize-dom.js — fold the two marker-rewrite .replace() passes (custom-element proxies + serialized attributes) into a single regex pass, halving large-string allocation churn on the hot tail of every capture. Output verified byte-identical against representative inputs.
  2. clone-dom.js — in getOuterHTML, when there are no shadow roots to embed, return docElement.outerHTML directly instead of the getHTML() → textContent='' → outerHTML.replace() reassembly, avoiding a full-size intermediate string + a replace on the common no-shadow-DOM page.

Honest scope

This is a mitigation, not a complete OOM fix — the reproduction shows the full DOM clone dominates the transient peak, so these string-copy savings don't move a tight boundary on their own. Larger follow-ups (streamed/chunked snapshot transfer to avoid the in-renderer JSON.stringify + bridge double-marshal; capping huge inline content; customer guidance) are tracked in PER-10135.

Testing

  • @percy/dom karma test suite passes.
  • Combined-regex output verified byte-identical to the prior two passes.
  • No-leak validated across 100 serializes (see above).

🤖 Generated with Claude Code

@rishigupta1599
rishigupta1599 requested a review from a team as a code owner July 22, 2026 18:05
Percy snapshot serialization of heavy SPAs allocates large transient
strings on the hot tail of every capture. On a customer app already near
the renderer memory ceiling, that churn adds GC pressure that can
contribute to intermittent renderer OOM / browser disconnects.

Two safe, output-preserving reductions:

- serialize-dom.js: fold the two marker-rewrite `.replace()` passes
  (custom-element proxies + serialized attributes) into a single regex
  pass, halving the large-string allocation churn. Output verified
  byte-identical.
- clone-dom.js: in getOuterHTML, when there are no shadow roots to embed
  as declarative <template shadowrootmode>, return `docElement.outerHTML`
  directly instead of the getHTML()->textContent=''->outerHTML.replace()
  reassembly, avoiding a full-size intermediate string + a replace on the
  common no-shadow-DOM page.

Not a complete OOM fix on its own — the full DOM clone dominates the
transient peak (see PER-10135 for the memory-constrained reproduction and
analysis). Validated as NOT a memory leak: across 100 serializes on one
tab the post-GC heap is flat (~0.0005 MB/snapshot) and the live DOM node
count is constant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@rishigupta1599
rishigupta1599 force-pushed the fix/per-10135-dom-serialize-memory branch from 02e4d8a to 9eb405c Compare July 22, 2026 18:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant