#!/usr/bin/env node // Page-value reproduction only. This public fixture does not replace the controlled // run's separate encrypted-profile provenance, signature and runtime-load review. import assert from 'node:assert/strict'; import fs from 'node:fs/promises'; import { createReadStream } from 'node:fs'; import http from 'node:http'; import os from 'node:os'; import path from 'node:path'; import { createHash, randomBytes } from 'node:crypto'; import { execFile, spawn } from 'node:child_process'; import { createRequire } from 'node:module'; import { promisify } from 'node:util'; import { fileURLToPath, pathToFileURL } from 'node:url'; const execute = promisify(execFile), pause = ms => new Promise(resolve => setTimeout(resolve, ms)); const SELF = fileURLToPath(import.meta.url); const HTML = new URL('./render-fixture.html', import.meta.url); const sha = value => createHash('sha256').update(value).digest('hex'); const withTimeout = (promise, ms, label) => { let timer; return Promise.race([promise, new Promise((_, reject) => { timer = setTimeout(() => reject(new Error(`${label} exceeded ${ms}ms`)), ms); })]).finally(() => clearTimeout(timer)); }; async function fileHash(filename) { const digest = createHash('sha256'); for await (const chunk of createReadStream(filename)) digest.update(chunk); return digest.digest('hex'); } async function processTable() { const { stdout } = await execute('ps', ['-Ao', 'pid=,ppid=,pgid=,stat=,args='], { timeout: 3000, maxBuffer: 8 * 1024 * 1024 }); return stdout.split('\n').map(line => line.match(/^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.*)$/)).filter(Boolean).map(match => ({ pid: Number(match[1]), ppid: Number(match[2]), pgid: Number(match[3]), state: match[4], command: match[5] })); } function descendants(rows, rootPid) { const result = [], queue = [rootPid], seen = new Set(queue); while (queue.length) { const parent = queue.shift(); for (const row of rows) if (row.ppid === parent && !seen.has(row.pid)) { seen.add(row.pid); result.push(row); queue.push(row.pid); } } return result; } async function identity(pid) { try { const stat = await fs.readFile(`/proc/${pid}/stat`, 'utf8'); const fields = stat.slice(stat.lastIndexOf(')') + 2).split(' '); return { startTicks: fields[19], state: fields[0] }; } catch { return null; } } async function runPool({ workerPath, tasks, workers = 20, timeoutMs = 180000, attemptTimeoutMs = 60000, env = process.env }) { const limit = Math.min(workers, tasks.length, 9), active = new Map(), allStates = [], results = new Array(tasks.length); const started = Date.now(); let next = 0, peakActive = 0, stopped = false, globalTimedOut = false, refreshing = false; const cleanupErrors = [], browserConcurrencySamples = []; let peakBrowserLanes = 0; const remember = async (state, row) => { if (!state.known.has(row.pid)) { const found = await identity(row.pid); if (found) state.known.set(row.pid, { ...row, ...found }); } }; const refresh = async () => { if (refreshing) return; refreshing = true; try { const rows = await processTable(); const browserLanes = tasks.filter(task => rows.some(row => row.command.includes(`--user-data-dir=${task.udd}`) && !row.command.includes('--type='))); peakBrowserLanes = Math.max(peakBrowserLanes, browserLanes.length); browserConcurrencySamples.push({ elapsedMs: Date.now() - started, browserLaneCount: browserLanes.length, lanes: browserLanes.map(task => task.id) }); for (const state of active.values()) { const owned = descendants(rows, state.child.pid); for (const row of rows) if (row.command.includes(`--user-data-dir=${state.task.udd}`) || row.pgid === state.child.pid) owned.push(row); await Promise.all(owned.map(row => remember(state, row))); } } catch (error) { cleanupErrors.push(`process-snapshot: ${error.message}`); } finally { refreshing = false; } }; const signalOwned = async (state, signal) => { for (const [pid, known] of state.known) { const current = await identity(pid); if (pid > 1 && pid !== process.pid && current?.startTicks === known.startTicks && current.state !== 'Z') { try { process.kill(pid, signal); } catch (error) { if (error.code !== 'ESRCH') cleanupErrors.push(`${signal} ${pid}: ${error.message}`); } } } const leader = await identity(state.child.pid), knownLeader = state.known.get(state.child.pid); if (leader && knownLeader && leader.startTicks === knownLeader.startTicks) { try { process.kill(-state.child.pid, signal); } catch (error) { if (error.code !== 'ESRCH') cleanupErrors.push(`${signal} PGID ${state.child.pid}: ${error.message}`); } } }; const terminate = async (state, reason) => { if (state.terminating) return state.terminating; state.terminationReason = reason; state.terminating = (async () => { await refresh(); await signalOwned(state, 'SIGTERM'); await pause(250); await signalOwned(state, 'SIGKILL'); })(); return state.terminating; }; let resolveDone; const done = new Promise(resolve => { resolveDone = resolve; }); const finish = async (state, code, signal) => { if (state.finishing) return; state.finishing = true; clearTimeout(state.timer); if (state.terminating) await state.terminating; await refresh(); await terminate(state, state.terminationReason ?? 'worker-exit-final-sweep'); active.delete(state.child.pid); results[state.index] = { index: state.index, taskId: state.task.id, code, signal, timedOut: state.timedOut, terminationReason: state.terminationReason, stdout: state.stdout, stderr: state.stderr, outputTruncated: state.outputTruncated, processes: [...state.known.values()], elapsedMs: Date.now() - state.started }; pump(); }; const launch = (task, index) => { const child = spawn(process.execPath, [workerPath], { detached: true, stdio: ['ignore', 'pipe', 'pipe'], env: { ...env, DISPLAY: ':88', PUBLIC_RENDER_TASK: JSON.stringify(task) } }); const state = { child, task, index, started: Date.now(), known: new Map(), stdout: '', stderr: '', timedOut: false, outputTruncated: false }; active.set(child.pid, state); allStates.push(state); peakActive = Math.max(peakActive, active.size); void remember(state, { pid: child.pid, ppid: process.pid, pgid: child.pid, command: `${process.execPath} ${workerPath}`, state: 'R' }); for (const stream of ['stdout', 'stderr']) child[stream].on('data', chunk => { const value = state[stream] + chunk.toString(); if (value.length > 2 * 1024 * 1024) { state.outputTruncated = true; state[stream] = value.slice(-2 * 1024 * 1024); } else state[stream] = value; }); child.on('error', error => { state.stderr += `spawn-error: ${error.message}\n`; void finish(state, null, null); }); child.on('exit', (code, signal) => { void finish(state, code, signal); }); state.timer = setTimeout(() => { state.timedOut = true; void terminate(state, 'task-timeout'); }, Math.min(attemptTimeoutMs, Math.max(1, timeoutMs - (Date.now() - started)))); }; const pump = () => { while (!stopped && active.size < limit && next < tasks.length) { const index = next++; launch(tasks[index], index); } if (active.size === 0 && (stopped || next >= tasks.length)) resolveDone(); }; const stop = reason => { stopped = true; for (const state of active.values()) { state.timedOut = true; void terminate(state, reason); } if (!active.size) resolveDone(); }; const handleSignal = signal => stop(signal); const sigterm = () => handleSignal('SIGTERM'), sigint = () => handleSignal('SIGINT'); process.on('SIGTERM', sigterm); process.on('SIGINT', sigint); const timer = setTimeout(() => { globalTimedOut = true; stop('global-timeout'); }, timeoutMs); const poll = setInterval(() => { void refresh(); }, 250); try { pump(); await done; } finally { clearTimeout(timer); clearInterval(poll); process.off('SIGTERM', sigterm); process.off('SIGINT', sigint); } for (let index = 0; index < tasks.length; index++) if (!results[index]) results[index] = { index, taskId: tasks[index].id, code: null, status: 'not-started', timedOut: globalTimedOut }; await pause(100); const residual = []; for (const state of allStates) for (const [pid, known] of state.known) { const current = await identity(pid); if (current?.startTicks === known.startTicks) residual.push({ taskId: state.task.id, pid, state: current.state, command: known.command }); } const finalRows = await processTable(); for (const row of finalRows) if (tasks.some(task => row.command.includes(`--user-data-dir=${task.udd}`)) && !residual.some(found => found.pid === row.pid)) residual.push(row); return { requestedWorkers: workers, effectiveWorkers: limit, peakActive, peakBrowserLanes, browserConcurrencySamples, totalTimeoutMs: timeoutMs, taskTimeoutMs: attemptTimeoutMs, globalTimedOut, elapsedMs: Date.now() - started, results, cleanup: { residual, errors: cleanupErrors, verified: residual.length === 0 && cleanupErrors.length === 0 } }; } async function collect(task) { let context, server; const record = { taskId: task.id, lane: task.lane, repeat: task.repeat, status: 'running', startedAt: new Date().toISOString(), headed: true, display: ':88', viewportInput: { width: 1280, height: 900 }, scope: 'page values only; profile loading is not independently certified by this public reproduction' }; try { const require = createRequire(pathToFileURL(task.packageJson)); const { chromium } = require(task.packageRoot); const html = await fs.readFile(HTML), routePath = `/${randomBytes(16).toString('hex')}/`; let resolvePayload, rejectPayload, posted = false; const postedResult = new Promise((resolve, reject) => { resolvePayload = resolve; rejectPayload = reject; }); postedResult.catch(() => {}); const counts = { pageGets: 0, resultPosts: 0, otherRequests: 0 }; server = http.createServer(async (request, response) => { if (request.method === 'GET' && request.url === routePath) { counts.pageGets++; response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store', 'Content-Security-Policy': "default-src 'self'; script-src 'unsafe-inline'; connect-src 'self'; img-src 'none'; object-src 'none'; base-uri 'none'" }); response.end(html); return; } if (request.method === 'POST' && request.url === `${routePath}results` && !posted) { counts.resultPosts++; try { const chunks = []; let bytes = 0; for await (const chunk of request) { bytes += chunk.length; if (bytes > 131072) throw new Error('Page result exceeds 128 KiB'); chunks.push(chunk); } const payload = JSON.parse(Buffer.concat(chunks).toString('utf8')); posted = true; response.writeHead(200, { 'Content-Type': 'text/plain' }); response.end('recorded'); resolvePayload(payload); } catch (error) { response.writeHead(400); response.end('invalid result'); rejectPayload(error); } return; } counts.otherRequests++; response.writeHead(404); response.end(); }); await new Promise((resolve, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', resolve); }); const origin = `http://127.0.0.1:${server.address().port}`, url = `${origin}${routePath}`; const args = ['--disable-background-networking', '--disable-component-update', '--disable-sync', '--no-first-run', '--no-default-browser-check', '--password-store=basic', '--use-mock-keychain']; if (task.lane === 'declarative') args.push(`--bot-profile=${task.profile}`); record.explicitArguments = args.map(arg => arg.startsWith('--bot-profile=') ? '--bot-profile=' : arg); context = await chromium.launchPersistentContext(task.udd, { executablePath: task.browser, headless: false, viewport: record.viewportInput, args, timeout: 30000, chromiumSandbox: false }); await context.route('**/*', route => route.request().url().startsWith(`${origin}/`) ? route.continue() : route.abort()); const page = context.pages()[0] || await context.newPage(); const session = await context.newCDPSession(page); record.reportedBrowserVersion = await session.send('Browser.getVersion'); const response = await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 }); assert.equal(response?.status(), 200); const observation = await withTimeout(postedResult, 45000, 'same-origin result POST'); assert.equal(observation.schema, 'issue001204-rendering-v1'); assert.equal(observation.pageUrl, url); observation.pageUrl = '//'; record.observation = observation; record.serverCounts = counts; assert.equal(counts.pageGets, 1); assert.equal(counts.resultPosts, 1); record.unavailable = ['canvas', 'webgl', 'fonts', 'audio'].filter(name => observation[name]?.status !== 'available'); if (observation.canvas?.status === 'available') { assert.equal(observation.canvas.byteLength, 96000); assert.match(observation.canvas.rgbaSha256, /^[a-f0-9]{64}$/); } if (observation.audio?.status === 'available') { assert.equal(observation.audio.length, 5000); assert.equal(observation.audio.sampleByteLength, 20000); assert.ok(Number.isFinite(observation.audio.last500AbsoluteSum)); assert.match(observation.audio.sampleBytesSha256, /^[a-f0-9]{64}$/); } record.status = record.unavailable.length ? 'incomplete-observation' : 'page-values-collected'; } catch (error) { record.status = 'failed'; record.error = { name: error.name, message: String(error.message).replaceAll(task.browser, '').replaceAll(task.profile || '\u0000', '').replaceAll(task.udd, '').slice(0, 3000) }; } finally { if (context) { try { await withTimeout(context.close(), 5000, 'context.close'); record.contextClosed = true; } catch (error) { record.contextClosed = false; record.status = 'cleanup-failed'; record.closeError = { name: error.name }; } } if (server) { server.closeAllConnections(); await new Promise(resolve => server.close(resolve)); } record.completedAt = new Date().toISOString(); await fs.writeFile(task.resultPath, JSON.stringify(record, null, 2) + '\n'); if (record.status !== 'page-values-collected') process.exitCode = 1; } } function parse(argv) { const result = { workers: 20 }; for (let index = 0; index < argv.length; index++) { const match = argv[index].match(/^--([^=]+)(?:=(.*))?$/); assert.ok(match && ['stock', 'botbrowser', 'profile', 'native', 'patched', 'workers', 'pool', 'output'].includes(match[1]), `Unknown option ${argv[index]}`); const key = match[1] === 'pool' ? 'workers' : match[1], value = match[2] ?? argv[++index]; assert.ok(value && !value.startsWith('--'), `Missing --${key} value`); result[key] = value; } result.workers = Number(result.workers); assert.ok(Number.isInteger(result.workers) && result.workers >= 1 && result.workers <= 20, '--workers must be 1..20'); for (const key of ['stock', 'botbrowser', 'profile', 'native', 'patched']) assert.ok(path.isAbsolute(result[key] || ''), `Supply an absolute --${key} path`); return result; } async function packageInfo(root, name, version) { root = await fs.realpath(root); const packageJson = path.join(root, 'package.json'), bytes = await fs.readFile(packageJson), data = JSON.parse(bytes); assert.equal(data.name, name); assert.equal(data.version, version); return { root, packageJson, public: { name, version, packageJsonSha256: sha(bytes) } }; } async function browserInfo(filename, expectedVersion) { const executable = await fs.realpath(filename), { stdout, stderr } = await execute(executable, ['--password-store=basic', '--use-mock-keychain', '--version'], { timeout: 10000, maxBuffer: 1048576 }); const version = `${stdout}${stderr}`.match(/\b(\d+\.\d+\.\d+\.\d+)\b/)?.[1]; assert.equal(version, expectedVersion); return { executable, public: { version, binarySha256: await fileHash(executable) } }; } async function availableResources() { const before = os.cpus(); await pause(250); const after = os.cpus(); let idle = 0, total = 0; for (let index = 0; index < after.length; index++) { idle += after[index].times.idle - before[index].times.idle; total += Object.keys(after[index].times).reduce((sum, key) => sum + after[index].times[key] - before[index].times[key], 0); } const meminfo = await fs.readFile('/proc/meminfo', 'utf8'); return { capturedAt: new Date().toISOString(), cpuCount: after.length, estimatedIdleCores: total ? idle / total * after.length : 0, availableMemoryBytes: Number(meminfo.match(/^MemAvailable:\s*(\d+)\s*kB/m)?.[1] || 0) * 1024 }; } async function main() { const started = Date.now(), options = parse(process.argv.slice(2)); const report = { status: 'running', ranAt: new Date().toISOString(), scope: 'Owned-page value reproduction; this is not a profile-load audit or a detector test', requestedWorkers: options.workers, headed: true, display: process.env.DISPLAY, profileLoadAudited: false, limitations: ['Native and patched lanes share one stock executable. BotBrowser uses a different full browser version and a Windows-target profile on Linux; this comparison does not isolate one implementation cause.', 'Candidate font hits are not a full installed-font inventory. Fixed canvas and audio digests are not identities or security scores.', 'The controlled evidence has separate signature and runtime loading review. Public page values cannot replace that review.', 'The 180-second execution deadline excludes a short final process cleanup interval. Temporary task directories are retained.'] }; const output = path.resolve(options.output || 'render-comparison-output.json'); try { assert.equal(process.platform, 'linux'); assert.equal(process.env.DISPLAY, ':88'); const [stock, botbrowser, native, patched] = await Promise.all([browserInfo(options.stock, '157.0.8103.50'), browserInfo(options.botbrowser, '157.0.8103.38'), packageInfo(options.native, 'playwright-core', '1.64.1'), packageInfo(options.patched, 'rebrowser-playwright-core', '1.64.102')]); assert.notEqual(stock.executable, botbrowser.executable); const profile = await fs.realpath(options.profile); assert.equal(path.basename(profile), 'chrome157_win11_x64.enc'); report.inputs = { stock: stock.public, botbrowser: botbrowser.public, native: native.public, patched: patched.public, profileSha256: await fileHash(profile), profileTarget: 'win11_x64' }; report.source = { scriptSha256: await fileHash(SELF), htmlSha256: await fileHash(fileURLToPath(HTML)) }; report.resources = await availableResources(); const effective = Math.min(options.workers, 9); assert.ok(report.resources.estimatedIdleCores >= effective && report.resources.availableMemoryBytes >= effective * 1024 ** 3, 'Insufficient fresh resources for the requested pool; no silent serial fallback'); const workspace = await fs.mkdtemp(path.join(os.tmpdir(), 'public-render-fixture-')), tasks = []; for (let repeat = 1; repeat <= 3; repeat++) for (const lane of ['native', 'patched', 'declarative']) { const id = `${lane}-${repeat}`, udd = path.join(workspace, `${id}-udd`), dependency = lane === 'patched' ? patched : native; await fs.mkdir(udd); tasks.push({ id, lane, repeat, udd, resultPath: path.join(workspace, `${id}.json`), browser: lane === 'declarative' ? botbrowser.executable : stock.executable, profile: lane === 'declarative' ? profile : null, packageRoot: dependency.root, packageJson: dependency.packageJson }); } const pool = await runPool({ workerPath: SELF, tasks, workers: options.workers, timeoutMs: Math.max(1, 180000 - (Date.now() - started)), attemptTimeoutMs: 60000 }); report.execution = { requestedWorkers: pool.requestedWorkers, effectiveWorkers: pool.effectiveWorkers, peakActive: pool.peakActive, peakBrowserLanes: pool.peakBrowserLanes, browserConcurrencySamples: pool.browserConcurrencySamples, globalTimedOut: pool.globalTimedOut, totalDeadlineMs: 180000, poolBudgetMs: pool.totalTimeoutMs, taskDeadlineMs: 60000, elapsedMs: pool.elapsedMs, cleanup: { verified: pool.cleanup.verified, residualCount: pool.cleanup.residual.length, errorCount: pool.cleanup.errors.length } }; report.tasks = await Promise.all(tasks.map(async (task, index) => { let record; try { record = JSON.parse(await fs.readFile(task.resultPath, 'utf8')); } catch (error) { record = { taskId: task.id, lane: task.lane, repeat: task.repeat, status: 'missing-result', error: { name: error.name } }; } const runtime = pool.results[index]; return { ...record, workerExitCode: runtime.code, workerTimedOut: runtime.timedOut }; })); report.profileUnchanged = await fileHash(profile) === report.inputs.profileSha256; report.status = report.tasks.every(task => task.status === 'page-values-collected' && task.workerExitCode === 0 && !task.workerTimedOut) && pool.cleanup.verified && !pool.globalTimedOut && report.profileUnchanged ? 'page-values-collected' : 'incomplete'; } catch (error) { report.status = 'setup-failed'; report.error = { name: error.name, message: String(error.message).slice(0, 3000) }; } report.completedAt = new Date().toISOString(); report.elapsedMs = Date.now() - started; await fs.writeFile(output, JSON.stringify(report, null, 2) + '\n'); console.log(JSON.stringify({ status: report.status, profileLoadAudited: false, output, peakActive: report.execution?.peakActive, peakBrowserLanes: report.execution?.peakBrowserLanes }, null, 2)); if (report.status !== 'page-values-collected') process.exitCode = 1; } if (process.env.PUBLIC_RENDER_TASK) await collect(JSON.parse(process.env.PUBLIC_RENDER_TASK)); else await main();