Commit Graph
7 Commits
Author SHA1 Message Date
SGavrl 76e5280c32 fix(dom): preserve namespaced attribute qualified names 2026-08-01 16:00:52 +00:00
SGavrl becb3d941f perf(dom): avoid per-element node clone when serializing template contents
The template.content bridge (#463) routed every element through
content_source(), which called get_node().clone() to read the Copy
template_contents field. That deep-cloned each element's Node (its attrs
Vec plus a String per attribute) on every innerHTML/outerHTML/dump-html
serialization, once per non-void element.

template_contents is already destructured from the node in the serialize
loop, so read it there directly. content_source() (inner_html path) now
reads it through with_node() by reference. Serialization output is
byte-identical; ~10% faster on serialization-heavy pages.
2026-07-24 14:37:42 +00:00
Andrey Mnatsakanov ae438e196b fix(dom): bridge <template> contents to JS so .content is not always empty (#463)
The parser already put template children in a separate contents document
per the HTML spec (tree_sink.rs), but nothing exposed that node, so
`get content()` fabricated an empty DocumentFragment and the parsed
markup was unreachable:

    document.body.innerHTML = '<template id=t><p>a</p></template>';
    t.content.childNodes.length   // 0 (Chrome: 1)
    t.innerHTML                   // "" (Chrome: '<p>a</p>')

Every <template>-driven UI — custom elements cloning
`template.content`, lit, Vue runtime templates, htmx fragments, Alpine
x-if/x-for — stamped nothing, with no error raised.

Three layers were involved:

- `DomTree::template_contents` returns the contents node, allocating one
  on demand so templates built via `createElement` work too. Exposed as
  the `template_contents` op; `get content()` wraps it as a
  DocumentFragment cached by node id, so `.content` keeps a stable
  identity across reads.

- `import_node_from` cloned the element data verbatim, carrying the
  *source* tree's contents NodeId into the destination tree, where it
  indexed whatever unrelated node occupied that slot. That is why a
  template injected via innerHTML resolved `.content` to a bogus node
  rather than merely an empty one. The contents are now imported into a
  fresh node and the reference remapped, queued onto the existing
  worklist so nested templates stay iterative.

- The serializer emitted a template's own (always empty) children, so
  contents vanished from outerHTML/innerHTML and `cloneNode(true)` —
  which round-trips through outer_html — produced an empty template.
  `content_source` resolves a template to its contents document for both
  inner_html and outer_html.

With serialization fixed on the Rust side, the `set innerHTML` template
branch that routed to `.content` now has a matching getter for free, so
no template special case is needed on the JS hot path.
2026-07-24 08:31:55 +02:00
Andrey Mnatsakanov 367601b1ca fix(dom): neutralize all comment terminators when serializing, not just "-->" (#402)
The previous fix escaped only "-->" in comment data, but the HTML tokenizer
ends a comment on four sequences: "-->", "--!>", a leading ">", and a leading
"->". document.createComment(...) accepts arbitrary strings, so a scripted
comment like ">​<img src=x onerror=alert(1)>" still serialized to
"<!--><img ...>-->": the leading ">" abrupt-closes the empty comment and the
trailing <img> parses as live markup (mXSS). The "--!>" and "->" forms escape
the same way.

Every terminator form requires a ">", and entities are not decoded inside
comments, so escape every ">" to "&gt;": this neutralizes all four at once,
keeps the payload inside a single comment, and supersedes the "-->"-only guard.

Regression test builds a scripted comment for each terminator form, serializes,
re-parses, and asserts no <img> escapes; the leading-">", leading-"->", and
"--!>" cases failed before this change.
2026-07-13 22:46:16 +00:00
Andrey Mnatsakanov b662ff8d72 fix(dom): make serialize, textContent, and import iterative (#367)
serialize_node/serialize_children, collect_text_inner (textContent), and
import_node_from recursed once per DOM nesting level with no depth cap, so a
deeply nested tree overflowed the thread stack and aborted the process. A
stack overflow is a hard abort that op_dom's catch_unwind cannot recover, so
one hostile page could take down the worker. descendants() was already made
iterative and capped for exactly this reason; these paths were missed.

Rewrite all three as iterative walks over an explicit heap stack and add the
same defense-in-depth cap so a cyclic graph stops instead of spinning.
Serialized output and traversal order are unchanged. Adds tests that build a
100k-deep tree and exercise each path.

Closes #364.
2026-07-09 10:47:25 +02:00
SGavrl 4603c05ab2 fix(dom): neutralize "-->" when serializing comment contents
The comment serializer wrote its contents verbatim. The HTML parser can
never produce a comment whose data contains "-->" (it ends the comment),
but script can via document.createComment("a-->b"). Serializing that
verbatim closed the comment early and let the trailing text escape into
markup. Replace "-->" with "--&gt;" so the output round-trips as a single
comment.

Fixes #342.
2026-07-01 15:58:49 +00:00
h4ckf0r0day 8749725b45 Initial Release 2026-04-13 12:30:28 +02:00