git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

scripts/web-probe/browser.mjs (2.3K)

 1 // browser.mjs: what capture.mjs and drive.mjs share — serve a site directory
 2 // on localhost (WebGPU needs a secure context) and open it in headless
 3 // Chromium with WebGPU on SwiftShader.
 4 //
 5 // Playwright is found as `playwright` on the module path, or at
 6 // $PLAYWRIGHT_MODULE (a package directory) when it is installed elsewhere.
 7 import { createRequire } from 'module';
 8 import http from 'http';
 9 import fs from 'fs';
10 import path from 'path';
11 
12 const require = createRequire(import.meta.url);
13 const { chromium } = require(process.env.PLAYWRIGHT_MODULE || 'playwright');
14 const types = { '.html': 'text/html', '.js': 'text/javascript', '.wasm': 'application/wasm', '.json': 'application/json' };
15 
16 /// Open `page` (a file in `root`) at `width` x `height`, with `query` (a
17 /// URL query string, `?…`) if given. Resolves to { page, logs, close };
18 /// `logs` collects the page's console and errors.
19 export async function open(root, file, width, height, query = '') {
20   const server = http.createServer((q, r) => {
21     const p = decodeURIComponent(q.url.split('?')[0]);
22     const f = path.join(root, p === '/' ? file : p);
23     fs.readFile(f, (e, d) => {
24       if (e) { r.writeHead(404); r.end(); return; }
25       r.writeHead(200, { 'content-type': types[path.extname(f)] || 'application/octet-stream' });
26       r.end(d);
27     });
28   });
29   await new Promise(res => server.listen(0, '127.0.0.1', res));
30   // SwiftShader for WebGPU, and for the compositor too: a headless shell with
31   // GPU compositing has no shared-image backing for a WebGPU canvas, and the
32   // device is lost on the first present ("A valid external Instance reference
33   // no longer exists").
34   const browser = await chromium.launch({ args: [
35     '--enable-unsafe-webgpu', '--use-webgpu-adapter=swiftshader', '--enable-features=Vulkan',
36     '--use-angle=swiftshader', '--enable-unsafe-swiftshader', '--disable-gpu-compositing',
37   ] });
38   const page = await browser.newPage({ viewport: { width, height }, deviceScaleFactor: Number(process.env.PROBE_DPR || 1) });
39   const logs = [];
40   page.on('console', m => logs.push(`[${m.type()}] ${m.text()}`));
41   page.on('pageerror', e => logs.push(`[pageerror] ${e}`));
42   await page.goto(`http://127.0.0.1:${server.address().port}/${query}`);
43   return { page, logs, close: async () => { await browser.close(); server.close(); } };
44 }