pcbjam/docs/features/wx-dom-port/README.md
Viktor Vaczi 7dc1bf27e0 refactor: collapse dual-mode plumbing — the DOM port is the only WASM build
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>
2026-06-12 20:19:48 +02:00

5.7 KiB
Raw Blame History

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 in Create(); the shared wxWindowWasm machinery then syncs geometry (TLW-relative absolute positioning, recursing into descendants; ancestor-viewport clipping via clip-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 subtracts GetClientAreaOrigin() (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 absolute left/top/w/h.
  • Events flow element listener → ccall('wx_dom_event')domevents.cpp routing table → virtual wxWindowWasm::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.cpp Canvas2D 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 from src/wasm/window.cpp; canvas-island content (grid cells, list rows, calendar dates, AUI parts...) is published from thin paint-site hooks that call helpers in src/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 in src/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 (24 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.