Skip to content

Commit

Permalink
Update declarative Shadow DOM opt-in mechanics
Browse files Browse the repository at this point in the history
The issue thread [1] has had more discussion, after the initial draft
of declarative Shadow DOM opt-in landed [2]. This CL implements those
bits of feedback. In particular:
 - There is no public allowDeclarativeShadowDom state available on
   Document or DocumentFragment.
 - All APIs use call parameters to avoid state, with the exception
   of DOMParser.
 - innerHTML no longer supports Declarative Shadow DOM.
 - A new setInnerHTML() function allows opt-in access to DSD.
 - Several of the more obscure APIs do not have an opt-in for
   declarative Shadow DOM, such as XHR, createContextualFragment, and
   document.write.
 - The sandbox flag has been removed from iframes completely. The new
   plan is to use DocumentPolicy to enable declarative Shadow DOM for
   iframes. For now, iframes always support declarative Shadow DOM.
 - 'allowDeclarativeShadowDOM' has become 'allowShadowRoot'.

[1] whatwg/dom#912 (comment)
[2] https://chromium-review.googlesource.com/c/chromium/src/+/2513525

Bug: 1042130

Change-Id: I3a2becf2a113cc8647b29077d2efea1c990d4547
Cq-Do-Not-Cancel-Tryjobs: true
  • Loading branch information
mfreed7 authored and chromium-wpt-export-bot committed Nov 11, 2020
1 parent 745dadd commit efb857d
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
<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 All @@ -20,7 +18,7 @@
const declarativeString = `<${elementType} id=theelement>${getDeclarativeContent(mode, delegatesFocus)}
<span class='lightdom'>${lightDomTextContent}</span></${elementType}>`;
const wrapper = document.createElement('div');
wrapper.innerHTML = declarativeString;
wrapper.setInnerHTML(declarativeString, {allowShadowRoot: true});
const element = wrapper.querySelector('#theelement');
return {wrapper: wrapper, element: element};
}
Expand Down
32 changes: 15 additions & 17 deletions shadow-dom/declarative/declarative-shadow-dom-basic.tentative.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
</div>

<script>
document.allowDeclarativeShadowDom = true;

test(() => {
const host = document.querySelector('#host');
const c1 = host.querySelector('#c1');
Expand All @@ -34,14 +32,14 @@

test(() => {
const div = document.createElement('div');
div.innerHTML = `
div.setInnerHTML(`
<div id="host">
<template shadowroot="open">
<slot id="s1" name="slot1"></slot>
</template>
<div id="c1" slot="slot1"></div>
</div>
`;
`, {allowShadowRoot: true});
const host = div.querySelector('#host');
const c1 = host.querySelector('#c1');
assert_true(!!c1);
Expand All @@ -55,12 +53,12 @@

test(() => {
const div = document.createElement('div');
div.innerHTML = `
div.setInnerHTML(`
<div id="host">
<template shadowroot="invalid">
</template>
</div>
`;
`, {allowShadowRoot: true});
const host = div.querySelector('#host');
assert_equals(host.shadowRoot, null, "Shadow root was found");
const tmpl = host.querySelector('template');
Expand All @@ -71,25 +69,25 @@

test(() => {
const div = document.createElement('div');
div.innerHTML = `
div.setInnerHTML(`
<div id="host">
<template shadowroot="closed">
</template>
</div>
`;
`, {allowShadowRoot: true});
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');
div.innerHTML = `
div.setInnerHTML(`
<div id="host">
<template shadowroot="open">
<slot id="s1" name="slot1"></slot>
</div>
`;
`, {allowShadowRoot: true});
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");
Expand All @@ -100,35 +98,35 @@

test(() => {
const div = document.createElement('div');
div.innerHTML = `
div.setInnerHTML(`
<div id="host">
<template shadowroot="open" shadowrootdelegatesfocus>
</template>
</div>
`;
`, {allowShadowRoot: true});
var host = div.querySelector('#host');
assert_true(!!host.shadowRoot,"No shadow root found");
assert_true(host.shadowRoot.delegatesFocus,"delegatesFocus should be true");
div.innerHTML = `
div.setInnerHTML(`
<div id="host">
<template shadowroot="open">
</template>
</div>
`;
`, {allowShadowRoot: true});
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');

test(() => {
const host = document.createElement('div');
// Root element of innerHTML is a <template shadowroot>:
host.innerHTML = '<template shadowroot=open></template>';
// Root element of setInnerHTML is a <template shadowroot>:
host.setInnerHTML('<template shadowroot=open></template>', {allowShadowRoot: 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('shadowroot'),"open","'shadowroot' attribute should still be present");
}, 'Declarative Shadow DOM: innerHTML root element');
}, 'Declarative Shadow DOM: setInnerHTML root element');
</script>

<div id="multi-host" style="display:none">
Expand Down
144 changes: 57 additions & 87 deletions shadow-dom/declarative/declarative-shadow-dom-opt-in.tentative.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,97 +56,83 @@
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;
}, 'innerHTML on element - disallowed');

test(() => {
const div = document.createElement('div');
div.setInnerHTML(content);
assert_dsd(div,false);
div.setInnerHTML(content, {allowShadowRoot: false});
assert_dsd(div,false);
div.setInnerHTML(content, {allowShadowRoot: true});
assert_dsd(div,true);
document.allowDeclarativeShadowDom = false; // Don't affect other tests
}, 'innerHTML on element');
}, 'setInnerHTML 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;
div.setInnerHTML(templateContent, {allowShadowRoot: true});
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');
}, 'setInnerHTML on element, with nested 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');
assert_dsd(temp.content,false, 'innerHTML should not allow declarative shadow content');
temp.setInnerHTML(content, {allowShadowRoot: true});
assert_dsd(temp.content,true, 'setInnerHTML should allow declarative shadow content if enabled');
}, 'setInnerHTML 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);
document.allowDeclarativeShadowDom = true;
temp.innerHTML = templateContent;
temp.setInnerHTML(templateContent, {allowShadowRoot: true});
assert_dsd(temp.content.querySelector('#tmpl').content,true);
document.allowDeclarativeShadowDom = false; // Don't affect other tests
}, 'Setting template.innerHTML with nested template content');
}, 'setInnerHTML on template, with nested template content');

