✨ feat(parser): declarative Shadow DOM (<template shadowrootmode>)#577
Merged
gaborbernat merged 4 commits intoJul 7, 2026
Merged
Conversation
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 tox-dev#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.
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.
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.
for more information, see https://pre-commit.ci
Merging this PR will not alter performance
Performance Changes
Comparing Footnotes
|
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.
Servers ship shadow trees inline with a
<template shadowrootmode>, and a browser attaches them during parsing so the encapsulation is in place before any script runs. turbohtml parsed such a template as an ordinary template element, leaving the shadow tree unbuilt and the markup misrepresented. This wires declarative shadow DOM into the C tree builder soparsereproduces what a navigating browser produces, closes #549.The tree builder follows the WHATWG tree-construction steps for the
templatestart tag directly. 🌱 When a<template>carries ashadowrootmodeofopenorclosedand its parent is a valid shadow host, it attaches a shadow root to that parent, reusing the #573 Shadow DOM primitives (attach_shadow, the off-tree shadow node, the per-tree host table) rather than a new tree model, and redirects the template's content into the shadow root. The template element is created only on the stack of open elements, so it never joins the light tree and disappears once its content is parsed.shadowrootdelegatesfocusandshadowrootclonableset flags surfaced asShadowRoot.delegates_focusandShadowRoot.clonable. Two guards keep the transform faithful to the DOM: the host must be a valid shadow host name (a flow-content element or a hyphenated custom-element name), and a parent that already hosts a shadow root keeps its first one.The feature honors the spec's per-document
allow declarative shadow rootsflag. Whole-documentparsesets it on, matching a browser navigation, whileparse_fragmentleaves it off, matching aninnerHTMLassignment; the newallow_declarative_shadow_rootsargument on each flips that default, turning a fragment into thesetHTMLUnsafecase. 📦 This changes the default document parse: a stray<template shadowrootmode>now leaves the light tree, so passallow_declarative_shadow_roots=Falseto keep the old plain-template behavior.