tests: Vendor blink perf tests (#38654)

Vendors the [blink perf
tests](https://chromium.googlesource.com/chromium/src/+/HEAD/third_party/blink/perf_tests/).
These perf tests are useful to evaluate the performance of servo. 
The license that governs the perf tests is included in the folder. 
Running benchmark cases automatically is left to future work.

The update.py script is taken from mozjs and slightly adapted, so we can
easily filter
(and patch if this should be necessary in the future.

Testing: This PR just adds the perf_tests, but does not use or modify
them in any way.

---------

Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>
This commit is contained in:
Jonathan Schwender 2025-08-17 11:54:04 +02:00 committed by GitHub
parent 7621332824
commit ee781b71b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
648 changed files with 359694 additions and 0 deletions

View file

@ -0,0 +1 @@
file://third_party/blink/renderer/core/dom/OWNERS

View file

@ -0,0 +1,39 @@
// We reuse the "backend" of the imperative Shadow DOM Distribution API for a new custom element, <my-detail>/<my-summary>.
//TODO(crbug.com/869308):Emulate other <summary><details> features
class MySummaryElement extends HTMLElement {
constructor() {
super();
}
}
customElements.define("my-summary", MySummaryElement);
customElements.define("my-detail", class extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open", slotAssignment: "manual" });
}
connectedCallback() {
const target = this;
if (!target.shadowRoot.querySelector(':scope > slot')) {
const slot1 = document.createElement("slot");
const slot2 = document.createElement("slot");
const shadowRoot = target.shadowRoot;
shadowRoot.appendChild(slot1);
shadowRoot.appendChild(slot2);
slot1.style.display = "block";
slot1.style.backgroundColor = "red";
const observer = new MutationObserver(function(mutations) {
//Get the first <my-summary> element from <my-detail>'s direct children
const my_summary = target.querySelector(':scope > my-summary');
if (my_summary) {
slot1.assign(my_summary);
} else {
slot1.assign();
}
slot2.assign(...target.childNodes);
});
observer.observe(this, {childList: true});
}
}
});

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<div id="host">
</div>
<script>
const host = document.querySelector("#host");
const shadow_root = host.attachShadow({ mode: 'open' });
const slot1 = document.createElement("slot");
const child1 = document.createElement("child");
shadow_root.appendChild(slot1);
window.onload = function() {
PerfTestRunner.measureTime({
description: "Measure performance of slot assignment in auto-slotting mode in shadow root.",
run: function() {
const start = PerfTestRunner.now();
for (let i = 0; i < 100; i++) {
const slot2 = document.createElement("slot");
slot2.name = "slot2";
host.appendChild(child1);
shadow_root.appendChild(slot2);
child1.slot = "slot2";
child1.assignedSlot;
slot2.assignedElements();
slot2.remove();
child1.assignedSlot;
}
return PerfTestRunner.now() - start;
}
});
}
</script>

View file

@ -0,0 +1,26 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<div id="host">
</div>
<script>
const host = document.querySelector("#host");
const shadow_root = host.attachShadow({ mode: 'open', slotAssignment: 'manual' });
const slot1 = document.createElement("slot");
const child1 = document.createElement("child");
slot1.assign(child1);
shadow_root.appendChild(slot1);
window.onload = function() {
PerfTestRunner.measureTime({
description: "Measure performance of appendChild in manual-slotting mode in shadow root.",
run: function() {
const start = PerfTestRunner.now();
for (let i = 0; i < 100; i++) {
host.appendChild(child1);
child1.remove();
}
return PerfTestRunner.now() - start;
}
});
}
</script>

View file

@ -0,0 +1,26 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<div id="host">
</div>
<script>
const host = document.querySelector("#host");
const shadow_root = host.attachShadow({ mode: 'open', slotAssignment: 'manual' });
const slot1 = document.createElement("slot");
const child1 = document.createElement("child");
shadow_root.appendChild(slot1);
window.onload = function() {
PerfTestRunner.measureTime({
description: "Measure performance of assign function in manual-slotting mode in shadow root.",
run: function() {
const start = PerfTestRunner.now();
for (let i = 0; i < 100; i++) {
host.appendChild(child1);
slot1.assign(child1);
child1.remove();
}
return PerfTestRunner.now() - start;
}
});
}
</script>

View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<div id="host">
</div>
<script>
const host = document.querySelector("#host");
const shadow_root = host.attachShadow({ mode: 'open', slotAssignment: 'manual' });
const slot1 = document.createElement("slot");
const child1 = document.createElement("child");
slot1.assign(child1);
shadow_root.appendChild(slot1);
window.onload = function() {
PerfTestRunner.measureTime({
description: "Measure performance of assignedElements() in manual-slotting mode in shadow root.",
run: function() {
const start = PerfTestRunner.now();
for (let i = 0; i < 100; i++) {
host.appendChild(child1);
slot1.assignedElements();
child1.remove();
}
return PerfTestRunner.now() - start;
}
});
}
</script>

View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<div id="host">
</div>
<script>
const host = document.querySelector("#host");
const shadow_root = host.attachShadow({ mode: 'open', slotAssignment: 'manual' });
const slot1 = document.createElement("slot");
const child1 = document.createElement("child");
slot1.assign(child1);
shadow_root.appendChild(slot1);
window.onload = function() {
PerfTestRunner.measureTime({
description: "Measure performance of assignedSlot in manual-slotting mode in shadow root.",
run: function() {
const start = PerfTestRunner.now();
for (let i = 0; i < 100; i++) {
host.appendChild(child1);
child1.assignedSlot;
child1.remove();
}
return PerfTestRunner.now() - start;
}
});
}
</script>

View file

@ -0,0 +1,35 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<script src="custom-detail-summary.js"></script>
<my-detail id="my-detail">
<my-summary id="my-summary">summary</my-summary>
</my-detail>
<my-summary id="my-summary1">added-summary1</my-summary>
<my-summary id="my-summary2">added-summary2</my-summary>
<script>
const host = document.querySelector("#my-detail");
const sum = document.querySelector("#my-summary");
const sum1 = document.querySelector("#my-summary1");
const sum2 = document.querySelector("#my-summary2");
window.onload = function() {
for (let i = 0; i < 100; i++) {
host.appendChild(document.createElement("my-summary"));
}
PerfTestRunner.measureTime({
description: "Measure performance of my-detail element in manual-slotting mode in shadow root when my-summary element is inserted when the child node is over 100.",
run: function() {
const start = PerfTestRunner.now();
for (let i = 0; i < 100; i++) {
host.appendChild(sum1);
host.insertBefore(sum2, sum);
PerfTestRunner.forceLayout();
sum1.remove();
sum2.remove();
PerfTestRunner.forceLayout();
}
return PerfTestRunner.now() - start;
}
});
}
</script>

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<script src="custom-detail-summary.js"></script>
<my-detail id="my-detail">
<my-summary id="my-summary">summary</my-summary>
</my-detail>
<my-summary id="my-summary1">added-summary1</my-summary>
<my-summary id="my-summary2">added-summary2</my-summary>
<script>
const host = document.querySelector("#my-detail");
const sum = document.querySelector("#my-summary");
const sum1 = document.querySelector("#my-summary1");
const sum2 = document.querySelector("#my-summary2");
window.onload = function() {
PerfTestRunner.measureTime({
description: "Measure performance of my-detail element in manual-slotting mode in shadow root when my-summary element is inserted.",
run: function() {
const start = PerfTestRunner.now();
for (let i = 0; i < 100; i++) {
host.appendChild(sum1);
host.insertBefore(sum2, sum);
PerfTestRunner.forceLayout();
sum1.remove();
sum2.remove();
PerfTestRunner.forceLayout();
}
return PerfTestRunner.now() - start;
}
});
}
</script>

View file

@ -0,0 +1,34 @@
<!doctype html>
<script src="../resources/runner.js"></script>
<details id="details">
<summary id="summary">summary</summary>
</details>
<summary id="summary1">added-summary1</summary>
<summary id="summary2">added-summary2</summary>
<script>
const host = document.querySelector("#details");
const sum = document.querySelector("#summary");
const sum1 = document.querySelector("#summary1");
const sum2 = document.querySelector("#summary2");
window.onload = function() {
for (let i = 0; i < 100; i++) {
host.appendChild(document.createElement("summary"));
}
PerfTestRunner.measureTime({
description: "Measure performance of built-in detail element when summary element is inserted when the child node is over 100",
run: function() {
const start = PerfTestRunner.now();
for (let i = 0; i < 100; i++) {
host.appendChild(sum1);
host.insertBefore(sum2, sum);
PerfTestRunner.forceLayout();
sum1.remove();
sum2.remove();
PerfTestRunner.forceLayout();
}
return PerfTestRunner.now() - start;
}
});
}
</script>

View file

@ -0,0 +1,31 @@
<!doctype html>
<script src="../resources/runner.js"></script>
<details id="details">
<summary id="summary">summary</summary>
</details>
<summary id="summary1">added-summary1</summary>
<summary id="summary2">added-summary2</summary>
<script>
const host = document.querySelector("#details");
const sum = document.querySelector("#summary");
const sum1 = document.querySelector("#summary1");
const sum2 = document.querySelector("#summary2");
window.onload = function() {
PerfTestRunner.measureTime({
description: "Measure performance of built-in detail element when summary element is inserted.",
run: function() {
const start = PerfTestRunner.now();
for (let i = 0; i < 100; i++) {
host.appendChild(sum1);
host.insertBefore(sum2, sum);
PerfTestRunner.forceLayout();
sum1.remove();
sum2.remove();
PerfTestRunner.forceLayout();
}
return PerfTestRunner.now() - start;
}
});
}
</script>

View file

@ -0,0 +1,28 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<div id="host">
<div id="child"></div>
</div>
<script>
const host = document.querySelector("#host");
const shadow_root = host.attachShadow({ mode: 'open', slotAssignment: 'manual' });
const slot1 = document.createElement("slot");
const child1 = document.createElement("child");
const child = document.querySelector("#child");
slot1.assign(child1);
shadow_root.appendChild(slot1);
window.onload = function() {
PerfTestRunner.measureTime({
description: "Measure performance of insertBefore in manual-slotting mode in shadow root.",
run: function() {
const start = PerfTestRunner.now();
for (let i = 0; i < 100; i++) {
host.insertBefore(child1,child);
child1.remove();
}
return PerfTestRunner.now() - start;
}
});
}
</script>

View file

@ -0,0 +1,29 @@
<!doctype html>
<script src="../resources/runner.js"></script>
<div id="host">
<div id="child1"></div>
</div>
<script>
const host = document.querySelector("#host");
const child1 = document.querySelector("#child1");
const shadow_root = host.attachShadow({ mode: "open", slotAssignment: "manual" });
window.onload = function() {
PerfTestRunner.measureTime({
description: "Measure performance of slot assignment in manual-slotting mode in shadow root.",
run: function() {
const start = PerfTestRunner.now();
for (let i = 0; i < 100; i++) {
const slot1 = document.createElement("slot");
shadow_root.appendChild(slot1);
slot1.assign(child1);
child1.assignedSlot;
slot1.assignedElements();
slot1.remove();
child1.assignedSlot;
}
return PerfTestRunner.now() - start;
}
});
}
</script>

View file

@ -0,0 +1,57 @@
<!DOCTYPE html>
<body>
<pre id="log"></pre>
<script src="../resources/runner.js"></script>
<script src="resources/declarative.js"></script>
<script>
/* See third_party/blink/perf_tests/shadow_dom/shadow-dom-overhead.html
for methodology here. The differences here are:
1. No declarative shadow DOM is used.
2. A "loop at the end" polyfill is used, which is the fastest
known method for polyfilling declarative Shadow DOM [1].
3. The measurement is done via an iframe, so that the polyfill
can execute synchronously, immediately after parsing.
[1] https://github.com/mfreed7/declarative-shadow-dom/blob/master/README.md#results
*/
const depth = 6;
const copies = 100;
const shadowHtml = getShadowMarkup(false, depth, copies, /*lightDomDuplicates=*/1) + getPolyfillMarkup(/*escapeScript=*/true);
const lightDomHtml1 = getShadowMarkup(false, depth, copies, /*lightDomDuplicates=*/1);
const lightDomHtml2 = getShadowMarkup(false, depth, copies, /*lightDomDuplicates=*/2);
async function runSingleTest() {
let samples = [];
for (let i = 0; i < 100; i++) {
const t1 = await measureLoadTimeIframe(lightDomHtml1);
const t2 = await measureLoadTimeIframe(shadowHtml);
const t3 = await measureLoadTimeIframe(lightDomHtml2);
if (t2 > t1 && t3 > t1) {
samples.push((t2-t1) / (t3-t1));
}
}
PerfTestRunner.assert_true(samples.length > 3,'Too many skipped measurements');
// The result is the total overhead, in *percent*, *per shadow root*.
return 100*median(samples);
}
let isDone = false;
PerfTestRunner.startMeasureValuesAsync({
description: 'This benchmark tests the overhead of imperative Shadow DOM',
unit: 'percent',
run: async function() {
while (!isDone) {
PerfTestRunner.addRunTestStartMarker();
const test_result = await runSingleTest();
PerfTestRunner.measureValueAsync(test_result);
PerfTestRunner.addRunTestEndMarker();
}
},
done: () => {isDone = true;},
warmUpCount: 2,
iterationCount: 30,
});
</script>

View file

@ -0,0 +1,106 @@
(function() {
const lightDomElementName = 'x-elemnt'; // Must have the same length as 'template'
function getSnippet(useShadowDom,innerContent,lightDomDuplicates) {
innerContent = innerContent ?? '<span><!--This is the leaf node--></span>';
lightDomDuplicates = lightDomDuplicates ?? 1;
PerfTestRunner.assert_true(!useShadowDom || lightDomDuplicates === 1,'Only light dom content can use duplicates');
let openTag = useShadowDom ? '<template shadowrootmode=open>' : `<${lightDomElementName} shadowrootmode=open>`;
let closeTag = useShadowDom ? '</template>' : `</${lightDomElementName}>`;
let hiddenLightDomContent = useShadowDom ? '<span>Some non-slotted light dom content</span>' : '<!-- Some hidden light-dom content here -->';
let extraCopies = '';
while (lightDomDuplicates>1) {
extraCopies += `${openTag}${closeTag}`;
--lightDomDuplicates;
}
return `<div class="host">${extraCopies}${openTag}${innerContent}<span><!--Shadow content here--></span>${closeTag}${hiddenLightDomContent}</div>`;
}
function getShadowMarkup(useShadowDom, depth, copies, lightDomDuplicates) {
let snippet = undefined;
for (let d=0;d<depth;++d) {
snippet = getSnippet(useShadowDom, snippet, lightDomDuplicates);
}
let html = '<!DOCTYPE html><body>';
for(let i=0;i<copies;++i) {
html += snippet;
}
return html;
}
function getPolyfillMarkup(escapeClosingTag) {
escapeClosingTag = escapeClosingTag ?? true;
return `<script>
document.querySelectorAll('${lightDomElementName}').forEach(element => {
const shadowRoot = element.parentNode.attachShadow({ mode: 'open' });
shadowRoot.replaceChildren(...Array.from(element.childNodes));
element.remove();
});
<${escapeClosingTag ? '\/' : '/'}script>
}`
}
const domParser = new DOMParser();
function parseHtml(html) {
return Document.parseHTMLUnsafe(html);
}
function measureParse(html) {
let start = PerfTestRunner.now();
parseHtml(html);
return PerfTestRunner.now() - start;
}
function parseAndAppend(parent, html) {
const fragment = Document.parseHTMLUnsafe(html);
parent.replaceChildren(...fragment.body.childNodes);
}
function measureParseAndAppend(parent, html) {
parent.replaceChildren(); // Ensure empty
let start = PerfTestRunner.now();
parseAndAppend(parent, html);
return PerfTestRunner.now() - start;
}
async function measureLoadTimeIframe(html) {
return new Promise((resolve, reject) => {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.srcdoc = html;
iframe.onload = () => {
resolve(PerfTestRunner.now() - start);
iframe.remove();
};
let start = PerfTestRunner.now();
document.body.appendChild(iframe);
});
}
function median(data) {
data.sort();
const middle = Math.floor(data.length / 2);
return data.length % 2 ? data[middle] : (data[middle - 1] + data[middle]) / 2;
}
// Do some double-checks that things are working:
function testParse(html) {
const test_div = document.createElement('div');
measureParseAndAppend(test_div, html);
return test_div;
}
PerfTestRunner.assert_true(HTMLTemplateElement.prototype.hasOwnProperty("shadowRootMode"),'Declarative Shadow DOM not enabled/supported');
PerfTestRunner.assert_true(testParse(getShadowMarkup(true, 1, 1)).firstChild.shadowRoot,'Declarative Shadow DOM not detected');
PerfTestRunner.assert_true(getShadowMarkup(true, 5, 6).length === getShadowMarkup(false, 5, 6).length,'Shadow and light DOM content should have identical length');
const light1 = testParse(getShadowMarkup(false, 5, 6, /*lightDomDuplicates=*/1)).querySelectorAll(lightDomElementName).length;
const light2 = testParse(getShadowMarkup(false, 5, 6, /*lightDomDuplicates=*/2)).querySelectorAll(lightDomElementName).length;
PerfTestRunner.assert_true(light1*2 === light2,"The lightDomDuplicates parameter isn't working");
window.parseHtml = parseHtml;
window.measureParse = measureParse;
window.parseAndAppend = parseAndAppend;
window.measureParseAndAppend = measureParseAndAppend;
window.measureLoadTimeIframe = measureLoadTimeIframe;
window.getShadowMarkup = getShadowMarkup;
window.getPolyfillMarkup = getPolyfillMarkup;
window.median = median;
})();

View file

@ -0,0 +1,51 @@
<!DOCTYPE html>
<body>
<pre id="log"></pre>
<script src="../resources/runner.js"></script>
<script src="resources/declarative.js"></script>
<script>
/* See third_party/blink/perf_tests/shadow_dom/shadow-dom-overhead.html
for methodology here. The only difference here is that this version
uses iframe-based measurement, instead of DOMParser-based measurement.
*/
const depth = 6;
const copies = 100;
const shadowHtml = getShadowMarkup(true, depth, copies);
const lightDomHtml1 = getShadowMarkup(false, depth, copies, /*lightDomDuplicates=*/1);
const lightDomHtml2 = getShadowMarkup(false, depth, copies, /*lightDomDuplicates=*/2);
async function runSingleTest() {
let samples = [];
for (let i = 0; i < 100; i++) {
const t1 = await measureLoadTimeIframe(lightDomHtml1);
const t2 = await measureLoadTimeIframe(shadowHtml);
const t3 = await measureLoadTimeIframe(lightDomHtml2);
if (t2 > t1 && t3 > t1) {
samples.push((t2-t1) / (t3-t1));
}
}
PerfTestRunner.assert_true(samples.length > 3,'Too many skipped measurements');
// The result is the total overhead, in *percent*, *per shadow root*.
return 100*median(samples);
}
let isDone = false;
PerfTestRunner.startMeasureValuesAsync({
description: 'This benchmark tests the overhead of declarative Shadow DOM (via iframe)',
unit: 'percent',
run: async function() {
while (!isDone) {
PerfTestRunner.addRunTestStartMarker();
const test_result = await runSingleTest();
PerfTestRunner.measureValueAsync(test_result);
PerfTestRunner.addRunTestEndMarker();
}
},
done: () => {isDone = true;},
warmUpCount: 2,
iterationCount: 30,
});
</script>

View file

@ -0,0 +1,61 @@
<!DOCTYPE html>
<body>
<pre id="log"></pre>
<script src="../resources/runner.js"></script>
<script src="resources/declarative.js"></script>
<script>
/*
This test tries to measure the overhead associated with parsing and
attaching one shadow root to the tree, as compared to an "ordinary"
element. To measure this, we make three timing measurements:
t1 = A + (b + d)x
t2 = A + (c + d)x
t3 = A + (2b + d)x
where:
A = fixed overhead associated with parsing and tree building
b = per light-dom "ordinary" node time
c = per shadow root time
d = per "other element" node time. This is the other elements, e.g. <span>
and <!-- comment --> nodes, mixed in with the test markup. To solve
for the percent overhead, we need this to be non-zero (see below).
x = number of repeats of the test block, per run of the test.
we want to solve for:
(c/b - 1) = percent overhead, per shadow root
So:
(t2 - t1)/x = c - b = (c/b - 1)b
(t3 - t1)/x = b
(c/b - 1) = (t2-t1)/(t3-t1) = percent overhead
*/
const depth = 6;
const copies = 100;
const shadowHtml = getShadowMarkup(true, depth, copies);
const lightDomHtml1 = getShadowMarkup(false, depth, copies, /*lightDomDuplicates=*/1);
const lightDomHtml2 = getShadowMarkup(false, depth, copies, /*lightDomDuplicates=*/2);
PerfTestRunner.measureValue({
description: 'This benchmark tests the overhead of declarative Shadow DOM',
unit: 'percent',
run: function() {
let samples = [];
for (let i = 0; i < 100; i++) {
const t1 = measureParse(lightDomHtml1);
const t2 = measureParse(shadowHtml);
const t3 = measureParse(lightDomHtml2);
if (t2 > t1 && t3 > t1) {
samples.push((t2-t1) / (t3-t1));
}
}
PerfTestRunner.assert_true(samples.length > 3,'Too many skipped measurements');
// The result is the total overhead, in *percent*, *per shadow root*.
return 100*median(samples);
},
warmUpCount: 2,
iterationCount: 30,
});
</script>

View file

@ -0,0 +1,53 @@
<!doctype html>
<style>
div {
color: orange;
}
</style>
<script src="../resources/runner.js"></script>
<script>
var listSize = 1000;
window.onload = function() {
PerfTestRunner.measureTime({
description: "Measures performance of creating and rendering elements with shadow roots from templates (contains attribute selector styles).",
run: function() {
var list = document.querySelector('#list');
var tmpl = document.querySelector("#tmpl");
list.innerHTML = '';
var start = PerfTestRunner.now();
var i = 0;
do {
var host = document.createElement('div');
var root = host.attachShadow({mode:'open'});
root.appendChild(tmpl.content.cloneNode(true));
var light = document.createElement('div');
list.appendChild(host);
} while (++i < listSize);
PerfTestRunner.forceLayout();
return PerfTestRunner.now() - start;
}
});
}
</script>
<template id="tmpl">
<!-- None of these styles match, but that's on purpose -->
<style>
[foo] {
color: blue;
}
[bar] {
color: red;
}
[baz] {
color: green;
}
</style>
<div>
<div>
<div>item</div>
</div>
</div>
<slot></slot>
</template>
<section id="list"></section>

View file

@ -0,0 +1,40 @@
<!doctype html>
<script src="../resources/runner.js"></script>
<script>
var listSize = 1000;
window.onload = function() {
PerfTestRunner.measureTime({
description: "Measures performance of creating and rendering elements with shadow roots from templates (contains class selector styles and a media query).",
run: function() {
var list = document.querySelector('#list');
var tmpl = document.querySelector("#tmpl");
list.innerHTML = '';
var start = PerfTestRunner.now();
var i = 0;
do {
var host = document.createElement('div');
var root = host.attachShadow({mode:'open'});
root.appendChild(tmpl.content.cloneNode(true));
var light = document.createElement('div');
list.appendChild(host);
} while (++i < listSize);
PerfTestRunner.forceLayout();
return PerfTestRunner.now() - start;
}
});
}
</script>
<template id="tmpl">
<style>
@media (max-width: 600px) {
div { color: red; }
}
.foo { color: black; }
.bar { color: blue; }
.baz { color: green; }
.bat { color: orange; }
</style>
<div>item</div>
</template>
<section id="list"></section>

View file

@ -0,0 +1,46 @@
<!doctype html>
<style>
.foo {
border: 1px solid black;
padding: 10px;
}
.foo .bar.baz {
color: blue;
}
</style>
<script src="../resources/runner.js"></script>
<script>
var listSize = 1000;
window.onload = function() {
var root = document.querySelector('#list').attachShadow({mode:'open'});
root.appendChild(document.createElement('slot'));
PerfTestRunner.measureTime({
description: "Measures performance of creating and rendering elements with shadow roots (contains class descendant selector styles).",
setup: function() {
},
run: function() {
var frag = document.createDocumentFragment();
var tmpl = document.querySelector('#tmpl');
var start = PerfTestRunner.now();
var i = 0;
do {
frag.appendChild(tmpl.content.cloneNode(true));;
} while (++i < listSize);
document.querySelector('#list').appendChild(frag);
PerfTestRunner.forceLayout();
return PerfTestRunner.now() - start;
},
done: function() {
document.querySelector('#list').innerHTML = '';
}
});
}
</script>
<template id="tmpl">
<div class="foo">
<div class="bar baz">item</div>
</div>
</template>
<section id="list"></section>

View file

@ -0,0 +1,49 @@
<!doctype html>
<style>
div {
color: orange;
}
</style>
<script src="../resources/runner.js"></script>
<script>
var listSize = 100;
window.onload = function() {
PerfTestRunner.measureTime({
description: "Measures performance of creating and rendering elements with shadow roots from templates (contains class descendant selector styles).",
run: function() {
var list = document.querySelector('#list');
var tmpl = document.querySelector("#tmpl");
list.innerHTML = '';
var start = PerfTestRunner.now();
var i = 0;
do {
var host = document.createElement('div');
var root = host.attachShadow({mode:'open'});
root.appendChild(tmpl.content.cloneNode(true));
var light = document.createElement('div');
list.appendChild(host);
} while (++i < listSize);
PerfTestRunner.forceLayout();
return PerfTestRunner.now() - start;
}
});
}
</script>
<template id="tmpl">
<style>
.foo .bar .foo.bar.baz {
color: blue;
}
.bar.baz {
color: red;
}
</style>
<div class="foo">
<div class="bar">
<div class="foo bar baz">item</div>
</div>
</div>
<content></content>
</template>
<section id="list"></section>

View file

@ -0,0 +1,37 @@
<!doctype html>
<html>
<head>
<script src="../resources/runner.js"></script>
</head>
<body><iframe></iframe>
</body>
<script>
function setup() {
var frame = document.getElementsByTagName("iframe")[0];
var testDoc = frame.contentDocument;
testDoc.body.innerHTML = "<head></head><body><div id='sandbox'></div></body>";
return testDoc;
}
PerfTestRunner.measureTime({
description: "Measures performance of inserting CSS into the shadow DOM.",
run: function() {
var testDoc = setup();
var start = PerfTestRunner.now();
var sandbox = testDoc.getElementById('sandbox');
for (var i = 0; i < 100; i++) {
var elementA = testDoc.createElement("div");
var shadowRootForA = elementA.attachShadow({mode:'open'});
shadowRootForA.innerHTML = "<style>@host{:scope{display:block}}ul.canaryisdev li:nth-of-type(4)::after{content:'canary/dev'}ul.canaryisdev li:nth-of-type(5)::after{content:'beta'}ul.canaryisdev li:nth-of-type(6)::after{content:'stable'}ul.canaryisdev li:nth-of-type(7)::after{content:''}ul.betaisdev li:nth-of-type(4)::after{content:'canary'}ul.betaisdev li:nth-of-type(5)::after{content:'dev/beta'}ul.betaisdev li:nth-of-type(6)::after{content:'stable'}ul.betaisdev li:nth-of-type(7)::after{content:''}ul li{cursor:pointer;padding:3px 0}ul li::before{-webkit-transition:all 500ms ease;-moz-transition:all 500ms ease;-o-transition:all 500ms ease;transition:all 500ms ease;content:'';margin-right:5px;border-left:3px solid transparent}ul li::after{font-size:75%;margin-left:10px}ul li:first-of-type,ul li:nth-of-type(2),ul li:nth-of-type(3){font-size:75%;font-style:italic}ul li:nth-of-type(3){border-bottom:1px solid #d4d4d4;padding-bottom:10px;margin-bottom:5px}ul li:nth-of-type(4)::after{content:'canary'}ul li:nth-of-type(5)::after{content:'dev'}ul li:nth-of-type(6)::after{content:'beta'}ul li:nth-of-type(7)::after{content:'stable'}ul li[selected]{font-weight:600;color:#366597}ul li[selected]::before{border-color:#366597}</style><div>A</div>";
sandbox.appendChild(elementA);
var elementB = testDoc.createElement("div");
var shadowRootForB = elementB.attachShadow({mode:'open'});
shadowRootForB.innerHTML = "<style>@host{:scope{display:block;padding:1px}}.milestone-marker{text-align:right;text-transform:uppercase;margin-top:10px;font-weight:600;font-size:14px;color:#366597}@media only screen and (max-width: 700px){[data-first-of-milestone]:after{font-size:12px;font-weight:normal;top:-22px;opacity:1;text-transform:uppercase}}</style><div>B</div>";
sandbox.appendChild(elementB);
}
return PerfTestRunner.now() - start;
}
});
</script>
</html>

View file

@ -0,0 +1,101 @@
function createDeepDiv(nest) {
const x = document.createElement('div');
if (nest > 0)
x.appendChild(createDeepDiv(nest - 1));
return x;
}
function createDeepComponent(nest) {
// Creating a nested component where a node is re-distributed into a slot in
// a despendant shadow tree
const div = document.createElement('div');
div.appendChild(document.createElement('slot'));
div.appendChild(document.createElement('p'));
if (nest > 0) {
const shadowRoot = div.attachShadow({ mode: 'open' });
shadowRoot.appendChild(createDeepComponent(nest - 1));
}
return div;
}
function createHostTree(hostChildren) {
return createHostTreeWith({
hostChildren,
createChildFunction: () => document.createElement('div'),
});
}
function createHostTreeWithDeepComponentChild(hostChildren) {
return createHostTreeWith({
hostChildren,
createChildFunction: () => createDeepComponent(100),
});
}
function GetDeepestFirstChild(firstChild) {
// Assuming a shadow root's first child always exists, and it can be a host.
// createDeepComponent constructs such a tree.
if (!firstChild.shadowRoot)
return firstChild;
return GetDeepestFirstChild(firstChild.shadowRoot.firstChild);
}
function rotateChildren(parent) {
// A tree structure will change, rotating children.
const firstChild = parent.firstChild;
firstChild.remove();
parent.appendChild(firstChild);
}
function removeLastChildAndAppend(host) {
// A tree structure won't change
const lastChild = host.lastChild;
lastChild.remove();
host.appendChild(lastChild);
}
function createHostTreeWith({hostChildren, createChildFunction}) {
const host = document.createElement('div');
host.id = 'host';
for (let i = 0; i < hostChildren; ++i) {
const div = createChildFunction();
host.appendChild(div);
}
const shadowRoot = host.attachShadow({ mode: 'open' });
shadowRoot.appendChild(document.createElement('slot'));
return host;
}
function runHostChildrenMutationThenGetDistribution(host, loop) {
const slot = host.shadowRoot.querySelector('slot');
for (let i = 0; i < loop; ++i) {
const firstChild = host.firstChild;
firstChild.remove();
host.appendChild(firstChild);
slot.assignedNodes({ flatten: true });
}
}
function runHostChildrenMutationThenLayout(host, loop) {
for (let i = 0; i < loop; ++i) {
const firstChild = host.firstChild;
firstChild.remove();
host.appendChild(firstChild);
PerfTestRunner.forceLayout();
}
}
function runHostChildrenMutationAppendThenLayout(host, loop) {
for (let i = 0; i < loop; ++i) {
host.appendChild(document.createElement('div'));
PerfTestRunner.forceLayout();
}
}
function runHostChildrenMutationPrependThenLayout(host, loop) {
for (let i = 0; i < loop; ++i) {
host.insertBefore(document.createElement('div'), host.firstChild);
PerfTestRunner.forceLayout();
}
}

View file

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="../resources/runner.js"></script>
<script type="text/javascript" src="./v1-common.js"></script>
</head>
<body>
<div id="parent"></div>
<script>
const hostChildren = 500;
const host = createHostTreeWithDeepComponentChild(hostChildren);
document.querySelector('#parent').appendChild(host);
for (let i = 0; i < hostChildren; ++i) {
host.children[i].id = i;
}
PerfTestRunner.measureTime({
description: `Ensure slot assignment doesn't run for a disconnected and reconnected tree`,
run: () => {
for (let i = 0; i < hostChildren; ++i) {
const firstChild = host.firstChild;
firstChild.remove();
firstChild.shadowRoot.querySelector('slot').assignedNodes();
host.appendChild(firstChild);
firstChild.shadowRoot.querySelector('slot').assignedNodes();
}
PerfTestRunner.assert_true(host.firstChild.id === "0", 'The test loop should end as it started');
},
});
</script>
</body>
</html>

View file

@ -0,0 +1,47 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<!-- This is a micro benchmark to catch an unintentional regression.
If the reason of a regression is clear, it is okay.
We do not have to optimize the result of the benchmark. -->
<div id="wrapper">
<div id="host"></div>
</div>
<script>
'use strict';
const numChildOfHost = 10;
const numDivsInShadow = 100;
const loops = 20;
for (let i = 0; i < numChildOfHost; ++i) {
let div = document.createElement('div');
div.appendChild(document.createTextNode('div' + i));
host.appendChild(div);
}
const slot = document.createElement('slot');
const shadowRoot = host.attachShadow({mode: 'open'});
shadowRoot.appendChild(slot);
for (let i = 0; i < numDivsInShadow; ++i) {
let div = document.createElement('div');
shadowRoot.appendChild(div);
}
function run() {
let div = document.createElement('div');
for (let i = 0; i < loops; ++i) {
host.appendChild(div);
slot.assignedNodes({flatten: true});
host.removeChild(div);
slot.assignedNodes({flatten: true});
}
}
PerfTestRunner.measureTime({
description: "Measure v1 distribution performance",
run: run,
done: () => {
wrapper.innerHTML = '';
}
});
</script>

View file

@ -0,0 +1,49 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<!-- This is a micro benchmark to catch an unintentional regression.
If the reason of a regression is clear, it is okay.
We do not have to optimize the result of the benchmark. -->
<div id="wrapper">
<div id="host"></div>
</div>
<script>
'use strict';
const numChildOfHost = 50;
const numDivsInShadow = 20;
const loops = 20;
const slot1 = document.createElement('slot');
slot1.setAttribute('name', 'slot1');
const slot2 = document.createElement('slot');
slot2.setAttribute('name', 'slot2');
const shadowRoot = host.attachShadow({mode: 'open'});
shadowRoot.appendChild(slot1);
shadowRoot.appendChild(slot2);
for (let i = 0; i < numDivsInShadow; ++i) {
let div = document.createElement('div');
shadowRoot.appendChild(div);
}
function run() {
for (let i = 0; i < loops; ++i) {
for (let j = 0; j < numChildOfHost; ++j) {
let div1 = document.createElement('div');
div1.setAttribute('slot', 'slot1');
host.appendChild(div1);
let div2 = document.createElement('div');
div2.setAttribute('slot', 'slot2');
host.appendChild(div2);
}
host.innerHTML = '';
}
}
PerfTestRunner.measureTime({
description: "Measure v1 distribution performance",
run: run,
done: () => {
wrapper.innerHTML = '';
}
});
</script>

View file

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="../resources/runner.js"></script>
<script type="text/javascript" src="./v1-common.js"></script>
</head>
<body>
<div id="parent"></div>
<script>
const hostChildren = 100;
const loop = 10;
let host;
PerfTestRunner.measureTime({
description: `Measure distribution (${hostChildren} deep host children)`,
setup: () => {
host = createHostTreeWithDeepComponentChild(hostChildren);
document.querySelector('#parent').appendChild(host);
},
run: () => {
runHostChildrenMutationThenGetDistribution(host, loop);
},
teardown: () => {
host.remove();
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="../resources/runner.js"></script>
<script type="text/javascript" src="./v1-common.js"></script>
</head>
<body>
<div id="parent"></div>
<script>
const hostChildren = 100;
const loop = 10;
let host;
PerfTestRunner.measureTime({
description: `Measure layout (${hostChildren} deep host children)`,
setup: () => {
host = createHostTreeWithDeepComponentChild(hostChildren);
document.querySelector('#parent').appendChild(host);
},
run: () => {
runHostChildrenMutationThenLayout(host, loop);
},
teardown: () => {
host.remove();
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="../resources/runner.js"></script>
<script type="text/javascript" src="./v1-common.js"></script>
</head>
<body>
<div id="parent"></div>
<script>
const hostChildren = 100;
const loop = 10;
let host;
PerfTestRunner.measureTime({
description: `Measure layout (${hostChildren} shallow host children)`,
setup: () => {
host = createHostTree(hostChildren);
document.querySelector('#parent').appendChild(host);
},
run: () => {
runHostChildrenMutationAppendThenLayout(host, loop);
},
teardown: () => {
host.remove();
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="../resources/runner.js"></script>
<script type="text/javascript" src="./v1-common.js"></script>
</head>
<body>
<div id="parent"></div>
<script>
const hostChildren = 100;
const loop = 10;
let host;
PerfTestRunner.measureTime({
description: `Measure distribution (${hostChildren} shallow host children)`,
setup: () => {
host = createHostTree(hostChildren);
document.querySelector('#parent').appendChild(host);
},
run: () => {
runHostChildrenMutationThenGetDistribution(host, loop);
},
teardown: () => {
host.remove();
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="../resources/runner.js"></script>
<script type="text/javascript" src="./v1-common.js"></script>
</head>
<body>
<div id="parent"></div>
<script>
const hostChildren = 100;
const loop = 10;
let host;
PerfTestRunner.measureTime({
description: `Measure layout (${hostChildren} shallow host children)`,
setup: () => {
host = createHostTree(hostChildren);
document.querySelector('#parent').appendChild(host);
},
run: () => {
runHostChildrenMutationThenLayout(host, loop);
},
teardown: () => {
host.remove();
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="../resources/runner.js"></script>
<script type="text/javascript" src="./v1-common.js"></script>
</head>
<body>
<div id="parent"></div>
<script>
const hostChildren = 100;
const loop = 10;
let host;
PerfTestRunner.measureTime({
description: `Measure layout (${hostChildren} shallow host children)`,
setup: () => {
host = createHostTree(hostChildren);
document.querySelector('#parent').appendChild(host);
},
run: () => {
runHostChildrenMutationPrependThenLayout(host, loop);
},
teardown: () => {
host.remove();
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="../resources/runner.js"></script>
<script type="text/javascript" src="./v1-common.js"></script>
</head>
<body>
<div id="parent"></div>
<script>
const deepNestLevel = 50;
const loop = 100;
let host;
let deepSlot;
PerfTestRunner.measureTime({
description: `Measure distribution`,
setup: () => {
host = createDeepComponent(deepNestLevel);
document.querySelector('#parent').appendChild(host);
deepSlot = GetDeepestFirstChild(host).querySelector('slot');
document.body.offsetLeft;
},
run: () => {
for (let i = 0; i < loop; ++i) {
rotateChildren(deepSlot.parentNode);
document.body.offsetLeft;
}
},
teardown: () => {
host.remove();
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="../resources/runner.js"></script>
<script type="text/javascript" src="./v1-common.js"></script>
</head>
<body>
<div id="parent"></div>
<script>
const deepNestLevel = 50;
const loop = 100;
let host;
let deepSlot;
PerfTestRunner.measureTime({
description: `Measure distribution`,
setup: () => {
host = createDeepComponent(deepNestLevel);
document.querySelector('#parent').appendChild(host);
deepSlot = GetDeepestFirstChild(host).querySelector('slot');
document.body.offsetLeft;
},
run: () => {
for (let i = 0; i < loop; ++i) {
rotateChildren(deepSlot.parentNode);
deepSlot.assignedNodes();
}
},
teardown: () => {
host.remove();
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="../resources/runner.js"></script>
<script type="text/javascript" src="./v1-common.js"></script>
</head>
<body>
<div id="parent"></div>
<script>
const deepNestLevel = 50;
const loop = 100;
let host;
let deepSlot;
PerfTestRunner.measureTime({
description: `Measure distribution`,
setup: () => {
host = createDeepComponent(deepNestLevel);
document.querySelector('#parent').appendChild(host);
deepSlot = GetDeepestFirstChild(host).querySelector('slot');
document.body.offsetLeft;
},
run: () => {
for (let i = 0; i < loop; ++i) {
rotateChildren(deepSlot.parentNode);
deepSlot.assignedNodes({ flatten: true });
}
},
teardown: () => {
host.remove();
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="../resources/runner.js"></script>
<script type="text/javascript" src="./v1-common.js"></script>
</head>
<body>
<div id="parent"></div>
<script>
const deepNestLevel = 50;
const loop = 100;
let host;
PerfTestRunner.measureTime({
description: `Measure distribution`,
setup: () => {
host = createDeepComponent(deepNestLevel);
document.querySelector('#parent').appendChild(host);
document.body.offsetLeft;
},
run: () => {
for (let i = 0; i < loop; ++i) {
rotateChildren(host);
document.body.offsetLeft;
}
},
teardown: () => {
host.remove();
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="../resources/runner.js"></script>
<script type="text/javascript" src="./v1-common.js"></script>
</head>
<body>
<div id="parent"></div>
<script>
const deepNestLevel = 50;
const loop = 100;
let host;
let deepSlot;
PerfTestRunner.measureTime({
description: `Measure distribution`,
setup: () => {
host = createDeepComponent(deepNestLevel);
document.querySelector('#parent').appendChild(host);
deepSlot = GetDeepestFirstChild(host).querySelector('slot');
document.body.offsetLeft;
},
run: () => {
for (let i = 0; i < loop; ++i) {
rotateChildren(host);
deepSlot.assignedNodes();
}
},
teardown: () => {
host.remove();
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="../resources/runner.js"></script>
<script type="text/javascript" src="./v1-common.js"></script>
</head>
<body>
<div id="parent"></div>
<script>
const deepNestLevel = 50;
const loop = 100;
let host;
let deepSlot;
PerfTestRunner.measureTime({
description: `Measure distribution`,
setup: () => {
host = createDeepComponent(deepNestLevel);
document.querySelector('#parent').appendChild(host);
deepSlot = GetDeepestFirstChild(host).querySelector('slot');
document.body.offsetLeft;
},
run: () => {
for (let i = 0; i < loop; ++i) {
rotateChildren(host);
deepSlot.assignedNodes({ flatten: true });
}
},
teardown: () => {
host.remove();
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,55 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<!-- This is a micro benchmark to catch an unintentional regression.
If the reason of a regression is clear, it is okay.
We do not have to optimize the result of the benchmark. -->
<div id="wrapper">
<div id="host"></div>
</div>
<script>
'use strict';
const numChildOfHost = 20;
const numDivsInShadow = 20;
const loops = 100;
const slot1 = document.createElement('slot');
slot1.setAttribute('name', 'slot1');
const slot2 = document.createElement('slot');
slot2.setAttribute('name', 'slot2');
const shadowRoot = host.attachShadow({mode: 'open'});
shadowRoot.appendChild(slot1);
shadowRoot.appendChild(slot2);
for (let i = 0; i < numDivsInShadow; ++i) {
let div = document.createElement('div');
shadowRoot.appendChild(div);
}
for (let i = 0; i < numChildOfHost; ++i) {
let div1 = document.createElement('div');
div1.setAttribute('slot', 'slot1');
host.appendChild(div1);
let div2 = document.createElement('div');
div2.setAttribute('slot', 'slot2');
host.appendChild(div2);
}
function run() {
for (let i = 0; i < loops; ++i) {
const slot3 = document.createElement('slot');
slot1.setAttribute('name', 'slot3');
const slot4 = document.createElement('slot');
slot1.setAttribute('name', 'slot1');
slot3.remove();
slot4.remove();
}
}
PerfTestRunner.measureTime({
description: "Measure v1 distribution performance",
run: run,
done: () => {
wrapper.innerHTML = '';
}
});
</script>

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="../resources/runner.js"></script>
<script type="text/javascript" src="./v1-common.js"></script>
</head>
<body>
<div id="parent"></div>
<script>
const hostChildren = 10;
const loop = 10;
let host;
PerfTestRunner.measureTime({
description: `Measure distribution (${hostChildren} deep host children)`,
setup: () => {
host = createHostTreeWithDeepComponentChild(hostChildren);
document.querySelector('#parent').appendChild(host);
document.body.offsetLeft;
},
run: () => {
runHostChildrenMutationThenGetDistribution(host, loop);
},
teardown: () => {
host.remove();
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="../resources/runner.js"></script>
<script type="text/javascript" src="./v1-common.js"></script>
</head>
<body>
<div id="parent"></div>
<script>
const hostChildren = 10;
const loop = 10;
let host;
PerfTestRunner.measureTime({
description: `Measure layout (${hostChildren} deep host children)`,
setup: () => {
host = createHostTreeWithDeepComponentChild(hostChildren);
document.querySelector('#parent').appendChild(host);
document.body.offsetLeft;
},
run: () => {
runHostChildrenMutationThenLayout(host, loop);
},
teardown: () => {
host.remove();
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="../resources/runner.js"></script>
<script type="text/javascript" src="./v1-common.js"></script>
</head>
<body>
<div id="parent"></div>
<script>
const hostChildren = 10;
const loop = 10;
let host;
PerfTestRunner.measureTime({
description: `Measure distribution (${hostChildren} shallow host children)`,
setup: () => {
host = createHostTree(hostChildren);
document.querySelector('#parent').appendChild(host);
},
run: () => {
runHostChildrenMutationThenGetDistribution(host, loop);
},
teardown: () => {
host.remove();
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="../resources/runner.js"></script>
<script type="text/javascript" src="./v1-common.js"></script>
</head>
<body>
<div id="parent"></div>
<script>
const hostChildren = 10;
const loop = 10;
let host;
PerfTestRunner.measureTime({
description: `Measure layout (${hostChildren} shallow host children)`,
setup: () => {
host = createHostTree(hostChildren);
document.querySelector('#parent').appendChild(host);
document.body.offsetLeft;
},
run: () => {
runHostChildrenMutationThenLayout(host, loop);
},
teardown: () => {
host.remove();
}
});
</script>
</body>
</html>