Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save jogibear9988/35acb171a6ecab1edf9acb60dc437654 to your computer and use it in GitHub Desktop.

Select an option

Save jogibear9988/35acb171a6ecab1edf9acb60dc437654 to your computer and use it in GitHub Desktop.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>getBoxQuads designer select-all drag benchmark</title>
<style>
:root {
color-scheme: light;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
color: #172027;
background: #f4f6f7;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-width: 980px;
min-height: 100vh;
overflow: hidden;
}
button,
select {
font: inherit;
}
#app {
display: grid;
grid-template-rows: auto minmax(320px, 1fr);
height: 100vh;
}
#toolbar {
position: relative;
z-index: 3;
display: grid;
grid-template-columns: minmax(260px, 1.1fr) minmax(620px, 2fr);
gap: 20px;
padding: 14px 18px 16px;
border-bottom: 1px solid #cbd3d7;
background: #ffffff;
box-shadow: 0 2px 8px rgb(20 34 42 / 8%);
}
h1 {
margin: 0 0 10px;
font-size: 18px;
line-height: 1.25;
letter-spacing: 0;
}
.controls {
display: flex;
align-items: end;
gap: 8px;
}
label {
display: grid;
gap: 4px;
min-width: 220px;
color: #53616a;
font-size: 12px;
font-weight: 650;
}
select,
button {
height: 34px;
border: 1px solid #aebbc1;
border-radius: 5px;
background: #ffffff;
color: #172027;
}
select {
padding: 0 30px 0 9px;
}
button {
padding: 0 13px;
cursor: pointer;
font-weight: 650;
}
button:hover:not(:disabled) {
border-color: #347b67;
background: #f1faf7;
}
button:disabled {
cursor: default;
opacity: 0.48;
}
#run {
border-color: #176b54;
background: #176b54;
color: #ffffff;
}
#run:hover:not(:disabled) {
border-color: #105541;
background: #105541;
}
#fixture-status {
margin-top: 9px;
color: #65737b;
font: 12px/1.35 ui-monospace, SFMono-Regular, Menlo, monospace;
}
#fixture-status.error {
color: #a62b2b;
}
#metrics {
display: grid;
grid-template-columns: repeat(7, minmax(82px, 1fr));
gap: 8px;
align-content: start;
}
.metric {
min-height: 55px;
padding: 7px 9px;
border-left: 3px solid #7a8d97;
background: #eef2f3;
}
.metric strong {
display: block;
overflow: hidden;
font: 700 17px/1.25 ui-monospace, SFMono-Regular, Menlo, monospace;
text-overflow: ellipsis;
white-space: nowrap;
}
.metric span {
display: block;
margin-top: 2px;
color: #65737b;
font-size: 11px;
line-height: 1.2;
}
#progress-wrap {
grid-column: 1 / -1;
display: grid;
grid-template-columns: minmax(140px, auto) 1fr;
gap: 10px;
align-items: center;
height: 24px;
}
#progress-text {
color: #47565e;
font: 12px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
}
#progress-track {
height: 6px;
overflow: hidden;
border-radius: 3px;
background: #dce3e6;
}
#progress {
width: 0;
height: 100%;
background: #1787a3;
transition: width 100ms linear;
}
#workspace {
position: relative;
min-height: 0;
overflow: hidden;
background:
linear-gradient(#d8dfe2 1px, transparent 1px),
linear-gradient(90deg, #d8dfe2 1px, transparent 1px),
#edf1f2;
background-size: 24px 24px;
}
#designer-canvas {
position: absolute;
inset: 0;
contain: paint;
overflow: hidden;
}
#selection-overlays {
position: absolute;
inset: 0;
z-index: 2;
width: 100%;
height: 100%;
overflow: visible;
pointer-events: none;
}
#selection-overlays .selection {
fill: none;
stroke: rgb(9 127 159 / 72%);
stroke-width: 1;
}
#selection-overlays .placement {
fill: rgb(22 107 84 / 4%);
stroke: rgb(22 107 84 / 48%);
stroke-width: 1;
}
#selection-overlays .multiple-selection {
fill: none;
stroke: rgb(166 43 43 / 68%);
stroke-dasharray: 4 3;
stroke-width: 1;
}
#result-panel {
position: absolute;
right: 14px;
bottom: 14px;
z-index: 4;
display: none;
width: min(900px, calc(100% - 28px));
max-height: min(330px, calc(100% - 28px));
overflow: auto;
border: 1px solid #adb9be;
border-radius: 6px;
background: rgb(255 255 255 / 96%);
box-shadow: 0 8px 26px rgb(22 34 41 / 20%);
}
#result-panel.visible {
display: block;
}
.result-head {
position: sticky;
top: 0;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 8px 10px;
border-bottom: 1px solid #d5dcdf;
background: #ffffff;
}
.result-head strong {
font-size: 13px;
}
.result-actions {
display: flex;
gap: 6px;
}
.result-actions button {
height: 28px;
padding: 0 9px;
font-size: 12px;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 11px;
font-variant-numeric: tabular-nums;
}
th,
td {
padding: 6px 8px;
border-bottom: 1px solid #e2e7e9;
text-align: right;
white-space: nowrap;
}
th:first-child,
td:first-child,
th:nth-child(2),
td:nth-child(2) {
text-align: left;
}
th {
color: #5c6970;
font-weight: 650;
}
#result-json {
margin: 0;
padding: 10px;
overflow: auto;
border-top: 1px solid #d5dcdf;
background: #f5f7f8;
font: 10px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
white-space: pre-wrap;
}
</style>
</head>
<body>
<main id="app">
<section id="toolbar">
<div>
<h1>getBoxQuads designer select-all drag benchmark</h1>
<div class="controls">
<label>
Geometry implementation
<select id="mode">
<option value="native">Native getBoxQuads</option>
<option value="polyfill-cache">Polyfill, per-batch cache</option>
<option value="polyfill-no-cache">Polyfill, no cache</option>
</select>
</label>
<button id="run" type="button" disabled>Run</button>
</div>
<div id="fixture-status">Loading the 1,008-element designer fixture...</div>
</div>
<div id="metrics">
<div class="metric"><strong id="fps">-</strong><span>frames / second</span></div>
<div class="metric"><strong id="frame-p95">-</strong><span>frame p95, ms</span></div>
<div class="metric"><strong id="placement">-</strong><span>ancestor move avg, ms</span></div>
<div class="metric"><strong id="arrow-refresh">-</strong><span>arrow batch avg, ms</span></div>
<div class="metric"><strong id="pass-one">-</strong><span>full refresh avg, ms</span></div>
<div class="metric"><strong id="pass-two">-</strong><span>overlay refresh avg, ms</span></div>
<div class="metric"><strong id="calls-second">-</strong><span>quad calls / second</span></div>
<div id="progress-wrap">
<span id="progress-text">Preparing</span>
<div id="progress-track"><div id="progress"></div></div>
</div>
</div>
</section>
<section id="workspace">
<div id="designer-canvas"></div>
<svg id="selection-overlays" aria-hidden="true">
<g id="selection-paths"></g>
<g id="placement-rects"></g>
<path id="multiple-selection-path" class="multiple-selection"></path>
</svg>
<aside id="result-panel">
<div class="result-head">
<strong>Benchmark results</strong>
<div class="result-actions">
<button id="copy" type="button">Copy JSON</button>
<button id="close-results" type="button" aria-label="Close results">Close</button>
</div>
</div>
<table>
<thead>
<tr>
<th>Browser</th>
<th>Mode</th>
<th>Access pattern</th>
<th>FPS</th>
<th>Frame p95</th>
<th>Move avg</th>
<th>Arrows avg</th>
<th>Full refresh</th>
<th>Overlay avg</th>
<th>Calls/s</th>
<th>Error</th>
</tr>
</thead>
<tbody id="result-rows"></tbody>
</table>
<pre id="result-json"></pre>
</aside>
</section>
</main>
<script type="module">
const FIXTURE_URL = "https://gist.githubusercontent.com/jogibear9988/11942ee156cb6f6354427548ca246835/raw/a1a411871b2abfdc1784d1f8ed1538b22de777a9/gistfile1.txt";
const POLYFILL_URL = "https://cdn.jsdelivr.net/gh/jogibear9988/getBoxQuadsPolyfill@f0741159da3f3a6c9d3bedfb5deb1076dd22b8d4/getBoxQuads.js";
const SHADOW_DEPTH = 6;
const WARMUP_MS = 2000;
const MEASURE_MS = 8000;
const CONNECTOR_REFRESH_INTERVAL_MS = 100;
const params = new URLSearchParams(location.search);
const ACCESS_PATTERN = params.get("access") === "batched" ?
"batched reads, then writes" : "interleaved reads and writes";
const requestedOverlayBudget = Number(
params.get("budget"));
const OVERLAY_REFRESH_BUDGET_MS =
Number.isFinite(requestedOverlayBudget) && requestedOverlayBudget > 0
? requestedOverlayBudget
: 20;
const FULL_OVERLAY_REFRESH_INTERVAL_MS = 250;
const designerCanvas = document.querySelector("#designer-canvas");
const workspace = document.querySelector("#workspace");
const selectionOverlays = document.querySelector("#selection-overlays");
const selectionPathsRoot = document.querySelector("#selection-paths");
const placementRectsRoot = document.querySelector("#placement-rects");
const multipleSelectionPath = document.querySelector("#multiple-selection-path");
const modeSelect = document.querySelector("#mode");
const runButton = document.querySelector("#run");
const fixtureStatus = document.querySelector("#fixture-status");
const progressText = document.querySelector("#progress-text");
const progress = document.querySelector("#progress");
const resultPanel = document.querySelector("#result-panel");
const resultRows = document.querySelector("#result-rows");
const resultJson = document.querySelector("#result-json");
const copyButton = document.querySelector("#copy");
const closeResultsButton = document.querySelector("#close-results");
const metricElements = {
fps: document.querySelector("#fps"),
frameP95: document.querySelector("#frame-p95"),
placement: document.querySelector("#placement"),
arrowRefresh: document.querySelector("#arrow-refresh"),
passOne: document.querySelector("#pass-one"),
passTwo: document.querySelector("#pass-two"),
callsSecond: document.querySelector("#calls-second"),
};
let fixtureElements = [];
let targets = [];
let connectors = [];
let selectionPaths = [];
let placementRects = [];
let fixtureRoot = null;
let fixtureBaseTransform = "";
let running = false;
let animationFrame = 0;
let lastResult = null;
class BenchmarkShadowLayer extends HTMLElement {
constructor() {
super();
const depth = Number(this.dataset.depth || 0);
const translateX = 2 + depth * 0.7;
const translateY = depth % 2 ? -1.5 : 1.25;
const root = this.attachShadow({ mode: "open" });
root.innerHTML = `
<style>
:host {
position: absolute;
inset: 0;
display: block;
overflow: visible;
}
.surface {
position: absolute;
inset: 0;
transform: translate(${translateX}px, ${translateY}px);
transform-origin: 0 0;
}
</style>
<div class="surface"><slot></slot></div>
`;
}
}
customElements.define("benchmark-shadow-layer", BenchmarkShadowLayer);
function resizeOverlay() {
const width = Math.max(1, workspace.clientWidth);
const height = Math.max(1, workspace.clientHeight);
selectionOverlays.setAttribute("viewBox", `0 0 ${width} ${height}`);
}
function browserName() {
const ua = navigator.userAgent;
const firefox = ua.match(/Firefox\/(\d+(?:\.\d+)?)/);
const chromium = ua.match(/(?:Chromium|Chrome)\/(\d+(?:\.\d+)?)/);
if (firefox) return `Firefox ${firefox[1]}`;
if (chromium) return `${ua.includes("Chromium") ? "Chromium" : "Chrome"} ${chromium[1]}`;
return navigator.userAgentData?.brands?.map((brand) => `${brand.brand} ${brand.version}`).join(", ") || ua;
}
function percentile(values, fraction) {
if (!values.length) return 0;
const ordered = [...values].sort((a, b) => a - b);
const index = Math.min(ordered.length - 1, Math.max(0, Math.ceil(ordered.length * fraction) - 1));
return ordered[index];
}
function average(values) {
if (!values.length) return 0;
return values.reduce((sum, value) => sum + value, 0) / values.length;
}
function round(value, digits = 2) {
const factor = 10 ** digits;
return Math.round(value * factor) / factor;
}
function quadBounds(quad) {
const points = [quad.p1, quad.p2, quad.p3, quad.p4];
const xs = points.map((point) => point.x);
const ys = points.map((point) => point.y);
const left = Math.min(...xs);
const top = Math.min(...ys);
return {
left,
top,
right: Math.max(...xs),
bottom: Math.max(...ys),
};
}
function nativeAvailable() {
return targets.length > 0 && typeof targets[0].getBoxQuads === "function";
}
async function createGeometryRunner(mode) {
if (mode === "native") {
if (!nativeAvailable()) {
throw new Error("Native getBoxQuads is not available in this browser.");
}
return {
label: "native",
beginBatch() {},
getBoxQuads(node, options) {
return node.getBoxQuads(options);
},
};
}
const moduleUrl = `${POLYFILL_URL}?benchmark=${mode}`;
const polyfill = await import(moduleUrl);
if (mode === "polyfill-cache") {
polyfill.useCache();
return {
label: "polyfill, per-batch cache",
beginBatch() {
polyfill.clearCache();
},
getBoxQuads: polyfill.getBoxQuads,
};
}
return {
label: "polyfill, no cache",
beginBatch() {},
getBoxQuads: polyfill.getBoxQuads,
};
}
function quadPathData(quad) {
return `M ${quad.p1.x} ${quad.p1.y} L ${quad.p2.x} ${quad.p2.y} ` +
`L ${quad.p3.x} ${quad.p3.y} L ${quad.p4.x} ${quad.p4.y} Z`;
}
function updatePlacementRect(rect, canvasRect, overlayRect) {
overlayRect.setAttribute("x", rect.left - canvasRect.left);
overlayRect.setAttribute("y", rect.top - canvasRect.top);
overlayRect.setAttribute("width", rect.width);
overlayRect.setAttribute("height", rect.height);
}
function updateMultipleSelectionBounds(canvasRect, targetRects = null) {
let left = Infinity;
let top = Infinity;
let right = -Infinity;
let bottom = -Infinity;
for (let index = 0; index < targets.length; index += 1) {
const rect = targetRects ? targetRects[index] :
targets[index].getBoundingClientRect();
left = Math.min(left, rect.left - canvasRect.left);
top = Math.min(top, rect.top - canvasRect.top);
right = Math.max(right, rect.right - canvasRect.left);
bottom = Math.max(bottom, rect.bottom - canvasRect.top);
}
multipleSelectionPath.setAttribute("d",
`M ${left} ${top} H ${right} V ${bottom} H ${left} Z`);
}
function refreshConnectors() {
for (const connector of connectors) {
connector.refresh();
}
}
function updateLiveMetrics(samples, measuredElapsed) {
const seconds = Math.max(0.001, measuredElapsed / 1000);
metricElements.fps.textContent = (samples.frameIntervals.length / seconds).toFixed(1);
metricElements.frameP95.textContent = percentile(samples.frameIntervals, 0.95).toFixed(1);
metricElements.placement.textContent = average(samples.placementTimes).toFixed(1);
metricElements.arrowRefresh.textContent = average(samples.connectorBatchTimes).toFixed(1);
metricElements.passOne.textContent = average(samples.fullOverlayRefreshTimes).toFixed(1);
metricElements.passTwo.textContent = average(samples.overlayRefreshTimes).toFixed(1);
metricElements.callsSecond.textContent = Math.round(samples.quadCalls / seconds).toLocaleString("en-US");
}
function addResult(result) {
const row = document.createElement("tr");
const values = [
result.browser,
result.mode,
result.accessPattern,
result.fps.toFixed(1),
`${result.frameMs.p95.toFixed(1)} ms`,
`${result.moveLoopMs.avg.toFixed(1)} ms`,
`${result.connectorRefreshMs.avg.toFixed(1)} ms`,
`${result.fullOverlayRefreshMs.avg.toFixed(1)} ms`,
`${result.overlayRefreshMs.avg.toFixed(1)} ms`,
Math.round(result.quadCallsPerSecond).toLocaleString("en-US"),
`${result.correctness.maxAabbErrorPx.toFixed(2)} px`,
];
for (const value of values) {
const cell = document.createElement("td");
cell.textContent = value;
row.appendChild(cell);
}
resultRows.prepend(row);
resultJson.textContent = JSON.stringify(result, null, 2);
resultPanel.classList.add("visible");
}
function buildResult(runner, samples, measureStart, measureEnd, checksum) {
const measuredSeconds = Math.max(0.001, (measureEnd - measureStart) / 1000);
const stats = (values) => ({
avg: round(average(values), 3),
p50: round(percentile(values, 0.5), 3),
p95: round(percentile(values, 0.95), 3),
p99: round(percentile(values, 0.99), 3),
});
return {
benchmark: "getBoxQuads designer select-all drag workload v1",
browser: browserName(),
userAgent: navigator.userAgent,
mode: runner.label,
accessPattern: ACCESS_PATTERN,
nativeGetBoxQuadsAvailable: nativeAvailable(),
fixture: {
source: FIXTURE_URL,
fixtureElements: fixtureElements.length,
selectedElements: targets.length,
movedElements: 1,
movingArrows: connectors.length,
connectors: connectors.length,
connectorRefreshIntervalMs: CONNECTOR_REFRESH_INTERVAL_MS,
shadowRootDepth: SHADOW_DEPTH,
overlayRefreshBudgetMs: OVERLAY_REFRESH_BUDGET_MS,
fullOverlayRefreshIntervalMs: FULL_OVERLAY_REFRESH_INTERVAL_MS,
overlayDomNodes: selectionPaths.length + placementRects.length + 1,
connectorLayoutReadsPerBatch: connectors.length * 3,
connectorPathWritesPerBatch: connectors.length,
warmupMs: WARMUP_MS,
measureMs: MEASURE_MS,
},
frames: samples.frames,
elapsedMs: round((measureEnd - measureStart), 3),
fps: round(samples.frames / measuredSeconds, 3),
quadCalls: samples.quadCalls,
quadCallsPerSecond: round(samples.quadCalls / measuredSeconds, 3),
frameMs: stats(samples.frameIntervals),
callbackMs: stats(samples.callbackTimes),
moveLoopMs: stats(samples.placementTimes),
connectorRefreshes: samples.connectorBatchTimes.length,
connectorRefreshMs: stats(samples.connectorBatchTimes),
connectorCostPerFrameMs: round(average(samples.connectorFrameTimes), 3),
selectionBoundsMs: stats(samples.selectionBoundsTimes),
overlayRefreshMs: stats(samples.overlayRefreshTimes),
fullOverlayRefreshes: samples.fullOverlayRefreshTimes.length,
fullOverlayRefreshMs: stats(samples.fullOverlayRefreshTimes),
refreshedElementsPerFrame: stats(samples.refreshedElementCounts),
droppedFrames: {
over16_7ms: samples.frameIntervals.filter((value) => value > 16.7).length,
over33_3ms: samples.frameIntervals.filter((value) => value > 33.3).length,
},
correctness: {
maxAabbErrorPx: round(samples.maxError, 4),
checksum: round(checksum, 3),
},
};
}
async function runBenchmark() {
if (running || !targets.length) return;
running = true;
runButton.disabled = true;
modeSelect.disabled = true;
resultPanel.classList.remove("visible");
progress.style.width = "0%";
fixtureStatus.classList.remove("error");
try {
const runner = await createGeometryRunner(modeSelect.value);
const samples = {
frames: 0,
quadCalls: 0,
frameIntervals: [],
callbackTimes: [],
placementTimes: [],
connectorBatchTimes: [],
connectorFrameTimes: [],
selectionBoundsTimes: [],
overlayRefreshTimes: [],
fullOverlayRefreshTimes: [],
refreshedElementCounts: [],
maxError: 0,
};
let checksum = 0;
let startedAt = 0;
let measureStart = 0;
let previousFrameTime = 0;
let lastUiUpdate = 0;
let lastConnectorRefresh = -Infinity;
let lastFullOverlayRefresh = -Infinity;
await new Promise((resolve, reject) => {
const frame = (now) => {
try {
if (!startedAt) {
startedAt = now;
previousFrameTime = now;
}
const totalElapsed = now - startedAt;
const measuring = totalElapsed >= WARMUP_MS;
if (measuring && !measureStart) {
measureStart = now;
previousFrameTime = now;
}
if (measureStart && now - measureStart >= MEASURE_MS) {
resolve(now);
return;
}
const callbackStart = performance.now();
const wave = now / 420;
const moveX = Math.sin(wave) * 18;
const moveY = Math.cos(wave * 0.83) * 11;
const moveStart = performance.now();
// Select-all includes the fixture container and all descendants.
// The designer's child filter therefore moves only this ancestor.
fixtureRoot.style.transform =
`${fixtureBaseTransform} translate(${moveX}px, ${moveY}px)`;
const moveTime = performance.now() - moveStart;
// The fixture's independent timers are disabled below. Refresh
// the same 201 SVG arrows as one deterministic 100 ms batch so
// every browser gets the fixture's real cadence without timer
// registration jitter. Each arrow reads three rectangles and
// writes its path.
let connectorTime = 0;
let connectorRefreshed = false;
if (now - lastConnectorRefresh >= CONNECTOR_REFRESH_INTERVAL_MS) {
const connectorStart = performance.now();
refreshConnectors();
connectorTime = performance.now() - connectorStart;
connectorRefreshed = true;
lastConnectorRefresh = now;
}
// ExtensionManager clears the polyfill cache after placement and
// before refreshing overlays for the newly transformed selection.
runner.beginBatch();
const overlayStart = performance.now();
const canvasRect = designerCanvas.getBoundingClientRect();
let selectionBoundsTime = 0;
let refreshedElementCount = 0;
let frameQuadCalls = 0;
let checkQuad = null;
let checkTarget = null;
const fullOverlayRefresh =
now - lastFullOverlayRefresh >= FULL_OVERLAY_REFRESH_INTERVAL_MS;
if (fullOverlayRefresh) {
lastFullOverlayRefresh = now;
}
if (ACCESS_PATTERN === "batched reads, then writes") {
const boundsStart = performance.now();
const targetRects = targets.map(
(target) => target.getBoundingClientRect());
selectionBoundsTime = performance.now() - boundsStart;
const refreshed = [];
for (let index = 0; index < targets.length; index += 1) {
const target = targets[index];
const quad = runner.getBoxQuads(target, {
box: "border",
relativeTo: designerCanvas,
})[0];
frameQuadCalls += 1;
if (quad) {
refreshed.push({index, quad, rect: targetRects[index]});
checksum += quad.p3.x * 0.0000001 + quad.p3.y * 0.0000002;
if (!checkQuad) {
checkQuad = quad;
checkTarget = target;
}
}
if (!fullOverlayRefresh &&
performance.now() - overlayStart >= OVERLAY_REFRESH_BUDGET_MS) {
break;
}
}
updateMultipleSelectionBounds(canvasRect, targetRects);
for (const {index, quad, rect} of refreshed) {
selectionPaths[index].setAttribute("d", quadPathData(quad));
updatePlacementRect(rect, canvasRect, placementRects[index]);
}
refreshedElementCount = refreshed.length;
} else {
for (let index = 0; index < targets.length; index += 1) {
const target = targets[index];
const quad = runner.getBoxQuads(target, {
box: "border",
relativeTo: designerCanvas,
})[0];
frameQuadCalls += 1;
if (!quad) continue;
// SelectionDefaultExtension writes its SVG after reading the
// quad. PlacementExtension then reads a rectangle and writes
// another SVG node, producing the designer's read/write order.
selectionPaths[index].setAttribute("d", quadPathData(quad));
if (index === 0) {
const boundsStart = performance.now();
updateMultipleSelectionBounds(canvasRect);
selectionBoundsTime = performance.now() - boundsStart;
}
const rect = target.getBoundingClientRect();
updatePlacementRect(rect, canvasRect, placementRects[index]);
refreshedElementCount += 1;
checksum += quad.p3.x * 0.0000001 + quad.p3.y * 0.0000002;
if (!checkQuad) {
checkQuad = quad;
checkTarget = target;
}
if (!fullOverlayRefresh &&
performance.now() - overlayStart >= OVERLAY_REFRESH_BUDGET_MS) {
break;
}
}
}
const overlayRefreshTime = performance.now() - overlayStart;
if (measuring && checkQuad) {
const bounds = quadBounds(checkQuad);
const rect = checkTarget.getBoundingClientRect();
const error = Math.max(
Math.abs(bounds.left - (rect.left - canvasRect.left)),
Math.abs(bounds.top - (rect.top - canvasRect.top)),
Math.abs(bounds.right - (rect.right - canvasRect.left)),
Math.abs(bounds.bottom - (rect.bottom - canvasRect.top)),
);
samples.maxError = Math.max(samples.maxError, error);
}
const callbackTime = performance.now() - callbackStart;
if (measuring) {
samples.frames += 1;
samples.quadCalls += frameQuadCalls;
if (now > measureStart) {
samples.frameIntervals.push(now - previousFrameTime);
}
samples.callbackTimes.push(callbackTime);
samples.placementTimes.push(moveTime);
samples.connectorFrameTimes.push(connectorTime);
if (connectorRefreshed) {
samples.connectorBatchTimes.push(connectorTime);
}
samples.selectionBoundsTimes.push(selectionBoundsTime);
samples.overlayRefreshTimes.push(overlayRefreshTime);
if (fullOverlayRefresh) {
samples.fullOverlayRefreshTimes.push(overlayRefreshTime);
}
samples.refreshedElementCounts.push(refreshedElementCount);
}
previousFrameTime = now;
if (now - lastUiUpdate > 250) {
lastUiUpdate = now;
if (!measuring) {
const warmupProgress = Math.min(1, totalElapsed / WARMUP_MS);
progressText.textContent = `Warm-up ${Math.round(warmupProgress * 100)}%`;
progress.style.width = `${warmupProgress * 20}%`;
} else {
const measuredElapsed = now - measureStart;
const measureProgress = Math.min(1, measuredElapsed / MEASURE_MS);
progressText.textContent = `Measuring ${Math.round(measureProgress * 100)}%`;
progress.style.width = `${20 + measureProgress * 80}%`;
updateLiveMetrics(samples, measuredElapsed);
}
}
animationFrame = requestAnimationFrame(frame);
} catch (error) {
reject(error);
}
};
animationFrame = requestAnimationFrame(frame);
}).then((measureEnd) => {
lastResult = buildResult(runner, samples, measureStart, measureEnd, checksum);
window.__getBoxQuadsBenchmarkResult = lastResult;
console.log("getBoxQuads benchmark result", lastResult);
addResult(lastResult);
updateLiveMetrics(samples, measureEnd - measureStart);
progressText.textContent = "Complete";
progress.style.width = "100%";
});
} catch (error) {
console.error(error);
fixtureStatus.textContent = error instanceof Error ? error.message : String(error);
fixtureStatus.classList.add("error");
progressText.textContent = "Failed";
} finally {
running = false;
runButton.disabled = false;
modeSelect.disabled = false;
}
}
async function loadFixture() {
resizeOverlay();
const response = await fetch(FIXTURE_URL);
if (!response.ok) throw new Error(`Fixture request failed: HTTP ${response.status}`);
const source = await response.text();
const pausedSource = source.replace(
"this._refreshInterval = setInterval(this.refresh, 100);",
"this._refreshInterval = null;",
);
if (pausedSource === source) {
throw new Error("Could not pause the fixture's connector timers.");
}
const template = document.createElement("template");
template.innerHTML = pausedSource;
const fixtureScript = template.content.querySelector("script");
if (!fixtureScript) throw new Error("The fixture script was not found.");
const executableScript = document.createElement("script");
executableScript.textContent = fixtureScript.textContent;
fixtureScript.remove();
document.head.appendChild(executableScript);
fixtureRoot = template.content.querySelector("div");
if (!fixtureRoot) throw new Error("The fixture root was not found.");
fixtureRoot.id = "benchmark-fixture-root";
let parent = designerCanvas;
for (let depth = 0; depth < SHADOW_DEPTH; depth += 1) {
const layer = document.createElement("benchmark-shadow-layer");
layer.dataset.depth = String(depth);
parent.appendChild(layer);
parent = layer;
}
parent.appendChild(template.content);
await customElements.whenDefined("ui-place");
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
fixtureElements = [...fixtureRoot.querySelectorAll("ui-place")];
connectors = [...fixtureRoot.querySelectorAll("ui-move")];
if (fixtureElements.length !== 1008) {
throw new Error(`Expected 1,008 ui-place elements, found ${fixtureElements.length}.`);
}
if (connectors.length !== 201) {
throw new Error(`Expected 201 ui-move connectors, found ${connectors.length}.`);
}
// Match DesignerCanvas.handleSelectAll(): the primary item comes first,
// followed by the remaining places, connectors, and their common parent.
targets = [fixtureElements[1], fixtureElements[0], ...fixtureElements.slice(2),
...connectors, fixtureRoot];
fixtureBaseTransform = fixtureRoot.style.transform || "";
const svgNamespace = "http://www.w3.org/2000/svg";
const selectionFragment = document.createDocumentFragment();
const placementFragment = document.createDocumentFragment();
for (let index = 0; index < targets.length; index += 1) {
const path = document.createElementNS(svgNamespace, "path");
path.setAttribute("class", "selection");
selectionFragment.appendChild(path);
selectionPaths.push(path);
const rect = document.createElementNS(svgNamespace, "rect");
rect.setAttribute("class", "placement");
placementFragment.appendChild(rect);
placementRects.push(rect);
}
selectionPathsRoot.appendChild(selectionFragment);
placementRectsRoot.appendChild(placementFragment);
const nativeSupported = nativeAvailable();
modeSelect.querySelector('option[value="native"]').disabled = !nativeSupported;
modeSelect.value = nativeSupported ? "native" : "polyfill-cache";
fixtureStatus.textContent = `${targets.length.toLocaleString("en-US")} selected elements | ${ACCESS_PATTERN} | 1 moved ancestor | ${connectors.length} moving arrows | ${OVERLAY_REFRESH_BUDGET_MS} ms drag refresh | full refresh/${FULL_OVERLAY_REFRESH_INTERVAL_MS} ms | ${SHADOW_DEPTH} shadow roots | native ${nativeSupported ? "available" : "unavailable"}`;
runButton.disabled = false;
progressText.textContent = "Ready";
window.__getBoxQuadsBenchmarkReady = true;
const requestedMode = params.get("mode");
if (requestedMode && [...modeSelect.options].some((option) => option.value === requestedMode && !option.disabled)) {
modeSelect.value = requestedMode;
}
if (params.get("autorun") !== "0") {
setTimeout(runBenchmark, 500);
}
}
runButton.addEventListener("click", runBenchmark);
window.__runGetBoxQuadsBenchmark = runBenchmark;
copyButton.addEventListener("click", async () => {
if (!lastResult) return;
await navigator.clipboard.writeText(JSON.stringify(lastResult, null, 2));
copyButton.textContent = "Copied";
setTimeout(() => { copyButton.textContent = "Copy JSON"; }, 1000);
});
closeResultsButton.addEventListener("click", () => resultPanel.classList.remove("visible"));
window.addEventListener("resize", resizeOverlay);
loadFixture().catch((error) => {
console.error(error);
fixtureStatus.textContent = error instanceof Error ? error.message : String(error);
fixtureStatus.classList.add("error");
progressText.textContent = "Failed to load";
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment