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

Add XML parser tests for custom elements #35350

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Custom Elements: create an element for a token must perform a microtask checkpoint</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org" />
<meta name="assert" content="When the HTML parser creates an element for a token, it must perform a microtask checkpoint before invoking the constructor" />
<meta name="help" content="https://html.spec.whatwg.org/multipage/parsing.html#create-an-element-for-the-token" />
<meta name="help" content="https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint" />
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
</head>
<body>
<div id="log"></div>
<script>
<![CDATA[

async function construct_custom_element_in_parser(test, markup)
{
const window = await create_window_in_test_async(test, 'application/xml', `<?xml version="1.0" encoding="utf-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<body><script>
class SomeElement extends HTMLElement {
constructor() {
super();
window.recordsListInConstructor = recordsList.map((records) => records.slice(0));
}
}
customElements.define('some-element', SomeElement);

const recordsList = [];
const observer = new MutationObserver((records) => {
recordsList.push(records);
});
observer.observe(document.body, {childList: true, subtree: true});

window.onload = () => {
window.recordsListInDOMContentLoaded = recordsList.map((records) => records.slice(0));
}

</scr` + `ipt>${markup}</body></html>`);
return window;
}

promise_test(async function () {
const contentWindow = await construct_custom_element_in_parser(this, '<b><some-element></some-element></b>');
const contentDocument = contentWindow.document;

let recordsList = contentWindow.recordsListInConstructor;
assert_true(Array.isArray(recordsList));
assert_equals(recordsList.length, 1);
assert_true(Array.isArray(recordsList[0]));
assert_equals(recordsList[0].length, 1);
let record = recordsList[0][0];
assert_equals(record.type, 'childList');
assert_equals(record.target, contentDocument.body);
assert_equals(record.previousSibling, contentDocument.querySelector('script'));
assert_equals(record.nextSibling, null);
assert_equals(record.removedNodes.length, 0);
assert_equals(record.addedNodes.length, 1);
assert_equals(record.addedNodes[0], contentDocument.querySelector('b'));

recordsList = contentWindow.recordsListInDOMContentLoaded;
assert_true(Array.isArray(recordsList));
assert_equals(recordsList.length, 2);
assert_true(Array.isArray(recordsList[1]));
assert_equals(recordsList[1].length, 1);
record = recordsList[1][0];
assert_equals(record.type, 'childList');
assert_equals(record.target, contentDocument.querySelector('b'));
assert_equals(record.previousSibling, null);
assert_equals(record.nextSibling, null);
assert_equals(record.removedNodes.length, 0);
assert_equals(record.addedNodes.length, 1);
assert_equals(record.addedNodes[0], contentDocument.querySelector('some-element'));
}, 'XML parser must perform a microtask checkpoint before constructing a custom element');

]]>
</script>
</body>
</html>
14 changes: 14 additions & 0 deletions custom-elements/resources/custom-elements-helpers.js
Expand Up @@ -12,6 +12,20 @@ function create_window_in_test(t, srcdoc) {
return p;
}

function create_window_in_test_async(test, mime, doc) {
return new Promise((resolve) => {
let iframe = document.createElement('iframe');
blob = new Blob([doc], {type: mime});
iframe.src = URL.createObjectURL(blob);
iframe.onload = (event) => {
let contentWindow = iframe.contentWindow;
test.add_cleanup(() => iframe.remove());
resolve(contentWindow);
};
document.body.appendChild(iframe);
});
}

