fix(wasm): Fix Asyncify modal errors with global lock mechanism

Update wxwidgets submodule with fix for consecutive modal dialog crashes.
The fix prevents overlapping Asyncify operations that caused "indirect call
to null" and "func is not a function" errors when a second modal was
triggered immediately after the first one completed.

Also includes:
- docs: Clarify build script order and descriptions in CLAUDE.md
- refactor(test): Remove debug logging from wizard test

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Viktor Vaczi 2026-01-19 14:32:33 +01:00
commit 51b158f6b1
4 changed files with 45 additions and 13 deletions

View file

@ -12,8 +12,8 @@ The tests have log files in tests/logs/{wxwidgets/kicad}/{test-name} after each
Always check screenshots for validating tests
Run e2e tests from /tests folder: `npm run test:kicad` or `npm run test:e2e` (not playwright directly)
Build wxwidgets with scripts/build-wxuniversal-wasm.sh
Build kicad with docker/build.sh
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 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.
Don't pipe outputs, just run the scripts. Maybe with flex if you need that.

40
learning.md Normal file
View file

@ -0,0 +1,40 @@
# Learning Notes
## Asyncify and Consecutive Modal Dialogs
### Problem
When multiple modal dialogs are triggered in quick succession, Asyncify operations can overlap causing crashes:
- "indirect call to null"
- "func is not a function"
- "index out of bounds"
### Root Cause
Per Emscripten docs: "It is not safe to start an async operation while another is already running."
When the first modal completes:
1. Asyncify begins rewinding the C++ stack
2. C++ code triggers second modal before rewind completes
3. Second modal's Asyncify operation conflicts with first modal's cleanup
4. Asyncify state corruption occurs
### Key Insight
**You cannot use ANY Asyncify mechanism to wait** - neither `EM_ASYNC_JS` await nor `emscripten_sleep()` - while another Asyncify operation is cleaning up. Both use Asyncify internally and cause the same conflict.
### Solution Pattern
1. Use a **global lock** to track when Asyncify is busy
2. Check lock with **synchronous JS** (`EM_JS`, not `EM_ASYNC_JS`) - this doesn't use Asyncify
3. If locked, **return immediately** instead of waiting
4. Release lock via **double setTimeout(0)** to ensure Asyncify fully completes before allowing new operations
```javascript
// Good: Synchronous check (no Asyncify)
EM_JS(int, isLocked, (), { return Module._locked ? 1 : 0; });
// Bad: This uses Asyncify and will cause conflicts
while (isLocked()) {
emscripten_sleep(10); // Uses Asyncify!
}
```
### File Reference
`wxwidgets/src/wasm/dialog.cpp` - Modal implementation with lock mechanism

View file

@ -1,5 +1,5 @@
import { test, expect } from './fixtures';
import { clickByLabel, dumpElements } from '../e2e/utils/element-tracker';
import { clickByLabel } from '../e2e/utils/element-tracker';
/**
* PCBnew WASM E2E Tests
@ -20,24 +20,17 @@ test.describe('PCBnew WASM', () => {
// Click through wizard - try Next >, then Finish if not found
for (let i = 1; i <= 10; i++) {
// Dump buttons before clicking to see what's available
console.log(`Step ${i}: Looking for buttons...`);
await dumpElements(page);
// Try "Next >" first (exact button label)
let clicked = await clickByLabel(page, 'Next >');
if (clicked) {
console.log(`Step ${i}: Clicked "Next >"`);
// Continue to next page
} else {
// Try Finish button
clicked = await clickByLabel(page, 'Finish');
if (clicked) {
console.log(`Step ${i}: Clicked "Finish"`);
await page.waitForTimeout(500);
await page.screenshot({ path: `test-results/wizard-${String(i).padStart(2, '0')}-finish.png` });
break;
} else {
console.log(`Step ${i}: No "Next >" or "Finish" found`);
break;
}
}
@ -51,7 +44,6 @@ test.describe('PCBnew WASM', () => {
// Verify PCBnew loaded
const canvasCount = await page.locator('canvas').count();
console.log('Canvas count after wizard:', canvasCount);
expect(canvasCount).toBeGreaterThan(0);
});
});

@ -1 +1 @@
Subproject commit 47ebb3991d91614ca7cd6396bb6305a73d184232
Subproject commit dd370ac191d5f20ea847b1bce4a611464f5baa65