fix(wasm): 3D-viewer toolbar clicks hijacked by hidden main-frame DOM controls — input barrier + canvas anchoring

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 <noreply@anthropic.com>
This commit is contained in:
Istvan Matejcsok 2026-08-26 13:13:57 +02:00
commit d7cb570f07
6 changed files with 380 additions and 5 deletions

View file

@ -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 $@

View file

@ -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 <select> with
// pointer-events:auto, while a secondary frame's window div is
// pointer-events:none (clicks fall through to #canvas for the C++ hit-test).
// Without an input barrier for the main window's controls, a click on the
// secondary frame's toolbar lands on the hidden <select> 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 <emscripten/emscripten.h>
#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 <select> in the wasm port) near the top-left,
// where the secondary frame will overlap it — mirrors pcbnew's
// track-width selector under the 3D viewer's toolbar. On a panel: a
// wxFrame auto-sizes a lone child to fill its client area, which
// would stretch a bare choice fullscreen.
wxPanel* panel = new wxPanel(this);
wxArrayString widths;
widths.Add("Track: use netclass width");
widths.Add("0.2 mm");
widths.Add("0.25 mm");
widths.Add("0.4 mm");
m_choice = new wxChoice(panel, ID_CHOICE_TRACK, wxPoint(10, 40),
wxSize(180, 24), widths);
m_choice->SetSelection(0);
Bind(wxEVT_CHOICE, &MainTestFrame::OnChoice, this, ID_CHOICE_TRACK);
SecondaryFrame* sec = new SecondaryFrame(this);
sec->Show(true);
#ifdef __EMSCRIPTEN__
EM_ASM({
console.log('[SECFRAME] secondary-frame test app started');
});
#endif
}
private:
void OnChoice(wxCommandEvent& evt)
{
#ifdef __EMSCRIPTEN__
EM_ASM({
console.log('[SECFRAME] choice selected ' + $0);
}, evt.GetSelection());
#else
wxUnusedVar(evt);
#endif
}
wxChoice* m_choice;
};
wxIMPLEMENT_APP(SecondaryFrameTestApp);
bool SecondaryFrameTestApp::OnInit()
{
if (!wxApp::OnInit())
return false;
MainTestFrame* frame = new MainTestFrame();
frame->Show(true);
return true;
}