The native renderer and core share the vectors in
crates/forma-ir/tests/fixtures/parity/inv1.json. The native walker executes each
case through static and dynamic attributes; core executes static and reactive
h(), strict adoption expectations and later writes. Browser tests replay every
name/URL vector in Chromium, Firefox and WebKit. Changes to this policy update
both readers and the shared table together.
An ordinary attribute name must match [A-Za-z_:][-A-Za-z0-9_:.]* in its entirety.
The rule is ASCII: Unicode letters, empty names, leading digits, whitespace,
quotes, slashes and angle brackets are refused. This deliberately retains the
stricter existing native rule; the DOM accepting a name is insufficient. A name
starting with xlink: also needs a nonempty local name in that grammar with no
colon, because the browser writer uses setAttributeNS. Both sides omit an
empty local name, a leading digit/dash, or an extra colon. Names
beginning with on, in any ASCII casing, never become inline handler attributes.
Authored function event props still use the existing listener API. Typed raw
markup, style, control properties and internal island metadata retain their own
policies; an accepted name grants no raw-HTML or executable-code authority.
The URL-bearing set is href, src, action, formaction, xlink:href,
poster, background and data, matched case-insensitively. Check the raw
value before text normalization: a NUL anywhere refuses the value. For scheme
classification, remove U+0000–U+0020 and U+007F–U+009F, lowercase ASCII, and refuse
prefixes javascript, vbscript and data:text/html. This is Forma's
conservative policy, not an exact implementation of the browser URL parser.
The prefixes intentionally include near misses such as javascript-guide and
data:text/htmlx; a relative path such as /javascript-guide is allowed. Allowed
values are subsequently written with the ordinary value normalization/escaping
rules; the classification transform does not rewrite them.
A data:image/svg+xml prefix is allowed only on the existing image/media-context
tag set: img, image, video, audio, source, body, table, thead,
tbody, tfoot, tr, td, th. Other tags and missing context refuse it. The
context check is case-insensitive. srcset retains its separate image-candidate
semantics; it is not parsed as a single URL by this predicate. An ordinary
title or data-href containing URL-like text remains ordinary data.
Refusal means omission. Fresh core drops invalid names before reading their
values; a reactive refused URL removes its prior accepted attribute. During
strict adoption, the expected value for a rejected attribute is absence. If the
served DOM contains it, validation fails with FORMA_E_HYDRATE_VALUE, preserves
the served nodes and attributes, and puts that island in error. It never repairs
the initial DOM. A neighboring valid island can still activate. A served omission
adopts without initial writes, and later allowed updates use the same policy.
G2's ordinary text and attribute payloads are tested as data in both languages.
Raw srcdoc and dangerouslySetInnerHTML still require the typed trusted-value
boundary from FORMA-109; this specification is not a general HTML sanitizer.
Verification#
- Native:
walker::tests::inv1_vectors. - Core:
packages/core/src/dom/__tests__/inv1-vectors.test.ts, alongside the renderer contract and trusted-HTML suites. - Browser:
packages/core/e2e/inv1.spec.ts, including a rejected-name island, a working neighbor, retained mutation observations, and a positive later-write control.
The JavaScript unit DOM has a stale getAttribute('xml:lang') cache after a
second write (its actual Attr.value and serialized DOM change). The unit helper
reads the attribute node, and the browser replay checks native getAttribute
for every vector. Rejected qualified names also use the real-browser island
replay, since the model splits no-namespace colon names differently. No production
behavior is changed to accommodate that model.