1use std::array::from_ref;
6use std::cell::Cell;
7use std::f64::consts::PI;
8use std::mem;
9use std::time::{Duration, Instant};
10
11use embedder_traits::{
12 Cursor, EmbedderMsg, ImeEvent, InputEvent, InputEventId, InputEventOutcome, InputEventResult,
13 KeyboardEvent as EmbedderKeyboardEvent, MouseButton, MouseButtonAction, MouseButtonEvent,
14 MouseLeftViewportEvent, MouseMoveEvent, TouchEvent as EmbedderTouchEvent, TouchEventType,
15 TouchId, TouchPointerType, UntrustedNodeAddress, WheelEvent as EmbedderWheelEvent,
16};
17#[cfg(feature = "gamepad")]
18use embedder_traits::{
19 GamepadEvent as EmbedderGamepadEvent, GamepadSupportedHapticEffects, GamepadUpdateType,
20};
21use euclid::{Point2D, Vector2D};
22use js::context::{JSContext, NoGC};
23use keyboard_types::{
24 Code, CompositionState, Key, KeyState, KeyboardEvent as KeyboardTypesEvent, Modifiers, NamedKey,
25};
26use layout_api::{HitTestFlags, ScrollContainerQueryFlags, node_id_from_scroll_id};
27use rustc_hash::FxHashMap;
28use script_bindings::cell::DomRefCell;
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::ShadowRootBinding::ShadowRootMethods;
35use script_bindings::codegen::GenericBindings::TouchBinding::TouchMethods;
36use script_bindings::codegen::GenericBindings::WindowBinding::{ScrollBehavior, WindowMethods};
37use script_bindings::inheritance::Castable;
38use script_bindings::num::Finite;
39use script_bindings::root::{Dom, DomRoot, DomSlice};
40use script_bindings::str::DOMString;
41use script_traits::{ConstellationInputEvent, MouseButtons};
42use servo_config::pref;
43use servo_constellation_traits::{KeyboardScroll, ScriptToConstellationMessage};
44use style::Atom;
45use style_traits::CSSPixel;
46use webrender_api::ExternalScrollId;
47
48#[cfg(feature = "gamepad")]
49use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionName;
50use crate::dom::bindings::inheritance::{ElementTypeId, HTMLElementTypeId, NodeTypeId};
51use crate::dom::bindings::refcounted::Trusted;
52use crate::dom::bindings::root::MutNullableDom;
53use crate::dom::bindings::trace::NoTrace;
54use crate::dom::document::FireMouseEventType;
55use crate::dom::document::editing::{
56 editing_action_from_composition_event, editing_action_from_keyboard_event,
57};
58use crate::dom::document::focus::FocusableArea;
59use crate::dom::document::interactive_element_command::InteractiveElementCommand;
60use crate::dom::event::{EventBubbles, EventCancelable, EventComposed, EventFlags};
61#[cfg(feature = "gamepad")]
62use crate::dom::gamepad::gamepad::{Gamepad, contains_user_gesture};
63#[cfg(feature = "gamepad")]
64use crate::dom::gamepad::gamepadevent::GamepadEventType;
65use crate::dom::html::form_controls::htmlinputelement::HTMLInputElement;
66use crate::dom::inputevent::HitTestResult;
67use crate::dom::iterators::ShadowIncluding;
68use crate::dom::keyboardevent::KeyboardEvent;
69use crate::dom::node::focus::FocusTrigger;
70use crate::dom::node::{self, Node, NodeTraits};
71use crate::dom::pointerevent::{PointerEvent, PointerId};
72use crate::dom::types::{
73 CompositionEvent, Element, Event, EventTarget, GlobalScope, HTMLAnchorElement, HTMLElement,
74 HTMLLabelElement, MouseEvent, Touch, TouchEvent, TouchList, WheelEvent, Window,
75};
76use crate::dom::window::scrolling_box::{ScrollAxisState, ScrollRequirement, ScrollingBoxAxis};
77use crate::drag::drag_gesture::DragGesture;
78use crate::realms::enter_auto_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.reset_click_count();
124 }
125 }
126
127 fn increment_click_count(
128 &mut self,
129 button: MouseButton,
130 point: Point2D<f32, CSSPixel>,
131 ) -> usize {
132 self.time = Some(Instant::now());
133 self.point = Some(point);
134 self.button = Some(button);
135 self.count += 1;
136 self.count
137 }
138
139 fn reset_click_count(&mut self) {
140 self.count = 0;
141 self.time = None;
142 self.point = None;
143 }
144}
145
146#[derive(JSTraceable, MallocSizeOf)]
150#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
151pub(crate) struct DocumentEventHandler {
152 window: Dom<Window>,
154 #[no_trace]
156 #[ignore_malloc_size_of = "InputEvent contains data from outside crates"]
157 pending_input_events: DomRefCell<Vec<ConstellationInputEvent>>,
158 mouse_move_event_index: DomRefCell<Option<usize>>,
160 #[no_trace]
162 #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
163 coalesced_mouse_move_event_ids: DomRefCell<Vec<InputEventId>>,
164 wheel_event_index: DomRefCell<Option<usize>>,
169 #[no_trace]
171 #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
172 coalesced_wheel_event_ids: DomRefCell<Vec<InputEventId>>,
173 click_counting_info: DomRefCell<ClickCountingInfo>,
175 #[no_trace]
176 last_mouse_button_down_point: Cell<Option<Point2D<f32, CSSPixel>>>,
177 #[no_trace]
181 mouse_button_state: Cell<MouseButtons>,
182 current_hover_target: MutNullableDom<Element>,
184 current_active_element: MutNullableDom<Element>,
187 most_recently_clicked_element: MutNullableDom<Element>,
189 #[no_trace]
191 most_recent_mousemove_point: Cell<Option<Point2D<f32, CSSPixel>>>,
192 #[no_trace]
195 current_cursor: Cell<Option<Cursor>>,
196 active_touch_points: DomRefCell<Vec<Dom<Touch>>>,
198 touch_sequence_click_allowed: Cell<bool>,
200 #[no_trace]
202 active_keyboard_modifiers: Cell<Modifiers>,
203 active_pointer_ids: DomRefCell<FxHashMap<i32, i32>>,
205 next_touch_pointer_id: Cell<i32>,
207 access_key_handlers: DomRefCell<FxHashMap<NoTrace<Code>, Dom<HTMLElement>>>,
209 pending_pointer_capture: DomRefCell<FxHashMap<i32, Dom<Element>>>,
213 pointer_capture_target: DomRefCell<FxHashMap<i32, Dom<Element>>>,
216 drag_gesture: DomRefCell<Option<DragGesture>>,
219}
220
221impl DocumentEventHandler {
222 pub(crate) fn new(window: &Window) -> Self {
223 Self {
224 window: Dom::from_ref(window),
225 pending_input_events: Default::default(),
226 mouse_move_event_index: Default::default(),
227 coalesced_mouse_move_event_ids: Default::default(),
228 wheel_event_index: Default::default(),
229 coalesced_wheel_event_ids: Default::default(),
230 click_counting_info: Default::default(),
231 last_mouse_button_down_point: Default::default(),
232 mouse_button_state: Cell::new(MouseButtons::empty()),
233 current_hover_target: Default::default(),
234 current_active_element: Default::default(),
235 most_recently_clicked_element: Default::default(),
236 most_recent_mousemove_point: Default::default(),
237 current_cursor: Default::default(),
238 active_touch_points: Default::default(),
239 touch_sequence_click_allowed: Cell::new(true),
240 active_keyboard_modifiers: Default::default(),
241 active_pointer_ids: Default::default(),
242 next_touch_pointer_id: Cell::new(1),
243 access_key_handlers: Default::default(),
244 pending_pointer_capture: Default::default(),
245 pointer_capture_target: Default::default(),
246 drag_gesture: Default::default(),
247 }
248 }
249
250 pub(crate) fn note_pending_input_event(&self, event: ConstellationInputEvent) {
252 let mut pending_input_events = self.pending_input_events.borrow_mut();
253 if matches!(event.event.event, InputEvent::MouseMove(..)) {
254 if let Some(mouse_move_event) = self
256 .mouse_move_event_index
257 .borrow()
258 .and_then(|index| pending_input_events.get_mut(index))
259 {
260 self.coalesced_mouse_move_event_ids
261 .borrow_mut()
262 .push(mouse_move_event.event.id);
263 *mouse_move_event = event;
264 return;
265 }
266
267 *self.mouse_move_event_index.borrow_mut() = Some(pending_input_events.len());
268 }
269
270 if let InputEvent::Wheel(ref new_wheel_event) = event.event.event {
271 if let Some(existing_constellation_wheel_event) = self
273 .wheel_event_index
274 .borrow()
275 .and_then(|index| pending_input_events.get_mut(index)) &&
276 let InputEvent::Wheel(ref mut existing_wheel_event) =
277 existing_constellation_wheel_event.event.event &&
278 existing_wheel_event.delta.mode == new_wheel_event.delta.mode
279 {
280 self.coalesced_wheel_event_ids
281 .borrow_mut()
282 .push(existing_constellation_wheel_event.event.id);
283 existing_wheel_event.delta.x += new_wheel_event.delta.x;
284 existing_wheel_event.delta.y += new_wheel_event.delta.y;
285 existing_wheel_event.delta.z += new_wheel_event.delta.z;
286 existing_wheel_event.point = new_wheel_event.point;
287 existing_constellation_wheel_event.event.id = event.event.id;
288 return;
289 }
290
291 *self.wheel_event_index.borrow_mut() = Some(pending_input_events.len());
292 }
293
294 pending_input_events.push(event);
295 }
296
297 pub(crate) fn has_pending_input_events(&self) -> bool {
300 !self.pending_input_events.borrow().is_empty()
301 }
302
303 pub(crate) fn alternate_action_keyboard_modifier_active(&self) -> bool {
304 #[cfg(target_os = "macos")]
305 {
306 self.active_keyboard_modifiers
307 .get()
308 .contains(Modifiers::META)
309 }
310 #[cfg(not(target_os = "macos"))]
311 {
312 self.active_keyboard_modifiers
313 .get()
314 .contains(Modifiers::CONTROL)
315 }
316 }
317
318 pub(crate) fn handle_pending_input_events(&self, cx: &mut JSContext) {
319 debug_assert!(
320 !self.pending_input_events.borrow().is_empty(),
321 "handle_pending_input_events called with no events"
322 );
323 let mut realm = enter_auto_realm(cx, &*self.window);
324 let cx = &mut realm.current_realm();
325
326 *self.mouse_move_event_index.safe_borrow_mut(cx.no_gc()) = None;
328 *self.wheel_event_index.safe_borrow_mut(cx.no_gc()) = None;
329 let pending_input_events =
330 mem::take(&mut *self.pending_input_events.safe_borrow_mut(cx.no_gc()));
331 let mut coalesced_mouse_move_event_ids = mem::take(
332 &mut *self
333 .coalesced_mouse_move_event_ids
334 .safe_borrow_mut(cx.no_gc()),
335 );
336 let mut coalesced_wheel_event_ids =
337 mem::take(&mut *self.coalesced_wheel_event_ids.safe_borrow_mut(cx.no_gc()));
338
339 let mut input_event_outcomes = Vec::with_capacity(
340 pending_input_events.len() +
341 coalesced_mouse_move_event_ids.len() +
342 coalesced_wheel_event_ids.len(),
343 );
344 for event in pending_input_events {
350 self.active_keyboard_modifiers
351 .set(event.active_keyboard_modifiers);
352 let result = match event.event.event {
353 InputEvent::MouseButton(mouse_button_event) => {
354 self.handle_native_mouse_button_event(cx, mouse_button_event, &event);
355 InputEventResult::default()
356 },
357 InputEvent::MouseMove(_) => {
358 self.handle_native_mouse_move_event(cx, &event);
359 input_event_outcomes.extend(
360 mem::take(&mut coalesced_mouse_move_event_ids)
361 .into_iter()
362 .map(|id| InputEventOutcome {
363 id,
364 result: InputEventResult::default(),
365 }),
366 );
367 InputEventResult::default()
368 },
369 InputEvent::MouseLeftViewport(mouse_leave_event) => {
370 self.handle_mouse_left_viewport_event(cx, &event, &mouse_leave_event);
371 InputEventResult::default()
372 },
373 InputEvent::Touch(touch_event) => self.handle_touch_event(cx, touch_event, &event),
374 InputEvent::Wheel(wheel_event) => {
375 let result = self.handle_wheel_event(cx, wheel_event, &event);
376 input_event_outcomes.extend(
377 mem::take(&mut coalesced_wheel_event_ids)
378 .into_iter()
379 .map(|id| InputEventOutcome { id, result }),
380 );
381 result
382 },
383 InputEvent::Keyboard(keyboard_event) => {
384 self.handle_keyboard_event(cx, keyboard_event)
385 },
386 InputEvent::Ime(ime_event) => self.handle_ime_event(cx, ime_event),
387 #[cfg(feature = "gamepad")]
388 InputEvent::Gamepad(gamepad_event) => {
389 self.handle_gamepad_event(gamepad_event);
390 InputEventResult::default()
391 },
392 InputEvent::EditingAction(clipboard_action) => {
393 let document = self.window.Document();
394 let focused_node = document
395 .focus_handler()
396 .focused_area()
397 .dom_anchor(&document);
398 let editing_context = document.editing_context(cx.no_gc(), &focused_node);
399 document.handle_clipboard_action(cx, &editing_context, clipboard_action)
400 },
401 };
402
403 input_event_outcomes.push(InputEventOutcome {
404 id: event.event.id,
405 result,
406 });
407 }
408
409 self.notify_embedder_that_events_were_handled(input_event_outcomes);
410 }
411
412 fn notify_embedder_that_events_were_handled(
413 &self,
414 input_event_outcomes: Vec<InputEventOutcome>,
415 ) {
416 let trusted_window = Trusted::new(&*self.window);
419 self.window
420 .as_global_scope()
421 .task_manager()
422 .dom_manipulation_task_source()
423 .queue_unconditionally(task!(notify_webdriver_input_event_completed: move || {
424 let window = trusted_window.root();
425 window.send_to_embedder(
426 EmbedderMsg::InputEventsHandled(window.webview_id(), input_event_outcomes));
427 }));
428 }
429
430 pub(crate) fn target_for_events_following_focus(&self) -> DomRoot<EventTarget> {
443 let document = self.window.Document();
444 match &*document.focus_handler().focused_area() {
445 FocusableArea::Node { node, .. } => DomRoot::from_ref(node.upcast()),
446 FocusableArea::IFrameViewport { iframe_element, .. } => {
447 DomRoot::from_ref(iframe_element.upcast())
448 },
449 FocusableArea::Viewport => document
450 .GetBody()
451 .map(DomRoot::upcast)
452 .or_else(|| document.GetDocumentElement().map(DomRoot::upcast))
453 .unwrap_or_else(|| DomRoot::from_ref(self.window.upcast())),
454 }
455 }
456
457 pub(crate) fn set_cursor(&self, cursor: Option<Cursor>) {
458 if cursor == self.current_cursor.get() {
459 return;
460 }
461 self.current_cursor.set(cursor);
462 self.window.send_to_embedder(EmbedderMsg::SetCursor(
463 self.window.webview_id(),
464 cursor.unwrap_or_default(),
465 ));
466 }
467
468 fn handle_mouse_left_viewport_event(
469 &self,
470 cx: &mut JSContext,
471 input_event: &ConstellationInputEvent,
472 mouse_leave_event: &MouseLeftViewportEvent,
473 ) {
474 if let Some(current_hover_target) = self.current_hover_target.get() {
475 let current_hover_target = current_hover_target.upcast::<Node>();
476 for element in current_hover_target
477 .inclusive_ancestors(ShadowIncluding::Yes)
478 .filter_map(DomRoot::downcast::<Element>)
479 {
480 element.set_hover_state(false);
481 }
482
483 if let Some(hit_test_result) =
484 self.most_recent_mousemove_point.get().and_then(|point| {
485 self.window
486 .hit_test_from_point_in_viewport(HitTestFlags::empty(), point)
487 })
488 {
489 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
490 cx,
491 &self.window,
492 FireMouseEventType::Out,
493 &hit_test_result,
494 input_event,
495 );
496
497 mouse_out_event
499 .to_pointer_hover_event(cx, "pointerout")
500 .upcast::<Event>()
501 .fire(cx, current_hover_target.upcast());
502
503 mouse_out_event
504 .upcast::<Event>()
505 .fire(cx, current_hover_target.upcast());
506
507 self.handle_mouse_enter_leave_event(
508 cx,
509 current_hover_target,
510 None,
511 FireMouseEventType::Leave,
512 &hit_test_result,
513 input_event,
514 );
515 }
516 }
517
518 if !mouse_leave_event.focus_moving_to_another_iframe {
523 self.window
527 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
528 self.set_cursor(None);
529 } else {
530 self.current_cursor.set(None);
531 }
532
533 self.current_hover_target.set(None);
534 self.most_recent_mousemove_point.set(None);
535 }
536
537 fn handle_mouse_enter_leave_event(
538 &self,
539 cx: &mut JSContext,
540 event_target: &Node,
541 related_target: Option<&Node>,
542 event_type: FireMouseEventType,
543 hit_test_result: &HitTestResult,
544 input_event: &ConstellationInputEvent,
545 ) {
546 assert!(matches!(
547 event_type,
548 FireMouseEventType::Enter | FireMouseEventType::Leave
549 ));
550
551 let common_ancestor = match related_target.as_ref() {
552 Some(related_target) => event_target
553 .common_ancestor_in_flat_tree(cx.no_gc(), related_target)
554 .unwrap_or_else(|| DomRoot::from_ref(event_target)),
555 None => DomRoot::from_ref(event_target),
556 };
557
558 let mut targets: Vec<_> = event_target
561 .inclusive_ancestors_in_flat_tree_unrooted(cx.no_gc())
562 .take_while(|node| *node != *common_ancestor)
563 .map(|node| node.as_rooted())
564 .collect();
565
566 if event_type == FireMouseEventType::Enter {
569 targets.reverse();
570 }
571
572 let pointer_event_name = match event_type {
573 FireMouseEventType::Enter => "pointerenter",
574 FireMouseEventType::Leave => "pointerleave",
575 _ => unreachable!(),
576 };
577
578 for target in targets {
579 let mouse_event = MouseEvent::new_for_platform_motion_event(
580 cx,
581 &self.window,
582 event_type,
583 hit_test_result,
584 input_event,
585 );
586 mouse_event
587 .upcast::<Event>()
588 .set_related_target(related_target.as_ref().map(|target| target.upcast()));
589
590 mouse_event
592 .to_pointer_hover_event(cx, pointer_event_name)
593 .upcast::<Event>()
594 .fire(cx, target.upcast());
595
596 mouse_event.upcast::<Event>().fire(cx, target.upcast());
598 }
599 }
600
601 fn handle_native_mouse_move_event(
603 &self,
604 cx: &mut JSContext,
605 input_event: &ConstellationInputEvent,
606 ) {
607 let pointer_id = PointerId::Mouse as i32;
610 let released_disconnected =
611 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
612
613 let hit_test_flags = if self
614 .drag_gesture
615 .borrow()
616 .as_ref()
617 .is_some_and(DragGesture::need_dom_position_from_hit_test)
618 {
619 HitTestFlags::IncludeDomPosition
620 } else {
621 HitTestFlags::empty()
622 };
623
624 let Some(hit_test_result) = self
629 .window
630 .hit_test_from_input_event(hit_test_flags, input_event)
631 else {
632 return;
633 };
634
635 {
636 let mut maybe_drag_gesture = self.drag_gesture.borrow_mut();
637 if maybe_drag_gesture.as_mut().is_some_and(|drag_gesture| {
638 !drag_gesture.handle_mouse_move_event(cx, input_event, &hit_test_result)
639 }) {
640 *maybe_drag_gesture = None;
641 }
642 }
643
644 let old_mouse_move_point = self
645 .most_recent_mousemove_point
646 .replace(Some(hit_test_result.point_in_frame));
647 let is_compatibility_event_for_touch = matches!(
650 input_event.event.event,
651 InputEvent::MouseMove(ref event) if event.is_compatibility_event_for_touch
652 );
653 if !is_compatibility_event_for_touch &&
654 old_mouse_move_point == Some(hit_test_result.point_in_frame)
655 {
656 return;
657 }
658
659 self.set_cursor(Some(hit_test_result.cursor));
661
662 let Some(new_target) = hit_test_result
663 .node
664 .inclusive_ancestors(ShadowIncluding::Yes)
665 .find_map(DomRoot::downcast::<Element>)
666 else {
667 return;
668 };
669
670 let capture_is_active = self.get_pointer_capture_target(pointer_id).is_some();
671 let old_hover_target = self.current_hover_target.get();
672 let target_has_changed = old_hover_target
673 .as_ref()
674 .is_none_or(|old_target| *old_target != new_target);
675
676 if target_has_changed {
679 if let Some(old_target) = self.current_hover_target.get() {
681 let old_target_is_ancestor_of_new_target = old_target
682 .upcast::<Node>()
683 .is_ancestor_of(new_target.upcast::<Node>());
684
685 if !old_target_is_ancestor_of_new_target {
688 for element in old_target
689 .upcast::<Node>()
690 .inclusive_ancestors(ShadowIncluding::Yes)
691 .filter_map(DomRoot::downcast::<Element>)
692 {
693 element.set_hover_state(false);
694 }
695 }
696
697 if !capture_is_active {
698 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
699 cx,
700 &self.window,
701 FireMouseEventType::Out,
702 &hit_test_result,
703 input_event,
704 );
705 mouse_out_event
706 .upcast::<Event>()
707 .set_related_target(Some(new_target.upcast()));
708
709 mouse_out_event
711 .to_pointer_hover_event(cx, "pointerout")
712 .upcast::<Event>()
713 .fire(cx, old_target.upcast());
714
715 mouse_out_event
716 .upcast::<Event>()
717 .fire(cx, old_target.upcast());
718
719 if !old_target_is_ancestor_of_new_target {
720 let event_target = old_target.upcast::<Node>();
721 let moving_into = Some(new_target.upcast::<Node>());
722 self.handle_mouse_enter_leave_event(
723 cx,
724 event_target,
725 moving_into,
726 FireMouseEventType::Leave,
727 &hit_test_result,
728 input_event,
729 );
730 }
731 }
732 }
733
734 for element in new_target
736 .upcast::<Node>()
737 .inclusive_ancestors(ShadowIncluding::Yes)
738 .filter_map(DomRoot::downcast::<Element>)
739 {
740 element.set_hover_state(true);
741 }
742
743 if !capture_is_active {
744 let mouse_over_event = MouseEvent::new_for_platform_motion_event(
745 cx,
746 &self.window,
747 FireMouseEventType::Over,
748 &hit_test_result,
749 input_event,
750 );
751 mouse_over_event
752 .upcast::<Event>()
753 .set_related_target(old_hover_target.as_ref().map(|target| target.upcast()));
754
755 mouse_over_event
757 .to_pointer_hover_event(cx, "pointerover")
758 .upcast::<Event>()
759 .dispatch(cx, new_target.upcast(), false);
760
761 mouse_over_event
762 .upcast::<Event>()
763 .dispatch(cx, new_target.upcast(), false);
764
765 let moving_from = old_hover_target
766 .as_ref()
767 .map(|old_target| old_target.upcast::<Node>());
768 let event_target = new_target.upcast::<Node>();
769 self.handle_mouse_enter_leave_event(
770 cx,
771 event_target,
772 moving_from,
773 FireMouseEventType::Enter,
774 &hit_test_result,
775 input_event,
776 );
777 }
778 }
779
780 let mouse_event = MouseEvent::new_for_platform_motion_event(
783 cx,
784 &self.window,
785 FireMouseEventType::Move,
786 &hit_test_result,
787 input_event,
788 );
789
790 let pointer_target = self
795 .get_pointer_capture_target(pointer_id)
796 .map(DomRoot::upcast::<EventTarget>)
797 .unwrap_or_else(|| DomRoot::from_ref(new_target.upcast::<EventTarget>()));
798
799 let pointer_event = mouse_event.to_pointer_event(cx, Atom::from("pointermove"));
800 pointer_event.upcast::<Event>().set_composed(true);
801 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
802
803 if !released_disconnected {
807 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
808 }
809
810 mouse_event.upcast::<Event>().fire(cx, &pointer_target);
812
813 self.update_current_hover_target_and_status(cx.no_gc(), Some(new_target));
814 }
815
816 fn update_current_hover_target_and_status(
817 &self,
818 no_gc: &NoGC,
819 new_hover_target: Option<DomRoot<Element>>,
820 ) {
821 let current_hover_target = self.current_hover_target.get();
822 if current_hover_target == new_hover_target {
823 return;
824 }
825
826 let previous_hover_target = self.current_hover_target.get();
827 self.current_hover_target.set(new_hover_target.as_deref());
828
829 if let Some(target) = self.current_hover_target.get() &&
832 let Some(anchor) = target
833 .upcast::<Node>()
834 .inclusive_ancestors(ShadowIncluding::Yes)
835 .find_map(DomRoot::downcast::<HTMLAnchorElement>)
836 {
837 let status = anchor
838 .full_href_url_for_user_interface(no_gc)
839 .map(|url| url.to_string());
840 self.window
841 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), status));
842 return;
843 }
844
845 if previous_hover_target.is_none_or(|previous_hover_target| {
850 previous_hover_target
851 .upcast::<Node>()
852 .inclusive_ancestors(ShadowIncluding::Yes)
853 .any(|node| node.is::<HTMLAnchorElement>())
854 }) {
855 self.window
856 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
857 }
858 }
859
860 pub(crate) fn handle_refresh_cursor(&self) {
861 let Some(most_recent_mousemove_point) = self.most_recent_mousemove_point.get() else {
862 return;
863 };
864
865 let Some(hit_test_result) = self
866 .window
867 .hit_test_from_point_in_viewport(HitTestFlags::empty(), most_recent_mousemove_point)
868 else {
869 return;
870 };
871
872 self.set_cursor(Some(hit_test_result.cursor));
873 }
874
875 fn set_active_element(&self, no_gc: &NoGC, original_target: &Element) {
876 let find_element_for_activation = |element: &Element| {
877 let node: &Node = element.upcast();
878 if node.is_in_ua_widget() &&
879 let Some(containing_shadow_root) = node.containing_shadow_root()
880 {
881 return containing_shadow_root.Host();
882 }
883
884 if node.type_id() ==
886 NodeTypeId::Element(ElementTypeId::HTMLElement(
887 HTMLElementTypeId::HTMLLabelElement,
888 ))
889 {
890 let label = element.downcast::<HTMLLabelElement>().unwrap();
891 if let Some(control) = label.GetControl(no_gc) {
892 return DomRoot::from_ref(control.upcast::<Element>());
893 }
894 }
895
896 DomRoot::from_ref(element)
897 };
898 let element_for_activation = find_element_for_activation(original_target);
899
900 if let Some(currently_active_element) = self.current_active_element.get() {
903 if currently_active_element == element_for_activation {
904 return;
905 }
906 self.unset_active_element();
907 }
908
909 element_for_activation.set_active_state(true);
910 self.current_active_element
911 .set(Some(&*element_for_activation));
912 }
913
914 fn unset_active_element(&self) {
915 if let Some(active_element) = self.current_active_element.take() {
916 active_element.set_active_state(false);
917 }
918 }
919
920 fn handle_native_mouse_button_event(
923 &self,
924 cx: &mut JSContext,
925 mouse_button_event: MouseButtonEvent,
926 input_event: &ConstellationInputEvent,
927 ) {
928 {
929 let mut maybe_drag_gesture = self.drag_gesture.borrow_mut();
930 if maybe_drag_gesture
931 .as_mut()
932 .is_some_and(|drag_gesture| !drag_gesture.handle_mouse_button_event(input_event))
933 {
934 *maybe_drag_gesture = None;
935 }
936 }
937
938 let flags = if input_event.primary_button_is_pressed() ||
939 input_event.auxiliary_button_is_pressed()
940 {
941 HitTestFlags::IncludeDomPosition
942 } else {
943 HitTestFlags::empty()
944 };
945 let Some(hit_test_result) = self.window.hit_test_from_input_event(flags, input_event)
947 else {
948 return;
949 };
950
951 debug!(
952 "{:?}: at {:?}",
953 mouse_button_event.action, hit_test_result.point_in_frame
954 );
955
956 let document = self.window.Document();
959 if mouse_button_event.action == MouseButtonAction::Down {
960 document
961 .focus_handler()
962 .set_sequential_focus_navigation_starting_point(&hit_test_result.node);
963 }
964
965 let Some(element) = hit_test_result
966 .node
967 .inclusive_ancestors(ShadowIncluding::Yes)
968 .find_map(DomRoot::downcast::<Element>)
969 else {
970 return;
971 };
972
973 let node = element.upcast::<Node>();
974 debug!("{:?} on {:?}", mouse_button_event.action, node.debug_str());
975
976 if mouse_button_event.button == MouseButton::Primary {
981 if mouse_button_event.action == MouseButtonAction::Down {
982 self.set_active_element(cx.no_gc(), &element);
983 }
984 if mouse_button_event.action == MouseButtonAction::Up {
985 self.unset_active_element();
986 }
987 }
988
989 if element.is_actually_disabled() {
993 return;
994 }
995
996 let mouse_event_type = match mouse_button_event.action {
997 embedder_traits::MouseButtonAction::Up => atom!("mouseup"),
998 embedder_traits::MouseButtonAction::Down => atom!("mousedown"),
999 };
1000
1001 if mouse_button_event.action == MouseButtonAction::Down {
1008 self.click_counting_info
1009 .safe_borrow_mut(cx.no_gc())
1010 .reset_click_count_if_necessary(
1011 mouse_button_event.button,
1012 hit_test_result.point_in_frame,
1013 );
1014 }
1015
1016 let mouse_event = MouseEvent::for_platform_button_event(
1017 cx,
1018 mouse_event_type,
1019 mouse_button_event,
1020 input_event.pressed_mouse_buttons,
1021 &self.window,
1022 &hit_test_result,
1023 input_event.active_keyboard_modifiers,
1024 self.click_counting_info.borrow().count + 1,
1025 );
1026
1027 match mouse_button_event.action {
1028 MouseButtonAction::Down => {
1029 self.last_mouse_button_down_point
1030 .set(Some(hit_test_result.point_in_frame));
1031
1032 let pointer_event_name = if self.mouse_button_state.get().is_empty() {
1034 "pointerdown".into()
1039 } else {
1040 "pointermove".into()
1046 };
1047 let pointer_event = mouse_event.to_pointer_event(cx, pointer_event_name);
1048
1049 let pointer_id = PointerId::Mouse as i32;
1051
1052 let released_disconnected =
1054 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
1055
1056 let pointer_target = self
1058 .get_pointer_capture_target(pointer_id)
1059 .map(DomRoot::upcast::<EventTarget>)
1060 .unwrap_or_else(|| DomRoot::from_ref(node.upcast::<EventTarget>()));
1061
1062 self.mouse_button_state
1064 .set(input_event.pressed_mouse_buttons);
1065
1066 let pointer_event_result =
1067 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1068
1069 if !released_disconnected {
1073 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
1074 }
1075
1076 let result = mouse_event
1078 .upcast::<Event>()
1079 .dispatch(cx, node.upcast(), false);
1080
1081 if result &&
1085 pointer_event_result &&
1086 let Some(node) = mouse_event
1087 .upcast::<Event>()
1088 .GetTarget()
1089 .and_then(DomRoot::downcast::<Node>)
1090 {
1091 let editing_context = document.editing_context(cx, &node);
1092 editing_context.handle_mousedown_event(cx, &mouse_event, &hit_test_result);
1093 }
1094
1095 if result {
1098 document
1102 .focus_handler()
1103 .focus(cx, &node.find_click_focusable_area(cx));
1104 }
1105
1106 if let MouseButton::Secondary = mouse_button_event.button {
1109 self.maybe_show_context_menu(cx, node.upcast(), &hit_test_result, input_event);
1110 }
1111 },
1112 MouseButtonAction::Up => {
1114 let mouse_button_state = self.mouse_button_state.get();
1116 let exactly_one_button = mouse_button_state.exactly_one_button_pressed();
1117 let pointer_event_name = if exactly_one_button {
1118 "pointerup".into()
1123 } else {
1124 "pointermove".into()
1130 };
1131 let pointer_event = mouse_event.to_pointer_event(cx, pointer_event_name);
1132
1133 let pointer_id = PointerId::Mouse as i32;
1135
1136 let released_disconnected =
1138 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
1139
1140 let pointer_target = self
1142 .get_pointer_capture_target(pointer_id)
1143 .map(DomRoot::upcast::<EventTarget>)
1144 .unwrap_or_else(|| DomRoot::from_ref(node.upcast::<EventTarget>()));
1145
1146 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1147
1148 self.mouse_button_state
1151 .set(input_event.pressed_mouse_buttons);
1152
1153 if !released_disconnected {
1157 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
1158 }
1159
1160 if exactly_one_button {
1162 self.implicit_release_pointer_capture(cx, pointer_id, "mouse", true);
1163 }
1164
1165 mouse_event
1167 .upcast::<Event>()
1168 .dispatch(cx, node.upcast(), false);
1169
1170 self.click_counting_info
1174 .safe_borrow_mut(cx.no_gc())
1175 .increment_click_count(
1176 mouse_button_event.button,
1177 hit_test_result.point_in_frame,
1178 );
1179
1180 self.maybe_trigger_click_for_mouse_button_down_event(
1181 cx,
1182 mouse_button_event,
1183 input_event,
1184 &hit_test_result,
1185 &element,
1186 );
1187 },
1188 }
1189 }
1190
1191 fn maybe_trigger_click_for_mouse_button_down_event(
1194 &self,
1195 cx: &mut JSContext,
1196 event: MouseButtonEvent,
1197 input_event: &ConstellationInputEvent,
1198 hit_test_result: &HitTestResult,
1199 element: &Element,
1200 ) {
1201 if event.button != MouseButton::Primary {
1202 return;
1203 }
1204
1205 let Some(last_mouse_button_down_point) = self.last_mouse_button_down_point.take() else {
1206 return;
1207 };
1208
1209 let distance = last_mouse_button_down_point.distance_to(hit_test_result.point_in_frame);
1210 let maximum_click_distance = 10.0 * self.window.device_pixel_ratio().get();
1211 if distance > maximum_click_distance {
1212 return;
1213 }
1214
1215 self.most_recently_clicked_element.set(Some(element));
1219
1220 let click_count = self.click_counting_info.borrow().count;
1221 element.set_click_in_progress(true);
1222 MouseEvent::for_platform_button_event(
1223 cx,
1224 atom!("click"),
1225 event,
1226 input_event.pressed_mouse_buttons,
1227 &self.window,
1228 hit_test_result,
1229 input_event.active_keyboard_modifiers,
1230 click_count,
1231 )
1232 .upcast::<Event>()
1233 .dispatch(cx, element.upcast(), false);
1234 element.set_click_in_progress(false);
1235
1236 if click_count.is_multiple_of(2) {
1245 MouseEvent::for_platform_button_event(
1246 cx,
1247 Atom::from("dblclick"),
1248 event,
1249 input_event.pressed_mouse_buttons,
1250 &self.window,
1251 hit_test_result,
1252 input_event.active_keyboard_modifiers,
1253 2,
1254 )
1255 .upcast::<Event>()
1256 .dispatch(cx, element.upcast(), false);
1257 }
1258 }
1259
1260 fn maybe_show_context_menu(
1262 &self,
1263 cx: &mut JSContext,
1264 target: &EventTarget,
1265 hit_test_result: &HitTestResult,
1266 input_event: &ConstellationInputEvent,
1267 ) {
1268 let menu_event = PointerEvent::new(
1270 cx,
1271 &self.window, "contextmenu".into(), EventBubbles::Bubbles, EventCancelable::Cancelable, Some(&self.window), 0, hit_test_result.point_in_frame.to_i32(),
1278 hit_test_result.point_in_frame.to_i32(),
1279 hit_test_result
1280 .point_relative_to_initial_containing_block
1281 .to_i32(),
1282 input_event.active_keyboard_modifiers,
1283 MouseButton::Secondary,
1284 input_event.pressed_mouse_buttons,
1285 None, None, PointerId::Mouse as i32, 1, 1, 0.5, 0.0, 0, 0, 0, PI / 2.0, 0.0, DOMString::from_static("mouse"), true, vec![], vec![], );
1302 menu_event.upcast::<Event>().set_composed(true);
1303
1304 let result = menu_event.upcast::<Event>().fire(cx, target);
1306
1307 if result {
1309 self.window
1310 .Document()
1311 .embedder_controls()
1312 .show_context_menu(cx.no_gc(), hit_test_result);
1313 };
1314 }
1315
1316 fn handle_touch_event(
1317 &self,
1318 cx: &mut JSContext,
1319 event: EmbedderTouchEvent,
1320 input_event: &ConstellationInputEvent,
1321 ) -> InputEventResult {
1322 let flags = HitTestFlags::empty();
1323 let Some(hit_test_result) = self.window.hit_test_from_input_event(flags, input_event)
1325 else {
1326 self.update_active_touch_points_when_early_return(event);
1327 return Default::default();
1328 };
1329
1330 let TouchId(identifier) = event.touch_id;
1331
1332 let Some(element) = hit_test_result
1333 .node
1334 .inclusive_ancestors(ShadowIncluding::Yes)
1335 .find_map(DomRoot::downcast::<Element>)
1336 else {
1337 self.update_active_touch_points_when_early_return(event);
1338 return Default::default();
1339 };
1340
1341 let current_target = DomRoot::upcast::<EventTarget>(element.clone());
1342 let window = &*self.window;
1343
1344 let client_x = Finite::wrap(hit_test_result.point_in_frame.x as f64);
1345 let client_y = Finite::wrap(hit_test_result.point_in_frame.y as f64);
1346 let page_x =
1347 Finite::wrap(hit_test_result.point_in_frame.x as f64 + window.PageXOffset() as f64);
1348 let page_y =
1349 Finite::wrap(hit_test_result.point_in_frame.y as f64 + window.PageYOffset() as f64);
1350
1351 let pointer_touch = Touch::new(
1353 cx,
1354 window,
1355 identifier,
1356 ¤t_target,
1357 client_x,
1358 client_y, client_x,
1360 client_y,
1361 page_x,
1362 page_y,
1363 );
1364
1365 let pointer_event_name = match event.event_type {
1367 TouchEventType::Down => "pointerdown",
1368 TouchEventType::Move => "pointermove",
1369 TouchEventType::Up => "pointerup",
1370 TouchEventType::Cancel => "pointercancel",
1371 };
1372
1373 let pointer_type = match event.pointer_type {
1375 TouchPointerType::Pen => "pen",
1376 TouchPointerType::Touch => "touch",
1377 };
1378
1379 let pointer_id = self.get_or_create_pointer_id_for_touch(identifier);
1381 let is_primary = self.is_primary_pointer(pointer_id);
1382
1383 if matches!(event.event_type, TouchEventType::Down) {
1386 let pointer_over = pointer_touch.to_pointer_event(
1388 cx,
1389 window,
1390 "pointerover",
1391 pointer_id,
1392 is_primary,
1393 pointer_type,
1394 input_event.active_keyboard_modifiers,
1395 true, Some(hit_test_result.point_in_node),
1397 );
1398 pointer_over.upcast::<Event>().fire(cx, ¤t_target);
1399
1400 self.fire_pointer_event_for_touch(
1402 cx,
1403 &element,
1404 &pointer_touch,
1405 pointer_id,
1406 "pointerenter",
1407 is_primary,
1408 pointer_type,
1409 input_event,
1410 &hit_test_result,
1411 );
1412 }
1413
1414 let released_disconnected = if matches!(event.event_type, TouchEventType::Cancel) {
1418 false
1419 } else {
1420 self.release_disconnected_pointer_capture(cx, pointer_id, pointer_type, is_primary)
1421 };
1422
1423 let pointer_target = self
1425 .get_pointer_capture_target(pointer_id)
1426 .map(DomRoot::upcast::<EventTarget>)
1427 .unwrap_or_else(|| current_target.clone());
1428
1429 let pointer_event = pointer_touch.to_pointer_event(
1430 cx,
1431 window,
1432 pointer_event_name,
1433 pointer_id,
1434 is_primary,
1435 pointer_type,
1436 input_event.active_keyboard_modifiers,
1437 event.is_cancelable(),
1438 Some(hit_test_result.point_in_node),
1439 );
1440 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1441
1442 if !released_disconnected && !matches!(event.event_type, TouchEventType::Cancel) {
1448 self.process_pending_pointer_capture(cx, pointer_id, pointer_type, is_primary);
1449 }
1450
1451 if matches!(
1454 event.event_type,
1455 TouchEventType::Up | TouchEventType::Cancel
1456 ) {
1457 self.implicit_release_pointer_capture(cx, pointer_id, pointer_type, is_primary);
1458 }
1459
1460 if matches!(
1463 event.event_type,
1464 TouchEventType::Up | TouchEventType::Cancel
1465 ) {
1466 let pointer_out = pointer_touch.to_pointer_event(
1468 cx,
1469 window,
1470 "pointerout",
1471 pointer_id,
1472 is_primary,
1473 pointer_type,
1474 input_event.active_keyboard_modifiers,
1475 true, Some(hit_test_result.point_in_node),
1477 );
1478 pointer_out.upcast::<Event>().fire(cx, ¤t_target);
1479
1480 self.fire_pointer_event_for_touch(
1482 cx,
1483 &element,
1484 &pointer_touch,
1485 pointer_id,
1486 "pointerleave",
1487 is_primary,
1488 pointer_type,
1489 input_event,
1490 &hit_test_result,
1491 );
1492 }
1493
1494 let (touch_dispatch_target, changed_touch) = match event.event_type {
1495 TouchEventType::Down => {
1496 self.touch_sequence_click_allowed.set(true);
1497 self.active_touch_points
1499 .safe_borrow_mut(cx.no_gc())
1500 .push(Dom::from_ref(&*pointer_touch));
1501 self.set_active_element(cx.no_gc(), &element);
1502 (current_target, pointer_touch)
1503 },
1504 _ => {
1505 let active_touch_points = self.active_touch_points.borrow();
1511 let Some(index) = active_touch_points
1512 .iter()
1513 .position(|point| point.Identifier() == identifier)
1514 else {
1515 warn!("No active touch point for {:?}", event.event_type);
1516 return Default::default();
1517 };
1518 let original_target = active_touch_points[index].Target();
1520 drop(active_touch_points);
1521
1522 let touch_with_touchstart_target = Touch::new(
1523 cx,
1524 window,
1525 identifier,
1526 &original_target,
1527 client_x,
1528 client_y,
1529 client_x,
1530 client_y,
1531 page_x,
1532 page_y,
1533 );
1534
1535 let mut active_touch_points = self.active_touch_points.safe_borrow_mut(cx.no_gc());
1536 match event.event_type {
1538 TouchEventType::Move => {
1539 active_touch_points[index] = Dom::from_ref(&*touch_with_touchstart_target);
1540 },
1541 TouchEventType::Up | TouchEventType::Cancel => {
1542 active_touch_points.swap_remove(index);
1543 self.remove_pointer_id_for_touch(identifier);
1544 self.unset_active_element();
1545 },
1546 TouchEventType::Down => unreachable!("Should have been handled above"),
1547 }
1548 (original_target, touch_with_touchstart_target)
1549 },
1550 };
1551
1552 rooted_vec!(let mut target_touches);
1553 target_touches.extend(
1554 self.active_touch_points
1555 .borrow()
1556 .iter()
1557 .filter(|touch| touch.Target() == touch_dispatch_target)
1558 .cloned(),
1559 );
1560
1561 let event_name = match event.event_type {
1562 TouchEventType::Down => "touchstart",
1563 TouchEventType::Move => "touchmove",
1564 TouchEventType::Up => "touchend",
1565 TouchEventType::Cancel => "touchcancel",
1566 };
1567
1568 let touches = TouchList::new(cx, window, self.active_touch_points.borrow().r());
1569 let changed_touches = TouchList::new(cx, window, from_ref(&&*changed_touch));
1570 let target_touches = TouchList::new(cx, window, target_touches.r());
1571
1572 let touch_event = TouchEvent::new(
1573 cx,
1574 window,
1575 event_name.into(),
1576 EventBubbles::Bubbles,
1577 EventCancelable::from(event.is_cancelable()),
1578 EventComposed::Composed,
1579 Some(window),
1580 0i32,
1581 &touches,
1582 &changed_touches,
1583 &target_touches,
1584 false,
1586 false,
1587 false,
1588 false,
1589 );
1590 let dom_event = touch_event.upcast::<Event>();
1591 dom_event.fire(cx, &touch_dispatch_target);
1592 let flags = dom_event.flags();
1593
1594 if flags.contains(EventFlags::Canceled) &&
1596 matches!(event.event_type, TouchEventType::Down | TouchEventType::Up)
1597 {
1598 self.touch_sequence_click_allowed.set(false);
1599 }
1600
1601 let synthesizes_mouse_events = matches!(event.event_type, TouchEventType::Up) &&
1604 event.can_synthesize_mouse_events &&
1605 self.touch_sequence_click_allowed.get() &&
1606 !flags.contains(EventFlags::Canceled) &&
1607 self.active_touch_points.borrow().is_empty();
1608
1609 if synthesizes_mouse_events {
1610 self.synthesize_compatibility_mouse_events(cx, &event, input_event);
1611 } else if matches!(
1612 event.event_type,
1613 TouchEventType::Up | TouchEventType::Cancel
1614 ) {
1615 self.click_counting_info
1619 .safe_borrow_mut(cx.no_gc())
1620 .reset_click_count();
1621 }
1622
1623 flags.into()
1624 }
1625
1626 fn synthesize_compatibility_mouse_events(
1630 &self,
1631 cx: &mut JSContext,
1632 event: &EmbedderTouchEvent,
1633 input_event: &ConstellationInputEvent,
1634 ) {
1635 let make_input_event =
1637 |input_event_to_send: InputEvent, pressed_mouse_buttons: MouseButtons| {
1638 ConstellationInputEvent {
1639 hit_test_result: input_event.hit_test_result.clone(),
1640 pressed_mouse_buttons,
1641 active_keyboard_modifiers: input_event.active_keyboard_modifiers,
1642 event: input_event_to_send.into(),
1643 }
1644 };
1645
1646 let point = event.point;
1647 let mouse_move_input_event = make_input_event(
1648 InputEvent::MouseMove(MouseMoveEvent::new_compatibility_for_touch(point)),
1649 MouseButtons::empty(),
1650 );
1651 self.handle_native_mouse_move_event(cx, &mouse_move_input_event);
1652
1653 for (action, pressed_mouse_buttons) in [
1654 (MouseButtonAction::Down, MouseButtons::Primary),
1655 (MouseButtonAction::Up, MouseButtons::empty()),
1656 ] {
1657 let button_event = MouseButtonEvent::new(action, MouseButton::Primary, point);
1658 self.handle_native_mouse_button_event(
1659 cx,
1660 button_event,
1661 &make_input_event(InputEvent::MouseButton(button_event), pressed_mouse_buttons),
1662 );
1663 }
1664 }
1665
1666 fn update_active_touch_points_when_early_return(&self, event: EmbedderTouchEvent) {
1675 match event.event_type {
1676 TouchEventType::Down | TouchEventType::Move => {},
1677 TouchEventType::Up | TouchEventType::Cancel => {
1678 let mut active_touch_points = self.active_touch_points.borrow_mut();
1679 if let Some(index) = active_touch_points
1680 .iter()
1681 .position(|t| t.Identifier() == event.touch_id.0)
1682 {
1683 active_touch_points.swap_remove(index);
1684 self.remove_pointer_id_for_touch(event.touch_id.0);
1685 } else {
1686 warn!(
1687 "Received {:?} for a non-active touch point {}",
1688 event.event_type, event.touch_id.0
1689 );
1690 }
1691 },
1692 }
1693 }
1694
1695 fn handle_keyboard_event(
1697 &self,
1698 cx: &mut JSContext,
1699 keyboard_event: EmbedderKeyboardEvent,
1700 ) -> InputEventResult {
1701 let target = &self.target_for_events_following_focus();
1702 let keyevent = KeyboardEvent::new_with_platform_keyboard_event(
1703 cx,
1704 &self.window,
1705 keyboard_event.event.state.event_type().into(),
1706 &keyboard_event.event,
1707 );
1708
1709 let event = keyevent.upcast::<Event>();
1710 event.set_composed(true);
1711 event.fire(cx, target);
1712
1713 let mut flags = event.flags();
1718 if keyboard_event.event.state != KeyState::Down || flags.contains(EventFlags::Canceled) {
1719 return flags.into();
1720 }
1721
1722 let fires_keypress_event = keyboard_event_fires_keypress_event(&keyboard_event);
1726 if fires_keypress_event {
1727 let keypress_event = KeyboardEvent::new_with_platform_keyboard_event(
1728 cx,
1729 &self.window,
1730 atom!("keypress"),
1731 &keyboard_event.event,
1732 );
1733 keypress_event.upcast::<Event>().set_composed(true);
1734 let event = keypress_event.upcast::<Event>();
1735 event.fire(cx, target);
1736 flags |= event.flags();
1737 }
1738
1739 if flags.intersects(EventFlags::Handled | EventFlags::Canceled) {
1742 return flags.into();
1743 }
1744
1745 self.run_default_keydown_handler(cx, target, &keyboard_event, flags.into())
1746 }
1747
1748 fn handle_ime_event(&self, cx: &mut JSContext, event: ImeEvent) -> InputEventResult {
1749 let document = self.window.Document();
1750 let composition_event = match event {
1751 ImeEvent::Dismissed => {
1752 document.focus_handler().focus(cx, &FocusableArea::Viewport);
1753 return Default::default();
1754 },
1755 ImeEvent::Composition(composition_event) => composition_event,
1756 };
1757
1758 let focused_area = document.focus_handler().focused_area();
1763 let Some(focused_element) = focused_area.element() else {
1764 return Default::default();
1766 };
1767
1768 let cancelable = composition_event.state == CompositionState::Start;
1769 let event = CompositionEvent::new(
1770 cx,
1771 &self.window,
1772 composition_event.state.event_type().into(),
1773 true,
1774 cancelable,
1775 Some(&self.window),
1776 0,
1777 DOMString::from(composition_event.data.as_str()),
1778 );
1779
1780 let event = event.upcast::<Event>();
1781 event.fire(cx, focused_element.upcast());
1782 let composition_event_result: InputEventResult = event.flags().into();
1783
1784 if event
1785 .flags()
1786 .intersects(EventFlags::Canceled | EventFlags::Handled)
1787 {
1788 return composition_event_result;
1789 }
1790
1791 let editing_action = editing_action_from_composition_event(&composition_event);
1792 let editing_host = document.editing_context(cx.no_gc(), focused_element.upcast());
1793 if editing_host.perform_editing_action(cx, editing_action) {
1794 return composition_event_result | InputEventResult::Consumed;
1795 }
1796
1797 composition_event_result
1798 }
1799
1800 fn handle_wheel_event(
1801 &self,
1802 cx: &mut JSContext,
1803 event: EmbedderWheelEvent,
1804 input_event: &ConstellationInputEvent,
1805 ) -> InputEventResult {
1806 let flags = HitTestFlags::empty();
1808 let Some(hit_test_result) = self.window.hit_test_from_input_event(flags, input_event)
1809 else {
1810 return Default::default();
1811 };
1812
1813 let Some(el) = hit_test_result
1814 .node
1815 .inclusive_ancestors(ShadowIncluding::Yes)
1816 .find_map(DomRoot::downcast::<Element>)
1817 else {
1818 return Default::default();
1819 };
1820
1821 let node = el.upcast::<Node>();
1822 debug!(
1823 "wheel: on {:?} at {:?}",
1824 node.debug_str(),
1825 hit_test_result.point_in_frame
1826 );
1827
1828 let event_type = "wheel".into();
1829
1830 let cancelable = EventCancelable::from(
1831 self.window
1832 .upcast::<EventTarget>()
1833 .has_non_passive_listener(&event_type) ||
1834 node.inclusive_ancestors(ShadowIncluding::Yes)
1835 .any(|target| {
1836 target
1837 .upcast::<EventTarget>()
1838 .has_non_passive_listener(&event_type)
1839 }),
1840 );
1841 let dom_event = WheelEvent::new(
1843 cx,
1844 &self.window,
1845 event_type,
1846 EventBubbles::Bubbles,
1847 cancelable,
1848 Some(&self.window),
1849 0i32,
1850 hit_test_result.point_in_frame.to_i32(),
1851 hit_test_result.point_in_frame.to_i32(),
1852 hit_test_result
1853 .point_relative_to_initial_containing_block
1854 .to_i32(),
1855 input_event.active_keyboard_modifiers,
1856 MouseButton::Primary,
1857 input_event.pressed_mouse_buttons,
1858 None,
1859 None,
1860 Finite::wrap(-event.delta.x),
1865 Finite::wrap(-event.delta.y),
1866 Finite::wrap(-event.delta.z),
1867 event.delta.mode as u32,
1868 );
1869
1870 let dom_event = dom_event.upcast::<Event>();
1871 dom_event.set_trusted(true);
1872 dom_event.set_composed(true);
1873 dom_event.fire(cx, node.upcast());
1874
1875 dom_event.flags().into()
1876 }
1877
1878 #[cfg(feature = "gamepad")]
1879 fn handle_gamepad_event(&self, gamepad_event: EmbedderGamepadEvent) {
1880 match gamepad_event {
1881 EmbedderGamepadEvent::Connected(index, name, bounds, supported_haptic_effects) => {
1882 self.handle_gamepad_connect(
1883 index.0,
1884 name,
1885 bounds.axis_bounds,
1886 bounds.button_bounds,
1887 supported_haptic_effects,
1888 );
1889 },
1890 EmbedderGamepadEvent::Disconnected(index) => {
1891 self.handle_gamepad_disconnect(index.0);
1892 },
1893 EmbedderGamepadEvent::Updated(index, update_type) => {
1894 self.receive_new_gamepad_button_or_axis(index.0, update_type);
1895 },
1896 };
1897 }
1898
1899 #[cfg(feature = "gamepad")]
1901 fn handle_gamepad_connect(
1902 &self,
1903 _index: usize,
1907 name: String,
1908 axis_bounds: (f64, f64),
1909 button_bounds: (f64, f64),
1910 supported_haptic_effects: GamepadSupportedHapticEffects,
1911 ) {
1912 let doc = self.window.Document();
1914
1915 if !doc.allowed_to_use_feature(PermissionName::Gamepad) {
1918 return;
1919 }
1920
1921 let trusted_window = Trusted::new(&*self.window);
1922
1923 self.window
1926 .upcast::<GlobalScope>()
1927 .task_manager()
1928 .gamepad_task_source()
1929 .queue(task!(gamepad_connected: move |cx| {
1930 let window = trusted_window.root();
1931
1932 let navigator = window.Navigator(cx);
1935 let selected_index = navigator.select_gamepad_index();
1936 let gamepad = Gamepad::new(
1937 cx,
1938 &window,
1939 selected_index,
1940 name,
1941 "standard".into(),
1942 axis_bounds,
1943 button_bounds,
1944 supported_haptic_effects,
1945 false,
1946 );
1947
1948 navigator.set_gamepad(selected_index as usize, Some(&gamepad));
1950
1951 if navigator.has_gamepad_gesture() {
1953 gamepad.set_exposed(true);
1955 if window.Document().is_fully_active() {
1960 gamepad.notify_event(cx, GamepadEventType::Connected);
1961 }
1962 }
1963 }));
1964 }
1965
1966 #[cfg(feature = "gamepad")]
1968 fn handle_gamepad_disconnect(&self, index: usize) {
1969 let trusted_window = Trusted::new(&*self.window);
1973 self.window
1974 .upcast::<GlobalScope>()
1975 .task_manager()
1976 .gamepad_task_source()
1977 .queue(task!(gamepad_disconnected: move |cx| {
1978 let window = trusted_window.root();
1979 let navigator = window.Navigator(cx);
1980
1981 if let Some(gamepad) = navigator.get_gamepad(index) {
1982 gamepad.update_connected(false);
1984 if gamepad.exposed() && window.Document().is_fully_active() {
1992 gamepad.notify_event(cx, GamepadEventType::Disconnected);
1993 }
1994 }
1995
1996 navigator.set_gamepad(index, None);
2000 navigator.shrink_gamepads_list();
2003 }));
2004 }
2005
2006 #[cfg(feature = "gamepad")]
2008 fn receive_new_gamepad_button_or_axis(&self, index: usize, update_type: GamepadUpdateType) {
2009 let trusted_window = Trusted::new(&*self.window);
2012
2013 self.window
2016 .upcast::<GlobalScope>()
2017 .task_manager()
2018 .gamepad_task_source()
2019 .queue(task!(update_gamepad_state: move |cx| {
2020 let window = trusted_window.root();
2021 let document = window.Document();
2022 document.event_handler().update_gamepad_state(cx, index, update_type);
2023 }));
2024 }
2025
2026 #[cfg(feature = "gamepad")]
2028 fn update_gamepad_state(
2029 &self,
2030 cx: &mut JSContext,
2031 gamepad_index: usize,
2032 update_type: GamepadUpdateType,
2033 ) {
2034 use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods;
2035 let now = *self.window.Performance(cx).Now();
2038
2039 let navigator = self.window.Navigator(cx);
2042
2043 if let Some(gamepad) = navigator.get_gamepad(gamepad_index) {
2044 gamepad.update_timestamp(now);
2046 match update_type {
2049 GamepadUpdateType::Axis(axis_index, value) => {
2050 gamepad.map_and_normalize_axes(axis_index, value);
2051 },
2052 GamepadUpdateType::Button(button_index, value) => {
2053 gamepad.map_and_normalize_buttons(button_index, value);
2054 },
2055 };
2056 if !navigator.has_gamepad_gesture() && contains_user_gesture(update_type) {
2061 navigator.set_has_gamepad_gesture(true);
2063 navigator
2065 .get_connected_gamepad()
2066 .iter()
2067 .for_each(|connected_gamepad| {
2068 connected_gamepad.set_exposed(true);
2070 connected_gamepad.update_timestamp(now);
2072 let trusted_gamepad = Trusted::new(&**connected_gamepad);
2079 if self.window.Document().is_fully_active() {
2080 self.window
2081 .upcast::<GlobalScope>()
2082 .task_manager()
2083 .gamepad_task_source()
2084 .queue(task!(fire_gamepad_connected: move |cx| {
2085 let gamepad = trusted_gamepad.root();
2086 gamepad.notify_event(cx, GamepadEventType::Connected);
2087 }));
2088 }
2089 });
2090 }
2091 }
2092 }
2093
2094 #[expect(unsafe_code)]
2096 pub(crate) fn handle_embedder_scroll_event(&self, scrolled_node: ExternalScrollId) {
2097 let document = self.window.Document();
2099 if scrolled_node.is_root() {
2100 document.handle_viewport_scroll_event();
2101 } else {
2102 let node_id = node_id_from_scroll_id(scrolled_node.0 as usize);
2106 let node = unsafe {
2107 node::from_untrusted_node_address(UntrustedNodeAddress::from_id(node_id))
2108 };
2109 let Some(element) = node
2110 .inclusive_ancestors(ShadowIncluding::Yes)
2111 .find_map(DomRoot::downcast::<Element>)
2112 else {
2113 return;
2114 };
2115
2116 element.handle_scroll_event();
2117 }
2118 }
2119
2120 pub(crate) fn maybe_dispatch_simulated_click(
2126 &self,
2127 cx: &mut JSContext,
2128 node: &Node,
2129 event: &KeyboardEvent,
2130 ) {
2131 if event.key() != Key::Named(NamedKey::Enter) && event.original_code() != Some(Code::Space)
2132 {
2133 return;
2134 }
2135
2136 if node
2144 .downcast::<Element>()
2145 .and_then(Element::as_maybe_activatable)
2146 .is_none() ||
2147 node.downcast::<HTMLInputElement>()
2148 .is_some_and(|input| input.is_textual_or_password())
2149 {
2150 return;
2151 }
2152
2153 node.fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
2154 event.upcast::<Event>().mark_as_handled();
2155 }
2156
2157 pub(crate) fn run_default_keydown_handler(
2158 &self,
2159 cx: &mut JSContext,
2160 event_target: &EventTarget,
2161 event: &EmbedderKeyboardEvent,
2162 input_event_result: InputEventResult,
2163 ) -> InputEventResult {
2164 let document = self.window.Document();
2165 let node = event_target
2166 .downcast::<Node>()
2167 .unwrap_or_else(|| document.upcast());
2168
2169 let event = &event.event;
2170 if let Some(editing_action) = editing_action_from_keyboard_event(event) {
2171 let editing_host = document.editing_context(cx.no_gc(), node);
2172 if editing_host.perform_editing_action(cx, editing_action) {
2173 return input_event_result | InputEventResult::Consumed;
2174 }
2175 }
2176
2177 if self.maybe_handle_accesskey(cx, event) {
2178 return input_event_result | InputEventResult::Consumed;
2179 }
2180
2181 let mut is_space = false;
2182 let scroll = match &event.key {
2183 Key::Named(NamedKey::ArrowDown) => KeyboardScroll::Down,
2184 Key::Named(NamedKey::ArrowLeft) => KeyboardScroll::Left,
2185 Key::Named(NamedKey::ArrowRight) => KeyboardScroll::Right,
2186 Key::Named(NamedKey::ArrowUp) => KeyboardScroll::Up,
2187 Key::Named(NamedKey::End) => KeyboardScroll::End,
2188 Key::Named(NamedKey::Home) => KeyboardScroll::Home,
2189 Key::Named(NamedKey::PageDown) => KeyboardScroll::PageDown,
2190 Key::Named(NamedKey::PageUp) => KeyboardScroll::PageUp,
2191 Key::Character(string) if string == " " => {
2192 is_space = true;
2193 if event.modifiers.contains(Modifiers::SHIFT) {
2194 KeyboardScroll::PageUp
2195 } else {
2196 KeyboardScroll::PageDown
2197 }
2198 },
2199 Key::Named(NamedKey::Tab) => {
2200 self.window
2206 .Document()
2207 .focus_handler()
2208 .sequential_focus_navigation_via_keyboard_event(cx, event);
2209 return input_event_result | InputEventResult::Consumed;
2210 },
2211 _ => return input_event_result,
2212 };
2213
2214 if !event.modifiers.is_empty() && !is_space {
2215 return input_event_result;
2216 }
2217
2218 self.do_keyboard_scroll(cx, scroll);
2219 input_event_result | InputEventResult::Consumed
2220 }
2221
2222 pub(crate) fn do_keyboard_scroll(&self, cx: &mut JSContext, scroll: KeyboardScroll) {
2223 let scroll_axis = match scroll {
2224 KeyboardScroll::Left | KeyboardScroll::Right => ScrollingBoxAxis::X,
2225 _ => ScrollingBoxAxis::Y,
2226 };
2227
2228 let document = self.window.Document();
2229 let mut scrolling_box = document
2230 .focus_handler()
2231 .focused_area()
2232 .element()
2233 .or(self.most_recently_clicked_element.get().as_deref())
2234 .and_then(|element| element.scrolling_box(ScrollContainerQueryFlags::Inclusive))
2235 .unwrap_or_else(|| {
2236 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2237 });
2238
2239 while !scrolling_box.can_keyboard_scroll_in_axis(cx.no_gc(), scroll_axis) {
2240 if scrolling_box.is_viewport() {
2242 break;
2243 }
2244 let parent = scrolling_box.parent().unwrap_or_else(|| {
2245 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2246 });
2247 scrolling_box = parent;
2248 }
2249
2250 let calculate_current_scroll_offset_and_delta = || {
2251 const LINE_HEIGHT: f32 = 76.0;
2252 const LINE_WIDTH: f32 = 76.0;
2253
2254 let current_scroll_offset = scrolling_box.scroll_position();
2255 (
2256 current_scroll_offset,
2257 match scroll {
2258 KeyboardScroll::Home => Vector2D::new(0.0, -current_scroll_offset.y),
2259 KeyboardScroll::End => Vector2D::new(
2260 0.0,
2261 -current_scroll_offset.y + scrolling_box.content_size().height -
2262 scrolling_box.size(cx.no_gc()).height,
2263 ),
2264 KeyboardScroll::PageDown => Vector2D::new(
2265 0.0,
2266 scrolling_box.size(cx.no_gc()).height - 2.0 * LINE_HEIGHT,
2267 ),
2268 KeyboardScroll::PageUp => Vector2D::new(
2269 0.0,
2270 2.0 * LINE_HEIGHT - scrolling_box.size(cx.no_gc()).height,
2271 ),
2272 KeyboardScroll::Up => Vector2D::new(0.0, -LINE_HEIGHT),
2273 KeyboardScroll::Down => Vector2D::new(0.0, LINE_HEIGHT),
2274 KeyboardScroll::Left => Vector2D::new(-LINE_WIDTH, 0.0),
2275 KeyboardScroll::Right => Vector2D::new(LINE_WIDTH, 0.0),
2276 },
2277 )
2278 };
2279
2280 let parent_pipeline = self.window.parent_info();
2284 if scrolling_box.is_viewport() && parent_pipeline.is_none() {
2285 let (_, delta) = calculate_current_scroll_offset_and_delta();
2286 self.window
2287 .paint_api()
2288 .scroll_viewport_by_delta(self.window.webview_id(), delta);
2289 }
2290
2291 if !scrolling_box.can_keyboard_scroll_in_axis(cx.no_gc(), scroll_axis) {
2294 assert!(scrolling_box.is_viewport());
2295
2296 let window_proxy = document.window().window_proxy();
2297 if let Some(iframe) = window_proxy.frame_element() {
2298 let iframe_window = iframe.owner_window();
2301 let mut realm = enter_auto_realm(cx, &*iframe_window);
2302 let cx = &mut realm;
2303 iframe_window
2304 .Document()
2305 .event_handler()
2306 .do_keyboard_scroll(cx, scroll);
2307 } else if let Some(parent_pipeline) = parent_pipeline {
2308 document.window().send_to_constellation(
2312 ScriptToConstellationMessage::ForwardKeyboardScroll(parent_pipeline, scroll),
2313 );
2314 };
2315 return;
2316 }
2317
2318 let (current_scroll_offset, delta) = calculate_current_scroll_offset_and_delta();
2319 scrolling_box.scroll_to(cx, delta + current_scroll_offset, ScrollBehavior::Auto);
2320 }
2321
2322 fn get_or_create_pointer_id_for_touch(&self, touch_id: i32) -> i32 {
2325 let mut active_pointer_ids = self.active_pointer_ids.borrow_mut();
2326
2327 if let Some(&pointer_id) = active_pointer_ids.get(&touch_id) {
2328 return pointer_id;
2329 }
2330
2331 let pointer_id = self.next_touch_pointer_id.get();
2332 active_pointer_ids.insert(touch_id, pointer_id);
2333 self.next_touch_pointer_id.set(pointer_id + 1);
2334 pointer_id
2335 }
2336
2337 fn remove_pointer_id_for_touch(&self, touch_id: i32) {
2339 self.active_pointer_ids.borrow_mut().remove(&touch_id);
2340 }
2341
2342 fn is_primary_pointer(&self, pointer_id: i32) -> bool {
2345 self.active_pointer_ids
2348 .borrow()
2349 .values()
2350 .min()
2351 .is_some_and(|primary_pointer| *primary_pointer == pointer_id)
2352 }
2353
2354 #[expect(clippy::too_many_arguments)]
2358 fn fire_pointer_event_for_touch(
2359 &self,
2360 cx: &mut JSContext,
2361 target_element: &Element,
2362 touch: &Touch,
2363 pointer_id: i32,
2364 event_name: &str,
2365 is_primary: bool,
2366 pointer_type: &str,
2367 input_event: &ConstellationInputEvent,
2368 hit_test_result: &HitTestResult,
2369 ) {
2370 let mut targets: Vec<_> = target_element
2371 .upcast::<Node>()
2372 .inclusive_ancestors_in_flat_tree_unrooted(cx.no_gc())
2373 .map(|node| node.as_rooted())
2374 .collect();
2375
2376 if event_name == "pointerenter" {
2378 targets.reverse();
2379 }
2380
2381 for target in targets {
2382 let pointer_event = touch.to_pointer_event(
2383 cx,
2384 &self.window,
2385 event_name,
2386 pointer_id,
2387 is_primary,
2388 pointer_type,
2389 input_event.active_keyboard_modifiers,
2390 false,
2391 Some(hit_test_result.point_in_node),
2392 );
2393 pointer_event.upcast::<Event>().fire(cx, target.upcast());
2394 }
2395 }
2396
2397 pub(crate) fn has_assigned_access_key(&self, element: &HTMLElement) -> bool {
2398 self.access_key_handlers
2399 .borrow()
2400 .values()
2401 .any(|value| &**value == element)
2402 }
2403
2404 pub(crate) fn unassign_access_key(&self, element: &HTMLElement) {
2405 self.access_key_handlers
2406 .borrow_mut()
2407 .retain(|_, value| &**value != element)
2408 }
2409
2410 pub(crate) fn assign_access_key(&self, element: &HTMLElement, code: Code) {
2411 let mut access_key_handlers = self.access_key_handlers.borrow_mut();
2412 access_key_handlers
2414 .entry(code.into())
2415 .or_insert(Dom::from_ref(element));
2416 }
2417
2418 fn maybe_handle_accesskey(&self, cx: &mut JSContext, event: &KeyboardTypesEvent) -> bool {
2419 #[cfg(target_os = "macos")]
2420 let access_key_modifiers = Modifiers::CONTROL | Modifiers::ALT;
2421 #[cfg(not(target_os = "macos"))]
2422 let access_key_modifiers = Modifiers::SHIFT | Modifiers::ALT;
2423
2424 if event.modifiers != access_key_modifiers {
2425 return false;
2426 }
2427
2428 let Some(html_element) = self
2429 .access_key_handlers
2430 .borrow()
2431 .get(&event.code.into())
2432 .map(|html_element| html_element.as_rooted())
2433 else {
2434 return false;
2435 };
2436
2437 let Ok(command) = InteractiveElementCommand::try_from(&*html_element) else {
2445 return false;
2446 };
2447
2448 if command.disabled() || command.hidden() {
2449 return false;
2450 }
2451
2452 let node = html_element.upcast::<Node>();
2453 if !node.is_connected() {
2454 return false;
2455 }
2456
2457 for node in node.inclusive_ancestors_unrooted(cx.no_gc(), ShadowIncluding::Yes) {
2458 if node
2459 .downcast::<HTMLElement>()
2460 .is_some_and(|html_element| html_element.Hidden())
2461 {
2462 return false;
2463 }
2464 }
2465
2466 self.focus_and_scroll_to_element_for_key_event(cx, html_element.upcast());
2469 command.perform_action(cx);
2470 true
2471 }
2472
2473 pub(crate) fn focus_and_scroll_to_element_for_key_event(
2474 &self,
2475 cx: &mut JSContext,
2476 element: &Element,
2477 ) {
2478 element
2479 .upcast::<Node>()
2480 .run_the_focusing_steps(cx, None, FocusTrigger::Other);
2481 let scroll_axis = ScrollAxisState {
2482 position: ScrollLogicalPosition::Center,
2483 requirement: ScrollRequirement::IfNotVisible,
2484 };
2485 element.scroll_into_view_with_options(
2486 cx,
2487 ScrollBehavior::Auto,
2488 scroll_axis,
2489 scroll_axis,
2490 None,
2491 None,
2492 );
2493 }
2494
2495 pub(crate) fn is_active_pointer(&self, pointer_id: i32) -> bool {
2498 if pointer_id == PointerId::Mouse as i32 {
2499 !self.mouse_button_state.get().is_empty()
2501 } else {
2502 self.active_pointer_ids
2504 .borrow()
2505 .values()
2506 .any(|&id| id == pointer_id)
2507 }
2508 }
2509
2510 pub(crate) fn set_pending_pointer_capture(&self, pointer_id: i32, element: &Element) {
2513 self.pending_pointer_capture
2514 .borrow_mut()
2515 .insert(pointer_id, Dom::from_ref(element));
2516 }
2517
2518 pub(crate) fn clear_pending_pointer_capture(&self, pointer_id: i32) {
2521 self.pending_pointer_capture
2522 .borrow_mut()
2523 .remove(&pointer_id);
2524 }
2525
2526 pub(crate) fn has_pointer_capture(&self, pointer_id: i32, element: &Element) -> bool {
2529 self.pending_pointer_capture
2530 .borrow()
2531 .get(&pointer_id)
2532 .is_some_and(|el| &**el == element)
2533 }
2534
2535 fn get_pointer_capture_target(&self, pointer_id: i32) -> Option<DomRoot<Element>> {
2540 self.pointer_capture_target
2541 .borrow()
2542 .get(&pointer_id)
2543 .map(|el| DomRoot::from_ref(&**el))
2544 .filter(|el| el.upcast::<Node>().is_connected())
2545 }
2546
2547 fn release_disconnected_pointer_capture(
2553 &self,
2554 cx: &mut JSContext,
2555 pointer_id: i32,
2556 pointer_type: &str,
2557 is_primary: bool,
2558 ) -> bool {
2559 let capture_target = self
2560 .pointer_capture_target
2561 .borrow()
2562 .get(&pointer_id)
2563 .map(|el| DomRoot::from_ref(&**el));
2564 if let Some(capture_element) = capture_target &&
2565 !capture_element.upcast::<Node>().is_connected()
2566 {
2567 let document = self.window.Document();
2569 self.fire_pointer_capture_event_at_target(
2570 cx,
2571 "lostpointercapture",
2572 pointer_id,
2573 pointer_type,
2574 is_primary,
2575 document.upcast::<EventTarget>(),
2576 );
2577 self.pending_pointer_capture
2579 .safe_borrow_mut(cx.no_gc())
2580 .remove(&pointer_id);
2581 self.pointer_capture_target
2582 .safe_borrow_mut(cx.no_gc())
2583 .remove(&pointer_id);
2584 return true;
2585 }
2586 false
2587 }
2588
2589 fn fire_pointer_capture_event(
2592 &self,
2593 cx: &mut JSContext,
2594 event_type: &str,
2595 pointer_id: i32,
2596 pointer_type: &str,
2597 is_primary: bool,
2598 target: &Element,
2599 ) {
2600 self.fire_pointer_capture_event_at_target(
2601 cx,
2602 event_type,
2603 pointer_id,
2604 pointer_type,
2605 is_primary,
2606 target.upcast::<EventTarget>(),
2607 );
2608 }
2609
2610 fn fire_pointer_capture_event_at_target(
2612 &self,
2613 cx: &mut JSContext,
2614 event_type: &str,
2615 pointer_id: i32,
2616 pointer_type: &str,
2617 is_primary: bool,
2618 target: &EventTarget,
2619 ) {
2620 let pointer_event = PointerEvent::new(
2621 cx,
2622 &self.window,
2623 event_type.into(),
2624 EventBubbles::Bubbles,
2625 EventCancelable::NotCancelable,
2626 Some(&self.window),
2627 0,
2628 Point2D::new(0, 0),
2629 Point2D::new(0, 0),
2630 Point2D::new(0, 0),
2631 Modifiers::empty(),
2632 MouseButton::Primary,
2633 MouseButtons::empty(),
2634 None,
2635 None,
2636 pointer_id,
2637 1,
2638 1,
2639 0.0,
2640 0.0,
2641 0,
2642 0,
2643 0,
2644 PI / 2.0,
2645 0.0,
2646 DOMString::from(pointer_type),
2647 is_primary,
2648 vec![],
2649 vec![],
2650 );
2651 pointer_event.upcast::<Event>().set_composed(true);
2652 pointer_event.upcast::<Event>().fire(cx, target);
2653 }
2654
2655 #[expect(clippy::too_many_arguments)]
2659 fn fire_pointer_boundary_event(
2660 &self,
2661 cx: &mut JSContext,
2662 event_type: &str,
2663 bubbles: EventBubbles,
2664 pointer_id: i32,
2665 pointer_type: &str,
2666 is_primary: bool,
2667 target: &Element,
2668 related_target: Option<&Element>,
2669 ) {
2670 let pointer_event = PointerEvent::new(
2671 cx,
2672 &self.window,
2673 event_type.into(),
2674 bubbles,
2675 EventCancelable::NotCancelable,
2676 Some(&self.window),
2677 0,
2678 Point2D::new(0, 0),
2679 Point2D::new(0, 0),
2680 Point2D::new(0, 0),
2681 Modifiers::empty(),
2682 MouseButton::None,
2683 self.mouse_button_state.get(),
2684 related_target.map(|el| el.upcast::<EventTarget>()),
2685 None,
2686 pointer_id,
2687 1,
2688 1,
2689 0.0,
2690 0.0,
2691 0,
2692 0,
2693 0,
2694 PI / 2.0,
2695 0.0,
2696 DOMString::from(pointer_type),
2697 is_primary,
2698 vec![],
2699 vec![],
2700 );
2701 pointer_event.upcast::<Event>().set_composed(true);
2702 pointer_event.upcast::<Event>().fire(cx, target.upcast());
2703 }
2704
2705 fn fire_pointer_capture_boundary_transition(
2710 &self,
2711 cx: &mut JSContext,
2712 pointer_id: i32,
2713 pointer_type: &str,
2714 is_primary: bool,
2715 old_target: &Element,
2716 new_target: &Element,
2717 ) {
2718 if pointer_type != "mouse" {
2719 return;
2720 }
2721 if old_target == new_target {
2722 return;
2723 }
2724 self.fire_pointer_boundary_event(
2725 cx,
2726 "pointerout",
2727 EventBubbles::Bubbles,
2728 pointer_id,
2729 pointer_type,
2730 is_primary,
2731 old_target,
2732 Some(new_target),
2733 );
2734 self.fire_pointer_boundary_event(
2735 cx,
2736 "pointerleave",
2737 EventBubbles::DoesNotBubble,
2738 pointer_id,
2739 pointer_type,
2740 is_primary,
2741 old_target,
2742 Some(new_target),
2743 );
2744 self.fire_pointer_boundary_event(
2745 cx,
2746 "pointerover",
2747 EventBubbles::Bubbles,
2748 pointer_id,
2749 pointer_type,
2750 is_primary,
2751 new_target,
2752 Some(old_target),
2753 );
2754 self.fire_pointer_boundary_event(
2755 cx,
2756 "pointerenter",
2757 EventBubbles::DoesNotBubble,
2758 pointer_id,
2759 pointer_type,
2760 is_primary,
2761 new_target,
2762 Some(old_target),
2763 );
2764 }
2765
2766 fn implicit_release_pointer_capture(
2769 &self,
2770 cx: &mut JSContext,
2771 pointer_id: i32,
2772 pointer_type: &str,
2773 is_primary: bool,
2774 ) {
2775 let capture_element = self
2776 .pointer_capture_target
2777 .borrow()
2778 .get(&pointer_id)
2779 .map(|el| DomRoot::from_ref(&**el));
2780 if let Some(capture_element) = capture_element {
2781 if capture_element.upcast::<Node>().is_connected() {
2782 self.fire_pointer_capture_event(
2783 cx,
2784 "lostpointercapture",
2785 pointer_id,
2786 pointer_type,
2787 is_primary,
2788 &capture_element,
2789 );
2790 if let Some(hover_target) = self.current_hover_target.get() {
2793 self.fire_pointer_capture_boundary_transition(
2794 cx,
2795 pointer_id,
2796 pointer_type,
2797 is_primary,
2798 &capture_element,
2799 &hover_target,
2800 );
2801 }
2802 } else {
2803 let document = self.window.Document();
2804 self.fire_pointer_capture_event_at_target(
2805 cx,
2806 "lostpointercapture",
2807 pointer_id,
2808 pointer_type,
2809 is_primary,
2810 document.upcast::<EventTarget>(),
2811 );
2812 }
2813 }
2814 self.pending_pointer_capture
2815 .safe_borrow_mut(cx.no_gc())
2816 .remove(&pointer_id);
2817 self.pointer_capture_target
2818 .safe_borrow_mut(cx.no_gc())
2819 .remove(&pointer_id);
2820 }
2821
2822 fn process_pending_pointer_capture(
2826 &self,
2827 cx: &mut JSContext,
2828 pointer_id: i32,
2829 pointer_type: &str,
2830 is_primary: bool,
2831 ) {
2832 let pending = self
2833 .pending_pointer_capture
2834 .borrow()
2835 .get(&pointer_id)
2836 .map(|el| DomRoot::from_ref(&**el));
2837 let current = self
2838 .pointer_capture_target
2839 .borrow()
2840 .get(&pointer_id)
2841 .map(|el| DomRoot::from_ref(&**el));
2842
2843 let pending_connected = pending
2846 .as_ref()
2847 .is_some_and(|el| el.upcast::<Node>().is_connected());
2848
2849 let pointer_is_active = self.is_active_pointer(pointer_id);
2852
2853 match (&pending, ¤t) {
2854 (Some(pending_el), None) if pending_connected => {
2855 if pointer_is_active {
2856 if let Some(hover_target) = self.current_hover_target.get() {
2859 self.fire_pointer_capture_boundary_transition(
2860 cx,
2861 pointer_id,
2862 pointer_type,
2863 is_primary,
2864 &hover_target,
2865 pending_el,
2866 );
2867 }
2868 self.fire_pointer_capture_event(
2869 cx,
2870 "gotpointercapture",
2871 pointer_id,
2872 pointer_type,
2873 is_primary,
2874 pending_el,
2875 );
2876 self.pointer_capture_target
2877 .safe_borrow_mut(cx.no_gc())
2878 .insert(pointer_id, Dom::from_ref(pending_el));
2879 } else {
2880 self.pending_pointer_capture
2881 .safe_borrow_mut(cx.no_gc())
2882 .remove(&pointer_id);
2883 }
2884 },
2885 (Some(pending_el), Some(current_el))
2886 if pending_connected && pending_el != current_el =>
2887 {
2888 self.fire_pointer_capture_event(
2889 cx,
2890 "lostpointercapture",
2891 pointer_id,
2892 pointer_type,
2893 is_primary,
2894 current_el,
2895 );
2896 if pointer_is_active {
2897 self.fire_pointer_capture_boundary_transition(
2898 cx,
2899 pointer_id,
2900 pointer_type,
2901 is_primary,
2902 current_el,
2903 pending_el,
2904 );
2905 self.fire_pointer_capture_event(
2906 cx,
2907 "gotpointercapture",
2908 pointer_id,
2909 pointer_type,
2910 is_primary,
2911 pending_el,
2912 );
2913 self.pointer_capture_target
2914 .safe_borrow_mut(cx.no_gc())
2915 .insert(pointer_id, Dom::from_ref(pending_el));
2916 } else {
2917 self.pending_pointer_capture
2918 .safe_borrow_mut(cx.no_gc())
2919 .remove(&pointer_id);
2920 self.pointer_capture_target
2921 .safe_borrow_mut(cx.no_gc())
2922 .remove(&pointer_id);
2923 }
2924 },
2925 (None, Some(current_el)) | (Some(_), Some(current_el)) if !pending_connected => {
2926 self.fire_pointer_capture_event(
2927 cx,
2928 "lostpointercapture",
2929 pointer_id,
2930 pointer_type,
2931 is_primary,
2932 current_el,
2933 );
2934 if let Some(hover_target) = self.current_hover_target.get() {
2937 self.fire_pointer_capture_boundary_transition(
2938 cx,
2939 pointer_id,
2940 pointer_type,
2941 is_primary,
2942 current_el,
2943 &hover_target,
2944 );
2945 }
2946 self.pointer_capture_target
2947 .safe_borrow_mut(cx.no_gc())
2948 .remove(&pointer_id);
2949 if !pending_connected {
2950 self.pending_pointer_capture
2951 .safe_borrow_mut(cx.no_gc())
2952 .remove(&pointer_id);
2953 }
2954 },
2955 _ => {},
2956 }
2957 }
2958
2959 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
2960 pub(crate) fn install_drag_gesture(&self, drag_gesture: DragGesture) {
2961 *self.drag_gesture.borrow_mut() = Some(drag_gesture);
2962 }
2963}
2964
2965fn keyboard_event_fires_keypress_event(keyboard_event: &EmbedderKeyboardEvent) -> bool {
2973 let is_character_value_key = matches!(
2974 keyboard_event.event.key,
2975 Key::Character(_) | Key::Named(NamedKey::Enter)
2976 );
2977 keyboard_event.event.state == KeyState::Down &&
2978 is_character_value_key &&
2979 !keyboard_event.event.is_composing
2980}
2981
2982pub(crate) fn character_to_code(character: char) -> Option<Code> {
2983 Some(match character.to_ascii_lowercase() {
2984 '`' => Code::Backquote,
2985 '\\' => Code::Backslash,
2986 '[' | '{' => Code::BracketLeft,
2987 ']' | '}' => Code::BracketRight,
2988 ',' | '<' => Code::Comma,
2989 '0' => Code::Digit0,
2990 '1' => Code::Digit1,
2991 '2' => Code::Digit2,
2992 '3' => Code::Digit3,
2993 '4' => Code::Digit4,
2994 '5' => Code::Digit5,
2995 '6' => Code::Digit6,
2996 '7' => Code::Digit7,
2997 '8' => Code::Digit8,
2998 '9' => Code::Digit9,
2999 '=' => Code::Equal,
3000 'a' => Code::KeyA,
3001 'b' => Code::KeyB,
3002 'c' => Code::KeyC,
3003 'd' => Code::KeyD,
3004 'e' => Code::KeyE,
3005 'f' => Code::KeyF,
3006 'g' => Code::KeyG,
3007 'h' => Code::KeyH,
3008 'i' => Code::KeyI,
3009 'j' => Code::KeyJ,
3010 'k' => Code::KeyK,
3011 'l' => Code::KeyL,
3012 'm' => Code::KeyM,
3013 'n' => Code::KeyN,
3014 'o' => Code::KeyO,
3015 'p' => Code::KeyP,
3016 'q' => Code::KeyQ,
3017 'r' => Code::KeyR,
3018 's' => Code::KeyS,
3019 't' => Code::KeyT,
3020 'u' => Code::KeyU,
3021 'v' => Code::KeyV,
3022 'w' => Code::KeyW,
3023 'x' => Code::KeyX,
3024 'y' => Code::KeyY,
3025 'z' => Code::KeyZ,
3026 '-' => Code::Minus,
3027 '.' => Code::Period,
3028 '\'' | '"' => Code::Quote,
3029 ';' => Code::Semicolon,
3030 '/' => Code::Slash,
3031 ' ' => Code::Space,
3032 _ => return None,
3033 })
3034}