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>
This commit is contained in:
Viktor Vaczi 2026-06-12 10:18:16 +02:00
commit 7dc1bf27e0
32 changed files with 199 additions and 335 deletions

1
.gitignore vendored
View file

@ -52,7 +52,6 @@ wxwidgets-clean/
/tests/apps/kicad/*.wasm /tests/apps/kicad/*.wasm
# DOM-port test bundles (mirrored + built by scripts/build-wasm-test.sh --dom) # DOM-port test bundles (mirrored + built by scripts/build-wasm-test.sh --dom)
/tests/apps-dom/
# Worktree-local canvas reference snapshot for regression gating (see # Worktree-local canvas reference snapshot for regression gating (see
# features/wx-dom-port docs); derived from a local run, not a project asset # features/wx-dom-port docs); derived from a local run, not a project asset

View file

@ -13,7 +13,7 @@ Always check screenshots for validating tests
Run e2e tests from /tests folder: `npm run test:kicad` or `npm run test:e2e` (not playwright directly) Run e2e tests from /tests folder: `npm run test:kicad` or `npm run test:e2e` (not playwright directly)
Build kicad with docker/build.sh (includes wxwidgets build, runs in docker) Build kicad with docker/build.sh (includes wxwidgets build, runs in docker)
Build wxwidgets standalone with scripts/build-wxuniversal-wasm.sh (runs on machine, for wxwidgets-only changes) Build wxwidgets standalone with scripts/build-wx-wasm.sh (runs on machine, for wxwidgets-only changes)
Build CPP wxwidgets tests with scripts/builds-wasm-test.sh Build CPP wxwidgets tests with scripts/builds-wasm-test.sh
The build scripts pipe their outputs into log files so that they won't clog the LLM context. The build scripts pipe their outputs into log files so that they won't clog the LLM context.
Don't pipe outputs, just run the scripts. Maybe with flex if you need that. Don't pipe outputs, just run the scripts. Maybe with flex if you need that.

View file

@ -14,7 +14,7 @@ git submodule update --init --recursive
./docker/build.sh ./docker/build.sh
# 3. Build wxWidgets for local testing # 3. Build wxWidgets for local testing
./scripts/build-wxuniversal-wasm.sh ./scripts/build-wx-wasm.sh
# 4. Build wxWidgets test apps # 4. Build wxWidgets test apps
./scripts/build-wasm-test.sh ./scripts/build-wasm-test.sh
@ -29,7 +29,7 @@ npm run test:kicad # KiCad tests (2 tests)
```bash ```bash
# Requires: Node.js 18+ (Emscripten SDK auto-installed on first build) # Requires: Node.js 18+ (Emscripten SDK auto-installed on first build)
./scripts/build-wxuniversal-wasm.sh ./scripts/build-wx-wasm.sh
./scripts/build-wasm-test.sh ./scripts/build-wasm-test.sh
cd tests && npm install && npm test cd tests && npm install && npm test
``` ```
@ -48,7 +48,7 @@ kicad-wasm/
│ ├── shims/ # Runtime JavaScript shims │ ├── shims/ # Runtime JavaScript shims
│ └── stubs/ # Stub implementations (libgit2, curl) │ └── stubs/ # Stub implementations (libgit2, curl)
├── scripts/ # Build scripts ├── scripts/ # Build scripts
│ ├── build-wxuniversal-wasm.sh # Build wxWidgets for WASM │ ├── build-wx-wasm.sh # Build wxWidgets for WASM
│ ├── build-wasm-test.sh # Build wxWidgets test apps │ ├── build-wasm-test.sh # Build wxWidgets test apps
│ ├── deps/ # Dependency build scripts │ ├── deps/ # Dependency build scripts
│ ├── kicad/ # KiCad build scripts │ ├── kicad/ # KiCad build scripts
@ -98,7 +98,7 @@ Build standalone wxWidgets test apps for feature testing:
```bash ```bash
# Build wxWidgets for WASM # Build wxWidgets for WASM
./scripts/build-wxuniversal-wasm.sh ./scripts/build-wx-wasm.sh
# Build test apps # Build test apps
./scripts/build-wasm-test.sh ./scripts/build-wasm-test.sh

View file

@ -178,20 +178,14 @@ compile_app() {
echo "" echo ""
echo "=== Building ${app} (${index}/${total}) ===" echo "=== Building ${app} (${index}/${total}) ==="
# WX_PORT=dom builds against the DOM wxWidgets into separate build and local out_dir="output"
# output trees (kicad-<app>-dom, output/dom/).
local out_dir
out_dir=$(out_dir_for)
local kicad_build="kicad-${app}" local kicad_build="kicad-${app}"
if [[ "${WX_PORT:-}" == "dom" ]]; then
kicad_build="kicad-${app}-dom"
fi
# Run build inside the container. # Run build inside the container.
# -e EMSDK=/emsdk: `docker compose exec` bypasses the entrypoint that sources # -e EMSDK=/emsdk: `docker compose exec` bypasses the entrypoint that sources
# emsdk_env.sh, so the build shell would lack emcc/embuilder on PATH. Setting # emsdk_env.sh, so the build shell would lack emcc/embuilder on PATH. Setting
# EMSDK lets scripts/common/env.sh source /emsdk/emsdk_env.sh and activate the toolchain. # EMSDK lets scripts/common/env.sh source /emsdk/emsdk_env.sh and activate the toolchain.
docker compose -f docker/docker-compose.yml exec -e EMSDK=/emsdk -e WX_PORT="${WX_PORT:-}" \ docker compose -f docker/docker-compose.yml exec -e EMSDK=/emsdk \
kicad-wasm-builder \ kicad-wasm-builder \
"/workspace/scripts/kicad/build-${app}.sh" "${ARGS[@]}" "/workspace/scripts/kicad/build-${app}.sh" "${ARGS[@]}"
@ -204,8 +198,8 @@ compile_app() {
cp /workspace/build-wasm/${kicad_build}/${subdir}/${app}.{js,wasm,wasm.debug.wasm,wasm.map,worker.js} /workspace/${out_dir}/ 2>/dev/null || \ cp /workspace/build-wasm/${kicad_build}/${subdir}/${app}.{js,wasm,wasm.debug.wasm,wasm.map,worker.js} /workspace/${out_dir}/ 2>/dev/null || \
cp /workspace/build-wasm/${kicad_build}/${subdir}/${app}.{js,wasm} /workspace/${out_dir}/; \ cp /workspace/build-wasm/${kicad_build}/${subdir}/${app}.{js,wasm} /workspace/${out_dir}/; \
cp /workspace/build-wasm/${kicad_build}/resources/images.tar.gz /workspace/${out_dir}/ 2>/dev/null || true; \ cp /workspace/build-wasm/${kicad_build}/resources/images.tar.gz /workspace/${out_dir}/ 2>/dev/null || true; \
cp /workspace/build-wasm/wxwidgets/build/wasm/wx.js /workspace/${out_dir}/ 2>/dev/null || true; \ cp /workspace/wxwidgets/build/wasm/wx.js /workspace/${out_dir}/ 2>/dev/null || true; \
if [ -n \"${WX_PORT:-}\" ]; then cp /workspace/build-wasm/wxwidgets/build/wasm/wx-dom.js /workspace/${out_dir}/ 2>/dev/null || true; fi" cp /workspace/wxwidgets/build/wasm/wx-dom.js /workspace/${out_dir}/ 2>/dev/null || true"
# The container runs as root, so files in the bind-mounted ./output land # The container runs as root, so files in the bind-mounted ./output land
# root-owned on the host. macOS Docker Desktop remaps ownership to the host # root-owned on the host. macOS Docker Desktop remaps ownership to the host
@ -215,24 +209,13 @@ compile_app() {
chown -R "$(id -u):$(id -g)" /workspace/output || true chown -R "$(id -u):$(id -g)" /workspace/output || true
} }
# Output dir for the current flavor: the DOM port (WX_PORT=dom) keeps its
# bundles in output/dom/ so the canvas bundles are never clobbered.
out_dir_for() {
if [[ "${WX_PORT:-}" == "dom" ]]; then
echo "output/dom"
else
echo "output"
fi
}
# Phase 2 of one app: host-side post-processing (dyncall shims, finalize, # Phase 2 of one app: host-side post-processing (dyncall shims, finalize,
# asyncify + -O2). Pure host work on output/${app}.* — independent of the # asyncify + -O2). Pure host work on output/${app}.* — independent of the
# container, which is what makes it safe to run in the background while the # container, which is what makes it safe to run in the background while the
# next app compiles. # next app compiles.
postprocess_app() { postprocess_app() {
local app="$1" local app="$1"
local out_dir local out_dir="output"
out_dir=$(out_dir_for)
# Inject dynCall shims (fixes "dynCall_* is not defined" errors in Emscripten 4.x) # Inject dynCall shims (fixes "dynCall_* is not defined" errors in Emscripten 4.x)
kw_stage dyncall-shims kw_stage dyncall-shims

View file

@ -143,7 +143,7 @@ Import patterns used:
**Full rebuild removes:** **Full rebuild removes:**
- `build-wasm/stamps/*` - All build stamps - `build-wasm/stamps/*` - All build stamps
- `build-wasm/deps/*` - All dependency builds - `build-wasm/deps/*` - All dependency builds
- `build-wasm/wxwidgets-universal` - wxWidgets build - `build-wasm/wxwidgets` - wxWidgets build
- `build-wasm/sysroot/*` - Installed headers/libraries - `build-wasm/sysroot/*` - Installed headers/libraries
- `build-wasm/kicad-pcbnew` - KiCad build - `build-wasm/kicad-pcbnew` - KiCad build
@ -208,7 +208,7 @@ After changing build flags (debug/release), use `--full` to force a complete reb
|--------|---------| |--------|---------|
| `docker/build.sh` | Host entry point (starts Docker, runs build) | | `docker/build.sh` | Host entry point (starts Docker, runs build) |
| `scripts/kicad/build-pcbnew.sh` | KiCad PCBnew build (runs inside Docker) | | `scripts/kicad/build-pcbnew.sh` | KiCad PCBnew build (runs inside Docker) |
| `scripts/build-wxuniversal-wasm.sh` | wxWidgets build | | `scripts/build-wx-wasm.sh` | wxWidgets build |
| `scripts/build-wasm-test.sh` | Build wxWidgets test apps | | `scripts/build-wasm-test.sh` | Build wxWidgets test apps |
| `scripts/deps/build-all-deps.sh` | All dependencies | | `scripts/deps/build-all-deps.sh` | All dependencies |
| `scripts/deps/build-*.sh` | Individual dependency builds | | `scripts/deps/build-*.sh` | Individual dependency builds |
@ -240,7 +240,7 @@ OpenCASCADE is the longest dependency to build (~30 minutes).
### Incremental build not picking up changes ### Incremental build not picking up changes
- For KiCad: use `--clean-kicad` to force rebuild - For KiCad: use `--clean-kicad` to force rebuild
- For wxWidgets: delete `build-wasm/wxwidgets-universal/Makefile` to force reconfigure - For wxWidgets: delete `build-wasm/wxwidgets/Makefile` to force reconfigure
### WASM exception with numeric error (e.g., `3788888`) ### WASM exception with numeric error (e.g., `3788888`)
- Build with debug symbols (default): No `--release` flag - Build with debug symbols (default): No `--release` flag

View file

@ -1,65 +1,71 @@
# wxWidgets DOM port (`feature/wx-dom-port`) # wxWidgets DOM port (`feature/wx-dom-port`)
A second WASM build mode where wxWidgets widgets are **real HTML elements** THE wxWidgets WASM port: widgets are **real HTML elements**
(`wxButton``<button>`, `wxTextCtrl``<input>`, menus → popup divs) (`wxButton``<button>`, `wxTextCtrl``<input>`, menus → popup divs)
instead of wxUniversal pixels on a canvas. Goals: styleable modern UI, instead of pixels on a canvas. Goals: styleable modern UI, native text
native text input/IME, accessibility, crisp rendering — while the existing input/IME, accessibility, crisp rendering.
canvas port keeps working unchanged from the same source tree.
Status: COMPLETE (Phases 07). wxWidgets-side port done, both wx e2e Status: COMPLETE and consolidated (2026-06-12). The original canvas
suites green from one tree, and all six KiCad apps (pcbnew, eeschema, (wxUniversal) mode and all dual-build plumbing have been **removed**
calculator, pl_editor, symbol_editor, gerbview) build and run on the DOM `--with-wasm` builds the DOM port, full stop. All `src/univ/` +
port — full kicad e2e suite green under `WX_PORT=dom` (28/0, 1 flaky, `include/wx/univ/` modifications were reverted to upstream v3.2.6, so the
2 skipped). Build with `WX_PORT=dom docker/build.sh <app>` (outputs in wxwidgets fork is upstream + new `src/wasm/` + `include/wx/wasm/` +
`output/dom/`); test with `WX_PORT=dom npm run test:kicad`. Work `build/wasm/` files + thin hooks. All six KiCad apps (pcbnew, eeschema,
intentionally stays on `feature/wx-dom-port` (root + wxwidgets + kicad) — calculator, pl_editor, symbol_editor, gerbview) build and run on it.
not merged to `main`/`wasm-port`.
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 ## Architecture in one page
- **One toolkit, two modes.** `--with-wasm --enable-universal` = canvas
(untouched); `--with-wasm` alone = DOM. Selection is file-level via the
build system: `src/univ/` only in canvas builds, `WASM_SRC` (native
controls) only under `WXUNIV_0`. Source of truth is
`build/bakefiles/files.bkl` → dockerized bakefile regenerates
`Makefile.in`/`autoconf_inc.m4` → autoconf regenerates `configure`
(`scripts/build-wxuniversal-wasm.sh` auto-detects each stage's
staleness; `--dom` flag selects the mode).
- **C++ owns state; the DOM is a projection.** Controls call - **C++ owns state; the DOM is a projection.** Controls call
`WasmCreateDomNode("type")` once in `Create()`; the shared `WasmCreateDomNode("type")` once in `Create()`; the shared
`wxWindowWasm` machinery then syncs geometry (TLW-relative absolute `wxWindowWasm` machinery then syncs geometry (TLW-relative absolute
positioning, recursing into descendants), visibility positioning, recursing into descendants; ancestor-viewport clipping via
(`IsShownOnScreen`, whole subtree), enabled/font/focus/destruction. `clip-path`), visibility (`IsShownOnScreen`, whole subtree),
DOM-native state (typed text, checked, selection) syncs back through enabled/font/focus/destruction. DOM-native state (typed text, checked,
events into C++ caches so getters stay synchronous. 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 - **Layout is wx sizers, not CSS.** DOM contributes intrinsic measurement
only (clone-based, in an offscreen always-rendered host — sizers run only (clone-based, in an offscreen always-rendered host — sizers run
before `Show()`); positions are written as absolute `left/top/w/h`. before `Show()`); positions are written as absolute `left/top/w/h`.
- **Events** flow element listener → `ccall('wx_dom_event')` - **Events** flow element listener → `ccall('wx_dom_event')`
`domevents.cpp` routing table → virtual `wxWindowWasm::OnDomEvent` `domevents.cpp` routing table → virtual `wxWindowWasm::OnDomEvent`
the same direct-dispatch pattern as the port's mouse callbacks (proven the same direct-dispatch pattern as the port's mouse callbacks (proven
against Asyncify-suspended modals). Keyboard arbitration: keystrokes go against Asyncify-suspended modals). Document-level listeners forward
untouched to focused DOM editables (Escape excepted). 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, - **Canvas islands.** Owner-drawn/generic widgets (wxGrid, listctrl,
tree, AUI, STC, calendar...) keep painting via the shared `dc.cpp` tree, AUI, STC, calendar...) keep painting via the shared `dc.cpp`
Canvas2D path inside per-window canvases; they render near-pixel-equal Canvas2D path inside per-window canvases — the same architecture native
to the canvas port. ports use for owner-drawn widgets.
- **Menus/toolbars** serialize C++→JS as JSON; popups are DOM divs; - **Menus/toolbars** serialize C++→JS as JSON; popups are DOM divs;
command ids route back via `wxDOM_EVENT_MENU`/`TOOL`. Menu titles, command ids route back via `wxDOM_EVENT_MENU`/`TOOL`. The DOM-native
items, and tools register as rendered elements with the same wxNotebook renders a real tab strip (`<button role=tab>`).
type strings the canvas port uses, so registry-based e2e utilities work - **E2E registry.** `window.wxElementRegistry` (wx.js) carries element
identically on both ports. 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), Key files: `wxwidgets/build/wasm/wx-dom.js` (the whole JS control layer),
`wxwidgets/include/wx/wasm/private/dom.h` (C++→JS bridge), `wxwidgets/include/wx/wasm/private/dom.h` (C++→JS bridge),
`wxwidgets/src/wasm/domevents.cpp` (event routing + bitmap data URLs), `wxwidgets/src/wasm/domevents.cpp` (event routing + bitmap data URLs),
`wxwidgets/src/wasm/window.cpp` (DOM-backing machinery, guarded `wxwidgets/src/wasm/window.cpp` (DOM-backing machinery),
`#ifndef __WXUNIVERSAL__`). `wxwidgets/src/wasm/elementtracker.cpp` (e2e registry bridge).
## What works (e2e-verified, WX_PORT=dom) ## What works (e2e-verified)
Full `tests/e2e` suite green (256/0; canvas 263/0 from the same tree, its Full `tests/e2e` suite green; full kicad suite green.
stable screenshot set byte-identical to the pre-feature reference).
- Native DOM: stattext, button (stock labels, default size), textctrl - Native DOM: stattext, button (stock labels, default size), textctrl
(single/multi/password, two-way sync, wxEVT_TEXT/_ENTER), checkbox, (single/multi/password, two-way sync, wxEVT_TEXT/_ENTER), checkbox,
@ -67,8 +73,9 @@ stable screenshot set byte-identical to the pre-feature reference).
togglebutton, gauge, slider, statline, statbox, statbmp (PNG data togglebutton, gauge, slider, statline, statbox, statbmp (PNG data
URLs), bitmap buttons, choice, listbox, editable combobox URLs), bitmap buttons, choice, listbox, editable combobox
(input+datalist), checklistbox, spinbutton (drives generic wxSpinCtrl), (input+datalist), checklistbox, spinbutton (drives generic wxSpinCtrl),
menubar+menus, toolbar, tooltips (title attrs), dialogs (shared notebook (DOM tab strip), menubar+menus, toolbar, tooltips (#wx-tooltip
Asyncify ShowModal), frame bar geometry (univ-parity). layer, island widgets included), dialogs (shared Asyncify ShowModal),
frame bar geometry.
- Canvas islands verified visually: AUI, calendar, virtual listctrl, - Canvas islands verified visually: AUI, calendar, virtual listctrl,
grid, tree, propgrid, STC. grid, tree, propgrid, STC.
@ -78,19 +85,22 @@ stable screenshot set byte-identical to the pre-feature reference).
sizing); bitmap-button vertical centering; checklistbox selection sizing); bitmap-button vertical centering; checklistbox selection
highlight; wxLB_SINGLE uses a multiple `<select>`; native scrollbars highlight; wxLB_SINGLE uses a multiple `<select>`; native scrollbars
replace univ gutters. replace univ gutters.
- Tracker hooks in `src/generic/`+aui/stc/propgrid are shared with the - Element-registry hooks remain in `src/generic/` + aui/stc/propgrid as
canvas port and stay until the canvas port retires (the DOM port itself thin guarded one-liners (canvas islands have no per-item DOM, so tests
doesn't need them — its controls register from creation + ARIA). The need C++-fed geometry); everything heavier lives in
univ hooks die with univ automatically. `src/wasm/elementtracker.cpp`.
## Upstreaming surface (vs `wasm-port` base) ## Fork surface (vs upstream wxWidgets v3.2.6)
8 commits, ~107 files: the port directory itself (new files), 35 Upstream + new files (`src/wasm/`, `include/wx/wasm/`, `build/wasm/`),
dispatch-header branches (24 lines each), build-system entries the `__WXWASM__` dispatch branches in `include/wx/*.h` (24 lines each),
(bakefile-generated), one-line `prntbase.cpp` PostScript condition, and WASM toolkit registration in the build system (bakefile-generated),
two genuine upstream bug fixes in the generic notebook wasm-generic fixes in `src/common/`, a handful of unguarded island
(`tabg.cpp` uninitialized background brush; `notebook.cpp` first-page rendering tweaks in `src/generic/` (renderg/gridctrl/stattextg/msgdlgg/
selection never syncing the tab view). Zero changes to univ/aui/stc. 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 15-bug log the cross-port screenshot See `visual-notes.md` for the full bug log the cross-port screenshot
comparison protocol produced. comparison protocol produced.

View file

@ -184,3 +184,33 @@ User-reported pcbnew issues, all fixed and e2e-pinned
25. Universal tooltip layer (#wx-tooltip div, 600 ms, hover hit-test 25. Universal tooltip layer (#wx-tooltip div, 600 ms, hover hit-test
driven) — island widgets (color swatches, visibility toggles) get driven) — island widgets (color swatches, visibility toggles) get
KiCad's SetToolTip texts; title attrs replaced by aria-label. KiCad's SetToolTip texts; title attrs replaced by aria-label.
## DOM-only consolidation (2026-06-12)
The canvas (wxUniversal) mode and dual-build plumbing were removed; the
DOM port is THE WASM port. Rebuilding everything from clean surfaced
three latent bugs (all pre-existing — verified by rebuilding the
pre-consolidation tree, which failed identically):
26. The DOM projection treated GetScreenPosition() as the window's
top-left; it is ClientToScreen(0,0) — the CLIENT-AREA origin. The
first widget with a non-zero client origin (the DOM notebook's tab
strip) rendered its whole box a strip-height too low, overlapping
its own pages, and ComputeAncestorClip double-added the origin,
clipping page content (dataview/grid/minimal buttons cut to
slivers, clicks swallowed by the strip). This was bug-logged
earlier as "tab-strip/content slight overlap" and believed
cosmetic. Fixed in UpdateDomGeometryRecursive (+ eager strip
re-measure + re-projection in wxNotebook::WasmRebuildTabs).
27. The DOM port never mirrored spin arrows and text fields into the
e2e registry (the canvas port published them from univ paint
hooks, which died with univ). wx-dom.js now registers
'spinbutton' (up/down) and 'textctrl' (singleline/multiline)
rendered elements, and clears a control's mirrored entries on
destroy.
28. tests: clickCanvas() used locator.click(), whose actionability
check refuses points covered by DOM elements — in the DOM port
real widgets legitimately cover the canvas. It now dispatches via
page.mouse. The minimal-app comprehensive spec drives the wxChoice
through its native <select> (browsers own that popup; it cannot be
coordinate-clicked).

View file

@ -10,7 +10,6 @@ source "$(dirname "$0")/common/env.sh"
# ./build-wasm-test.sh # Incremental build (default) # ./build-wasm-test.sh # Incremental build (default)
# ./build-wasm-test.sh --clean # Clean build from scratch # ./build-wasm-test.sh --clean # Clean build from scratch
# ./build-wasm-test.sh --debug # Build with debug symbols # ./build-wasm-test.sh --debug # Build with debug symbols
# ./build-wasm-test.sh --dom # Build against the DOM port into tests/apps-dom
# ./build-wasm-test.sh menu # Build only the menu test # ./build-wasm-test.sh menu # Build only the menu test
# ./build-wasm-test.sh --debug menu # Build menu test with debug symbols # ./build-wasm-test.sh --debug menu # Build menu test with debug symbols
@ -18,7 +17,6 @@ set -e
DEBUG_BUILD=0 DEBUG_BUILD=0
CLEAN_BUILD=0 CLEAN_BUILD=0
DOM_BUILD=0
TARGET="" TARGET=""
# Parse arguments # Parse arguments
@ -27,8 +25,6 @@ for arg in "$@"; do
DEBUG_BUILD=1 DEBUG_BUILD=1
elif [ "$arg" = "--clean" ]; then elif [ "$arg" = "--clean" ]; then
CLEAN_BUILD=1 CLEAN_BUILD=1
elif [ "$arg" = "--dom" ]; then
DOM_BUILD=1
elif [ "$arg" != "" ]; then elif [ "$arg" != "" ]; then
TARGET="$arg" TARGET="$arg"
fi fi
@ -38,29 +34,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
TESTS_DIR="$PROJECT_ROOT/tests" TESTS_DIR="$PROJECT_ROOT/tests"
if [ "$DOM_BUILD" = "1" ]; then BUILD_DIR="$PROJECT_ROOT/build-wasm/wxwidgets"
BUILD_DIR="$PROJECT_ROOT/build-wasm/wxwidgets-dom" WASM_APP_DIR="$TESTS_DIR/apps"
# DOM apps build in a mirrored source tree so canvas and DOM bundles
# coexist; relative ../../ paths in Makefile.wasm resolve identically.
WASM_APP_DIR="$TESTS_DIR/apps-dom"
echo "Mirroring test app sources into apps-dom..."
# kicad/ is excluded: apps-dom/kicad is owned by
# tests/scripts/setup-kicad-wasm.sh (DOM bundles + injected pages) and
# apps/kicad only has the checked-in HTML — mirroring with --delete
# would wipe the synced multi-hundred-MB kicad artifacts.
rsync -a --delete \
--exclude 'kicad/' \
--exclude '*.o' --exclude '*.d' \
--exclude '*_test.html' --exclude '*_test.js' --exclude '*_test.wasm' \
--exclude '*_repro.html' --exclude '*_repro.js' --exclude '*_repro.wasm' \
--exclude '*.wasm.map' \
"$TESTS_DIR/apps/" "$WASM_APP_DIR/"
MAKE_PORT_ARGS="PORT=dom"
else
BUILD_DIR="$PROJECT_ROOT/build-wasm/wxwidgets-universal"
WASM_APP_DIR="$TESTS_DIR/apps"
MAKE_PORT_ARGS=""
fi
STANDALONE_DIR="$WASM_APP_DIR/standalone" STANDALONE_DIR="$WASM_APP_DIR/standalone"
echo "=== Building wxWidgets WASM Test Applications ===" echo "=== Building wxWidgets WASM Test Applications ==="
@ -80,7 +55,7 @@ echo "Test app dir: $WASM_APP_DIR"
# Verify wxWidgets is built # Verify wxWidgets is built
if [ ! -f "$BUILD_DIR/wx-config" ]; then if [ ! -f "$BUILD_DIR/wx-config" ]; then
echo "ERROR: wxWidgets not built. Run build-wxuniversal-wasm.sh first" echo "ERROR: wxWidgets not built. Run build-wx-wasm.sh first"
exit 1 exit 1
fi fi
@ -120,9 +95,9 @@ fi
# Build (pass DEBUG flag if requested). App links are independent, so honor # Build (pass DEBUG flag if requested). App links are independent, so honor
# JOBS/PARALLEL_JOBS from env.sh (each emcc link is slow due to Asyncify). # JOBS/PARALLEL_JOBS from env.sh (each emcc link is slow due to Asyncify).
if [ "$DEBUG_BUILD" = "1" ]; then if [ "$DEBUG_BUILD" = "1" ]; then
make -j"${JOBS:-1}" -f Makefile.wasm DEBUG=1 $MAKE_PORT_ARGS "$MAKE_TARGET" make -j"${JOBS:-1}" -f Makefile.wasm DEBUG=1 "$MAKE_TARGET"
else else
make -j"${JOBS:-1}" -f Makefile.wasm $MAKE_PORT_ARGS "$MAKE_TARGET" make -j"${JOBS:-1}" -f Makefile.wasm "$MAKE_TARGET"
fi fi
echo "" echo ""

View file

@ -1,5 +1,6 @@
#!/bin/bash #!/bin/bash
# Build wxWidgets with wxUniversal for WebAssembly # Build wxWidgets for WebAssembly (the DOM port: widgets are real HTML
# elements; owner-drawn widgets render into per-window canvas islands)
# Redirect all output to a log file (re-execs script with redirection) # Redirect all output to a log file (re-execs script with redirection)
source "$(dirname "$0")/common/logging.sh" source "$(dirname "$0")/common/logging.sh"
@ -20,10 +21,8 @@ source "$(dirname "$0")/common/stages.sh"
# ghcr.io/vslavik/bakefile:0.2 bakefile_gen # ghcr.io/vslavik/bakefile:0.2 bakefile_gen
# #
# Usage: # Usage:
# ./build-wxuniversal-wasm.sh # Incremental build (default) # ./build-wx-wasm.sh # Incremental build (default)
# ./build-wxuniversal-wasm.sh --clean # Clean build from scratch # ./build-wx-wasm.sh --clean # Clean build from scratch
# ./build-wxuniversal-wasm.sh --dom # Build the DOM port (native widgets,
# # no wxUniversal) into build-wasm/wxwidgets-dom
set -e set -e
@ -31,34 +30,19 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
WX_SOURCE="$PROJECT_ROOT/wxwidgets" WX_SOURCE="$PROJECT_ROOT/wxwidgets"
# Parse arguments: --clean, --no-clean (default; kept for the kicad # Parse arguments: --clean or --no-clean (default; kept for the kicad
# pipeline's explicit call) and/or --dom, in any order. WX_PORT=dom env is # pipeline's explicit call), in any order.
# equivalent to --dom so the flavor can flow through the docker pipeline.
CLEAN_BUILD=0 CLEAN_BUILD=0
DOM_BUILD=0
[ "${WX_PORT:-}" = "dom" ] && DOM_BUILD=1
for arg in "$@"; do for arg in "$@"; do
case "$arg" in case "$arg" in
--clean) CLEAN_BUILD=1 ;; --clean) CLEAN_BUILD=1 ;;
--no-clean) CLEAN_BUILD=0 ;; --no-clean) CLEAN_BUILD=0 ;;
--dom) DOM_BUILD=1 ;;
*) echo "Unknown argument: $arg"; exit 1 ;; *) echo "Unknown argument: $arg"; exit 1 ;;
esac esac
done done
if [ "$DOM_BUILD" = "1" ]; then BUILD_DIR="$PROJECT_ROOT/build-wasm/wxwidgets"
BUILD_DIR="$PROJECT_ROOT/build-wasm/wxwidgets-dom" WXLIB_PREFIX="libwx_wasmu"
# Native (non-universal) widget set: controls are real DOM elements.
UNIVERSAL_FLAG=""
# Tooltips are DOM title attributes (wx/wasm/tooltip.h).
PORT_EXTRA_FLAGS=""
WXLIB_PREFIX="libwx_wasmu"
else
BUILD_DIR="$PROJECT_ROOT/build-wasm/wxwidgets-universal"
UNIVERSAL_FLAG="--enable-universal"
PORT_EXTRA_FLAGS=""
WXLIB_PREFIX="libwx_wasmunivu"
fi
# Use our config.sub wrapper for autoconf projects # Use our config.sub wrapper for autoconf projects
# CONFIG_SHELL is critical: nested configures (pcre, etc.) do SHELL=${CONFIG_SHELL-/bin/sh} # CONFIG_SHELL is critical: nested configures (pcre, etc.) do SHELL=${CONFIG_SHELL-/bin/sh}
@ -72,7 +56,7 @@ export AUTOM4TE="$SCRIPT_DIR/config/autom4te-wrapper.sh"
# Use JOBS from env.sh if set, otherwise use all available cores # Use JOBS from env.sh if set, otherwise use all available cores
JOBS="${JOBS:-$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)}" JOBS="${JOBS:-$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)}"
echo "=== Building wxWidgets wxUniversal for WASM ===" echo "=== Building wxWidgets for WASM ==="
echo "Project root: $PROJECT_ROOT" echo "Project root: $PROJECT_ROOT"
echo "Build dir: $BUILD_DIR" echo "Build dir: $BUILD_DIR"
echo "wxWidgets source: $WX_SOURCE" echo "wxWidgets source: $WX_SOURCE"
@ -130,7 +114,6 @@ if [ $NEEDS_CONFIGURE -eq 1 ]; then
# Configure with emconfigure # Configure with emconfigure
# Key flags based on wxWidgets-wasm: # Key flags based on wxWidgets-wasm:
# --host=emscripten Host system (detected via config.sub) # --host=emscripten Host system (detected via config.sub)
# --enable-universal Use wxUniversal (draws widgets directly)
# --disable-shared Build static libraries # --disable-shared Build static libraries
# --with-opengl Enable OpenGL/WebGL support # --with-opengl Enable OpenGL/WebGL support
# --enable-exceptions Enable C++ exceptions (needed for KiCad debug builds) # --enable-exceptions Enable C++ exceptions (needed for KiCad debug builds)
@ -183,8 +166,6 @@ if [ $NEEDS_CONFIGURE -eq 1 ]; then
emconfigure "$WX_SOURCE/configure" \ emconfigure "$WX_SOURCE/configure" \
--host=emscripten \ --host=emscripten \
--without-subdirs \ --without-subdirs \
${UNIVERSAL_FLAG} \
${PORT_EXTRA_FLAGS} \
--disable-shared \ --disable-shared \
--with-opengl \ --with-opengl \
--enable-exceptions \ --enable-exceptions \

View file

@ -31,7 +31,7 @@ export STUBS_DIR="$PROJECT_ROOT/stubs"
export CMAKE_MODULES="$PROJECT_ROOT/cmake" export CMAKE_MODULES="$PROJECT_ROOT/cmake"
# wxWidgets build location # wxWidgets build location
export WX_BUILD="$BUILD_ROOT/wxwidgets-universal" export WX_BUILD="$BUILD_ROOT/wxwidgets"
# Emscripten settings # Emscripten settings
export EMSDK_QUIET=1 export EMSDK_QUIET=1

View file

@ -8,25 +8,8 @@ set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
# --port dom compares the DOM-port artifacts (WX_PORT=dom test runs) against BASELINE_DIR="$PROJECT_ROOT/tests/baseline-screenshots"
# their own baseline set; canvas paths are the default and stay unchanged. TEST_RESULTS_DIR="$PROJECT_ROOT/tests/test-results"
PORT=""
ARGS=()
while [ $# -gt 0 ]; do
case "$1" in
--port) PORT="$2"; shift 2 ;;
*) ARGS+=("$1"); shift ;;
esac
done
set -- "${ARGS[@]+"${ARGS[@]}"}"
if [ "$PORT" = "dom" ]; then
BASELINE_DIR="$PROJECT_ROOT/tests/baseline-screenshots-dom"
TEST_RESULTS_DIR="$PROJECT_ROOT/tests/test-results/dom"
else
BASELINE_DIR="$PROJECT_ROOT/tests/baseline-screenshots"
TEST_RESULTS_DIR="$PROJECT_ROOT/tests/test-results"
fi
# Check if directories exist # Check if directories exist
if [ ! -d "$BASELINE_DIR" ]; then if [ ! -d "$BASELINE_DIR" ]; then

View file

@ -75,17 +75,9 @@ source "${SCRIPT_DIR}/../common/stages.sh"
KICAD_DIR="${PROJECT_ROOT}/kicad" KICAD_DIR="${PROJECT_ROOT}/kicad"
WASM_LAYER="${PROJECT_ROOT}/wasm" WASM_LAYER="${PROJECT_ROOT}/wasm"
# WX_PORT=dom links against the DOM (non-universal) wxWidgets build and KICAD_BUILD="${BUILD_ROOT}/kicad-${APP_NAME}"
# keeps its KiCad build tree separate from the canvas one. KICAD_STAMP="${BUILD_ROOT}/stamps/kicad-${APP_NAME}.stamp"
if [ "${WX_PORT:-}" = "dom" ]; then WX_BUILD="${BUILD_ROOT}/wxwidgets"
KICAD_BUILD="${BUILD_ROOT}/kicad-${APP_NAME}-dom"
KICAD_STAMP="${BUILD_ROOT}/stamps/kicad-${APP_NAME}-dom.stamp"
WX_BUILD="${BUILD_ROOT}/wxwidgets-dom"
else
KICAD_BUILD="${BUILD_ROOT}/kicad-${APP_NAME}"
KICAD_STAMP="${BUILD_ROOT}/stamps/kicad-${APP_NAME}.stamp"
WX_BUILD="${BUILD_ROOT}/wxwidgets-universal"
fi
# Parse arguments - incremental build by default (optimized for development) # Parse arguments - incremental build by default (optimized for development)
NO_CLEAN=1 NO_CLEAN=1
@ -167,7 +159,7 @@ if [ $FULL_CLEAN -eq 1 ]; then
log_info "Full clean: removing all stamps and build directories..." log_info "Full clean: removing all stamps and build directories..."
rm -rf "${STAMPS_DIR}"/* rm -rf "${STAMPS_DIR}"/*
rm -rf "${BUILD_ROOT}/deps"/* rm -rf "${BUILD_ROOT}/deps"/*
rm -rf "${BUILD_ROOT}/wxwidgets-universal" rm -rf "${BUILD_ROOT}/wxwidgets"
rm -rf "${BUILD_ROOT}/stubs" rm -rf "${BUILD_ROOT}/stubs"
rm -rf "${KICAD_BUILD}" rm -rf "${KICAD_BUILD}"
rm -rf "${SYSROOT}"/* rm -rf "${SYSROOT}"/*
@ -196,7 +188,7 @@ fi
# Step 4: Build wxWidgets (incremental - only recompiles changed files) # Step 4: Build wxWidgets (incremental - only recompiles changed files)
kw_stage wxwidgets kw_stage wxwidgets
log_info "Building wxWidgets..." log_info "Building wxWidgets..."
"${SCRIPT_DIR}/../build-wxuniversal-wasm.sh" --no-clean "${SCRIPT_DIR}/../build-wx-wasm.sh" --no-clean
log_info "Building KiCad ${APP_NAME} ${KICAD_VERSION} for WASM..." log_info "Building KiCad ${APP_NAME} ${KICAD_VERSION} for WASM..."

View file

@ -7,30 +7,22 @@
# Usage: # Usage:
# ./update-baseline-screenshots.sh # Only copy NEW screenshots # ./update-baseline-screenshots.sh # Only copy NEW screenshots
# ./update-baseline-screenshots.sh --all # Copy new + significantly different # ./update-baseline-screenshots.sh --all # Copy new + significantly different
# ./update-baseline-screenshots.sh --port dom # Same, for the DOM-port baseline set
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TESTS_DIR="$SCRIPT_DIR/../tests" TESTS_DIR="$SCRIPT_DIR/../tests"
COPY_ALL=0 COPY_ALL=0
PORT=""
THRESHOLD=5 # Percent threshold for "significant" difference THRESHOLD=5 # Percent threshold for "significant" difference
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
case "$1" in case "$1" in
--all) COPY_ALL=1; shift ;; --all) COPY_ALL=1; shift ;;
--port) PORT="$2"; shift 2 ;;
*) shift ;; *) shift ;;
esac esac
done done
if [ "$PORT" = "dom" ]; then SOURCE_DIR="$TESTS_DIR/test-results"
SOURCE_DIR="$TESTS_DIR/test-results/dom" DEST_DIR="$TESTS_DIR/baseline-screenshots"
DEST_DIR="$TESTS_DIR/baseline-screenshots-dom"
else
SOURCE_DIR="$TESTS_DIR/test-results"
DEST_DIR="$TESTS_DIR/baseline-screenshots"
fi
if [ $COPY_ALL -eq 1 ]; then if [ $COPY_ALL -eq 1 ]; then
echo "Mode: Copy NEW + SIGNIFICANTLY DIFFERENT screenshots" echo "Mode: Copy NEW + SIGNIFICANTLY DIFFERENT screenshots"

View file

@ -8,16 +8,8 @@
TOOLS_ROOT = ../../wxwidgets/build/wasm TOOLS_ROOT = ../../wxwidgets/build/wasm
KICAD_ROOT = ../../kicad KICAD_ROOT = ../../kicad
# PORT=dom links against the DOM (non-universal) wxWidgets build and adds the WXCONFIG = ../../build-wasm/wxwidgets/wx-config
# wx-dom.js shim. Invoked by scripts/build-wasm-test.sh --dom from a mirrored WXLIB_PREFIX = libwx_wasmu
# source tree in tests/apps-dom (so canvas and DOM bundles coexist).
ifeq ($(PORT),dom)
WXCONFIG = ../../build-wasm/wxwidgets-dom/wx-config
WXLIB_PREFIX = libwx_wasmu
else
WXCONFIG = ../../build-wasm/wxwidgets-universal/wx-config
WXLIB_PREFIX = libwx_wasmunivu
endif
CXX = em++ CXX = em++
WX_CXXFLAGS := $(shell $(WXCONFIG) --cxxflags) WX_CXXFLAGS := $(shell $(WXCONFIG) --cxxflags)
@ -113,25 +105,16 @@ LDFLAGS_COROUTINE = $(DEBUG_LDFLAGS) $(COROUTINE_BASE_LDFLAGS) $(WX_LDFLAGS_NOGL
# forbids the very multi-parked-sleep states the harness exists to exercise. # forbids the very multi-parked-sleep states the harness exists to exercise.
LDFLAGS_RACES = $(DEBUG_LDFLAGS) $(COROUTINE_BASE_LDFLAGS) -sASSERTIONS=0 $(WX_LDFLAGS_NOGL) LDFLAGS_RACES = $(DEBUG_LDFLAGS) $(COROUTINE_BASE_LDFLAGS) -sASSERTIONS=0 $(WX_LDFLAGS_NOGL)
# In dom mode a second pre-js carries the DOM-control shim ("--pre-js A --pre-js B" # A second pre-js carries the DOM-control shim ("--pre-js A --pre-js B"
# after expansion — emcc accepts repeated --pre-js). # after expansion — emcc accepts repeated --pre-js).
# JS_FILES lists the actual pre-js paths (no flags) so the link targets can # JS_FILES lists the actual pre-js paths (no flags) so the link targets can
# depend on them — otherwise editing a shim wouldn't trigger a relink. # depend on them — otherwise editing a shim wouldn't trigger a relink.
ifeq ($(PORT),dom) JS = $(TOOLS_ROOT)/wx.js --pre-js $(TOOLS_ROOT)/wx-dom.js
JS = $(TOOLS_ROOT)/wx.js --pre-js $(TOOLS_ROOT)/wx-dom.js JS_FILES = $(TOOLS_ROOT)/wx.js $(TOOLS_ROOT)/wx-dom.js
JS_FILES = $(TOOLS_ROOT)/wx.js $(TOOLS_ROOT)/wx-dom.js
else
JS = $(TOOLS_ROOT)/wx.js
JS_FILES = $(TOOLS_ROOT)/wx.js
endif
HTML = $(TOOLS_ROOT)/template.html HTML = $(TOOLS_ROOT)/template.html
# wxWidgets library directory - used as dependency to rebuild when libs change # wxWidgets library directory - used as dependency to rebuild when libs change
ifeq ($(PORT),dom) WX_LIB_DIR = ../../build-wasm/wxwidgets/lib
WX_LIB_DIR = ../../build-wasm/wxwidgets-dom/lib
else
WX_LIB_DIR = ../../build-wasm/wxwidgets-universal/lib
endif
# Key library that changes when wxWidgets is rebuilt # Key library that changes when wxWidgets is rebuilt
WX_CORE_LIB = $(WX_LIB_DIR)/$(WXLIB_PREFIX)_core-3.2-emscripten.a WX_CORE_LIB = $(WX_LIB_DIR)/$(WXLIB_PREFIX)_core-3.2-emscripten.a

View file

@ -193,6 +193,7 @@
<!-- wxWidgets WASM glue code (defines getConfigEntryLength, etc.) --> <!-- wxWidgets WASM glue code (defines getConfigEntryLength, etc.) -->
<script src="wx.js"></script> <script src="wx.js"></script>
<script src="wx-dom.js"></script>
<script async src="calculator.js"></script> <script async src="calculator.js"></script>
</body> </body>
</html> </html>

View file

@ -225,6 +225,7 @@
<!-- wxWidgets WASM glue code (defines getConfigEntryLength, etc.) --> <!-- wxWidgets WASM glue code (defines getConfigEntryLength, etc.) -->
<script src="wx.js"></script> <script src="wx.js"></script>
<script src="wx-dom.js"></script>
<script async src="eeschema.js"></script> <script async src="eeschema.js"></script>
</body> </body>
</html> </html>

View file

@ -195,6 +195,7 @@
<!-- wxWidgets WASM glue code (defines getConfigEntryLength, etc.) --> <!-- wxWidgets WASM glue code (defines getConfigEntryLength, etc.) -->
<script src="wx.js"></script> <script src="wx.js"></script>
<script src="wx-dom.js"></script>
<script async src="gerbview.js"></script> <script async src="gerbview.js"></script>
</body> </body>
</html> </html>

View file

@ -229,6 +229,7 @@
<!-- wxWidgets WASM glue code (defines getConfigEntryLength, etc.) --> <!-- wxWidgets WASM glue code (defines getConfigEntryLength, etc.) -->
<script src="wx.js"></script> <script src="wx.js"></script>
<script src="wx-dom.js"></script>
<script async src="pcbnew.js"></script> <script async src="pcbnew.js"></script>
</body> </body>
</html> </html>

View file

@ -194,6 +194,7 @@
<!-- wxWidgets WASM glue code (defines getConfigEntryLength, etc.) --> <!-- wxWidgets WASM glue code (defines getConfigEntryLength, etc.) -->
<script src="wx.js"></script> <script src="wx.js"></script>
<script src="wx-dom.js"></script>
<script async src="pcbnew.js"></script> <script async src="pcbnew.js"></script>
</body> </body>
</html> </html>

View file

@ -219,6 +219,7 @@
<!-- wxWidgets WASM glue code (defines getConfigEntryLength, etc.) --> <!-- wxWidgets WASM glue code (defines getConfigEntryLength, etc.) -->
<script src="wx.js"></script> <script src="wx.js"></script>
<script src="wx-dom.js"></script>
<script async src="pl_editor.js"></script> <script async src="pl_editor.js"></script>
</body> </body>
</html> </html>

View file

@ -218,6 +218,7 @@
<!-- wxWidgets WASM glue code (defines getConfigEntryLength, etc.) --> <!-- wxWidgets WASM glue code (defines getConfigEntryLength, etc.) -->
<script src="wx.js"></script> <script src="wx.js"></script>
<script src="wx-dom.js"></script>
<script async src="symbol_editor.js"></script> <script async src="symbol_editor.js"></script>
</body> </body>
</html> </html>

View file

@ -1,8 +1,7 @@
import { test, expect, waitForApp } from './utils/fixtures'; import { test, expect, waitForApp } from './utils/fixtures';
// Port-agnostic boot check: the app starts, the wx element registry fills, // Boot check: the app starts, the wx element registry fills, and the
// and (in DOM mode) the wx-dom shim is present. Runs under both WX_PORT // wx-dom shim is present. Intentionally takes no screenshots.
// values; intentionally takes no screenshots.
test.describe('Application boot', () => { test.describe('Application boot', () => {
test('minimal app boots and registers elements', async ({ page, testLogger }) => { test('minimal app boots and registers elements', async ({ page, testLogger }) => {
const fatal: string[] = []; const fatal: string[] = [];
@ -24,7 +23,7 @@ test.describe('Application boot', () => {
expect(elementCount).toBeGreaterThan(0); expect(elementCount).toBeGreaterThan(0);
const isDomPort = await page.evaluate(() => (window as any).wxDomPort === true); const isDomPort = await page.evaluate(() => (window as any).wxDomPort === true);
expect(isDomPort).toBe(process.env.WX_PORT === 'dom'); expect(isDomPort, 'wx-dom.js shim should be loaded').toBe(true);
expect(fatal, `page errors: ${fatal.join('\n')}`).toHaveLength(0); expect(fatal, `page errors: ${fatal.join('\n')}`).toHaveLength(0);
}); });

View file

@ -2,31 +2,10 @@ import { test as base } from '@playwright/test';
import * as path from 'path'; import * as path from 'path';
import { setupTestLogger, writeTestLogs, TestLogger, MAIN_CANVAS, waitForApp, tryLoadApp, getCanvasBox, WXWIDGETS_LOGS_DIR, getTestFileName } from './test-utils'; import { setupTestLogger, writeTestLogs, TestLogger, MAIN_CANVAS, waitForApp, tryLoadApp, getCanvasBox, WXWIDGETS_LOGS_DIR, getTestFileName } from './test-utils';
// WX_PORT=dom runs the same specs against the DOM-port bundles; screenshots
// and logs are namespaced into a dom/ subdirectory so the canvas artifacts
// stay untouched (baselines: tests/baseline-screenshots-dom/).
const IS_DOM_PORT = process.env.WX_PORT === 'dom';
// Extend base test with automatic logging // Extend base test with automatic logging
export const test = base.extend<{ export const test = base.extend<{
testLogger: TestLogger; testLogger: TestLogger;
}>({ }>({
page: async ({ page }, use) => {
if (IS_DOM_PORT) {
const origScreenshot = page.screenshot.bind(page);
page.screenshot = ((options?: Parameters<typeof origScreenshot>[0]) => {
if (options?.path) {
const dir = path.dirname(options.path);
// Specs write to test-results/<name>.png; redirect to test-results/dom/.
if (path.basename(dir) === 'test-results') {
options = { ...options, path: path.join(dir, 'dom', path.basename(options.path)) };
}
}
return origScreenshot(options);
}) as typeof page.screenshot;
}
await use(page);
},
testLogger: async ({ page }, use, testInfo) => { testLogger: async ({ page }, use, testInfo) => {
// Build test name from describe block + test title // Build test name from describe block + test title
const testName = testInfo.titlePath.join(' - '); const testName = testInfo.titlePath.join(' - ');
@ -35,11 +14,9 @@ export const test = base.extend<{
await use(logger); await use(logger);
// Write logs to wxwidgets/<test-file>/ directory (wxwidgets/dom/<test-file>/ for the DOM port) // Write logs to wxwidgets/<test-file>/ directory
const testFileName = getTestFileName(testInfo.file); const testFileName = getTestFileName(testInfo.file);
const logsDir = IS_DOM_PORT const logsDir = path.join(WXWIDGETS_LOGS_DIR, testFileName);
? path.join(WXWIDGETS_LOGS_DIR, 'dom', testFileName)
: path.join(WXWIDGETS_LOGS_DIR, testFileName);
writeTestLogs(testName, logger, logsDir); writeTestLogs(testName, logger, logsDir);
logger.cleanup(); logger.cleanup();
}, },

View file

@ -10,9 +10,6 @@ import {
clickTextCtrl, clickTextCtrl,
findSingleLineTextCtrl, findSingleLineTextCtrl,
findMultiLineTextCtrl, findMultiLineTextCtrl,
clickComboButton,
selectComboItem,
clickListboxItem,
clickListboxItemByIndex clickListboxItemByIndex
} from './utils/element-tracker'; } from './utils/element-tracker';
@ -44,10 +41,14 @@ function isKnownWarning(error: string): boolean {
error.includes('Debug:'); // wxWidgets debug prefix error.includes('Debug:'); // wxWidgets debug prefix
} }
// Click at specific canvas coordinates // Click at specific canvas coordinates. Uses page.mouse (no actionability
// check): in the DOM port real elements legitimately cover the canvas, and
// the click goes through the app's normal input routing either way.
async function clickCanvas(page: Page, x: number, y: number) { async function clickCanvas(page: Page, x: number, y: number) {
const canvas = page.locator(MAIN_CANVAS); const canvas = page.locator(MAIN_CANVAS);
await canvas.click({ position: { x, y } }); const box = await canvas.boundingBox();
if (!box) throw new Error('Canvas not found');
await page.mouse.click(box.x + x, box.y + y);
} }
// Drag on canvas from one point to another // Drag on canvas from one point to another
@ -183,34 +184,21 @@ test.describe('wxWidgets WASM - Diagnostics', () => {
await page.screenshot({ path: 'test-results/10-lists-clicked.png', fullPage: true }); await page.screenshot({ path: 'test-results/10-lists-clicked.png', fullPage: true });
// Test wxChoice dropdown using element tracking // Test the wxChoice dropdown. It renders as a native <select> (the
// Find the Choice dropdown by its current value ("Red") and click to open it // browser owns the popup, so it cannot be driven by coordinate
const { findAllComboButtons } = await import('./utils/element-tracker'); // clicks) — drive it through the DOM and verify the value sticks.
const comboButtons = await findAllComboButtons(page); const choice = page
const choiceCombo = comboButtons.find(c => c.label === 'Red'); .locator('select:not([multiple])')
expect(choiceCombo, 'Should find Choice dropdown with "Red" value').not.toBeNull(); .filter({ has: page.locator('option', { hasText: 'Red' }) })
.first();
expect(await choice.count(), 'Choice <select> should exist').toBe(1);
// Open the dropdown await choice.selectOption({ label: 'Green' });
await page.mouse.click(choiceCombo!.centerX, choiceCombo!.centerY);
await page.waitForTimeout(300);
// Select "Green" from the dropdown
const greenClicked = await clickListboxItem(page, 'Green');
expect(greenClicked, 'Should be able to click "Green" item').toBe(true);
await page.waitForTimeout(300); await page.waitForTimeout(300);
await page.screenshot({ path: 'test-results/10b-choice-selected.png', fullPage: true }); await page.screenshot({ path: 'test-results/10b-choice-selected.png', fullPage: true });
// Open the dropdown again to verify it works expect(await choice.inputValue(), 'Choice should now be Green').toBe('Green');
const reopened = await clickComboButton(page);
expect(reopened, 'Should be able to open dropdown').toBe(true);
await page.waitForTimeout(500);
await page.screenshot({ path: 'test-results/10c-choice-dropdown-reopen.png', fullPage: true });
// Close the dropdown by pressing Escape
await page.keyboard.press('Escape');
await page.waitForTimeout(300);
// === Menu interaction === // === Menu interaction ===
// Click File menu using element registry // Click File menu using element registry

View file

@ -13,7 +13,7 @@ CXX = em++
# Paths relative to this Makefile # Paths relative to this Makefile
PROJECT_ROOT = ../../.. PROJECT_ROOT = ../../..
KICAD_ROOT = $(PROJECT_ROOT)/kicad KICAD_ROOT = $(PROJECT_ROOT)/kicad
WX_BUILD = $(PROJECT_ROOT)/build-wasm/wxwidgets-universal WX_BUILD = $(PROJECT_ROOT)/build-wasm/wxwidgets
TOOLS_ROOT = $(PROJECT_ROOT)/wxwidgets/build/wasm TOOLS_ROOT = $(PROJECT_ROOT)/wxwidgets/build/wasm
# Output directory # Output directory
@ -85,7 +85,9 @@ LEGACY_GL_STUBS = --js-library=$(MAKEFILE_DIR)legacy_gl_stubs.js
LDFLAGS = $(DEBUG_LDFLAGS) $(BASE_LDFLAGS) $(EM_GL_FLAGS) $(LEGACY_GL_STUBS) $(WX_LDFLAGS) LDFLAGS = $(DEBUG_LDFLAGS) $(BASE_LDFLAGS) $(EM_GL_FLAGS) $(LEGACY_GL_STUBS) $(WX_LDFLAGS)
# Templates # Templates
JS = $(TOOLS_ROOT)/wx.js # wx-dom.js rides as a second pre-js ("--pre-js A --pre-js B" after
# expansion) — the WASM port's DOM layer is required at runtime.
JS = $(TOOLS_ROOT)/wx.js --pre-js $(TOOLS_ROOT)/wx-dom.js
HTML_TEMPLATE = $(TOOLS_ROOT)/template.html HTML_TEMPLATE = $(TOOLS_ROOT)/template.html
# Source files # Source files

View file

@ -107,13 +107,11 @@ test.describe('Appearance panel (Layers/Objects/Nets)', () => {
await selectTab(page, 'Layers'); await selectTab(page, 'Layers');
await page.screenshot({ path: 'test-results/appearance-03-layers-again.png' }); await page.screenshot({ path: 'test-results/appearance-03-layers-again.png' });
// DOM port: layer rows must survive the tab round-trip (regression: // Layer rows must survive the tab round-trip (regression:
// pages came back blank after switching away and back) // pages came back blank after switching away and back)
if (await page.evaluate(() => !!window.wxDomPort)) { const tops = await rowLabelTops(page, ['F.Cu', 'B.Cu']);
const tops = await rowLabelTops(page, ['F.Cu', 'B.Cu']); expect(tops['F.Cu'], 'F.Cu row visible after tab round-trip').not.toBeNull();
expect(tops['F.Cu'], 'F.Cu row visible after tab round-trip').not.toBeNull(); expect(tops['B.Cu'], 'B.Cu row visible after tab round-trip').not.toBeNull();
expect(tops['B.Cu'], 'B.Cu row visible after tab round-trip').not.toBeNull();
}
}); });
test('layer list scrolls with the wheel and clips at the pane', async ({ page }) => { test('layer list scrolls with the wheel and clips at the pane', async ({ page }) => {
@ -123,37 +121,31 @@ test.describe('Appearance panel (Layers/Objects/Nets)', () => {
const layersTab = tabs.find(t => t.label === 'Layers'); const layersTab = tabs.find(t => t.label === 'Layers');
expect(layersTab).toBeTruthy(); expect(layersTab).toBeTruthy();
const isDom = await page.evaluate(() => !!window.wxDomPort);
// hover INSIDE the layer list (just below the tab strip) // hover INSIDE the layer list (just below the tab strip)
const hoverX = layersTab!.centerX; const hoverX = layersTab!.centerX;
const hoverY = layersTab!.centerY + 120; const hoverY = layersTab!.centerY + 120;
await page.mouse.move(hoverX, hoverY); await page.mouse.move(hoverX, hoverY);
const before = isDom ? await rowLabelTops(page, ['B.Cu', 'F.Mask']) : null; const before = await rowLabelTops(page, ['B.Cu', 'F.Mask']);
await page.mouse.wheel(0, 240); await page.mouse.wheel(0, 240);
await page.waitForTimeout(800); await page.waitForTimeout(800);
await page.screenshot({ path: 'test-results/appearance-10-layers-scrolled.png' }); await page.screenshot({ path: 'test-results/appearance-10-layers-scrolled.png' });
if (isDom && before) { const after = await rowLabelTops(page, ['B.Cu', 'F.Mask']);
const after = await rowLabelTops(page, ['B.Cu', 'F.Mask']); expect(after['B.Cu'], 'B.Cu moved up after wheel scroll')
expect(after['B.Cu'], 'B.Cu moved up after wheel scroll') .toBeLessThan(before['B.Cu']!);
.toBeLessThan(before['B.Cu']!); expect(after['F.Mask'], 'F.Mask moved up after wheel scroll')
expect(after['F.Mask'], 'F.Mask moved up after wheel scroll') .toBeLessThan(before['F.Mask']!);
.toBeLessThan(before['F.Mask']!);
}
// scroll back up restores the start of the list // scroll back up restores the start of the list
await page.mouse.wheel(0, -480); await page.mouse.wheel(0, -480);
await page.waitForTimeout(800); await page.waitForTimeout(800);
await page.screenshot({ path: 'test-results/appearance-11-layers-scrolled-back.png' }); await page.screenshot({ path: 'test-results/appearance-11-layers-scrolled-back.png' });
if (isDom && before) { const restored = await rowLabelTops(page, ['B.Cu']);
const restored = await rowLabelTops(page, ['B.Cu']); expect(restored['B.Cu'], 'B.Cu back at its original position')
expect(restored['B.Cu'], 'B.Cu back at its original position') .toBe(before['B.Cu']);
.toBe(before['B.Cu']);
}
}); });
test('objects page scrolls with the wheel', async ({ page }) => { test('objects page scrolls with the wheel', async ({ page }) => {
@ -163,17 +155,16 @@ test.describe('Appearance panel (Layers/Objects/Nets)', () => {
const tabs = await appearanceTabs(page); const tabs = await appearanceTabs(page);
const objectsTab = tabs.find(t => t.label === 'Objects')!; const objectsTab = tabs.find(t => t.label === 'Objects')!;
const isDom = await page.evaluate(() => !!window.wxDomPort);
await page.mouse.move(objectsTab.centerX, objectsTab.centerY + 120); await page.mouse.move(objectsTab.centerX, objectsTab.centerY + 120);
const before = isDom ? await rowLabelTops(page, ['Ratsnest']) : null; const before = await rowLabelTops(page, ['Ratsnest']);
await page.mouse.wheel(0, 240); await page.mouse.wheel(0, 240);
await page.waitForTimeout(800); await page.waitForTimeout(800);
await page.screenshot({ path: 'test-results/appearance-20-objects-scrolled.png' }); await page.screenshot({ path: 'test-results/appearance-20-objects-scrolled.png' });
if (isDom && before && before['Ratsnest'] !== null) { if (before['Ratsnest'] !== null) {
const after = await rowLabelTops(page, ['Ratsnest']); const after = await rowLabelTops(page, ['Ratsnest']);
expect(after['Ratsnest'], 'Ratsnest row moved after wheel scroll') expect(after['Ratsnest'], 'Ratsnest row moved after wheel scroll')
.toBeLessThan(before['Ratsnest']!); .toBeLessThan(before['Ratsnest']!);

View file

@ -9,7 +9,6 @@
"build-wasm": "cd apps && make -f Makefile.wasm", "build-wasm": "cd apps && make -f Makefile.wasm",
"build:collab": "node collab/build.mjs", "build:collab": "node collab/build.mjs",
"serve": "npx serve apps -p 8080 -c ../serve.json", "serve": "npx serve apps -p 8080 -c ../serve.json",
"serve:dom": "npx serve apps-dom -p 8080 -c ../serve.json",
"setup:kicad": "./scripts/setup-kicad-wasm.sh", "setup:kicad": "./scripts/setup-kicad-wasm.sh",
"test:web": "npm run setup:kicad && playwright test --config=playwright-web.config.ts --project=firefox", "test:web": "npm run setup:kicad && playwright test --config=playwright-web.config.ts --project=firefox",
"test:web:headed": "npm run setup:kicad && playwright test --config=playwright-web.config.ts --project=chromium --headed", "test:web:headed": "npm run setup:kicad && playwright test --config=playwright-web.config.ts --project=chromium --headed",

View file

@ -74,9 +74,7 @@ const PCBNEW_FAMILY_SPECS = [
'**/load-pcb-probe.spec.ts', '**/load-pcb-probe.spec.ts',
]; ];
// WX_PORT=dom runs against the DOM-port bundles (setup-kicad-wasm.sh syncs const appsDir = 'apps';
// them into tests/apps-dom/kicad).
const appsDir = process.env.WX_PORT === 'dom' ? 'apps-dom' : 'apps';
export default defineConfig({ export default defineConfig({
globalSetup: './global-setup.ts', globalSetup: './global-setup.ts',

View file

@ -62,9 +62,7 @@ function findFreePort(): number {
const port = resolvePort(); const port = resolvePort();
// WX_PORT=dom runs the suite against the DOM-port bundles (built by const appsDir = 'apps';
// scripts/build-wasm-test.sh --dom into tests/apps-dom with identical layout).
const appsDir = process.env.WX_PORT === 'dom' ? 'apps-dom' : 'apps';
export default defineConfig({ export default defineConfig({
globalSetup: './global-setup.ts', globalSetup: './global-setup.ts',

View file

@ -11,17 +11,8 @@ set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# WX_PORT=dom serves the DOM-flavor bundles (built by docker/build.sh with KICAD_TEST="$PROJECT_ROOT/tests/apps/kicad"
# WX_PORT=dom into output-dom/) from tests/apps-dom/kicad. OUTPUT_DIR="$PROJECT_ROOT/output"
if [ "${WX_PORT:-}" = "dom" ]; then
KICAD_TEST="$PROJECT_ROOT/tests/apps-dom/kicad"
OUTPUT_DIR="$PROJECT_ROOT/output/dom"
KICAD_BUILD_SUFFIX="-dom"
else
KICAD_TEST="$PROJECT_ROOT/tests/apps/kicad"
OUTPUT_DIR="$PROJECT_ROOT/output"
KICAD_BUILD_SUFFIX=""
fi
mkdir -p "$KICAD_TEST" mkdir -p "$KICAD_TEST"
@ -72,15 +63,15 @@ copy_app() {
echo "Output ${app} not found locally, trying Docker volume..." echo "Output ${app} not found locally, trying Docker volume..."
if docker compose -f "$PROJECT_ROOT/docker/docker-compose.yml" cp \ if docker compose -f "$PROJECT_ROOT/docker/docker-compose.yml" cp \
kicad-wasm-builder:/workspace/build-wasm/kicad-${app}${KICAD_BUILD_SUFFIX}/${subdir}/${app}.js "$KICAD_TEST/" 2>/dev/null \ kicad-wasm-builder:/workspace/build-wasm/kicad-${app}/${subdir}/${app}.js "$KICAD_TEST/" 2>/dev/null \
&& docker compose -f "$PROJECT_ROOT/docker/docker-compose.yml" cp \ && docker compose -f "$PROJECT_ROOT/docker/docker-compose.yml" cp \
kicad-wasm-builder:/workspace/build-wasm/kicad-${app}${KICAD_BUILD_SUFFIX}/${subdir}/${app}.wasm "$KICAD_TEST/" 2>/dev/null; then kicad-wasm-builder:/workspace/build-wasm/kicad-${app}/${subdir}/${app}.wasm "$KICAD_TEST/" 2>/dev/null; then
docker compose -f "$PROJECT_ROOT/docker/docker-compose.yml" cp \ docker compose -f "$PROJECT_ROOT/docker/docker-compose.yml" cp \
kicad-wasm-builder:/workspace/build-wasm/kicad-${app}${KICAD_BUILD_SUFFIX}/${subdir}/${app}.wasm.map "$KICAD_TEST/" 2>/dev/null || true kicad-wasm-builder:/workspace/build-wasm/kicad-${app}/${subdir}/${app}.wasm.map "$KICAD_TEST/" 2>/dev/null || true
docker compose -f "$PROJECT_ROOT/docker/docker-compose.yml" cp \ docker compose -f "$PROJECT_ROOT/docker/docker-compose.yml" cp \
kicad-wasm-builder:/workspace/build-wasm/kicad-${app}${KICAD_BUILD_SUFFIX}/${subdir}/${app}.worker.js "$KICAD_TEST/" 2>/dev/null || true kicad-wasm-builder:/workspace/build-wasm/kicad-${app}/${subdir}/${app}.worker.js "$KICAD_TEST/" 2>/dev/null || true
docker compose -f "$PROJECT_ROOT/docker/docker-compose.yml" cp \ docker compose -f "$PROJECT_ROOT/docker/docker-compose.yml" cp \
kicad-wasm-builder:/workspace/build-wasm/kicad-${app}${KICAD_BUILD_SUFFIX}/resources/images.tar.gz "$KICAD_TEST/" 2>/dev/null || true kicad-wasm-builder:/workspace/build-wasm/kicad-${app}/resources/images.tar.gz "$KICAD_TEST/" 2>/dev/null || true
return 0 return 0
fi fi
@ -107,33 +98,19 @@ if [ -f "$OUTPUT_DIR/wx.js" ]; then
smart_cp "$OUTPUT_DIR/wx.js" "$KICAD_TEST" smart_cp "$OUTPUT_DIR/wx.js" "$KICAD_TEST"
else else
if docker compose -f "$PROJECT_ROOT/docker/docker-compose.yml" cp \ if docker compose -f "$PROJECT_ROOT/docker/docker-compose.yml" cp \
kicad-wasm-builder:/workspace/build-wasm/wxwidgets/build/wasm/wx.js "$KICAD_TEST/" 2>/dev/null; then kicad-wasm-builder:/workspace/wxwidgets/build/wasm/wx.js "$KICAD_TEST/" 2>/dev/null; then
: :
else else
smart_cp "$PROJECT_ROOT/wxwidgets/build/wasm/wx.js" "$KICAD_TEST" smart_cp "$PROJECT_ROOT/wxwidgets/build/wasm/wx.js" "$KICAD_TEST"
fi fi
fi fi
if [ "${WX_PORT:-}" = "dom" ]; then # The control-layer shim loads after wx.js (the checked-in kicad pages
# DOM port: the control-layer shim loads after wx.js. # reference both via <script> tags).
if [ -f "$OUTPUT_DIR/wx-dom.js" ]; then if [ -f "$OUTPUT_DIR/wx-dom.js" ]; then
smart_cp "$OUTPUT_DIR/wx-dom.js" "$KICAD_TEST" smart_cp "$OUTPUT_DIR/wx-dom.js" "$KICAD_TEST"
else else
smart_cp "$PROJECT_ROOT/wxwidgets/build/wasm/wx-dom.js" "$KICAD_TEST" smart_cp "$PROJECT_ROOT/wxwidgets/build/wasm/wx-dom.js" "$KICAD_TEST"
fi
# The checked-in kicad pages load wx.js via a <script> tag; the DOM
# flavor needs wx-dom.js right after it. Sync the pages and inject the
# tag (idempotent).
for page in "$PROJECT_ROOT"/tests/apps/kicad/*.html; do
[ -f "$page" ] || continue
dest="$KICAD_TEST/$(basename "$page")"
cp "$page" "$dest"
if ! grep -q 'wx-dom.js' "$dest"; then
perl -i -pe 's{(<script src="wx.js"></script>)}{$1\n <script src="wx-dom.js"></script>}' "$dest"
fi
done
echo " + kicad pages with wx-dom.js injected"
fi fi
echo "KiCad WASM files synced to $KICAD_TEST" echo "KiCad WASM files synced to $KICAD_TEST"

View file

@ -71,7 +71,7 @@ if(EMSCRIPTEN AND wxWidgets_CONFIG_EXECUTABLE)
# Set the wx configuration for port detection # Set the wx configuration for port detection
# For WASM, we use our own port implementation # For WASM, we use our own port implementation
set(_wx_selected_config "wasm-wasmuniv-unicode-static-3.2" CACHE INTERNAL "") set(_wx_selected_config "wasm-unicode-static-3.2" CACHE INTERNAL "")
set(wxWidgets_FIND_STYLE "unix" CACHE INTERNAL "") set(wxWidgets_FIND_STYLE "unix" CACHE INTERNAL "")
# Set KICAD_WX_PORT to "wasm" - kiplatform will need our custom source files # Set KICAD_WX_PORT to "wasm" - kiplatform will need our custom source files

@ -1 +1 @@
Subproject commit 21f2da07f81cf7a0f00726fc1d10f7c76b6a45d2 Subproject commit 9dbacc9448ef540ed963e22b27d60c078ebaa9b2