bi-04 — Analysis
Required invariants
- Wrapper identity is stable.
document.body === document.body, and expandos survive a round trip through C++. This single requirement forces the unified heap. - Wrappers are created lazily. A node has no JS object until script observes it — which is why
a gratuitous
querySelectorAll('*')costs more than the query. - Mutation records, it does not recompute. A thousand DOM writes are not a thousand layouts; one interleaved geometry read makes them so.
- Author code never observes an inconsistent tree. Custom element reactions are queued;
MutationObserverdelivers on a microtask; synchronous mutation events were removed for exactly this reason. - Style and layout use the flat tree. Not the node tree. Slot assignment does not change
parentNode. - Reachability is not sufficient for liveness.
ActiveScriptWrappablekeeps objects with pending activity alive; theExecutionContextteardown clause stops that becoming a leak.
The lifetime failure modes
| Leak shape | Mechanism | How you find it |
|---|---|---|
| Detached subtree | a JS reference holds a removed node and everything its listeners close over | heap snapshot, retainer chain |
| Never-disconnected observer | observer holds target, target holds document scope | audit disconnect() in cleanup |
Long-lived Persistent<T> | a root from non-GC code that is never dropped | read the Trace() methods |
| Listener on a shared object | closure retains the component | remove in cleanup — fw-02's lab |
What the heap partitioning discloses
Node, CSSValue, and LayoutObject each get a dedicated typed space in Oilpan. That is a
performance disclosure: those are the highest-volume allocations in a renderer. Your stylesheet
allocates too — CSSValue earning its own space is the evidence, and it corrects the common model
in which "the DOM" is the only thing that costs memory.
The coupling nobody documents
Oilpan schedules GCs through the message loop, at points where no objects are referenced from the native stack, so collection can be precise rather than conservative. The scheduler is therefore also a GC safepoint mechanism — a cross-subsystem constraint that appears in no document about either scheduling or garbage collection alone. Finding couplings like this is what reading two subsystems together buys you.