Skip to main content

ferroui_browser/
browser_input_handler.rs

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
23/// What the input handler needs from the top-level it belongs to; replaced
24/// by a recorder in the tests.
25pub(crate) trait IInputTopLevel {
26    /// Calls the input callback of the top-level, if it has one.
27    fn dispatch_input(&self, args: Rc<dyn IRawInputEventArgs>);
28
29    /// The client size of the top-level: what the wheel scrolls by when
30    /// the page reports its deltas in pages.
31    fn page_size(&self) -> Size;
32}
33
34/// The calls the input handler makes into the page; replaced by a recorder
35/// in the tests.
36pub(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/// `deltaMode` of a wheel event: the deltas are pixels (the unit of every
67/// mode that is not one of the two below).
68#[cfg(test)]
69const DOM_DELTA_PIXEL: i32 = 0;
70/// `deltaMode` of a wheel event: the deltas are lines.
71const DOM_DELTA_LINE: i32 = 1;
72/// `deltaMode` of a wheel event: the deltas are pages.
73const DOM_DELTA_PAGE: i32 = 2;
74
75/// The oldest a pointer event of the page can be when it is handled; an
76/// older one has a time stamp of another origin.
77const MAX_EVENT_AGE_MILLISECONDS: f64 = 5000.0;
78
79/// The pixels the wheel delta of the framework stands for: the scroll
80/// content presenter scrolls 50 units per delta of 1, and the reference
81/// implementation of this backend divides the pixel deltas by 50.
82const PIXELS_PER_WHEEL_DELTA: f64 = 50.0;
83
84/// The lines of a wheel event that make one unit of the wheel delta of the
85/// framework.
86///
87/// A delta of 1 is one notch of a wheel: the Windows backend of the
88/// reference implementation divides the wheel message by 120
89/// (`WHEEL_DELTA`, one notch), the X11 backend reports 1 per wheel click.
90/// One notch scrolls three lines, the default of the systems that count
91/// wheel scrolling in lines (and what browsers that use this unit report
92/// per notch), so three lines are one delta.
93const LINES_PER_WHEEL_DELTA: f64 = 3.0;
94
95/// The numbers the page sends per coalesced point.
96const ITEMS_PER_POINT: usize = 6;
97
98/// Turns the events of the element of a top-level into raw input events
99/// and delivers them to the top-level.
100pub 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    /// The time stamp of the last pointer event: the time stamps of the
107    /// events of the page do not go back.
108    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    /// Creates the input handler of a top-level and subscribes to the input
120    /// events of `container`.
121    ///
122    /// # Panics
123    /// Panics when `container` or `input_element` is null or undefined.
124    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        // Input is dispatched synchronously on the UI thread: the original groups raw events
150        // (`RawEventGrouper`) only when it runs with a managed dispatcher on another thread.
151        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    /// The text input method of the top-level.
186    pub fn text_input_method(&self) -> &Rc<BrowserTextInputMethod> {
187        &self.text_input_method
188    }
189
190    /// The input pane of the top-level.
191    pub fn input_pane(&self) -> &Rc<BrowserInputPane> {
192        &self.input_pane
193    }
194
195    /// The milliseconds since the handler was created: the time stamp of
196    /// the raw input events.
197    pub fn timestamp(&self) -> u64 {
198        self.sw.elapsed().as_millis() as u64
199    }
200
201    /// The time stamp of a pointer event that the page raised `event_age`
202    /// milliseconds ago (`performance.now() - event.timeStamp`).
203    ///
204    /// Not in the original, which stamps a pointer event with the time it is
205    /// handled. The page handles input between the frames it draws: with a
206    /// frame that takes longer than the 40 ms after which the velocity
207    /// tracker takes the pointer for stopped (a page that draws on its own
208    /// thread, on a slow phone), a finger lifted while it moves was stamped
209    /// as if it had rested before, and a flick did not scroll on. The time
210    /// the browser raised the event is when the finger was there. An age
211    /// that is negative, not a number or older than a few seconds (a browser
212    /// whose time stamps have another origin) stands for an event raised now.
213    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    /// The input root the events are raised for.
227    pub(crate) fn input_root(&self) -> Option<Rc<dyn IInputRoot>> {
228        self.input_root.borrow().clone()
229    }
230
231    /// Ends the subscription to the input events of the element.
232    // Not in the original, which never unsubscribes.
233    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    /// Whether the modifiers of a key event make it a command rather than a
255    /// character: Meta, or Control without Alt.
256    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, // should be pen eraser button,
283                _ => 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, // should be pen eraser button,
298                _ => RawPointerEventType::Move,
299            },
300        }
301    }
302
303    /// The points of the numbers the page sends for the coalesced events of
304    /// a pointer move, without the last one.
305    // Differs from the original, whose loop steps the index by the numbers per point while it
306    // bounds it by the count of points and so decodes only about one point in six: here every
307    // point but the last is decoded.
308    fn decode_coalesced_events(points_props: &[f64]) -> Vec<RawPointerPoint> {
309        let points_count = points_props.len() / ITEMS_PER_POINT;
310
311        // Skip the last one, as it is already processed point.
312        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    /// The wheel delta of the framework for the deltas of a wheel event of
320    /// the page.
321    // Differs from the original, which divides by 50 whatever the unit of the deltas is.
322    fn wheel_delta(delta_x: f64, delta_y: f64, delta_mode: i32, page_size: Size) -> Vector {
323        match delta_mode {
324            // Lines are counted in notches (see `LINES_PER_WHEEL_DELTA`).
325            DOM_DELTA_LINE => Vector::new(-(delta_x / LINES_PER_WHEEL_DELTA), -(delta_y / LINES_PER_WHEEL_DELTA)),
326            // A page is the size of the view, in the pixels a delta stands for.
327            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            // Pixels, as the original; anything else a browser might invent is taken for pixels.
332            _ => 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        // The event of the page is shared with the lazily computed points, so that it can be
355        // released after the event has been processed whether or not the points were asked for.
356        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                // To minimize interop usage, we resolve all points properties in a single call.
368                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        // Release the handle of the event after processing it.
385        // The intermediate points are only expected to be accessed synchronously during event processing.
386        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    /// The wheel turned; `delta_mode` is the unit of the deltas
465    /// (`WheelEvent.deltaMode`).
466    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    /// A drag operation of the page entered, moved over, left or dropped on
489    /// the element of the top-level. `data_transfer` is the data transfer of
490    /// the event and `items` its items as readable data items.
491    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            // If a file is dropped, the storage module is needed to read it. Not awaited, as in
502            // the original.
503            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    /// See [`on_drag_event`](Self::on_drag_event): whether the event was
524    /// handled, and the effect to show when the event was raised.
525    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        // The original imports its storage module here; `on_drag_event` does, before the event
543        // is handled.
544
545        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        // Note, due to complications of JS interop, we ignore this return value.
582        // And instead assume, that event is handled for any "drop" and "drag-over" stages.
583        let handled = matches!(event_type, RawDragEventType::Drop | RawDragEventType::DragOver)
584            && drop_effect != DragDropEffects::NONE;
585        (handled, Some(drop_effect))
586    }
587
588    /// The name of drag effects as the original writes them to the page:
589    /// the name of the enumeration value in lower case, so that a
590    /// combination of effects (`"copy, move"`) is not a value the page
591    /// accepts and leaves the effect unchanged.
592    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            // The original writes the number of a value with bits it has no name for.
608            return effects.bits().to_string();
609        }
610        names.join(", ")
611    }
612
613    /// A key went down. `code` and `key` are the values of the event of the
614    /// page; a missing one (some virtual keyboards and autofill send key
615    /// events without them) is an unknown key.
616    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        // One UTF-16 unit, as the original counts.
622        // Differs from the original, which turns every such key into text: with Meta, or with
623        // Control alone, the key is a shortcut of the application, the browser or the system,
624        // not a character (Control with Alt is AltGr and does produce one). Otherwise an
625        // unhandled Ctrl+R would type "r" and, being handled as text, keep the browser from
626        // reloading.
627        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    /// A key went up; see [`on_key_down`](Self::on_key_down).
635    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        // TODO: refactor pointer devices, so we can reuse single instance here.
692        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    /// Raises a raw drag event; the effects the target accepted, or `None`
722    /// when there is no input root to raise it for.
723    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        // The original hands the event to its event grouper when it runs with a managed
786        // dispatcher; here there is none and the event is dispatched right away.
787        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    /// Records the events the handler dispatches, as the top-level would
845    /// receive them.
846    struct TestTopLevel {
847        events: RefCell<Vec<Rc<dyn IRawInputEventArgs>>>,
848        /// The points the events carried, read while the event was being dispatched.
849        points: RefCell<Vec<Option<Vec<RawPointerPoint>>>>,
850        handle: Cell<bool>,
851        read_points: Cell<bool>,
852        /// The effects a drop target accepts, set on the drag events.
853        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        /// A handler without an input root.
943        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    // --- what does not need a handler -------------------------------------
999
1000    #[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, //
1090            3.0, 4.0, 0.2, 20.0, 21.0, 22.0, //
1091            5.0, 6.0, 0.3, 30.0, 31.0, 32.0, //
1092            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        // Eight points: a loop that steps by the numbers per point while it counts points
1110        // would decode two of the seven.
1111        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        // Numbers that do not make a whole point are ignored.
1131        assert!(BrowserInputHandler::decode_coalesced_events(&[1.0, 2.0, 0.5]).is_empty());
1132        // Two points and a number left over: the first point.
1133        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    // --- the handler ------------------------------------------------------
1184
1185    #[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        // An unknown kind of pointer is a mouse.
1243        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        // A cancelled touch is raised, and still reported as not handled.
1296        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        // The event of the page was released: the page is not asked.
1358        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        // A page is the size the top-level reports.
1387        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    /// The device of the event at `index` is the wheel device of the handler.
1394    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    /// A text input client that only records its preedit text.
1403    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        // Not handled: the key and then its text.
1488        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        // A named key, and a character outside the basic plane (two UTF-16 units), are no text.
1495        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        // A key up is never text.
1500        assert!(!fixture.handler.on_key_up(Some("KeyA"), Some("a"), 0));
1501        assert_eq!(5, fixture.event_count());
1502
1503        // Handled: the key only.
1504        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        // Control or Meta: the key only.
1522        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        // Shift, Alt (Option on a Mac) and AltGr (Control with Alt) produce characters.
1528        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        // Without a client the text input method ignores the composition.
1564        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        // A composition that ends without text raises nothing.
1583        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        // An event raised 100 ms before it is handled.
1608        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        // An age that is not one (another origin, not a number) stands for now; and the time
1614        // stamps do not go back.
1615        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    // --- drag and drop ------------------------------------------------------
1632
1633    /// The drag-and-drop device of the application, bound for the duration
1634    /// of a test.
1635    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}