chromium/third_party/blink/manual_tests/dom/dom-parts-api.html

<!DOCTYPE html>
<script src="../../perf_tests/resources/runner.js"></script>
<script src="../../perf_tests/dom/resources/dom-parts-api.js"></script>

<fieldset id=controls disabled>
  <span id=presets></span>
  <label>Width <input type=number id=width></label>
  <label>Depth <input type=number id=depth></label>
  <label>Extra <input type=number id=extra></label>
  <span>
    <label>Total parts: <input type=number disabled id=total_parts></label>
    <label><input type=checkbox id=show_dom>Show cloned content</label>
  </span>
  <label>Total DOM nodes: <input type=number disabled id=total_nodes></label>
  <label>Repeats <input type=number id=repeats></label>
 <!-- <button id="Raw">Raw</button> -->
  <button id="Comments" reference>Comments</button>
  <button id="PartsFlat">Parts (flat)</button>
  <button id="PartsNested">Parts (nested)</button>
</fieldset>
<div id="status"></div>
<div id="testContainer" class="hidden"></div>
<script>
  const container = window.testContainer;
  const statusDiv = document.getElementById('status');

  if (typeof document.getPartRoot !== "function") {
    window.onload = () => document.write('<h1>DOM Parts API is not supported in this browser. Perhaps enable it?</h1>');
  }

  async function setStatus(m, enabled) {
    statusDiv.textContent = m;
    controls.disabled = !enabled;
    await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
  }

  async function recreateAllContent() {
    await window.setStatus('Building templates...', false);
    window.createAllContent(getWidth(), getDepth(), getExtra());
    await window.setStatus('Ready.', true);
  }

  const width = document.getElementById('width');
  const depth = document.getElementById('depth');
  const extra = document.getElementById('extra');
  const repeats = document.getElementById('repeats');
  const showDOM = document.getElementById('show_dom');
  const totalParts = document.getElementById('total_parts');
  const totalNodes = document.getElementById('total_nodes');
  let timeoutId;
  function getWidth() { return Number(width.value); }
  function getDepth() { return Number(depth.value); }
  function getExtra() { return Number(extra.value); }
  function getRepeats() { return Number(repeats.value); }
  function updateTotalParts() {
    let totalP = 0, totalN = 0;
    for(let i=1;i<=getDepth();++i) {
      const thisLayer = Math.pow(getWidth(), i);
      const thisLayerNodeParts = Math.pow(getWidth(), i-1); // 1 NodePart per layer
      totalP += thisLayer + thisLayerNodeParts;
      totalN += thisLayer*(1 + 1 + 2 + getExtra()) + thisLayerNodeParts * 2; // <section>, label text, 2 comments, plus extra nodes, plus 2 per NodePart
    }
    totalParts.value = totalP;
    totalNodes.value = totalN;
    clearTimeout(timeoutId);
    timeoutId = setTimeout(recreateAllContent,1000);
  }
  width.onchange = updateTotalParts;
  depth.onchange = updateTotalParts;
  extra.onchange = updateTotalParts;
  showDOM.onchange = () => {
    testContainer.classList.toggle('hidden');
  }

  const presets = [
    {name: 'Small defaults', width:2, depth: 2, extra: 0, repeats: 1},
    {name: 'Medium defaults', width:5, depth: 3, extra: 3, repeats: 1000},
    {name: 'Big defaults', width:5, depth: 5, extra: 8, repeats: 100},
  ];
  for (let p of presets) {
    const button = document.createElement('button');
    button.textContent = p.name;
    button.onclick = () => {
      width.value = p.width;
      depth.value = p.depth;
      extra.value = p.extra;
      repeats.value = p.repeats;
      updateTotalParts();
    };
    document.getElementById('presets').appendChild(button);
  }
  document.getElementById('presets').firstChild.onclick();

  const test = async (testCase) => {
    await setStatus('Running test...', false);
    const results = manualRunTest(testCase, getRepeats(), container);
    await setStatus('Ready.', true);
    return results;
  }

  function speedRatio(shorterTime, longerTime) {
    return `${(1 + Math.abs(longerTime - shorterTime) / shorterTime).toFixed(2)}x`;
  }
  let referenceTime;
  const listen = (node, cb) => {
    node.addEventListener('click', async () => {
      const {cloneTime, appendTime, getPartsTime, state} = await cb();
      const countInfo = countNodesAndParts(state);
      const nodes = countInfo.nodes - 2; // 2 for extra container nodes
      if (countInfo.partCount != state.parts.length) { alert('error'); }
      const nodeParts = countInfo.nodeParts;
      const childNodeParts = countInfo.childNodeParts;
      const totalTime = cloneTime+appendTime+getPartsTime;
      let comparison = '';
      if (node.hasAttribute('reference')) {
        referenceTime = totalTime;
      } else if (referenceTime) {
        if (totalTime <= referenceTime) {
          comparison = ` (${speedRatio(totalTime,referenceTime)} Faster than Manual)`;
        } else {
          comparison = ` (${speedRatio(referenceTime,totalTime)} Slower than Manual)`;
        }
      }
      node.textContent = `${node.id}: ${cloneTime.toFixed(1)}ms + ${appendTime.toFixed(1)}ms + ${getPartsTime.toFixed(1)}ms = ${totalTime.toFixed(1)}ms, ${state.parts.length} parts (${nodeParts}&${childNodeParts}), ${nodes} nodes${comparison}`;
    });
  }

//  listen(Raw, () => test('Raw, no parts'));
  listen(Comments, () => test('Manual tree walk'));
  listen(PartsFlat, () => test('Parts, flat'));
  listen(PartsNested, () => test('Parts, nested'));
</script>

<style>
body {
  font-family: system-ui;
  font-size: 10px;
}
section {
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  --s: 4px;
  gap: var(--s);
  padding: var(--s);
  border-radius: var(--s);
  border: 1px dashed gray;
}

:not(:has(section section)) {
  flex-direction: row;
}
span.extra {
  width: 2px;
  height: 2px;
  background-color: blue;
}

#controls {
  display: inline-flex;
  min-width: 400px;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 16px;
  border:0;
}

#testContainer.hidden {
  display: none;
}
</style>