// Layout/theme performance probe for a running wayBetter app (release build started with // WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=). Plain CDP. Site pages are // only read and reloaded, never clicked. Shell input is synthesized with CDP (no OS input, no focus // change). // // SITE_ORIGIN=https://x.com node dev-tools/resize-perf.mjs // theme reload the first site tab 3x: first paint vs. theme applied, unthemed frames // (needs THEME_GET and THEME_SET: the app's theme commands; THEME_PRESET= measures // with that theme and gives the owner's back) // drag drag the first split divider for 2 s: shell frames, tabs_sync calls, native lag // switch click between two tabs of one group 10x: click -> tab visible latency // window (driven by window-resize.ps1 in parallel) shell resize -> native resize lag // // Lag is measured across processes with epoch timestamps (performance.timeOrigin + now) taken in // the shell (when a layout was sent) and in the site tab (its own `resize` event, i.e. when the // native bounds reached the page). SKIP_PATH=/compose leaves pages under that path out of `theme`. const [port = '9333', ...cases] = process.argv.slice(2); const origin = process.env.SITE_ORIGIN; if (!origin) throw new Error('SITE_ORIGIN is required (the site\'s origin, e.g. https://x.com)'); const skipPath = process.env.SKIP_PATH ?? ''; const wait = ms => new Promise(resolve => setTimeout(resolve, ms)); async function targets() { return (await fetch(`http://127.0.0.1:${port}/json/list`)).json(); } function connect(target) { const ws = new WebSocket(target.webSocketDebuggerUrl); let id = 0; const pending = new Map(); ws.onmessage = event => { const msg = JSON.parse(event.data); if (msg.id) { pending.get(msg.id)?.(msg); pending.delete(msg.id); } }; const ready = new Promise(resolve => { ws.onopen = resolve; }); const send = async (method, params = {}) => { await ready; return new Promise(resolve => { const n = ++id; pending.set(n, resolve); ws.send(JSON.stringify({ id: n, method, params })); }); }; // A frozen page (a parked, hidden tab) never answers: give up after 5 s. const evaluate = async expression => { const result = await Promise.race([ send('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true }), wait(5000).then(() => { throw new Error(`${target.url}: no answer`); }), ]); if (result.result?.exceptionDetails) throw new Error(JSON.stringify(result.result.exceptionDetails).slice(0, 400)); return result.result?.result?.value; }; return { send, evaluate, close: () => ws.close(), url: target.url, id: target.id }; } const pct = (values, p) => { if (!values.length) return null; const s = [...values].sort((a, b) => a - b); return +s[Math.min(s.length - 1, Math.floor(p * s.length))].toFixed(1); }; const stats = values => ({ n: values.length, p50: pct(values, 0.5), p95: pct(values, 0.95), max: values.length ? +Math.max(...values).toFixed(1) : null }); const list = await targets(); const shell = connect(list.find(t => t.type === 'page' && t.url.startsWith('http://tauri.localhost'))); const xTargets = list.filter(t => t.type === 'page' && t.url.startsWith(`${origin}/`)); const xs = []; for (const x of xTargets.map(connect)) { try { await x.evaluate('1'); xs.push(x); } catch { x.close(); } } const results = {}; /** Shell recorder: frame times and every tabs_sync (send epoch, round trip, rects). */ const SHELL_PROBE = `(() => { if (window.__perf) return; const P = window.__perf = { frames: [], syncs: [], on: false }; const now = () => performance.timeOrigin + performance.now(); const loop = t => { if (P.on) P.frames.push(t); requestAnimationFrame(loop); }; requestAnimationFrame(loop); // __TAURI_INTERNALS__.invoke is read-only; its IPC goes through fetch (http://ipc.localhost/). const original = window.fetch.bind(window); window.fetch = (input, init) => { const url = String(input && input.url || input); if (!P.on || !url.includes('/tabs_sync')) return original(input, init); let items = []; try { items = JSON.parse(init.body).items; } catch {} const entry = { at: now(), items: items.map(i => [i.id, i.x, i.y, i.width, i.height, i.visible]) }; P.syncs.push(entry); return original(input, init).finally(() => { entry.rtt = now() - entry.at; }); }; })()`; /** Site tab recorder: its own resize and visibility events, epoch timestamps. */ const X_PROBE = `(() => { if (window.__perf) return; const P = window.__perf = { resizes: [], vis: [] }; const now = () => performance.timeOrigin + performance.now(); addEventListener('resize', () => P.resizes.push([now(), innerWidth, innerHeight])); document.addEventListener('visibilitychange', () => P.vis.push([now(), document.visibilityState])); })()`; async function arm() { await shell.evaluate(SHELL_PROBE); for (const x of xs) await x.evaluate(X_PROBE); await shell.evaluate('(__perf.frames = [], __perf.syncs = [], __perf.on = true)'); for (const x of xs) await x.evaluate('(__perf.resizes = [], __perf.vis = [])'); } async function collect() { const shellData = await shell.evaluate('(__perf.on = false, { frames: __perf.frames, syncs: __perf.syncs })'); const xData = []; for (const x of xs) xData.push({ id: await x.evaluate('(window.__wbTabId ?? null)'), ...(await x.evaluate('({ resizes: __perf.resizes, vis: __perf.vis, w: innerWidth, h: innerHeight })')) }); return { shellData, xData }; } /** For every site tab resize: the latest earlier sync that carried that width for some tab = its cause. */ function lagOf(shellData, xData) { const lags = []; let unmatched = 0; for (const x of xData) { for (const [at, w, h] of x.resizes) { const cause = [...shellData.syncs].reverse().find(s => s.at <= at && s.items.some(i => Math.abs(i[3] - w) <= 1)); if (cause) lags.push(at - cause.at); else unmatched++; } } return { lag: stats(lags), unmatched }; } const frameStats = frames => stats(frames.slice(1).map((t, i) => t - frames[i])); if (cases.includes('theme')) { // A visible site page (a hidden one gets no animation frames to sample). let x = null; for (const candidate of xs) if (!x && await candidate.evaluate(`document.visibilityState === 'visible' && !(${JSON.stringify(skipPath)} && location.pathname.startsWith(${JSON.stringify(skipPath)}))`)) x = candidate; if (!x) throw new Error('no visible site tab'); const themeGet = process.env.THEME_GET; const themeSet = process.env.THEME_SET; if (!themeGet || !themeSet) throw new Error('the theme case needs THEME_GET and THEME_SET (the app\'s theme commands)'); results.themeTarget = x.url; // THEME_PRESET=nord: measure with that theme, then give the owner's theme back. const invoke = (cmd, args) => shell.evaluate(`window.__TAURI_INTERNALS__.invoke(${JSON.stringify(cmd)}, ${JSON.stringify(args)})`); const original = await invoke(themeGet, {}); if (process.env.THEME_PRESET) { await invoke(themeSet, { theme: { preset: process.env.THEME_PRESET } }); await wait(1500); } await x.send('Page.enable'); // Before every other document script: record paints, theme style arrival and each frame's body color. const probe = `(() => { const t0 = performance.now(); const P = window.__themeProbe = { style: null, vars: null, frames: [], dcl: null }; const seen = () => { const s = document.getElementById('bt-theme'); if (P.style === null && s && s.textContent) P.style = performance.now(); const v = document.getElementById('wb-theme-vars') || document.getElementById('bt-theme-snapshot'); if (P.vars === null && v) P.vars = performance.now(); }; new MutationObserver(seen).observe(document, { childList: true, subtree: true, characterData: true }); document.addEventListener('DOMContentLoaded', () => { P.dcl = performance.now(); }); const frame = t => { seen(); // The color the page shows: 's (themes paint the canvas there, stays transparent), // else 's (X's own inline color before a theme). const shown = () => { const h = document.documentElement && getComputedStyle(document.documentElement).backgroundColor; const b = document.body && getComputedStyle(document.body).backgroundColor; return h && h !== 'rgba(0, 0, 0, 0)' ? h : b || h || ''; }; const bg = shown(); P.frames.push([Math.round(t), bg, !!(document.getElementById('bt-theme')?.textContent), !!document.body]); if (t - t0 < 6000) requestAnimationFrame(frame); }; requestAnimationFrame(frame); })()`; const { result } = await x.send('Page.addScriptToEvaluateOnNewDocument', { source: probe }); const runs = []; for (let run = 0; run < 3; run++) { await x.send('Page.reload', {}); await wait(7000); const data = await x.evaluate(`(() => { const P = window.__themeProbe; const paint = Object.fromEntries(performance.getEntriesByType('paint').map(e => [e.name, Math.round(e.startTime)])); const h = getComputedStyle(document.documentElement).backgroundColor; const themedBg = h !== 'rgba(0, 0, 0, 0)' ? h : getComputedStyle(document.body).backgroundColor; // Frames once there is a body to paint (paint timing entries are not exposed in every run). const painted = P.frames.filter(f => f[3]); const unthemed = painted.filter(f => f[1] !== themedBg); return { paint, firstBody: painted[0]?.[0] ?? null, firstThemedFrame: P.frames.find(f => f[3] && f[1] === themedBg)?.[0] ?? null, style: P.style && Math.round(P.style), vars: P.vars && Math.round(P.vars), dcl: P.dcl && Math.round(P.dcl), themedBg, framesAfterFirstPaint: painted.length, unthemedFrames: unthemed.length, lastUnthemedAt: unthemed.length ? unthemed[unthemed.length - 1][0] : null, firstBg: painted[0]?.[1] ?? null }; })()`); runs.push(data); } await x.send('Page.removeScriptToEvaluateOnNewDocument', { identifier: result.identifier }); if (process.env.THEME_PRESET) await invoke(themeSet, { theme: original }); results.theme = runs; } if (cases.includes('drag')) { await arm(); const sash = await shell.evaluate(`(() => { const s = document.querySelector('.dv-split-view-container.dv-horizontal > .dv-sash-container > .dv-sash'); const b = s.getBoundingClientRect(); return [b.left + b.width / 2, b.top + b.height / 2]; })()`); const [sx, sy] = sash; const mouse = (type, x, buttons = 1) => shell.send('Input.dispatchMouseEvent', { type, x, y: sy, button: 'left', buttons, clickCount: 1 }); await mouse('mousePressed', sx); const start = Date.now(); let steps = 0; while (Date.now() - start < 2000) { const phase = (Date.now() - start) / 2000; await mouse('mouseMoved', sx - 200 * Math.sin(phase * Math.PI * 2)); steps++; await wait(8); } await mouse('mouseMoved', sx); const release = await shell.evaluate('performance.timeOrigin + performance.now()'); await mouse('mouseReleased', sx, 0); await wait(1000); const { shellData, xData } = await collect(); const lastX = Math.max(0, ...xData.flatMap(x => x.resizes.map(r => r[0]))); results.drag = { moves: steps, shellFrames: frameStats(shellData.frames.filter(t => true)), syncs: shellData.syncs.length, syncRtt: stats(shellData.syncs.map(s => s.rtt).filter(v => v != null)), xResizes: xData.map(x => x.resizes.length), ...lagOf(shellData, xData), settleAfterRelease: +(lastX - release).toFixed(1), }; } if (cases.includes('window')) { // window-resize.ps1 resizes the (non-foreground) window for ~2 s while this records. await arm(); await shell.evaluate(`(() => { __perf.win = []; addEventListener('resize', () => __perf.win.push([performance.timeOrigin + performance.now(), innerWidth])); })()`); await wait(Number(process.env.WINDOW_MS ?? 3500)); const { shellData, xData } = await collect(); const win = await shell.evaluate('__perf.win'); const shellLag = []; // Native first (the site tab resized before the shell even saw the window change) counts as <= 0. for (const x of xData) for (const [at] of x.resizes) { const cause = [...win].reverse().find(w => w[0] <= at); const after = win.find(w => w[0] > at); if (cause) shellLag.push(at - cause[0]); else if (after) shellLag.push(-(after[0] - at)); } results.window = { shellResizes: win.length, shellFrames: frameStats(shellData.frames), syncs: shellData.syncs.length, syncRtt: stats(shellData.syncs.map(s => s.rtt).filter(v => v != null)), xResizes: xData.map(x => x.resizes.length), xAfterShellResize: stats(shellLag), // With window-resize.ps1's printed restore time: how long the site tab keeps resizing after the window stopped. lastXResize: Math.round(Math.max(0, ...xData.flatMap(x => x.resizes.map(r => r[0])))), lastShellResize: Math.round(Math.max(0, ...win.map(w => w[0]))), ...lagOf(shellData, xData), }; } if (cases.includes('switch')) { await arm(); // Two web tabs in one group: click the inactive one, wait, click back. Latency = click -> the // clicked tab's page becomes visible. const group = await shell.evaluate(`(() => { const g = [...document.querySelectorAll('.dv-groupview')].map(g => g.closest('.dv-view')).find(v => v && v.querySelectorAll('.dv-tab').length >= 2); return !!g; })()`); const latencies = []; if (group) { for (let i = 0; i < 20; i++) { const clicked = await shell.evaluate(`(() => { const tabs = [...document.querySelectorAll('.dv-tab')]; const next = tabs.find(t => t.classList.contains('dv-inactive-tab')); const at = performance.timeOrigin + performance.now(); next.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, button: 0 })); next.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, button: 0 })); next.dispatchEvent(new PointerEvent('pointerup', { bubbles: true, button: 0 })); next.click(); return at; })()`); await wait(600); const shown = []; for (const x of xs) { const vis = await x.evaluate('__perf.vis'); const hit = vis.find(v => v[1] === 'visible' && v[0] >= clicked); if (hit) shown.push(hit[0] - clicked); await x.evaluate('(__perf.vis = [])'); } if (shown.length) latencies.push(Math.min(...shown)); } } const { shellData } = await collect(); results.switch = { group, latency: stats(latencies), syncRtt: stats(shellData.syncs.map(s => s.rtt).filter(v => v != null)) }; } console.log(JSON.stringify(results, null, 1)); shell.close(); for (const x of xs) x.close();