✨ feat(dom): add Shadow DOM tree model#573
Merged
Merged
Conversation
Merging this PR will improve performance by 9.45%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ⚡ | test_feature[text-content] |
2 ms | 1.8 ms | +9.45% |
| 🆕 | test_feature[shadow] |
N/A | 598.1 µs | N/A |
Tip
Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.
Comparing gaborbernat:feat/553-shadow-dom (d650b46) with main (88400b9)
Footnotes
-
18 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
Model the DOM Living Standard shadow tree: attach_shadow attaches an open or closed ShadowRoot to a host element, and the host-to-root link is recorded in a per-tree table so the shadow root stays off the light tree -- it is never a child of the host, so existing queries, traversal, and serialization skip it for free and reach it only through Element.shadow_root or the returned reference. Slots assign the host's children by name in the C core, computed on demand from the spec's find-a-slot and find-slotables walks so an assignment always reflects the current tree: assigned_nodes and assigned_elements read a slot's assignment (with a flatten option that falls back to a slot's own children and expands nested shadow slots), assigned_slot gives the slot a child landed in, and flattened_children returns the composed tree with every slot replaced by what it received. A shadow root reuses the document-fragment node with a tag_flags bit so node_wrap types it as ShadowRoot; the algorithms run under the per-tree critical section. This is the foundation a later declarative-shadow-DOM change builds on. closes tox-dev#553
4991bb8 to
d650b46
Compare
gaborbernat
added a commit
that referenced
this pull request
Jul 7, 2026
…577) * ✨ feat(parser): attach declarative shadow roots A <template shadowrootmode="open|closed"> now attaches a shadow root to its parent instead of building a template content fragment, per the WHATWG tree-construction steps. The C tree builder creates the template only on the stack of open elements, attaches a shadow root to the adjusted current node (reusing the #573 shadow primitives), and redirects the template's content into it, so the template never joins the light tree. shadowrootdelegatesfocus and shadowrootclonable set the matching flags, read through the new ShadowRoot.delegates_focus and ShadowRoot.clonable. The behavior follows the per-document "allow declarative shadow roots" flag: parse() honors it by default (a browser navigation) and parse_fragment() does not (an innerHTML assignment), each overridable with allow_declarative_shadow_roots. A parent that is not a valid shadow host, or already hosts a shadow root, keeps an ordinary template. * 📊 feat(bench): time declarative shadow parsing Add a parse-shadow operation that parses a document of web-component cards whose <template shadowrootmode> attaches a shadow root to each host, gated in CodSpeed over a large inline synthetic corpus. Classify it as a whole-document op in pgo_corpus so the PGO training build feeds it real pages instead of raising KeyError. * 📝 docs(shadow): document declarative shadow roots Cover the parser feature across the four Diátaxis dimensions (the tokenizing tutorial, the shadow-dom how-to and explanation, the parsing reference), extend the parse5 migration guide, and add the towncrier fragment. Tests exercise open and closed modes, the delegatesfocus and clonable flags, nesting, host validity, the already-hosted fallback, the fragment opt-in, and that a plain template still builds a content fragment. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
turbohtml modeled the light DOM but had no way to represent a shadow tree, so code ported from a browser or from jsdom lost
attachShadow,<slot>composition, and the flattened tree it relied on. 🌳 This adds the DOM Living Standard shadow-tree model as the foundation the later declarative-shadow-DOM work (#549) builds on.Element.attach_shadowattaches an open or closedShadowRootand records the host-to-root link in a per-tree table. A shadow root reuses the existing document-fragment node, flagged sonode_wraptypes it asShadowRoot, and it is held off the light tree: it stays outside the host's children, so the existing queries, traversal, and serialization skip it without a special case, and you reach it only throughElement.shadow_root(which readsNonefor a closed root) or the referenceattach_shadowreturned. Slot assignment runs in C from the spec's find-a-slot and find-slotables walks, computed on demand rather than cached, soassigned_nodes,assigned_elements, andassigned_slotreflect the tree as it stands each time you ask.assigned_nodes(flatten=True)falls back to a slot's own children and expands nested shadow slots, andNode.flattened_childrenreturns the composed tree with every slot replaced by what it received. Every algorithm runs under the per-tree critical section.Because a shadow root sits beside the host instead of inside it, cloning or serializing the host does not carry its shadow tree, matching the encapsulation the spec describes. The jsdom migration guide gains a Shadow DOM section mapping
attachShadowandassignedNodesto their turbohtml spellings.closes #553