sitesmith capture: keep a var() border shorthand that a border-top overrides
`border:1px solid var(--x); border-top:4px solid ...` (the AI's accent-top card) lost its left/right/
bottom sides in the self-review capture in Chromium AND Firefox: once a later declaration overrides
one longhand, the CSSOM can no longer serialise the var() shorthand (it and its remaining longhands
read back as ""), so the 4c1c8db recovery had nothing to emit. Recover such shorthands from the
declaration block's source text (inline value; innermost blocks of a sheet, matched to rules by
cssText), re-parse each ALONE through the CSSOM and judge it with the unchanged value/URL gate.
e2e-capture-fidelity gains the card (var/var, var/literal, stylesheet) sampled on all four sides.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6e48cb82b7
commit
28119f0666
2 files changed
+130
-13
No files matched your search
@@ -28,6 +28,7 @@ const PAGE = `<!doctype html><html><head><style>
|
||||
body{margin:0;background:#ffffff}
|
||||
.sheet-bg{background:var(--wsb-secondary);height:100px}
|
||||
.sheet-border{border:12px solid var(--wsb-border);height:76px;background-color:#ffffff}
|
||||
.sheet-card{border:6px solid var(--wsb-border);border-top:14px solid var(--wsb-accent);height:60px;background-color:#ffffff}
|
||||
</style></head><body>
|
||||
<div style="background: var(--wsb-secondary); height:100px"></div>
|
||||
<div style="background-color: var(--wsb-primary); height:100px"></div>
|
||||
@@ -36,6 +37,9 @@ body{margin:0;background:#ffffff}
|
||||
<div class="sheet-border"></div>
|
||||
<div style="background: linear-gradient(var(--wsb-accent), var(--wsb-accent)); height:100px"></div>
|
||||
<div style="padding: var(--pad) 0 0 0; background-color:#ffffff"><div style="height:60px;background-color:#00aa00"></div></div>
|
||||
<div style="padding:0;background-color:#ffffff;border:6px solid var(--wsb-border);border-top:14px solid var(--wsb-accent);border-radius:0;height:60px"></div>
|
||||
<div style="background-color:#ffffff;border:6px solid var(--wsb-border);border-top:14px solid #00aa00;height:60px"></div>
|
||||
<div class="sheet-card"></div>
|
||||
</body></html>`;
|
||||
// [label, x, y, expected rgb]
|
||||
const POINTS = [
|
||||
@@ -48,6 +52,17 @@ const POINTS = [
|
||||
['inline `background: linear-gradient(var(--wsb-accent), ...)`', 640, 550, [0xd9, 0xa2, 0x4a]],
|
||||
['inline `padding: var(--pad) 0 0 0` keeps the 40px gap (white above the green box)', 640, 620, [0xff, 0xff, 0xff]],
|
||||
['... and the green box starts below the padding', 640, 670, [0x00, 0xaa, 0x00]],
|
||||
// A card written `border: ... var(); border-top: <override>` (the AI's "accent top" card, eval 2026-11-01):
|
||||
// the CSSOM cannot serialise the overridden var() shorthand, so the left/right/bottom sides were lost.
|
||||
...[['inline `border:var()` + `border-top:var()` card', 700, [0xd9, 0xa2, 0x4a]],
|
||||
['inline `border:var()` + literal `border-top:#00aa00` card', 780, [0x00, 0xaa, 0x00]],
|
||||
['stylesheet `.sheet-card{border:var(); border-top:var()}`', 860, [0xd9, 0xa2, 0x4a]]].flatMap(([label, y0, top]) => [
|
||||
[`${label}: top border`, 640, y0 + 7, top],
|
||||
[`${label}: left border`, 2, y0 + 50, [0xd4, 0x37, 0x2c]],
|
||||
[`${label}: right border`, 1277, y0 + 50, [0xd4, 0x37, 0x2c]],
|
||||
[`${label}: bottom border`, 640, y0 + 77, [0xd4, 0x37, 0x2c]],
|
||||
[`${label}: interior stays white`, 640, y0 + 45, [0xff, 0xff, 0xff]],
|
||||
]),
|
||||
];
|
||||
|
||||
const srv = http.createServer((q, r) => { r.setHeader('content-type', 'text/html'); r.end('<!doctype html><title>host</title><body>host</body>'); }).listen(0);
|
||||
@@ -67,6 +82,13 @@ async function run(name, launcher, exe) {
|
||||
// The recovered shorthand goes through the same URL gate: a var() fallback url() to a non-allowed target is dropped.
|
||||
const hostile = await page.evaluate((h) => window.sanitizeCaptureHtml(h), '<div style="background: var(--missing, url(/beacon?x)) center; border: 1px solid var(--b)">x</div><style>.a{background:var(--q, url(https://evil.example/a.png)) no-repeat}</style>');
|
||||
check(name, 'recovered var() shorthand with a disallowed url() fallback is dropped (inline + sheet)', hostile.ok && !/beacon|evil\.example/.test(hostile.html) && /border:\s*1px solid var\(--b\)/.test(hostile.html), hostile.ok ? hostile.html.slice(0, 300) : hostile.reason);
|
||||
check(name, 'sanitiser keeps an overridden `border: ... var()` before its `border-top` override (inline + sheet)', clean.ok
|
||||
&& /border:\s*6px solid var\(--wsb-border\);\s*border-top:\s*14px solid var\(--wsb-accent\)/.test(clean.html)
|
||||
&& /border:\s*6px solid var\(--wsb-border\);\s*border-top(-width)?:\s*14px/.test(clean.html)
|
||||
&& /\.sheet-card\s*\{[^}]*border:\s*6px solid var\(--wsb-border\);\s*border-top:/.test(clean.html));
|
||||
// Recovered-from-source shorthands pass the same URL gate.
|
||||
const hostile2 = await page.evaluate((h) => window.sanitizeCaptureHtml(h), '<div style="background: var(--m, url(/beacon2?x)) center; background-color: red; border: 1px solid var(--b); border-top: 2px solid red">x</div><style>.b{background:var(--q, url(https://evil.example/b.png)) no-repeat;background-position:0 0}</style>');
|
||||
check(name, 'overridden var() shorthand with a disallowed url() fallback is still dropped (inline + sheet)', hostile2.ok && !/beacon2|evil\.example/.test(hostile2.html) && /border:\s*1px solid var\(--b\)/.test(hostile2.html), hostile2.ok ? '' : hostile2.reason);
|
||||
const res = await page.evaluate(([h, pts]) => window.captureAndSample(h, pts), [PAGE, POINTS.map((p) => [p[1], p[2]])]);
|
||||
check(name, 'capture succeeded', res.ok === true, res.ok ? '' : res.reason);
|
||||
if (res.ok) POINTS.forEach(([label, , , want], i) => check(name, label, near(res.samples[i], want), `got rgb(${res.samples[i].join(',')}) want rgb(${want.join(',')})`));
|
||||
|
||||
Reference in new issue
Block a user