Automatic update from web-platform-tests Harden custom element microtask checkpoint WPT against callback batching Avoid asserting MutationObserver callback batch counts and assert mutation semantics instead. Fixed: 504005450 Change-Id: I13f90cbb3f50297fdc31e77441f2119c68a9eaa9 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7790306 Reviewed-by: Joey Arhar <jarhar@chromium.org> Commit-Queue: Helmut Januschka <helmut@januschka.com> Cr-Commit-Position: refs/heads/main@{#1647816} -- wpt-commits: a2a262a4b3978882db295be663724b993ed8cd24 wpt-pr: 60651
159 lines
6.7 KiB
HTML
159 lines
6.7 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<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">
|
|
<meta name="help" content="https://html.spec.whatwg.org/multipage/parsing.html#adoption-agency-algorithm">
|
|
<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>
|
|
|
|
async function construct_custom_element_in_parser(test, markup)
|
|
{
|
|
const window = await create_window_in_test(test, `
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<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;
|
|
}
|
|
|
|
function flatten_records_list(recordsList)
|
|
{
|
|
// MutationObserver callback batching is not deterministic across runs.
|
|
// Flatten callback batches so assertions focus on mutation semantics.
|
|
assert_true(Array.isArray(recordsList));
|
|
const flattened = [];
|
|
for (const records of recordsList) {
|
|
assert_true(Array.isArray(records));
|
|
flattened.push(...records);
|
|
}
|
|
return flattened;
|
|
}
|
|
|
|
promise_test(async function () {
|
|
const contentWindow = await construct_custom_element_in_parser(this, '<b><some-element></b>');
|
|
const contentDocument = contentWindow.document;
|
|
|
|
const constructorRecords =
|
|
flatten_records_list(contentWindow.recordsListInConstructor);
|
|
const script = contentDocument.querySelector('script');
|
|
const bElement = contentDocument.querySelector('b');
|
|
const someElement = contentDocument.querySelector('some-element');
|
|
const isBInsertionRecord = (candidate) =>
|
|
candidate.type === 'childList' &&
|
|
candidate.target === contentDocument.body &&
|
|
candidate.previousSibling === script &&
|
|
candidate.nextSibling === null &&
|
|
candidate.removedNodes.length === 0 &&
|
|
candidate.addedNodes.length === 1 &&
|
|
candidate.addedNodes[0] === bElement;
|
|
const isSomeElementInsertionRecord = (candidate) =>
|
|
candidate.type === 'childList' &&
|
|
candidate.target === bElement &&
|
|
candidate.previousSibling === null &&
|
|
candidate.nextSibling === null &&
|
|
candidate.removedNodes.length === 0 &&
|
|
candidate.addedNodes.length === 1 &&
|
|
candidate.addedNodes[0] === someElement;
|
|
|
|
assert_equals(constructorRecords.length, 1);
|
|
assert_true(isBInsertionRecord(constructorRecords[0]),
|
|
'first mutation record must be the <b> insertion into document.body');
|
|
|
|
const allRecords =
|
|
flatten_records_list(contentWindow.recordsListInDOMContentLoaded);
|
|
assert_equals(allRecords.length, 2);
|
|
assert_true(isSomeElementInsertionRecord(allRecords[1]),
|
|
'second mutation record must be the <some-element> insertion into <b>');
|
|
}, 'HTML parser must perform a microtask checkpoint before constructing a custom element');
|
|
|
|
promise_test(async function () {
|
|
const contentWindow = await construct_custom_element_in_parser(this, '<b><i>hello</b><some-element>');
|
|
const contentDocument = contentWindow.document;
|
|
|
|
const constructorRecords = flatten_records_list(contentWindow.recordsListInConstructor);
|
|
assert_equals(constructorRecords.length, 4);
|
|
|
|
let record = constructorRecords[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'));
|
|
|
|
record = constructorRecords[1];
|
|
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('i'));
|
|
|
|
record = constructorRecords[2];
|
|
assert_equals(record.type, 'childList');
|
|
assert_equals(record.target, contentDocument.querySelector('i'));
|
|
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].nodeType, Node.TEXT_NODE);
|
|
assert_equals(record.addedNodes[0].data, "hello");
|
|
|
|
const secondI = contentDocument.querySelectorAll('i')[1];
|
|
record = constructorRecords[3];
|
|
assert_equals(record.type, 'childList');
|
|
assert_equals(record.target, contentDocument.body);
|
|
assert_equals(record.previousSibling, contentDocument.querySelector('b'));
|
|
assert_equals(record.nextSibling, null);
|
|
assert_equals(record.removedNodes.length, 0);
|
|
assert_equals(record.addedNodes.length, 1);
|
|
assert_equals(record.addedNodes[0], secondI);
|
|
|
|
const allRecords = flatten_records_list(contentWindow.recordsListInDOMContentLoaded);
|
|
assert_equals(allRecords.length, 5);
|
|
const someElement = contentDocument.querySelector('some-element');
|
|
const isSomeElementInsertionRecord = (candidate) =>
|
|
candidate.type === 'childList' &&
|
|
candidate.target === secondI &&
|
|
candidate.previousSibling === null &&
|
|
candidate.nextSibling === null &&
|
|
candidate.removedNodes.length === 0 &&
|
|
candidate.addedNodes.length === 1 &&
|
|
candidate.addedNodes[0] === someElement;
|
|
assert_true(isSomeElementInsertionRecord(allRecords[4]),
|
|
'last mutation record must be the <some-element> insertion into the second <i>');
|
|
}, 'HTML parser must perform a microtask checkpoint before constructing a custom element during the adoption agency algorithm');
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|