Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change declarative Shadow DOM fragment parsing to be opt-in #26398

Merged
merged 1 commit into from
Nov 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
<script src='../resources/shadow-dom-utils.js'></script>

<script>
document.allowDeclarativeShadowDom = true;

const shadowContent = '<span>Shadow tree</span><slot></slot>';
function getDeclarativeContent(mode, delegatesFocus) {
const delegatesFocusText = delegatesFocus ? ' shadowrootdelegatesfocus' : '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<div id="host">
<div id="host" style="display:none">
<template shadowroot="open">
<slot id="s1" name="slot1"></slot>
</template>
<div id="c1" slot="slot1"></div>
</div>

<script>
document.allowDeclarativeShadowDom = true;

test(() => {
const host = document.querySelector('#host');
const c1 = host.querySelector('#c1');
Expand Down Expand Up @@ -129,7 +131,7 @@
}, 'Declarative Shadow DOM: innerHTML root element');
</script>

<div id="multi-host">
<div id="multi-host" style="display:none">
<template shadowroot="open">
<span>root 1</span>
</template>
Expand Down Expand Up @@ -157,6 +159,7 @@
test(() => {
const template = document.querySelector('#template-containing-shadow');
const host1 = document.createElement('div');
host1.style.display = 'none';
document.body.appendChild(host1);
host1.appendChild(template.content.cloneNode(true));
let innerDiv = host1.querySelector('div.innerdiv');
Expand All @@ -165,6 +168,7 @@
assert_equals(innerDiv.querySelector('template'), null, "No leftover template node");

const host2 = document.createElement('div');
host2.style.display = 'none';
document.body.appendChild(host2);
host2.appendChild(template.content.cloneNode(true));
innerDiv = host2.querySelector('div.innerdiv');
Expand All @@ -187,6 +191,7 @@
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>.
Expand All @@ -210,6 +215,7 @@
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');
Expand Down
226 changes: 226 additions & 0 deletions shadow-dom/declarative/declarative-shadow-dom-opt-in.tentative.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Declarative Shadow DOM</title>
<link rel="author" title="Mason Freed" href="mailto:masonfreed@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>
<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 shadowroot=open>
<span class=content>Content</span>
</template>
</div>
</div>
</div>

<script>
const content = `
<html><body>
<div class=wrapper>
<div class=host>
<template shadowroot=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('shadowroot'),'open','The shadowroot 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);
assert_false(document.allowDeclarativeShadowDom,'The default value for allowDeclarativeShadowDom should be false');
}, 'Non-fragment parsing needs no opt-in');

test(() => {
const div = document.createElement('div');
div.innerHTML = content;
assert_false(document.allowDeclarativeShadowDom,'The default value for allowDeclarativeShadowDom should be false');
assert_dsd(div,false);
document.allowDeclarativeShadowDom = true;
div.innerHTML = content;
assert_dsd(div,true);
document.allowDeclarativeShadowDom = false; // Don't affect other tests
}, 'innerHTML on element');

test(() => {
const templateContent = `<template id=tmpl>${content}</template>`;
assert_false(document.allowDeclarativeShadowDom,'Default allowDeclarativeShadowDom should be false');
const div = document.createElement('div');
div.innerHTML = templateContent;
assert_dsd(div.querySelector('#tmpl').content,false);
document.allowDeclarativeShadowDom = true;
div.innerHTML = templateContent;
assert_dsd(div.querySelector('#tmpl').content,true);
// Make sure to set back to avoid affecting other tests
document.allowDeclarativeShadowDom = false; // Don't affect other tests
}, 'innerHTML on element, with template content');

test(() => {
const temp = document.createElement('template');
temp.innerHTML = content;
assert_dsd(temp.content,false, 'innerHTML by default should not allow declarative shadow content');
assert_false(document.allowDeclarativeShadowDom,'The default value for document.allowDeclarativeShadowDom should be false');
assert_false(temp.content.allowDeclarativeShadowDom,'The default value for template fragment allowDeclarativeShadowDom should be false');
temp.content.allowDeclarativeShadowDom = true;
assert_false(document.allowDeclarativeShadowDom,'Setting template allowDeclarativeShadowDom shouldn\'t affect document.allowDeclarativeShadowDom');
temp.innerHTML = content;
assert_true(temp.content.allowDeclarativeShadowDom,'Setting allowDeclarativeShadowDom should persist across innerHTML set');
assert_dsd(temp.content,true, 'innerHTML should allow declarative shadow content if template.content.allowDeclarativeShadowDom is set');
temp.content.allowDeclarativeShadowDom = false;
document.allowDeclarativeShadowDom = true;
temp.innerHTML = content;
assert_false(temp.content.allowDeclarativeShadowDom,'Setting allowDeclarativeShadowDom should persist across innerHTML set');
assert_true(document.allowDeclarativeShadowDom,'document.allowDeclarativeShadowDom should still be set');
assert_dsd(temp.content,true, 'innerHTML should allow declarative shadow content if document.allowDeclarativeShadowDom is set');
document.allowDeclarativeShadowDom = false; // Don't affect other tests
}, 'Setting template.innerHTML');

test(() => {
const templateContent = `<template id=tmpl>${content}</template>`;
const temp = document.createElement('template');
temp.innerHTML = templateContent;
assert_dsd(temp.content.querySelector('#tmpl').content,false);
document.allowDeclarativeShadowDom = true;
temp.innerHTML = templateContent;
assert_dsd(temp.content.querySelector('#tmpl').content,true);
document.allowDeclarativeShadowDom = false; // Don't affect other tests
}, 'Setting template.innerHTML with nested template content');

test(() => {
const parser = new DOMParser();
let fragment = parser.parseFromString(content,'text/html');
assert_dsd(fragment.body,false);
assert_false(parser.allowDeclarativeShadowDom,'The default value for allowDeclarativeShadowDom should be false');
parser.allowDeclarativeShadowDom = true;
assert_false(document.allowDeclarativeShadowDom,'Setting allowDeclarativeShadowDom shouldn\'t affect document.allowDeclarativeShadowDom');
fragment = parser.parseFromString(content,'text/html');
assert_dsd(fragment.body,true);
}, 'DOMParser');

test(() => {
const doc = document.implementation.createHTMLDocument("Document");
doc.body.innerHTML = content;
assert_dsd(doc.body,false);
assert_false(doc.allowDeclarativeShadowDom,'The default value for allowDeclarativeShadowDom should be false');
doc.allowDeclarativeShadowDom = true;
assert_false(document.allowDeclarativeShadowDom,'Setting allowDeclarativeShadowDom shouldn\'t affect document.allowDeclarativeShadowDom');
doc.body.innerHTML = content;
assert_dsd(doc.body,true);
}, 'createHTMLDocument');

test(() => {
const doc = document.implementation.createHTMLDocument("Document");
let range = doc.createRange();
range.selectNode(doc.body);
let documentFragment = range.createContextualFragment(content);
assert_dsd(documentFragment,false);
doc.allowDeclarativeShadowDom = true;
assert_false(document.allowDeclarativeShadowDom,'Setting allowDeclarativeShadowDom shouldn\'t affect document.allowDeclarativeShadowDom');
documentFragment = range.createContextualFragment(content);
assert_dsd(documentFragment,true);
}, 'createContextualFragment');

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';
assert_false(client.allowDeclarativeShadowDom,'The default value for allowDeclarativeShadowDom should be false');
client.send();
}, 'XMLHttpRequest, disabled');

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,true);
t.done();
}));
client.open("GET", `data:text/html,${content}`);
client.responseType = 'document';
client.allowDeclarativeShadowDom = true;
assert_false(document.allowDeclarativeShadowDom,'Setting allowDeclarativeShadowDom shouldn\'t affect document.allowDeclarativeShadowDom');
client.send();
}, 'XMLHttpRequest, enabled');

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,false);
t.done();
}));
iframe.srcdoc = content;
}, 'iframe, disabled');

async_test((t) => {
const iframe = document.createElement('iframe');
iframe.style.display = "none";
iframe.sandbox = "allow-same-origin allow-declarative-shadow-dom";
document.body.appendChild(iframe);
iframe.addEventListener('load', t.step_func_done(() => {
assert_dsd(iframe.contentDocument.body,true);
t.done();
}));
iframe.allowDeclarativeShadowDom = true;
assert_false(document.allowDeclarativeShadowDom,'Setting allowDeclarativeShadowDom shouldn\'t affect document.allowDeclarativeShadowDom');
iframe.srcdoc = content;
}, 'iframe, enabled');

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_disallow', false));
}, 'iframe without allow-declarative-shadow-dom sandbox flag disallows declarative Shadow DOM');

async_test((t) => {
window.addEventListener('message', getHandler(t,'iframe_allow', true));
}, 'iframe with allow-declarative-shadow-dom sandbox flag allows declarative Shadow DOM');

</script>

<iframe name="iframe_disallow" sandbox="allow-scripts" src="support/declarative-child-frame.html" ></iframe>
<iframe name="iframe_allow" sandbox="allow-scripts allow-declarative-shadow-dom" src="support/declarative-child-frame.html"></iframe>
23 changes: 23 additions & 0 deletions shadow-dom/declarative/support/declarative-child-frame.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<div id=iframe>
<div id=wrapper>
<div id=host>
<template shadowroot=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>