diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/shadow-dom/declarative | |
parent | Initial commit. (diff) | |
download | firefox-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')
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 <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); +} |