Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

  • This test has a WPT meta file that expects 3 subtest issues.
  • This WPT test may be referenced by the following Test IDs:
<!DOCTYPE html>
<title>shadowrootadoptedstylesheets serialization round-trip via getHTML()</title>
<meta name="author" title="Kurt Catti-Schmidt" href="mailto:kschmi@microsoft.com" />
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script type="importmap">
{
"imports": {
"foo": "data:text/css,span {color:blue}",
"bar": "data:text/css,span {color:red}"
}
}
</script>
<div id="host_basic">
<template shadowrootmode="open"
shadowrootserializable
shadowrootadoptedstylesheets="foo">
<span>basic</span>
</template>
</div>
<div id="host_whitespace">
<template shadowrootmode="open"
shadowrootserializable
shadowrootadoptedstylesheets=" foo bar ">
<span>whitespace</span>
</template>
</div>
<div id="host_empty">
<template shadowrootmode="open"
shadowrootserializable
shadowrootadoptedstylesheets="">
<span>empty</span>
</template>
</div>
<div id="host_absent">
<template shadowrootmode="open" shadowrootserializable>
<span>absent</span>
</template>
</div>
<script>
const opts = { serializableShadowRoots: true };
test(() => {
// Non-empty value round-trips verbatim.
let host = document.getElementById("host_basic");
let html = host.getHTML(opts);
assert_true(
html.includes("shadowrootadoptedstylesheets=\"foo\""),
`Serialized markup must contain shadowrootadoptedstylesheets="foo". Got: ${html}`
);
// Whitespace is preserved verbatim.
host = document.getElementById("host_whitespace");
html = host.getHTML(opts);
assert_true(
html.includes("shadowrootadoptedstylesheets=\" foo bar \""),
`Serialized markup must preserve verbatim whitespace. Got: ${html}`
);
// Authored empty value round-trips as present-and-empty.
host = document.getElementById("host_empty");
html = host.getHTML(opts);
assert_true(
html.includes("shadowrootadoptedstylesheets=\"\""),
`Serialized markup must contain shadowrootadoptedstylesheets="" (present, empty). Got: ${html}`
);
// Absent attribute is omitted from serialized markup.
host = document.getElementById("host_absent");
html = host.getHTML(opts);
assert_false(
html.includes("shadowrootadoptedstylesheets"),
`Serialized markup must omit shadowrootadoptedstylesheets when not authored. Got: ${html}`
);
}, "Element.getHTML() round-trips shadowrootadoptedstylesheets across absent, empty, basic, and whitespace values.");
test(() => {
// Direct ShadowRoot.getHTML() returns children-only markup; the wrapping
// <template> (which is what would carry the shadowroot* attributes) is not
// emitted, so the attribute must NOT appear in the result.
const host = document.getElementById("host_basic");
const html = host.shadowRoot.getHTML();
assert_false(
html.includes("shadowrootadoptedstylesheets"),
`ShadowRoot.getHTML() returns children-only markup; must not include the wrapping template attribute. Got: ${html}`
);
}, "ShadowRoot.getHTML() returns children-only markup (no wrapping template).");
test(() => {
// The shadowrootadoptedstylesheets content attribute is populated only
// by the HTML parser when the declarative <template> is processed.
// Subsequent mutations to ShadowRoot.adoptedStyleSheets (e.g., pushing a
// constructor sheet) update the live IDL list but do NOT update the
// serialized attribute value. As a consequence, script-added sheets are
// necessarily dropped on serialization (and, by extension, on cloneNode
// / setHTMLUnsafe round-trips), since constructor sheets have no URL or
// import-map specifier that could be emitted.
const host = document.getElementById("host_basic");
const constructed = new CSSStyleSheet();
constructed.replaceSync("span { font-weight: bold; }");
const original = Array.from(host.shadowRoot.adoptedStyleSheets);
host.shadowRoot.adoptedStyleSheets = [...original, constructed];
try {
assert_equals(host.shadowRoot.adoptedStyleSheets.length, 2,
"Sanity: live adoptedStyleSheets reflects the script mutation.");
const html = host.getHTML(opts);
assert_true(
html.includes("shadowrootadoptedstylesheets=\"foo\""),
`Serialized markup must still emit the original attribute value. Got: ${html}`
);
// The constructor sheet's rule must not leak into the serialized
// markup in any form (it has no URL / specifier representation).
assert_false(
html.includes("font-weight"),
`Serialized markup must not contain text from the script-added constructor sheet. Got: ${html}`
);
// No second occurrence of the attribute either (the serializer emits
// it once, regardless of the live list length).
assert_equals(
(html.match(/shadowrootadoptedstylesheets/g) || []).length, 1,
`shadowrootadoptedstylesheets attribute must appear exactly once. Got: ${html}`
);
} finally {
host.shadowRoot.adoptedStyleSheets = original;
}
}, "getHTML() ignores script-added entries in adoptedStyleSheets and emits only the authored shadowrootadoptedstylesheets value.");
test(() => {
// Same invariant when the authored value is the present-but-empty form:
// pushing a constructor sheet does not promote the empty attribute to a
// non-empty one in the serialized markup.
const host = document.getElementById("host_empty");
const constructed = new CSSStyleSheet();
constructed.replaceSync("span { font-style: italic; }");
const original = Array.from(host.shadowRoot.adoptedStyleSheets);
host.shadowRoot.adoptedStyleSheets = [...original, constructed];
try {
assert_equals(host.shadowRoot.adoptedStyleSheets.length, 1,
"Sanity: live adoptedStyleSheets reflects the script mutation.");
const html = host.getHTML(opts);
assert_true(
html.includes("shadowrootadoptedstylesheets=\"\""),
`Empty authored value must round-trip as present-and-empty even after script mutation. Got: ${html}`
);
assert_false(
html.includes("font-style"),
`Constructor-sheet rule text must not appear in serialized markup. Got: ${html}`
);
} finally {
host.shadowRoot.adoptedStyleSheets = original;
}
}, "getHTML() with an empty authored shadowrootadoptedstylesheets value is unaffected by script mutations.");
</script>