wasm fixes
Some checks failed
CI / build-test-release (push) Failing after 16m28s

This commit is contained in:
Jorijn van der Graaf 2026-05-26 22:50:08 +02:00
commit 2b7e37e3b9
3 changed files with 311 additions and 46 deletions

View file

@ -1125,9 +1125,20 @@ void Crafter::EnableWasiBrowserRuntime(Configuration& cfg) {
};
walk(&cfg);
// Per-build cache-busting token. Stamped onto every script src + the
// wasm URL so a regular browser reload sees fresh files even though
// the dev server (python -m http.server) sends no Cache-Control
// headers. Using ms-since-epoch is enough to be unique per build
// without invoking any version-control machinery.
const std::string buildId = std::to_string(
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count());
std::string envScriptTags;
for (const std::string& name : envScripts) {
envScriptTags += std::format(" <script src=\"{}\" type=\"module\"></script>\n", name);
envScriptTags += std::format(
" <script src=\"{}?v={}\" type=\"module\"></script>\n",
name, buildId);
}
// Walk the dep graph again for non-JS assets — these get pre-loaded by
@ -1215,6 +1226,7 @@ void Crafter::EnableWasiBrowserRuntime(Configuration& cfg) {
std::string html = buf.str();
html = std::regex_replace(html, std::regex(R"(\{\{WASM\}\})"), cfg.outputName + ".wasm");
html = std::regex_replace(html, std::regex(R"(\{\{ENV_SCRIPTS\}\})"), envScriptTags);
html = std::regex_replace(html, std::regex(R"(\{\{BUILDID\}\})"), buildId);
std::ofstream out(htmlPath);
out << html;
out.close();
@ -1467,20 +1479,81 @@ int Crafter::Run(int argc, char** argv) {
return basePort;
};
const int port = findFreePort(8080, 16);
// Cross-origin isolation: the browser coarsens
// performance.now() (and chrono::steady_clock under wasi)
// to ~0.1ms unless the response carries COOP/COEP, which
// floors the --timing overlay's sub-ms phase counters to
// zero. Emitting same-origin / require-corp drops the
// resolution to ~5µs and also unlocks SharedArrayBuffer
// for any future threading work. CORP keeps the local
// asset fetches passing under require-corp.
auto writeFile = [](const fs::path& p, std::string_view contents) {
std::ofstream f(p, std::ios::binary | std::ios::trunc);
f.write(contents.data(), static_cast<std::streamsize>(contents.size()));
};
std::string cmd;
std::string_view picked;
bool isolated = false;
if (have("caddy")) {
picked = "caddy";
cmd = std::format("caddy file-server --listen :{} --root {}", port, absDir.string());
} else if (have("python3")) {
picked = "python3";
cmd = std::format("python3 -m http.server --directory {} {}", absDir.string(), port);
} else if (have("python")) {
picked = "python";
cmd = std::format("python -m http.server --directory {} {}", absDir.string(), port);
isolated = true;
// caddy file-server has no --header flag; write a
// Caddyfile next to the build output. Adapter is
// inferred from the .caddyfile extension when run
// via `caddy run`.
fs::path cf = absDir / "Caddyfile.coi";
writeFile(cf, std::format(
":{} {{\n"
" root * {}\n"
" header Cross-Origin-Opener-Policy \"same-origin\"\n"
" header Cross-Origin-Embedder-Policy \"require-corp\"\n"
" header Cross-Origin-Resource-Policy \"same-origin\"\n"
" header Cache-Control \"no-store\"\n"
" file_server\n"
"}}\n",
port, absDir.string()));
cmd = std::format("caddy run --config {} --adapter caddyfile",
cf.string());
} else if (have("python3") || have("python")) {
std::string_view py = have("python3") ? "python3" : "python";
picked = py;
isolated = true;
// Inline a tiny SimpleHTTPRequestHandler subclass
// that appends the COI headers on every response.
// Lives in absDir so the user can re-run it manually
// (`python3 absDir/.serve-coi.py 8080`).
fs::path sp = absDir / ".serve-coi.py";
writeFile(sp,
"import http.server, socketserver, sys, os\n"
"class H(http.server.SimpleHTTPRequestHandler):\n"
" def end_headers(self):\n"
" self.send_header('Cross-Origin-Opener-Policy', 'same-origin')\n"
" self.send_header('Cross-Origin-Embedder-Policy', 'require-corp')\n"
" self.send_header('Cross-Origin-Resource-Policy', 'same-origin')\n"
" self.send_header('Cache-Control', 'no-store')\n"
" super().end_headers()\n"
"socketserver.TCPServer.allow_reuse_address = True\n"
"port = int(sys.argv[1]) if len(sys.argv) > 1 else 8080\n"
"os.chdir(os.path.dirname(os.path.abspath(__file__)))\n"
"with socketserver.TCPServer(('', port), H) as s:\n"
" s.serve_forever()\n");
cmd = std::format("{} {} {}", py, sp.string(), port);
} else if (have("php")) {
picked = "php";
cmd = std::format("php -S 0.0.0.0:{} -t {}", port, absDir.string());
// php -S supports a router script — emit one that
// sets COI headers then delegates to the built-in
// static-file handler by returning false.
fs::path rp = absDir / ".serve-coi.php";
writeFile(rp,
"<?php\n"
"header('Cross-Origin-Opener-Policy: same-origin');\n"
"header('Cross-Origin-Embedder-Policy: require-corp');\n"
"header('Cross-Origin-Resource-Policy: same-origin');\n"
"header('Cache-Control: no-store');\n"
"return false;\n");
isolated = true;
cmd = std::format("php -S 0.0.0.0:{} -t {} {}",
port, absDir.string(), rp.string());
} else if (have("ruby")) {
picked = "ruby";
cmd = std::format("ruby -run -e httpd {} -p{}", absDir.string(), port);
@ -1496,7 +1569,16 @@ int Crafter::Run(int argc, char** argv) {
"caddy, python3, python, php, ruby, busybox, npx (Node.js).");
return 1;
}
std::println("serving {} via {} at http://localhost:{}/", absDir.string(), picked, port);
if (isolated) {
std::println("serving {} via {} at http://localhost:{}/ (cross-origin isolated)",
absDir.string(), picked, port);
} else {
std::println("serving {} via {} at http://localhost:{}/", absDir.string(), picked, port);
std::println(std::cerr,
"warning: {} does not emit COOP/COEP — performance.now() will be coarse "
"(~0.1ms). Install caddy, python3, or php for cross-origin isolation.",
picked);
}
return std::system(cmd.c_str()) == 0 ? 0 : 1;
}
// wasi-cli wasm — needs a standalone runtime.