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 }