diff --git a/crates/ui-egui/src/app/control.rs b/crates/ui-egui/src/app/control.rs index 525c5419c..2f608971b 100644 --- a/crates/ui-egui/src/app/control.rs +++ b/crates/ui-egui/src/app/control.rs @@ -53,6 +53,8 @@ impl LightcraftApp { if self.pending_gestures.len() >= MAX_PENDING_GESTURES { return Err(format!("{}: {MAX_PENDING_GESTURES} control gestures are already pending", method)); } + // refused at once, without waiting for a layout or taking a pending slot + crate::control::check_gesture_numbers(&method, ¶ms)?; let now = self.tick_now_ms(); self.pending_gestures.push(PendingGesture { method, diff --git a/crates/ui-egui/src/control.rs b/crates/ui-egui/src/control.rs index 25bc00812..4b704069c 100644 --- a/crates/ui-egui/src/control.rs +++ b/crates/ui-egui/src/control.rs @@ -607,8 +607,9 @@ const MAX_GESTURE_NUMBER: f64 = 1e6; /// ±[`MAX_GESTURE_NUMBER`]. A larger one turns into an infinite or NaN `f32` position or scroll /// delta: egui asserts on NaN positions, and a release build would hand them to every widget. /// `count` and `steps` are clamped where they are read instead, and `ui.zoom`'s `factor` (a -/// multiplier, not a position) is checked and clamped by its own branch. -fn check_gesture_numbers(method: &str, p: &Value) -> Result<(), String> { +/// multiplier, not a position) is checked and clamped by its own branch. Checked when a request +/// is admitted and again here, for callers that resolve a gesture directly. +pub(crate) fn check_gesture_numbers(method: &str, p: &Value) -> Result<(), String> { fn check(method: &str, key: &str, v: &Value) -> Result<(), String> { match v { Value::Number(n) if !matches!(key, "count" | "steps" | "factor") => match n.as_f64() { diff --git a/crates/ui-egui/src/tests/lifecycle_037.rs b/crates/ui-egui/src/tests/lifecycle_037.rs index eb24e9b7a..d994b9300 100644 --- a/crates/ui-egui/src/tests/lifecycle_037.rs +++ b/crates/ui-egui/src/tests/lifecycle_037.rs @@ -930,6 +930,13 @@ fn gestures_refuse_numbers_that_are_not_screen_positions() { crate::control::gesture_events(&mut app, &ctx, "ui.drag", &serde_json::json!({"x": 1.0, "toX": 999_999.0, "steps": 1e12})).unwrap(); assert!(!app.synthetic.is_empty()); + // refused when the request is admitted: no pending slot, no wait for a layout + let (tx, rx) = reply_channel(); + let e = app.admit_gesture(&ctx, "ui.click".into(), serde_json::json!({"x": 1e308}), tx).expect_err("refused at admission"); + assert!(e.contains("between"), "{e}"); + assert!(app.pending_gestures.is_empty()); + drop(rx); + // through the whole control channel, as an agent sends it: an error, not a panic let mut h = Headless::new(self::app(), [800.0, 600.0], 1.0); h.step();