function test_with_window(f, name, srcdoc) {
promise_test((t) => {
return create_window_in_test(t, srcdoc)
Expand Down
@@ -0,0 +1,134 @@
<?xml version="1.0" encoding="utf-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Custom Elements: create an element for a token must increment and decrement document's throw-on-dynamic-markup-insertion counter</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org" />
<meta name="assert" content="Invoking document.open, document.write, document.writeln, and document.write must throw an exception when the HTML parser is creating a custom element for a token" />
<meta name="help" content="https://html.spec.whatwg.org/multipage/parsing.html#create-an-element-for-the-token" />
<meta name="help" content="https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#throw-on-dynamic-markup-insertion-counter" />
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
</head>
<body>
<div id="log"></div>
<script>
<![CDATA[

async function construct_custom_element_in_parser(test, code)
{
window.executed = false;
window.exception = false;
const content_window = await create_window_in_test_async(test, 'application/xml', `<?xml version="1.0" encoding="utf-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script>
<![CDATA[
let executed = false;
let exception = null;
class CustomElement extends window.HTMLElement {
constructor() {
super();
try {
${code}
} catch (error) {
exception = error;
}
executed = true;
}
}
customElements.define('some-element', CustomElement);
]]` + `>
</` + `script>
</head>
<body>
<some-element></some-element>
<script>
top.executed = executed;
top.exception = exception;
</script>
</body>
</html>`);
let content_document;
try {
content_document = content_window.document;
} catch (error) { }
assert_true(executed, 'Must synchronously instantiate a custom element');
return {window: content_window, document: content_document, exception};
}

promise_test(async function () {
const result = await construct_custom_element_in_parser(this, `document.open()`);
assert_throws_dom('InvalidStateError', result.window.DOMException, () => { throw result.exception; }, 'Must throw an InvalidStateError');
}, 'document.open() must throw an InvalidStateError when synchronously constructing a custom element');

promise_test(async function () {
const result = await construct_custom_element_in_parser(this, `document.open('text/html')`);
assert_throws_dom('InvalidStateError', result.window.DOMException, () => { throw result.exception; }, 'Must throw an InvalidStateError');
}, 'document.open("text/html") must throw an InvalidStateError when synchronously constructing a custom element');

// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-document-open-window
promise_test(async function () {
let load_promise = new Promise((resolve) => window.onmessage = (event) => resolve(event.data));
const url = top.location.href.substring(0, top.location.href.lastIndexOf('/')) + '/resources/navigation-destination.html';
const result = await construct_custom_element_in_parser(this, `document.open('${url}', '_self', '')`);
assert_equals(result.exception, null);
assert_equals(await load_promise, 'didNavigate');
}, 'document.open(URL) must NOT throw an InvalidStateError when synchronously constructing a custom element');

promise_test(async function () {
const result = await construct_custom_element_in_parser(this, `document.close()`);
assert_not_equals(result.exception, null);
assert_throws_dom('InvalidStateError', result.window.DOMException, () => { throw result.exception; }, 'Must throw an InvalidStateError');
}, 'document.close() must throw an InvalidStateError when synchronously constructing a custom element');

promise_test(async function () {
const result = await construct_custom_element_in_parser(this, `document.write('<b>some text</b>')`);
assert_throws_dom('InvalidStateError', result.window.DOMException, () => { throw result.exception; }, 'Must throw an InvalidStateError');
assert_equals(result.document.querySelector('b'), null, 'Must not insert new content');
assert_false(result.document.body.innerHTML.includes('some text'), 'Must not insert new content');
}, 'document.write must throw an InvalidStateError when synchronously constructing a custom element');

promise_test(async function () {
const result = await construct_custom_element_in_parser(this, `document.writeln('<b>some text</b>')`);
assert_throws_dom('InvalidStateError', result.window.DOMException, () => { throw result.exception; }, 'Must throw an InvalidStateError');
assert_equals(result.document.querySelector('b'), null, 'Must not insert new content');
assert_false(result.document.body.innerHTML.includes('some text'), 'Must not insert new content');
}, 'document.writeln must throw an InvalidStateError when synchronously constructing a custom element');

promise_test(async function () {
window.another_window = await create_window_in_test_async(this, 'text/html', '<!DOCTYPE html><html><body>');
const result = await construct_custom_element_in_parser(this, `top.another_window.document.open()`);
assert_equals(result.exception, null);
}, 'document.open() of another document must not throw an InvalidStateError when synchronously constructing a custom element');

promise_test(async function () {
window.another_window = await create_window_in_test_async(this, 'text/html', '<!DOCTYPE html><html><body>');
const result = await construct_custom_element_in_parser(this, `top.another_window.document.open('text/html')`);
assert_equals(result.exception, null);
}, 'document.open("text/html") of another document must not throw an InvalidStateError when synchronously constructing a custom element');

promise_test(async function () {
window.another_window = await create_window_in_test_async(this, 'text/html', '<!DOCTYPE html><html><body>');
const result = await construct_custom_element_in_parser(this, `top.another_window.document.close()`);
assert_equals(result.exception, null);
}, 'document.close() of another document must not throw an InvalidStateError when synchronously constructing a custom element');

promise_test(async function () {
window.another_window = await create_window_in_test_async(this, 'text/html', '<!DOCTYPE html><html><body>');
const result = await construct_custom_element_in_parser(this, `top.another_window.document.write('<b>some text</b>')`);
assert_equals(result.exception, null);
assert_equals(another_window.document.querySelector('b').outerHTML, '<b>some text</b>');
}, 'document.write of another document must not throw an InvalidStateError when synchronously constructing a custom element');

promise_test(async function () {
window.another_window = await create_window_in_test_async(this, 'text/html', '<!DOCTYPE html><html><body>');
const result = await construct_custom_element_in_parser(this, `top.another_window.document.writeln('<b>some text</b>')`);
assert_equals(result.exception, null);
assert_equals(another_window.document.querySelector('b').outerHTML, '<b>some text</b>');
}, 'document.writeln of another document must not throw an InvalidStateError when synchronously constructing a custom element');

]]>
</script>
</body>
</html>
@@ -0,0 +1,134 @@
<?xml version="1.0" encoding="utf-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Custom Elements: create an element for a token must increment and decrement document's throw-on-dynamic-markup-insertion counter</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org" />
<meta name="assert" content="Invoking document.open, document.write, document.writeln, and document.write must throw an exception when the HTML parser is creating a custom element for a token" />
<meta name="help" content="https://html.spec.whatwg.org/multipage/parsing.html#create-an-element-for-the-token" />
<meta name="help" content="https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#throw-on-dynamic-markup-insertion-counter" />
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
</head>
<body>
<div id="log"></div>
<script>
<![CDATA[

async function custom_element_reactions_in_parser(test, code)
{
window.executed = false;
window.exception = false;
const content_window = await create_window_in_test_async(test, 'application/xml', `<?xml version="1.0" encoding="utf-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script>
<![CDATA[
let executed = false;
let exception = null;
class CustomElement extends window.HTMLElement {
constructor() {
super();
try {
${code}
} catch (error) {
exception = error;
}
executed = true;
}
}
CustomElement.observedAttributes = ['title'];
customElements.define('some-element', CustomElement);
]]` + `>
</` + `script>
</head>
<body>
<some-element title="some title"></some-element>
<script>
top.executed = executed;
top.exception = exception;
</script>
</body>
</html>`);
let content_document;
try {
content_document = content_window.document;
} catch (error) { }
assert_true(executed, 'Must immediately process custom element reactions for setting attributes');
return {window: content_window, document: content_document, exception};
}

promise_test(async function () {
const result = await custom_element_reactions_in_parser(this, `document.open()`);
assert_throws_dom('InvalidStateError', result.window.DOMException, () => { throw result.exception; }, 'Must throw an InvalidStateError');
}, 'document.open() must throw an InvalidStateError when processing custom element reactions for a synchronous constructed custom element');

promise_test(async function () {
const result = await custom_element_reactions_in_parser(this, `document.open('text/html')`);
assert_throws_dom('InvalidStateError', result.window.DOMException, () => { throw result.exception; }, 'Must throw an InvalidStateError');
}, 'document.open("text/html") must throw an InvalidStateError when processing custom element reactions for a synchronous constructed custom element');

// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-document-open-window
promise_test(async function () {
let load_promise = new Promise((resolve) => window.onmessage = (event) => resolve(event.data));
const url = top.location.href.substring(0, top.location.href.lastIndexOf('/')) + '/resources/navigation-destination.html';
const result = await custom_element_reactions_in_parser(this, `document.open('${url}', '_self', '')`);
assert_equals(result.exception, null);
assert_equals(await load_promise, 'didNavigate');
}, 'document.open(URL) must NOT throw an InvalidStateError when processing custom element reactions for a synchronous constructed custom element');

promise_test(async function () {
const result = await custom_element_reactions_in_parser(this, `document.close()`);
assert_throws_dom('InvalidStateError', result.window.DOMException, () => { throw result.exception; }, 'Must throw an InvalidStateError');
}, 'document.close() must throw an InvalidStateError when processing custom element reactions for a synchronous constructed custom element');

promise_test(async function () {
const result = await custom_element_reactions_in_parser(this, `document.write('<b>some text</b>')`);
assert_throws_dom('InvalidStateError', result.window.DOMException, () => { throw result.exception; }, 'Must throw an InvalidStateError');
assert_equals(result.document.querySelector('b'), null, 'Must not insert new content');
assert_false(result.document.body.innerHTML.includes('some text'), 'Must not insert new content');
}, 'document.write must throw an InvalidStateError when processing custom element reactions for a synchronous constructed custom element');

promise_test(async function () {
const result = await custom_element_reactions_in_parser(this, `document.writeln('<b>some text</b>')`);
assert_throws_dom('InvalidStateError', result.window.DOMException, () => { throw result.exception; }, 'Must throw an InvalidStateError');
assert_equals(result.document.querySelector('b'), null, 'Must not insert new content');
assert_false(result.document.body.innerHTML.includes('some text'), 'Must not insert new content');
}, 'document.writeln must throw an InvalidStateError when processing custom element reactions for a synchronous constructed custom element');

promise_test(async function () {
window.another_window = await create_window_in_test_async(this);
const result = await custom_element_reactions_in_parser(this, `top.another_window.document.open()`);
assert_equals(result.exception, null);
}, 'document.open() of another document must not throw an InvalidStateError when processing custom element reactions for a synchronous constructed custom element');

promise_test(async function () {
window.another_window = await create_window_in_test_async(this);
const result = await custom_element_reactions_in_parser(this, `top.another_window.document.open('text/html')`);
assert_equals(result.exception, null);
}, 'document.open("text/html") of another document must not throw an InvalidStateError when processing custom element reactions for a synchronous constructed custom element');

promise_test(async function () {
window.another_window = await create_window_in_test_async(this);
const result = await custom_element_reactions_in_parser(this, `top.another_window.document.close()`);
assert_equals(result.exception, null);
}, 'document.close() of another document must not throw an InvalidStateError when processing custom element reactions for a synchronous constructed custom element');

promise_test(async function () {
window.another_window = await create_window_in_test_async(this);
const result = await custom_element_reactions_in_parser(this, `top.another_window.document.write('<b>some text</b>')`);
assert_equals(result.exception, null);
assert_equals(another_window.document.querySelector('b').outerHTML, '<b>some text</b>');
}, 'document.write of another document must not throw an InvalidStateError when processing custom element reactions for a synchronous constructed custom element');

promise_test(async function () {
window.another_window = await create_window_in_test_async(this);
const result = await custom_element_reactions_in_parser(this, `top.another_window.document.writeln('<b>some text</b>')`);
assert_equals(result.exception, null);
assert_equals(another_window.document.querySelector('b').outerHTML, '<b>some text</b>');
}, 'document.writeln of another document must not throw an InvalidStateError when processing custom element reactions for a synchronous constructed custom element');

]]>
</script>
</body>
</html>