mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 05:10:03 -04:00
eframe web: detect and report panics during startup (#2992)
* Detect panics during initialization and show them to the user * PanicHandler now also logs the panics * Add example of how to call into your app from JS * Refactor: break out AppRunner and AppRunnerRef to own files * Hide AppRunner * Simplify user code * AppRunnerRef -> WebRunner * Better docs * Don't paint until first animation frame * Update multiple_apps.html * Update web demo * Cleanup and fixes * left-align panic message in html
This commit is contained in:
95
crates/eframe/src/web/panic_handler.rs
Normal file
95
crates/eframe/src/web/panic_handler.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use egui::mutex::Mutex;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Detects panics, logs them using `console.error`, and stores the panics message and callstack.
|
||||
///
|
||||
/// This lets you query `PanicHandler` for the panic message (if any) so you can show it in the HTML.
|
||||
///
|
||||
/// Chep to clone (ref-counted).
|
||||
#[derive(Clone)]
|
||||
pub struct PanicHandler(Arc<Mutex<PanicHandlerInner>>);
|
||||
|
||||
impl PanicHandler {
|
||||
/// Install a panic hook.
|
||||
pub fn install() -> Self {
|
||||
let handler = Self(Arc::new(Mutex::new(Default::default())));
|
||||
|
||||
let handler_clone = handler.clone();
|
||||
let previous_hook = std::panic::take_hook();
|
||||
std::panic::set_hook(Box::new(move |panic_info| {
|
||||
let summary = PanicSummary::new(panic_info);
|
||||
|
||||
// Log it using console.error
|
||||
error(format!(
|
||||
"{}\n\nStack:\n\n{}",
|
||||
summary.message(),
|
||||
summary.callstack()
|
||||
));
|
||||
|
||||
// Remember the summary:
|
||||
handler_clone.0.lock().summary = Some(summary);
|
||||
|
||||
// Propagate panic info to the previously registered panic hook
|
||||
previous_hook(panic_info);
|
||||
}));
|
||||
|
||||
handler
|
||||
}
|
||||
|
||||
/// Has there been a panic?
|
||||
pub fn has_panicked(&self) -> bool {
|
||||
self.0.lock().summary.is_some()
|
||||
}
|
||||
|
||||
/// What was the panic message and callstack?
|
||||
pub fn panic_summary(&self) -> Option<PanicSummary> {
|
||||
self.0.lock().summary.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct PanicHandlerInner {
|
||||
summary: Option<PanicSummary>,
|
||||
}
|
||||
|
||||
/// Contains a summary about a panics.
|
||||
///
|
||||
/// This is basically a human-readable version of [`std::panic::PanicInfo`]
|
||||
/// with an added callstack.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PanicSummary {
|
||||
message: String,
|
||||
callstack: String,
|
||||
}
|
||||
|
||||
impl PanicSummary {
|
||||
pub fn new(info: &std::panic::PanicInfo<'_>) -> Self {
|
||||
let message = info.to_string();
|
||||
let callstack = Error::new().stack();
|
||||
Self { message, callstack }
|
||||
}
|
||||
|
||||
pub fn message(&self) -> String {
|
||||
self.message.clone()
|
||||
}
|
||||
|
||||
pub fn callstack(&self) -> String {
|
||||
self.callstack.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn error(msg: String);
|
||||
|
||||
type Error;
|
||||
|
||||
#[wasm_bindgen(constructor)]
|
||||
fn new() -> Error;
|
||||
|
||||
#[wasm_bindgen(structural, method, getter)]
|
||||
fn stack(error: &Error) -> String;
|
||||
}
|
||||
Reference in New Issue
Block a user