// Scripts never read the sub-pixel geometry the base harness measures directly; they read
// `offsetWidth` / `clientWidth` / `scrollWidth`, which round to an integer CSS pixel per
// the CSSOM View spec. A sub-pixel quantization difference between engines only becomes
// script-visible if it lands on opposite sides of that integer boundary in different
// engines. This sweeps the twelve-level nested-percentage chain (same shape as
// crates/harness/src/cases.rs `recipe_nesting_depth`, which is where the base harness's
// worst disagreement -- 0.0375px -- was found) across container widths and all four
// device pixel ratios, and reports every width/DPR where the integer-rounded properties
// disagree across engines.
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { launchAll, closeAll, openPages, measureOnPages, doc, ENGINE_NAMES } from './engines.mjs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const resultsDir = path.join(__dirname, 'results');
fs.mkdirSync(resultsDir, { recursive: true });
const DEPTH = 12;
const STEP_PCTS = ['83.333%', '91.667%', '75%', '33.333%', '16.6667%', '14.2857%'];
function buildHtml(stepPct, outerWidth) {
let inner = '
';
for (let i = 0; i < DEPTH; i++) inner = `${inner}
`;
const css = `.wrap{width:${outerWidth}px}.lvl{width:${stepPct}}.leaf{width:${stepPct};height:15px;background:#369}`;
return doc(css, `${inner}
`);
}
function measureFn() {
const leaf = document.querySelector('.leaf');
const wrap = document.querySelector('.wrap');
return {
leafOffsetWidth: leaf.offsetWidth,
leafClientWidth: leaf.clientWidth,
leafBoundingWidth: leaf.getBoundingClientRect().width,
wrapOffsetWidth: wrap.offsetWidth,
wrapScrollWidth: wrap.scrollWidth,
};
}
async function main() {
const { browsers, versions } = await launchAll();
const dprs = [1, 1.5, 2, 3];
const widths = [];
for (let w = 60; w <= 1000; w += 4) widths.push(w);
const hits = [];
let cases = 0;
const t0 = Date.now();
for (const dpr of dprs) {
const { pages, close } = await openPages(browsers, dpr);
for (const stepPct of STEP_PCTS) {
for (const width of widths) {
cases++;
const html = buildHtml(stepPct, width);
const perEngine = await measureOnPages(pages, html, measureFn);
const offsets = ENGINE_NAMES.map((n) => perEngine[n].leafOffsetWidth);
const clients = ENGINE_NAMES.map((n) => perEngine[n].leafClientWidth);
const allAgreeOffset = offsets.every((v) => v === offsets[0]);
const allAgreeClient = clients.every((v) => v === clients[0]);
if (!allAgreeOffset || !allAgreeClient) {
hits.push({ dpr, stepPct, width, raw: perEngine });
}
}
}
await close();
}
await closeAll(browsers);
const elapsedMs = Date.now() - t0;
const out = { versions, cases, elapsedMs, hits };
fs.writeFileSync(path.join(resultsDir, 'integer-rounding.json'), JSON.stringify(out, null, 2));
console.log(`integer-rounding: ${cases} cases in ${elapsedMs}ms, ${hits.length} divergent hits`);
console.log('versions:', versions);
for (const hit of hits.slice(0, 40)) console.log(JSON.stringify(hit));
if (hits.length > 40) console.log(`... and ${hits.length - 40} more (see results/integer-rounding.json)`);
}
main();