1use crate::browser_data_transfer_helper::{IReadableDataItems, JsReadableDataItems};
2use crate::browser_drag_data_transfer::BrowserDragDataTransfer;
3use crate::browser_input_pane::BrowserInputPane;
4use crate::browser_mouse_device::{BrowserMouseDevice, ContainerPointerCapture, IPointerCapture};
5use crate::browser_text_input_method::BrowserTextInputMethod;
6use crate::interop::{ferro_module, input_helper, JsObject};
7use crate::key_interop;
8use crate::windowing_platform::BrowserWindowingPlatform;
9use ferroui_base::input::raw::{
10 IDragDropDevice, IRawInputEventArgs, RawDragEvent, RawDragEventType, RawKeyEventArgs, RawKeyEventType, RawMouseWheelEventArgs, RawPointerEventArgs,
11 RawPointerEventType, RawPointerPoint, RawTextInputEventArgs, RawTouchEventArgs,
12};
13use ferroui_base::input::{
14 DragDropEffects, IDataTransfer, IInputDevice, IInputRoot, IntermediatePoints, KeyDeviceType, MouseDevice, PenDevice, RawInputModifiers,
15 TouchDevice,
16};
17use ferroui_base::threading::Dispatcher;
18use ferroui_base::{FerroLocator, LocatorExtensions, Point, Size, Vector};
19use std::cell::{Cell, LazyCell, RefCell};
20use std::rc::{Rc, Weak};
21use std::time::Instant;
22
23pub(crate) trait IInputTopLevel {
26 fn dispatch_input(&self, args: Rc<dyn IRawInputEventArgs>);
28
29 fn page_size(&self) -> Size;
32}
33
34pub(crate) trait IInputPage {
37 fn subscribe_input_events(&self, top_level_id: i32);
38 fn unsubscribe_input_events(&self);
39 fn get_coalesced_events(&self, pointer_event: &JsObject) -> Vec<f64>;
40}
41
42struct InputPage {
43 container: JsObject,
44 subscription: RefCell<Option<JsObject>>,
45}
46
47impl IInputPage for InputPage {
48 fn subscribe_input_events(&self, top_level_id: i32) {
49 let subscription = input_helper::subscribe_input_events(&self.container, top_level_id);
50 let old = self.subscription.replace(Some(subscription));
51 drop(old);
52 }
53
54 fn unsubscribe_input_events(&self) {
55 let subscription = self.subscription.borrow_mut().take();
56 if let Some(subscription) = subscription {
57 input_helper::unsubscribe_input_events(&subscription);
58 }
59 }
60
61 fn get_coalesced_events(&self, pointer_event: &JsObject) -> Vec<f64> {
62 input_helper::get_coalesced_events(pointer_event)
63 }
64}
65
66#[cfg(test)]
69const DOM_DELTA_PIXEL: i32 = 0;
70const DOM_DELTA_LINE: i32 = 1;
72const DOM_DELTA_PAGE: i32 = 2;
74
75const MAX_EVENT_AGE_MILLISECONDS: f64 = 5000.0;
78
79const PIXELS_PER_WHEEL_DELTA: f64 = 50.0;
83
84const LINES_PER_WHEEL_DELTA: f64 = 3.0;
94
95const ITEMS_PER_POINT: usize = 6;
97
98pub struct BrowserInputHandler {
101 weak_self: Weak<BrowserInputHandler>,
102 top_level_impl: Weak<dyn IInputTopLevel>,
103 page: Box<dyn IInputPage>,
104 container: Rc<dyn IPointerCapture>,
105 sw: Instant,
106 last_pointer_timestamp: Cell<u64>,
109 touch_device: Rc<TouchDevice>,
110 pen_device: Rc<PenDevice>,
111 wheel_mouse_device: Rc<MouseDevice>,
112 mouse_devices: RefCell<Vec<BrowserMouseDevice>>,
113 input_root: RefCell<Option<Rc<dyn IInputRoot>>>,
114 text_input_method: Rc<BrowserTextInputMethod>,
115 input_pane: Rc<BrowserInputPane>,
116}
117
118impl BrowserInputHandler {
119 pub(crate) fn new(
125 top_level_impl: Weak<dyn IInputTopLevel>,
126 container: JsObject,
127 input_element: JsObject,
128 top_level_id: i32,
129 ) -> Rc<Self> {
130 if container.is_null() || container.is_undefined() {
131 panic!("Value cannot be null. (Parameter 'container')");
132 }
133
134 let page = InputPage { container: container.clone(), subscription: RefCell::new(None) };
135 let capture = Rc::new(ContainerPointerCapture::new(container.clone()));
136
137 Self::create(top_level_impl, Box::new(page), capture, top_level_id, move |this| {
138 BrowserTextInputMethod::new(this, container, input_element)
139 })
140 }
141
142 fn create(
143 top_level_impl: Weak<dyn IInputTopLevel>,
144 page: Box<dyn IInputPage>,
145 container: Rc<dyn IPointerCapture>,
146 top_level_id: i32,
147 text_input_method: impl FnOnce(Weak<BrowserInputHandler>) -> Rc<BrowserTextInputMethod>,
148 ) -> Rc<Self> {
149 let this = Rc::new_cyclic(|weak_self: &Weak<BrowserInputHandler>| Self {
152 weak_self: weak_self.clone(),
153 top_level_impl,
154 page,
155 container,
156 sw: Instant::now(),
157 last_pointer_timestamp: Cell::new(0),
158 touch_device: TouchDevice::new(),
159 pen_device: PenDevice::new(false),
160 wheel_mouse_device: MouseDevice::new(),
161 mouse_devices: RefCell::new(Vec::new()),
162 input_root: RefCell::new(None),
163 text_input_method: text_input_method(weak_self.clone()),
164 input_pane: BrowserInputPane::new(),
165 });
166
167 this.page.subscribe_input_events(top_level_id);
168
169 this
170 }
171
172 #[cfg(test)]
173 fn with_page(
174 top_level_impl: Weak<dyn IInputTopLevel>,
175 page: Box<dyn IInputPage>,
176 container: Rc<dyn IPointerCapture>,
177 text_input_page: Box<dyn crate::browser_text_input_method::ITextInputPage>,
178 top_level_id: i32,
179 ) -> Rc<Self> {
180 Self::create(top_level_impl, page, container, top_level_id, move |this| {
181 BrowserTextInputMethod::with_page(this, text_input_page)
182 })
183 }
184
185 pub fn text_input_method(&self) -> &Rc<BrowserTextInputMethod> {
187 &self.text_input_method
188 }
189
190 pub fn input_pane(&self) -> &Rc<BrowserInputPane> {
192 &self.input_pane
193 }
194
195 pub fn timestamp(&self) -> u64 {
198 self.sw.elapsed().as_millis() as u64
199 }
200
201 fn event_timestamp(&self, event_age: f64) -> u64 {
214 let now = self.sw.elapsed().as_secs_f64() * 1000.0;
215 let age = if (0.0..=MAX_EVENT_AGE_MILLISECONDS).contains(&event_age) { event_age } else { 0.0 };
216 let timestamp = ((now - age).max(0.0) as u64).max(self.last_pointer_timestamp.get());
217 self.last_pointer_timestamp.set(timestamp);
218 timestamp
219 }
220
221 pub(crate) fn set_input_root(&self, input_root: Rc<dyn IInputRoot>) {
222 let old = self.input_root.replace(Some(input_root));
223 drop(old);
224 }
225
226 pub(crate) fn input_root(&self) -> Option<Rc<dyn IInputRoot>> {
228 self.input_root.borrow().clone()
229 }
230
231 pub(crate) fn dispose(&self) {
234 self.page.unsubscribe_input_events();
235 }
236
237 fn create_raw_pointer(
238 offset_x: f64,
239 offset_y: f64,
240 pressure: f64,
241 tilt_x: f64,
242 tilt_y: f64,
243 twist: f64,
244 ) -> RawPointerPoint {
245 let mut point = RawPointerPoint::new();
246 point.position = Point::new(offset_x, offset_y);
247 point.pressure = pressure as f32;
248 point.x_tilt = tilt_x as f32;
249 point.y_tilt = tilt_y as f32;
250 point.twist = twist as f32;
251 point
252 }
253
254 fn is_command_chord(modifier: i32) -> bool {
257 let modifiers = Self::to_raw_input_modifiers(modifier);
258 modifiers.contains(RawInputModifiers::META)
259 || (modifiers.contains(RawInputModifiers::CONTROL) && !modifiers.contains(RawInputModifiers::ALT))
260 }
261
262 fn to_raw_input_modifiers(modifier: i32) -> RawInputModifiers {
263 RawInputModifiers::from_bits_retain(modifier)
264 }
265
266 fn pointer_move_type(pointer_type: &str) -> RawPointerEventType {
267 match pointer_type {
268 "touch" => RawPointerEventType::TouchUpdate,
269 _ => RawPointerEventType::Move,
270 }
271 }
272
273 fn pointer_down_type(pointer_type: &str, buttons: i32) -> RawPointerEventType {
274 match pointer_type {
275 "touch" => RawPointerEventType::TouchBegin,
276 _ => match buttons {
277 0 => RawPointerEventType::LeftButtonDown,
278 1 => RawPointerEventType::MiddleButtonDown,
279 2 => RawPointerEventType::RightButtonDown,
280 3 => RawPointerEventType::XButton1Down,
281 4 => RawPointerEventType::XButton2Down,
282 5 => RawPointerEventType::XButton1Down, _ => RawPointerEventType::Move,
284 },
285 }
286 }
287
288 fn pointer_up_type(pointer_type: &str, buttons: i32) -> RawPointerEventType {
289 match pointer_type {
290 "touch" => RawPointerEventType::TouchEnd,
291 _ => match buttons {
292 0 => RawPointerEventType::LeftButtonUp,
293 1 => RawPointerEventType::MiddleButtonUp,
294 2 => RawPointerEventType::RightButtonUp,
295 3 => RawPointerEventType::XButton1Up,
296 4 => RawPointerEventType::XButton2Up,
297 5 => RawPointerEventType::XButton1Up, _ => RawPointerEventType::Move,
299 },
300 }
301 }
302
303 fn decode_coalesced_events(points_props: &[f64]) -> Vec<RawPointerPoint> {
309 let points_count = points_props.len() / ITEMS_PER_POINT;
310
311 points_props
313 .chunks_exact(ITEMS_PER_POINT)
314 .take(points_count.saturating_sub(1))
315 .map(|props| Self::create_raw_pointer(props[0], props[1], props[2], props[3], props[4], props[5]))
316 .collect()
317 }
318
319 fn wheel_delta(delta_x: f64, delta_y: f64, delta_mode: i32, page_size: Size) -> Vector {
323 match delta_mode {
324 DOM_DELTA_LINE => Vector::new(-(delta_x / LINES_PER_WHEEL_DELTA), -(delta_y / LINES_PER_WHEEL_DELTA)),
326 DOM_DELTA_PAGE => Vector::new(
328 -(delta_x * page_size.width / PIXELS_PER_WHEEL_DELTA),
329 -(delta_y * page_size.height / PIXELS_PER_WHEEL_DELTA),
330 ),
331 _ => Vector::new(-(delta_x / PIXELS_PER_WHEEL_DELTA), -(delta_y / PIXELS_PER_WHEEL_DELTA)),
333 }
334 }
335
336 #[allow(clippy::too_many_arguments)]
337 pub fn on_pointer_move(
338 &self,
339 pointer_type: &str,
340 pointer_id: i64,
341 offset_x: f64,
342 offset_y: f64,
343 pressure: f64,
344 tilt_x: f64,
345 tilt_y: f64,
346 twist: f64,
347 modifier: i32,
348 event_age: f64,
349 args_obj: JsObject,
350 ) -> bool {
351 let point = Self::create_raw_pointer(offset_x, offset_y, pressure, tilt_x, tilt_y, twist);
352 let type_ = Self::pointer_move_type(pointer_type);
353
354 let args_obj = Rc::new(Cell::new(Some(args_obj)));
357
358 let coalesced_events: IntermediatePoints = {
359 let args_obj = args_obj.clone();
360 let this = self.weak_self.clone();
361 let get_points: Box<dyn FnOnce() -> Option<Vec<RawPointerPoint>>> = Box::new(move || {
362 let args_obj = args_obj.take();
363 let (Some(this), Some(args_obj)) = (this.upgrade(), args_obj) else {
364 return Some(Vec::new());
365 };
366
367 let points_props = this.page.get_coalesced_events(&args_obj);
369 Some(Self::decode_coalesced_events(&points_props))
370 });
371 Rc::new(LazyCell::new(get_points))
372 };
373
374 let handled = self.raw_pointer_event(
375 type_,
376 pointer_type,
377 point,
378 Self::to_raw_input_modifiers(modifier),
379 pointer_id,
380 Some(coalesced_events),
381 event_age,
382 );
383
384 let args_obj = args_obj.take();
387 drop(args_obj);
388
389 handled
390 }
391
392 #[allow(clippy::too_many_arguments)]
393 pub fn on_pointer_down(
394 &self,
395 pointer_type: &str,
396 pointer_id: i64,
397 buttons: i32,
398 offset_x: f64,
399 offset_y: f64,
400 pressure: f64,
401 tilt_x: f64,
402 tilt_y: f64,
403 twist: f64,
404 modifier: i32,
405 event_age: f64,
406 ) -> bool {
407 let type_ = Self::pointer_down_type(pointer_type, buttons);
408
409 let point = Self::create_raw_pointer(offset_x, offset_y, pressure, tilt_x, tilt_y, twist);
410 self.raw_pointer_event(type_, pointer_type, point, Self::to_raw_input_modifiers(modifier), pointer_id, None, event_age)
411 }
412
413 #[allow(clippy::too_many_arguments)]
414 pub fn on_pointer_up(
415 &self,
416 pointer_type: &str,
417 pointer_id: i64,
418 buttons: i32,
419 offset_x: f64,
420 offset_y: f64,
421 pressure: f64,
422 tilt_x: f64,
423 tilt_y: f64,
424 twist: f64,
425 modifier: i32,
426 event_age: f64,
427 ) -> bool {
428 let type_ = Self::pointer_up_type(pointer_type, buttons);
429
430 let point = Self::create_raw_pointer(offset_x, offset_y, pressure, tilt_x, tilt_y, twist);
431 self.raw_pointer_event(type_, pointer_type, point, Self::to_raw_input_modifiers(modifier), pointer_id, None, event_age)
432 }
433
434 #[allow(clippy::too_many_arguments)]
435 pub fn on_pointer_cancel(
436 &self,
437 pointer_type: &str,
438 pointer_id: i64,
439 offset_x: f64,
440 offset_y: f64,
441 pressure: f64,
442 tilt_x: f64,
443 tilt_y: f64,
444 twist: f64,
445 modifier: i32,
446 event_age: f64,
447 ) -> bool {
448 if pointer_type == "touch" {
449 let point = Self::create_raw_pointer(offset_x, offset_y, pressure, tilt_x, tilt_y, twist);
450 self.raw_pointer_event(
451 RawPointerEventType::TouchCancel,
452 pointer_type,
453 point,
454 Self::to_raw_input_modifiers(modifier),
455 pointer_id,
456 None,
457 event_age,
458 );
459 }
460
461 false
462 }
463
464 pub fn on_wheel(
467 &self,
468 offset_x: f64,
469 offset_y: f64,
470 delta_x: f64,
471 delta_y: f64,
472 delta_mode: i32,
473 modifier: i32,
474 ) -> bool {
475 let page_size = if delta_mode == DOM_DELTA_PAGE {
476 self.top_level_impl.upgrade().map_or(Size::new(0.0, 0.0), |top_level| top_level.page_size())
477 } else {
478 Size::new(0.0, 0.0)
479 };
480
481 self.raw_mouse_wheel_event(
482 Point::new(offset_x, offset_y),
483 Self::wheel_delta(delta_x, delta_y, delta_mode, page_size),
484 Self::to_raw_input_modifiers(modifier),
485 )
486 }
487
488 pub fn on_drag_event(
492 &self,
493 type_: &str,
494 offset_x: f64,
495 offset_y: f64,
496 modifiers: i32,
497 data_transfer: &JsObject,
498 items: JsObject,
499 ) -> bool {
500 if matches!(type_, "dragenter" | "dragover" | "dragleave" | "drop") {
501 Dispatcher::ui_thread().to_task_scheduler().start_local(async {
504 let _ = ferro_module::import_storage().await;
505 });
506 }
507
508 let effect_allowed = input_helper::get_effect_allowed(data_transfer);
509 let (handled, drop_effect) = self.on_drag_event_core(
510 type_,
511 offset_x,
512 offset_y,
513 modifiers,
514 effect_allowed.as_deref().unwrap_or("none"),
515 JsReadableDataItems::new(items),
516 );
517 if let Some(drop_effect) = drop_effect {
518 input_helper::set_drop_effect(data_transfer, &Self::drop_effect_name(drop_effect));
519 }
520 handled
521 }
522
523 pub(crate) fn on_drag_event_core(
526 &self,
527 type_: &str,
528 offset_x: f64,
529 offset_y: f64,
530 modifiers: i32,
531 effect_allowed_str: &str,
532 items: Rc<dyn IReadableDataItems>,
533 ) -> (bool, Option<DragDropEffects>) {
534 let event_type = match type_ {
535 "dragenter" => RawDragEventType::DragEnter,
536 "dragover" => RawDragEventType::DragOver,
537 "dragleave" => RawDragEventType::DragLeave,
538 "drop" => RawDragEventType::Drop,
539 _ => return (false, None),
540 };
541
542 let position = Point::new(offset_x, offset_y);
546
547 let effect_allowed_str = effect_allowed_str.to_ascii_lowercase();
548 let mut effect_allowed = DragDropEffects::NONE;
549
550 if effect_allowed_str.contains("copy") {
551 effect_allowed |= DragDropEffects::COPY;
552 }
553
554 if effect_allowed_str.contains("link") {
555 effect_allowed |= DragDropEffects::LINK;
556 }
557
558 if effect_allowed_str.contains("move") {
559 effect_allowed |= DragDropEffects::MOVE;
560 }
561
562 if effect_allowed_str == "all" || effect_allowed_str == "uninitialized" {
563 effect_allowed |= DragDropEffects::MOVE | DragDropEffects::COPY | DragDropEffects::LINK;
564 }
565
566 if effect_allowed == DragDropEffects::NONE {
567 return (false, None);
568 }
569
570 let data_transfer: Rc<dyn IDataTransfer> = BrowserDragDataTransfer::new(items);
571 let Some(drop_effect) = self.raw_drag_event(
572 event_type,
573 position,
574 Self::to_raw_input_modifiers(modifiers),
575 data_transfer,
576 effect_allowed,
577 ) else {
578 return (false, None);
579 };
580
581 let handled = matches!(event_type, RawDragEventType::Drop | RawDragEventType::DragOver)
584 && drop_effect != DragDropEffects::NONE;
585 (handled, Some(drop_effect))
586 }
587
588 fn drop_effect_name(effects: DragDropEffects) -> String {
593 if effects == DragDropEffects::NONE {
594 return "none".to_string();
595 }
596
597 let mut names = Vec::new();
598 for (flag, name) in
599 [(DragDropEffects::COPY, "copy"), (DragDropEffects::MOVE, "move"), (DragDropEffects::LINK, "link")]
600 {
601 if effects.contains(flag) {
602 names.push(name.to_string());
603 }
604 }
605 let unknown = effects.bits() & !(DragDropEffects::COPY | DragDropEffects::MOVE | DragDropEffects::LINK).bits();
606 if unknown != 0 {
607 return effects.bits().to_string();
609 }
610 names.join(", ")
611 }
612
613 pub fn on_key_down(&self, code: Option<&str>, key: Option<&str>, modifier: i32) -> bool {
617 let (code, key) = (code.unwrap_or_default(), key.unwrap_or_default());
618 let mut handled =
619 self.raw_keyboard_event(RawKeyEventType::KeyDown, code, key, Self::to_raw_input_modifiers(modifier));
620
621 if !handled && key.encode_utf16().count() == 1 && !Self::is_command_chord(modifier) {
628 handled = self.raw_text_event(key);
629 }
630
631 handled
632 }
633
634 pub fn on_key_up(&self, code: Option<&str>, key: Option<&str>, modifier: i32) -> bool {
636 let (code, key) = (code.unwrap_or_default(), key.unwrap_or_default());
637 self.raw_keyboard_event(RawKeyEventType::KeyUp, code, key, Self::to_raw_input_modifiers(modifier))
638 }
639
640 fn raw_pointer_event(
641 &self,
642 event_type: RawPointerEventType,
643 pointer_type: &str,
644 p: RawPointerPoint,
645 modifiers: RawInputModifiers,
646 touch_point_id: i64,
647 intermediate_points: Option<IntermediatePoints>,
648 event_age: f64,
649 ) -> bool {
650 if let Some(input_root) = self.input_root() {
651 let device = self.get_pointer_device(pointer_type, touch_point_id);
652 let timestamp = self.event_timestamp(event_age);
653
654 if device.as_any().is::<TouchDevice>() {
655 let args = Rc::new(RawTouchEventArgs::with_point(
656 device,
657 timestamp,
658 input_root,
659 event_type,
660 p,
661 modifiers,
662 touch_point_id,
663 ));
664 args.set_intermediate_points(intermediate_points);
665
666 self.schedule_input(args.clone());
667
668 return args.handled();
669 }
670
671 let args =
672 Rc::new(RawPointerEventArgs::with_point(device, timestamp, input_root, event_type, p, modifiers));
673 args.set_raw_pointer_id(touch_point_id);
674 args.set_intermediate_points(intermediate_points);
675
676 self.schedule_input(args.clone());
677
678 return args.handled();
679 }
680
681 false
682 }
683
684 fn get_pointer_device(&self, pointer_type: &str, pointer_id: i64) -> Rc<dyn IInputDevice> {
685 if pointer_type == "touch" {
686 return self.touch_device.clone();
687 } else if pointer_type == "pen" {
688 return self.pen_device.clone();
689 }
690
691 let existing = self
693 .mouse_devices
694 .borrow()
695 .iter()
696 .find(|mouse_device| mouse_device.pointer_id() == pointer_id)
697 .map(|mouse_device| mouse_device.device().clone());
698 if let Some(mouse_device) = existing {
699 return mouse_device;
700 }
701
702 let new_mouse_device = BrowserMouseDevice::new(pointer_id, self.container.clone());
703 let device = new_mouse_device.device().clone();
704 self.mouse_devices.borrow_mut().push(new_mouse_device);
705 device
706 }
707
708 fn raw_mouse_wheel_event(&self, p: Point, v: Vector, modifiers: RawInputModifiers) -> bool {
709 if let Some(input_root) = self.input_root() {
710 let device: Rc<dyn IInputDevice> = self.wheel_mouse_device.clone();
711 let args = Rc::new(RawMouseWheelEventArgs::new(device, self.timestamp(), input_root, p, v, modifiers));
712
713 self.schedule_input(args.clone());
714
715 return args.handled();
716 }
717
718 false
719 }
720
721 fn raw_drag_event(
724 &self,
725 event_type: RawDragEventType,
726 position: Point,
727 modifiers: RawInputModifiers,
728 data_transfer: Rc<dyn IDataTransfer>,
729 drop_effect: DragDropEffects,
730 ) -> Option<DragDropEffects> {
731 let device = FerroLocator::current().get_required_service::<dyn IDragDropDevice>();
732 let input_root = self.input_root()?;
733 let event_args =
734 Rc::new(RawDragEvent::new(device, event_type, input_root, position, data_transfer, drop_effect, modifiers));
735 self.schedule_input(event_args.clone());
736 Some(event_args.effects())
737 }
738
739 fn raw_keyboard_event(
740 &self,
741 type_: RawKeyEventType,
742 dom_code: &str,
743 dom_key: &str,
744 modifiers: RawInputModifiers,
745 ) -> bool {
746 let Some(input_root) = self.input_root() else {
747 return false;
748 };
749
750 let physical_key = key_interop::physical_key_from_dom_code(Some(dom_code));
751 let key = key_interop::key_from_dom_key(Some(dom_key), physical_key);
752 let key_symbol = key_interop::key_symbol_from_dom_key(Some(dom_key));
753
754 let device: Rc<dyn IInputDevice> = BrowserWindowingPlatform::keyboard();
755 let args = Rc::new(RawKeyEventArgs::new(
756 device,
757 self.timestamp(),
758 input_root,
759 type_,
760 key,
761 modifiers,
762 physical_key,
763 key_symbol,
764 KeyDeviceType::Keyboard,
765 ));
766
767 self.schedule_input(args.clone());
768
769 args.handled()
770 }
771
772 pub(crate) fn raw_text_event(&self, text: &str) -> bool {
773 if let Some(input_root) = self.input_root() {
774 let device: Rc<dyn IInputDevice> = BrowserWindowingPlatform::keyboard();
775 let args = Rc::new(RawTextInputEventArgs::new(device, self.timestamp(), input_root, text));
776 self.schedule_input(args.clone());
777
778 return args.handled();
779 }
780
781 false
782 }
783
784 fn schedule_input(&self, args: Rc<dyn IRawInputEventArgs>) {
785 self.dispatch_input(args);
788 }
789
790 fn dispatch_input(&self, args: Rc<dyn IRawInputEventArgs>) {
791 if self.input_root.borrow().is_none() {
792 return;
793 }
794
795 if let Some(top_level_impl) = self.top_level_impl.upgrade() {
796 top_level_impl.dispatch_input(args);
797 }
798 }
799}
800
801#[cfg(test)]
802mod tests {
803 use super::*;
804 use crate::browser_text_input_method::ITextInputPage;
805 use ferroui_base::input::text_input::{
806 ITextInputMethodImpl, TextInputMethodClient, TextInputMethodClientEvents, TextSelection,
807 };
808 use ferroui_base::input::{FocusManager, InputElement, Key, KeyboardDevice, PhysicalKey};
809 use ferroui_base::threading::Dispatcher;
810 use ferroui_base::{Rect, Ref, Visual};
811
812 struct TestRoot {
813 element: Ref<InputElement>,
814 }
815
816 impl IInputRoot for TestRoot {
817 fn focus_manager(&self) -> Option<Rc<FocusManager>> {
818 None
819 }
820
821 fn pointer_over_element(&self) -> Option<Ref<InputElement>> {
822 None
823 }
824
825 fn set_pointer_over_element(&self, _value: Option<Ref<InputElement>>) {}
826
827 fn cursor_element(&self) -> Option<Ref<InputElement>> {
828 None
829 }
830
831 fn set_cursor_element(&self, _value: Option<Ref<InputElement>>) {}
832
833 fn root_element(&self) -> Ref<InputElement> {
834 self.element.clone()
835 }
836
837 fn focus_root(&self) -> Ref<InputElement> {
838 self.element.clone()
839 }
840
841 fn pointer_over_invalidated(&self) {}
842 }
843
844 struct TestTopLevel {
847 events: RefCell<Vec<Rc<dyn IRawInputEventArgs>>>,
848 points: RefCell<Vec<Option<Vec<RawPointerPoint>>>>,
850 handle: Cell<bool>,
851 read_points: Cell<bool>,
852 drag_effects: Cell<Option<DragDropEffects>>,
854 page_size: Size,
855 }
856
857 impl IInputTopLevel for TestTopLevel {
858 fn dispatch_input(&self, args: Rc<dyn IRawInputEventArgs>) {
859 if self.handle.get() {
860 args.set_handled(true);
861 }
862 if let (Some(effects), Some(drag)) = (self.drag_effects.get(), args.downcast_ref::<RawDragEvent>()) {
863 drag.set_effects(effects);
864 }
865 if self.read_points.get() {
866 let points = args
867 .downcast_ref::<RawPointerEventArgs>()
868 .and_then(|args| args.intermediate_points())
869 .and_then(|points| (**points).clone());
870 self.points.borrow_mut().push(points);
871 }
872 self.events.borrow_mut().push(args);
873 }
874
875 fn page_size(&self) -> Size {
876 self.page_size
877 }
878 }
879
880 #[derive(Default)]
881 struct PageLog {
882 calls: RefCell<Vec<String>>,
883 coalesced: RefCell<Vec<f64>>,
884 }
885
886 struct RecordingPage(Rc<PageLog>);
887
888 impl IInputPage for RecordingPage {
889 fn subscribe_input_events(&self, top_level_id: i32) {
890 self.0.calls.borrow_mut().push(format!("subscribe {top_level_id}"));
891 }
892
893 fn unsubscribe_input_events(&self) {
894 self.0.calls.borrow_mut().push("unsubscribe".to_string());
895 }
896
897 fn get_coalesced_events(&self, _pointer_event: &JsObject) -> Vec<f64> {
898 self.0.calls.borrow_mut().push("coalesced".to_string());
899 self.0.coalesced.borrow().clone()
900 }
901 }
902
903 struct RecordingCapture(Rc<PageLog>);
904
905 impl IPointerCapture for RecordingCapture {
906 fn set_pointer_capture(&self, pointer_id: i64) {
907 self.0.calls.borrow_mut().push(format!("capture {pointer_id}"));
908 }
909
910 fn release_pointer_capture(&self, pointer_id: i64) {
911 self.0.calls.borrow_mut().push(format!("release {pointer_id}"));
912 }
913 }
914
915 struct SilentTextInputPage;
916
917 impl ITextInputPage for SilentTextInputPage {
918 fn hide_input_element(&self) {}
919 fn show_input_element(&self) {}
920 fn focus_input_element(&self) {}
921 fn focus_container_element(&self) {}
922 fn clear_input_element(&self) {}
923 fn set_surrounding_text(&self, _text: &str, _start: i32, _end: i32) {}
924 fn set_bounds(&self, _x: i32, _y: i32, _width: i32, _height: i32, _caret: i32) {}
925
926 fn selection_start(&self) -> i32 {
927 0
928 }
929
930 fn selection_end(&self) -> i32 {
931 0
932 }
933 }
934
935 struct Fixture {
936 handler: Rc<BrowserInputHandler>,
937 top_level: Rc<TestTopLevel>,
938 page: Rc<PageLog>,
939 }
940
941 impl Fixture {
942 fn without_root() -> Self {
944 let top_level = Rc::new(TestTopLevel {
945 events: RefCell::new(Vec::new()),
946 points: RefCell::new(Vec::new()),
947 handle: Cell::new(false),
948 read_points: Cell::new(false),
949 drag_effects: Cell::new(None),
950 page_size: Size::new(800.0, 600.0),
951 });
952 let page = Rc::new(PageLog::default());
953 let weak: Weak<TestTopLevel> = Rc::downgrade(&top_level);
954 let handler = BrowserInputHandler::with_page(
955 weak,
956 Box::new(RecordingPage(page.clone())),
957 Rc::new(RecordingCapture(page.clone())),
958 Box::new(SilentTextInputPage),
959 42,
960 );
961 Self { handler, top_level, page }
962 }
963
964 fn new() -> Self {
965 let fixture = Self::without_root();
966 fixture.handler.set_input_root(Rc::new(TestRoot { element: InputElement::new() }));
967 fixture
968 }
969
970 fn event_count(&self) -> usize {
971 self.top_level.events.borrow().len()
972 }
973
974 fn event(&self, index: usize) -> Rc<dyn IRawInputEventArgs> {
975 self.top_level.events.borrow()[index].clone()
976 }
977
978 fn pointer_event_type(&self, index: usize) -> RawPointerEventType {
979 let event = self.event(index);
980 event.downcast_ref::<RawPointerEventArgs>().expect("a pointer event").type_()
981 }
982 }
983
984 fn point(x: f64, y: f64, pressure: f32, x_tilt: f32, y_tilt: f32, twist: f32) -> RawPointerPoint {
985 let mut point = RawPointerPoint::new();
986 point.position = Point::new(x, y);
987 point.pressure = pressure;
988 point.x_tilt = x_tilt;
989 point.y_tilt = y_tilt;
990 point.twist = twist;
991 point
992 }
993
994 fn assert_close(expected: f64, actual: f64) {
995 assert!((expected - actual).abs() < 1e-9, "expected {expected}, got {actual}");
996 }
997
998 #[test]
1001 fn the_modifiers_of_the_page_are_the_raw_input_modifiers() {
1002 for (bits, expected) in [
1003 (0, RawInputModifiers::NONE),
1004 (1, RawInputModifiers::ALT),
1005 (2, RawInputModifiers::CONTROL),
1006 (4, RawInputModifiers::SHIFT),
1007 (8, RawInputModifiers::META),
1008 (16, RawInputModifiers::LEFT_MOUSE_BUTTON),
1009 (32, RawInputModifiers::RIGHT_MOUSE_BUTTON),
1010 (64, RawInputModifiers::MIDDLE_MOUSE_BUTTON),
1011 (128, RawInputModifiers::X_BUTTON_1_MOUSE_BUTTON),
1012 (256, RawInputModifiers::X_BUTTON_2_MOUSE_BUTTON),
1013 (512, RawInputModifiers::PEN_INVERTED),
1014 (1024, RawInputModifiers::PEN_ERASER),
1015 (2048, RawInputModifiers::PEN_BARREL_BUTTON),
1016 ] {
1017 assert_eq!(expected, BrowserInputHandler::to_raw_input_modifiers(bits), "{bits}");
1018 }
1019
1020 assert_eq!(
1021 RawInputModifiers::CONTROL | RawInputModifiers::SHIFT | RawInputModifiers::LEFT_MOUSE_BUTTON,
1022 BrowserInputHandler::to_raw_input_modifiers(2 | 4 | 16)
1023 );
1024 assert_eq!(RawInputModifiers::KEYBOARD_MASK, BrowserInputHandler::to_raw_input_modifiers(15));
1025 }
1026
1027 #[test]
1028 fn a_move_of_a_touch_is_a_touch_update_and_any_other_move_a_move() {
1029 assert_eq!(RawPointerEventType::TouchUpdate, BrowserInputHandler::pointer_move_type("touch"));
1030 assert_eq!(RawPointerEventType::Move, BrowserInputHandler::pointer_move_type("mouse"));
1031 assert_eq!(RawPointerEventType::Move, BrowserInputHandler::pointer_move_type("pen"));
1032 assert_eq!(RawPointerEventType::Move, BrowserInputHandler::pointer_move_type(""));
1033 }
1034
1035 #[test]
1036 fn the_button_of_a_pointer_down_selects_the_event() {
1037 for pointer_type in ["mouse", "pen", ""] {
1038 for (button, expected) in [
1039 (0, RawPointerEventType::LeftButtonDown),
1040 (1, RawPointerEventType::MiddleButtonDown),
1041 (2, RawPointerEventType::RightButtonDown),
1042 (3, RawPointerEventType::XButton1Down),
1043 (4, RawPointerEventType::XButton2Down),
1044 (5, RawPointerEventType::XButton1Down),
1045 (6, RawPointerEventType::Move),
1046 (-1, RawPointerEventType::Move),
1047 ] {
1048 assert_eq!(expected, BrowserInputHandler::pointer_down_type(pointer_type, button), "{button}");
1049 }
1050 }
1051
1052 for button in [-1, 0, 1, 2, 5] {
1053 assert_eq!(RawPointerEventType::TouchBegin, BrowserInputHandler::pointer_down_type("touch", button));
1054 }
1055 }
1056
1057 #[test]
1058 fn the_button_of_a_pointer_up_selects_the_event() {
1059 for pointer_type in ["mouse", "pen", ""] {
1060 for (button, expected) in [
1061 (0, RawPointerEventType::LeftButtonUp),
1062 (1, RawPointerEventType::MiddleButtonUp),
1063 (2, RawPointerEventType::RightButtonUp),
1064 (3, RawPointerEventType::XButton1Up),
1065 (4, RawPointerEventType::XButton2Up),
1066 (5, RawPointerEventType::XButton1Up),
1067 (6, RawPointerEventType::Move),
1068 (-1, RawPointerEventType::Move),
1069 ] {
1070 assert_eq!(expected, BrowserInputHandler::pointer_up_type(pointer_type, button), "{button}");
1071 }
1072 }
1073
1074 for button in [-1, 0, 1, 2, 5] {
1075 assert_eq!(RawPointerEventType::TouchEnd, BrowserInputHandler::pointer_up_type("touch", button));
1076 }
1077 }
1078
1079 #[test]
1080 fn a_raw_pointer_point_carries_the_position_and_the_state_of_the_pen() {
1081 let raw = BrowserInputHandler::create_raw_pointer(10.5, 20.25, 0.75, 30.0, -45.0, 90.0);
1082
1083 assert_eq!(point(10.5, 20.25, 0.75, 30.0, -45.0, 90.0), raw);
1084 }
1085
1086 #[test]
1087 fn every_coalesced_point_but_the_last_is_decoded() {
1088 let props = [
1089 1.0, 2.0, 0.1, 10.0, 11.0, 12.0, 3.0, 4.0, 0.2, 20.0, 21.0, 22.0, 5.0, 6.0, 0.3, 30.0, 31.0, 32.0, 7.0, 8.0, 0.4, 40.0, 41.0, 42.0,
1093 ];
1094
1095 let points = BrowserInputHandler::decode_coalesced_events(&props);
1096
1097 assert_eq!(
1098 vec![
1099 point(1.0, 2.0, 0.1, 10.0, 11.0, 12.0),
1100 point(3.0, 4.0, 0.2, 20.0, 21.0, 22.0),
1101 point(5.0, 6.0, 0.3, 30.0, 31.0, 32.0),
1102 ],
1103 points
1104 );
1105 }
1106
1107 #[test]
1108 fn more_points_than_numbers_per_point_are_all_decoded() {
1109 let props: Vec<f64> = (0..8 * ITEMS_PER_POINT).map(|i| i as f64).collect();
1112
1113 let points = BrowserInputHandler::decode_coalesced_events(&props);
1114
1115 assert_eq!(7, points.len());
1116 for (index, decoded) in points.iter().enumerate() {
1117 let first = (index * ITEMS_PER_POINT) as f64;
1118 assert_eq!(Point::new(first, first + 1.0), decoded.position);
1119 assert_eq!((first + 2.0) as f32, decoded.pressure);
1120 assert_eq!((first + 3.0) as f32, decoded.x_tilt);
1121 assert_eq!((first + 4.0) as f32, decoded.y_tilt);
1122 assert_eq!((first + 5.0) as f32, decoded.twist);
1123 }
1124 }
1125
1126 #[test]
1127 fn a_move_of_one_point_or_none_has_no_coalesced_points() {
1128 assert!(BrowserInputHandler::decode_coalesced_events(&[]).is_empty());
1129 assert!(BrowserInputHandler::decode_coalesced_events(&[1.0, 2.0, 0.5, 0.0, 0.0, 0.0]).is_empty());
1130 assert!(BrowserInputHandler::decode_coalesced_events(&[1.0, 2.0, 0.5]).is_empty());
1132 let props = [1.0, 2.0, 0.5, 0.0, 0.0, 0.0, 3.0, 4.0, 0.5, 0.0, 0.0, 0.0, 9.0];
1134 assert_eq!(vec![point(1.0, 2.0, 0.5, 0.0, 0.0, 0.0)], BrowserInputHandler::decode_coalesced_events(&props));
1135 }
1136
1137 #[test]
1138 fn pixels_of_the_wheel_are_divided_by_fifty_and_inverted() {
1139 let page = Size::new(800.0, 600.0);
1140
1141 let delta = BrowserInputHandler::wheel_delta(-50.0, 100.0, DOM_DELTA_PIXEL, page);
1142 assert_close(1.0, delta.x);
1143 assert_close(-2.0, delta.y);
1144
1145 let none = BrowserInputHandler::wheel_delta(0.0, 0.0, DOM_DELTA_PIXEL, page);
1146 assert_close(0.0, none.x);
1147 assert_close(0.0, none.y);
1148 }
1149
1150 #[test]
1151 fn three_lines_of_the_wheel_are_one_notch() {
1152 let page = Size::new(800.0, 600.0);
1153
1154 let delta = BrowserInputHandler::wheel_delta(-3.0, 3.0, DOM_DELTA_LINE, page);
1155
1156 assert_close(1.0, delta.x);
1157 assert_close(-1.0, delta.y);
1158
1159 let one_line = BrowserInputHandler::wheel_delta(0.0, 1.0, DOM_DELTA_LINE, page);
1160 assert_close(-1.0 / 3.0, one_line.y);
1161 }
1162
1163 #[test]
1164 fn a_page_of_the_wheel_is_the_size_of_the_view() {
1165 let page = Size::new(800.0, 600.0);
1166
1167 let delta = BrowserInputHandler::wheel_delta(1.0, -1.0, DOM_DELTA_PAGE, page);
1168
1169 assert_close(-16.0, delta.x);
1170 assert_close(12.0, delta.y);
1171 }
1172
1173 #[test]
1174 fn an_unknown_unit_of_the_wheel_is_taken_for_pixels() {
1175 let page = Size::new(800.0, 600.0);
1176
1177 let delta = BrowserInputHandler::wheel_delta(50.0, 50.0, 7, page);
1178
1179 assert_close(-1.0, delta.x);
1180 assert_close(-1.0, delta.y);
1181 }
1182
1183 #[test]
1186 fn the_handler_subscribes_to_the_events_of_its_top_level_and_unsubscribes_when_disposed() {
1187 let fixture = Fixture::without_root();
1188 assert_eq!(vec!["subscribe 42"], *fixture.page.calls.borrow());
1189
1190 fixture.handler.dispose();
1191 assert_eq!(vec!["subscribe 42", "unsubscribe"], *fixture.page.calls.borrow());
1192 }
1193
1194 #[test]
1195 fn without_an_input_root_nothing_is_dispatched() {
1196 let fixture = Fixture::without_root();
1197 fixture.top_level.handle.set(true);
1198
1199 assert!(!fixture.handler.on_pointer_down("mouse", 1, 0, 1.0, 2.0, 0.5, 0.0, 0.0, 0.0, 16, 0.0));
1200 assert!(!fixture.handler.on_pointer_up("mouse", 1, 0, 1.0, 2.0, 0.5, 0.0, 0.0, 0.0, 0, 0.0));
1201 assert!(!fixture.handler.on_pointer_move("mouse", 1, 1.0, 2.0, 0.5, 0.0, 0.0, 0.0, 0, 0.0, JsObject::UNDEFINED));
1202 assert!(!fixture.handler.on_pointer_cancel("touch", 1, 1.0, 2.0, 0.5, 0.0, 0.0, 0.0, 0, 0.0));
1203 assert!(!fixture.handler.on_wheel(1.0, 2.0, 0.0, 100.0, DOM_DELTA_PIXEL, 0));
1204 assert!(!fixture.handler.on_key_down(Some("KeyA"), Some("a"), 0));
1205 assert!(!fixture.handler.on_key_up(Some("KeyA"), Some("a"), 0));
1206 assert!(!fixture.handler.raw_text_event("a"));
1207
1208 assert_eq!(0, fixture.event_count());
1209 }
1210
1211 #[test]
1212 fn a_mouse_button_raises_a_pointer_event_of_the_mouse_device_of_its_pointer() {
1213 let _scope = Dispatcher::unit_test_scope();
1214 let fixture = Fixture::new();
1215
1216 assert!(!fixture.handler.on_pointer_down("mouse", 7, 2, 10.0, 20.0, 0.5, 1.0, 2.0, 3.0, 32 | 4, 0.0));
1217 fixture.top_level.handle.set(true);
1218 assert!(fixture.handler.on_pointer_up("mouse", 7, 2, 11.0, 21.0, 0.0, 0.0, 0.0, 0.0, 4, 0.0));
1219
1220 assert_eq!(2, fixture.event_count());
1221 let down = fixture.event(0);
1222 let down = down.downcast_ref::<RawPointerEventArgs>().expect("a pointer event");
1223 assert!(fixture.event(0).downcast_ref::<RawTouchEventArgs>().is_none());
1224 assert_eq!(RawPointerEventType::RightButtonDown, down.type_());
1225 assert_eq!(point(10.0, 20.0, 0.5, 1.0, 2.0, 3.0), down.point());
1226 assert_eq!(RawInputModifiers::RIGHT_MOUSE_BUTTON | RawInputModifiers::SHIFT, down.input_modifiers());
1227 assert_eq!(7, down.raw_pointer_id());
1228 assert!(down.intermediate_points().is_none());
1229 assert!(down.device().as_any().is::<MouseDevice>());
1230
1231 assert_eq!(RawPointerEventType::RightButtonUp, fixture.pointer_event_type(1));
1232 }
1233
1234 #[test]
1235 fn every_pointer_of_the_mouse_has_a_device_of_its_own_that_is_reused() {
1236 let _scope = Dispatcher::unit_test_scope();
1237 let fixture = Fixture::new();
1238
1239 fixture.handler.on_pointer_down("mouse", 1, 0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 16, 0.0);
1240 fixture.handler.on_pointer_up("mouse", 1, 0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0, 0.0);
1241 fixture.handler.on_pointer_down("mouse", 2, 0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 16, 0.0);
1242 fixture.handler.on_pointer_down("", 1, 0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 16, 0.0);
1244
1245 let device = |index: usize| Rc::as_ptr(fixture.event(index).device()) as *const ();
1246 assert_eq!(device(0), device(1));
1247 assert_ne!(device(0), device(2));
1248 assert_eq!(device(0), device(3));
1249 assert_eq!(2, fixture.handler.mouse_devices.borrow().len());
1250 }
1251
1252 #[test]
1253 fn a_touch_raises_touch_events_of_the_touch_device() {
1254 let _scope = Dispatcher::unit_test_scope();
1255 let fixture = Fixture::new();
1256
1257 fixture.handler.on_pointer_down("touch", 11, 0, 1.0, 2.0, 0.5, 0.0, 0.0, 0.0, 0, 0.0);
1258 fixture.handler.on_pointer_move("touch", 11, 3.0, 4.0, 0.5, 0.0, 0.0, 0.0, 0, 0.0, JsObject::UNDEFINED);
1259 fixture.handler.on_pointer_up("touch", 11, 0, 5.0, 6.0, 0.5, 0.0, 0.0, 0.0, 0, 0.0);
1260 assert!(!fixture.handler.on_pointer_cancel("touch", 12, 7.0, 8.0, 0.5, 0.0, 0.0, 0.0, 0, 0.0));
1261
1262 assert_eq!(4, fixture.event_count());
1263 for (index, expected) in [
1264 RawPointerEventType::TouchBegin,
1265 RawPointerEventType::TouchUpdate,
1266 RawPointerEventType::TouchEnd,
1267 RawPointerEventType::TouchCancel,
1268 ]
1269 .into_iter()
1270 .enumerate()
1271 {
1272 let event = fixture.event(index);
1273 let touch = event.downcast_ref::<RawTouchEventArgs>().expect("a touch event");
1274 assert_eq!(expected, touch.type_());
1275 assert!(touch.device().as_any().is::<TouchDevice>());
1276 }
1277
1278 let begin = fixture.event(0);
1279 assert_eq!(11, begin.downcast_ref::<RawPointerEventArgs>().expect("a pointer event").raw_pointer_id());
1280 let cancel = fixture.event(3);
1281 assert_eq!(12, cancel.downcast_ref::<RawPointerEventArgs>().expect("a pointer event").raw_pointer_id());
1282 assert!(fixture.handler.mouse_devices.borrow().is_empty());
1283 }
1284
1285 #[test]
1286 fn a_cancelled_pointer_that_is_not_a_touch_raises_nothing() {
1287 let _scope = Dispatcher::unit_test_scope();
1288 let fixture = Fixture::new();
1289 fixture.top_level.handle.set(true);
1290
1291 assert!(!fixture.handler.on_pointer_cancel("mouse", 1, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0, 0.0));
1292 assert!(!fixture.handler.on_pointer_cancel("pen", 1, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0, 0.0));
1293 assert_eq!(0, fixture.event_count());
1294
1295 assert!(!fixture.handler.on_pointer_cancel("touch", 1, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0, 0.0));
1297 assert_eq!(1, fixture.event_count());
1298 }
1299
1300 #[test]
1301 fn a_pen_raises_pointer_events_of_the_pen_device() {
1302 let _scope = Dispatcher::unit_test_scope();
1303 let fixture = Fixture::new();
1304
1305 fixture.handler.on_pointer_down("pen", 5, 0, 1.0, 2.0, 0.8, 10.0, 20.0, 30.0, 16, 0.0);
1306 fixture.handler.on_pointer_down("pen", 5, 5, 1.0, 2.0, 0.8, 10.0, 20.0, 30.0, 1024, 0.0);
1307
1308 let down = fixture.event(0);
1309 let down = down.downcast_ref::<RawPointerEventArgs>().expect("a pointer event");
1310 assert!(fixture.event(0).downcast_ref::<RawTouchEventArgs>().is_none());
1311 assert_eq!(RawPointerEventType::LeftButtonDown, down.type_());
1312 assert!(down.device().as_any().is::<PenDevice>());
1313 assert_eq!(5, down.raw_pointer_id());
1314 assert_eq!(point(1.0, 2.0, 0.8, 10.0, 20.0, 30.0), down.point());
1315 assert_eq!(RawPointerEventType::XButton1Down, fixture.pointer_event_type(1));
1316 assert!(fixture.handler.mouse_devices.borrow().is_empty());
1317 }
1318
1319 #[test]
1320 fn the_coalesced_points_of_a_move_are_asked_for_only_when_they_are_read() {
1321 let _scope = Dispatcher::unit_test_scope();
1322 let fixture = Fixture::new();
1323 *fixture.page.coalesced.borrow_mut() =
1324 vec![1.0, 2.0, 0.5, 0.0, 0.0, 0.0, 3.0, 4.0, 0.5, 0.0, 0.0, 0.0, 5.0, 6.0, 0.5, 0.0, 0.0, 0.0];
1325 fixture.page.calls.borrow_mut().clear();
1326
1327 fixture.handler.on_pointer_move("mouse", 1, 5.0, 6.0, 0.5, 0.0, 0.0, 0.0, 0, 0.0, JsObject::UNDEFINED);
1328 assert!(fixture.page.calls.borrow().is_empty());
1329
1330 fixture.top_level.read_points.set(true);
1331 fixture.handler.on_pointer_move("mouse", 1, 5.0, 6.0, 0.5, 0.0, 0.0, 0.0, 0, 0.0, JsObject::UNDEFINED);
1332
1333 assert_eq!(vec!["coalesced"], *fixture.page.calls.borrow());
1334 assert_eq!(
1335 vec![Some(vec![point(1.0, 2.0, 0.5, 0.0, 0.0, 0.0), point(3.0, 4.0, 0.5, 0.0, 0.0, 0.0)])],
1336 *fixture.top_level.points.borrow()
1337 );
1338 assert_eq!(RawPointerEventType::Move, fixture.pointer_event_type(1));
1339 }
1340
1341 #[test]
1342 fn the_coalesced_points_of_a_move_are_empty_once_the_move_has_been_processed() {
1343 let _scope = Dispatcher::unit_test_scope();
1344 let fixture = Fixture::new();
1345 *fixture.page.coalesced.borrow_mut() = vec![1.0, 2.0, 0.5, 0.0, 0.0, 0.0, 3.0, 4.0, 0.5, 0.0, 0.0, 0.0];
1346 fixture.page.calls.borrow_mut().clear();
1347
1348 fixture.handler.on_pointer_move("mouse", 1, 3.0, 4.0, 0.5, 0.0, 0.0, 0.0, 0, 0.0, JsObject::UNDEFINED);
1349
1350 let event = fixture.event(0);
1351 let points = event
1352 .downcast_ref::<RawPointerEventArgs>()
1353 .expect("a pointer event")
1354 .intermediate_points()
1355 .expect("a move carries its lazily computed points");
1356 assert_eq!(Some(Vec::new()), (**points).clone());
1357 assert!(fixture.page.calls.borrow().is_empty());
1359 }
1360
1361 #[test]
1362 fn the_wheel_raises_a_wheel_event_of_the_wheel_device() {
1363 let _scope = Dispatcher::unit_test_scope();
1364 let fixture = Fixture::new();
1365
1366 assert!(!fixture.handler.on_wheel(10.0, 20.0, 0.0, 100.0, DOM_DELTA_PIXEL, 2));
1367 fixture.handler.on_wheel(10.0, 20.0, 0.0, 3.0, DOM_DELTA_LINE, 0);
1368 fixture.handler.on_wheel(10.0, 20.0, 1.0, 1.0, DOM_DELTA_PAGE, 0);
1369 fixture.top_level.handle.set(true);
1370 assert!(fixture.handler.on_wheel(10.0, 20.0, 0.0, 100.0, DOM_DELTA_PIXEL, 0));
1371
1372 let event = fixture.event(0);
1373 let wheel = event.downcast_ref::<RawMouseWheelEventArgs>().expect("a wheel event");
1374 assert_eq!(RawPointerEventType::Wheel, wheel.type_());
1375 assert_eq!(Point::new(10.0, 20.0), wheel.position());
1376 assert_close(0.0, wheel.delta().x);
1377 assert_close(-2.0, wheel.delta().y);
1378 assert_eq!(RawInputModifiers::CONTROL, wheel.input_modifiers());
1379 assert_wheel_device(&fixture, 0);
1380 assert_wheel_device(&fixture, 3);
1381 assert!(fixture.handler.mouse_devices.borrow().is_empty());
1382
1383 let lines = fixture.event(1);
1384 assert_close(-1.0, lines.downcast_ref::<RawMouseWheelEventArgs>().expect("a wheel event").delta().y);
1385
1386 let pages = fixture.event(2);
1388 let pages = pages.downcast_ref::<RawMouseWheelEventArgs>().expect("a wheel event").delta();
1389 assert_close(-16.0, pages.x);
1390 assert_close(-12.0, pages.y);
1391 }
1392
1393 fn assert_wheel_device(fixture: &Fixture, index: usize) {
1395 let event = fixture.event(index);
1396 assert_eq!(
1397 Rc::as_ptr(event.device()) as *const (),
1398 Rc::as_ptr(&fixture.handler.wheel_mouse_device) as *const ()
1399 );
1400 }
1401
1402 struct TestClient {
1404 events: TextInputMethodClientEvents,
1405 preedit: RefCell<Vec<Option<String>>>,
1406 }
1407
1408 impl TextInputMethodClient for TestClient {
1409 fn events(&self) -> &TextInputMethodClientEvents {
1410 &self.events
1411 }
1412
1413 fn text_view_visual(&self) -> Option<Ref<Visual>> {
1414 None
1415 }
1416
1417 fn supports_preedit(&self) -> bool {
1418 true
1419 }
1420
1421 fn supports_surrounding_text(&self) -> bool {
1422 true
1423 }
1424
1425 fn surrounding_text(&self) -> String {
1426 String::new()
1427 }
1428
1429 fn cursor_rectangle(&self) -> Rect {
1430 Rect::default()
1431 }
1432
1433 fn selection(&self) -> TextSelection {
1434 TextSelection::new(0, 0)
1435 }
1436
1437 fn set_selection(&self, _value: TextSelection) {}
1438
1439 fn set_preedit_text(&self, preedit_text: Option<&str>) {
1440 self.preedit.borrow_mut().push(preedit_text.map(str::to_string));
1441 }
1442 }
1443
1444 fn with_keyboard() -> Rc<KeyboardDevice> {
1445 let keyboard = KeyboardDevice::new();
1446 BrowserWindowingPlatform::set_keyboard_for_unit_tests(Some(keyboard.clone()));
1447 keyboard
1448 }
1449
1450 #[test]
1451 fn a_key_raises_a_key_event_of_the_keyboard_of_the_platform() {
1452 let _scope = Dispatcher::unit_test_scope();
1453 let keyboard = with_keyboard();
1454 let fixture = Fixture::new();
1455 fixture.top_level.handle.set(true);
1456
1457 assert!(fixture.handler.on_key_down(Some("ShiftRight"), Some("Shift"), 4));
1458 assert!(fixture.handler.on_key_up(Some("Numpad1"), Some("1"), 0));
1459
1460 assert_eq!(2, fixture.event_count());
1461 let down = fixture.event(0);
1462 let down = down.downcast_ref::<RawKeyEventArgs>().expect("a key event");
1463 assert_eq!(RawKeyEventType::KeyDown, down.type_());
1464 assert_eq!(Key::RightShift, down.key());
1465 assert_eq!(PhysicalKey::ShiftRight, down.physical_key());
1466 assert_eq!(None, down.key_symbol());
1467 assert_eq!(RawInputModifiers::SHIFT, down.modifiers());
1468 assert_eq!(KeyDeviceType::Keyboard, down.key_device_type());
1469 assert_eq!(Rc::as_ptr(down.device()) as *const (), Rc::as_ptr(&keyboard) as *const ());
1470
1471 let up = fixture.event(1);
1472 let up = up.downcast_ref::<RawKeyEventArgs>().expect("a key event");
1473 assert_eq!(RawKeyEventType::KeyUp, up.type_());
1474 assert_eq!(Key::NumPad1, up.key());
1475 assert_eq!(PhysicalKey::NumPad1, up.physical_key());
1476 assert_eq!(Some("1".to_string()), up.key_symbol());
1477
1478 BrowserWindowingPlatform::set_keyboard_for_unit_tests(None);
1479 }
1480
1481 #[test]
1482 fn a_key_down_of_one_character_that_is_not_handled_becomes_text() {
1483 let _scope = Dispatcher::unit_test_scope();
1484 let _keyboard = with_keyboard();
1485 let fixture = Fixture::new();
1486
1487 assert!(!fixture.handler.on_key_down(Some("KeyA"), Some("a"), 0));
1489 assert_eq!(2, fixture.event_count());
1490 assert!(fixture.event(0).downcast_ref::<RawKeyEventArgs>().is_some());
1491 let text = fixture.event(1);
1492 assert_eq!("a", text.downcast_ref::<RawTextInputEventArgs>().expect("a text event").text());
1493
1494 assert!(!fixture.handler.on_key_down(Some("Enter"), Some("Enter"), 0));
1496 assert!(!fixture.handler.on_key_down(Some("KeyA"), Some("\u{1f600}"), 0));
1497 assert_eq!(4, fixture.event_count());
1498
1499 assert!(!fixture.handler.on_key_up(Some("KeyA"), Some("a"), 0));
1501 assert_eq!(5, fixture.event_count());
1502
1503 fixture.top_level.handle.set(true);
1505 assert!(fixture.handler.on_key_down(Some("KeyB"), Some("b"), 0));
1506 assert_eq!(6, fixture.event_count());
1507
1508 BrowserWindowingPlatform::set_keyboard_for_unit_tests(None);
1509 }
1510
1511 #[test]
1512 fn a_character_key_with_a_command_modifier_is_not_text() {
1513 let _scope = Dispatcher::unit_test_scope();
1514 let _keyboard = with_keyboard();
1515 let fixture = Fixture::new();
1516 const ALT: i32 = 1;
1517 const CONTROL: i32 = 2;
1518 const SHIFT: i32 = 4;
1519 const META: i32 = 8;
1520
1521 assert!(!fixture.handler.on_key_down(Some("KeyR"), Some("r"), CONTROL));
1523 assert!(!fixture.handler.on_key_down(Some("KeyR"), Some("r"), META));
1524 assert!(!fixture.handler.on_key_down(Some("KeyR"), Some("R"), CONTROL | SHIFT));
1525 assert_eq!(3, fixture.event_count());
1526
1527 assert!(!fixture.handler.on_key_down(Some("KeyR"), Some("R"), SHIFT));
1529 assert!(!fixture.handler.on_key_down(Some("KeyR"), Some("\u{ae}"), ALT));
1530 assert!(!fixture.handler.on_key_down(Some("KeyQ"), Some("@"), CONTROL | ALT));
1531 assert_eq!(9, fixture.event_count());
1532 let text = fixture.event(8);
1533 assert_eq!("@", text.downcast_ref::<RawTextInputEventArgs>().expect("a text event").text());
1534
1535 BrowserWindowingPlatform::set_keyboard_for_unit_tests(None);
1536 }
1537
1538 #[test]
1539 fn a_key_event_without_code_or_key_is_an_unknown_key_and_no_text() {
1540 let _scope = Dispatcher::unit_test_scope();
1541 let _keyboard = with_keyboard();
1542 let fixture = Fixture::new();
1543
1544 assert!(!fixture.handler.on_key_down(None, None, 0));
1545 assert!(!fixture.handler.on_key_up(None, None, 0));
1546
1547 assert_eq!(2, fixture.event_count());
1548 let down = fixture.event(0);
1549 let down = down.downcast_ref::<RawKeyEventArgs>().expect("a key event");
1550 assert_eq!(Key::None, down.key());
1551 assert_eq!(PhysicalKey::None, down.physical_key());
1552 assert_eq!(None, down.key_symbol());
1553
1554 BrowserWindowingPlatform::set_keyboard_for_unit_tests(None);
1555 }
1556
1557 #[test]
1558 fn the_text_of_a_finished_composition_is_raised_as_text() {
1559 let _scope = Dispatcher::unit_test_scope();
1560 let _keyboard = with_keyboard();
1561 let fixture = Fixture::new();
1562
1563 fixture.handler.text_input_method().on_composition_end(Some("\u{304b}"));
1565 assert_eq!(0, fixture.event_count());
1566
1567 let client =
1568 Rc::new(TestClient { events: TextInputMethodClientEvents::new(), preedit: RefCell::new(Vec::new()) });
1569 let text_input_client: Rc<dyn TextInputMethodClient> = client.clone();
1570 fixture.handler.text_input_method().set_client(Some(text_input_client));
1571
1572 fixture.handler.text_input_method().on_composition_start();
1573 fixture.handler.text_input_method().on_composition_update(Some("ka"));
1574 assert_eq!(0, fixture.event_count());
1575
1576 fixture.handler.text_input_method().on_composition_end(Some("\u{304b}"));
1577 assert_eq!(1, fixture.event_count());
1578 let text = fixture.event(0);
1579 assert_eq!("\u{304b}", text.downcast_ref::<RawTextInputEventArgs>().expect("a text event").text());
1580 assert_eq!(vec![None, Some("ka".to_string()), None], *client.preedit.borrow());
1581
1582 fixture.handler.text_input_method().on_composition_end(None);
1584 assert_eq!(1, fixture.event_count());
1585
1586 BrowserWindowingPlatform::set_keyboard_for_unit_tests(None);
1587 }
1588
1589 #[test]
1590 fn the_time_stamps_of_the_events_do_not_go_back() {
1591 let _scope = Dispatcher::unit_test_scope();
1592 let fixture = Fixture::new();
1593
1594 fixture.handler.on_pointer_down("mouse", 1, 0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 16, 0.0);
1595 fixture.handler.on_pointer_up("mouse", 1, 0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0, 0.0);
1596
1597 assert!(fixture.event(0).timestamp() <= fixture.event(1).timestamp());
1598 assert!(fixture.event(1).timestamp() <= fixture.handler.timestamp());
1599 }
1600
1601 #[test]
1602 fn a_pointer_event_is_stamped_with_the_time_the_page_raised_it() {
1603 let _scope = Dispatcher::unit_test_scope();
1604 let fixture = Fixture::new();
1605 std::thread::sleep(std::time::Duration::from_millis(120));
1606
1607 fixture.handler.on_pointer_down("touch", 1, 0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0, 100.0);
1609 let now = fixture.handler.timestamp();
1610 let stamped = fixture.event(0).timestamp();
1611 assert!(stamped + 100 <= now + 1 && stamped + 100 + 50 >= now, "stamped {stamped} at {now}");
1612
1613 fixture.handler.on_pointer_move("touch", 1, 1.0, 1.0, 0.5, 0.0, 0.0, 0.0, 0, 1.0e12, JsObject::UNDEFINED);
1616 fixture.handler.on_pointer_move("touch", 1, 2.0, 2.0, 0.5, 0.0, 0.0, 0.0, 0, f64::NAN, JsObject::UNDEFINED);
1617 fixture.handler.on_pointer_up("touch", 1, 0, 2.0, 2.0, 0.5, 0.0, 0.0, 0.0, 0, 110.0);
1618 assert!(fixture.event(1).timestamp() >= now);
1619 assert!(fixture.event(2).timestamp() >= fixture.event(1).timestamp());
1620 assert_eq!(fixture.event(2).timestamp(), fixture.event(3).timestamp());
1621 }
1622
1623 #[test]
1624 fn the_handler_has_a_closed_input_pane_and_a_text_input_method_that_is_not_composing() {
1625 let fixture = Fixture::without_root();
1626
1627 assert!(!fixture.handler.text_input_method().is_composing());
1628 assert!(fixture.handler.input_pane().on_geometry_change(0.0, 0.0, 0.0, 0.0));
1629 }
1630
1631 struct DragDropScope(Rc<dyn ferroui_base::reactive::IDisposable>);
1636
1637 impl DragDropScope {
1638 fn new() -> Self {
1639 let scope = FerroLocator::enter_scope();
1640 FerroLocator::current_mutable()
1641 .bind::<dyn IDragDropDevice>()
1642 .to_constant(ferroui_base::input::DragDropDevice::instance() as Rc<dyn IDragDropDevice>);
1643 Self(scope)
1644 }
1645 }
1646
1647 impl Drop for DragDropScope {
1648 fn drop(&mut self) {
1649 self.0.dispose();
1650 }
1651 }
1652
1653 fn text_items(text: &'static str) -> Rc<dyn IReadableDataItems> {
1654 use crate::browser_data_transfer_helper::tests::{FakeReadableDataItem, FakeReadableDataItems, FakeValue};
1655 Rc::new(FakeReadableDataItems(vec![FakeReadableDataItem::new(
1656 &["text/plain"],
1657 &[("text/plain", FakeValue::String(text))],
1658 )]))
1659 }
1660
1661 fn drag_event(fixture: &Fixture, index: usize) -> Rc<dyn IRawInputEventArgs> {
1662 let event = fixture.event(index);
1663 assert!(event.downcast_ref::<RawDragEvent>().is_some(), "a drag event");
1664 event
1665 }
1666
1667 #[test]
1668 fn the_drag_events_of_the_page_are_raw_drag_events() {
1669 let _scope = Dispatcher::unit_test_scope();
1670 let _drag_drop = DragDropScope::new();
1671 let fixture = Fixture::new();
1672
1673 for (type_, expected) in [
1674 ("dragenter", RawDragEventType::DragEnter),
1675 ("dragover", RawDragEventType::DragOver),
1676 ("dragleave", RawDragEventType::DragLeave),
1677 ("drop", RawDragEventType::Drop),
1678 ] {
1679 fixture.handler.on_drag_event_core(type_, 10.0, 20.0, 2, "copy", text_items("a"));
1680 let event = drag_event(&fixture, fixture.event_count() - 1);
1681 let drag = event.downcast_ref::<RawDragEvent>().unwrap();
1682 assert_eq!(expected, drag.type_());
1683 assert_eq!(Point::new(10.0, 20.0), drag.location());
1684 assert_eq!(ferroui_base::input::KeyModifiers::CONTROL, drag.key_modifiers());
1685 }
1686 assert_eq!(4, fixture.event_count());
1687
1688 assert_eq!((false, None), fixture.handler.on_drag_event_core("drag", 0.0, 0.0, 0, "copy", text_items("a")));
1689 assert_eq!(4, fixture.event_count());
1690 }
1691
1692 #[test]
1693 fn the_allowed_effects_are_read_from_the_effect_the_page_allows() {
1694 let _scope = Dispatcher::unit_test_scope();
1695 let _drag_drop = DragDropScope::new();
1696 let fixture = Fixture::new();
1697
1698 let all = DragDropEffects::COPY | DragDropEffects::MOVE | DragDropEffects::LINK;
1699 for (effect_allowed, expected) in [
1700 ("copy", DragDropEffects::COPY),
1701 ("copyLink", DragDropEffects::COPY | DragDropEffects::LINK),
1702 ("copyMove", DragDropEffects::COPY | DragDropEffects::MOVE),
1703 ("linkMove", DragDropEffects::LINK | DragDropEffects::MOVE),
1704 ("move", DragDropEffects::MOVE),
1705 ("all", all),
1706 ("uninitialized", all),
1707 ("ALL", all),
1708 ] {
1709 let (_, effect) = fixture.handler.on_drag_event_core("dragenter", 0.0, 0.0, 0, effect_allowed, text_items("a"));
1710 assert_eq!(Some(expected), effect, "{effect_allowed}");
1711 }
1712
1713 let count = fixture.event_count();
1714 assert_eq!((false, None), fixture.handler.on_drag_event_core("dragover", 0.0, 0.0, 0, "none", text_items("a")));
1715 assert_eq!(count, fixture.event_count());
1716 }
1717
1718 #[test]
1719 fn a_drag_over_or_a_drop_is_handled_when_the_target_accepts_an_effect() {
1720 let _scope = Dispatcher::unit_test_scope();
1721 let _drag_drop = DragDropScope::new();
1722 let fixture = Fixture::new();
1723
1724 fixture.top_level.drag_effects.set(Some(DragDropEffects::COPY));
1725 assert_eq!(
1726 (true, Some(DragDropEffects::COPY)),
1727 fixture.handler.on_drag_event_core("dragover", 0.0, 0.0, 0, "all", text_items("a"))
1728 );
1729 assert_eq!(
1730 (true, Some(DragDropEffects::COPY)),
1731 fixture.handler.on_drag_event_core("drop", 0.0, 0.0, 0, "all", text_items("a"))
1732 );
1733 assert_eq!(
1734 (false, Some(DragDropEffects::COPY)),
1735 fixture.handler.on_drag_event_core("dragenter", 0.0, 0.0, 0, "all", text_items("a"))
1736 );
1737
1738 fixture.top_level.drag_effects.set(Some(DragDropEffects::NONE));
1739 assert_eq!(
1740 (false, Some(DragDropEffects::NONE)),
1741 fixture.handler.on_drag_event_core("drop", 0.0, 0.0, 0, "all", text_items("a"))
1742 );
1743 }
1744
1745 #[test]
1746 fn the_dropped_text_is_read_from_the_items_of_the_page() {
1747 use ferroui_base::input::{DataFormat, DataTransferExtensions};
1748
1749 let _scope = Dispatcher::unit_test_scope();
1750 let _drag_drop = DragDropScope::new();
1751 let fixture = Fixture::new();
1752
1753 fixture.handler.on_drag_event_core("drop", 0.0, 0.0, 0, "copy", text_items("dropped"));
1754 let event = drag_event(&fixture, 0);
1755 let data_transfer = event.downcast_ref::<RawDragEvent>().unwrap().data_transfer().clone();
1756 assert!(data_transfer.formats().iter().any(|format| DataFormat::text() == *format));
1757 assert_eq!(Some("dropped".to_string()), data_transfer.try_get_text());
1758 }
1759
1760 #[test]
1761 fn without_an_input_root_a_drag_raises_nothing() {
1762 let _scope = Dispatcher::unit_test_scope();
1763 let _drag_drop = DragDropScope::new();
1764 let fixture = Fixture::without_root();
1765
1766 assert_eq!((false, None), fixture.handler.on_drag_event_core("drop", 0.0, 0.0, 0, "copy", text_items("a")));
1767 assert_eq!(0, fixture.event_count());
1768 }
1769
1770 #[test]
1771 fn the_drop_effect_is_the_lower_case_name_of_the_effects() {
1772 assert_eq!("none", BrowserInputHandler::drop_effect_name(DragDropEffects::NONE));
1773 assert_eq!("copy", BrowserInputHandler::drop_effect_name(DragDropEffects::COPY));
1774 assert_eq!("move", BrowserInputHandler::drop_effect_name(DragDropEffects::MOVE));
1775 assert_eq!("link", BrowserInputHandler::drop_effect_name(DragDropEffects::LINK));
1776 assert_eq!(
1777 "copy, move, link",
1778 BrowserInputHandler::drop_effect_name(DragDropEffects::COPY | DragDropEffects::MOVE | DragDropEffects::LINK)
1779 );
1780 assert_eq!("9", BrowserInputHandler::drop_effect_name(DragDropEffects::from_bits_retain(9)));
1781 }
1782}