项目文件夹

文件
2026-05-10 15:46:04 +00:00

295 行
8.5 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Browser Harness edge-case benchmark</title>
<style>
:root {
color-scheme: light;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #f7faf9;
color: #17211f;
}
body {
margin: 0;
padding: 32px;
}
main {
max-width: 1120px;
margin: 0 auto;
}
h1 {
font-size: 32px;
margin: 0 0 8px;
}
.intro {
margin: 0 0 28px;
color: #4b5d58;
max-width: 760px;
line-height: 1.45;
}
.grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18px;
}
.task {
background: #ffffff;
border: 1px solid #d8e3df;
border-radius: 8px;
padding: 20px;
min-height: 250px;
box-shadow: 0 1px 2px rgba(11, 36, 31, 0.06);
}
.task h2 {
font-size: 18px;
margin: 0 0 12px;
}
.status {
display: inline-flex;
align-items: center;
min-height: 28px;
margin-top: 14px;
padding: 4px 10px;
border-radius: 999px;
background: #edf2f0;
color: #4b5d58;
font-size: 13px;
font-weight: 700;
}
.status.pass {
background: #d9f8e7;
color: #126236;
}
.upload-label, .drag-card {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 150px;
height: 46px;
border-radius: 6px;
background: #17211f;
color: white;
font-weight: 800;
cursor: pointer;
user-select: none;
}
input[type="file"] {
position: absolute;
width: 1px;
height: 1px;
opacity: 0.01;
}
.drop-zone {
display: flex;
align-items: center;
justify-content: center;
min-height: 92px;
margin-top: 14px;
border: 2px dashed #87a49d;
border-radius: 8px;
color: #587069;
background: #f1f6f4;
font-weight: 700;
}
.drop-zone.hot {
border-color: #168a54;
background: #e4f9ed;
color: #126236;
}
canvas {
display: block;
width: 100%;
max-width: 420px;
height: 140px;
border: 1px solid #b9c8c3;
border-radius: 6px;
background: white;
touch-action: none;
}
#coordinate-canvas {
cursor: crosshair;
}
.summary {
margin-top: 22px;
padding: 16px 18px;
background: #eaf2ef;
border-radius: 8px;
color: #31423e;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 14px;
}
@media (max-width: 760px) {
body {
padding: 18px;
}
.grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<main>
<h1>Browser Harness edge-case benchmark</h1>
<p class="intro">
Four small browser tasks that usually force agents to switch tactics:
hidden file input, drag/drop event payloads, canvas signature input, and a
coordinate-only canvas target.
</p>
<section class="grid">
<article class="task" data-task="file">
<h2>1. File upload</h2>
<label class="upload-label" for="file-input">Choose file</label>
<input id="file-input" type="file">
<div id="file-status" class="status">waiting for file</div>
</article>
<article class="task" data-task="drag">
<h2>2. Drag and drop</h2>
<div id="drag-source" class="drag-card" draggable="true">drag token</div>
<div id="drop-target" class="drop-zone">drop token here</div>
<div id="drag-status" class="status">waiting for drop</div>
</article>
<article class="task" data-task="signature">
<h2>3. Canvas signature</h2>
<canvas id="signature-canvas" width="420" height="140"></canvas>
<div id="signature-status" class="status">waiting for stroke</div>
</article>
<article class="task" data-task="coordinate">
<h2>4. Coordinate target</h2>
<canvas id="coordinate-canvas" width="420" height="140"></canvas>
<div id="coordinate-status" class="status">waiting for target click</div>
</article>
</section>
<pre id="summary" class="summary">window.bhBenchmarkResults() -> pending</pre>
</main>
<script>
(() => {
const state = {
file: false,
drag: false,
signature: false,
coordinate: false,
};
function mark(name, detail) {
state[name] = true;
const node = document.getElementById(`${name}-status`);
node.textContent = detail || "passed";
node.classList.add("pass");
renderSummary();
}
function renderSummary() {
document.getElementById("summary").textContent =
JSON.stringify(window.bhBenchmarkResults(), null, 2);
}
window.bhBenchmarkResults = () => ({
...state,
passed: Object.values(state).every(Boolean),
});
document.getElementById("file-input").addEventListener("change", (event) => {
const file = event.target.files && event.target.files[0];
if (file && file.name) mark("file", `uploaded: ${file.name}`);
});
const source = document.getElementById("drag-source");
const target = document.getElementById("drop-target");
source.addEventListener("dragstart", (event) => {
event.dataTransfer.setData("text/plain", "browser-harness-token");
});
target.addEventListener("dragenter", () => target.classList.add("hot"));
target.addEventListener("dragover", (event) => event.preventDefault());
target.addEventListener("dragleave", () => target.classList.remove("hot"));
target.addEventListener("drop", (event) => {
event.preventDefault();
target.classList.remove("hot");
if (event.dataTransfer.getData("text/plain") === "browser-harness-token") {
target.textContent = "token dropped";
mark("drag", "dropped token");
}
});
const signature = document.getElementById("signature-canvas");
const sigCtx = signature.getContext("2d");
sigCtx.lineWidth = 4;
sigCtx.lineCap = "round";
sigCtx.strokeStyle = "#17211f";
let drawing = false;
let last = null;
let pathLength = 0;
let pointCount = 0;
function sigPoint(event) {
const rect = signature.getBoundingClientRect();
return {
x: (event.clientX - rect.left) * (signature.width / rect.width),
y: (event.clientY - rect.top) * (signature.height / rect.height),
};
}
function startSignature(event) {
drawing = true;
last = sigPoint(event);
pointCount = 1;
pathLength = 0;
sigCtx.beginPath();
sigCtx.moveTo(last.x, last.y);
}
function moveSignature(event) {
if (!drawing) return;
const next = sigPoint(event);
sigCtx.lineTo(next.x, next.y);
sigCtx.stroke();
pathLength += Math.hypot(next.x - last.x, next.y - last.y);
pointCount += 1;
last = next;
}
function endSignature() {
drawing = false;
if (pathLength > 120 && pointCount >= 5) mark("signature", "signature captured");
}
signature.addEventListener("pointerdown", startSignature);
signature.addEventListener("pointermove", moveSignature);
signature.addEventListener("pointerup", endSignature);
signature.addEventListener("mousedown", startSignature);
signature.addEventListener("mousemove", moveSignature);
window.addEventListener("mouseup", endSignature);
const coordinate = document.getElementById("coordinate-canvas");
const coordCtx = coordinate.getContext("2d");
const targetPoint = { x: 312, y: 82, radius: 18 };
coordCtx.fillStyle = "#f1f6f4";
coordCtx.fillRect(0, 0, coordinate.width, coordinate.height);
coordCtx.fillStyle = "#168a54";
coordCtx.beginPath();
coordCtx.arc(targetPoint.x, targetPoint.y, targetPoint.radius, 0, Math.PI * 2);
coordCtx.fill();
coordCtx.fillStyle = "#17211f";
coordCtx.font = "700 16px system-ui";
coordCtx.fillText("click the green target", 24, 42);
coordinate.addEventListener("click", (event) => {
const rect = coordinate.getBoundingClientRect();
const x = (event.clientX - rect.left) * (coordinate.width / rect.width);
const y = (event.clientY - rect.top) * (coordinate.height / rect.height);
if (Math.hypot(x - targetPoint.x, y - targetPoint.y) <= targetPoint.radius) {
mark("coordinate", "target clicked");
}
});
renderSummary();
})();
</script>
</body>
</html>