test(() => {
const parser = new DOMParser();
const div = document.createElement('div');
const shadow = div.attachShadow({mode: 'open'});
shadow.innerHTML = content;
assert_dsd(shadow,false);
shadow.setInnerHTML(content, {allowShadowRoot: true});
assert_dsd(shadow,true);
}, 'setInnerHTML on shadowRoot');

test(() => {
let 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');
parser = new DOMParser({allowShadowRoot: true});
fragment = parser.parseFromString(content,'text/html');
assert_dsd(fragment.body,true);
}, 'DOMParser');

test(() => {
const doc = document.implementation.createHTMLDocument("Document");
const doc = document.implementation.createHTMLDocument('');
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;
doc.body.setInnerHTML(content, {allowShadowRoot: true});
assert_dsd(doc.body,true);
}, 'createHTMLDocument');
}, 'createHTMLDocument with setInnerHTML');

test(() => {
const doc = document.implementation.createHTMLDocument("Document");
const doc = document.implementation.createHTMLDocument('');
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');
}, 'createContextualFragment - not supported');

async_test((t) => {
let client = new XMLHttpRequest();
Expand All @@ -157,49 +143,38 @@
}));
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');
test(() => {
const div = document.createElement('div');
div.insertAdjacentHTML('afterbegin',content);
assert_dsd(div,false);
}, 'insertAdjacentHTML on element - not supported');

test(() => {
document.write(`<div id=doc-write-1 style="display:none">${content}</div>`);
assert_dsd(document.getElementById('doc-write-1'),true);
}, 'document.write allowed from synchronous script loaded from main document');

test(() => {
const doc = document.implementation.createHTMLDocument('');
doc.write(`<div id=doc-write-1>${content}</div>`);
assert_dsd(doc.getElementById('doc-write-1'),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,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";
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.allowDeclarativeShadowDom = true;
assert_false(document.allowDeclarativeShadowDom,'Setting allowDeclarativeShadowDom shouldn\'t affect document.allowDeclarativeShadowDom');
iframe.srcdoc = content;
}, 'iframe, enabled');
}, 'iframe');

async_test((t) => {
const iframe = document.createElement('iframe');
Expand All @@ -210,7 +185,7 @@
t.done();
}));
iframe.srcdoc = content;
}, 'iframe, no sandbox - supports DSD by default');
}, 'iframe, no sandbox');

function getHandler(t, name, shouldHaveShadow) {
return (e) => {
Expand All @@ -224,19 +199,14 @@
};
}
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');
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 flag allows declarative Shadow DOM');
}, 'iframe with no sandbox 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>
<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>
2 changes: 1 addition & 1 deletion shadow-dom/declarative/getinnerhtml.tentative.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
}

function runAllTests() {
const allElements = HTML5_ELEMENT_NAMES;
const allElements = [...HTML5_ELEMENT_NAMES, 'htmlunknown'];
const safelisted = ATTACHSHADOW_SAFELISTED_ELEMENTS;
for (const elementName of allElements) {
const allowsShadowDom = safelisted.includes(elementName);
Expand Down
Loading

0 comments on commit efb857d

Please sign in to comment.