Skip to main content

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}