fix(dom): reduce transient allocation during serialize (PER-10135)#2349
Open
rishigupta1599 wants to merge 1 commit into
Open
fix(dom): reduce transient allocation during serialize (PER-10135)#2349rishigupta1599 wants to merge 1 commit into
rishigupta1599 wants to merge 1 commit into
Conversation
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
force-pushed
the
fix/per-10135-dom-serialize-memory
branch
from
July 22, 2026 18:12
02e4d8a to
9eb405c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context — PER-10135
Customer (RS Components / LCNC, Selenium SDK) sees intermittent renderer OOM / browser disconnects during
PercyDOM.serializeon long (~50 min) sessions, both Chrome and Firefox.Root cause (investigated + reproduced)
Not a memory leak. A single
serializeproduces 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.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-sizedoesn't bound a renderer) — which is why earlier attempts failed.What this PR changes (safe, output-preserving)
Two reductions to Percy's per-snapshot transient footprint:
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.clone-dom.js— ingetOuterHTML, when there are no shadow roots to embed, returndocElement.outerHTMLdirectly instead of thegetHTML() → 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/domkarma test suite passes.🤖 Generated with Claude Code