[scoped-registries] How does customElements.upgrade() work?
justinfagnani opened this issue · 3 comments
Right now the global registry's customeElemets.upgrade()
is the only way to upgrade a disconnected tree. With scoped registries, we will now have different registries each with an upgrade()
method. We have to define how they work.
First, some goals:
- We can't break existing code
- We want existing calls to
.upgrade()
to do the "right" thing if elements in the subtree start using scoped registries.
This brings up a question of user intent. There are potentially two ways to interpret the current intent of a customElements.upgrade(tree)
call:
- Upgrade all the elements that are defined in the global registry
- Upgrade all the elements that are defined in any registry
These are ambiguous at the moment because there is currently only the one global registry.
There seem to me to be a couple of options of how to handle upgrade()
. Let's consider how they work on a tree structure like this:
<div>
<x-a>
#shadowroot (global registry)
<x-b>
#shadowroot (scoped registry)
<x-c>
#shadowroot (global registry)
<x-d>
</div>
And let's assume that this structure is create in such a way that <x-a>
, <x-b>
, and <x-d>
are defined in the global registry after the tree is created, and somehow <x-c>
is defined in <x-b>
's scoped registry after <x-b>
's shadow root is created.
This might be contrived and a very rare way to build a tree, but it's possible and I think shows the limit of the question we have to answer.
The question is what happens if someone calls:
customElements.upgrade(x_a);
-
Upgrade only the elements defined in the global registry.
This may seem like the simplest approach, but it could break some cases where
x-c
and other elements need to interact (via events, etc). I would presume it would upgrade nested shadow roots using the global registry, including those nested in scoped-registry using roots likex-c
's.Presumably with this option, a scoped upgrade call (ie,
this.shadowRoot.customElements.upgrade(this.shadowRoot)
) would only upgrade definitions in that registry, in any shadow root in the tree using that registry, and not elements defined in the global registry. -
Upgrade all elements.
This might capture the intent of the call better, and results in a maximally functioning subtree.
One way to do this would be to say that all registries have the same behavior for
.upgrade()
- that it's roughly equivalent to having a staticCustomElementRegistry.upgrade()
method.
It might be rare to be in a situation where the answer here matters. I think generic code that needs to call .upgrade()
is also probably creating the disconnected subtree, and in order to use scoped registries must already be creating the subtree with one of the scoped element creation APIs.
Still, I think option 2 probably matches user intent better. I'm trying to think of a case where that behavior isn't what you want - where you only want definitions from a single registry to upgrade.
2023 TPAC F2F Resolution: We go with option (2), upgrading all elements across custom element registries. We would only expose upgrade function only on the global custom element registry for clarity.
If the upgrade call happens before the definition of x-c in x-b's shadow root, then upgrade needs to be called again after that, right? F.e. imagine x-b is fetching the JS for x-c while the first upgrade() call happens.
Or, will the scoped registry be marked as "upgraded" such that as soon as the definition for x-c is defined, x-c will be upgraded even if the tree is disconnected?
About the ordering (acknowledging that option 2 is already chosen), it shouldn't matter, because if consumers needing to interact with other elements use whenDefined
to robustly know when elements are ready to be interacted, upgrade order won't matter.
We've figured this one out by now with web platform tests:
https://github.com/web-platform-tests/wpt/blob/master/custom-elements/scoped-registry/scoped-registry-define-upgrade-order.tentative.html