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