The canvas (wxUniversal) mode is gone (wxwidgets submodule); remove every piece of side-by-side plumbing so there is exactly one build and one test flow: - scripts/build-wxuniversal-wasm.sh -> scripts/build-wx-wasm.sh; no --dom/--enable-universal; builds into build-wasm/wxwidgets - build-wasm-test.sh: no DOM_BUILD / apps-dom rsync mirror / PORT=dom; apps build straight into tests/apps (Makefile.wasm PORT conditionals collapsed; wx.js + wx-dom.js always pre-js) - docker/build.sh, build-kicad-target.sh, env.sh: WX_PORT / -dom / -universal suffixes removed; kicad builds to kicad-<app>, outputs to output/; wx.js/wx-dom.js copied from the real source path (/workspace/wxwidgets/build/wasm — the old build-wasm path never existed and silently failed) - setup-kicad-wasm.sh: single target dir; the perl wx-dom.js injection is gone — the 7 checked-in kicad pages now reference wx-dom.js directly - playwright configs serve apps/; fixtures drop the test-results/dom and logs/wxwidgets/dom namespacing; boot.spec asserts wxDomPort unconditionally; pcbnew.spec uses one reference image; appearance.spec assertions unconditional - compare/update-baseline-screenshots.sh: --port removed - tests/gal-regression/wasm/Makefile: links build-wasm/wxwidgets and carries wx-dom.js as a second pre-js — the gal-webgl suite (30 specs) now actually builds and runs here (it needed host-side boost+glm via scripts/deps; the bundle had been missing, timing the whole spec out) - tests: clickCanvas() dispatches via page.mouse (DOM widgets legitimately cover the canvas; locator actionability refused the click); the comprehensive spec drives wxChoice through its native <select> (browser-owned popup cannot be coordinate-clicked) - docs: README/CLAUDE.md/build.md script names and dirs; features/wx-dom-port README reframed (DOM is THE port), visual-notes bugs 26-28; FindwxWidgets.cmake config label drops 'wasmuniv' - wxwidgets submodule -> 9dbacc9448 (DOM-only port, fork diff shrunk) Gate: full wx e2e suite 292 passed / 1 skipped / 0 failed — first run ever with the gal-webgl specs green (28 scenarios + load + sequential). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5.7 KiB
wxWidgets DOM port (feature/wx-dom-port)
THE wxWidgets WASM port: widgets are real HTML elements
(wxButton → <button>, wxTextCtrl → <input>, menus → popup divs)
instead of pixels on a canvas. Goals: styleable modern UI, native text
input/IME, accessibility, crisp rendering.
Status: COMPLETE and consolidated (2026-06-12). The original canvas
(wxUniversal) mode and all dual-build plumbing have been removed —
--with-wasm builds the DOM port, full stop. All src/univ/ +
include/wx/univ/ modifications were reverted to upstream v3.2.6, so the
wxwidgets fork is upstream + new src/wasm/ + include/wx/wasm/ +
build/wasm/ files + thin hooks. All six KiCad apps (pcbnew, eeschema,
calculator, pl_editor, symbol_editor, gerbview) build and run on it.
Build: docker/build.sh <app> (outputs in output/), wx standalone via
scripts/build-wx-wasm.sh, test apps via scripts/build-wasm-test.sh.
Test: npm test / npm run test:kicad from tests/ (no env vars).
Work intentionally stays on feature/wx-dom-port (root + wxwidgets +
kicad) — not merged to main/wasm-port.
Architecture in one page
- C++ owns state; the DOM is a projection. Controls call
WasmCreateDomNode("type")once inCreate(); the sharedwxWindowWasmmachinery then syncs geometry (TLW-relative absolute positioning, recursing into descendants; ancestor-viewport clipping viaclip-path), visibility (IsShownOnScreen, whole subtree), enabled/font/focus/destruction. DOM-native state (typed text, checked, selection) syncs back through events into C++ caches so getters stay synchronous. NB:GetScreenPosition()is the CLIENT-AREA origin (ClientToScreen(0,0)), not the top-left — the projection subtractsGetClientAreaOrigin()(matters for wxNotebook's tab strip). - Layout is wx sizers, not CSS. DOM contributes intrinsic measurement
only (clone-based, in an offscreen always-rendered host — sizers run
before
Show()); positions are written as absoluteleft/top/w/h. - Events flow element listener →
ccall('wx_dom_event')→domevents.cpprouting table → virtualwxWindowWasm::OnDomEvent— the same direct-dispatch pattern as the port's mouse callbacks (proven against Asyncify-suspended modals). Document-level listeners forward mouse activity over DOM children into the wx hit-test pipeline (wx_dom_mouse); wheel events walk up the window hierarchy. Keyboard arbitration: keystrokes go untouched to focused DOM editables (Escape excepted). - Canvas islands. Owner-drawn/generic widgets (wxGrid, listctrl,
tree, AUI, STC, calendar...) keep painting via the shared
dc.cppCanvas2D path inside per-window canvases — the same architecture native ports use for owner-drawn widgets. - Menus/toolbars serialize C++→JS as JSON; popups are DOM divs;
command ids route back via
wxDOM_EVENT_MENU/TOOL. The DOM-native wxNotebook renders a real tab strip (<button role=tab>). - E2E registry.
window.wxElementRegistry(wx.js) carries element geometry for Playwright. wxWindow-level entries come fromsrc/wasm/window.cpp; canvas-island content (grid cells, list rows, calendar dates, AUI parts...) is published from thin paint-site hooks that call helpers insrc/wasm/elementtracker.cpp; DOM-native composites (tabs, menu items, tools, spin arrows, text fields) are mirrored by wx-dom.js. One contract for everything.
Key files: wxwidgets/build/wasm/wx-dom.js (the whole JS control layer),
wxwidgets/include/wx/wasm/private/dom.h (C++→JS bridge),
wxwidgets/src/wasm/domevents.cpp (event routing + bitmap data URLs),
wxwidgets/src/wasm/window.cpp (DOM-backing machinery),
wxwidgets/src/wasm/elementtracker.cpp (e2e registry bridge).
What works (e2e-verified)
Full tests/e2e suite green; full kicad suite green.
- Native DOM: stattext, button (stock labels, default size), textctrl (single/multi/password, two-way sync, wxEVT_TEXT/_ENTER), checkbox, radiobutton (HTML name groups from wxRB_GROUP chains), radiobox, togglebutton, gauge, slider, statline, statbox, statbmp (PNG data URLs), bitmap buttons, choice, listbox, editable combobox (input+datalist), checklistbox, spinbutton (drives generic wxSpinCtrl), notebook (DOM tab strip), menubar+menus, toolbar, tooltips (#wx-tooltip layer, island widgets included), dialogs (shared Asyncify ShowModal), frame bar geometry.
- Canvas islands verified visually: AUI, calendar, virtual listctrl, grid, tree, propgrid, STC.
Known gaps / polish (tracked in visual-notes.md)
- wxSpinCtrl text field collapses beside the spin pair (generic composite
sizing); bitmap-button vertical centering; checklistbox selection
highlight; wxLB_SINGLE uses a multiple
<select>; native scrollbars replace univ gutters. - Element-registry hooks remain in
src/generic/+ aui/stc/propgrid as thin guarded one-liners (canvas islands have no per-item DOM, so tests need C++-fed geometry); everything heavier lives insrc/wasm/elementtracker.cpp.
Fork surface (vs upstream wxWidgets v3.2.6)
Upstream + new files (src/wasm/, include/wx/wasm/, build/wasm/),
the __WXWASM__ dispatch branches in include/wx/*.h (2–4 lines each),
WASM toolkit registration in the build system (bakefile-generated),
wasm-generic fixes in src/common/, a handful of unguarded island
rendering tweaks in src/generic/ (renderg/gridctrl/stattextg/msgdlgg/
spinctlg/filedlgg), and thin #ifdef __EMSCRIPTEN__ tracker shims at
paint sites. src/univ/, include/wx/univ/, src/generic/notebook.cpp,
tabg.cpp, build/msw/, tests/makefile.vc are byte-identical to
upstream.
See visual-notes.md for the full bug log the cross-port screenshot
comparison protocol produced.