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 @@
mixins: "//third_party/blink/renderer/core/dom/COMMON_METADATA"

View file

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

View file

@ -0,0 +1,23 @@
<!doctype html>
<script src="../resources/runner.js"></script>
<div id="container"></div>
<script>
const formCount = 1000;
const forms = {}
const container = document.getElementById("container")
for (let i = 0; i < formCount; i++) {
container.insertAdjacentHTML("beforeend",
`<form id="form-${i}"></form>`)
const form = document.getElementById(`form-${i}`)
forms[form.id] = form;
}
PerfTestRunner.measureRunsPerSecond({
description: 'Measure the speed of cloning body containing form elements inserted into a collection',
run: () => {
document.body.cloneNode(true);
}
});
</script>

View file

@ -0,0 +1,28 @@
<script src="../resources/runner.js"></script>
<div id="holder"></div>
<script>
const holderElement = document.getElementById("holder");
customElements.define('my-element', class MyElement extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<style>:host { color: red; }</style>';
}
});
PerfTestRunner.measureRunsPerSecond({
description: "Measures performance of styling custom elements with shadowRoot's innerHTML.",
run: function() {
holderElement.textContent = '';
for (i = 0; i < 100; i++) {
holderElement.appendChild(document.createElement('my-element'));
// force layout.
document.body.offsetWidth;
}
},
warmUpCount: 1,
});
</script>

View file

@ -0,0 +1,30 @@
<script src="../resources/runner.js"></script>
<div id="holder"></div>
<script>
const holderElement = document.getElementById("holder");
const constructedStyleSheet = new CSSStyleSheet();
constructedStyleSheet.insertRule("* { color: red; }");
window.customElements.define("element-constructed", class extends HTMLElement {
constructor() {
super();
}
}, { style: constructedStyleSheet });
PerfTestRunner.measureRunsPerSecond({
description: "Measures performance of styling custom elements with default style.",
run: function() {
holderElement.textContent = '';
for (i = 0; i < 100; i++) {
holderElement.appendChild(document.createElement('element-constructed'));
// force layout.
document.body.offsetWidth;
}
},
warmUpCount: 1,
});
</script>

View file

@ -0,0 +1,28 @@
<!DOCTYPE html>
<style>
select, ::picker(select) {
appearance: base-select;
}
</style>
<body>
<script src="../resources/runner.js"></script>
<select id="container"></select>
<script>
var container = document.getElementById('container');
var nodes = [];
var childCount = 500;
for (var i = 0; i < childCount; ++i)
nodes.push(document.createElement('option'));
PerfTestRunner.measureRunsPerSecond({
description: 'Measures performance of adding option elements to a single-selection select element.',
run: function() {
for (var i = 0; i < childCount; ++i) {
nodes[i].selected = !!(i % 2);
container.appendChild(nodes[i]);
}
container.innerHTML = '';
}
});
</script>
</body>

View file

@ -0,0 +1,34 @@
<!DOCTYPE html>
<body>
<script src="../resources/runner.js"></script>
<style>
select, ::picker(select) {
appearance: base-select;
}
</style>
<select id="container"></select>
<script>
var container = document.getElementById('container');
var nodes = [];
var childCount = 1000;
for (var i = 0; i < childCount; ++i) {
var option = document.createElement('option');
option.textContent = i;
nodes.push(option);
}
PerfTestRunner.measureRunsPerSecond({
description: 'Measures performance of removing option elements from a single-selection select element.',
run: () => {
for (var i = 0; i < childCount; ++i) {
nodes[i].selected = false;
container.appendChild(nodes[i]);
}
container.offsetLeft;
for (var i = 0; i < childCount; ++i)
container.removeChild(nodes[i]);
}
});
</script>
</body>

View file

