// 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();