()
| 50 | } |
| 51 | |
| 52 | async function probe(): Promise<ProbeResult> { |
| 53 | let chromiumPath = ""; |
| 54 | try { |
| 55 | const { default: chromium } = await import("@sparticuz/chromium"); |
| 56 | chromiumPath = await chromium.executablePath(); |
| 57 | const args = chromium.args; |
| 58 | |
| 59 | const puppeteer = await import("puppeteer-core"); |
| 60 | |
| 61 | // Write probe HTML to /tmp + serve via file:// — no HTTP server in the |
| 62 | // probe so we don't add a dependency surface that could mask a |
| 63 | // Chrome-side issue. `mkdtempSync` (vs `tmpdir() + Date.now()`) gives |
| 64 | // an unguessable directory name so two concurrent probes on the same |
| 65 | // host don't collide and CodeQL's insecure-tempfile rule clears. |
| 66 | const tmpHtmlDir = mkdtempSync(join(tmpdir(), "hf-beginframe-")); |
| 67 | const htmlPath = join(tmpHtmlDir, "probe.html"); |
| 68 | await fs.writeFile(htmlPath, PROBE_HTML, "utf-8"); |
| 69 | |
| 70 | // BeginFrame requires the full compositor-driving flag set. These match |
| 71 | // the args the engine's `browserManager` passes when `captureMode !== |
| 72 | // "screenshot"`. Without the surface-synchronization + threaded-disable |
| 73 | // flags, Chrome's compositor returns `hasDamage: false` and skips the |
| 74 | // screenshot — the same observation pinned in the hyperframes memory |
| 75 | // ("Chrome's beginFrame with `screenshot` param always reports |
| 76 | // hasDamage=true"). |
| 77 | const beginFrameFlags = [ |
| 78 | "--deterministic-mode", |
| 79 | "--enable-begin-frame-control", |
| 80 | "--disable-new-content-rendering-timeout", |
| 81 | "--run-all-compositor-stages-before-draw", |
| 82 | "--disable-threaded-animation", |
| 83 | "--disable-threaded-scrolling", |
| 84 | "--disable-checker-imaging", |
| 85 | "--disable-image-animation-resync", |
| 86 | "--enable-surface-synchronization", |
| 87 | // Software GL — Lambda has no GPU; matches the in-process renderer's |
| 88 | // software-locked path. |
| 89 | "--use-gl=angle", |
| 90 | "--use-angle=swiftshader", |
| 91 | "--enable-unsafe-swiftshader", |
| 92 | ]; |
| 93 | |
| 94 | const browser = await puppeteer.launch({ |
| 95 | executablePath: chromiumPath, |
| 96 | headless: "shell", |
| 97 | args: [...args, ...beginFrameFlags], |
| 98 | defaultViewport: { width: 800, height: 600 }, |
| 99 | }); |
| 100 | try { |
| 101 | const page = await browser.newPage(); |
| 102 | await page.goto(`file://${htmlPath}`, { waitUntil: "domcontentloaded", timeout: 30_000 }); |
| 103 | const session = await page.createCDPSession(); |
| 104 | await session.send("HeadlessExperimental.enable"); |
| 105 | // Warm-up beginFrame with noDisplayUpdates: true — drives the |
| 106 | // compositor without producing a screenshot, matching how the engine |
| 107 | // primes a capture loop. |
| 108 | await session.send("HeadlessExperimental.beginFrame", { |
| 109 | frameTimeTicks: 0, |
no test coverage detected