Skip to content

Add test cases for null registry element upon adoption#56423

Open
ja-y-son wants to merge 5 commits intoweb-platform-tests:masterfrom
ja-y-son:adopt-update
Open

Add test cases for null registry element upon adoption#56423
ja-y-son wants to merge 5 commits intoweb-platform-tests:masterfrom
ja-y-son:adopt-update

Conversation

@ja-y-son
Copy link
Copy Markdown
Contributor

@ja-y-son ja-y-son commented Dec 2, 2025

Add test cases that covers the scenarios in whatwg/dom#1437

@ja-y-son
Copy link
Copy Markdown
Contributor Author

ja-y-son commented Dec 4, 2025

@annevk PTAL, thanks!

Copy link
Copy Markdown
Member

@annevk annevk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have some tests where you just call adoptNode(). And some tests with attachShadow() instead of declarative shadow roots.

And some tests where we initialize() various parent nodes in this null/global shadow tree with a scoped registry prior to adoption.

@ja-y-son
Copy link
Copy Markdown
Contributor Author

Added more test cases to hopefully cover most of the adoption cases that we're interested in this spec change. I haven't implemented this part of spec change in chromium so I tried my best to verify that each test case is asserting the correct expectation, but will appreciate a lot for another review of them.

Copy link
Copy Markdown
Member

@annevk annevk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generally looks good. I'd like to try this in WebKit, but the patch needs rebasing it seems.

cc @rniwa


function runTest(operation, operationName) {
// Null registry child tests
test((test) => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional nit (applies quite a few times):

Suggested change
test((test) => {
test(test => {

@ja-y-son
Copy link
Copy Markdown
Contributor Author

ja-y-son commented Jan 4, 2026

@annevk Updated the nit. Let me know if you've tried on Safari and if you think we should make any other changes on the PR

@annevk
Copy link
Copy Markdown
Member

annevk commented Jan 7, 2026

Still has many lint errors that need to be fixed. Please make sure CI is green.

@ja-y-son
Copy link
Copy Markdown
Contributor Author

ja-y-son commented Jan 8, 2026

Thanks for the heads up! Fixed rest of the nit

@annevk
Copy link
Copy Markdown
Member

annevk commented Jan 13, 2026

It seems this has a merge conflict now. Can you address it? I'll also ping @rniwa again as I'd really like him to take a look as well.

const [doc, win] = makeIframe(t);
doc.body.append(clone);
const host2shadow = doc.querySelector('#template-host-2').shadowRoot;
assert_equals(host2shadow.querySelector('new-foo').customElementRegistry, null);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why we expect the registry of new-foo to be null here. When we're adopting this element in append, inclusiveDescendant is an element, and its custom element registry is null at that point. In step 3.1.3.2.2, inclusiveDescendant's custom element registry is null and the parent isn't null (it's a ShadowRoot) or an exclusive DocumentFragment node so we move onto step 3.1.3.2.3. Step 3.1.3.2.3 says we set the registry to the result of looking up a custom element registry given inclusiveDescendant's parent. Here, inclusiveDescendant's parent is a ShadowRoot which is a clone of a ShadowRoot which had been initialized in line 175 of this test. So far as I can tell, customElementRegistry shouldn't be null here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct that new-foo has parents that have valid scoped custom element registry. At the same time in step 3.1.3.2.4, it outlines that the registry we should set to the inclusiveDescendant is the effective global custom element registry of that registry from the parent. Given the parent's registry's is scoped is true, we will end up setting a null registry to new-foo

// Null registry child tests
test(test => {
const [win, host] = operation(addNullRegistryChild(createParentElementWithRegistryAttribute(null)), test);
assert_equals(host.querySelector('a-b').customElementRegistry, null);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why this case uses null registry either. When appendToNewDocument is called, we're appending the parent div without customelementregistry, meaning its registry is not null (uses the global registry) along with its descendants, the first of which has customelementregistry specified and uses null registry. However, step 3.1.3.3 of adopting a node says to lookup the custom element registry given its parent, which is the parent div that uses a global registry. Step 3.1.3.4 sets the registry of the inner div (one with customelementregistry) to this global registry as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We were going to include this spec change in whatwg/dom#1437 but decided to leave it to whatwg/html#12000 as its related to the element attribute. But essentially, we're planning to have a similar concept to shadowrootcustomelementregistry of "keep custom element registry null", so in this case the element with the attribute will remain to have null registry upon adoption. I can add these tests related to element attribute back later if this seems too confusing.

doc.body.append(clone);
const host1shadow = doc.querySelector('#template-host-1').shadowRoot;
assert_equals(host1shadow.querySelector('x-foo').customElementRegistry, win.customElements);
assert_true(host1shadow.querySelector('x-foo') instanceof XFoo);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will always fail because XFoo extends the outer document's HTMLElement so the HTMLElement constructor will throw upon an upgrade attempt. Each one of these test cases that use XFoo in an iframe should define its own class which extends iframe's HTMLElement.

Here's a simple HTML which demonstrates that the constructor extending HTMLElement in the outer document is problematic:

<!DOCTYPE html>
<html>
<body>
<script>

class CustomElement extends HTMLElement { };

const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.contentWindow.customElements.define('custom-element', CustomElement);
iframe.contentDocument.body.innerHTML = '<custom-element></custom-element>';

</script>
</body>
</html>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this! Will fix the issue.


test(test => {
const [win, host] = operation(addScopedRegistryChild(createParentDeclarativeShadowRootWithRegistryAttribute(scopedRegistry)), test);
assert_equals(host.shadowRoot.querySelector('a-b').customElementRegistry, scopedRegistry);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this expected behavior is correct. createParentDeclarativeShadowRootWithRegistryAttribute calls initialize but only after having inserted the cloned subtree into iframe's document. Because adopting a node sets the registry of the shadow root and initialize only sets the registry when the current value is null, initialize will have no effect on the subtree.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createParentDeclarativeShadowRootWithRegistryAttribute actually does not insert the cloned subtree into an iframe's document, but just an outer document, and since appending within the same document does not give the shadowroot a non-null registry, running initialize should be effective on setting registry on the given shadow root.

@rniwa
Copy link
Copy Markdown
Contributor

rniwa commented Jan 22, 2026

There could be issues with other test cases but can we tackle the above issues first?

@ja-y-son ja-y-son requested review from annevk and rniwa February 11, 2026 21:02
@ja-y-son
Copy link
Copy Markdown
Contributor Author

@rniwa @annevk Gentle ping on this PR as I'd like to conclude changes here before we keep working on whatwg/html#12000

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants