bi-05 — Analysis
Required invariants
- One wrapper per object per world. Identity and expando survival depend on it.
- Cross-origin access is allow-listed, not filtered. Only
[CrossOrigin]members pass; the default is deny. [Unforgeable]properties cannot be shadowed by page script. This is what makes readingwindow.locationfor a trust decision sound.- Type coercion follows Web IDL exactly. The surprising coercions in DOM APIs are conformance, not sloppiness.
- Worlds are isolated. An extension content script's expandos are invisible to the page and vice versa — a security boundary, not a convention.
The four costs of crossing
Naming them separately is what makes "DOM access is slow" into a usable model:
| Cost | When it dominates |
|---|---|
| Crossing — not inlineable into optimised JS | tight loops over many small accesses |
| Conversion — JS ↔ WTF representations | anything string-heavy (className, textContent) |
| Checks — IDL coercion + security | cross-realm access |
| Side effects — some getters flush style/layout | offsetWidth, getComputedStyle, getBoundingClientRect |
Cost 4 produces order-of-magnitude surprises and is invisible in a JS-only mental model. It is the
reason bi-08's forced-synchronous-layout trace is the highest-value trace in the set.
Why generation rather than hand-writing
generated_in_core.gni and generated_in_modules.gni total roughly half a megabyte of
filenames. The argument for generation is uniformity: thousands of interfaces each needing an
unwrap, a security check, IDL-conformant coercion, and correct exception translation. No human
process gets that right thousands of times.
The cost is navigability, and it is real — bi-01 Technique 4 exists because of it. This is one of
the clearest "complexity that is obviously worth it" cases in the tree, and it is worth contrasting
with the two-GC split, which is much less obviously worth it and exists partly for organisational
reasons.
What would falsify the design
If wrappers could be recreated per access, most of this layer would disappear. They cannot, because
expandos and === must survive — a requirement imposed by JavaScript semantics, not by Blink.
The complexity is inherited from the language, not invented by the engine.