brep_kernel/panic_hook.rs
1//! The wasm panic hook: a Rust panic reaches the browser console instead of
2//! the bare `RuntimeError: unreachable` wasm traps produce by default.
3//!
4//! [`set_once`] is what every wasm entry point in the family calls first —
5//! the kernel's `#[wasm_bindgen]` ABI, `brep_render::Engine::new`, the app's
6//! `start` and its history worker. It replaces the `console_error_panic_hook`
7//! crate, whose whole content this is; the kernel already links `wasm-bindgen`,
8//! so owning it costs nothing and drops a dependency from three crates.
9//!
10//! On every non-wasm target it is a no-op, deliberately — and that is a change
11//! from the crate it replaces. `console_error_panic_hook::set_once()` installed
12//! a native hook too, one that printed the panic message alone; because the ABI
13//! functions that call it also run in native tests and in `BREP_mcp`'s headless
14//! host, the first ABI call there quietly cost every later panic its backtrace
15//! and the `RUST_BACKTRACE` hint. Doing nothing leaves the standard hook in
16//! place, which prints both.
17
18#[cfg(target_arch = "wasm32")]
19mod imp {
20 use wasm_bindgen::prelude::*;
21
22 #[wasm_bindgen]
23 extern "C" {
24 #[wasm_bindgen(js_namespace = console)]
25 fn error(msg: String);
26
27 /// A JS `Error`, constructed only to read the stack it captures — wasm
28 /// frames do not appear in a Rust backtrace, but they do appear here.
29 type Error;
30
31 #[wasm_bindgen(constructor)]
32 fn new() -> Error;
33
34 #[wasm_bindgen(structural, method, getter)]
35 fn stack(error: &Error) -> String;
36 }
37
38 /// `panic!` → `console.error`, with the JS-side stack appended.
39 fn hook(info: &std::panic::PanicHookInfo<'_>) {
40 // `PanicHookInfo`'s Display is already "panicked at src/x.rs:1:2:\nmsg".
41 let mut msg = info.to_string();
42 msg.push_str("\n\nStack:\n\n");
43 msg.push_str(&Error::new().stack());
44 msg.push_str("\n\n");
45 error(msg);
46 }
47
48 pub fn set_once() {
49 use std::sync::Once;
50 static SET: Once = Once::new();
51 SET.call_once(|| std::panic::set_hook(Box::new(hook)));
52 }
53}
54
55#[cfg(not(target_arch = "wasm32"))]
56mod imp {
57 /// No-op: the standard hook already prints the panic and a backtrace.
58 pub fn set_once() {}
59}
60
61/// Install the panic hook, at most once per module instance. Cheap enough to
62/// call from every entry point, which is how it is used.
63pub fn set_once() {
64 imp::set_once();
65}