Repository navigation
Passing the scoped custom element registry to the fragment parser has non-obvious side effects #12274
Description
Activity
- addedtopic: custom elementsRelates to custom elements (as defined in DOM and HTML)Relates to custom elements (as defined in DOM and HTML)agenda+To be discussed at a triage meetingTo be discussed at a triage meeting
on Mar 17, 2026 Also in general the behavior of an element's scoped registry (without shadow) is really strange IMO...
e.g., the following wouldn't upgrade the element:
const customElementRegistry = new CustomElementRegistry() customElementRegistry.define("x-element", class MyElement extends HTMLElement { constructor() { super(); console.log("ctor"); } } ); const doc = new DOMParser().parseFromString("<x-element></x-element>", "text/html") const container = document.createElement("div", {customElementRegistry}); document.body.append(container); // The element is adopted and connected, but wouldn't upgrade to anything. container.append(doc.querySelector("x-element"))
But if you add
container.innerHTML = container.innerHTMLat the end, it would, because then the "fragment parser takes custom element registry into account" kicks in.This feels non-obvious and unintuitive to me.
This is a little related: #12209
- addedagenda+To be discussed at a triage meetingTo be discussed at a triage meetingand removedagenda+To be discussed at a triage meetingTo be discussed at a triage meeting
on Mar 18, 2026 This has some observable side effects that might not be ideal.
I think everything noted here except the interaction with the sanitizer is by design and expected. The registry is determined at construction time and cannot mutate after that, unless it starts as
null.Also in general the behavior of an element's scoped registry (without shadow) is really strange IMO...
The behavior specifically related to adopting elements from different documents is treated specially for backwards compatibility. This means that the behavior of
parseFromStringandinnerHTMLis different because in the former case the elements are created in a different document with anullregistry. See this example evolved from the parseFromString code above.- removedagenda+To be discussed at a triage meetingTo be discussed at a triage meeting
on Mar 26, 2026 - addedagenda+To be discussed at a triage meetingTo be discussed at a triage meeting
on Jun 1, 2026 Looking over the past notes I am not sure this was discussed, at least I couldn't find a resolution, but we should resolve this. I think deserializing with a null reg and initializing post-serialisation makes the most sense. But I'd like us to decide on that before making the necessary changes.
- removedagenda+To be discussed at a triage meetingTo be discussed at a triage meeting
on Jun 23, 2026 @noamr if this is fixed by your sanitizer streaming PR as the minutes imply, can you couple it to your PR so this will get closed when we land that change?
Reacted by Noam Rosenthal
What is the issue with the HTML Standard?
Currently, the fragment parser uses the custom element registry of its context element when parsing.
(https://html.spec.whatwg.org/multipage/parsing.html#html-fragment-parsing-algorithm step 8)
This has some observable side effects that might not be ideal.
Specifically, the custom element is upgraded and its constructor is invoked at fragment-parse time rather than at connection time.
It is observable in the following scenarios:
innerHTMLon a disconnected element with a registry upgrades its custom elements and calls the constructorsNot sure how to go about this, since it's the only way those element-scoped registries are respected.