1use std::array::from_ref;
6use std::cell::{Cell, RefCell};
7use std::cmp::Ordering;
8use std::f64::consts::PI;
9use std::mem;
10use std::rc::Rc;
11use std::str::FromStr;
12use std::time::{Duration, Instant};
13
14use base::generic_channel::GenericCallback;
15use constellation_traits::{KeyboardScroll, ScriptToConstellationMessage};
16use embedder_traits::{
17 Cursor, EditingActionEvent, EmbedderMsg, ImeEvent, InputEvent, InputEventId, InputEventOutcome,
18 InputEventResult, KeyboardEvent as EmbedderKeyboardEvent, MouseButton, MouseButtonAction,
19 MouseButtonEvent, MouseLeftViewportEvent, TouchEvent as EmbedderTouchEvent, TouchEventType,
20 TouchId, UntrustedNodeAddress, WheelEvent as EmbedderWheelEvent,
21};
22#[cfg(feature = "gamepad")]
23use embedder_traits::{
24 GamepadEvent as EmbedderGamepadEvent, GamepadSupportedHapticEffects, GamepadUpdateType,
25};
26use euclid::{Point2D, Vector2D};
27use js::jsapi::JSAutoRealm;
28use keyboard_types::{Code, Key, KeyState, Modifiers, NamedKey};
29use layout_api::{ScrollContainerQueryFlags, node_id_from_scroll_id};
30use rustc_hash::FxHashMap;
31use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
32use script_bindings::codegen::GenericBindings::ElementBinding::ScrollLogicalPosition;
33use script_bindings::codegen::GenericBindings::EventBinding::EventMethods;
34use script_bindings::codegen::GenericBindings::HTMLElementBinding::HTMLElementMethods;
35use script_bindings::codegen::GenericBindings::HTMLLabelElementBinding::HTMLLabelElementMethods;
36use script_bindings::codegen::GenericBindings::KeyboardEventBinding::KeyboardEventMethods;
37use script_bindings::codegen::GenericBindings::NavigatorBinding::NavigatorMethods;
38use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods;
39use script_bindings::codegen::GenericBindings::TouchBinding::TouchMethods;
40use script_bindings::codegen::GenericBindings::WindowBinding::{ScrollBehavior, WindowMethods};
41use script_bindings::inheritance::Castable;
42use script_bindings::match_domstring_ascii;
43use script_bindings::num::Finite;
44use script_bindings::reflector::DomObject;
45use script_bindings::root::{Dom, DomRoot, DomSlice};
46use script_bindings::script_runtime::CanGc;
47use script_bindings::str::DOMString;
48use script_traits::ConstellationInputEvent;
49use servo_config::pref;
50use style::Atom;
51use style_traits::CSSPixel;
52use webrender_api::ExternalScrollId;
53
54use crate::dom::bindings::cell::DomRefCell;
55use crate::dom::bindings::inheritance::{ElementTypeId, HTMLElementTypeId, NodeTypeId};
56use crate::dom::bindings::refcounted::Trusted;
57use crate::dom::bindings::root::MutNullableDom;
58use crate::dom::bindings::trace::NoTrace;
59use crate::dom::clipboardevent::ClipboardEventType;
60use crate::dom::document::{FireMouseEventType, FocusInitiator};
61use crate::dom::event::{EventBubbles, EventCancelable, EventComposed, EventFlags};
62#[cfg(feature = "gamepad")]
63use crate::dom::gamepad::gamepad::{Gamepad, contains_user_gesture};
64#[cfg(feature = "gamepad")]
65use crate::dom::gamepad::gamepadevent::GamepadEventType;
66use crate::dom::inputevent::HitTestResult;
67use crate::dom::interactive_element_command::InteractiveElementCommand;
68use crate::dom::keyboardevent::KeyboardEvent;
69use crate::dom::node::{self, Node, NodeTraits, ShadowIncluding};
70use crate::dom::pointerevent::{PointerEvent, PointerId};
71use crate::dom::scrolling_box::{ScrollAxisState, ScrollRequirement, ScrollingBoxAxis};
72use crate::dom::types::{
73 ClipboardEvent, CompositionEvent, DataTransfer, Element, Event, EventTarget, GlobalScope,
74 HTMLAnchorElement, HTMLElement, HTMLLabelElement, MouseEvent, Touch, TouchEvent, TouchList,
75 WheelEvent, Window,
76};
77use crate::drag_data_store::{DragDataStore, Kind, Mode};
78use crate::realms::enter_realm;
79
80#[derive(Default, JSTraceable, MallocSizeOf)]
90struct ClickCountingInfo {
91 time: Option<Instant>,
92 #[no_trace]
93 point: Option<Point2D<f32, CSSPixel>>,
94 #[no_trace]
95 button: Option<MouseButton>,
96 count: usize,
97}
98
99impl ClickCountingInfo {
100 fn reset_click_count_if_necessary(
101 &mut self,
102 button: MouseButton,
103 point_in_frame: Point2D<f32, CSSPixel>,
104 ) {
105 let (Some(previous_button), Some(previous_point), Some(previous_time)) =
106 (self.button, self.point, self.time)
107 else {
108 assert_eq!(self.count, 0);
109 return;
110 };
111
112 let double_click_timeout =
113 Duration::from_millis(pref!(dom_document_dblclick_timeout) as u64);
114 let double_click_distance_threshold = pref!(dom_document_dblclick_dist) as u64;
115
116 let line = point_in_frame - previous_point;
118 let distance = (line.dot(line) as f64).sqrt();
119 if previous_button != button ||
120 Instant::now().duration_since(previous_time) > double_click_timeout ||
121 distance > double_click_distance_threshold as f64
122 {
123 self.count = 0;
124 self.time = None;
125 self.point = None;
126 }
127 }
128
129 fn increment_click_count(
130 &mut self,
131 button: MouseButton,
132 point: Point2D<f32, CSSPixel>,
133 ) -> usize {
134 self.time = Some(Instant::now());
135 self.point = Some(point);
136 self.button = Some(button);
137 self.count += 1;
138 self.count
139 }
140}
141
142#[derive(JSTraceable, MallocSizeOf)]
146#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
147pub(crate) struct DocumentEventHandler {
148 window: Dom<Window>,
150 #[no_trace]
152 #[ignore_malloc_size_of = "InputEvent contains data from outside crates"]
153 pending_input_events: DomRefCell<Vec<ConstellationInputEvent>>,
154 mouse_move_event_index: DomRefCell<Option<usize>>,
156 #[no_trace]
158 #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
159 coalesced_move_event_ids: DomRefCell<Vec<InputEventId>>,
160 wheel_event_index: DomRefCell<Option<usize>>,
165 #[no_trace]
167 #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
168 coalesced_wheel_event_ids: DomRefCell<Vec<InputEventId>>,
169 click_counting_info: DomRefCell<ClickCountingInfo>,
171 #[no_trace]
172 last_mouse_button_down_point: Cell<Option<Point2D<f32, CSSPixel>>>,
173 down_button_count: Cell<u32>,
176 current_hover_target: MutNullableDom<Element>,
178 most_recently_clicked_element: MutNullableDom<Element>,
180 #[no_trace]
182 most_recent_mousemove_point: Cell<Option<Point2D<f32, CSSPixel>>>,
183 #[no_trace]
186 current_cursor: Cell<Option<Cursor>>,
187 active_touch_points: DomRefCell<Vec<Dom<Touch>>>,
189 #[no_trace]
191 active_keyboard_modifiers: Cell<Modifiers>,
192 active_pointer_ids: DomRefCell<FxHashMap<i32, i32>>,
194 next_touch_pointer_id: Cell<i32>,
196 access_key_handlers: DomRefCell<FxHashMap<NoTrace<Code>, Dom<HTMLElement>>>,
198 sequential_focus_navigation_starting_point: MutNullableDom<Node>,
200}
201
202impl DocumentEventHandler {
203 pub(crate) fn new(window: &Window) -> Self {
204 Self {
205 window: Dom::from_ref(window),
206 pending_input_events: Default::default(),
207 mouse_move_event_index: Default::default(),
208 coalesced_move_event_ids: Default::default(),
209 wheel_event_index: Default::default(),
210 coalesced_wheel_event_ids: Default::default(),
211 click_counting_info: Default::default(),
212 last_mouse_button_down_point: Default::default(),
213 down_button_count: Cell::new(0),
214 current_hover_target: Default::default(),
215 most_recently_clicked_element: Default::default(),
216 most_recent_mousemove_point: Default::default(),
217 current_cursor: Default::default(),
218 active_touch_points: Default::default(),
219 active_keyboard_modifiers: Default::default(),
220 active_pointer_ids: Default::default(),
221 next_touch_pointer_id: Cell::new(1),
222 access_key_handlers: Default::default(),
223 sequential_focus_navigation_starting_point: Default::default(),
224 }
225 }
226
227 pub(crate) fn note_pending_input_event(&self, event: ConstellationInputEvent) {
229 let mut pending_input_events = self.pending_input_events.borrow_mut();
230 if matches!(event.event.event, InputEvent::MouseMove(..)) {
231 if let Some(mouse_move_event) = self
233 .mouse_move_event_index
234 .borrow()
235 .and_then(|index| pending_input_events.get_mut(index))
236 {
237 self.coalesced_move_event_ids
238 .borrow_mut()
239 .push(mouse_move_event.event.id);
240 *mouse_move_event = event;
241 return;
242 }
243
244 *self.mouse_move_event_index.borrow_mut() = Some(pending_input_events.len());
245 }
246
247 if let InputEvent::Wheel(ref new_wheel_event) = event.event.event {
248 if let Some(existing_constellation_wheel_event) = self
250 .wheel_event_index
251 .borrow()
252 .and_then(|index| pending_input_events.get_mut(index))
253 {
254 if let InputEvent::Wheel(ref mut existing_wheel_event) =
255 existing_constellation_wheel_event.event.event
256 {
257 if existing_wheel_event.delta.mode == new_wheel_event.delta.mode {
258 self.coalesced_wheel_event_ids
259 .borrow_mut()
260 .push(existing_constellation_wheel_event.event.id);
261 existing_wheel_event.delta.x += new_wheel_event.delta.x;
262 existing_wheel_event.delta.y += new_wheel_event.delta.y;
263 existing_wheel_event.delta.z += new_wheel_event.delta.z;
264 existing_wheel_event.point = new_wheel_event.point;
265 existing_constellation_wheel_event.event.id = event.event.id;
266 return;
267 }
268 }
269 }
270
271 *self.wheel_event_index.borrow_mut() = Some(pending_input_events.len());
272 }
273
274 pending_input_events.push(event);
275 }
276
277 pub(crate) fn has_pending_input_events(&self) -> bool {
280 !self.pending_input_events.borrow().is_empty()
281 }
282
283 pub(crate) fn alternate_action_keyboard_modifier_active(&self) -> bool {
284 #[cfg(target_os = "macos")]
285 {
286 self.active_keyboard_modifiers
287 .get()
288 .contains(Modifiers::META)
289 }
290 #[cfg(not(target_os = "macos"))]
291 {
292 self.active_keyboard_modifiers
293 .get()
294 .contains(Modifiers::CONTROL)
295 }
296 }
297
298 pub(crate) fn handle_pending_input_events(&self, can_gc: CanGc) {
299 debug_assert!(
300 !self.pending_input_events.borrow().is_empty(),
301 "handle_pending_input_events called with no events"
302 );
303 let _realm = enter_realm(&*self.window);
304
305 *self.mouse_move_event_index.borrow_mut() = None;
307 *self.wheel_event_index.borrow_mut() = None;
308 let pending_input_events = mem::take(&mut *self.pending_input_events.borrow_mut());
309 let mut coalesced_move_event_ids =
310 mem::take(&mut *self.coalesced_move_event_ids.borrow_mut());
311 let mut coalesced_wheel_event_ids =
312 mem::take(&mut *self.coalesced_wheel_event_ids.borrow_mut());
313
314 let mut input_event_outcomes = Vec::with_capacity(
315 pending_input_events.len() +
316 coalesced_move_event_ids.len() +
317 coalesced_wheel_event_ids.len(),
318 );
319 for event in pending_input_events {
325 self.active_keyboard_modifiers
326 .set(event.active_keyboard_modifiers);
327 let result = match event.event.event {
328 InputEvent::MouseButton(mouse_button_event) => {
329 self.handle_native_mouse_button_event(mouse_button_event, &event, can_gc);
330 InputEventResult::default()
331 },
332 InputEvent::MouseMove(_) => {
333 self.handle_native_mouse_move_event(&event, can_gc);
334 input_event_outcomes.extend(
335 mem::take(&mut coalesced_move_event_ids)
336 .into_iter()
337 .map(|id| InputEventOutcome {
338 id,
339 result: InputEventResult::default(),
340 }),
341 );
342 InputEventResult::default()
343 },
344 InputEvent::MouseLeftViewport(mouse_leave_event) => {
345 self.handle_mouse_left_viewport_event(&event, &mouse_leave_event, can_gc);
346 InputEventResult::default()
347 },
348 InputEvent::Touch(touch_event) => {
349 self.handle_touch_event(touch_event, &event, can_gc)
350 },
351 InputEvent::Wheel(wheel_event) => {
352 let result = self.handle_wheel_event(wheel_event, &event, can_gc);
353 input_event_outcomes.extend(
354 mem::take(&mut coalesced_wheel_event_ids)
355 .into_iter()
356 .map(|id| InputEventOutcome { id, result }),
357 );
358 result
359 },
360 InputEvent::Keyboard(keyboard_event) => {
361 self.handle_keyboard_event(keyboard_event, can_gc)
362 },
363 InputEvent::Ime(ime_event) => self.handle_ime_event(ime_event, can_gc),
364 #[cfg(feature = "gamepad")]
365 InputEvent::Gamepad(gamepad_event) => {
366 self.handle_gamepad_event(gamepad_event);
367 InputEventResult::default()
368 },
369 InputEvent::EditingAction(editing_action_event) => {
370 self.handle_editing_action(None, editing_action_event, can_gc)
371 },
372 };
373
374 input_event_outcomes.push(InputEventOutcome {
375 id: event.event.id,
376 result,
377 });
378 }
379
380 self.notify_embedder_that_events_were_handled(input_event_outcomes);
381 }
382
383 fn notify_embedder_that_events_were_handled(
384 &self,
385 input_event_outcomes: Vec<InputEventOutcome>,
386 ) {
387 let trusted_window = Trusted::new(&*self.window);
390 self.window
391 .as_global_scope()
392 .task_manager()
393 .dom_manipulation_task_source()
394 .queue(task!(notify_webdriver_input_event_completed: move || {
395 let window = trusted_window.root();
396 window.send_to_embedder(
397 EmbedderMsg::InputEventsHandled(window.webview_id(), input_event_outcomes));
398 }));
399 }
400
401 pub(crate) fn set_cursor(&self, cursor: Option<Cursor>) {
402 if cursor == self.current_cursor.get() {
403 return;
404 }
405 self.current_cursor.set(cursor);
406 self.window.send_to_embedder(EmbedderMsg::SetCursor(
407 self.window.webview_id(),
408 cursor.unwrap_or_default(),
409 ));
410 }
411
412 fn handle_mouse_left_viewport_event(
413 &self,
414 input_event: &ConstellationInputEvent,
415 mouse_leave_event: &MouseLeftViewportEvent,
416 can_gc: CanGc,
417 ) {
418 if let Some(current_hover_target) = self.current_hover_target.get() {
419 let current_hover_target = current_hover_target.upcast::<Node>();
420 for element in current_hover_target
421 .inclusive_ancestors(ShadowIncluding::Yes)
422 .filter_map(DomRoot::downcast::<Element>)
423 {
424 element.set_hover_state(false);
425 self.element_for_activation(element).set_active_state(false);
426 }
427
428 if let Some(hit_test_result) = self
429 .most_recent_mousemove_point
430 .get()
431 .and_then(|point| self.window.hit_test_from_point_in_viewport(point))
432 {
433 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
434 &self.window,
435 FireMouseEventType::Out,
436 &hit_test_result,
437 input_event,
438 can_gc,
439 );
440
441 mouse_out_event
443 .to_pointer_hover_event("pointerout", can_gc)
444 .upcast::<Event>()
445 .fire(current_hover_target.upcast(), can_gc);
446
447 mouse_out_event
448 .upcast::<Event>()
449 .fire(current_hover_target.upcast(), can_gc);
450
451 self.handle_mouse_enter_leave_event(
452 DomRoot::from_ref(current_hover_target),
453 None,
454 FireMouseEventType::Leave,
455 &hit_test_result,
456 input_event,
457 can_gc,
458 );
459 }
460 }
461
462 if !mouse_leave_event.focus_moving_to_another_iframe {
467 self.window
471 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
472 self.set_cursor(None);
473 } else {
474 self.current_cursor.set(None);
475 }
476
477 self.current_hover_target.set(None);
478 self.most_recent_mousemove_point.set(None);
479 }
480
481 fn handle_mouse_enter_leave_event(
482 &self,
483 event_target: DomRoot<Node>,
484 related_target: Option<DomRoot<Node>>,
485 event_type: FireMouseEventType,
486 hit_test_result: &HitTestResult,
487 input_event: &ConstellationInputEvent,
488 can_gc: CanGc,
489 ) {
490 assert!(matches!(
491 event_type,
492 FireMouseEventType::Enter | FireMouseEventType::Leave
493 ));
494
495 let common_ancestor = match related_target.as_ref() {
496 Some(related_target) => event_target
497 .common_ancestor_in_flat_tree(related_target)
498 .unwrap_or_else(|| DomRoot::from_ref(&*event_target)),
499 None => DomRoot::from_ref(&*event_target),
500 };
501
502 let mut targets = vec![];
505 let mut current = Some(event_target);
506 while let Some(node) = current {
507 if node == common_ancestor {
508 break;
509 }
510 current = node.parent_in_flat_tree();
511 targets.push(node);
512 }
513
514 if event_type == FireMouseEventType::Enter {
517 targets = targets.into_iter().rev().collect();
518 }
519
520 let pointer_event_name = match event_type {
521 FireMouseEventType::Enter => "pointerenter",
522 FireMouseEventType::Leave => "pointerleave",
523 _ => unreachable!(),
524 };
525
526 for target in targets {
527 let mouse_event = MouseEvent::new_for_platform_motion_event(
528 &self.window,
529 event_type,
530 hit_test_result,
531 input_event,
532 can_gc,
533 );
534 mouse_event
535 .upcast::<Event>()
536 .set_related_target(related_target.as_ref().map(|target| target.upcast()));
537
538 mouse_event
540 .to_pointer_hover_event(pointer_event_name, can_gc)
541 .upcast::<Event>()
542 .fire(target.upcast(), can_gc);
543
544 mouse_event.upcast::<Event>().fire(target.upcast(), can_gc);
546 }
547 }
548
549 fn handle_native_mouse_move_event(&self, input_event: &ConstellationInputEvent, can_gc: CanGc) {
551 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
553 return;
554 };
555
556 let old_mouse_move_point = self
557 .most_recent_mousemove_point
558 .replace(Some(hit_test_result.point_in_frame));
559 if old_mouse_move_point == Some(hit_test_result.point_in_frame) {
560 return;
561 }
562
563 self.set_cursor(Some(hit_test_result.cursor));
565
566 let Some(new_target) = hit_test_result
567 .node
568 .inclusive_ancestors(ShadowIncluding::Yes)
569 .find_map(DomRoot::downcast::<Element>)
570 else {
571 return;
572 };
573
574 let old_hover_target = self.current_hover_target.get();
575 let target_has_changed = old_hover_target
576 .as_ref()
577 .is_none_or(|old_target| *old_target != new_target);
578
579 if target_has_changed {
582 if let Some(old_target) = self.current_hover_target.get() {
584 let old_target_is_ancestor_of_new_target = old_target
585 .upcast::<Node>()
586 .is_ancestor_of(new_target.upcast::<Node>());
587
588 if !old_target_is_ancestor_of_new_target {
591 for element in old_target
592 .upcast::<Node>()
593 .inclusive_ancestors(ShadowIncluding::No)
594 .filter_map(DomRoot::downcast::<Element>)
595 {
596 element.set_hover_state(false);
597 self.element_for_activation(element).set_active_state(false);
598 }
599 }
600
601 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
602 &self.window,
603 FireMouseEventType::Out,
604 &hit_test_result,
605 input_event,
606 can_gc,
607 );
608 mouse_out_event
609 .upcast::<Event>()
610 .set_related_target(Some(new_target.upcast()));
611
612 mouse_out_event
614 .to_pointer_hover_event("pointerout", can_gc)
615 .upcast::<Event>()
616 .fire(old_target.upcast(), can_gc);
617
618 mouse_out_event
619 .upcast::<Event>()
620 .fire(old_target.upcast(), can_gc);
621
622 if !old_target_is_ancestor_of_new_target {
623 let event_target = DomRoot::from_ref(old_target.upcast::<Node>());
624 let moving_into = Some(DomRoot::from_ref(new_target.upcast::<Node>()));
625 self.handle_mouse_enter_leave_event(
626 event_target,
627 moving_into,
628 FireMouseEventType::Leave,
629 &hit_test_result,
630 input_event,
631 can_gc,
632 );
633 }
634 }
635
636 for element in new_target
638 .upcast::<Node>()
639 .inclusive_ancestors(ShadowIncluding::Yes)
640 .filter_map(DomRoot::downcast::<Element>)
641 {
642 element.set_hover_state(true);
643 }
644
645 let mouse_over_event = MouseEvent::new_for_platform_motion_event(
646 &self.window,
647 FireMouseEventType::Over,
648 &hit_test_result,
649 input_event,
650 can_gc,
651 );
652 mouse_over_event
653 .upcast::<Event>()
654 .set_related_target(old_hover_target.as_ref().map(|target| target.upcast()));
655
656 mouse_over_event
658 .to_pointer_hover_event("pointerover", can_gc)
659 .upcast::<Event>()
660 .dispatch(new_target.upcast(), false, can_gc);
661
662 mouse_over_event
663 .upcast::<Event>()
664 .dispatch(new_target.upcast(), false, can_gc);
665
666 let moving_from =
667 old_hover_target.map(|old_target| DomRoot::from_ref(old_target.upcast::<Node>()));
668 let event_target = DomRoot::from_ref(new_target.upcast::<Node>());
669 self.handle_mouse_enter_leave_event(
670 event_target,
671 moving_from,
672 FireMouseEventType::Enter,
673 &hit_test_result,
674 input_event,
675 can_gc,
676 );
677 }
678
679 let mouse_event = MouseEvent::new_for_platform_motion_event(
682 &self.window,
683 FireMouseEventType::Move,
684 &hit_test_result,
685 input_event,
686 can_gc,
687 );
688
689 let pointer_event = mouse_event.to_pointer_event(Atom::from("pointermove"), can_gc);
691 pointer_event
692 .upcast::<Event>()
693 .fire(new_target.upcast(), can_gc);
694
695 mouse_event
698 .upcast::<Event>()
699 .fire(new_target.upcast(), can_gc);
700
701 self.update_current_hover_target_and_status(Some(new_target));
702 }
703
704 fn update_current_hover_target_and_status(&self, new_hover_target: Option<DomRoot<Element>>) {
705 let current_hover_target = self.current_hover_target.get();
706 if current_hover_target == new_hover_target {
707 return;
708 }
709
710 let previous_hover_target = self.current_hover_target.get();
711 self.current_hover_target.set(new_hover_target.as_deref());
712
713 if let Some(target) = self.current_hover_target.get() {
716 if let Some(anchor) = target
717 .upcast::<Node>()
718 .inclusive_ancestors(ShadowIncluding::Yes)
719 .find_map(DomRoot::downcast::<HTMLAnchorElement>)
720 {
721 let status = anchor
722 .full_href_url_for_user_interface()
723 .map(|url| url.to_string());
724 self.window
725 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), status));
726 return;
727 }
728 }
729
730 if previous_hover_target.is_none_or(|previous_hover_target| {
735 previous_hover_target
736 .upcast::<Node>()
737 .inclusive_ancestors(ShadowIncluding::Yes)
738 .any(|node| node.is::<HTMLAnchorElement>())
739 }) {
740 self.window
741 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
742 }
743 }
744
745 pub(crate) fn handle_refresh_cursor(&self) {
746 let Some(most_recent_mousemove_point) = self.most_recent_mousemove_point.get() else {
747 return;
748 };
749
750 let Some(hit_test_result) = self
751 .window
752 .hit_test_from_point_in_viewport(most_recent_mousemove_point)
753 else {
754 return;
755 };
756
757 self.set_cursor(Some(hit_test_result.cursor));
758 }
759
760 fn element_for_activation(&self, element: DomRoot<Element>) -> DomRoot<Element> {
761 if element.upcast::<Node>().type_id() ==
763 NodeTypeId::Element(ElementTypeId::HTMLElement(
764 HTMLElementTypeId::HTMLLabelElement,
765 ))
766 {
767 let label = element.downcast::<HTMLLabelElement>().unwrap();
768 if let Some(control) = label.GetControl() {
769 return DomRoot::from_ref(control.upcast::<Element>());
770 }
771 }
772 element
773 }
774
775 fn handle_native_mouse_button_event(
778 &self,
779 event: MouseButtonEvent,
780 input_event: &ConstellationInputEvent,
781 can_gc: CanGc,
782 ) {
783 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
785 return;
786 };
787
788 debug!(
789 "{:?}: at {:?}",
790 event.action, hit_test_result.point_in_frame
791 );
792
793 if event.action == MouseButtonAction::Down {
796 self.set_sequential_focus_navigation_starting_point(&hit_test_result.node);
797 }
798
799 let Some(element) = hit_test_result
800 .node
801 .inclusive_ancestors(ShadowIncluding::Yes)
802 .find_map(DomRoot::downcast::<Element>)
803 else {
804 return;
805 };
806
807 let node = element.upcast::<Node>();
808 debug!("{:?} on {:?}", event.action, node.debug_str());
809
810 if event.action == MouseButtonAction::Down {
814 self.element_for_activation(element.clone())
815 .set_active_state(true);
816 }
817 if event.action == MouseButtonAction::Up {
818 self.element_for_activation(element.clone())
819 .set_active_state(false);
820 }
821
822 if element.is_actually_disabled() {
826 return;
827 }
828
829 let mouse_event_type = match event.action {
830 embedder_traits::MouseButtonAction::Up => atom!("mouseup"),
831 embedder_traits::MouseButtonAction::Down => atom!("mousedown"),
832 };
833
834 if event.action == MouseButtonAction::Down {
841 self.click_counting_info
842 .borrow_mut()
843 .reset_click_count_if_necessary(event.button, hit_test_result.point_in_frame);
844 }
845
846 let dom_event = DomRoot::upcast::<Event>(MouseEvent::for_platform_button_event(
847 mouse_event_type,
848 event,
849 input_event.pressed_mouse_buttons,
850 &self.window,
851 &hit_test_result,
852 input_event.active_keyboard_modifiers,
853 self.click_counting_info.borrow().count + 1,
854 can_gc,
855 ));
856
857 match event.action {
858 MouseButtonAction::Down => {
859 self.last_mouse_button_down_point
860 .set(Some(hit_test_result.point_in_frame));
861
862 let down_button_count = self.down_button_count.get();
864
865 let event_type = if down_button_count == 0 {
866 "pointerdown"
867 } else {
868 "pointermove"
869 };
870 let pointer_event = dom_event
871 .downcast::<MouseEvent>()
872 .unwrap()
873 .to_pointer_event(event_type.into(), can_gc);
874
875 pointer_event.upcast::<Event>().fire(node.upcast(), can_gc);
876
877 self.down_button_count.set(down_button_count + 1);
878
879 let target_el = element.find_click_focusable_area();
884
885 let document = self.window.Document();
886 document.begin_focus_transaction();
887
888 document.request_focus(None, FocusInitiator::Click, can_gc);
890 document.request_focus(target_el.as_deref(), FocusInitiator::Click, can_gc);
891
892 let result = dom_event.dispatch(node.upcast(), false, can_gc);
894
895 if result && document.has_focus_transaction() {
898 document.commit_focus_transaction(FocusInitiator::Click, can_gc);
899 }
900
901 if let MouseButton::Right = event.button {
904 self.maybe_show_context_menu(
905 node.upcast(),
906 &hit_test_result,
907 input_event,
908 can_gc,
909 );
910 }
911 },
912 MouseButtonAction::Up => {
914 let down_button_count = self.down_button_count.get();
916
917 if down_button_count > 0 {
918 self.down_button_count.set(down_button_count - 1);
919 }
920
921 let event_type = if down_button_count == 0 {
922 "pointerup"
923 } else {
924 "pointermove"
925 };
926 let pointer_event = dom_event
927 .downcast::<MouseEvent>()
928 .unwrap()
929 .to_pointer_event(event_type.into(), can_gc);
930
931 pointer_event.upcast::<Event>().fire(node.upcast(), can_gc);
932
933 dom_event.dispatch(node.upcast(), false, can_gc);
935
936 self.click_counting_info
940 .borrow_mut()
941 .increment_click_count(event.button, hit_test_result.point_in_frame);
942
943 self.maybe_trigger_click_for_mouse_button_down_event(
944 event,
945 input_event,
946 &hit_test_result,
947 &element,
948 can_gc,
949 );
950 },
951 }
952 }
953
954 fn maybe_trigger_click_for_mouse_button_down_event(
957 &self,
958 event: MouseButtonEvent,
959 input_event: &ConstellationInputEvent,
960 hit_test_result: &HitTestResult,
961 element: &Element,
962 can_gc: CanGc,
963 ) {
964 if event.button != MouseButton::Left {
965 return;
966 }
967
968 let Some(last_mouse_button_down_point) = self.last_mouse_button_down_point.take() else {
969 return;
970 };
971
972 let distance = last_mouse_button_down_point.distance_to(hit_test_result.point_in_frame);
973 let maximum_click_distance = 10.0 * self.window.device_pixel_ratio().get();
974 if distance > maximum_click_distance {
975 return;
976 }
977
978 let delegated = element.find_click_focusable_area();
983 let element = delegated.as_deref().unwrap_or(element);
984 self.most_recently_clicked_element.set(Some(element));
985
986 let click_count = self.click_counting_info.borrow().count;
987 element.set_click_in_progress(true);
988 MouseEvent::for_platform_button_event(
989 atom!("click"),
990 event,
991 input_event.pressed_mouse_buttons,
992 &self.window,
993 hit_test_result,
994 input_event.active_keyboard_modifiers,
995 click_count,
996 can_gc,
997 )
998 .upcast::<Event>()
999 .dispatch(element.upcast(), false, can_gc);
1000 element.set_click_in_progress(false);
1001
1002 if click_count % 2 == 0 {
1011 MouseEvent::for_platform_button_event(
1012 Atom::from("dblclick"),
1013 event,
1014 input_event.pressed_mouse_buttons,
1015 &self.window,
1016 hit_test_result,
1017 input_event.active_keyboard_modifiers,
1018 2,
1019 can_gc,
1020 )
1021 .upcast::<Event>()
1022 .dispatch(element.upcast(), false, can_gc);
1023 }
1024 }
1025
1026 fn maybe_show_context_menu(
1028 &self,
1029 target: &EventTarget,
1030 hit_test_result: &HitTestResult,
1031 input_event: &ConstellationInputEvent,
1032 can_gc: CanGc,
1033 ) {
1034 let menu_event = PointerEvent::new(
1036 &self.window, "contextmenu".into(), EventBubbles::Bubbles, EventCancelable::Cancelable, Some(&self.window), 0, hit_test_result.point_in_frame.to_i32(),
1043 hit_test_result.point_in_frame.to_i32(),
1044 hit_test_result
1045 .point_relative_to_initial_containing_block
1046 .to_i32(),
1047 input_event.active_keyboard_modifiers,
1048 2i16, input_event.pressed_mouse_buttons,
1050 None, None, PointerId::Mouse as i32, 1, 1, 0.5, 0.0, 0, 0, 0, PI / 2.0, 0.0, DOMString::from("mouse"), true, vec![], vec![], can_gc,
1067 );
1068
1069 let result = menu_event.upcast::<Event>().fire(target, can_gc);
1071
1072 if result {
1074 self.window
1075 .Document()
1076 .embedder_controls()
1077 .show_context_menu(hit_test_result);
1078 };
1079 }
1080
1081 fn handle_touch_event(
1082 &self,
1083 event: EmbedderTouchEvent,
1084 input_event: &ConstellationInputEvent,
1085 can_gc: CanGc,
1086 ) -> InputEventResult {
1087 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
1089 self.update_active_touch_points_when_early_return(event);
1090 return Default::default();
1091 };
1092
1093 let TouchId(identifier) = event.touch_id;
1094
1095 let Some(element) = hit_test_result
1096 .node
1097 .inclusive_ancestors(ShadowIncluding::Yes)
1098 .find_map(DomRoot::downcast::<Element>)
1099 else {
1100 self.update_active_touch_points_when_early_return(event);
1101 return Default::default();
1102 };
1103
1104 let current_target = DomRoot::upcast::<EventTarget>(element.clone());
1105 let window = &*self.window;
1106
1107 let client_x = Finite::wrap(hit_test_result.point_in_frame.x as f64);
1108 let client_y = Finite::wrap(hit_test_result.point_in_frame.y as f64);
1109 let page_x =
1110 Finite::wrap(hit_test_result.point_in_frame.x as f64 + window.PageXOffset() as f64);
1111 let page_y =
1112 Finite::wrap(hit_test_result.point_in_frame.y as f64 + window.PageYOffset() as f64);
1113
1114 let pointer_touch = Touch::new(
1116 window,
1117 identifier,
1118 ¤t_target,
1119 client_x,
1120 client_y, client_x,
1122 client_y,
1123 page_x,
1124 page_y,
1125 can_gc,
1126 );
1127
1128 let pointer_event_name = match event.event_type {
1130 TouchEventType::Down => "pointerdown",
1131 TouchEventType::Move => "pointermove",
1132 TouchEventType::Up => "pointerup",
1133 TouchEventType::Cancel => "pointercancel",
1134 };
1135
1136 let pointer_id = self.get_or_create_pointer_id_for_touch(identifier);
1138 let is_primary = self.is_primary_pointer(pointer_id);
1139
1140 if matches!(event.event_type, TouchEventType::Down) {
1143 let pointer_over = pointer_touch.to_pointer_event(
1145 window,
1146 "pointerover",
1147 pointer_id,
1148 is_primary,
1149 input_event.active_keyboard_modifiers,
1150 true, Some(hit_test_result.point_in_node),
1152 can_gc,
1153 );
1154 pointer_over.upcast::<Event>().fire(¤t_target, can_gc);
1155
1156 self.fire_pointer_event_for_touch(
1158 &element,
1159 &pointer_touch,
1160 pointer_id,
1161 "pointerenter",
1162 is_primary,
1163 input_event,
1164 &hit_test_result,
1165 can_gc,
1166 );
1167 }
1168
1169 let pointer_event = pointer_touch.to_pointer_event(
1170 window,
1171 pointer_event_name,
1172 pointer_id,
1173 is_primary,
1174 input_event.active_keyboard_modifiers,
1175 event.is_cancelable(),
1176 Some(hit_test_result.point_in_node),
1177 can_gc,
1178 );
1179 pointer_event
1180 .upcast::<Event>()
1181 .fire(¤t_target, can_gc);
1182
1183 if matches!(
1186 event.event_type,
1187 TouchEventType::Up | TouchEventType::Cancel
1188 ) {
1189 let pointer_out = pointer_touch.to_pointer_event(
1191 window,
1192 "pointerout",
1193 pointer_id,
1194 is_primary,
1195 input_event.active_keyboard_modifiers,
1196 true, Some(hit_test_result.point_in_node),
1198 can_gc,
1199 );
1200 pointer_out.upcast::<Event>().fire(¤t_target, can_gc);
1201
1202 self.fire_pointer_event_for_touch(
1204 &element,
1205 &pointer_touch,
1206 pointer_id,
1207 "pointerleave",
1208 is_primary,
1209 input_event,
1210 &hit_test_result,
1211 can_gc,
1212 );
1213 }
1214
1215 let (touch_dispatch_target, changed_touch) = match event.event_type {
1216 TouchEventType::Down => {
1217 self.active_touch_points
1219 .borrow_mut()
1220 .push(Dom::from_ref(&*pointer_touch));
1221 self.element_for_activation(element).set_active_state(true);
1224 (current_target, pointer_touch)
1225 },
1226 _ => {
1227 let mut active_touch_points = self.active_touch_points.borrow_mut();
1233 let Some(index) = active_touch_points
1234 .iter()
1235 .position(|point| point.Identifier() == identifier)
1236 else {
1237 warn!("No active touch point for {:?}", event.event_type);
1238 return Default::default();
1239 };
1240 let original_target = active_touch_points[index].Target();
1242
1243 let touch_with_touchstart_target = Touch::new(
1244 window,
1245 identifier,
1246 &original_target,
1247 client_x,
1248 client_y,
1249 client_x,
1250 client_y,
1251 page_x,
1252 page_y,
1253 can_gc,
1254 );
1255
1256 match event.event_type {
1258 TouchEventType::Move => {
1259 active_touch_points[index] = Dom::from_ref(&*touch_with_touchstart_target);
1260 },
1261 TouchEventType::Up | TouchEventType::Cancel => {
1262 active_touch_points.swap_remove(index);
1263 self.remove_pointer_id_for_touch(identifier);
1264 self.element_for_activation(element).set_active_state(false);
1267 },
1268 TouchEventType::Down => unreachable!("Should have been handled above"),
1269 }
1270 (original_target, touch_with_touchstart_target)
1271 },
1272 };
1273
1274 rooted_vec!(let mut target_touches);
1275 target_touches.extend(
1276 self.active_touch_points
1277 .borrow()
1278 .iter()
1279 .filter(|touch| touch.Target() == touch_dispatch_target)
1280 .cloned(),
1281 );
1282
1283 let event_name = match event.event_type {
1284 TouchEventType::Down => "touchstart",
1285 TouchEventType::Move => "touchmove",
1286 TouchEventType::Up => "touchend",
1287 TouchEventType::Cancel => "touchcancel",
1288 };
1289
1290 let touch_event = TouchEvent::new(
1291 window,
1292 event_name.into(),
1293 EventBubbles::Bubbles,
1294 EventCancelable::from(event.is_cancelable()),
1295 EventComposed::Composed,
1296 Some(window),
1297 0i32,
1298 &TouchList::new(window, self.active_touch_points.borrow().r(), can_gc),
1299 &TouchList::new(window, from_ref(&&*changed_touch), can_gc),
1300 &TouchList::new(window, target_touches.r(), can_gc),
1301 false,
1303 false,
1304 false,
1305 false,
1306 can_gc,
1307 );
1308 let event = touch_event.upcast::<Event>();
1309 event.fire(&touch_dispatch_target, can_gc);
1310 event.flags().into()
1311 }
1312
1313 fn update_active_touch_points_when_early_return(&self, event: EmbedderTouchEvent) {
1322 match event.event_type {
1323 TouchEventType::Down | TouchEventType::Move => {},
1324 TouchEventType::Up | TouchEventType::Cancel => {
1325 let mut active_touch_points = self.active_touch_points.borrow_mut();
1326 if let Some(index) = active_touch_points
1327 .iter()
1328 .position(|t| t.Identifier() == event.touch_id.0)
1329 {
1330 active_touch_points.swap_remove(index);
1331 self.remove_pointer_id_for_touch(event.touch_id.0);
1332 } else {
1333 warn!(
1334 "Received {:?} for a non-active touch point {}",
1335 event.event_type, event.touch_id.0
1336 );
1337 }
1338 },
1339 }
1340 }
1341
1342 fn handle_keyboard_event(
1344 &self,
1345 keyboard_event: EmbedderKeyboardEvent,
1346 can_gc: CanGc,
1347 ) -> InputEventResult {
1348 let document = self.window.Document();
1349 let focused = document.get_focused_element();
1350 let body = document.GetBody();
1351
1352 let target = match (&focused, &body) {
1353 (Some(focused), _) => focused.upcast(),
1354 (&None, Some(body)) => body.upcast(),
1355 (&None, &None) => self.window.upcast(),
1356 };
1357
1358 let keyevent = KeyboardEvent::new_with_platform_keyboard_event(
1359 &self.window,
1360 keyboard_event.event.state.event_type().into(),
1361 &keyboard_event.event,
1362 can_gc,
1363 );
1364
1365 let event = keyevent.upcast::<Event>();
1366 event.fire(target, can_gc);
1367
1368 let mut flags = event.flags();
1369 if flags.contains(EventFlags::Canceled) {
1370 return flags.into();
1371 }
1372
1373 let is_character_value_key = matches!(
1379 keyboard_event.event.key,
1380 Key::Character(_) | Key::Named(NamedKey::Enter)
1381 );
1382 if keyboard_event.event.state == KeyState::Down &&
1383 is_character_value_key &&
1384 !keyboard_event.event.is_composing
1385 {
1386 let keypress_event = KeyboardEvent::new_with_platform_keyboard_event(
1388 &self.window,
1389 atom!("keypress"),
1390 &keyboard_event.event,
1391 can_gc,
1392 );
1393 let event = keypress_event.upcast::<Event>();
1394 event.fire(target, can_gc);
1395 flags = event.flags();
1396 }
1397
1398 flags.into()
1399 }
1400
1401 fn handle_ime_event(&self, event: ImeEvent, can_gc: CanGc) -> InputEventResult {
1402 let document = self.window.Document();
1403 let composition_event = match event {
1404 ImeEvent::Dismissed => {
1405 document.request_focus(
1406 document.GetBody().as_ref().map(|e| e.upcast()),
1407 FocusInitiator::Keyboard,
1408 can_gc,
1409 );
1410 return Default::default();
1411 },
1412 ImeEvent::Composition(composition_event) => composition_event,
1413 };
1414
1415 let focused = document.get_focused_element();
1420 let target = if let Some(elem) = &focused {
1421 elem.upcast()
1422 } else {
1423 return Default::default();
1425 };
1426
1427 let cancelable = composition_event.state == keyboard_types::CompositionState::Start;
1428 let event = CompositionEvent::new(
1429 &self.window,
1430 composition_event.state.event_type().into(),
1431 true,
1432 cancelable,
1433 Some(&self.window),
1434 0,
1435 DOMString::from(composition_event.data),
1436 can_gc,
1437 );
1438
1439 let event = event.upcast::<Event>();
1440 event.fire(target, can_gc);
1441 event.flags().into()
1442 }
1443
1444 fn handle_wheel_event(
1445 &self,
1446 event: EmbedderWheelEvent,
1447 input_event: &ConstellationInputEvent,
1448 can_gc: CanGc,
1449 ) -> InputEventResult {
1450 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
1452 return Default::default();
1453 };
1454
1455 let Some(el) = hit_test_result
1456 .node
1457 .inclusive_ancestors(ShadowIncluding::Yes)
1458 .find_map(DomRoot::downcast::<Element>)
1459 else {
1460 return Default::default();
1461 };
1462
1463 let node = el.upcast::<Node>();
1464 debug!(
1465 "wheel: on {:?} at {:?}",
1466 node.debug_str(),
1467 hit_test_result.point_in_frame
1468 );
1469
1470 let dom_event = WheelEvent::new(
1472 &self.window,
1473 "wheel".into(),
1474 EventBubbles::Bubbles,
1475 EventCancelable::Cancelable,
1476 Some(&self.window),
1477 0i32,
1478 hit_test_result.point_in_frame.to_i32(),
1479 hit_test_result.point_in_frame.to_i32(),
1480 hit_test_result
1481 .point_relative_to_initial_containing_block
1482 .to_i32(),
1483 input_event.active_keyboard_modifiers,
1484 0i16,
1485 input_event.pressed_mouse_buttons,
1486 None,
1487 None,
1488 Finite::wrap(-event.delta.x),
1493 Finite::wrap(-event.delta.y),
1494 Finite::wrap(-event.delta.z),
1495 event.delta.mode as u32,
1496 can_gc,
1497 );
1498
1499 let dom_event = dom_event.upcast::<Event>();
1500 dom_event.set_trusted(true);
1501 dom_event.fire(node.upcast(), can_gc);
1502
1503 dom_event.flags().into()
1504 }
1505
1506 #[cfg(feature = "gamepad")]
1507 fn handle_gamepad_event(&self, gamepad_event: EmbedderGamepadEvent) {
1508 match gamepad_event {
1509 EmbedderGamepadEvent::Connected(index, name, bounds, supported_haptic_effects) => {
1510 self.handle_gamepad_connect(
1511 index.0,
1512 name,
1513 bounds.axis_bounds,
1514 bounds.button_bounds,
1515 supported_haptic_effects,
1516 );
1517 },
1518 EmbedderGamepadEvent::Disconnected(index) => {
1519 self.handle_gamepad_disconnect(index.0);
1520 },
1521 EmbedderGamepadEvent::Updated(index, update_type) => {
1522 self.receive_new_gamepad_button_or_axis(index.0, update_type);
1523 },
1524 };
1525 }
1526
1527 #[cfg(feature = "gamepad")]
1529 fn handle_gamepad_connect(
1530 &self,
1531 _index: usize,
1535 name: String,
1536 axis_bounds: (f64, f64),
1537 button_bounds: (f64, f64),
1538 supported_haptic_effects: GamepadSupportedHapticEffects,
1539 ) {
1540 let trusted_window = Trusted::new(&*self.window);
1543 self.window
1544 .upcast::<GlobalScope>()
1545 .task_manager()
1546 .gamepad_task_source()
1547 .queue(task!(gamepad_connected: move || {
1548 let window = trusted_window.root();
1549
1550 let navigator = window.Navigator();
1551 let selected_index = navigator.select_gamepad_index();
1552 let gamepad = Gamepad::new(
1553 &window,
1554 selected_index,
1555 name,
1556 "standard".into(),
1557 axis_bounds,
1558 button_bounds,
1559 supported_haptic_effects,
1560 false,
1561 CanGc::note(),
1562 );
1563 navigator.set_gamepad(selected_index as usize, &gamepad, CanGc::note());
1564 }));
1565 }
1566
1567 #[cfg(feature = "gamepad")]
1569 fn handle_gamepad_disconnect(&self, index: usize) {
1570 let trusted_window = Trusted::new(&*self.window);
1571 self.window
1572 .upcast::<GlobalScope>()
1573 .task_manager()
1574 .gamepad_task_source()
1575 .queue(task!(gamepad_disconnected: move || {
1576 let window = trusted_window.root();
1577 let navigator = window.Navigator();
1578 if let Some(gamepad) = navigator.get_gamepad(index) {
1579 if window.Document().is_fully_active() {
1580 gamepad.update_connected(false, gamepad.exposed(), CanGc::note());
1581 navigator.remove_gamepad(index);
1582 }
1583 }
1584 }));
1585 }
1586
1587 #[cfg(feature = "gamepad")]
1589 fn receive_new_gamepad_button_or_axis(&self, index: usize, update_type: GamepadUpdateType) {
1590 let trusted_window = Trusted::new(&*self.window);
1591
1592 self.window.upcast::<GlobalScope>().task_manager().gamepad_task_source().queue(
1594 task!(update_gamepad_state: move || {
1595 let window = trusted_window.root();
1596 let navigator = window.Navigator();
1597 if let Some(gamepad) = navigator.get_gamepad(index) {
1598 let current_time = window.Performance().Now();
1599 gamepad.update_timestamp(*current_time);
1600 match update_type {
1601 GamepadUpdateType::Axis(index, value) => {
1602 gamepad.map_and_normalize_axes(index, value);
1603 },
1604 GamepadUpdateType::Button(index, value) => {
1605 gamepad.map_and_normalize_buttons(index, value);
1606 }
1607 };
1608 if !navigator.has_gamepad_gesture() && contains_user_gesture(update_type) {
1609 navigator.set_has_gamepad_gesture(true);
1610 navigator.GetGamepads()
1611 .iter()
1612 .filter_map(|g| g.as_ref())
1613 .for_each(|gamepad| {
1614 gamepad.set_exposed(true);
1615 gamepad.update_timestamp(*current_time);
1616 let new_gamepad = Trusted::new(&**gamepad);
1617 if window.Document().is_fully_active() {
1618 window.upcast::<GlobalScope>().task_manager().gamepad_task_source().queue(
1619 task!(update_gamepad_connect: move || {
1620 let gamepad = new_gamepad.root();
1621 gamepad.notify_event(GamepadEventType::Connected, CanGc::note());
1622 })
1623 );
1624 }
1625 });
1626 }
1627 }
1628 })
1629 );
1630 }
1631
1632 pub(crate) fn handle_editing_action(
1634 &self,
1635 element: Option<DomRoot<Element>>,
1636 action: EditingActionEvent,
1637 can_gc: CanGc,
1638 ) -> InputEventResult {
1639 let clipboard_event_type = match action {
1640 EditingActionEvent::Copy => ClipboardEventType::Copy,
1641 EditingActionEvent::Cut => ClipboardEventType::Cut,
1642 EditingActionEvent::Paste => ClipboardEventType::Paste,
1643 };
1644
1645 let script_triggered = false;
1647
1648 let script_may_access_clipboard = false;
1652
1653 if script_triggered && !script_may_access_clipboard {
1655 return InputEventResult::empty();
1656 }
1657
1658 let clipboard_event =
1660 self.fire_clipboard_event(element.clone(), clipboard_event_type, can_gc);
1661
1662 let event = clipboard_event.upcast::<Event>();
1665 if !event.IsTrusted() {
1666 return event.flags().into();
1667 }
1668
1669 if event.DefaultPrevented() {
1671 let event_type = event.Type();
1672 match_domstring_ascii!(event_type,
1673
1674 "copy" => {
1675 if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1678 let drag_data_store =
1679 clipboard_data.data_store().expect("This shouldn't fail");
1680 self.write_content_to_the_clipboard(&drag_data_store);
1681 }
1682 },
1683 "cut" => {
1684 if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1687 let drag_data_store =
1688 clipboard_data.data_store().expect("This shouldn't fail");
1689 self.write_content_to_the_clipboard(&drag_data_store);
1690 }
1691
1692 self.fire_clipboard_event(element, ClipboardEventType::Change, can_gc);
1694 },
1695 "paste" => (),
1699 _ => (),
1700 )
1701 }
1702
1703 event.flags().into()
1706 }
1707
1708 pub(crate) fn fire_clipboard_event(
1710 &self,
1711 target: Option<DomRoot<Element>>,
1712 clipboard_event_type: ClipboardEventType,
1713 can_gc: CanGc,
1714 ) -> DomRoot<ClipboardEvent> {
1715 let clipboard_event = ClipboardEvent::new(
1716 &self.window,
1717 None,
1718 clipboard_event_type.as_str().into(),
1719 EventBubbles::Bubbles,
1720 EventCancelable::Cancelable,
1721 None,
1722 can_gc,
1723 );
1724
1725 let mut drag_data_store = DragDataStore::new();
1728
1729 let trusted = true;
1733
1734 let document = self.window.Document();
1736 let target = target.or(document.get_focused_element());
1737 let target = target
1738 .map(|target| DomRoot::from_ref(target.upcast()))
1739 .or_else(|| {
1740 document
1741 .GetBody()
1742 .map(|body| DomRoot::from_ref(body.upcast()))
1743 })
1744 .unwrap_or_else(|| DomRoot::from_ref(self.window.upcast()));
1745
1746 match clipboard_event_type {
1749 ClipboardEventType::Copy | ClipboardEventType::Cut => {
1750 drag_data_store.set_mode(Mode::ReadWrite);
1752 },
1753 ClipboardEventType::Paste => {
1754 let (callback, receiver) =
1755 GenericCallback::new_blocking().expect("Could not create callback");
1756 self.window.send_to_embedder(EmbedderMsg::GetClipboardText(
1757 self.window.webview_id(),
1758 callback,
1759 ));
1760 let text_contents = receiver
1761 .recv()
1762 .map(Result::unwrap_or_default)
1763 .unwrap_or_default();
1764
1765 drag_data_store.set_mode(Mode::ReadOnly);
1767 if trusted {
1769 let data = DOMString::from(text_contents);
1773 let type_ = DOMString::from("text/plain");
1774 let _ = drag_data_store.add(Kind::Text { data, type_ });
1775
1776 }
1782 },
1783 ClipboardEventType::Change => (),
1784 }
1785
1786 let clipboard_event_data = DataTransfer::new(
1788 &self.window,
1789 Rc::new(RefCell::new(Some(drag_data_store))),
1790 can_gc,
1791 );
1792
1793 clipboard_event.set_clipboard_data(Some(&clipboard_event_data));
1795
1796 let event = clipboard_event.upcast::<Event>();
1798 event.set_trusted(trusted);
1799
1800 event.set_composed(true);
1802
1803 event.dispatch(&target, false, can_gc);
1805
1806 DomRoot::from(clipboard_event)
1807 }
1808
1809 fn write_content_to_the_clipboard(&self, drag_data_store: &DragDataStore) {
1811 if drag_data_store.list_len() > 0 {
1813 self.window
1815 .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
1816 for item in drag_data_store.iter_item_list() {
1818 match item {
1819 Kind::Text { data, .. } => {
1820 self.window.send_to_embedder(EmbedderMsg::SetClipboardText(
1824 self.window.webview_id(),
1825 data.to_string(),
1826 ));
1827 },
1828 Kind::File { .. } => {
1829 },
1833 }
1834 }
1835 } else {
1836 if drag_data_store.clear_was_called {
1838 self.window
1840 .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
1841 }
1844 }
1845 }
1846
1847 #[expect(unsafe_code)]
1850 pub(crate) fn handle_embedder_scroll_event(&self, scrolled_node: ExternalScrollId) {
1851 let document = self.window.Document();
1853 if scrolled_node.is_root() {
1854 document.handle_viewport_scroll_event();
1855 } else {
1856 let Some(node_id) = node_id_from_scroll_id(scrolled_node.0 as usize) else {
1858 return;
1859 };
1860 let node = unsafe {
1861 node::from_untrusted_node_address(UntrustedNodeAddress::from_id(node_id))
1862 };
1863 let Some(element) = node
1864 .inclusive_ancestors(ShadowIncluding::Yes)
1865 .find_map(DomRoot::downcast::<Element>)
1866 else {
1867 return;
1868 };
1869
1870 document.handle_element_scroll_event(&element);
1871 }
1872 }
1873
1874 pub(crate) fn maybe_dispatch_simulated_click(
1880 &self,
1881 node: &Node,
1882 event: &KeyboardEvent,
1883 can_gc: CanGc,
1884 ) -> bool {
1885 if event.key() != Key::Named(NamedKey::Enter) && event.original_code() != Some(Code::Space)
1886 {
1887 return false;
1888 }
1889
1890 if node
1894 .downcast::<Element>()
1895 .and_then(Element::as_maybe_activatable)
1896 .is_none()
1897 {
1898 return false;
1899 }
1900
1901 node.fire_synthetic_pointer_event_not_trusted(atom!("click"), can_gc);
1902 true
1903 }
1904
1905 pub(crate) fn run_default_keyboard_event_handler(
1906 &self,
1907 node: &Node,
1908 event: &KeyboardEvent,
1909 can_gc: CanGc,
1910 ) {
1911 if event.upcast::<Event>().type_() != atom!("keydown") {
1912 return;
1913 }
1914
1915 if self.maybe_dispatch_simulated_click(node, event, can_gc) {
1916 return;
1917 }
1918
1919 if self.maybe_handle_accesskey(event, can_gc) {
1920 return;
1921 }
1922
1923 let mut is_space = false;
1924 let scroll = match event.key() {
1925 Key::Named(NamedKey::ArrowDown) => KeyboardScroll::Down,
1926 Key::Named(NamedKey::ArrowLeft) => KeyboardScroll::Left,
1927 Key::Named(NamedKey::ArrowRight) => KeyboardScroll::Right,
1928 Key::Named(NamedKey::ArrowUp) => KeyboardScroll::Up,
1929 Key::Named(NamedKey::End) => KeyboardScroll::End,
1930 Key::Named(NamedKey::Home) => KeyboardScroll::Home,
1931 Key::Named(NamedKey::PageDown) => KeyboardScroll::PageDown,
1932 Key::Named(NamedKey::PageUp) => KeyboardScroll::PageUp,
1933 Key::Character(string) if &string == " " => {
1934 is_space = true;
1935 if event.modifiers().contains(Modifiers::SHIFT) {
1936 KeyboardScroll::PageUp
1937 } else {
1938 KeyboardScroll::PageDown
1939 }
1940 },
1941 Key::Named(NamedKey::Tab) => {
1942 self.sequential_focus_navigation_via_keyboard_event(event, can_gc);
1948 return;
1949 },
1950 _ => return,
1951 };
1952
1953 if !event.modifiers().is_empty() && !is_space {
1954 return;
1955 }
1956
1957 self.do_keyboard_scroll(scroll);
1958 }
1959
1960 pub(crate) fn set_sequential_focus_navigation_starting_point(&self, node: &Node) {
1961 self.sequential_focus_navigation_starting_point
1962 .set(Some(node));
1963 }
1964
1965 pub(crate) fn sequential_focus_navigation_starting_point(&self) -> Option<DomRoot<Node>> {
1966 self.sequential_focus_navigation_starting_point
1967 .get()
1968 .filter(|node| node.is_connected())
1969 }
1970
1971 fn sequential_focus_navigation_via_keyboard_event(&self, event: &KeyboardEvent, can_gc: CanGc) {
1972 let direction = if event.modifiers().contains(Modifiers::SHIFT) {
1973 SequentialFocusDirection::Backward
1974 } else {
1975 SequentialFocusDirection::Forward
1976 };
1977
1978 self.sequential_focus_navigation(direction, can_gc);
1979 }
1980
1981 fn sequential_focus_navigation(&self, direction: SequentialFocusDirection, can_gc: CanGc) {
1983 let mut starting_point = self
2000 .window
2001 .Document()
2002 .get_focused_element()
2003 .map(DomRoot::upcast::<Node>);
2004
2005 if let Some(sequential_focus_navigation_starting_point) =
2009 self.sequential_focus_navigation_starting_point()
2010 {
2011 if starting_point.as_ref().is_none_or(|starting_point| {
2012 starting_point.is_ancestor_of(&sequential_focus_navigation_starting_point)
2013 }) {
2014 starting_point = Some(sequential_focus_navigation_starting_point);
2015 }
2016 }
2017
2018 let candidate = starting_point
2032 .map(|starting_point| {
2033 self.find_element_for_tab_focus_following_element(direction, starting_point)
2034 })
2035 .unwrap_or_else(|| self.find_first_tab_focusable_element(direction));
2036
2037 if let Some(candidate) = candidate {
2039 self.focus_and_scroll_to_element_for_key_event(&candidate, can_gc);
2040 return;
2041 }
2042
2043 self.sequential_focus_navigation_starting_point.clear();
2045
2046 }
2055
2056 fn find_element_for_tab_focus_following_element(
2057 &self,
2058 direction: SequentialFocusDirection,
2059 starting_point: DomRoot<Node>,
2060 ) -> Option<DomRoot<Element>> {
2061 let root_node = self.window.Document().GetDocumentElement()?;
2062 let focused_element_tab_index = starting_point
2063 .downcast::<Element>()
2064 .and_then(Element::explicitly_set_tab_index)
2065 .unwrap_or_default();
2066 let mut winning_node_and_tab_index: Option<(DomRoot<Element>, i32)> = None;
2067 let mut saw_focused_element = false;
2068
2069 for node in root_node
2070 .upcast::<Node>()
2071 .traverse_preorder(ShadowIncluding::Yes)
2072 {
2073 if node == starting_point {
2074 saw_focused_element = true;
2075 continue;
2076 }
2077
2078 let Some(candidate_element) = DomRoot::downcast::<Element>(node) else {
2079 continue;
2080 };
2081 if !candidate_element.is_sequentially_focusable() {
2082 continue;
2083 }
2084
2085 let candidate_element_tab_index = candidate_element
2086 .explicitly_set_tab_index()
2087 .unwrap_or_default();
2088 let ordering =
2089 compare_tab_indices(focused_element_tab_index, candidate_element_tab_index);
2090 match direction {
2091 SequentialFocusDirection::Forward => {
2092 if saw_focused_element && ordering == Ordering::Equal {
2095 return Some(candidate_element);
2096 }
2097 if ordering != Ordering::Less {
2099 continue;
2100 }
2101 let Some((_, winning_tab_index)) = winning_node_and_tab_index else {
2102 if candidate_element_tab_index == focused_element_tab_index + 1 {
2106 return Some(candidate_element);
2107 }
2108
2109 winning_node_and_tab_index =
2110 Some((candidate_element, candidate_element_tab_index));
2111 continue;
2112 };
2113 if compare_tab_indices(candidate_element_tab_index, winning_tab_index) ==
2116 Ordering::Less
2117 {
2118 winning_node_and_tab_index =
2119 Some((candidate_element, candidate_element_tab_index))
2120 }
2121 },
2122 SequentialFocusDirection::Backward => {
2123 if !saw_focused_element && ordering == Ordering::Equal {
2126 winning_node_and_tab_index =
2127 Some((candidate_element, candidate_element_tab_index));
2128 continue;
2129 }
2130 if ordering != Ordering::Greater {
2132 continue;
2133 }
2134 let Some((_, winning_tab_index)) = winning_node_and_tab_index else {
2135 winning_node_and_tab_index =
2136 Some((candidate_element, candidate_element_tab_index));
2137 continue;
2138 };
2139 if compare_tab_indices(candidate_element_tab_index, winning_tab_index) !=
2143 Ordering::Less
2144 {
2145 winning_node_and_tab_index =
2146 Some((candidate_element, candidate_element_tab_index))
2147 }
2148 },
2149 }
2150 }
2151
2152 Some(winning_node_and_tab_index?.0)
2153 }
2154
2155 fn find_first_tab_focusable_element(
2156 &self,
2157 direction: SequentialFocusDirection,
2158 ) -> Option<DomRoot<Element>> {
2159 let root_node = self.window.Document().GetDocumentElement()?;
2160 let mut winning_node_and_tab_index: Option<(DomRoot<Element>, i32)> = None;
2161 for node in root_node
2162 .upcast::<Node>()
2163 .traverse_preorder(ShadowIncluding::Yes)
2164 {
2165 let Some(candidate_element) = DomRoot::downcast::<Element>(node) else {
2166 continue;
2167 };
2168 if !candidate_element.is_sequentially_focusable() {
2169 continue;
2170 }
2171
2172 let candidate_element_tab_index = candidate_element
2173 .explicitly_set_tab_index()
2174 .unwrap_or_default();
2175 match direction {
2176 SequentialFocusDirection::Forward => {
2177 if candidate_element_tab_index == 1 {
2181 return Some(candidate_element);
2182 }
2183
2184 if winning_node_and_tab_index
2187 .as_ref()
2188 .is_none_or(|(_, winning_tab_index)| {
2189 compare_tab_indices(candidate_element_tab_index, *winning_tab_index) ==
2190 Ordering::Less
2191 })
2192 {
2193 winning_node_and_tab_index =
2194 Some((candidate_element, candidate_element_tab_index));
2195 }
2196 },
2197 SequentialFocusDirection::Backward => {
2198 if winning_node_and_tab_index
2202 .as_ref()
2203 .is_none_or(|(_, winning_tab_index)| {
2204 compare_tab_indices(candidate_element_tab_index, *winning_tab_index) !=
2205 Ordering::Less
2206 })
2207 {
2208 winning_node_and_tab_index =
2209 Some((candidate_element, candidate_element_tab_index));
2210 }
2211 },
2212 }
2213 }
2214
2215 Some(winning_node_and_tab_index?.0)
2216 }
2217
2218 pub(crate) fn do_keyboard_scroll(&self, scroll: KeyboardScroll) {
2219 let scroll_axis = match scroll {
2220 KeyboardScroll::Left | KeyboardScroll::Right => ScrollingBoxAxis::X,
2221 _ => ScrollingBoxAxis::Y,
2222 };
2223
2224 let document = self.window.Document();
2225 let mut scrolling_box = document
2226 .get_focused_element()
2227 .or(self.most_recently_clicked_element.get())
2228 .and_then(|element| element.scrolling_box(ScrollContainerQueryFlags::Inclusive))
2229 .unwrap_or_else(|| {
2230 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2231 });
2232
2233 while !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
2234 if scrolling_box.is_viewport() {
2236 break;
2237 }
2238 let parent = scrolling_box.parent().unwrap_or_else(|| {
2239 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2240 });
2241 scrolling_box = parent;
2242 }
2243
2244 let calculate_current_scroll_offset_and_delta = || {
2245 const LINE_HEIGHT: f32 = 76.0;
2246 const LINE_WIDTH: f32 = 76.0;
2247
2248 let current_scroll_offset = scrolling_box.scroll_position();
2249 (
2250 current_scroll_offset,
2251 match scroll {
2252 KeyboardScroll::Home => Vector2D::new(0.0, -current_scroll_offset.y),
2253 KeyboardScroll::End => Vector2D::new(
2254 0.0,
2255 -current_scroll_offset.y + scrolling_box.content_size().height -
2256 scrolling_box.size().height,
2257 ),
2258 KeyboardScroll::PageDown => {
2259 Vector2D::new(0.0, scrolling_box.size().height - 2.0 * LINE_HEIGHT)
2260 },
2261 KeyboardScroll::PageUp => {
2262 Vector2D::new(0.0, 2.0 * LINE_HEIGHT - scrolling_box.size().height)
2263 },
2264 KeyboardScroll::Up => Vector2D::new(0.0, -LINE_HEIGHT),
2265 KeyboardScroll::Down => Vector2D::new(0.0, LINE_HEIGHT),
2266 KeyboardScroll::Left => Vector2D::new(-LINE_WIDTH, 0.0),
2267 KeyboardScroll::Right => Vector2D::new(LINE_WIDTH, 0.0),
2268 },
2269 )
2270 };
2271
2272 let parent_pipeline = self.window.parent_info();
2276 if scrolling_box.is_viewport() && parent_pipeline.is_none() {
2277 let (_, delta) = calculate_current_scroll_offset_and_delta();
2278 self.window
2279 .paint_api()
2280 .scroll_viewport_by_delta(self.window.webview_id(), delta);
2281 }
2282
2283 if !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
2286 assert!(scrolling_box.is_viewport());
2287
2288 let window_proxy = document.window().window_proxy();
2289 if let Some(iframe) = window_proxy.frame_element() {
2290 let cx = GlobalScope::get_cx();
2293 let iframe_window = iframe.owner_window();
2294 let _ac = JSAutoRealm::new(*cx, iframe_window.reflector().get_jsobject().get());
2295 iframe_window
2296 .Document()
2297 .event_handler()
2298 .do_keyboard_scroll(scroll);
2299 } else if let Some(parent_pipeline) = parent_pipeline {
2300 document.window().send_to_constellation(
2304 ScriptToConstellationMessage::ForwardKeyboardScroll(parent_pipeline, scroll),
2305 );
2306 };
2307 return;
2308 }
2309
2310 let (current_scroll_offset, delta) = calculate_current_scroll_offset_and_delta();
2311 scrolling_box.scroll_to(delta + current_scroll_offset, ScrollBehavior::Auto);
2312 }
2313
2314 fn get_or_create_pointer_id_for_touch(&self, touch_id: i32) -> i32 {
2317 let mut active_pointer_ids = self.active_pointer_ids.borrow_mut();
2318
2319 if let Some(&pointer_id) = active_pointer_ids.get(&touch_id) {
2320 return pointer_id;
2321 }
2322
2323 let pointer_id = self.next_touch_pointer_id.get();
2324 active_pointer_ids.insert(touch_id, pointer_id);
2325 self.next_touch_pointer_id.set(pointer_id + 1);
2326 pointer_id
2327 }
2328
2329 fn remove_pointer_id_for_touch(&self, touch_id: i32) {
2331 self.active_pointer_ids.borrow_mut().remove(&touch_id);
2332 }
2333
2334 fn is_primary_pointer(&self, pointer_id: i32) -> bool {
2337 self.active_pointer_ids
2340 .borrow()
2341 .values()
2342 .min()
2343 .is_some_and(|primary_pointer| *primary_pointer == pointer_id)
2344 }
2345
2346 #[allow(clippy::too_many_arguments)]
2350 fn fire_pointer_event_for_touch(
2351 &self,
2352 target_element: &Element,
2353 touch: &Touch,
2354 pointer_id: i32,
2355 event_name: &str,
2356 is_primary: bool,
2357 input_event: &ConstellationInputEvent,
2358 hit_test_result: &HitTestResult,
2359 can_gc: CanGc,
2360 ) {
2361 let mut targets: Vec<DomRoot<Node>> = vec![];
2363 let mut current: Option<DomRoot<Node>> = Some(DomRoot::from_ref(target_element.upcast()));
2364 while let Some(node) = current {
2365 targets.push(DomRoot::from_ref(&*node));
2366 current = node.parent_in_flat_tree();
2367 }
2368
2369 if event_name == "pointerenter" {
2371 targets.reverse();
2372 }
2373
2374 for target in targets {
2375 let pointer_event = touch.to_pointer_event(
2376 &self.window,
2377 event_name,
2378 pointer_id,
2379 is_primary,
2380 input_event.active_keyboard_modifiers,
2381 false,
2382 Some(hit_test_result.point_in_node),
2383 can_gc,
2384 );
2385 pointer_event
2386 .upcast::<Event>()
2387 .fire(target.upcast(), can_gc);
2388 }
2389 }
2390
2391 pub(crate) fn has_assigned_access_key(&self, element: &HTMLElement) -> bool {
2392 self.access_key_handlers
2393 .borrow()
2394 .values()
2395 .any(|value| &**value == element)
2396 }
2397
2398 pub(crate) fn unassign_access_key(&self, element: &HTMLElement) {
2399 self.access_key_handlers
2400 .borrow_mut()
2401 .retain(|_, value| &**value != element)
2402 }
2403
2404 pub(crate) fn assign_access_key(&self, element: &HTMLElement, code: Code) {
2405 let mut access_key_handlers = self.access_key_handlers.borrow_mut();
2406 access_key_handlers
2408 .entry(code.into())
2409 .or_insert(Dom::from_ref(element));
2410 }
2411
2412 fn maybe_handle_accesskey(&self, event: &KeyboardEvent, can_gc: CanGc) -> bool {
2413 #[cfg(target_os = "macos")]
2414 let access_key_modifiers = Modifiers::CONTROL | Modifiers::ALT;
2415 #[cfg(not(target_os = "macos"))]
2416 let access_key_modifiers = Modifiers::SHIFT | Modifiers::ALT;
2417
2418 if event.modifiers() != access_key_modifiers {
2419 return false;
2420 }
2421
2422 let Ok(code) = Code::from_str(&event.Code().str()) else {
2423 return false;
2424 };
2425
2426 let Some(html_element) = self
2427 .access_key_handlers
2428 .borrow()
2429 .get(&code.into())
2430 .map(|html_element| html_element.as_rooted())
2431 else {
2432 return false;
2433 };
2434
2435 let Ok(command) = InteractiveElementCommand::try_from(&*html_element) else {
2443 return false;
2444 };
2445
2446 if command.disabled() || command.hidden() {
2447 return false;
2448 }
2449
2450 let node = html_element.upcast::<Node>();
2451 if !node.is_connected() {
2452 return false;
2453 }
2454
2455 for node in node.inclusive_ancestors(ShadowIncluding::Yes) {
2456 if node
2457 .downcast::<HTMLElement>()
2458 .is_some_and(|html_element| html_element.Hidden())
2459 {
2460 return false;
2461 }
2462 }
2463
2464 self.focus_and_scroll_to_element_for_key_event(html_element.upcast(), can_gc);
2467 command.perform_action(can_gc);
2468 true
2469 }
2470
2471 fn focus_and_scroll_to_element_for_key_event(&self, element: &Element, can_gc: CanGc) {
2472 element
2473 .owner_document()
2474 .request_focus(Some(element), FocusInitiator::Keyboard, can_gc);
2475 let scroll_axis = ScrollAxisState {
2476 position: ScrollLogicalPosition::Center,
2477 requirement: ScrollRequirement::IfNotVisible,
2478 };
2479 element.scroll_into_view_with_options(
2480 ScrollBehavior::Auto,
2481 scroll_axis,
2482 scroll_axis,
2483 None,
2484 None,
2485 );
2486 }
2487}
2488
2489#[derive(Clone, Copy, PartialEq)]
2495enum SequentialFocusDirection {
2496 Forward,
2497 Backward,
2498}
2499
2500fn compare_tab_indices(a: i32, b: i32) -> Ordering {
2501 if a == b {
2502 Ordering::Equal
2503 } else if a == 0 {
2504 Ordering::Greater
2505 } else if b == 0 {
2506 Ordering::Less
2507 } else {
2508 a.cmp(&b)
2509 }
2510}
2511
2512pub(crate) fn character_to_code(character: char) -> Option<Code> {
2513 Some(match character.to_ascii_lowercase() {
2514 '`' => Code::Backquote,
2515 '\\' => Code::Backslash,
2516 '[' | '{' => Code::BracketLeft,
2517 ']' | '}' => Code::BracketRight,
2518 ',' | '<' => Code::Comma,
2519 '0' => Code::Digit0,
2520 '1' => Code::Digit1,
2521 '2' => Code::Digit2,
2522 '3' => Code::Digit3,
2523 '4' => Code::Digit4,
2524 '5' => Code::Digit5,
2525 '6' => Code::Digit6,
2526 '7' => Code::Digit7,
2527 '8' => Code::Digit8,
2528 '9' => Code::Digit9,
2529 '=' => Code::Equal,
2530 'a' => Code::KeyA,
2531 'b' => Code::KeyB,
2532 'c' => Code::KeyC,
2533 'd' => Code::KeyD,
2534 'e' => Code::KeyE,
2535 'f' => Code::KeyF,
2536 'g' => Code::KeyG,
2537 'h' => Code::KeyH,
2538 'i' => Code::KeyI,
2539 'j' => Code::KeyJ,
2540 'k' => Code::KeyK,
2541 'l' => Code::KeyL,
2542 'm' => Code::KeyM,
2543 'n' => Code::KeyN,
2544 'o' => Code::KeyO,
2545 'p' => Code::KeyP,
2546 'q' => Code::KeyQ,
2547 'r' => Code::KeyR,
2548 's' => Code::KeyS,
2549 't' => Code::KeyT,
2550 'u' => Code::KeyU,
2551 'v' => Code::KeyV,
2552 'w' => Code::KeyW,
2553 'x' => Code::KeyX,
2554 'y' => Code::KeyY,
2555 'z' => Code::KeyZ,
2556 '-' => Code::Minus,
2557 '.' => Code::Period,
2558 '\'' | '"' => Code::Quote,
2559 ';' => Code::Semicolon,
2560 '/' => Code::Slash,
2561 ' ' => Code::Space,
2562 _ => return None,
2563 })
2564}