wasm improvements
This commit is contained in:
parent
42a479572d
commit
6aa0338c0f
9 changed files with 603 additions and 70 deletions
|
|
@ -41,6 +41,24 @@ namespace Crafter::DomBindings {
|
|||
void SetProperty(std::int32_t ptr,
|
||||
const char* property, std::int32_t propertyLength,
|
||||
const char* value, std::int32_t valueLength);
|
||||
__attribute__((import_module("env"), import_name("setAttribute")))
|
||||
void SetAttribute(std::int32_t ptr,
|
||||
const char* name, std::int32_t nameLength,
|
||||
const char* value, std::int32_t valueLength);
|
||||
__attribute__((import_module("env"), import_name("removeAttribute")))
|
||||
void RemoveAttribute(std::int32_t ptr, const char* name, std::int32_t nameLength);
|
||||
__attribute__((import_module("env"), import_name("getAttribute")))
|
||||
const char* GetAttribute(std::int32_t ptr, const char* name, std::int32_t nameLength);
|
||||
__attribute__((import_module("env"), import_name("hasAttribute")))
|
||||
bool HasAttribute(std::int32_t ptr, const char* name, std::int32_t nameLength);
|
||||
__attribute__((import_module("env"), import_name("getChecked")))
|
||||
bool GetChecked(std::int32_t ptr);
|
||||
__attribute__((import_module("env"), import_name("setChecked")))
|
||||
void SetChecked(std::int32_t ptr, bool checked);
|
||||
__attribute__((import_module("env"), import_name("focusElement")))
|
||||
void FocusElement(std::int32_t ptr);
|
||||
__attribute__((import_module("env"), import_name("blurElement")))
|
||||
void BlurElement(std::int32_t ptr);
|
||||
__attribute__((import_module("env"), import_name("addClass")))
|
||||
void AddClass(std::int32_t ptr, const char* className, std::int32_t classNameLength);
|
||||
__attribute__((import_module("env"), import_name("removeClass")))
|
||||
|
|
@ -57,37 +75,54 @@ namespace Crafter::DomBindings {
|
|||
void SetValue(std::int32_t ptr, const char* value, std::int32_t valueLength);
|
||||
|
||||
// Per-event-kind listener register / unregister imports.
|
||||
//
|
||||
// Two flavours, because only some DOM events are cancelable:
|
||||
// CG_DOM_LISTENER_IMPORT_CANCELABLE — mouse, key and wheel kinds. The
|
||||
// add takes a trailing `preventDefault` the JS side applies before
|
||||
// dispatching into wasm (see additional/dom-env.js).
|
||||
// CG_DOM_LISTENER_IMPORT — everything else. focus/blur/change/input/
|
||||
// resize/scroll are not cancelable, so a flag there would be a
|
||||
// silent no-op and is better left off the API. `submit` is the odd
|
||||
// one out: its JS handler ALWAYS calls preventDefault (a native form
|
||||
// POST would navigate away from the wasm app), so it has no flag and
|
||||
// no way to opt out.
|
||||
#define CG_DOM_LISTENER_IMPORT(addName, removeName) \
|
||||
__attribute__((import_module("env"), import_name(#addName))) \
|
||||
void addName(std::int32_t ptr, std::int32_t id); \
|
||||
__attribute__((import_module("env"), import_name(#removeName))) \
|
||||
void removeName(std::int32_t ptr, std::int32_t id);
|
||||
#define CG_DOM_LISTENER_IMPORT_CANCELABLE(addName, removeName) \
|
||||
__attribute__((import_module("env"), import_name(#addName))) \
|
||||
void addName(std::int32_t ptr, std::int32_t id, bool preventDefault); \
|
||||
__attribute__((import_module("env"), import_name(#removeName))) \
|
||||
void removeName(std::int32_t ptr, std::int32_t id);
|
||||
|
||||
CG_DOM_LISTENER_IMPORT(addClickListener, removeClickListener)
|
||||
CG_DOM_LISTENER_IMPORT(addMouseOverListener, removeMouseOverListener)
|
||||
CG_DOM_LISTENER_IMPORT(addMouseOutListener, removeMouseOutListener)
|
||||
CG_DOM_LISTENER_IMPORT(addMouseMoveListener, removeMouseMoveListener)
|
||||
CG_DOM_LISTENER_IMPORT(addMouseDownListener, removeMouseDownListener)
|
||||
CG_DOM_LISTENER_IMPORT(addMouseUpListener, removeMouseUpListener)
|
||||
CG_DOM_LISTENER_IMPORT_CANCELABLE(addClickListener, removeClickListener)
|
||||
CG_DOM_LISTENER_IMPORT_CANCELABLE(addMouseOverListener, removeMouseOverListener)
|
||||
CG_DOM_LISTENER_IMPORT_CANCELABLE(addMouseOutListener, removeMouseOutListener)
|
||||
CG_DOM_LISTENER_IMPORT_CANCELABLE(addMouseMoveListener, removeMouseMoveListener)
|
||||
CG_DOM_LISTENER_IMPORT_CANCELABLE(addMouseDownListener, removeMouseDownListener)
|
||||
CG_DOM_LISTENER_IMPORT_CANCELABLE(addMouseUpListener, removeMouseUpListener)
|
||||
CG_DOM_LISTENER_IMPORT(addFocusListener, removeFocusListener)
|
||||
CG_DOM_LISTENER_IMPORT(addBlurListener, removeBlurListener)
|
||||
CG_DOM_LISTENER_IMPORT(addKeyDownListener, removeKeyDownListener)
|
||||
CG_DOM_LISTENER_IMPORT(addKeyUpListener, removeKeyUpListener)
|
||||
CG_DOM_LISTENER_IMPORT(addKeyPressListener, removeKeyPressListener)
|
||||
CG_DOM_LISTENER_IMPORT_CANCELABLE(addKeyDownListener, removeKeyDownListener)
|
||||
CG_DOM_LISTENER_IMPORT_CANCELABLE(addKeyUpListener, removeKeyUpListener)
|
||||
CG_DOM_LISTENER_IMPORT_CANCELABLE(addKeyPressListener, removeKeyPressListener)
|
||||
CG_DOM_LISTENER_IMPORT(addChangeListener, removeChangeListener)
|
||||
CG_DOM_LISTENER_IMPORT(addSubmitListener, removeSubmitListener)
|
||||
CG_DOM_LISTENER_IMPORT(addInputListener, removeInputListener)
|
||||
CG_DOM_LISTENER_IMPORT(addResizeListener, removeResizeListener)
|
||||
CG_DOM_LISTENER_IMPORT(addScrollListener, removeScrollListener)
|
||||
CG_DOM_LISTENER_IMPORT(addContextMenuListener, removeContextMenuListener)
|
||||
CG_DOM_LISTENER_IMPORT(addDragStartListener, removeDragStartListener)
|
||||
CG_DOM_LISTENER_IMPORT(addDragEndListener, removeDragEndListener)
|
||||
CG_DOM_LISTENER_IMPORT(addDropListener, removeDropListener)
|
||||
CG_DOM_LISTENER_IMPORT(addDragOverListener, removeDragOverListener)
|
||||
CG_DOM_LISTENER_IMPORT(addDragEnterListener, removeDragEnterListener)
|
||||
CG_DOM_LISTENER_IMPORT(addDragLeaveListener, removeDragLeaveListener)
|
||||
CG_DOM_LISTENER_IMPORT(addWheelListener, removeWheelListener)
|
||||
CG_DOM_LISTENER_IMPORT_CANCELABLE(addContextMenuListener, removeContextMenuListener)
|
||||
CG_DOM_LISTENER_IMPORT_CANCELABLE(addDragStartListener, removeDragStartListener)
|
||||
CG_DOM_LISTENER_IMPORT_CANCELABLE(addDragEndListener, removeDragEndListener)
|
||||
CG_DOM_LISTENER_IMPORT_CANCELABLE(addDropListener, removeDropListener)
|
||||
CG_DOM_LISTENER_IMPORT_CANCELABLE(addDragOverListener, removeDragOverListener)
|
||||
CG_DOM_LISTENER_IMPORT_CANCELABLE(addDragEnterListener, removeDragEnterListener)
|
||||
CG_DOM_LISTENER_IMPORT_CANCELABLE(addDragLeaveListener, removeDragLeaveListener)
|
||||
CG_DOM_LISTENER_IMPORT_CANCELABLE(addWheelListener, removeWheelListener)
|
||||
#undef CG_DOM_LISTENER_IMPORT
|
||||
#undef CG_DOM_LISTENER_IMPORT_CANCELABLE
|
||||
|
||||
// Per-event-kind callback maps. Counters are per-kind so two
|
||||
// different event kinds can share an id without aliasing — the JS
|
||||
|
|
@ -272,6 +307,23 @@ namespace {
|
|||
trackingList.push_back(id);
|
||||
return id;
|
||||
}
|
||||
// As AddImpl, for the cancelable kinds whose JS add takes the extra
|
||||
// preventDefault flag. Separate overload rather than a defaulted
|
||||
// parameter so passing the flag to a non-cancelable kind doesn't compile.
|
||||
template <typename EvT, typename JsAdd>
|
||||
std::int32_t AddCancelableImpl(Crafter::DomBindings::HandlerTable<EvT>& table,
|
||||
std::int32_t ptr,
|
||||
std::vector<std::int32_t>& trackingList,
|
||||
JsAdd jsAdd,
|
||||
std::function<void(EvT)> callback,
|
||||
bool preventDefault) {
|
||||
if (ptr == 0) return 0;
|
||||
std::int32_t id = table.maxId++;
|
||||
table.map.insert({id, std::move(callback)});
|
||||
jsAdd(ptr, id, preventDefault);
|
||||
trackingList.push_back(id);
|
||||
return id;
|
||||
}
|
||||
template <typename JsAdd>
|
||||
std::int32_t AddVoidImpl(Crafter::DomBindings::HandlerTable<void>& table,
|
||||
std::int32_t ptr,
|
||||
|
|
@ -412,6 +464,42 @@ namespace Crafter::Dom {
|
|||
if (ptr) Crafter::DomBindings::SetValue(ptr, value.data(), value.size());
|
||||
}
|
||||
|
||||
void HtmlElementPtr::SetAttribute(const std::string_view name,
|
||||
const std::string_view value) {
|
||||
if (ptr) Crafter::DomBindings::SetAttribute(ptr, name.data(), name.size(),
|
||||
value.data(), value.size());
|
||||
}
|
||||
void HtmlElementPtr::RemoveAttribute(const std::string_view name) {
|
||||
if (ptr) Crafter::DomBindings::RemoveAttribute(ptr, name.data(), name.size());
|
||||
}
|
||||
std::optional<std::string> HtmlElementPtr::GetAttribute(const std::string_view name) {
|
||||
if (!ptr) return std::nullopt;
|
||||
const char* raw = Crafter::DomBindings::GetAttribute(ptr, name.data(), name.size());
|
||||
// 0 means the attribute is absent, which is distinct from present-but-
|
||||
// empty — for boolean attributes presence alone is the signal.
|
||||
if (!raw) return std::nullopt;
|
||||
std::string out(raw);
|
||||
std::free(const_cast<char*>(raw));
|
||||
return out;
|
||||
}
|
||||
bool HtmlElementPtr::HasAttribute(const std::string_view name) {
|
||||
if (!ptr) return false;
|
||||
return Crafter::DomBindings::HasAttribute(ptr, name.data(), name.size());
|
||||
}
|
||||
bool HtmlElementPtr::GetChecked() {
|
||||
if (!ptr) return false;
|
||||
return Crafter::DomBindings::GetChecked(ptr);
|
||||
}
|
||||
void HtmlElementPtr::SetChecked(bool checked) {
|
||||
if (ptr) Crafter::DomBindings::SetChecked(ptr, checked);
|
||||
}
|
||||
void HtmlElementPtr::Focus() {
|
||||
if (ptr) Crafter::DomBindings::FocusElement(ptr);
|
||||
}
|
||||
void HtmlElementPtr::Blur() {
|
||||
if (ptr) Crafter::DomBindings::BlurElement(ptr);
|
||||
}
|
||||
|
||||
// Listener wrappers. Each Add/Remove pair just plugs the right
|
||||
// table, kind index, and js import into the helper templates. The
|
||||
// 23 (+1 for popstate, lived in :Router) listener kinds previously
|
||||
|
|
@ -423,6 +511,13 @@ namespace Crafter::Dom {
|
|||
handlerIds_[(std::size_t)Crafter::DomBindings::Kind::KindEnum], \
|
||||
Crafter::DomBindings::JsAdd, std::move(cb)); \
|
||||
}
|
||||
#define CG_DOM_ADD_CANCELABLE(MethodName, JsAdd, TableName, EvType, KindEnum) \
|
||||
std::int32_t HtmlElementPtr::MethodName(std::function<void(EvType)> cb, \
|
||||
bool preventDefault) { \
|
||||
return AddCancelableImpl<EvType>(Crafter::DomBindings::TableName, ptr, \
|
||||
handlerIds_[(std::size_t)Crafter::DomBindings::Kind::KindEnum], \
|
||||
Crafter::DomBindings::JsAdd, std::move(cb), preventDefault); \
|
||||
}
|
||||
#define CG_DOM_REMOVE(MethodName, JsRemove, TableName, KindEnum) \
|
||||
void HtmlElementPtr::MethodName(std::int32_t id) { \
|
||||
RemoveImpl(Crafter::DomBindings::TableName, ptr, \
|
||||
|
|
@ -430,27 +525,27 @@ namespace Crafter::Dom {
|
|||
Crafter::DomBindings::JsRemove, id); \
|
||||
}
|
||||
|
||||
CG_DOM_ADD (AddClickListener, addClickListener, clickT, MouseEvent, Click)
|
||||
CG_DOM_ADD_CANCELABLE(AddClickListener, addClickListener, clickT, MouseEvent, Click)
|
||||
CG_DOM_REMOVE(RemoveClickListener, removeClickListener, clickT, Click)
|
||||
CG_DOM_ADD (AddMouseOverListener, addMouseOverListener, mouseOverT, MouseEvent, MouseOver)
|
||||
CG_DOM_ADD_CANCELABLE(AddMouseOverListener, addMouseOverListener, mouseOverT, MouseEvent, MouseOver)
|
||||
CG_DOM_REMOVE(RemoveMouseOverListener, removeMouseOverListener, mouseOverT, MouseOver)
|
||||
CG_DOM_ADD (AddMouseOutListener, addMouseOutListener, mouseOutT, MouseEvent, MouseOut)
|
||||
CG_DOM_ADD_CANCELABLE(AddMouseOutListener, addMouseOutListener, mouseOutT, MouseEvent, MouseOut)
|
||||
CG_DOM_REMOVE(RemoveMouseOutListener, removeMouseOutListener, mouseOutT, MouseOut)
|
||||
CG_DOM_ADD (AddMouseMoveListener, addMouseMoveListener, mouseMoveT, MouseEvent, MouseMove)
|
||||
CG_DOM_ADD_CANCELABLE(AddMouseMoveListener, addMouseMoveListener, mouseMoveT, MouseEvent, MouseMove)
|
||||
CG_DOM_REMOVE(RemoveMouseMoveListener, removeMouseMoveListener, mouseMoveT, MouseMove)
|
||||
CG_DOM_ADD (AddMouseDownListener, addMouseDownListener, mouseDownT, MouseEvent, MouseDown)
|
||||
CG_DOM_ADD_CANCELABLE(AddMouseDownListener, addMouseDownListener, mouseDownT, MouseEvent, MouseDown)
|
||||
CG_DOM_REMOVE(RemoveMouseDownListener, removeMouseDownListener, mouseDownT, MouseDown)
|
||||
CG_DOM_ADD (AddMouseUpListener, addMouseUpListener, mouseUpT, MouseEvent, MouseUp)
|
||||
CG_DOM_ADD_CANCELABLE(AddMouseUpListener, addMouseUpListener, mouseUpT, MouseEvent, MouseUp)
|
||||
CG_DOM_REMOVE(RemoveMouseUpListener, removeMouseUpListener, mouseUpT, MouseUp)
|
||||
CG_DOM_ADD (AddFocusListener, addFocusListener, focusT, FocusEvent, Focus)
|
||||
CG_DOM_REMOVE(RemoveFocusListener, removeFocusListener, focusT, Focus)
|
||||
CG_DOM_ADD (AddBlurListener, addBlurListener, blurT, FocusEvent, Blur)
|
||||
CG_DOM_REMOVE(RemoveBlurListener, removeBlurListener, blurT, Blur)
|
||||
CG_DOM_ADD (AddKeyDownListener, addKeyDownListener, keyDownT, KeyboardEvent, KeyDown)
|
||||
CG_DOM_ADD_CANCELABLE(AddKeyDownListener, addKeyDownListener, keyDownT, KeyboardEvent, KeyDown)
|
||||
CG_DOM_REMOVE(RemoveKeyDownListener, removeKeyDownListener, keyDownT, KeyDown)
|
||||
CG_DOM_ADD (AddKeyUpListener, addKeyUpListener, keyUpT, KeyboardEvent, KeyUp)
|
||||
CG_DOM_ADD_CANCELABLE(AddKeyUpListener, addKeyUpListener, keyUpT, KeyboardEvent, KeyUp)
|
||||
CG_DOM_REMOVE(RemoveKeyUpListener, removeKeyUpListener, keyUpT, KeyUp)
|
||||
CG_DOM_ADD (AddKeyPressListener, addKeyPressListener, keyPressT, KeyboardEvent, KeyPress)
|
||||
CG_DOM_ADD_CANCELABLE(AddKeyPressListener, addKeyPressListener, keyPressT, KeyboardEvent, KeyPress)
|
||||
CG_DOM_REMOVE(RemoveKeyPressListener, removeKeyPressListener, keyPressT, KeyPress)
|
||||
CG_DOM_ADD (AddChangeListener, addChangeListener, changeT, ChangeEvent, Change)
|
||||
CG_DOM_REMOVE(RemoveChangeListener, removeChangeListener, changeT, Change)
|
||||
|
|
@ -460,21 +555,21 @@ namespace Crafter::Dom {
|
|||
CG_DOM_REMOVE(RemoveResizeListener, removeResizeListener, resizeT, Resize)
|
||||
CG_DOM_ADD (AddScrollListener, addScrollListener, scrollT, ScrollEvent, Scroll)
|
||||
CG_DOM_REMOVE(RemoveScrollListener, removeScrollListener, scrollT, Scroll)
|
||||
CG_DOM_ADD (AddContextMenuListener, addContextMenuListener, contextMenuT, MouseEvent, ContextMenu)
|
||||
CG_DOM_ADD_CANCELABLE(AddContextMenuListener, addContextMenuListener, contextMenuT, MouseEvent, ContextMenu)
|
||||
CG_DOM_REMOVE(RemoveContextMenuListener,removeContextMenuListener,contextMenuT, ContextMenu)
|
||||
CG_DOM_ADD (AddDragStartListener, addDragStartListener, dragStartT, MouseEvent, DragStart)
|
||||
CG_DOM_ADD_CANCELABLE(AddDragStartListener, addDragStartListener, dragStartT, MouseEvent, DragStart)
|
||||
CG_DOM_REMOVE(RemoveDragStartListener, removeDragStartListener, dragStartT, DragStart)
|
||||
CG_DOM_ADD (AddDragEndListener, addDragEndListener, dragEndT, MouseEvent, DragEnd)
|
||||
CG_DOM_ADD_CANCELABLE(AddDragEndListener, addDragEndListener, dragEndT, MouseEvent, DragEnd)
|
||||
CG_DOM_REMOVE(RemoveDragEndListener, removeDragEndListener, dragEndT, DragEnd)
|
||||
CG_DOM_ADD (AddDropListener, addDropListener, dropT, MouseEvent, Drop)
|
||||
CG_DOM_ADD_CANCELABLE(AddDropListener, addDropListener, dropT, MouseEvent, Drop)
|
||||
CG_DOM_REMOVE(RemoveDropListener, removeDropListener, dropT, Drop)
|
||||
CG_DOM_ADD (AddDragOverListener, addDragOverListener, dragOverT, MouseEvent, DragOver)
|
||||
CG_DOM_ADD_CANCELABLE(AddDragOverListener, addDragOverListener, dragOverT, MouseEvent, DragOver)
|
||||
CG_DOM_REMOVE(RemoveDragOverListener, removeDragOverListener, dragOverT, DragOver)
|
||||
CG_DOM_ADD (AddDragEnterListener, addDragEnterListener, dragEnterT, MouseEvent, DragEnter)
|
||||
CG_DOM_ADD_CANCELABLE(AddDragEnterListener, addDragEnterListener, dragEnterT, MouseEvent, DragEnter)
|
||||
CG_DOM_REMOVE(RemoveDragEnterListener, removeDragEnterListener, dragEnterT, DragEnter)
|
||||
CG_DOM_ADD (AddDragLeaveListener, addDragLeaveListener, dragLeaveT, MouseEvent, DragLeave)
|
||||
CG_DOM_ADD_CANCELABLE(AddDragLeaveListener, addDragLeaveListener, dragLeaveT, MouseEvent, DragLeave)
|
||||
CG_DOM_REMOVE(RemoveDragLeaveListener, removeDragLeaveListener, dragLeaveT, DragLeave)
|
||||
CG_DOM_ADD (AddWheelListener, addWheelListener, wheelT, WheelEvent, Wheel)
|
||||
CG_DOM_ADD_CANCELABLE(AddWheelListener, addWheelListener, wheelT, WheelEvent, Wheel)
|
||||
CG_DOM_REMOVE(RemoveWheelListener, removeWheelListener, wheelT, Wheel)
|
||||
#undef CG_DOM_ADD
|
||||
#undef CG_DOM_REMOVE
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -715,6 +715,17 @@ void Window::StartSync() {
|
|||
#endif
|
||||
}
|
||||
|
||||
void Window::StayAlive() {
|
||||
// Native has no equivalent of the browser's "return from main but keep
|
||||
// the instance alive" trick — the process simply ends. The event loop
|
||||
// IS what keeps a native window alive, so the only correct native
|
||||
// behaviour is StartSync's loop. Delegating keeps a single source that
|
||||
// compiles identically on both targets: an app can call StayAlive
|
||||
// unconditionally and get the cheap path in the browser without a
|
||||
// #ifdef at the call site.
|
||||
StartSync();
|
||||
}
|
||||
|
||||
void Window::StartUpdate() {
|
||||
lastFrameBegin = std::chrono::high_resolution_clock::now();
|
||||
updating = true;
|
||||
|
|
@ -1571,6 +1582,23 @@ void Window::StartSync() {
|
|||
std::_Exit(0);
|
||||
}
|
||||
|
||||
void Window::StayAlive() {
|
||||
// As StartSync, minus the rAF loop.
|
||||
//
|
||||
// The _Exit(0) is the part that keeps the instance alive: it skips
|
||||
// __wasm_call_dtors so every statically allocated object (event
|
||||
// listeners, HtmlElementPtr handles, the Window itself) survives, and
|
||||
// runtime.js catches the resulting __wasi_proc_exit via a sentinel.
|
||||
// The frame loop is a separate concern, and a page that only renders
|
||||
// DOM has no use for it — a rAF tick that runs forever to do nothing
|
||||
// costs battery on every open tab.
|
||||
//
|
||||
// Use this for event-driven DOM pages; use StartSync when something
|
||||
// actually animates or draws. As with StartSync, no caller code after
|
||||
// this point ever runs.
|
||||
std::_Exit(0);
|
||||
}
|
||||
|
||||
void Window::StartUpdate() {
|
||||
lastFrameBegin = std::chrono::high_resolution_clock::now();
|
||||
updating = true;
|
||||
|
|
|
|||
Loading…
Reference in a new issue