Skip to content

Refactor fragment parsing algorithm to match implementations#12624

Open
noamr wants to merge 32 commits into
mainfrom
noamr/root-insertion-point
Open

Refactor fragment parsing algorithm to match implementations#12624
noamr wants to merge 32 commits into
mainfrom
noamr/root-insertion-point

Conversation

@noamr

@noamr noamr commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

This makes a few key changes to the fragment parsing workflow, to better match existing implementations and tests.

  • Instead of inserting elements to a fake root element of a connected inert document and then appending them to a DocumentFragment, we let the parser insert the root's children straight into that DocumentFragment. This introduces an "adjusted root insertion target", which is a DocumentFragment to insert to if the parser wants to insert nodes to the root.
  • Instead of using allow declarative shadow roots directly from the document, it becomes a parser flag. This removes some confusion that makes some subtle cases use the wrong setting. It is taken from the document only in the "document opening steps" (document.open() of a document created elsewhere), as it's the only place where this setting is sticky.
  • The custom element registry is taken from the target of the fragment parse rather than from the context document. Together with the other changes, this essential for picking the correct registry e.g. when calling shadowRoot.innerHTML
  • The fragment parsing algorithms take in the target of the parse and infer the context element from that (i.e. it's either the target itself, or its fragment host if it's a DocumentFragment).
  • The "adjusted insertion target" is separate from the "appropriate place from inserting a node", as the parser internals make some decisions based on the "intended parent" which should remain unchanged by this PR.
  • The "adjusted insertion target" is also an enabler that can be used later on for HTML streaming and sanitize-while-parsing, as it can be the context element instead of the intermediate fragment.
  • The intermediate inert document is still around. It is needed to avoid creation time side effects like image loading and custom element constructors to occur before sanitization.

The above changes are a lot closer to how blink implements fragment parsing (and given the passing tests, likely WebKit and Gecko as well).

Closes #11023

(See WHATWG Working Mode: Changes for more details.)


/document-lifecycle.html ( diff )
/dynamic-markup-insertion.html ( diff )
/parsing.html ( diff )
/xhtml.html ( diff )

Comment thread source
Comment thread source Outdated
Comment thread source Outdated
Comment thread source Outdated
Comment thread source Outdated
Comment thread source Outdated
Comment thread source Outdated
Comment thread source Outdated
@noamr

noamr commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

@shannonbooth do you have a list of WPTs that this fixes? I want to add them to the OP.

@shannonbooth

shannonbooth commented Jun 30, 2026

Copy link
Copy Markdown
Member

@noamr noamr changed the title Use "adjusted insertion target" for fragment parsing Refactor fragment parsing algorithm to match implementations Jun 30, 2026
Comment thread source
Comment thread source Outdated
Comment thread source Outdated
Comment thread source Outdated
Comment thread source
Comment thread source
Comment thread source Outdated
Comment thread source
@noamr

noamr commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Fixed editorial changes, also made the root adjustment for text/comment/PI

Comment thread source Outdated
Comment thread source Outdated
Comment thread source Outdated
Comment thread source Outdated
Comment thread source Outdated
Comment thread source Outdated
Comment thread source Outdated
Comment thread source
Comment thread source Outdated
@noamr noamr closed this Jul 2, 2026
@noamr noamr reopened this Jul 2, 2026
@noamr noamr closed this Jul 2, 2026
@noamr noamr reopened this Jul 2, 2026
@noamr noamr mentioned this pull request Jul 2, 2026
6 tasks
Comment thread source Outdated
Comment thread source Outdated
Comment thread source Outdated
Comment thread source
Comment thread source Outdated
Comment thread source Outdated
Comment thread source Outdated
noamr added 8 commits July 2, 2026 17:23
Instead of inserting elements to a fake root element of a connected inert
document and then appending them to a `DocumentFragment`, we let the parser
insert the root's children straight into that `DocumentFragment`.

This is done by having an "adjusted insertion target" that the parser uses to
route insertions desined to the root of the stack directly into the fragment.

The observable behavior change is in subtle places that check for connectedness
when insertion, like radio buttons.
This is also creates an enabler that can be used later on for HTML streaming,
as the "adjusted insertion target" can be the context element instead of the
intermediate fragment.

Note that we still need some notion of the intermediate inert document,
to avoid creation time side effects like image loading and custom element
constructors to occur before sanitization.

(This resembles blink implementation, more than the current wording)

Closes #11023
@noamr noamr force-pushed the noamr/root-insertion-point branch from 455575a to 032ab92 Compare July 2, 2026 16:31
@annevk

annevk commented Jul 6, 2026

Copy link
Copy Markdown
Member

@noamr AI identified to more potential subtle issues here (bit wordy unfortunately):

  1. Custom element registry for template.innerHTML. The PR changed the fragment root's registry from context's registry (main:149796) to "look up a custom element registry given target" (source:149791). For template.innerHTML, target is the template contents (a plain DocumentFragment), and look up a custom element
    registry returns null for a non-shadow DocumentFragment (source:78283) — whereas main used the template element's registry. For the common global-registry case this is identical, but for scoped registries it's an observable difference.
  2. Comments/PIs inserted directly into root in the fragment case. In "after body"/"in frameset" etc., comments are inserted "as the last child of the html element" via an explicit location, which bypasses the new adjusted insertion location root-insertion-target redirection (source:144573 only redirects when the
    location is the topmost node via the default path). Such a comment would land on root, but the algorithm now returns fragment (not root's children), so it would be dropped where main included it. This requires the "after body" mode to be reachable in a fragment parse — which for an html context element it plausibly is.

noamr added 2 commits July 6, 2026 10:28
- Explicitly use template's custom registry
- Adjusted insertion location should adjust overrideTarget
@noamr

noamr commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

@noamr AI identified to more potential subtle issues here (bit wordy unfortunately):

  1. Custom element registry for template.innerHTML. The PR changed the fragment root's registry from context's registry (main:149796) to "look up a custom element registry given target" (source:149791). For template.innerHTML, target is the template contents (a plain DocumentFragment), and look up a custom element
    registry returns null for a non-shadow DocumentFragment (source:78283) — whereas main used the template element's registry. For the common global-registry case this is identical, but for scoped registries it's an observable difference.

Fixed. Special-casing the template contents case to take the CER from the template element.

  1. Comments/PIs inserted directly into root in the fragment case. In "after body"/"in frameset" etc., comments are inserted "as the last child of the html element" via an explicit location, which bypasses the new adjusted insertion location root-insertion-target redirection (source:144573 only redirects when the
    location is the topmost node via the default path). Such a comment would land on root, but the algorithm now returns fragment (not root's children), so it would be dropped where main included it. This requires the "after body" mode to be reachable in a fragment parse — which for an html context element it plausibly is.

Fixed. "adjusted insertion location" now takes in the override target.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Elements created while parsed inside a fragment think they are connected

3 participants