Files
sousa-gecko/testing/web-platform/tests/custom-elements/registries/template.window.js
T
Blink WPT Bot 72402cf2d0 Bug 2039678 [wpt PR 59888] - Fix custom element upgrade in declarative shadow roots parsed via setHTMLUnsafe, a=testonly
Automatic update from web-platform-tests
Fix custom element upgrade in declarative shadow roots parsed via setHTMLUnsafe (#59888)

When template.setHTMLUnsafe() parses declarative shadow roots, the
template document has no domWindow(), causing AttachShadowRootInternal
to call SetCustomElementRegistry(nullptr) which incorrectly sets
waiting_for_registry_=true. After adoption into the live document,
customElementRegistry() returns null, blocking TryToUpgrade.

Fix by adding an explicit waiting_for_scoped_registry parameter to
AttachShadowRootInternal to distinguish 'no window available' from
'explicitly null registry'. SetCustomElementRegistry is now only called
when registry || waiting_for_scoped_registry.

Also update sethtml-with-declarative-shadow-root.tentative.html
expectation that customElementRegistry is null when the sanitizer strips
shadowrootcustomelementregistry. The null was accidental — shadow roots
parsed in template documents (used by setHTML with sanitizer via
ForceInertTemplate) were incorrectly stuck with null registries. The
correct default when the attribute is absent is the global registry.

Bug: 512514744
Change-Id: If9cc66a90c46c6c3cdeda0bf9e090abff4da021a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7844339
Reviewed-by: Noam Rosenthal <nrosenthal@google.com>
Reviewed-by: Mason Freed <masonf@chromium.org>
Commit-Queue: Jayson Chen <jaysonchen@microsoft.com>
Cr-Commit-Position: refs/heads/main@{#1630685}

Co-authored-by: Jayson Chen <jaysonchen@microsoft.com>
--

wpt-commits: d21d659d9020b1a2ea83e443bf05e8c59a8090c1
wpt-pr: 59888
2026-06-02 10:25:54 +00:00

104 lines
6.1 KiB
JavaScript

test(() => {
const template = document.createElement("template");
assert_false(template.hasAttribute("shadowrootcustomelementregistry"));
assert_equals(template.shadowRootCustomElementRegistry, "");
template.shadowRootCustomElementRegistry = "blah";
assert_equals(template.getAttribute("shadowrootcustomelementregistry"), "blah");
assert_equals(template.shadowRootCustomElementRegistry, "blah");
}, "shadowRootCustomElementRegistry reflects as string");
test(() => {
const div = document.createElement("div");
div.setHTMLUnsafe(`<span><template shadowrootmode=open shadowrootcustomelementregistry shadowrootserializable></template></span>`);
assert_equals(div.firstChild.firstChild, null);
assert_equals(div.getHTML({ serializableShadowRoots: true }), "<span><template shadowrootmode=\"open\" shadowrootserializable=\"\" shadowrootcustomelementregistry=\"\"></template></span>");
}, "Serializing a null registry ShadowRoot with a global registry host (document)");
test(() => {
const div = document.createElement("div");
div.setHTMLUnsafe(`<span><template shadowrootmode=open shadowrootserializable></template></span>`);
assert_equals(div.firstChild.firstChild, null);
assert_equals(div.firstChild.shadowRoot.customElementRegistry, customElements);
assert_equals(div.getHTML({ serializableShadowRoots: true }), "<span><template shadowrootmode=\"open\" shadowrootserializable=\"\"></template></span>");
}, "Serializing a global registry ShadowRoot with a global registry host (document)");
test(() => {
const div = document.createElement("div");
div.setHTMLUnsafe(`<span><template shadowrootmode=open shadowrootcustomelementregistry shadowrootserializable></template></span>`);
const registry = new CustomElementRegistry();
registry.initialize(div.firstChild.shadowRoot);
assert_equals(div.firstChild.shadowRoot.customElementRegistry, registry);
assert_equals(div.getHTML({ serializableShadowRoots: true }), "<span><template shadowrootmode=\"open\" shadowrootserializable=\"\" shadowrootcustomelementregistry=\"\"></template></span>");
}, "Serializing a scoped registry ShadowRoot with a global registry host (document)");
test(() => {
const div = document.implementation.createHTMLDocument().createElement("div");
assert_equals(div.customElementRegistry, null);
div.setHTMLUnsafe(`<span><template shadowrootmode=open shadowrootcustomelementregistry shadowrootserializable></template></span>`);
assert_equals(div.firstChild.shadowRoot.customElementRegistry, null);
assert_equals(div.getHTML({ serializableShadowRoots: true }), "<span><template shadowrootmode=\"open\" shadowrootserializable=\"\"></template></span>");
}, "Serializing a null registry ShadowRoot with a null registry host (document)");
test(() => {
const div = document.implementation.createHTMLDocument().createElement("div");
assert_equals(div.customElementRegistry, null);
div.setHTMLUnsafe(`<span><template shadowrootmode=open shadowrootcustomelementregistry shadowrootserializable></template></span>`);
const registry = new CustomElementRegistry();
registry.initialize(div.firstChild.shadowRoot);
assert_equals(div.firstChild.shadowRoot.customElementRegistry, registry);
assert_equals(div.getHTML({ serializableShadowRoots: true }), "<span><template shadowrootmode=\"open\" shadowrootserializable=\"\" shadowrootcustomelementregistry=\"\"></template></span>");
}, "Serializing a scoped registry ShadowRoot with a null registry host (document)");
test(() => {
const registry = new CustomElementRegistry();
const hostDocument = document.implementation.createHTMLDocument();
registry.initialize(hostDocument);
assert_equals(hostDocument.customElementRegistry, registry);
const host = hostDocument.createElement('div');
assert_equals(host.customElementRegistry, registry);
const shadow = host.attachShadow({ mode: "closed", serializable: true, customElementRegistry: null });
assert_equals(shadow.customElementRegistry, null);
assert_equals(host.getHTML({ serializableShadowRoots: true }), `<template shadowrootmode="closed" shadowrootserializable="" shadowrootcustomelementregistry=""></template>`);
}, "Serializing a null registry ShadowRoot with a scoped registry host (document)");
test(() => {
const registry = new CustomElementRegistry();
const hostDocument = document.implementation.createHTMLDocument();
registry.initialize(hostDocument);
assert_equals(hostDocument.customElementRegistry, registry);
const host = hostDocument.createElement('div');
assert_equals(host.customElementRegistry, registry);
const shadow = host.attachShadow({ mode: "closed", serializable: true, customElementRegistry: registry });
assert_equals(shadow.customElementRegistry, registry);
assert_equals(host.getHTML({ serializableShadowRoots: true }), `<template shadowrootmode="closed" shadowrootserializable="" shadowrootcustomelementregistry=""></template>`);
}, "Serializing a scoped registry ShadowRoot with a scoped registry host (document)");
test(() => {
const registry = new CustomElementRegistry();
const element = document.createElement('a-b', { customElementRegistry: registry });
element.setHTMLUnsafe(`<a-b><template shadowrootmode="open"></template></a-b>`);
assert_equals(element.firstChild.customElementRegistry, registry);
assert_equals(element.firstChild.shadowRoot.customElementRegistry, customElements);
}, "A declarative shadow root gets its default registry from its node document");
test(() => {
const name = "declarative-shadow-upgrade-el";
let connectedCount = 0;
class TestElement extends HTMLElement {
connectedCallback() { connectedCount++; }
}
customElements.define(name, TestElement);
const template = document.createElement("template");
template.setHTMLUnsafe(
`<div><template shadowrootmode="open"><${name}></${name}></template></div>`);
document.body.appendChild(template.content);
const host = document.body.lastElementChild;
const el = host.shadowRoot.querySelector(name);
assert_true(el instanceof TestElement);
assert_equals(connectedCount, 1);
host.remove();
}, "Custom element upgrades inside a declarative shadow root parsed via template.setHTMLUnsafe()");