<!DOCTYPE html>
<meta charset="utf-8">
<title>Declarative Shadow DOM</title>
<link rel="author" href="mailto:[email protected]">
<link rel="help" href="https://github.com/whatwg/dom/issues/831">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src='../../html/resources/common.js'></script>
<body>
<style>
* { white-space: pre; }
iframe { display:none; }
</style>
<div id=log></div>
<div id=mainpage style="display:none">
<div class=wrapper>
<div class=host>
<template shadowrootmode=open>
<span class=content>Content</span>
</template>
</div>
</div>
</div>
<script>
const content = `
<html><body>
<div class=wrapper>
<div class=host>
<template shadowrootmode=open>
<span class=content>Content</span>
</template>
</div>
</div>
</body></html>
`;
function assert_dsd(el,shouldHaveShadow) {
const wrapper = el.querySelector('.wrapper');
assert_true(!!wrapper,'Unable to find wrapper element');
const host = wrapper.querySelector('.host');
assert_true(!!host,'Unable to find host element');
if (shouldHaveShadow) {
assert_true(!!host.shadowRoot, 'Shadow root NOT FOUND.');
assert_true(!!host.shadowRoot.querySelector('.content'),'Unable to locate content');
} else {
assert_true(!host.shadowRoot, 'Shadow root FOUND - none should be present.');
const tmpl = host.querySelector('template');
assert_true(!!tmpl, 'The template should be left as a <template> element');
assert_equals(tmpl.getAttribute('shadowrootmode'),'open','The shadowrootmode attribute should still be present');
assert_true(!!tmpl.content.querySelector('.content'),'Unable to locate content');
}
}
test(() => {
const div = document.getElementById('mainpage');
assert_dsd(div,true);
}, 'Non-fragment parsing needs no opt-in');
const noChildElements = ['iframe','noscript','script','select','style','template','textarea','title','colgroup'];
const elements = HTML5_ELEMENTS.filter(el => !noChildElements.includes(el));
for (let elementName of elements) {
var t = test(function() {
const el1 = document.createElement(elementName);
el1.innerHTML = content;
assert_dsd(el1,false);
const templateContent = `<template id=tmpl>${content}</template>`;
const el2 = document.createElement('div');
el2.innerHTML = templateContent;
assert_dsd(el2.querySelector('#tmpl').content,false);
}, `innerHTML on a <${elementName}>`);
}
test(() => {
const temp = document.createElement('template');
temp.innerHTML = content;
assert_dsd(temp.content,false, 'innerHTML should not allow declarative shadow content');
}, 'innerHTML on template');
test(() => {
const templateContent = `<template id=tmpl>${content}</template>`;
const temp = document.createElement('template');
temp.innerHTML = templateContent;
assert_dsd(temp.content.querySelector('#tmpl').content,false);
}, 'innerHTML on template, with nested template content');
test(() => {
const div = document.createElement('div');
const shadow = div.attachShadow({mode: 'open'});
shadow.innerHTML = content;
assert_dsd(shadow,false);
}, 'innerHTML on shadowRoot');
test(() => {
const parser = new DOMParser();
let fragment = parser.parseFromString(content, 'text/html');
assert_dsd(fragment.body,false);
fragment = parser.parseFromString(content, 'text/html', {includeShadowRoots: false});
assert_dsd(fragment.body,false);
fragment = parser.parseFromString(content, 'text/html', {includeShadowRoots: true});
assert_dsd(fragment.body,false);
}, 'DOMParser (includeShadowRoots is historical)');
test(() => {
const doc = document.implementation.createHTMLDocument('');
doc.body.innerHTML = content;
assert_dsd(doc.body,false);
}, 'createHTMLDocument with innerHTML - not supported');
test(() => {
const doc = document.implementation.createHTMLDocument('');
let range = doc.createRange();
range.selectNode(doc.body);
let documentFragment = range.createContextualFragment(content);
assert_dsd(documentFragment,false);
}, 'createContextualFragment - not supported');
async_test((t) => {
let client = new XMLHttpRequest();
client.addEventListener('load', t.step_func_done(() => {
assert_true(client.status == 200 && client.responseXML != null);
assert_dsd(client.responseXML.body,false);
t.done();
}));
client.open("GET", `data:text/html,${content}`);
client.responseType = 'document';
client.send();
}, 'XMLHttpRequest - not supported');
test(() => {
const div = document.createElement('div');
div.insertAdjacentHTML('afterbegin',content);
assert_dsd(div,false);
}, 'insertAdjacentHTML on element - not supported');
test(() => {
const id = 'doc-write-1';
document.write(`<div id=${id} style="display:none">${content}</div>`);
assert_dsd(document.getElementById(id),true);
}, 'document.write allowed from synchronous script loaded from main document');
test(() => {
const id = 'doc-write-2';
const doc = document.implementation.createHTMLDocument('');
doc.write(`<div id=${id}>${content}</div>`);
assert_dsd(doc.getElementById(id),false);
}, 'document.write disallowed on fresh document');
async_test((t) => {
const iframe = document.createElement('iframe');
iframe.style.display = "none";
iframe.sandbox = "allow-same-origin";
document.body.appendChild(iframe);
iframe.addEventListener('load', t.step_func_done(() => {
assert_dsd(iframe.contentDocument.body,true);
t.done();
}));
iframe.srcdoc = content;
}, 'iframe');
async_test((t) => {
const iframe = document.createElement('iframe');
iframe.style.display = "none";
document.body.appendChild(iframe);
iframe.addEventListener('load', t.step_func_done(() => {
assert_dsd(iframe.contentDocument.body,true);
t.done();
}));
iframe.srcdoc = content;
}, 'iframe, no sandbox');
function getHandler(t, name, shouldHaveShadow) {
return (e) => {
t.step(() => {
if (e.data.name == name) {
assert_false(e.data.error,e.data.msg);
assert_true(e.data.hasShadow == shouldHaveShadow);
t.done();
}
});
};
}
async_test((t) => {
window.addEventListener('message', getHandler(t, 'iframe-sandbox', true));
}, 'sandboxed iframe allows declarative Shadow DOM');
async_test((t) => {
window.addEventListener('message', getHandler(t,'iframe-no-sandbox', true));
}, 'iframe with no sandbox allows declarative Shadow DOM');
</script>
<iframe name="iframe-sandbox" sandbox="allow-scripts" src="support/declarative-child-frame.html" ></iframe>
<iframe name="iframe-no-sandbox" src="support/declarative-child-frame.html"></iframe>