/* Crafter®.Graphics Copyright (C) 2026 Catcrafts® catcrafts.net This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 3.0 as published by the Free Software Foundation; This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // Implementation of the :Dom partition. Only ever built when // CRAFTER_GRAPHICS_WINDOW_DOM is defined (project.cpp's DOM branch // is what brings this file into the source list). All WASM imports // resolve against the `env` module produced by additional/dom-env.js; // all exports become C entry points the JS bridge calls back through. module; module Crafter.Graphics; import std; using namespace Crafter::Dom; // ─── WASM env imports + per-event-kind handler maps ─────────────────── // // External linkage required for the import_module/import_name attributes // to actually wire up to a WASM env symbol — anonymous namespace would // hide them. The namespace also gates access from the extern "C" // dispatcher block below. namespace Crafter::DomBindings { // DOM ops (string args are pointer/length pairs — the JS side decodes // UTF-8 directly out of WASM linear memory; no copying on the C++ // side). __attribute__((import_module("env"), import_name("freeJs"))) void FreeJs(std::int32_t ptr); __attribute__((import_module("env"), import_name("getElementById"))) std::int32_t GetElementById(const char* id, std::int32_t idLength); __attribute__((import_module("env"), import_name("createElement"))) std::int32_t CreateElement(std::int32_t parentPtr, const char* tagName, std::int32_t tagNameLength, const char* id, std::int32_t idLength); __attribute__((import_module("env"), import_name("getBody"))) std::int32_t GetBody(); __attribute__((import_module("env"), import_name("setInnerHTML"))) void SetInnerHTML(std::int32_t ptr, const char* html, std::int32_t htmlLength); __attribute__((import_module("env"), import_name("setStyle"))) void SetStyle(std::int32_t ptr, const char* style, std::int32_t styleLength); __attribute__((import_module("env"), import_name("setProperty"))) 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("addClass"))) void AddClass(std::int32_t ptr, const char* className, std::int32_t classNameLength); __attribute__((import_module("env"), import_name("removeClass"))) void RemoveClass(std::int32_t ptr, const char* className, std::int32_t classNameLength); __attribute__((import_module("env"), import_name("toggleClass"))) void ToggleClass(std::int32_t ptr, const char* className, std::int32_t classNameLength); __attribute__((import_module("env"), import_name("hasClass"))) bool HasClass(std::int32_t ptr, const char* className, std::int32_t classNameLength); __attribute__((import_module("env"), import_name("deleteElement"))) void DeleteElement(std::int32_t ptr); __attribute__((import_module("env"), import_name("getValue"))) const char* GetValue(std::int32_t ptr); __attribute__((import_module("env"), import_name("setValue"))) void SetValue(std::int32_t ptr, const char* value, std::int32_t valueLength); // Per-event-kind listener register / unregister imports. #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); 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(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(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) #undef CG_DOM_LISTENER_IMPORT // Per-event-kind callback maps. Counters are per-kind so two // different event kinds can share an id without aliasing — the JS // bridge keys its own handler table by (ptr, id, kind) for the same // reason. `maxId` is bumped without wrap-around handling: 2 billion // listeners over the lifetime of a single page is enough. template struct HandlerTable { std::int32_t maxId = 0; std::unordered_map> map; }; template <> struct HandlerTable { std::int32_t maxId = 0; std::unordered_map> map; }; inline HandlerTable clickT, mouseOverT, mouseOutT, mouseMoveT, mouseDownT, mouseUpT, contextMenuT, dragStartT, dragEndT, dropT, dragOverT, dragEnterT, dragLeaveT; inline HandlerTable wheelT; inline HandlerTable focusT, blurT; inline HandlerTable keyDownT, keyUpT, keyPressT; inline HandlerTable changeT; inline HandlerTable inputT; inline HandlerTable resizeT; inline HandlerTable scrollT; inline HandlerTable submitT; inline HandlerTable popStateT; inline HandlerTable fetchT; // Reserved for future use. // Opaque cross-TU helpers for the popState table. Router.cpp lives // in a different translation unit and can't touch HandlerTable // (re-defining the specialization there would be an ODR violation); // it goes through these instead. Defined below the namespace. std::int32_t PopStateRegister(std::function cb); void PopStateUnregister(std::int32_t id); // The 24 stable indices used by HtmlElementPtr::handlerIds_[] to // map the per-kind id list back to the right `remove*Listener` // import on destruction. Order MUST match the cppm comment. enum class Kind : std::uint8_t { Click = 0, MouseOver, MouseOut, MouseMove, MouseDown, MouseUp, Focus, Blur, KeyDown, KeyUp, KeyPress, Change, Submit, Input, Resize, Scroll, ContextMenu, DragStart, DragEnd, Drop, DragOver, DragEnter, DragLeave, Wheel, }; } // ─── WASM exports the JS bridge calls back through ──────────────────── // // One per event kind plus the popstate / fetch dispatchers. Each takes // the handler id + the marshaled event fields, looks up the table, and // invokes the std::function. find()->second is intentional: a callback // firing for an id we never registered is a bridge bug worth crashing // on (the env.js side stores its own handler table keyed by id and // only calls Execute*Handler for ids it minted). extern "C" { __attribute__((export_name("WasmAlloc"))) void* WasmAlloc(std::int32_t size) { return std::malloc(size); } __attribute__((export_name("WasmFree"))) void WasmFree(void* ptr) { std::free(ptr); } #define CG_DOM_EXEC_MOUSE(exportName, table) \ __attribute__((export_name(#exportName))) \ void exportName(std::int32_t handlerID, \ double clientX, double clientY, \ double screenX, double screenY, \ std::int32_t button, std::int32_t buttons, \ bool altKey, bool ctrlKey, bool shiftKey, bool metaKey) { \ Crafter::DomBindings::table.map.find(handlerID)->second( \ MouseEvent(clientX, clientY, screenX, screenY, \ button, buttons, altKey, ctrlKey, shiftKey, metaKey)); \ } CG_DOM_EXEC_MOUSE(ExecuteClickHandler, clickT) CG_DOM_EXEC_MOUSE(ExecuteMouseOverHandler, mouseOverT) CG_DOM_EXEC_MOUSE(ExecuteMouseOutHandler, mouseOutT) CG_DOM_EXEC_MOUSE(ExecuteMouseMoveHandler, mouseMoveT) CG_DOM_EXEC_MOUSE(ExecuteMouseDownHandler, mouseDownT) CG_DOM_EXEC_MOUSE(ExecuteMouseUpHandler, mouseUpT) CG_DOM_EXEC_MOUSE(ExecuteContextMenuHandler, contextMenuT) CG_DOM_EXEC_MOUSE(ExecuteDragStartHandler, dragStartT) CG_DOM_EXEC_MOUSE(ExecuteDragEndHandler, dragEndT) CG_DOM_EXEC_MOUSE(ExecuteDropHandler, dropT) CG_DOM_EXEC_MOUSE(ExecuteDragOverHandler, dragOverT) CG_DOM_EXEC_MOUSE(ExecuteDragEnterHandler, dragEnterT) CG_DOM_EXEC_MOUSE(ExecuteDragLeaveHandler, dragLeaveT) #undef CG_DOM_EXEC_MOUSE __attribute__((export_name("ExecuteWheelHandler"))) void ExecuteWheelHandler(std::int32_t handlerID, double deltaX, double deltaY, double deltaZ, std::int32_t /*deltaMode*/, double clientX, double clientY, double screenX, double screenY, std::int32_t button, std::int32_t buttons, bool altKey, bool ctrlKey, bool shiftKey, bool metaKey) { Crafter::DomBindings::wheelT.map.find(handlerID)->second( WheelEvent(clientX, clientY, screenX, screenY, button, buttons, altKey, ctrlKey, shiftKey, metaKey, deltaX, deltaY, deltaZ)); } __attribute__((export_name("ExecuteFocusHandler"))) void ExecuteFocusHandler(std::int32_t handlerID, void* target, void* relatedTarget) { Crafter::DomBindings::focusT.map.find(handlerID)->second( FocusEvent(target, relatedTarget)); } __attribute__((export_name("ExecuteBlurHandler"))) void ExecuteBlurHandler(std::int32_t handlerID, void* target, void* relatedTarget) { Crafter::DomBindings::blurT.map.find(handlerID)->second( FocusEvent(target, relatedTarget)); } #define CG_DOM_EXEC_KEY(exportName, table) \ __attribute__((export_name(#exportName))) \ void exportName(std::int32_t handlerID, \ const char* key, std::int32_t keyCode, \ bool altKey, bool ctrlKey, bool shiftKey, bool metaKey) { \ Crafter::DomBindings::table.map.find(handlerID)->second( \ KeyboardEvent(key, keyCode, altKey, ctrlKey, shiftKey, metaKey)); \ } CG_DOM_EXEC_KEY(ExecuteKeyDownHandler, keyDownT) CG_DOM_EXEC_KEY(ExecuteKeyUpHandler, keyUpT) CG_DOM_EXEC_KEY(ExecuteKeyPressHandler, keyPressT) #undef CG_DOM_EXEC_KEY __attribute__((export_name("ExecuteChangeHandler"))) void ExecuteChangeHandler(std::int32_t handlerID, const char* value) { Crafter::DomBindings::changeT.map.find(handlerID)->second(ChangeEvent(value)); } __attribute__((export_name("ExecuteSubmitHandler"))) void ExecuteSubmitHandler(std::int32_t handlerID) { Crafter::DomBindings::submitT.map.find(handlerID)->second(); } __attribute__((export_name("ExecuteInputHandler"))) void ExecuteInputHandler(std::int32_t handlerID, const char* data, bool isComposing) { Crafter::DomBindings::inputT.map.find(handlerID)->second(InputEvent(data, isComposing)); } __attribute__((export_name("ExecuteResizeHandler"))) void ExecuteResizeHandler(std::int32_t handlerID, std::int32_t width, std::int32_t height) { Crafter::DomBindings::resizeT.map.find(handlerID)->second(ResizeEvent(width, height)); } __attribute__((export_name("ExecuteScrollHandler"))) void ExecuteScrollHandler(std::int32_t handlerID, double scrollX, double scrollY) { Crafter::DomBindings::scrollT.map.find(handlerID)->second(ScrollEvent(scrollX, scrollY)); } __attribute__((export_name("ExecutePopStateHandler"))) void ExecutePopStateHandler(std::int32_t handlerID) { Crafter::DomBindings::popStateT.map.find(handlerID)->second(); } } // ─── Listener add/remove helpers ────────────────────────────────────── // // Each Add*Listener follows the same five-step pattern; capturing it in // a single function template removes ~120 lines of near-duplicate body // code. AddImpl returns the freshly minted id (already pushed onto the // per-element tracking list). RemoveImpl performs the inverse for an // id known to belong to the right table. namespace { template std::int32_t AddImpl(Crafter::DomBindings::HandlerTable& table, std::int32_t ptr, std::vector& trackingList, JsAdd jsAdd, std::function callback) { if (ptr == 0) return 0; std::int32_t id = table.maxId++; table.map.insert({id, std::move(callback)}); jsAdd(ptr, id); trackingList.push_back(id); return id; } template std::int32_t AddVoidImpl(Crafter::DomBindings::HandlerTable& table, std::int32_t ptr, std::vector& trackingList, JsAdd jsAdd, std::function callback) { if (ptr == 0) return 0; std::int32_t id = table.maxId++; table.map.insert({id, std::move(callback)}); jsAdd(ptr, id); trackingList.push_back(id); return id; } template void RemoveImpl(Table& table, std::int32_t ptr, std::vector& trackingList, JsRemove jsRemove, std::int32_t id) { if (ptr == 0) return; table.map.erase(id); trackingList.erase(std::remove(trackingList.begin(), trackingList.end(), id), trackingList.end()); jsRemove(ptr, id); } } // ─── HtmlElementPtr ─────────────────────────────────────────────────── namespace Crafter::Dom { HtmlElementPtr::HtmlElementPtr(const std::string_view id) : ptr(Crafter::DomBindings::GetElementById(id.data(), id.size())) {} HtmlElementPtr::HtmlElementPtr(const std::string_view id, const std::string_view html) : ptr(Crafter::DomBindings::GetElementById(id.data(), id.size())) { if (ptr != 0) { Crafter::DomBindings::SetInnerHTML(ptr, html.data(), html.size()); } } HtmlElementPtr::HtmlElementPtr(HtmlElementPtr&& other) noexcept : ptr(other.ptr) { other.ptr = 0; for (std::size_t i = 0; i < 24; ++i) handlerIds_[i] = std::move(other.handlerIds_[i]); } HtmlElementPtr& HtmlElementPtr::operator=(HtmlElementPtr&& other) noexcept { if (this == &other) return *this; RemoveAllHandlersAndFree(); ptr = other.ptr; other.ptr = 0; for (std::size_t i = 0; i < 24; ++i) handlerIds_[i] = std::move(other.handlerIds_[i]); return *this; } HtmlElementPtr::~HtmlElementPtr() { RemoveAllHandlersAndFree(); } void HtmlElementPtr::RemoveAllHandlersAndFree() { if (ptr == 0) return; // Unregister every still-live handler the user forgot about. // The per-kind index matches the cppm comment ordering and the // Kind enum above; keep them in sync. namespace D = Crafter::DomBindings; auto sweep = [this](auto& table, auto jsRemove, std::size_t kind) { for (std::int32_t id : handlerIds_[kind]) { table.map.erase(id); jsRemove(ptr, id); } handlerIds_[kind].clear(); }; sweep(D::clickT, D::removeClickListener, (std::size_t)D::Kind::Click); sweep(D::mouseOverT, D::removeMouseOverListener, (std::size_t)D::Kind::MouseOver); sweep(D::mouseOutT, D::removeMouseOutListener, (std::size_t)D::Kind::MouseOut); sweep(D::mouseMoveT, D::removeMouseMoveListener, (std::size_t)D::Kind::MouseMove); sweep(D::mouseDownT, D::removeMouseDownListener, (std::size_t)D::Kind::MouseDown); sweep(D::mouseUpT, D::removeMouseUpListener, (std::size_t)D::Kind::MouseUp); sweep(D::focusT, D::removeFocusListener, (std::size_t)D::Kind::Focus); sweep(D::blurT, D::removeBlurListener, (std::size_t)D::Kind::Blur); sweep(D::keyDownT, D::removeKeyDownListener, (std::size_t)D::Kind::KeyDown); sweep(D::keyUpT, D::removeKeyUpListener, (std::size_t)D::Kind::KeyUp); sweep(D::keyPressT, D::removeKeyPressListener, (std::size_t)D::Kind::KeyPress); sweep(D::changeT, D::removeChangeListener, (std::size_t)D::Kind::Change); sweep(D::submitT, D::removeSubmitListener, (std::size_t)D::Kind::Submit); sweep(D::inputT, D::removeInputListener, (std::size_t)D::Kind::Input); sweep(D::resizeT, D::removeResizeListener, (std::size_t)D::Kind::Resize); sweep(D::scrollT, D::removeScrollListener, (std::size_t)D::Kind::Scroll); sweep(D::contextMenuT, D::removeContextMenuListener, (std::size_t)D::Kind::ContextMenu); sweep(D::dragStartT, D::removeDragStartListener, (std::size_t)D::Kind::DragStart); sweep(D::dragEndT, D::removeDragEndListener, (std::size_t)D::Kind::DragEnd); sweep(D::dropT, D::removeDropListener, (std::size_t)D::Kind::Drop); sweep(D::dragOverT, D::removeDragOverListener, (std::size_t)D::Kind::DragOver); sweep(D::dragEnterT, D::removeDragEnterListener, (std::size_t)D::Kind::DragEnter); sweep(D::dragLeaveT, D::removeDragLeaveListener, (std::size_t)D::Kind::DragLeave); sweep(D::wheelT, D::removeWheelListener, (std::size_t)D::Kind::Wheel); D::FreeJs(ptr); ptr = 0; } // DOM ops are thin pass-throughs; the JS side does the work. void HtmlElementPtr::SetInnerHTML(const std::string_view html) { if (ptr) Crafter::DomBindings::SetInnerHTML(ptr, html.data(), html.size()); } void HtmlElementPtr::SetStyle(const std::string_view style) { if (ptr) Crafter::DomBindings::SetStyle(ptr, style.data(), style.size()); } void HtmlElementPtr::SetProperty(const std::string_view property, const std::string_view value) { if (ptr) Crafter::DomBindings::SetProperty(ptr, property.data(), property.size(), value.data(), value.size()); } void HtmlElementPtr::AddClass(const std::string_view className) { if (ptr) Crafter::DomBindings::AddClass(ptr, className.data(), className.size()); } void HtmlElementPtr::RemoveClass(const std::string_view className) { if (ptr) Crafter::DomBindings::RemoveClass(ptr, className.data(), className.size()); } void HtmlElementPtr::ToggleClass(const std::string_view className) { if (ptr) Crafter::DomBindings::ToggleClass(ptr, className.data(), className.size()); } bool HtmlElementPtr::HasClass(const std::string_view className) { return ptr && Crafter::DomBindings::HasClass(ptr, className.data(), className.size()); } std::string HtmlElementPtr::GetValue() { if (!ptr) return {}; // The JS side WasmAlloc's a NUL-terminated buffer. We own it // after the call — copy into std::string and free. const char* raw = Crafter::DomBindings::GetValue(ptr); if (!raw) return {}; std::string out(raw); std::free(const_cast(raw)); return out; } void HtmlElementPtr::SetValue(const std::string_view value) { if (ptr) Crafter::DomBindings::SetValue(ptr, value.data(), value.size()); } // 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 // expanded to ~250 lines of body; here they're ~60. #define CG_DOM_ADD(MethodName, JsAdd, TableName, EvType, KindEnum) \ std::int32_t HtmlElementPtr::MethodName(std::function cb) { \ return AddImpl(Crafter::DomBindings::TableName, ptr, \ handlerIds_[(std::size_t)Crafter::DomBindings::Kind::KindEnum], \ Crafter::DomBindings::JsAdd, std::move(cb)); \ } #define CG_DOM_REMOVE(MethodName, JsRemove, TableName, KindEnum) \ void HtmlElementPtr::MethodName(std::int32_t id) { \ RemoveImpl(Crafter::DomBindings::TableName, ptr, \ handlerIds_[(std::size_t)Crafter::DomBindings::Kind::KindEnum], \ Crafter::DomBindings::JsRemove, id); \ } CG_DOM_ADD (AddClickListener, addClickListener, clickT, MouseEvent, Click) CG_DOM_REMOVE(RemoveClickListener, removeClickListener, clickT, Click) CG_DOM_ADD (AddMouseOverListener, addMouseOverListener, mouseOverT, MouseEvent, MouseOver) CG_DOM_REMOVE(RemoveMouseOverListener, removeMouseOverListener, mouseOverT, MouseOver) CG_DOM_ADD (AddMouseOutListener, addMouseOutListener, mouseOutT, MouseEvent, MouseOut) CG_DOM_REMOVE(RemoveMouseOutListener, removeMouseOutListener, mouseOutT, MouseOut) CG_DOM_ADD (AddMouseMoveListener, addMouseMoveListener, mouseMoveT, MouseEvent, MouseMove) CG_DOM_REMOVE(RemoveMouseMoveListener, removeMouseMoveListener, mouseMoveT, MouseMove) CG_DOM_ADD (AddMouseDownListener, addMouseDownListener, mouseDownT, MouseEvent, MouseDown) CG_DOM_REMOVE(RemoveMouseDownListener, removeMouseDownListener, mouseDownT, MouseDown) CG_DOM_ADD (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_REMOVE(RemoveKeyDownListener, removeKeyDownListener, keyDownT, KeyDown) CG_DOM_ADD (AddKeyUpListener, addKeyUpListener, keyUpT, KeyboardEvent, KeyUp) CG_DOM_REMOVE(RemoveKeyUpListener, removeKeyUpListener, keyUpT, KeyUp) CG_DOM_ADD (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) CG_DOM_ADD (AddInputListener, addInputListener, inputT, InputEvent, Input) CG_DOM_REMOVE(RemoveInputListener, removeInputListener, inputT, Input) CG_DOM_ADD (AddResizeListener, addResizeListener, resizeT, ResizeEvent, Resize) 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_REMOVE(RemoveContextMenuListener,removeContextMenuListener,contextMenuT, ContextMenu) CG_DOM_ADD (AddDragStartListener, addDragStartListener, dragStartT, MouseEvent, DragStart) CG_DOM_REMOVE(RemoveDragStartListener, removeDragStartListener, dragStartT, DragStart) CG_DOM_ADD (AddDragEndListener, addDragEndListener, dragEndT, MouseEvent, DragEnd) CG_DOM_REMOVE(RemoveDragEndListener, removeDragEndListener, dragEndT, DragEnd) CG_DOM_ADD (AddDropListener, addDropListener, dropT, MouseEvent, Drop) CG_DOM_REMOVE(RemoveDropListener, removeDropListener, dropT, Drop) CG_DOM_ADD (AddDragOverListener, addDragOverListener, dragOverT, MouseEvent, DragOver) CG_DOM_REMOVE(RemoveDragOverListener, removeDragOverListener, dragOverT, DragOver) CG_DOM_ADD (AddDragEnterListener, addDragEnterListener, dragEnterT, MouseEvent, DragEnter) CG_DOM_REMOVE(RemoveDragEnterListener, removeDragEnterListener, dragEnterT, DragEnter) CG_DOM_ADD (AddDragLeaveListener, addDragLeaveListener, dragLeaveT, MouseEvent, DragLeave) CG_DOM_REMOVE(RemoveDragLeaveListener, removeDragLeaveListener, dragLeaveT, DragLeave) CG_DOM_ADD (AddWheelListener, addWheelListener, wheelT, WheelEvent, Wheel) CG_DOM_REMOVE(RemoveWheelListener, removeWheelListener, wheelT, Wheel) #undef CG_DOM_ADD #undef CG_DOM_REMOVE // Submit takes std::function — needs the void specialization. std::int32_t HtmlElementPtr::AddSubmitListener(std::function cb) { return AddVoidImpl(Crafter::DomBindings::submitT, ptr, handlerIds_[(std::size_t)Crafter::DomBindings::Kind::Submit], Crafter::DomBindings::addSubmitListener, std::move(cb)); } void HtmlElementPtr::RemoveSubmitListener(std::int32_t id) { RemoveImpl(Crafter::DomBindings::submitT, ptr, handlerIds_[(std::size_t)Crafter::DomBindings::Kind::Submit], Crafter::DomBindings::removeSubmitListener, id); } // ─── HtmlElement ────────────────────────────────────────────────── HtmlElement::HtmlElement(const std::string_view id) : HtmlElementPtr(id) {} HtmlElement::HtmlElement(const std::string_view id, const std::string_view html) : HtmlElementPtr(id, html) {} HtmlElement::HtmlElement(HtmlElement&& other) noexcept : HtmlElementPtr(std::move(other)) {} HtmlElement& HtmlElement::operator=(HtmlElement&& other) noexcept { if (this == &other) return *this; HtmlElementPtr::operator=(std::move(other)); return *this; } HtmlElement::~HtmlElement() { // Remove the element from the DOM first. The base destructor // runs next (implicit) and will sweep handler maps + call // FreeJs on the still-valid JS cookie. DeleteElement only // removes the node from its parent; the cookie itself stays // alive in jsmemory until FreeJs. if (ptr != 0) { Crafter::DomBindings::DeleteElement(ptr); } } HtmlElement HtmlElement::Create(const HtmlElementPtr& parent, std::string_view tagName, std::string_view id) { std::int32_t handle = Crafter::DomBindings::CreateElement( parent.ptr, tagName.data(), static_cast(tagName.size()), id.data(), static_cast(id.size())); return HtmlElement(HtmlElementPtr::FromHandle{handle}); } HtmlElement HtmlElement::CreateInBody(std::string_view tagName, std::string_view id) { std::int32_t body = Crafter::DomBindings::GetBody(); std::int32_t handle = Crafter::DomBindings::CreateElement( body, tagName.data(), static_cast(tagName.size()), id.data(), static_cast(id.size())); Crafter::DomBindings::FreeJs(body); return HtmlElement(HtmlElementPtr::FromHandle{handle}); } } // Sibling-TU helpers exposed for Router.cpp. Keep the popStateT table // private to this TU and let Router go through these. namespace Crafter::DomBindings { std::int32_t PopStateRegister(std::function cb) { std::int32_t id = popStateT.maxId++; popStateT.map.insert({id, std::move(cb)}); return id; } void PopStateUnregister(std::int32_t id) { popStateT.map.erase(id); } }