Open keithamus opened 4 weeks ago
So - just to check in with implementers (and specifically @emilio, @mfreed7, @rniwa who were in the meeting and may recall the discussion, also /cc @smaug---- for more confirmation on the Gecko side) - could we always populate the
TreeScope
with a Node subtype or Node instance which has a new bit set to say "this is a dummy proxy Node that points to a parent but shouldn't be given to script, and is just for keeping the shadowroot reference alive so that createElement & co work seamlessly" (the variable name could use some bikeshedding).
I'm supportive of an approach like this to allow us to keep track of the link to follow to find the correct (scoped) registry. One nit might be that I was thinking you'd put a bit on Element
that means "my TreeScope reference isn't real - I'm disconnected. But the TreeScope reference does point to a TreeScope that has a registry that you can use for element creation". That's maybe what you meant by that paragraph, but I just wanted to make sure. But that's really in the details - I'm supportive.
Oh yeah 🤦 that makes much more sense!
Could someone clarify the exact situation in which this will be useful again?
Could someone clarify the exact situation in which this will be useful again?
When you create an element via a scoped registry, but it's not attached to the document, ie:
const el = shadowRootWithRegistry.createElement('div');
el.innerHTML = `<x-foo></x-foo>`;
shadowRootWithRegistry.append(el);
We don't want <x-foo>
upgrading in the gloabal registry in this case.
But the element isn't gonna upgrade until it's connected anyway, and at the time of connection, the element belongs to the shadow root with the scoped custom element registry so the normal lookup would work.
If <x-foo>
defined in the global registry it will upgrade immediately, right?
No. Customer elements only upgrade when they're connected.
@rniwa the constructor functions run though, right? And so instanceof
checks pass. For example:
customElements.define('foo-bar', class extends HTMLElement {
constructor() {
super()
console.log('FooBar element was constructed')
}
})
let div = document.createElement('div')
div.innerHTML = '<foo-bar></foo-bar>'
I wrote up some WPTs for this here: https://github.com/web-platform-tests/wpt/pull/46170
@keithamus Hm... you're right. The constructor runs in this case.
In https://github.com/w3c/tpac2024-breakouts/issues/26 we discussed the idea of how we could store the shadowroot of an element created with
shadowroot.createElement
. Storing the originating shadowroot of calls to this would make this APIs much more ergonomic, as calls to for e.g.innerHTML
on that node could retain the scoped registry in order to correctly assign definitions to custom elements during that call. Without this these APIs take an ergonomic hit.It was raised after the meeting (and therefore not in the minutes) that one way to do this without introducing overhead would be to utilise the dangling pointer for the "TreeRoot", which is a
nullptr
for disconnected nodes. In Chromium this pointer is toNode
,nsINode
in Gecko andEventTarget
in WebKit. Each of these classes has a set of bitflags with what looks like 1 flag remaining (Chromium'sNodeFlags
, and WebKit'sEventTargetFlag
explicitly mention 1-bit free, and while Gecko'sBooleanFlag
uses 32 bits, the last bit is a guard value which could potentially be repurposed).So - just to check in with implementers (and specifically @emilio, @mfreed7, @rniwa who were in the meeting and may recall the discussion, also /cc @smaug---- for more confirmation on the Gecko side) - could we always populate the
TreeScope
with a Node subtype or Node instance which has a new bit set to say "this is a dummy proxy Node that points to a parent but shouldn't be given to script, and is just for keeping the shadowroot reference alive so that createElement & co work seamlessly" (the variable name could use some bikeshedding).