@ -0,0 +1,37 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<div id="source" style="display:none">
<div></div>
</div>
<div id="destination" style="display:none"></div>
<script>
'use strict';
var sourceEl = document.getElementById('source').firstElementChild;
var destinationEl = document.getElementById('destination');
function buildTree(depth,parent) {
while (depth--) {
parent = parent.appendChild(document.createElement('span'));
}
}
PerfTestRunner.measureRunsPerSecond({
description: 'Measure the speed of cloning and appending a very deep tree',
setup: () => {
const treeDepth = 10000;
if (!sourceEl.childNodes.length) {
sourceEl.replaceChildren();
buildTree(treeDepth, sourceEl);
PerfTestRunner.assert_true(sourceEl.querySelectorAll('span').length === treeDepth,'Tree depth mismatch');
}
destinationEl.replaceChildren();
PerfTestRunner.gc();
},
run: () => {
destinationEl.appendChild(sourceEl.cloneNode(true));
}
});
</script>

View file

@ -0,0 +1,8 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<script src="resources/dom-parts-api.js"></script>
<body>
<script>
runPerfTest('Parts, flat','appendTime');
</script>

View file

@ -0,0 +1,8 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<script src="resources/dom-parts-api.js"></script>
<body>
<script>
runPerfTest('Parts, flat','cloneTime');
</script>

View file

@ -0,0 +1,8 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<script src="resources/dom-parts-api.js"></script>
<body>
<script>
runPerfTest('Parts, flat','getPartsTime');
</script>

View file

@ -0,0 +1,8 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<script src="resources/dom-parts-api.js"></script>
<body>
<script>
runPerfTest('Manual tree walk','appendTime');
</script>

View file

@ -0,0 +1,8 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<script src="resources/dom-parts-api.js"></script>
<body>
<script>
runPerfTest('Manual tree walk','cloneTime');
</script>

View file

@ -0,0 +1,8 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<script src="resources/dom-parts-api.js"></script>
<body>
<script>
runPerfTest('Manual tree walk','getPartsTime');
</script>

View file

@ -0,0 +1,8 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<script src="resources/dom-parts-api.js"></script>
<body>
<script>
runPerfTest('Parts, nested','appendTime');
</script>

View file

@ -0,0 +1,8 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<script src="resources/dom-parts-api.js"></script>
<body>
<script>
runPerfTest('Parts, nested','cloneTime');
</script>

View file

@ -0,0 +1,8 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<script src="resources/dom-parts-api.js"></script>
<body>
<script>
runPerfTest('Parts, nested','getPartsTime');
</script>

View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<div id="container" dir="auto"></div>
<script>
'use strict';
container.style.display = 'none';
PerfTestRunner.measureRunsPerSecond({
description: 'Measures performance of inserting a text node' +
'as a child of an element having dir=auto attribute',
setup() {
container.innerHTML = "";
},
run() {
for (var i = 0; i < 10000; i++)
container.appendChild(document.createTextNode('a'));
}
});
</script>

View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<div id="container"></div>
<script>
'use strict';
PerfTestRunner.assert_true(typeof window.GCController !== "undefined", 'GCController is required for accurate measurement');
PerfTestRunner.measureTime({
description: 'Adds, then lays out, a long list of sibling elements ' +
'separated by spaces',
setup: () => {
container.parentNode.replaceChild(container.cloneNode(false), container);
PerfTestRunner.gc();
},
run: () => {
const num_words = 30000;
for (var i = 0; i < num_words; i++) {
var a = document.createElement('a');
a.appendChild(document.createTextNode('' + i));
container.appendChild(a);
container.appendChild(document.createTextNode(' '));
}
PerfTestRunner.forceLayout();
}
});
</script>

