blob: c48230c17017cf6909bc281557c6acffbbbcb28b [file] [log] [blame]
<!DOCTYPE html>
<title>getHTML behavior</title>
<link rel='author' href='mailto:masonf@chromium.org'>
<link rel='help' href='https://github.com/whatwg/html/issues/8867'>
<script src='/resources/testharness.js'></script>
<script src='/resources/testharnessreport.js'></script>
<script src='../../html/resources/common.js'></script>
<body>
<script>
function testElementType(allowsShadowDom, elementType, runGetHTMLOnShadowRoot, declarativeShadowDom, mode, delegatesFocus, serializable, clonable) {
const t = test(t => {
// Create and attach element
let wrapper;
if (runGetHTMLOnShadowRoot) {
// This ensures we're testing both Element.getHTML() and ShadowRoot.getHTML().
const host = document.createElement('div');
t.add_cleanup(function() { host.remove(); });
document.body.appendChild(host);
wrapper = host.attachShadow({mode: 'open'});
} else {
wrapper = document.createElement('div');
t.add_cleanup(function() { wrapper.remove(); });
document.body.appendChild(wrapper);
}
let shadowRoot;
const isOpen = mode === 'open';
let initDict = {mode: mode, delegatesFocus: delegatesFocus, clonable};
let expectedSerializable = null;
switch (serializable) {
case undefined: expectedSerializable = false; break;
case "true": initDict.serializable = expectedSerializable = true; break;
case "false": initDict.serializable = expectedSerializable = false; break;
default: throw new Error(`Invalid serializable ${serializable}`);
}
const delegatesAttr = delegatesFocus ? ' shadowrootdelegatesfocus=""' : '';
const serializableAttr = expectedSerializable ? ' serializable=""' : '';
const clonableAttr = clonable ? ' shadowrootclonable=""' : '';
if (allowsShadowDom && declarativeShadowDom) {
const html = `<${elementType}><template shadowrootmode=${mode}${delegatesAttr}${serializableAttr}${clonableAttr}>`;
wrapper.setHTMLUnsafe(html);
if (isOpen) {
shadowRoot = wrapper.firstElementChild.shadowRoot;
} else {
// For closed shadow root, we rely on the behavior of attachShadow to return it to us
shadowRoot = wrapper.firstElementChild.attachShadow(initDict);
}
} else {
// Imperative shadow dom
const element = document.createElement(elementType);
wrapper.appendChild(element);
if (allowsShadowDom) {
shadowRoot = element.attachShadow(initDict);
}
}
assert_true(!allowsShadowDom || !!shadowRoot);
if (allowsShadowDom) {
const correctShadowHtml = `<template shadowrootmode="${mode}"${delegatesAttr}${serializableAttr}${clonableAttr}><slot></slot></template>`;
const correctHtml = `<${elementType}>${correctShadowHtml}</${elementType}>`;
assert_equals(shadowRoot.mode,mode);
assert_equals(shadowRoot.delegatesFocus,delegatesFocus);
assert_equals(shadowRoot.serializable,expectedSerializable);
assert_equals(shadowRoot.clonable,clonable);
shadowRoot.appendChild(document.createElement('slot'));
const emptyElement = `<${elementType}></${elementType}>`;
if (isOpen) {
if (expectedSerializable) {
assert_equals(wrapper.getHTML({includeShadowRoots: true}), correctHtml);
} else {
assert_equals(wrapper.getHTML({includeShadowRoots: true}), emptyElement);
}
} else {
// Closed shadow roots should not be returned unless shadowRoots specifically contains the shadow root:
assert_equals(wrapper.getHTML({includeShadowRoots: true}), emptyElement);
assert_equals(wrapper.getHTML({includeShadowRoots: true, shadowRoots: []}), emptyElement);
}
// If we provide the shadow root, serialize it, regardless of includeShadowRoots.
assert_equals(wrapper.getHTML({includeShadowRoots: true, shadowRoots: [shadowRoot]}),correctHtml);
assert_equals(wrapper.getHTML({shadowRoots: [shadowRoot]}),correctHtml);
// This should always throw - includeShadowRoots false, but we've provided roots.
assert_throws_dom("NotSupportedError",() => wrapper.getHTML({includeShadowRoots: false, shadowRoots: [shadowRoot]}));
} else {
// For non-shadow hosts, getHTML() should also match .innerHTML
assert_equals(wrapper.getHTML({includeShadowRoots: true}),wrapper.innerHTML);
}
// Either way, make sure getHTML({includeShadowRoots: false}) matches .innerHTML
assert_equals(wrapper.getHTML({includeShadowRoots: false}),wrapper.innerHTML,'getHTML() with includeShadowRoots false should return the same as .innerHTML');
// ...and that the default for includeShadowRoots is false.
assert_equals(wrapper.getHTML(),wrapper.innerHTML,'The default for includeShadowRoots should be false');
}, `${runGetHTMLOnShadowRoot ? 'ShadowRoot' : 'Element'}.getHTML() on <${elementType}>${allowsShadowDom ? `, with ${declarativeShadowDom ? 'declarative' : 'imperative'} shadow, mode=${mode}, delegatesFocus=${delegatesFocus}, serializable=${serializable}, clonable=${clonable}.` : ''}`);
}
function runAllTests() {
const allElements = [...HTML5_ELEMENTS, 'htmlunknown'];
const safelisted = HTML5_SHADOW_ALLOWED_ELEMENTS.filter(el => el != 'body');
for (const elementName of allElements) {
const allowsShadowDom = safelisted.includes(elementName);
for (const runGetHTMLOnShadowRoot of [false, true]) {
if (allowsShadowDom) {
for (const declarativeShadowDom of [false, true]) {
for (const delegatesFocus of [false, true]) {
for (const clonable of [false, true]) {
for (const mode of ['open', 'closed']) {
for (const serializable of [undefined, 'false', 'true']) {
testElementType(true, elementName, runGetHTMLOnShadowRoot, declarativeShadowDom, mode, delegatesFocus, serializable, clonable);
}
}
}
}
}
} else {
testElementType(false, elementName, runGetHTMLOnShadowRoot);
}
}
}
}
runAllTests();
</script>