bi-05 — Analysis

Required invariants

  1. One wrapper per object per world. Identity and expando survival depend on it.
  2. Cross-origin access is allow-listed, not filtered. Only [CrossOrigin] members pass; the default is deny.
  3. [Unforgeable] properties cannot be shadowed by page script. This is what makes reading window.location for a trust decision sound.
  4. Type coercion follows Web IDL exactly. The surprising coercions in DOM APIs are conformance, not sloppiness.
  5. 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:

CostWhen it dominates
Crossing — not inlineable into optimised JStight loops over many small accesses
Conversion — JS ↔ WTF representationsanything string-heavy (className, textContent)
Checks — IDL coercion + securitycross-realm access
Side effects — some getters flush style/layoutoffsetWidth, 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.