wasm improvements

This commit is contained in:
Jorijn van der Graaf 2026-08-05 04:08:13 +02:00
commit 6aa0338c0f
9 changed files with 603 additions and 70 deletions

View file

@ -20,8 +20,20 @@ namespace Crafter::DomBindings {
void AddPopStateListener(std::int32_t id);
__attribute__((import_module("env"), import_name("removePopStateListener")))
void RemovePopStateListener(std::int32_t id);
__attribute__((import_module("env"), import_name("replaceState")))
void ReplaceState(const char* data, std::int32_t dataLength,
const char* title, std::int32_t titleLength,
const char* url, std::int32_t urlLength);
__attribute__((import_module("env"), import_name("getPathName")))
const char* GetPathName();
__attribute__((import_module("env"), import_name("getSearch")))
const char* GetSearch();
__attribute__((import_module("env"), import_name("getHash")))
const char* GetHash();
__attribute__((import_module("env"), import_name("getHref")))
const char* GetHref();
__attribute__((import_module("env"), import_name("navigate")))
void Navigate(const char* url, std::int32_t urlLength, bool replace);
// Defined in Crafter.Graphics-Dom.cpp.
std::int32_t PopStateRegister(std::function<void()> cb);
@ -48,11 +60,114 @@ namespace Crafter::Router {
Crafter::DomBindings::PopStateUnregister(id);
}
void ReplaceState(std::string_view data, std::string_view title, std::string_view url) {
Crafter::DomBindings::ReplaceState(
data.data(), static_cast<std::int32_t>(data.size()),
title.data(), static_cast<std::int32_t>(title.size()),
url.data(), static_cast<std::int32_t>(url.size()));
}
// The four location accessors share the same shape: the JS side
// WasmAllocs a NUL-terminated UTF-8 copy, we adopt it into a
// std::string and free the raw buffer. A null return means the JS
// allocation failed.
namespace {
std::string AdoptJsString(const char* raw) {
if (!raw) return {};
std::string out(raw);
std::free(const_cast<char*>(raw));
return out;
}
}
std::string GetPath() {
const char* raw = Crafter::DomBindings::GetPathName();
if (!raw) return {};
std::string out(raw);
std::free(const_cast<char*>(raw));
return AdoptJsString(Crafter::DomBindings::GetPathName());
}
std::string GetSearch() {
return AdoptJsString(Crafter::DomBindings::GetSearch());
}
std::string GetHash() {
return AdoptJsString(Crafter::DomBindings::GetHash());
}
std::string GetHref() {
return AdoptJsString(Crafter::DomBindings::GetHref());
}
void Navigate(std::string_view url, bool replace) {
Crafter::DomBindings::Navigate(
url.data(), static_cast<std::int32_t>(url.size()), replace);
}
// ─── Query-string parsing ─────────────────────────────────────────
//
// Pure string work, no JS round-trip. Kept here rather than left to
// callers because every consumer would otherwise hand-roll percent
// decoding, and getting `+` vs `%20` wrong is the usual bug.
namespace {
int HexVal(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}
}
std::string PercentDecode(std::string_view in) {
std::string out;
out.reserve(in.size());
for (std::size_t i = 0; i < in.size(); ++i) {
const char c = in[i];
if (c == '+') {
// application/x-www-form-urlencoded encodes space as '+'.
out.push_back(' ');
} else if (c == '%' && i + 2 < in.size()) {
const int hi = HexVal(in[i + 1]);
const int lo = HexVal(in[i + 2]);
if (hi >= 0 && lo >= 0) {
out.push_back(static_cast<char>(hi * 16 + lo));
i += 2;
} else {
// Malformed escape: pass the '%' through rather than
// dropping input. Lets a stray '%' in a search box
// survive a round trip.
out.push_back(c);
}
} else {
out.push_back(c);
}
}
return out;
}
std::vector<std::pair<std::string, std::string>> ParseQuery(std::string_view query) {
std::vector<std::pair<std::string, std::string>> out;
if (!query.empty() && query.front() == '?') query.remove_prefix(1);
while (!query.empty()) {
const std::size_t amp = query.find('&');
std::string_view pair = query.substr(0, amp);
query = (amp == std::string_view::npos) ? std::string_view{}
: query.substr(amp + 1);
if (pair.empty()) continue; // tolerate "a=1&&b=2" and a trailing '&'
const std::size_t eq = pair.find('=');
if (eq == std::string_view::npos) {
// Valueless key ("?debug") — present, empty value.
out.emplace_back(PercentDecode(pair), std::string{});
} else {
out.emplace_back(PercentDecode(pair.substr(0, eq)),
PercentDecode(pair.substr(eq + 1)));
}
}
return out;
}
std::optional<std::string> QueryGet(std::string_view query, std::string_view key) {
for (auto& [k, v] : ParseQuery(query)) {
if (k == key) return v;
}
return std::nullopt;
}
}