fix(editor): fatal overlay matches Firefox trap messages + taps pthread-worker errors
The v0.1.19 prod crash (console-export-2026-7-31_17-49-20.log) opened with Firefox's bare "index out of bounds" — no "RuntimeError" prefix, no "table" — which the terminal-signature regex only knew in Chrome's spelling, so the overlay this feature exists for never promoted on the very trap it was built against. Match the bare form (+ "null function or function signature", the other Firefox spelling in this family). Also wrap the Worker constructor attach-only: a pthread worker's uncaught error fires an ErrorEvent on the Worker OBJECT, never on window, so worker crashes (raytracer pool etc.) were invisible to both listeners. The editor's main()/wx run on the page thread (no PROXY_TO_PTHREAD on the link line) — the tap is defense-in-depth, not the primary fix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019SE4o46Lnq3hF574FFq8x4
This commit is contained in:
parent
579f559b6c
commit
197f317bfb
1 changed files with 31 additions and 1 deletions
|
|
@ -1185,8 +1185,12 @@ export function WasmTool({
|
||||||
// genuinely terminal signatures promote to the fatal overlay; ordinary app
|
// genuinely terminal signatures promote to the fatal overlay; ordinary app
|
||||||
// errors must not hijack a working editor.
|
// errors must not hijack a working editor.
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
// NOTE: matched against `e.error.message`, which is BARE — Firefox's first
|
||||||
|
// trap is literally "index out of bounds" (no "RuntimeError", no "table")
|
||||||
|
// and slipped through the original pattern; the v0.1.19 prod log opens with
|
||||||
|
// exactly that message.
|
||||||
const terminal = (msg: string) =>
|
const terminal = (msg: string) =>
|
||||||
/RuntimeError|\babort(ed)?\b|table index is out of bounds|indirect call signature|memory access out of bounds|unreachable executed/i.test(
|
/RuntimeError|\babort(ed)?\b|\bindex out of bounds|indirect call signature|memory access out of bounds|unreachable executed|null function or function signature/i.test(
|
||||||
msg,
|
msg,
|
||||||
);
|
);
|
||||||
const onError = (e: ErrorEvent) => {
|
const onError = (e: ErrorEvent) => {
|
||||||
|
|
@ -1203,9 +1207,35 @@ export function WasmTool({
|
||||||
append(dumpTrace());
|
append(dumpTrace());
|
||||||
setFatal(msg);
|
setFatal(msg);
|
||||||
};
|
};
|
||||||
|
// With PROXY_TO_PTHREAD, main()/wx/timers — and therefore every asyncify
|
||||||
|
// trap in this family — throw INSIDE a pthread worker. A worker's uncaught
|
||||||
|
// error fires an ErrorEvent on the Worker OBJECT, never on `window`, so the
|
||||||
|
// two listeners below can't see the very traps this overlay exists for
|
||||||
|
// (v0.1.19 prod: three "Uncaught RuntimeError"s, overlay never promoted).
|
||||||
|
// Wrap the constructor attach-only: the glue spawns all pthread workers
|
||||||
|
// from this realm, so every one gets an error tap.
|
||||||
|
const NativeWorker = window.Worker;
|
||||||
|
const onWorkerError = (e: ErrorEvent) => {
|
||||||
|
const msg = String(e.message ?? "");
|
||||||
|
if (!terminal(msg)) return;
|
||||||
|
append(`[fatal] worker error: ${msg}`);
|
||||||
|
append(dumpTrace());
|
||||||
|
setFatal(msg);
|
||||||
|
};
|
||||||
|
const PatchedWorker = function (
|
||||||
|
this: unknown,
|
||||||
|
...args: ConstructorParameters<typeof Worker>
|
||||||
|
) {
|
||||||
|
const w = new NativeWorker(...args);
|
||||||
|
w.addEventListener("error", onWorkerError);
|
||||||
|
return w;
|
||||||
|
} as unknown as typeof Worker;
|
||||||
|
PatchedWorker.prototype = NativeWorker.prototype;
|
||||||
|
window.Worker = PatchedWorker;
|
||||||
window.addEventListener("error", onError);
|
window.addEventListener("error", onError);
|
||||||
window.addEventListener("unhandledrejection", onRejection);
|
window.addEventListener("unhandledrejection", onRejection);
|
||||||
return () => {
|
return () => {
|
||||||
|
window.Worker = NativeWorker;
|
||||||
window.removeEventListener("error", onError);
|
window.removeEventListener("error", onError);
|
||||||
window.removeEventListener("unhandledrejection", onRejection);
|
window.removeEventListener("unhandledrejection", onRejection);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue