Skip to content
forma.
v3.0
☰
Browse documentation
Start here
Build interfaces
Native rendering
Tooling
Security and operations
Architecture
Application guides
API reference

Docs/Architecture

FORMA 3.0

Attribute and URL safety

Shared rules for attribute names, URL values and strict DOM adoption.

View source ↗Markdown ↓Source 753be2a
On this page

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.

Find your next answer.

Search Forma 3.0 documentation, guides and examples.