From d7cb570f07ed2d7ce260f77c11aa3e7e950e561c Mon Sep 17 00:00:00 2001 From: Istvan Matejcsok <119620946+matejcsok-ee@users.noreply.github.com> Date: Wed, 26 Aug 2026 13:13:57 +0200 Subject: [PATCH] =?UTF-8?q?fix(wasm):=203D-viewer=20toolbar=20clicks=20hij?= =?UTF-8?q?acked=20by=20hidden=20main-frame=20DOM=20controls=20=E2=80=94?= =?UTF-8?q?=20input=20barrier=20+=20canvas=20anchoring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wxwidgets bump: main-window DOM controls join the input barrier when a secondary window overlaps them; secondary windows/GL canvases anchored to the .window.toplevel border→outline. Tests (TDD, red on the old wx.js): standalone secondary-frame app (main-frame wxChoice under a secondary wxFrame's AUI toolbar) + e2e/secondary-frame-input spec (fall-through hit-testing, click delivery, barrier follows drags), and kicad/3d-viewer-toolbar-hijack spec (viewer at top-left over pcbnew's combos — the user-reported repro). modal.spec border assertion updated to the outline ring. Co-Authored-By: Claude Fable 5 --- tests/apps/Makefile.wasm | 10 ++ .../secondary-frame/secondary-frame_test.cpp | 135 ++++++++++++++++++ tests/e2e/modal.spec.ts | 12 +- tests/e2e/secondary-frame-input.spec.ts | 119 +++++++++++++++ tests/kicad/3d-viewer-toolbar-hijack.spec.ts | 107 ++++++++++++++ wxwidgets | 2 +- 6 files changed, 380 insertions(+), 5 deletions(-) create mode 100644 tests/apps/standalone/secondary-frame/secondary-frame_test.cpp create mode 100644 tests/e2e/secondary-frame-input.spec.ts create mode 100644 tests/kicad/3d-viewer-toolbar-hijack.spec.ts diff --git a/tests/apps/Makefile.wasm b/tests/apps/Makefile.wasm index a7922ad..d49d278 100644 --- a/tests/apps/Makefile.wasm +++ b/tests/apps/Makefile.wasm @@ -175,6 +175,7 @@ all: minimal_test.html \ $(S)/layout/layout_test.html \ $(S)/aui/aui_test.html \ $(S)/toolbar/toolbar_test.html \ + $(S)/secondary-frame/secondary-frame_test.html \ $(S)/grid/grid_test.html \ $(S)/dialog/dialog_test.html \ $(S)/timer/timer_test.html \ @@ -362,6 +363,15 @@ $(S)/toolbar/toolbar_test.o: $(S)/toolbar/toolbar_test.cpp $(S)/toolbar/toolbar_test.html: $(S)/toolbar/toolbar_test.o $(WX_CORE_LIB) $(JS_FILES) $(CXX) $< $(LDFLAGS_NOGL) --pre-js $(JS) --shell-file $(HTML) -o $@ +# Secondary-frame input test (no GL) +$(S)/secondary-frame/secondary-frame_test.o: $(S)/secondary-frame/secondary-frame_test.cpp + $(CXX) -c $(CXXFLAGS) $< -o $@ + +$(S)/secondary-frame/secondary-frame_test.html: $(S)/secondary-frame/secondary-frame_test.o $(WX_CORE_LIB) $(JS_FILES) + $(CXX) $< $(LDFLAGS_NOGL) --pre-js $(JS) --shell-file $(HTML) -o $@ + +secondary-frame: $(S)/secondary-frame/secondary-frame_test.html + # Grid test (no GL) $(S)/grid/grid_test.o: $(S)/grid/grid_test.cpp $(CXX) -c $(CXXFLAGS) $< -o $@ diff --git a/tests/apps/standalone/secondary-frame/secondary-frame_test.cpp b/tests/apps/standalone/secondary-frame/secondary-frame_test.cpp new file mode 100644 index 0000000..9dc95dc --- /dev/null +++ b/tests/apps/standalone/secondary-frame/secondary-frame_test.cpp @@ -0,0 +1,135 @@ +// Secondary-frame input test - a secondary wxFrame (like KiCad's 3D viewer) +// overlapping the main frame's native DOM controls. +// +// The wasm port renders wxChoice as a real DOM underneath and pops +// its native dropdown (pcbjam: 3D viewer toolbar vs pcbnew's track-width +// selector). + +#include "wx/wxprec.h" + +#ifndef WX_PRECOMP + #include "wx/wx.h" +#endif + +#include "wx/artprov.h" +#include "wx/aui/auibar.h" + +#ifdef __EMSCRIPTEN__ +#include +#endif + +class SecondaryFrameTestApp : public wxApp +{ +public: + virtual bool OnInit() override; +}; + +enum { + ID_CHOICE_TRACK = wxID_HIGHEST + 1, + ID_TOOL_ORTHO, + ID_TOOL_LAYERS +}; + +class SecondaryFrame : public wxFrame +{ +public: + explicit SecondaryFrame(wxWindow* parent) + : wxFrame(parent, wxID_ANY, "Secondary Frame", + wxPoint(0, 10), wxSize(420, 220)) + { + wxAuiToolBar* tb = new wxAuiToolBar(this, wxID_ANY, wxDefaultPosition, + wxDefaultSize, wxAUI_TB_HORZ_LAYOUT | wxAUI_TB_HORIZONTAL); + tb->AddTool(ID_TOOL_ORTHO, "Ortho", + wxArtProvider::GetBitmap(wxART_TICK_MARK, wxART_TOOLBAR), + "Use orthographic projection", wxITEM_CHECK); + tb->AddTool(ID_TOOL_LAYERS, "Layers", + wxArtProvider::GetBitmap(wxART_LIST_VIEW, wxART_TOOLBAR), + "Show layers manager", wxITEM_CHECK); + tb->Realize(); + + wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(tb, 0, wxEXPAND); + sizer->AddStretchSpacer(1); + SetSizer(sizer); + + Bind(wxEVT_TOOL, &SecondaryFrame::OnTool, this); + } + +private: + void OnTool(wxCommandEvent& evt) + { + const char* name = evt.GetId() == ID_TOOL_ORTHO ? "Ortho" : "Layers"; +#ifdef __EMSCRIPTEN__ + EM_ASM({ + console.log('[SECFRAME] tool ' + UTF8ToString($0) + ' toggled ' + ($1 ? 'on' : 'off')); + }, name, evt.IsChecked() ? 1 : 0); +#else + wxUnusedVar(name); +#endif + } +}; + +class MainTestFrame : public wxFrame +{ +public: + MainTestFrame() + : wxFrame(nullptr, wxID_ANY, "Secondary Frame Input WASM Test", + wxDefaultPosition, wxSize(900, 600)) + { + // Native wxChoice (a DOM (pointer-events:auto) +// while a secondary frame's window div is pointer-events:none, so clicks over +// the frame are meant to fall through to #canvas for the C++ hit-test. Without +// a barrier for the main window's controls, the fall-through is intercepted by +// the hidden . DOM hit-testing at its centre must NOT surface the select. + const hit = await hitAtPage(page, toolPage.x, toolPage.y); + expect(hit, 'something must be hit-testable at the tool centre').not.toBeNull(); + expect(hit!.tag, `hit at tool centre page(${toolPage.x},${toolPage.y}) must fall through to #canvas, not the main frame's s with +// pointer-events:auto inside #main-window. Where the viewer's toolbar row +// overlaps one of those selects, the fall-through used to be intercepted by +// the hidden select — the browser popped "Track: use netclass width / Edit +// Pre-defined Sizes..." instead of pressing the viewer's button. The input +// barrier (recomputeModalBarrier in wx.js) must make the covered main-frame +// controls inert while the viewer overlaps them. +import { test, expect } from './fixtures'; +import { waitUntil, findByTooltip } from '../e2e/utils/element-tracker'; +import { waitForPcbnew } from './utils/pcbnew-ready'; +import { loadBoard, countGlCanvases, openThreeDViewer } from './utils/threed-viewer'; + +test.describe('3D viewer toolbar over pcbnew DOM controls', () => { + test.setTimeout(240000); + + test('viewer toolbar clicks are not hijacked by hidden pcbnew selects', async ({ page, testLogger }) => { + await page.goto('/kicad/pcbnew.html'); + await waitForPcbnew(page); + await loadBoard(page, testLogger); + + const glBefore = await countGlCanvases(page); + await openThreeDViewer(page, glBefore); + + // Drag the viewer to the top-left (the user-reported layout): its + // toolbar row then overlaps pcbnew's track-width/via/grid/zoom selects. + const bar = await page.evaluate(() => { + const win = [...document.querySelectorAll('#window-container .window.toplevel')] + .find((w) => getComputedStyle(w).display !== 'none' && w.getBoundingClientRect().width > 100)!; + const r = win.querySelector('.window-titlebar')!.getBoundingClientRect(); + const wr = win.getBoundingClientRect(); + return { x: r.left + 60, y: r.top + r.height / 2, winLeft: wr.left, winTop: wr.top }; + }); + await page.mouse.move(bar.x, bar.y); + await page.mouse.down(); + await page.mouse.move(bar.x - bar.winLeft, bar.y - bar.winTop + 2, { steps: 8 }); + await page.mouse.up(); + + // The viewer's own AUI toolbar is registry-tracked; wait for it. + await waitUntil(page, () => { + const r = window.wxElementRegistry; + return !!r?.findAllRendered + && r.findAllRendered({ elementType: 'tool' }) + .some((t) => t.tooltip?.includes('orthographic')); + }, '3D viewer toolbar rendered'); + + // Every centre of the VIEWER's own toolbar (the AUI toolbar that owns + // toggleOrtho, selected via parentId — the registry holds every + // frame's tools) must DOM-hit-test to #canvas: the buttons are + // canvas-painted, so anything else there (a pcbnew