View file

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<body>
<script src="../resources/runner.js"></script>
<script>
var element;
PerfTestRunner.measureTime({
description: "Measures performance of changing a DOM element's className property.",
setup: function() {
element = document.createElement('div');
},
run: function() {
for (var i = 0; i < 500000; ++i) {
element.className = 'class1';
element.className = 'class2';
}
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<body>
<script src="../resources/runner.js"></script>
<script>
var element;
PerfTestRunner.measureTime({
description: "Measures performance of changing a DOM element's id property.",
setup: function() {
element = document.createElement('div');
},
run: function() {
for (var i = 0; i < 500000; ++i) {
element.id = 'id1';
element.id = 'id2';
}
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<body>
<script src="../resources/runner.js"></script>
<script>
var element;
PerfTestRunner.measureTime({
description: "Measures performance of changing a DOM element's title property.",
setup: function() {
element = document.createElement('div');
},
run: function() {
for (var i = 0; i < 500000; ++i) {
element.title = 'title1';
element.title = 'title2';
}
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<script src="../resources/runner.js"></script>
<div id="container" dir="auto"></div>
<script>
'use strict';
container.style.display = 'none';
var textNode = document.createTextNode('a');
container.appendChild(textNode);
PerfTestRunner.measureRunsPerSecond({
description: 'Measures performance of changing a text data of the child' +
'in an element having dir=auto attribute ',
run() {
for (var i = 0; i < 10000; i++) {
textNode.replaceData(0, 1, "b");
textNode.replaceData(0, 1, "a");
}
}
});
</script>

View file

@ -0,0 +1,268 @@
(() => {
// Creates width <section> elements surrounded (optionally) by ChildNodeParts.
// Each <section> gets width children of the same kind, down to a depth of depth.
// If useParts is false, no Parts are constructed. If useParts is true, and
// chainParts is true, descendant Parts use ancestor Parts as their PartRoot.
// If chainParts is false, all Parts have the DocumentPartRoot as their root.
const createContent = (node, root, useParts, chainParts, width, depth, extra, at = 0) => {
at++;
for (let i=0; i<width; i++) {
const s = document.createComment('start');
const c = document.createElement('section');
c.textContent = `${at}.${i}`;
let extras = [];
for(let e=0;e<extra;e++) {
extras.push(Object.assign(document.createElement('span'),{classList:'extra'}));
}
if (i==0) {
const nodePartNode = document.createElement('section');
nodePartNode.textContent = 'nodepart';
extras.push(nodePartNode);
if (useParts) {
new NodePart(root, nodePartNode);
}
}
const e = document.createComment('e');
node.append(s, c, ...extras, e);
let nextLevelRoot = root;
if (useParts) {
const newPart = new ChildNodePart(root, s, e);
if (chainParts) {
nextLevelRoot = newPart;
}
}
if (at < depth) {
createContent(c, nextLevelRoot, useParts, chainParts, width, depth, extra, at);
}
}
}
const createTemplateWrapper = () => {
const template = document.createElement('template').content;
const wrapper = template.appendChild(document.createElement('div'));
return {template,wrapper};
}
let commentContent,partsFlatContent,partsNestedContent;
let contentCreated = false;
function createAllContent(width, depth, extra) {
contentCreated = true;
commentContent = createTemplateWrapper();
createContent(commentContent.wrapper, commentContent.template.getPartRoot(), /*useParts*/false, 0, width, depth, extra);
partsFlatContent = createTemplateWrapper();
createContent(partsFlatContent.wrapper, partsFlatContent.template.getPartRoot(), /*useParts*/true, /*chainParts*/false, width, depth, extra);
partsNestedContent = createTemplateWrapper();
createContent(partsNestedContent.wrapper, partsNestedContent.template.getPartRoot(), /*useParts*/true, /*chainParts*/true, width, depth, extra);
}
const errorCheck = (container) => {
if (!contentCreated) {
throw new Error('content must be created with createAllContent()');
}
if (!container.isConnected || container.ownerDocument != document) {
throw new Error('container must be in the document');
}
if (container.childNodes.length !== 0) {
throw new Error('container must be empty');
}
if (document.getPartRoot().getParts().length !== 0) {
throw new Error('test needs to start with no attached parts');
}
return {container};
}
const recursiveGetParts = (root,level=1) => {
let parts = Array.from(root.getParts());
const thisParts = [...parts];
for(let part of thisParts) {
if (part.getParts) {
parts.push(...recursiveGetParts(part,level+1));
}
}
return parts;
}
const countNodes = (node) => {
let c = 1;
node.childNodes.forEach(child => {
c += countNodes(child);
});
return c;
}
const countNodesAndParts = (state) => {
const nodes = countNodes(state.container);
const root = state.container.ownerDocument.getPartRoot();
let parts,nodeParts,childNodeParts;
if (!root.getParts().length) {
partCount = state.parts.length;
nodeParts = state.parts.filter(p => p instanceof FakeNodePart).length;
childNodeParts = state.parts.filter(p => p instanceof FakeChildNodePart).length;
} else {
const parts = recursiveGetParts(root);
partCount = parts.length;
nodeParts = parts.filter(p => p instanceof NodePart).length;
childNodeParts = parts.filter(p => p instanceof ChildNodePart).length;
}
return {nodes, partCount, nodeParts, childNodeParts};
}
class FakeChildNodePart {
start = null;
end = null;
constructor(start, end) {
this.start = start;
this.end = end;
}
}
class FakeNodePart {
node = null;
constructor(node) {
this.node = node;
}
}
const testList = [
{
test: "Raw, no parts",
prepare: (container) => errorCheck(container),
clone: (state) => {
state.clone = document.importNode(commentContent.template, true);
},
append: (state) => {
state.container.appendChild(state.clone);
},
getParts: (state) => {
state.parts = [];
},
},
{
test: "Manual tree walk",
prepare: (container) => {
state = errorCheck(container);
state.walker = document.createTreeWalker(document, 129 /* NodeFilter.SHOW_{ELEMENT|COMMENT} */);
return state;
},
clone: (state) => {
state.clone = document.importNode(commentContent.template, true);
},
append: (state) => {
state.container.appendChild(state.clone);
},
getParts: (state) => {
const parts = [];
state.walker.currentNode = state.container;
while (state.walker.nextNode()) {
const node = state.walker.currentNode;
if (node.nodeType === Node.COMMENT_NODE && node.textContent === 'start') {
parts.push(new FakeChildNodePart(node,node.nextSibling.nextSibling));
} else if (node.nodeType === Node.ELEMENT_NODE && node.firstChild?.nodeType === Node.TEXT_NODE && node.firstChild.textContent === 'nodepart') {
parts.push(new FakeNodePart(node));
}
}
state.parts = parts;
},
},
{
test: "Parts, flat",
prepare: (container) => errorCheck(container),
clone: (state) => {
state.clone = partsFlatContent.template.getPartRoot().clone().rootContainer;
},
append: (state) => {
state.container.appendChild(state.clone);
},
getParts: (state) => {
state.parts = document.getPartRoot().getParts();
},
},
{
test: "Parts, nested",
prepare: (container) => errorCheck(container),
clone: (state) => {
state.clone = partsNestedContent.template.getPartRoot().clone().rootContainer;
},
append: (state) => {
state.container.appendChild(state.clone);
},
getParts: (state) => {
state.parts = recursiveGetParts(document.getPartRoot());
},
},
];
const runTest = (testCase, repeats, container) => {
let cloneTime = 0;
let appendTime = 0;
let getPartsTime = 0;
let state;
for(let r=0;r<repeats;++r) {
// Clear out the old parts and content:
container.replaceChildren();
document.getPartRoot().getParts();
if (typeof window.GCController !== "undefined") {
// PerfTestRunner is providing GC.
PerfTestRunner.gc();
} else if (self.gc) {
// This requires --js-flags="--expose-gc" on the command line.
self.gc();
} else {
PerfTestRunner.assert_true(false,'This test requires some form of GC access');
}
state = testCase.prepare(container);
// Run the test
const start = performance.now();
testCase.clone(state);
const cloneDone = performance.now();
testCase.append(state);
const appendDone = performance.now();
testCase.getParts(state);
const partsDone = performance.now();
cloneTime += cloneDone - start;
appendTime += appendDone - cloneDone;
getPartsTime += partsDone - appendDone;
}
return {cloneTime, appendTime, getPartsTime, state};
}
const findTestCase = (testType) => {
const indx = testList.findIndex(v => v.test == testType);
PerfTestRunner.assert_true(indx >= 0,`Unable to find test for ${testType}`);
return testList[indx];
}
const runPerfTest = (testType,metric) => {
const width = 4;
const depth = 4;
const extra = 8;
const repeats = 10;
const container = document.createElement('div');
container.style="display:none";
document.body.appendChild(container);
PerfTestRunner.measureValue({
description: `This benchmark tests the ${metric} time for ${testType}, for the DOM Parts API`,
unit: 'ms',
setup: () => {
createAllContent(width, depth, extra);
},
run: function() {
const results = runTest(findTestCase(testType), repeats, container);
return results[metric];
},
warmUpCount: 2,
iterationCount: 30,
});
}
const manualRunTest = (testType, repeats, container) => {
return runTest(findTestCase(testType), repeats, container);
}
// Exposed functions:
window.runPerfTest =runPerfTest;
window.createAllContent = createAllContent;
window.manualRunTest = manualRunTest;
window.countNodesAndParts = countNodesAndParts;
})();

View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<body>
<script src="../resources/runner.js"></script>
<select id="container" multiple></select>
<script>
var container = document.getElementById('container');
var nodes = [];
var childCount = 500;
for (var i = 0; i < childCount; ++i) {
nodes.push(document.createElement('option'));
nodes[i].selected = !!(i % 2);
}
PerfTestRunner.measureRunsPerSecond({
description: 'Measures performance of adding option elements to a multi-selection select element.',
run: function() {
for (var i = 0; i < childCount; ++i)
container.appendChild(nodes[i]);
container.innerHTML = '';
}
});
</script>
</body>

View file

@ -0,0 +1,62 @@
<!DOCTYPE html>
<body>
<script src="../resources/runner.js"></script>
<button>user activation button</button>
<script>
const numOptions = 3000;
const numTestRuns = 6;
let isDone = false;
const button = document.querySelector('button');
async function runTest() {
while (!isDone) {
await new Promise(requestAnimationFrame);
await new Promise(requestAnimationFrame);
const select = document.createElement('select');
for (let i = 0; i < numOptions; i++) {
const option = document.createElement('option');
option.textContent = i;
select.appendChild(option);
}
document.body.appendChild(select);
// Get user activation for select.showPicker()
await new Promise(resolve => {
const buttonBox = button.getBoundingClientRect();
const x = buttonBox.x + (buttonBox.width / 2);
const y = buttonBox.y + (buttonBox.height / 2);
chrome.gpuBenchmarking.pointerActionSequence([{
source: 'mouse',
actions: [
{name: 'pointerDown', x: x, y: y},
{name: 'pointerUp', x: x, y: y}
]
}], resolve);
});
PerfTestRunner.addRunTestStartMarker();
const startTime = PerfTestRunner.now();
select.showPicker();
PerfTestRunner.measureValueAsync(PerfTestRunner.now() - startTime);
PerfTestRunner.addRunTestEndMarker();
select.remove();
}
}
PerfTestRunner.startMeasureValuesAsync({
unit: 'ms',
done: function() {
isDone = true;
},
run: function() {
runTest();
},
iterationCount: numTestRuns,
description: "Measures performance of opening select's picker."
});
</script>

View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<body>
<script src="../resources/runner.js"></script>
<select id="container"></select>
<script>
var container = document.getElementById('container');
var nodes = [];
var childCount = 500;
for (var i = 0; i < childCount; ++i)
nodes.push(document.createElement('option'));
PerfTestRunner.measureRunsPerSecond({
description: 'Measures performance of adding option elements to a single-selection select element.',
run: function() {
for (var i = 0; i < childCount; ++i) {
nodes[i].selected = !!(i % 2);
container.appendChild(nodes[i]);
}
container.innerHTML = '';
}
});
</script>
</body>

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<body>
<script src="../resources/runner.js"></script>
<select id="container"></select>
<script>
var container = document.getElementById('container');
var nodes = [];
var childCount = 1000;
for (var i = 0; i < childCount; ++i) {
var option = document.createElement('option');
option.textContent = i;
nodes.push(option);
}
PerfTestRunner.measureRunsPerSecond({
description: 'Measures performance of removing option elements from a single-selection select element.',
run: () => {
for (var i = 0; i < childCount; ++i) {
nodes[i].selected = false;
container.appendChild(nodes[i]);
}
container.offsetLeft;
for (var i = 0; i < childCount; ++i)
container.removeChild(nodes[i]);
}
});
</script>
</body>