show panics in the toast in wasm (#4582)

This commit is contained in:
Jonathan Kelley authored and GitHub committed 2025-08-19 17:13:13 -07:00
1 parent 165447a647
commit 74163984be
7 files changed
+68 -12

No files matched your search

+63 -5
View File
@@ -3,12 +3,12 @@
//! This sets up a websocket connection to the devserver and handles messages from it.
//! We also set up a little recursive timer that will attempt to reconnect if the connection is lost.
use std::fmt::Display;
use std::time::Duration;
use dioxus_devtools::{DevserverMsg, HotReloadMsg};
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use js_sys::JsString;
use std::fmt::Display;
use std::time::Duration;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen::{closure::Closure, JsValue};
use web_sys::{window, CloseEvent, MessageEvent, WebSocket};
@@ -21,15 +21,23 @@ const POLL_INTERVAL_SCALE_FACTOR: i32 = 2;
const TOAST_TIMEOUT: Duration = Duration::from_secs(5);
const TOAST_TIMEOUT_LONG: Duration = Duration::from_secs(3600); // Duration::MAX is too long for JS.
pub(crate) fn init() -> UnboundedReceiver<HotReloadMsg> {
pub(crate) fn init(config: &crate::Config) -> UnboundedReceiver<HotReloadMsg> {
// Create the tx/rx pair that we'll use for the top-level future in the dioxus loop
let (tx, rx) = unbounded();
// Wire up the websocket to the devserver
make_ws(tx.clone(), POLL_INTERVAL_MIN, false);
// Set up the playground
playground(tx);
// Set up the panic hook
if config.panic_hook {
std::panic::set_hook(Box::new(|info| {
hook_impl(info);
}));
}
rx
}
@@ -223,7 +231,7 @@ pub(crate) fn show_toast(
_ = js_sys::eval(&format!(
r#"
if (typeof {js_fn_name} !== "undefined") {{
window.{js_fn_name}("{header_text}", "{message}", "{level}", {as_ms});
window.{js_fn_name}(`{header_text}`, `{message}`, `{level}`, {as_ms});
}}
"#,
));
@@ -289,3 +297,53 @@ fn playground(tx: UnboundedSender<HotReloadMsg>) {
binding.forget();
}
fn hook_impl(info: &std::panic::PanicHookInfo) {
#[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;
}
let mut msg = info.to_string();
// Add the error stack to our message.
//
// This ensures that even if the `console` implementation doesn't
// include stacks for `console.error`, the stack is still available
// for the user. Additionally, Firefox's console tries to clean up
// stack traces, and ruins Rust symbols in the process
// (https://bugzilla.mozilla.org/show_bug.cgi?id=1519569) but since
// it only touches the logged message's associated stack, and not
// the message's contents, by including the stack in the message
// contents we make sure it is available to the user.
msg.push_str("\n\nStack:\n\n");
let e = Error::new();
let stack = e.stack();
msg.push_str(&stack);
// Safari's devtools, on the other hand, _do_ mess with logged
// messages' contents, so we attempt to break their heuristics for
// doing that by appending some whitespace.
// https://github.com/rustwasm/console_error_panic_hook/issues/7
msg.push_str("\n\n");
// Log the panic with `console.error`!
error(msg.clone());
show_toast(
"App panicked! See console for details.",
&msg,
ToastLevel::Error,
TOAST_TIMEOUT_LONG,
false,
)
}