summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/shadow-dom/declarative
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/shadow-dom/declarative
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/shadow-dom/declarative')
-rw-r--r--testing/web-platform/tests/shadow-dom/declarative/declarative-after-attachshadow.tentative.html46
-rw-r--r--testing/web-platform/tests/shadow-dom/declarative/declarative-parser-interaction.tentative.html39
-rw-r--r--testing/web-platform/tests/shadow-dom/declarative/declarative-shadow-dom-attachment.tentative.html98
-rw-r--r--testing/web-platform/tests/shadow-dom/declarative/declarative-shadow-dom-basic.tentative.html294
-rw-r--r--testing/web-platform/tests/shadow-dom/declarative/declarative-shadow-dom-opt-in.tentative.html198
-rw-r--r--testing/web-platform/tests/shadow-dom/declarative/declarative-with-disabled-shadow.tentative.html31
-rw-r--r--testing/web-platform/tests/shadow-dom/declarative/getinnerhtml.tentative.html79
-rw-r--r--testing/web-platform/tests/shadow-dom/declarative/innerhtml-before-closing-tag.tentative.html51
-rw-r--r--testing/web-platform/tests/shadow-dom/declarative/innerhtml-on-ordinary-template.tentative.html49
-rw-r--r--testing/web-platform/tests/shadow-dom/declarative/move-template-before-closing-tag.tentative.html91
-rw-r--r--testing/web-platform/tests/shadow-dom/declarative/script-access.tentative.html120
-rw-r--r--testing/web-platform/tests/shadow-dom/declarative/support/declarative-child-frame.html23
-rw-r--r--testing/web-platform/tests/shadow-dom/declarative/support/helpers.js4
13 files changed, 1123 insertions, 0 deletions
diff --git a/testing/web-platform/tests/shadow-dom/declarative/declarative-after-attachshadow.tentative.html b/testing/web-platform/tests/shadow-dom/declarative/declarative-after-attachshadow.tentative.html
new file mode 100644
index 0000000000..bfe2d66cfa
--- /dev/null
+++ b/testing/web-platform/tests/shadow-dom/declarative/declarative-after-attachshadow.tentative.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<title>Declarative Shadow DOM after attachShadow</title>
+<link rel='author' href='mailto:masonf@chromium.org'>
+<link rel='help' href='https://github.com/whatwg/dom/issues/831'>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+
+<body>
+<script>
+let gotHost = false;
+function myObserver(mutationsList, observer) {
+ for (let mutation of mutationsList) {
+ for (let n of mutation.addedNodes) {
+ if (n.id === 'host') {
+ gotHost = true;
+ const shadowRoot = n.attachShadow( {mode: 'closed'});
+ assert_true(!!shadowRoot, 'Unable to attach shadow imperatively');
+ }
+ }
+ }
+}
+const observer = new MutationObserver(myObserver);
+observer.observe(document.body, { childList: true, subtree: true });
+assert_false(gotHost, 'No mutations yet');
+</script>
+
+<div id=host>
+ <!-- Ensure observer runs at this point (https://github.com/web-platform-tests/wpt/issues/35393) -->
+ <script> // some content, which shouldn't be necessary </script>
+ <template shadowrootmode=open>
+ Content
+ <slot>Fallback</slot>
+ </template>
+</div>
+
+<script>
+test(t => {
+ t.add_cleanup(function() { observer.disconnect(); });
+ assert_true(gotHost);
+ let host = document.querySelector('#host');
+ let template = host.querySelector('template');
+ assert_true(!!template, 'Declarative shadow attach should fail, so template should be left over');
+ assert_true(!host.shadowRoot, 'Shadow root should be closed (from attachShadow call)');
+}, 'Declarative Shadow DOM: declarative shadow should fail if attachShadow() already called');
+</script>
+</body>
diff --git a/testing/web-platform/tests/shadow-dom/declarative/declarative-parser-interaction.tentative.html b/testing/web-platform/tests/shadow-dom/declarative/declarative-parser-interaction.tentative.html
new file mode 100644
index 0000000000..f5ff13822b
--- /dev/null
+++ b/testing/web-platform/tests/shadow-dom/declarative/declarative-parser-interaction.tentative.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Declarative Shadow DOM</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel="help" href="https://github.com/whatwg/dom/issues/831">
+<link rel="help" href="https://crbug.com/1203645">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/helpers.js"></script>
+
+
+This test should not crash, and there should be two lines of text visible below.
+<x-1>
+ <template shadowrootmode="open">
+ <style>
+ @import 'non-existent.css';
+ </style>
+ <slot></slot>
+ </template>
+ <p>Line 1</p>
+</x-1>
+
+<x-2>
+ <template shadowrootmode="open">
+ <slot></slot>
+ </template>
+ <p>Line 2</p>
+</x-2>
+
+<script>
+ window.onload = function() {
+ const x1 = document.querySelector('x-1');
+ const x2 = document.querySelector('x-2');
+ test(() => {
+ assert_true(!!x1);
+ assert_true(!!x2);
+ }, 'Declarative Shadow DOM: Test for crashes and improper parsing');
+ }
+</script>
diff --git a/testing/web-platform/tests/shadow-dom/declarative/declarative-shadow-dom-attachment.tentative.html b/testing/web-platform/tests/shadow-dom/declarative/declarative-shadow-dom-attachment.tentative.html
new file mode 100644
index 0000000000..d752b62d31
--- /dev/null
+++ b/testing/web-platform/tests/shadow-dom/declarative/declarative-shadow-dom-attachment.tentative.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<title>Declarative Shadow DOM Element Attachment</title>
+<link rel='author' href='mailto:masonf@chromium.org'>
+<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>
+<script src="support/helpers.js"></script>
+
+<script>
+const shadowContent = '<span>Shadow tree</span><slot></slot>';
+function getDeclarativeContent(mode, delegatesFocus) {
+ const delegatesFocusText = delegatesFocus ? ' shadowrootdelegatesfocus' : '';
+ return `<template shadowrootmode=${mode}${delegatesFocusText}>${shadowContent}</template>`;
+}
+
+const lightDomTextContent = 'Light DOM';
+function addDeclarativeShadowRoot(elementType, mode, delegatesFocus) {
+ const declarativeString = `<${elementType} id=theelement>${getDeclarativeContent(mode, delegatesFocus)}
+ <span class='lightdom'>${lightDomTextContent}</span></${elementType}>`;
+ const wrapper = document.createElement('div');
+ setInnerHTML(wrapper, declarativeString);
+ const element = wrapper.querySelector('#theelement');
+ return {wrapper: wrapper, element: element};
+}
+
+function testElementType(allowed, nochildren, elementType, mode, delegatesFocus) {
+ var t = test(function() {
+ const nodes = addDeclarativeShadowRoot(elementType, mode, delegatesFocus);
+ if (allowed) {
+ const element = nodes.element;
+ assert_true(!!element, 'Unable to locate the element');
+ // Just one light DOM child, and no leftover template.
+ assert_true(!nodes.wrapper.querySelector('template'));
+ assert_equals(element.children.length, 1);
+ assert_equals(element.children[0].textContent, lightDomTextContent);
+ let originalShadowRoot = null;
+ if (mode === 'open') {
+ assert_true(!!element.shadowRoot, 'Shadow root should be present');
+ assert_equals(element.shadowRoot.innerHTML, shadowContent, 'Correct shadow content');
+ assert_equals(element.shadowRoot.delegatesFocus,delegatesFocus,'Correct delegatesFocus')
+ originalShadowRoot = element.shadowRoot;
+ }
+
+ // Now, call attachShadow() and make sure we get back the same (original) shadowRoot, but empty.
+ const oppositeMode = (mode === 'open') ? 'closed' : 'open';
+ const newShadow = element.attachShadow({mode: oppositeMode}); // Should be no exception here
+ if (mode === 'open') {
+ assert_equals(element.shadowRoot, originalShadowRoot, 'The same shadow root should be returned');
+ assert_equals(element.shadowRoot.innerHTML, '', 'Empty shadow content');
+ assert_equals(element.shadowRoot.mode, mode, 'Original shadow mode');
+ }
+
+ assert_throws_dom('NotSupportedError', () => {
+ element.attachShadow({mode: mode});
+ }, 'Calling attachShadow a second time on an element with a declarative shadow fails (same mode)');
+
+ assert_throws_dom('NotSupportedError', () => {
+ element.attachShadow({mode: oppositeMode});
+ }, 'Calling attachShadow a second time on an element with a declarative shadow fails (opposite mode)');
+ } else {
+ if (!nochildren) {
+ // Invalid elements should retain a <template> element child with a shadowroot attribute.
+ const template = nodes.wrapper.querySelector('template[shadowrootmode]');
+ assert_true(!!template);
+ assert_equals(template.getAttribute('shadowrootmode'), mode, `Template with shadowrootmode=${mode} should be left over`);
+ const span = nodes.wrapper.querySelector('span.lightdom');
+ assert_true(!!span);
+ assert_equals(span.textContent, lightDomTextContent);
+ if (nodes.element) {
+ // For some tags (e.g. <html>) there won't be an element inside wrapper.
+ assert_true(!nodes.element.shadowRoot, 'Shadow root should not be present');
+ }
+ }
+ }
+ }, `Declarative Shadow DOM as a child of <${elementType}>, with mode=${mode}, delegatesFocus=${delegatesFocus}. Should be ${allowed ? 'safelisted' : 'disallowed'}.`);
+}
+
+function runAllTests() {
+ const noCheck = ['body', 'template'];
+ const safelisted = HTML5_SHADOW_ALLOWED_ELEMENTS.filter(el => !noCheck.includes(el));
+ const disallowed = HTML5_SHADOW_DISALLOWED_ELEMENTS.filter(el => !noCheck.includes(el));
+ const noChildElements = ['iframe','noscript','script','select','style','textarea','title'];
+ for (let delegatesFocus of [false, true]) {
+ for (let mode of ['open', 'closed', 'invalid']) {
+ for (let elementName of safelisted) {
+ testElementType(mode !== 'invalid', false, elementName, mode, delegatesFocus);
+ }
+ for (let elementName of disallowed) {
+ testElementType(false, noChildElements.includes(elementName), elementName, mode, delegatesFocus);
+ }
+ }
+ }
+}
+
+runAllTests();
+
+</script>
diff --git a/testing/web-platform/tests/shadow-dom/declarative/declarative-shadow-dom-basic.tentative.html b/testing/web-platform/tests/shadow-dom/declarative/declarative-shadow-dom-basic.tentative.html
new file mode 100644
index 0000000000..b71f7d1a37
--- /dev/null
+++ b/testing/web-platform/tests/shadow-dom/declarative/declarative-shadow-dom-basic.tentative.html
@@ -0,0 +1,294 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Declarative Shadow DOM</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<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="support/helpers.js"></script>
+
+<div id="host" style="display:none">
+ <template shadowrootmode="open">
+ <slot id="s1" name="slot1"></slot>
+ </template>
+ <div id="c1" slot="slot1"></div>
+</div>
+
+<script>
+test(() => {
+ const host = document.querySelector('#host');
+ const c1 = host.querySelector('#c1');
+ assert_true(!!c1);
+ assert_equals(host.querySelector('#s1'), null, "Should be inside shadow root");
+ assert_equals(host.querySelector('template'), null, "No leftover template node");
+ assert_true(!!host.shadowRoot,"No shadow root found");
+ const s1 = host.shadowRoot.querySelector('#s1');
+ assert_equals(c1.assignedSlot, s1);
+ assert_array_equals(s1.assignedNodes(), [c1]);
+}, 'Declarative Shadow DOM: Basic test');
+
+test(() => {
+ assert_true(HTMLTemplateElement.prototype.hasOwnProperty("shadowRootMode"),'Unable to feature detect');
+}, 'Declarative Shadow DOM: Feature detection');
+
+test(() => {
+ const t = document.createElement('template');
+ t.setAttribute('shadowrootmode','open');
+ assert_equals(t.shadowRootMode,'open','The shadowRootMode IDL should reflect the content attribute');
+ t.setAttribute('shadowrootmode','closed');
+ assert_equals(t.shadowRootMode,'closed','"open" and "closed" should both be valid values');
+ t.setAttribute('shadowrootmode','OpEn');
+ assert_equals(t.shadowRootMode,'open','Case insensitive');
+ t.setAttribute('shadowrootmode','INVALID');
+ assert_equals(t.shadowRootMode,null,'Invalid values map to null');
+ t.removeAttribute('shadowrootmode');
+ assert_equals(t.shadowRootMode,null,'No shadowrootmode attribute maps to null');
+}, 'Shadowrootmode reflection');
+
+test(() => {
+ const div = document.createElement('div');
+ setInnerHTML(div,`
+ <div id="host">
+ <template shadowrootmode="open">
+ <slot id="s1" name="slot1"></slot>
+ </template>
+ <div id="c1" slot="slot1"></div>
+ </div>
+ `);
+ const host = div.querySelector('#host');
+ const c1 = host.querySelector('#c1');
+ assert_true(!!c1);
+ assert_equals(host.querySelector('#s1'), null, "Should be inside shadow root");
+ assert_equals(host.querySelector('template'), null, "No leftover template node");
+ assert_true(!!host.shadowRoot,"No shadow root found");
+ const s1 = host.shadowRoot.querySelector('#s1');
+ assert_equals(c1.assignedSlot, s1);
+ assert_array_equals(s1.assignedNodes(), [c1]);
+}, 'Declarative Shadow DOM: Fragment parser basic test');
+
+test(() => {
+ const div = document.createElement('div');
+ setInnerHTML(div,`
+ <div id="host">
+ <template shadowrootmode="invalid">
+ </template>
+ </div>
+ `);
+ const host = div.querySelector('#host');
+ assert_equals(host.shadowRoot, null, "Shadow root was found");
+ const tmpl = host.querySelector('template');
+ assert_true(!!tmpl,"Template should still be present");
+ const shadowrootAttr = tmpl.getAttribute('shadowrootmode');
+ assert_equals(shadowrootAttr,"invalid","'shadowrootmode' attribute should still be present");
+}, 'Declarative Shadow DOM: Invalid shadow root attribute');
+
+test(() => {
+ const div = document.createElement('div');
+ setInnerHTML(div,`
+ <div id="host">
+ <template shadowrootmode="closed">
+ </template>
+ </div>
+ `);
+ const host = div.querySelector('#host');
+ assert_equals(host.shadowRoot, null, "Closed shadow root");
+ assert_equals(host.querySelector('template'), null, "No template - converted to shadow root");
+}, 'Declarative Shadow DOM: Closed shadow root attribute');
+
+test(() => {
+ const div = document.createElement('div');
+ setInnerHTML(div,`
+ <div id="host">
+ <template shadowrootmode="open">
+ <slot id="s1" name="slot1"></slot>
+ </div>
+ `);
+ const host = div.querySelector('#host');
+ assert_equals(host.querySelector('#s1'), null, "Should be inside shadow root");
+ assert_equals(host.querySelector('template'), null, "No leftover template node");
+ assert_true(!!host.shadowRoot,"No shadow root found");
+ const s1 = host.shadowRoot.querySelector('#s1');
+ assert_true(!!s1,"Slot should be inside the shadow root");
+}, 'Declarative Shadow DOM: Missing closing tag');
+
+test(() => {
+ const div = document.createElement('div');
+ setInnerHTML(div,`
+ <div id="host">
+ <template shadowrootmode="open" shadowrootdelegatesfocus>
+ </template>
+ </div>
+ `);
+ var host = div.querySelector('#host');
+ assert_true(!!host.shadowRoot,"No shadow root found");
+ assert_true(host.shadowRoot.delegatesFocus,"delegatesFocus should be true");
+ setInnerHTML(div,`
+ <div id="host">
+ <template shadowrootmode="open">
+ </template>
+ </div>
+ `);
+ host = div.querySelector('#host');
+ assert_true(!!host.shadowRoot,"No shadow root found");
+ assert_false(host.shadowRoot.delegatesFocus,"delegatesFocus should be false without the shadowrootdelegatesfocus attribute");
+}, 'Declarative Shadow DOM: delegates focus attribute');
+</script>
+
+<div id="multi-host" style="display:none">
+ <template shadowrootmode="open">
+ <span>root 1</span>
+ </template>
+ <template shadowrootmode="closed">
+ <span>root 2</span>
+ </template>
+</div>
+<script>
+test(() => {
+ const host = document.querySelector('#multi-host');
+ assert_equals(host.querySelector('template'), null, "No leftover template nodes from either root");
+ assert_true(!!host.shadowRoot,"No open shadow root found - first root should remain");
+ const innerSpan = host.shadowRoot.querySelector('span');
+ assert_equals(innerSpan.textContent, 'root 2', "Content should come from last declarative shadow root");
+}, 'Declarative Shadow DOM: Multiple roots');
+
+</script>
+
+<template id="template-containing-shadow">
+ <div class="innerdiv">
+ <template shadowrootmode=open>Content</template>
+ </div>
+</template>
+<script>
+test(() => {
+ const template = document.querySelector('#template-containing-shadow');
+ const container1 = document.createElement('div');
+ container1.style.display = 'none';
+ document.body.appendChild(container1);
+ container1.appendChild(template.content.cloneNode(true));
+ let innerDiv = container1.querySelector('div.innerdiv');
+ const shadowRoot1 = innerDiv.shadowRoot;
+ assert_true(!!shadowRoot1,"Inner div should have a shadow root");
+ assert_equals(innerDiv.querySelector('template'), null, "No leftover template node");
+
+ const container2 = document.createElement('div');
+ container2.style.display = 'none';
+ document.body.appendChild(container2);
+ container2.appendChild(template.content.cloneNode(true));
+ innerDiv = container2.querySelector('div.innerdiv');
+ const shadowRoot2 = innerDiv.shadowRoot;
+ assert_true(!!shadowRoot2,"Inner div should have a shadow root");
+ assert_equals(innerDiv.querySelector('template'), null, "No leftover template node");
+
+ assert_not_equals(shadowRoot1,shadowRoot2,'Should not get back the same shadow root');
+
+ // Make sure importNode also works.
+ const container3 = document.createElement('div');
+ container3.style.display = 'none';
+ document.body.appendChild(container3);
+ container3.appendChild(document.importNode(template.content,true));
+ innerDiv = container3.querySelector('div.innerdiv');
+ const shadowRoot3 = innerDiv.shadowRoot;
+ assert_true(!!shadowRoot3,"Inner div should have a shadow root");
+ assert_equals(innerDiv.querySelector('template'), null, "No leftover template node");
+ assert_not_equals(shadowRoot1,shadowRoot3,'Should not get back the same shadow root');
+
+}, 'Declarative Shadow DOM: template containing declarative shadow root');
+</script>
+
+<template id="template-containing-deep-shadow">
+ <div><div><div><div><div>
+ <div class="innerdiv">
+ <template shadowrootmode=open>Content</template>
+ </div>
+ </div></div></div></div></div>
+</template>
+<script>
+test(() => {
+ const template = document.querySelector('#template-containing-deep-shadow');
+ const host = document.createElement('div');
+ host.style.display = 'none';
+ document.body.appendChild(host);
+ host.appendChild(template.content.cloneNode(true));
+ assert_true(!!host.querySelector('div.innerdiv').shadowRoot,"Inner div should have a shadow root");
+}, 'Declarative Shadow DOM: template containing (deeply nested) declarative shadow root');
+</script>
+
+<template id="template-containing-template">
+ <div>
+ <template id="inner-template">
+ <div class="innerdiv">
+ <template shadowrootmode=open>Content</template>
+ </div>
+ </template>
+ </div>
+</template>
+<script>
+test(() => {
+ const template = document.querySelector('#template-containing-template');
+ const host = document.createElement('div');
+ host.style.display = 'none';
+ document.body.appendChild(host);
+ host.appendChild(template.content.cloneNode(true));
+ const innerTemplate = host.querySelector('#inner-template');
+ assert_true(!!innerTemplate.content.querySelector('div.innerdiv').shadowRoot,"Inner div should have a shadow root");
+}, 'Declarative Shadow DOM: template containing a template containing declarative shadow root');
+</script>
+
+<template id="template-containing-ua-shadow">
+ <div class="innerdiv">
+ <template shadowrootmode=open>
+ <video></video> <!--Assumed to have UA shadow root-->
+ </template>
+ </div>
+</template>
+<script>
+test(() => {
+ const template = document.querySelector('#template-containing-ua-shadow');
+ const host = document.createElement('div');
+ host.style.display = 'none';
+ document.body.appendChild(host);
+ // Mostly make sure clone of template *does* clone the
+ // shadow root, and doesn't crash on cloning the <video>.
+ host.appendChild(template.content.cloneNode(true));
+ let innerDiv = host.querySelector('div.innerdiv');
+ const shadowRoot = innerDiv.shadowRoot;
+ assert_true(!!shadowRoot,"Inner div should have a shadow root");
+ assert_equals(innerDiv.querySelector('template'), null, "No leftover template node");
+ assert_true(!!innerDiv.shadowRoot.querySelector('video'),'Video element should be present');
+}, 'Declarative Shadow DOM: template containing declarative shadow root and UA shadow root');
+</script>
+
+<template id="template-containing-ua-shadow-closed">
+ <div class="innerdiv">
+ <template shadowrootmode=closed>
+ <video></video> <!--Assumed to have UA shadow root-->
+ </template>
+ </div>
+</template>
+<script>
+test(() => {
+ const template = document.querySelector('#template-containing-ua-shadow-closed');
+ const host = document.createElement('div');
+ host.style.display = 'none';
+ document.body.appendChild(host);
+ host.appendChild(template.content.cloneNode(true));
+ let innerDiv = host.querySelector('div.innerdiv');
+ assert_true(!innerDiv.shadowRoot,"Inner div should have a closed shadow root");
+}, 'Declarative Shadow DOM: template containing closed declarative shadow root and UA shadow root');
+</script>
+
+<template id="root-element-shadow">
+ <template shadowrootmode=open>Content</template>
+</template>
+<script>
+test(() => {
+ // Root element of this template is a <template shadowroot>:
+ const template = document.querySelector('#root-element-shadow');
+ const host = document.createElement('div');
+ host.appendChild(template.content.cloneNode(true));
+ assert_equals(host.shadowRoot, null, "Shadow root should not be present");
+ const tmpl = host.querySelector('template');
+ assert_true(!!tmpl,"Template should still be present");
+ assert_equals(tmpl.getAttribute('shadowrootmode'),"open","'shadowrootmode' attribute should still be present");
+}, 'Declarative Shadow DOM: template root element');
+</script>
diff --git a/testing/web-platform/tests/shadow-dom/declarative/declarative-shadow-dom-opt-in.tentative.html b/testing/web-platform/tests/shadow-dom/declarative/declarative-shadow-dom-opt-in.tentative.html
new file mode 100644
index 0000000000..0a59768d22
--- /dev/null
+++ b/testing/web-platform/tests/shadow-dom/declarative/declarative-shadow-dom-opt-in.tentative.html
@@ -0,0 +1,198 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Declarative Shadow DOM</title>
+<link rel="author" href="mailto:masonf@chromium.org">
+<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,true);
+}, 'DOMParser');
+
+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>
diff --git a/testing/web-platform/tests/shadow-dom/declarative/declarative-with-disabled-shadow.tentative.html b/testing/web-platform/tests/shadow-dom/declarative/declarative-with-disabled-shadow.tentative.html
new file mode 100644
index 0000000000..849bffa5a7
--- /dev/null
+++ b/testing/web-platform/tests/shadow-dom/declarative/declarative-with-disabled-shadow.tentative.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>Declarative Shadow DOM with shadow disabled</title>
+<link rel='author' href='mailto:masonf@chromium.org'>
+<link rel='help' href='https://github.com/whatwg/dom/issues/831'>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+
+<body>
+<script>
+class ShadowDisabledElement extends HTMLElement {
+ static get disabledFeatures() {
+ return ['shadow'];
+ }
+}
+customElements.define('shadow-disabled',ShadowDisabledElement);
+</script>
+
+<shadow-disabled>
+ <template shadowrootmode="open"><span id=inside/></template>
+</shadow-disabled>
+
+<script>
+test(t => {
+ let element = document.querySelector('shadow-disabled');
+ assert_true(element instanceof ShadowDisabledElement);
+ let template = element.querySelector('template');
+ assert_true(!!template, 'Declarative shadow attach should fail, since shadow-disabled is true');
+ assert_true(!element.shadowRoot, 'Shadow root should not be present on custom element');
+}, 'Declarative Shadow DOM: declarative shadow should fail if attachShadow() already called');
+</script>
+</body>
diff --git a/testing/web-platform/tests/shadow-dom/declarative/getinnerhtml.tentative.html b/testing/web-platform/tests/shadow-dom/declarative/getinnerhtml.tentative.html
new file mode 100644
index 0000000000..ad7734d51e
--- /dev/null
+++ b/testing/web-platform/tests/shadow-dom/declarative/getinnerhtml.tentative.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<title>getInnerHTML </title>
+<link rel='author' href='mailto:masonf@chromium.org'>
+<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>
+
+<script>
+function testElementType(allowsShadowDom, elementType, applyToShadow, mode, delegatesFocus) {
+ const t = test(t => {
+ // Create and attach element
+ let wrapper;
+ if (applyToShadow) {
+ 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);
+ }
+ const element = document.createElement(elementType);
+ wrapper.appendChild(element);
+
+ const isOpen = mode === 'open';
+ if (allowsShadowDom) {
+ const delegatesAttr = delegatesFocus ? ' shadowrootdelegatesfocus=""' : '';
+ const correctShadowHtml = `<template shadowrootmode="${mode}"${delegatesAttr}><slot></slot></template>`;
+ const correctHtml = `<${elementType}>${correctShadowHtml}</${elementType}>`;
+ const shadowRoot = element.attachShadow({mode: mode, delegatesFocus: delegatesFocus});
+ shadowRoot.appendChild(document.createElement('slot'));
+ if (isOpen) {
+ // We can only test this for open roots
+ assert_equals(wrapper.getInnerHTML(),correctHtml,'The default for includeShadowRoots should be true');
+ } else {
+ // Closed shadow roots should not be returned unless closedRoots contains the shadow root:
+ const emptyElement = `<${elementType}></${elementType}>`;
+ assert_equals(wrapper.getInnerHTML({includeShadowRoots: true}), emptyElement);
+ assert_equals(wrapper.getInnerHTML({includeShadowRoots: true, closedRoots: []}), emptyElement);
+ }
+ assert_equals(wrapper.getInnerHTML({includeShadowRoots: true, closedRoots: [shadowRoot]}),correctHtml);
+ } else {
+ // For non-shadow hosts, getInnerHTML() should also match .innerHTML
+ assert_equals(wrapper.getInnerHTML({includeShadowRoots: true}),wrapper.innerHTML);
+ assert_equals(wrapper.getInnerHTML(),wrapper.innerHTML);
+ }
+
+ // Either way, make sure getInnerHTML({includeShadowRoots: false}) matches .innerHTML
+ assert_equals(wrapper.getInnerHTML({includeShadowRoots: false}),wrapper.innerHTML,'getInnerHTML() with includeShadowRoots false should return the same as .innerHTML');
+
+ }, `${applyToShadow ? 'ShadowRoot' : 'Element'}.getInnerHTML() on <${elementType}>${allowsShadowDom ? `, with mode=${mode}, delegatesFocus=${delegatesFocus}.` : ''}`);
+}
+
+function runAllTests() {
+ const allElements = [...HTML5_ELEMENTS, 'htmlunknown'];
+ const safelisted = HTML5_SHADOW_ALLOWED_ELEMENTS;
+ for (const elementName of allElements) {
+ const allowsShadowDom = safelisted.includes(elementName);
+ for (const applyToShadow of [false, true]) {
+ if (allowsShadowDom) {
+ for (const delegatesFocus of [false, true]) {
+ for (const mode of ['open', 'closed']) {
+ testElementType(true, elementName, applyToShadow, mode, delegatesFocus);
+ }
+ }
+ } else {
+ testElementType(false, elementName, applyToShadow);
+ }
+ }
+ }
+}
+
+runAllTests();
+
+</script>
diff --git a/testing/web-platform/tests/shadow-dom/declarative/innerhtml-before-closing-tag.tentative.html b/testing/web-platform/tests/shadow-dom/declarative/innerhtml-before-closing-tag.tentative.html
new file mode 100644
index 0000000000..e87c425860
--- /dev/null
+++ b/testing/web-platform/tests/shadow-dom/declarative/innerhtml-before-closing-tag.tentative.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<title>Declarative Shadow DOM innerHTML</title>
+<link rel='author' href='mailto:masonf@chromium.org'>
+<link rel='help' href='https://github.com/whatwg/dom/issues/831'>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+
+<body>
+<script>
+function myObserver(mutationsList) {
+ for (let mutation of mutationsList) {
+ if (Array.from(mutation.addedNodes).map(n=>n.id).includes('shadow')) {
+ assert_unreached('For streaming declarative Shadow DOM, the <template> element should never get added to the tree');
+ }
+ }
+ const host = document.querySelector('#host');
+ if (!host)
+ return;
+ const shadow = host.shadowRoot;
+ if (!shadow)
+ return;
+ const replaceNode = shadow.querySelector('#toreplace');
+ assert_true(!!replaceNode);
+ replaceNode.innerHTML = "<span id=newcontent>This should be in &lt;div>'s shadow root</span>";
+ observer.disconnect();
+}
+var observer = new MutationObserver(myObserver);
+observer.observe(document.body, { childList: true, subtree: true });
+</script>
+
+<div id=host>
+ <template id=shadow shadowrootmode=open>
+ <span id=toreplace><span id=oldcontent>This should get removed</span></span>
+ <!-- Ensure observer runs at this point (https://github.com/web-platform-tests/wpt/issues/35393) -->
+ <script> // some content, which shouldn't be necessary </script>
+ </template>
+</div>
+
+<script>
+test(t => {
+ t.add_cleanup(function() { observer.disconnect(); });
+ let host = document.querySelector('#host');
+ let template = host.querySelector('template');
+ assert_true(!template, 'Declarative shadow template should not be present');
+ let shadowroot = host.shadowRoot;
+ assert_true(!!shadowroot, 'Shadow root should be present');
+ assert_true(!!shadowroot.querySelector('#newcontent'),'The innerHTML replacement content should be present');
+ assert_true(!shadowroot.querySelector('#oldcontent'),'The old replaced content should not be present');
+}, 'Declarative Shadow DOM: innerHTML should work while parsing STREAMING declarative shadow root <template>');
+</script>
+</body>
diff --git a/testing/web-platform/tests/shadow-dom/declarative/innerhtml-on-ordinary-template.tentative.html b/testing/web-platform/tests/shadow-dom/declarative/innerhtml-on-ordinary-template.tentative.html
new file mode 100644
index 0000000000..87260013de
--- /dev/null
+++ b/testing/web-platform/tests/shadow-dom/declarative/innerhtml-on-ordinary-template.tentative.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<title>Declarative Shadow DOM innerHTML</title>
+<link rel='author' href='mailto:masonf@chromium.org'>
+<link rel='help' href='https://github.com/whatwg/dom/issues/831'>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+
+<body>
+<script>
+function myObserver(mutationsList) {
+ for (let mutation of mutationsList) {
+ for (let n of mutation.addedNodes) {
+ if (n.id === 'has-imperative-root') {
+ const shadowRoot = n.attachShadow( {mode: 'open'});
+ }
+ }
+ }
+}
+var observer = new MutationObserver(myObserver);
+observer.observe(document.body, { childList: true, subtree: true });
+</script>
+
+<div id='has-imperative-root'>
+ <!-- Ensure observer runs at this point (https://github.com/web-platform-tests/wpt/issues/35393) -->
+ <script> // some content, which shouldn't be necessary </script>
+ <!-- The imperative shadow root should be attached now. -->
+ <template id=ordinarytemplate shadowrootmode=open>
+ <span id=toreplace>This should get removed</span>
+ </template>
+ <script>
+ ordinarytemplate.innerHTML = '<span id=replaced>This should replace template contents</span>';
+ </script>
+</div>
+
+<script>
+test(t => {
+ t.add_cleanup(function() { observer.disconnect(); });
+ let host = document.querySelector('#has-imperative-root');
+ let shadowroot = host.shadowRoot;
+ assert_true(!!shadowroot, 'Shadow root should be present');
+ assert_false(shadowroot.hasChildNodes(), 'Shadow root should not contain any children');
+ let template = host.querySelector('template#ordinarytemplate');
+ assert_true(!!template, 'Since host has imperative root, <template> should be left over');
+ assert_false(template.hasChildNodes(),'template should not have direct children');
+ assert_true(!!template.content.querySelector('#replaced'),'The innerHTML replacement content should be present');
+ assert_true(!template.content.querySelector('#toreplace'),'The old replaced content should not be present');
+}, 'Declarative Shadow DOM: innerHTML should work on <template shadowroot> that is left over');
+</script>
+</body>
diff --git a/testing/web-platform/tests/shadow-dom/declarative/move-template-before-closing-tag.tentative.html b/testing/web-platform/tests/shadow-dom/declarative/move-template-before-closing-tag.tentative.html
new file mode 100644
index 0000000000..a3d7806f5d
--- /dev/null
+++ b/testing/web-platform/tests/shadow-dom/declarative/move-template-before-closing-tag.tentative.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<title>Declarative Shadow DOM: moving the template doesn't change attachment point</title>
+<link rel='author' href='mailto:masonf@chromium.org'>
+<link rel='help' href='https://github.com/whatwg/dom/issues/831'>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+
+<body>
+<script>
+function myObserver(mutationsList, observer) {
+ for (let mutation of mutationsList) {
+ for (let n of mutation.addedNodes) {
+ if (n.className === 'template' && !n.alreadyMoved) {
+ n.alreadyMoved = true;
+ let testWrapper = n.parentElement.parentElement;
+ let newHost = testWrapper.querySelector('div.new-host');
+ newHost.appendChild(n);
+ }
+ }
+ }
+}
+(new MutationObserver(myObserver)).observe(document.body, { childList: true, subtree: true });
+
+function runTest(testdiv) {
+ let description = testdiv.getAttribute('description');
+ let shouldAttach = testdiv.getAttribute('shouldattach') == 'true';
+ test(t => {
+ let template = testdiv.querySelector('.template');
+ let oldHost = testdiv.querySelector('.old-host');
+ let newHost = testdiv.querySelector('.new-host');
+ let shadowroot = oldHost.shadowRoot;
+ if (shouldAttach) {
+ assert_true(!template, 'Declarative shadow template should not be left over');
+ assert_true(!oldHost.children.length, 'Old host should not contain children');
+ assert_true(!newHost.children.length, 'New host should not contain any children');
+ assert_true(!!shadowroot, 'Shadow root should be present');
+ assert_true(!!shadowroot.querySelector('span.contents'), 'The shadow root should contain a span');
+ } else {
+ assert_true(!oldHost.children.length, 'Old host should not contain children');
+ assert_true(!!newHost.children.length, 'New host should now contain the template');
+ assert_true(!shadowroot, 'Shadow root should not be present');
+ assert_true(!!template, 'Declarative shadow template should be left over');
+ assert_equals(template, newHost.querySelector('.template'), 'Declarative shadow template should be in the new host');
+ assert_true(!!template.content.querySelector('span.contents'), 'The template should still contain its content');
+ }
+ }, description);
+}
+</script>
+
+<style>
+ div.test {
+ display: none;
+ }
+</style>
+
+<div class=test shouldattach=true description="Moving the template node during parsing should attach to initial parent (content before observer)">
+ <div class=new-host></div>
+ <div class=old-host>
+ <template class='template' shadowrootmode=open>
+ <span class=contents>Template contents</span>
+ <script></script> <!-- Observer runs here, moves <template> node -->
+ </template>
+ </div>
+</div>
+
+<div class=test shouldattach=true description="Moving the template node during parsing should attach to initial parent (content after observer)">
+ <div class=new-host></div>
+ <div class=old-host>
+ <template class='template' shadowrootmode=open>
+ <script></script> <!-- Observer runs here, moves <template> node -->
+ <span class=contents>Template contents</span>
+ </template>
+ </div>
+</div>
+
+<div class=test shouldattach=false description="Moving the template node from invalid parent to valid parent should still not attach">
+ <div class=new-host></div>
+ <video class=old-host>
+ <template class='template' shadowrootmode=open>
+ <span class=contents>Template contents</span>
+ <script></script> <!-- Observer runs here, moves <template> node -->
+ </template>
+ </video>
+</div>
+
+<script>
+ let tests = document.querySelectorAll('div.test');
+ assert_true(tests.length > 0);
+ tests.forEach(t => { runTest(t); });
+</script>
+</body>
diff --git a/testing/web-platform/tests/shadow-dom/declarative/script-access.tentative.html b/testing/web-platform/tests/shadow-dom/declarative/script-access.tentative.html
new file mode 100644
index 0000000000..6c927b1bd7
--- /dev/null
+++ b/testing/web-platform/tests/shadow-dom/declarative/script-access.tentative.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<title>Declarative Shadow DOM</title>
+<link rel='author' href='mailto:masonf@chromium.org'>
+<link rel='help' href='https://github.com/whatwg/dom/issues/831'>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+
+<body>
+<script>
+let templatesSeen = 0;
+
+function myObserver(mutationsList, observer) {
+ document.querySelectorAll('.host').forEach(n => {
+ templatesSeen++;
+ n.className = 'done';
+ switch (n.id) {
+ case 'openhost':
+ case 'closedhost':
+ assert_equals(n.querySelector('template'),null,'No template ever for streaming declarative Shadow DOM');
+ assert_equals(n.innerHTML, "", 'Declarative shadow host innerHTML should be empty - all content is inside shadow');
+ break;
+ case 'noroot':
+ const template = n.querySelector('template');
+ assert_true(!!template,'Regular template should still be present');
+ // Make sure adding 'shadowrootmode' attribute doesn't trigger a shadow root,
+ // even if added before parsing completes.
+ template.setAttribute('shadowrootmode','open');
+ assert_not_equals(template.content, null, 'Regular template should have content, even after adding shadowrootmode attribute');
+ assert_not_equals(template.innerHTML, "", 'Regular template should have innerHTML, even after adding shadowrootmode attribute');
+ assert_not_equals(template.getInnerHTML({includeShadowRoots: true}), "", 'Regular template should have getInnerHTML(), even after adding shadowrootmode attribute');
+ break;
+ default:
+ assert_unreached('Unrecognized template');
+ }
+ });
+}
+const observer = new MutationObserver(myObserver);
+observer.observe(document.body, { childList: true, subtree: true });
+assert_equals(templatesSeen, 0, 'No mutations yet');
+</script>
+
+<div id=openhost class=host><template shadowrootmode=open>
+ <slot></slot>
+ <!-- Ensure observer runs at this point (https://github.com/web-platform-tests/wpt/issues/35393) -->
+ <script> // some content, which shouldn't be necessary </script>
+</template></div>
+
+<div id=closedhost class=host><template shadowrootmode=closed>
+ <slot></slot>
+ <!-- Ensure observer runs at this point (https://github.com/web-platform-tests/wpt/issues/35393) -->
+ <script> // some content, which shouldn't be necessary </script>
+</template></div>
+
+<div id=noroot class=host><template>
+ <slot></slot>
+ <!-- Ensure observer runs at this point (https://github.com/web-platform-tests/wpt/issues/35393) -->
+ <script> // some content, which shouldn't be necessary </script>
+</template></div>
+
+<script>
+test(t => {
+ t.add_cleanup(function() { observer.disconnect(); });
+
+ assert_equals(templatesSeen, 3);
+
+ // Open shadow root
+ let host = document.querySelector('#openhost');
+ assert_equals(host.querySelector('template'), null, 'No template nodes');
+ assert_true(!!host.shadowRoot, 'Shadow root should exist');
+
+ // Closed shadow root
+ host = document.querySelector('#closedhost');
+ assert_equals(host.querySelector('template'), null, 'No template nodes');
+ assert_true(!host.shadowRoot, 'Closed shadow root (can\'t detect)');
+
+ // No shadow root
+ host = document.querySelector('#noroot');
+ assert_true(!!host.querySelector('template'), 'Template node still present for invalid shadowrootmode value');
+ assert_true(!host.shadowRoot, 'No shadow root');
+},'Streaming Declarative Shadow DOM: template .content() should be null');
+</script>
+
+
+<script>
+let synchronous_events_received = new Set();
+function synchronousHandler(e) {
+ synchronous_events_received.add(e.type);
+}
+const sync_events = ["DOMAttrModified","DOMAttributeNameChanged","DOMCharacterDataModified",
+ "DOMElementNameChanged","DOMNodeInserted","DOMNodeInsertedIntoDocument","DOMNodeRemoved",
+ "DOMNodeRemovedFromDocument","DOMSubtreeModified"];
+function addSyncObserver(evt) {
+ window.addEventListener(evt, synchronousHandler, true);
+}
+function removeSyncObserver(evt) {
+ window.removeEventListener(evt, synchronousHandler, true);
+}
+sync_events.forEach(e => addSyncObserver(e));
+</script>
+
+<div id=synchost1>
+ <template shadowrootmode=open>
+ <div class=foo>content</div>
+ <slot></slot>
+ </template>
+</div>
+
+<div id=synchost2>
+ <template shadowrootmode=closed>
+ <div class=foo>content</div>
+ <slot></slot>
+ </template>
+</div>
+
+<script>
+test(t => {
+ t.add_cleanup(function() { sync_events.forEach(e => removeSyncObserver(e)) });
+ assert_true(!synchronous_events_received.size,`Synchronous mutation events fired: ${Array.from(synchronous_events_received)}`);
+},'Synchronous mutation events should not be fired during streaming declarative shadow DOM parsing');
+</script>
diff --git a/testing/web-platform/tests/shadow-dom/declarative/support/declarative-child-frame.html b/testing/web-platform/tests/shadow-dom/declarative/support/declarative-child-frame.html
new file mode 100644
index 0000000000..603c47743b
--- /dev/null
+++ b/testing/web-platform/tests/shadow-dom/declarative/support/declarative-child-frame.html
@@ -0,0 +1,23 @@
+<div id=iframe>
+ <div id=wrapper>
+ <div id=host>
+ <template shadowrootmode=open>
+ <span id=content>Content</span>
+ </template>
+ </div>
+ </div>
+</div>
+
+<script>
+function sendStatus(error, hasShadow, msg) {
+ const name = window.name;
+ parent.postMessage({ name, error, hasShadow, msg }, '*');
+}
+
+window.addEventListener('load', () => {
+ const host = document.querySelector('#host');
+ if (!host)
+ return sendStatus(true, false, 'Unable to find host element');
+ return sendStatus(false, !!host.shadowRoot);
+});
+</script>
diff --git a/testing/web-platform/tests/shadow-dom/declarative/support/helpers.js b/testing/web-platform/tests/shadow-dom/declarative/support/helpers.js
new file mode 100644
index 0000000000..0be3add620
--- /dev/null
+++ b/testing/web-platform/tests/shadow-dom/declarative/support/helpers.js
@@ -0,0 +1,4 @@
+function setInnerHTML(el,content) {
+ const fragment = (new DOMParser()).parseFromString(`<pre>${content}</pre>`, 'text/html', {includeShadowRoots: true});
+ (el instanceof HTMLTemplateElement ? el.content : el).replaceChildren(...fragment.body.firstChild.childNodes);
+}