1use std::array::from_ref;
6use std::cell::{Cell, RefCell};
7use std::f64::consts::PI;
8use std::mem;
9use std::rc::Rc;
10use std::str::FromStr;
11use std::time::{Duration, Instant};
12
13use embedder_traits::{
14 Cursor, EditingActionEvent, EmbedderMsg, ImeEvent, InputEvent, InputEventId, InputEventOutcome,
15 InputEventResult, KeyboardEvent as EmbedderKeyboardEvent, MouseButton, MouseButtonAction,
16 MouseButtonEvent, MouseLeftViewportEvent, TouchEvent as EmbedderTouchEvent, TouchEventType,
17 TouchId, TouchPointerType, UntrustedNodeAddress, WheelEvent as EmbedderWheelEvent,
18};
19#[cfg(feature = "gamepad")]
20use embedder_traits::{
21 GamepadEvent as EmbedderGamepadEvent, GamepadSupportedHapticEffects, GamepadUpdateType,
22};
23use euclid::{Point2D, Vector2D};
24use js::context::JSContext;
25use keyboard_types::{Code, Key, KeyState, Modifiers, NamedKey};
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::KeyboardEventBinding::KeyboardEventMethods;
35use script_bindings::codegen::GenericBindings::ShadowRootBinding::ShadowRootMethods;
36use script_bindings::codegen::GenericBindings::TouchBinding::TouchMethods;
37use script_bindings::codegen::GenericBindings::WindowBinding::{ScrollBehavior, WindowMethods};
38use script_bindings::inheritance::Castable;
39use script_bindings::match_domstring_ascii;
40use script_bindings::num::Finite;
41use script_bindings::root::{Dom, DomRoot, DomSlice};
42use script_bindings::str::DOMString;
43use script_traits::ConstellationInputEvent;
44use servo_base::generic_channel::GenericCallback;
45use servo_config::pref;
46use servo_constellation_traits::{KeyboardScroll, ScriptToConstellationMessage};
47use style::Atom;
48use style_traits::CSSPixel;
49use webrender_api::ExternalScrollId;
50
51#[cfg(feature = "gamepad")]
52use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionName;
53use crate::dom::bindings::inheritance::{ElementTypeId, HTMLElementTypeId, NodeTypeId};
54use crate::dom::bindings::refcounted::Trusted;
55use crate::dom::bindings::root::MutNullableDom;
56use crate::dom::bindings::trace::NoTrace;
57use crate::dom::clipboardevent::ClipboardEventType;
58use crate::dom::document::FireMouseEventType;
59use crate::dom::document::focus::FocusableArea;
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::inputevent::HitTestResult;
66use crate::dom::interactive_element_command::InteractiveElementCommand;
67use crate::dom::iterators::ShadowIncluding;
68use crate::dom::keyboardevent::KeyboardEvent;
69use crate::dom::node::{self, Node, NodeTraits};
70use crate::dom::pointerevent::{PointerEvent, PointerId};
71use crate::dom::types::{
72 ClipboardEvent, CompositionEvent, DataTransfer, Element, Event, EventTarget, GlobalScope,
73 HTMLAnchorElement, HTMLElement, HTMLLabelElement, MouseEvent, Touch, TouchEvent, TouchList,
74 WheelEvent, Window,
75};
76use crate::dom::window::scrolling_box::{ScrollAxisState, ScrollRequirement, ScrollingBoxAxis};
77use crate::drag_data_store::{DragDataStore, Kind, Mode};
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.count = 0;
124 self.time = None;
125 self.point = None;
126 }
127 }
128
129 fn increment_click_count(
130 &mut self,
131 button: MouseButton,
132 point: Point2D<f32, CSSPixel>,
133 ) -> usize {
134 self.time = Some(Instant::now());
135 self.point = Some(point);
136 self.button = Some(button);
137 self.count += 1;
138 self.count
139 }
140}
141
142#[derive(JSTraceable, MallocSizeOf)]
146#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
147pub(crate) struct DocumentEventHandler {
148 window: Dom<Window>,
150 #[no_trace]
152 #[ignore_malloc_size_of = "InputEvent contains data from outside crates"]
153 pending_input_events: DomRefCell<Vec<ConstellationInputEvent>>,
154 mouse_move_event_index: DomRefCell<Option<usize>>,
156 #[no_trace]
158 #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
159 coalesced_mouse_move_event_ids: DomRefCell<Vec<InputEventId>>,
160 wheel_event_index: DomRefCell<Option<usize>>,
165 #[no_trace]
167 #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
168 coalesced_wheel_event_ids: DomRefCell<Vec<InputEventId>>,
169 click_counting_info: DomRefCell<ClickCountingInfo>,
171 #[no_trace]
172 last_mouse_button_down_point: Cell<Option<Point2D<f32, CSSPixel>>>,
173 mouse_buttons_down: Cell<u32>,
177 current_hover_target: MutNullableDom<Element>,
179 current_active_element: MutNullableDom<Element>,
182 most_recently_clicked_element: MutNullableDom<Element>,
184 #[no_trace]
186 most_recent_mousemove_point: Cell<Option<Point2D<f32, CSSPixel>>>,
187 #[no_trace]
190 current_cursor: Cell<Option<Cursor>>,
191 active_touch_points: DomRefCell<Vec<Dom<Touch>>>,
193 #[no_trace]
195 active_keyboard_modifiers: Cell<Modifiers>,
196 active_pointer_ids: DomRefCell<FxHashMap<i32, i32>>,
198 next_touch_pointer_id: Cell<i32>,
200 access_key_handlers: DomRefCell<FxHashMap<NoTrace<Code>, Dom<HTMLElement>>>,
202 pending_pointer_capture: DomRefCell<FxHashMap<i32, Dom<Element>>>,
206 pointer_capture_target: DomRefCell<FxHashMap<i32, Dom<Element>>>,
209}
210
211impl DocumentEventHandler {
212 pub(crate) fn new(window: &Window) -> Self {
213 Self {
214 window: Dom::from_ref(window),
215 pending_input_events: Default::default(),
216 mouse_move_event_index: Default::default(),
217 coalesced_mouse_move_event_ids: Default::default(),
218 wheel_event_index: Default::default(),
219 coalesced_wheel_event_ids: Default::default(),
220 click_counting_info: Default::default(),
221 last_mouse_button_down_point: Default::default(),
222 mouse_buttons_down: Cell::new(0),
223 current_hover_target: Default::default(),
224 current_active_element: Default::default(),
225 most_recently_clicked_element: Default::default(),
226 most_recent_mousemove_point: Default::default(),
227 current_cursor: Default::default(),
228 active_touch_points: Default::default(),
229 active_keyboard_modifiers: Default::default(),
230 active_pointer_ids: Default::default(),
231 next_touch_pointer_id: Cell::new(1),
232 access_key_handlers: Default::default(),
233 pending_pointer_capture: Default::default(),
234 pointer_capture_target: Default::default(),
235 }
236 }
237
238 pub(crate) fn note_pending_input_event(&self, event: ConstellationInputEvent) {
240 let mut pending_input_events = self.pending_input_events.borrow_mut();
241 if matches!(event.event.event, InputEvent::MouseMove(..)) {
242 if let Some(mouse_move_event) = self
244 .mouse_move_event_index
245 .borrow()
246 .and_then(|index| pending_input_events.get_mut(index))
247 {
248 self.coalesced_mouse_move_event_ids
249 .borrow_mut()
250 .push(mouse_move_event.event.id);
251 *mouse_move_event = event;
252 return;
253 }
254
255 *self.mouse_move_event_index.borrow_mut() = Some(pending_input_events.len());
256 }
257
258 if let InputEvent::Wheel(ref new_wheel_event) = event.event.event {
259 if let Some(existing_constellation_wheel_event) = self
261 .wheel_event_index
262 .borrow()
263 .and_then(|index| pending_input_events.get_mut(index)) &&
264 let InputEvent::Wheel(ref mut existing_wheel_event) =
265 existing_constellation_wheel_event.event.event &&
266 existing_wheel_event.delta.mode == new_wheel_event.delta.mode
267 {
268 self.coalesced_wheel_event_ids
269 .borrow_mut()
270 .push(existing_constellation_wheel_event.event.id);
271 existing_wheel_event.delta.x += new_wheel_event.delta.x;
272 existing_wheel_event.delta.y += new_wheel_event.delta.y;
273 existing_wheel_event.delta.z += new_wheel_event.delta.z;
274 existing_wheel_event.point = new_wheel_event.point;
275 existing_constellation_wheel_event.event.id = event.event.id;
276 return;
277 }
278
279 *self.wheel_event_index.borrow_mut() = Some(pending_input_events.len());
280 }
281
282 pending_input_events.push(event);
283 }
284
285 pub(crate) fn has_pending_input_events(&self) -> bool {
288 !self.pending_input_events.borrow().is_empty()
289 }
290
291 pub(crate) fn alternate_action_keyboard_modifier_active(&self) -> bool {
292 #[cfg(target_os = "macos")]
293 {
294 self.active_keyboard_modifiers
295 .get()
296 .contains(Modifiers::META)
297 }
298 #[cfg(not(target_os = "macos"))]
299 {
300 self.active_keyboard_modifiers
301 .get()
302 .contains(Modifiers::CONTROL)
303 }
304 }
305
306 pub(crate) fn handle_pending_input_events(&self, cx: &mut JSContext) {
307 debug_assert!(
308 !self.pending_input_events.borrow().is_empty(),
309 "handle_pending_input_events called with no events"
310 );
311 let mut realm = enter_auto_realm(cx, &*self.window);
312 let cx = &mut realm.current_realm();
313
314 *self.mouse_move_event_index.safe_borrow_mut(cx.no_gc()) = None;
316 *self.wheel_event_index.safe_borrow_mut(cx.no_gc()) = None;
317 let pending_input_events =
318 mem::take(&mut *self.pending_input_events.safe_borrow_mut(cx.no_gc()));
319 let mut coalesced_mouse_move_event_ids = mem::take(
320 &mut *self
321 .coalesced_mouse_move_event_ids
322 .safe_borrow_mut(cx.no_gc()),
323 );
324 let mut coalesced_wheel_event_ids =
325 mem::take(&mut *self.coalesced_wheel_event_ids.safe_borrow_mut(cx.no_gc()));
326
327 let mut input_event_outcomes = Vec::with_capacity(
328 pending_input_events.len() +
329 coalesced_mouse_move_event_ids.len() +
330 coalesced_wheel_event_ids.len(),
331 );
332 for event in pending_input_events {
338 self.active_keyboard_modifiers
339 .set(event.active_keyboard_modifiers);
340 let result = match event.event.event {
341 InputEvent::MouseButton(mouse_button_event) => {
342 self.handle_native_mouse_button_event(cx, mouse_button_event, &event);
343 InputEventResult::default()
344 },
345 InputEvent::MouseMove(_) => {
346 self.handle_native_mouse_move_event(cx, &event);
347 input_event_outcomes.extend(
348 mem::take(&mut coalesced_mouse_move_event_ids)
349 .into_iter()
350 .map(|id| InputEventOutcome {
351 id,
352 result: InputEventResult::default(),
353 }),
354 );
355 InputEventResult::default()
356 },
357 InputEvent::MouseLeftViewport(mouse_leave_event) => {
358 self.handle_mouse_left_viewport_event(cx, &event, &mouse_leave_event);
359 InputEventResult::default()
360 },
361 InputEvent::Touch(touch_event) => self.handle_touch_event(cx, touch_event, &event),
362 InputEvent::Wheel(wheel_event) => {
363 let result = self.handle_wheel_event(cx, wheel_event, &event);
364 input_event_outcomes.extend(
365 mem::take(&mut coalesced_wheel_event_ids)
366 .into_iter()
367 .map(|id| InputEventOutcome { id, result }),
368 );
369 result
370 },
371 InputEvent::Keyboard(keyboard_event) => {
372 self.handle_keyboard_event(cx, keyboard_event)
373 },
374 InputEvent::Ime(ime_event) => self.handle_ime_event(cx, ime_event),
375 #[cfg(feature = "gamepad")]
376 InputEvent::Gamepad(gamepad_event) => {
377 self.handle_gamepad_event(gamepad_event);
378 InputEventResult::default()
379 },
380 InputEvent::EditingAction(editing_action_event) => {
381 self.handle_editing_action(cx, None, editing_action_event)
382 },
383 };
384
385 input_event_outcomes.push(InputEventOutcome {
386 id: event.event.id,
387 result,
388 });
389 }
390
391 self.notify_embedder_that_events_were_handled(input_event_outcomes);
392 }
393
394 fn notify_embedder_that_events_were_handled(
395 &self,
396 input_event_outcomes: Vec<InputEventOutcome>,
397 ) {
398 let trusted_window = Trusted::new(&*self.window);
401 self.window
402 .as_global_scope()
403 .task_manager()
404 .dom_manipulation_task_source()
405 .queue(task!(notify_webdriver_input_event_completed: move || {
406 let window = trusted_window.root();
407 window.send_to_embedder(
408 EmbedderMsg::InputEventsHandled(window.webview_id(), input_event_outcomes));
409 }));
410 }
411
412 fn target_for_events_following_focus(&self) -> DomRoot<EventTarget> {
416 let document = self.window.Document();
417 match &*document.focus_handler().focused_area() {
418 FocusableArea::Node { node, .. } => DomRoot::from_ref(node.upcast()),
419 FocusableArea::IFrameViewport { iframe_element, .. } => {
420 DomRoot::from_ref(iframe_element.upcast())
421 },
422 FocusableArea::Viewport => document
423 .GetBody()
424 .map(DomRoot::upcast)
425 .unwrap_or_else(|| DomRoot::from_ref(self.window.upcast())),
426 }
427 }
428
429 pub(crate) fn set_cursor(&self, cursor: Option<Cursor>) {
430 if cursor == self.current_cursor.get() {
431 return;
432 }
433 self.current_cursor.set(cursor);
434 self.window.send_to_embedder(EmbedderMsg::SetCursor(
435 self.window.webview_id(),
436 cursor.unwrap_or_default(),
437 ));
438 }
439
440 fn handle_mouse_left_viewport_event(
441 &self,
442 cx: &mut JSContext,
443 input_event: &ConstellationInputEvent,
444 mouse_leave_event: &MouseLeftViewportEvent,
445 ) {
446 if let Some(current_hover_target) = self.current_hover_target.get() {
447 let current_hover_target = current_hover_target.upcast::<Node>();
448 for element in current_hover_target
449 .inclusive_ancestors(ShadowIncluding::Yes)
450 .filter_map(DomRoot::downcast::<Element>)
451 {
452 element.set_hover_state(false);
453 }
454
455 let flags = HitTestFlags::empty();
456 if let Some(hit_test_result) = self
457 .most_recent_mousemove_point
458 .get()
459 .and_then(|point| self.window.hit_test_from_point_in_viewport(flags, point))
460 {
461 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
462 cx,
463 &self.window,
464 FireMouseEventType::Out,
465 &hit_test_result,
466 input_event,
467 );
468
469 mouse_out_event
471 .to_pointer_hover_event(cx, "pointerout")
472 .upcast::<Event>()
473 .fire(cx, current_hover_target.upcast());
474
475 mouse_out_event
476 .upcast::<Event>()
477 .fire(cx, current_hover_target.upcast());
478
479 self.handle_mouse_enter_leave_event(
480 cx,
481 current_hover_target,
482 None,
483 FireMouseEventType::Leave,
484 &hit_test_result,
485 input_event,
486 );
487 }
488 }
489
490 if !mouse_leave_event.focus_moving_to_another_iframe {
495 self.window
499 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
500 self.set_cursor(None);
501 } else {
502 self.current_cursor.set(None);
503 }
504
505 self.current_hover_target.set(None);
506 self.most_recent_mousemove_point.set(None);
507 }
508
509 fn handle_mouse_enter_leave_event(
510 &self,
511 cx: &mut JSContext,
512 event_target: &Node,
513 related_target: Option<&Node>,
514 event_type: FireMouseEventType,
515 hit_test_result: &HitTestResult,
516 input_event: &ConstellationInputEvent,
517 ) {
518 assert!(matches!(
519 event_type,
520 FireMouseEventType::Enter | FireMouseEventType::Leave
521 ));
522
523 let common_ancestor = match related_target.as_ref() {
524 Some(related_target) => event_target
525 .common_ancestor_in_flat_tree(related_target)
526 .unwrap_or_else(|| DomRoot::from_ref(event_target)),
527 None => DomRoot::from_ref(event_target),
528 };
529
530 let mut targets: Vec<_> = event_target
533 .inclusive_ancestors_in_flat_tree()
534 .take_while(|node| *node != common_ancestor)
535 .collect();
536
537 if event_type == FireMouseEventType::Enter {
540 targets.reverse();
541 }
542
543 let pointer_event_name = match event_type {
544 FireMouseEventType::Enter => "pointerenter",
545 FireMouseEventType::Leave => "pointerleave",
546 _ => unreachable!(),
547 };
548
549 for target in targets {
550 let mouse_event = MouseEvent::new_for_platform_motion_event(
551 cx,
552 &self.window,
553 event_type,
554 hit_test_result,
555 input_event,
556 );
557 mouse_event
558 .upcast::<Event>()
559 .set_related_target(related_target.as_ref().map(|target| target.upcast()));
560
561 mouse_event
563 .to_pointer_hover_event(cx, pointer_event_name)
564 .upcast::<Event>()
565 .fire(cx, target.upcast());
566
567 mouse_event.upcast::<Event>().fire(cx, target.upcast());
569 }
570 }
571
572 fn handle_native_mouse_move_event(
574 &self,
575 cx: &mut JSContext,
576 input_event: &ConstellationInputEvent,
577 ) {
578 let pointer_id = PointerId::Mouse as i32;
581 let released_disconnected =
582 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
583
584 let flags = if input_event.primary_button_is_pressed() {
585 HitTestFlags::IncludeDomPosition
586 } else {
587 HitTestFlags::empty()
588 };
589
590 let Some(hit_test_result) = self.window.hit_test_from_input_event(flags, input_event)
595 else {
596 return;
597 };
598
599 let old_mouse_move_point = self
600 .most_recent_mousemove_point
601 .replace(Some(hit_test_result.point_in_frame));
602 if old_mouse_move_point == Some(hit_test_result.point_in_frame) {
603 return;
604 }
605
606 self.set_cursor(Some(hit_test_result.cursor));
608
609 let Some(new_target) = hit_test_result
610 .node
611 .inclusive_ancestors(ShadowIncluding::Yes)
612 .find_map(DomRoot::downcast::<Element>)
613 else {
614 return;
615 };
616
617 let capture_is_active = self.get_pointer_capture_target(pointer_id).is_some();
618 let old_hover_target = self.current_hover_target.get();
619 let target_has_changed = old_hover_target
620 .as_ref()
621 .is_none_or(|old_target| *old_target != new_target);
622
623 if target_has_changed {
626 if let Some(old_target) = self.current_hover_target.get() {
628 let old_target_is_ancestor_of_new_target = old_target
629 .upcast::<Node>()
630 .is_ancestor_of(new_target.upcast::<Node>());
631
632 if !old_target_is_ancestor_of_new_target {
635 for element in old_target
636 .upcast::<Node>()
637 .inclusive_ancestors(ShadowIncluding::Yes)
638 .filter_map(DomRoot::downcast::<Element>)
639 {
640 element.set_hover_state(false);
641 }
642 }
643
644 if !capture_is_active {
645 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
646 cx,
647 &self.window,
648 FireMouseEventType::Out,
649 &hit_test_result,
650 input_event,
651 );
652 mouse_out_event
653 .upcast::<Event>()
654 .set_related_target(Some(new_target.upcast()));
655
656 mouse_out_event
658 .to_pointer_hover_event(cx, "pointerout")
659 .upcast::<Event>()
660 .fire(cx, old_target.upcast());
661
662 mouse_out_event
663 .upcast::<Event>()
664 .fire(cx, old_target.upcast());
665
666 if !old_target_is_ancestor_of_new_target {
667 let event_target = old_target.upcast::<Node>();
668 let moving_into = Some(new_target.upcast::<Node>());
669 self.handle_mouse_enter_leave_event(
670 cx,
671 event_target,
672 moving_into,
673 FireMouseEventType::Leave,
674 &hit_test_result,
675 input_event,
676 );
677 }
678 }
679 }
680
681 for element in new_target
683 .upcast::<Node>()
684 .inclusive_ancestors(ShadowIncluding::Yes)
685 .filter_map(DomRoot::downcast::<Element>)
686 {
687 element.set_hover_state(true);
688 }
689
690 if !capture_is_active {
691 let mouse_over_event = MouseEvent::new_for_platform_motion_event(
692 cx,
693 &self.window,
694 FireMouseEventType::Over,
695 &hit_test_result,
696 input_event,
697 );
698 mouse_over_event
699 .upcast::<Event>()
700 .set_related_target(old_hover_target.as_ref().map(|target| target.upcast()));
701
702 mouse_over_event
704 .to_pointer_hover_event(cx, "pointerover")
705 .upcast::<Event>()
706 .dispatch(cx, new_target.upcast(), false);
707
708 mouse_over_event
709 .upcast::<Event>()
710 .dispatch(cx, new_target.upcast(), false);
711
712 let moving_from = old_hover_target
713 .as_ref()
714 .map(|old_target| old_target.upcast::<Node>());
715 let event_target = new_target.upcast::<Node>();
716 self.handle_mouse_enter_leave_event(
717 cx,
718 event_target,
719 moving_from,
720 FireMouseEventType::Enter,
721 &hit_test_result,
722 input_event,
723 );
724 }
725 }
726
727 let mouse_event = MouseEvent::new_for_platform_motion_event(
730 cx,
731 &self.window,
732 FireMouseEventType::Move,
733 &hit_test_result,
734 input_event,
735 );
736
737 let pointer_target = self
742 .get_pointer_capture_target(pointer_id)
743 .map(DomRoot::upcast::<EventTarget>)
744 .unwrap_or_else(|| DomRoot::from_ref(new_target.upcast::<EventTarget>()));
745
746 let pointer_event = mouse_event.to_pointer_event(cx, Atom::from("pointermove"));
747 pointer_event.upcast::<Event>().set_composed(true);
748 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
749
750 if !released_disconnected {
754 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
755 }
756
757 mouse_event.upcast::<Event>().fire(cx, &pointer_target);
759
760 self.update_current_hover_target_and_status(Some(new_target));
761 }
762
763 fn update_current_hover_target_and_status(&self, new_hover_target: Option<DomRoot<Element>>) {
764 let current_hover_target = self.current_hover_target.get();
765 if current_hover_target == new_hover_target {
766 return;
767 }
768
769 let previous_hover_target = self.current_hover_target.get();
770 self.current_hover_target.set(new_hover_target.as_deref());
771
772 if let Some(target) = self.current_hover_target.get() &&
775 let Some(anchor) = target
776 .upcast::<Node>()
777 .inclusive_ancestors(ShadowIncluding::Yes)
778 .find_map(DomRoot::downcast::<HTMLAnchorElement>)
779 {
780 let status = anchor
781 .full_href_url_for_user_interface()
782 .map(|url| url.to_string());
783 self.window
784 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), status));
785 return;
786 }
787
788 if previous_hover_target.is_none_or(|previous_hover_target| {
793 previous_hover_target
794 .upcast::<Node>()
795 .inclusive_ancestors(ShadowIncluding::Yes)
796 .any(|node| node.is::<HTMLAnchorElement>())
797 }) {
798 self.window
799 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
800 }
801 }
802
803 pub(crate) fn handle_refresh_cursor(&self) {
804 let Some(most_recent_mousemove_point) = self.most_recent_mousemove_point.get() else {
805 return;
806 };
807
808 let flags = HitTestFlags::empty();
809 let Some(hit_test_result) = self
810 .window
811 .hit_test_from_point_in_viewport(flags, most_recent_mousemove_point)
812 else {
813 return;
814 };
815
816 self.set_cursor(Some(hit_test_result.cursor));
817 }
818
819 fn set_active_element(&self, original_target: &Element) {
820 let find_element_for_activation = |element: &Element| {
821 let node: &Node = element.upcast();
822 if node.is_in_ua_widget() &&
823 let Some(containing_shadow_root) = node.containing_shadow_root()
824 {
825 return containing_shadow_root.Host();
826 }
827
828 if node.type_id() ==
830 NodeTypeId::Element(ElementTypeId::HTMLElement(
831 HTMLElementTypeId::HTMLLabelElement,
832 ))
833 {
834 let label = element.downcast::<HTMLLabelElement>().unwrap();
835 if let Some(control) = label.GetControl() {
836 return DomRoot::from_ref(control.upcast::<Element>());
837 }
838 }
839
840 DomRoot::from_ref(element)
841 };
842 let element_for_activation = find_element_for_activation(original_target);
843
844 if let Some(currently_active_element) = self.current_active_element.get() {
847 if currently_active_element == element_for_activation {
848 return;
849 }
850 self.unset_active_element();
851 }
852
853 element_for_activation.set_active_state(true);
854 self.current_active_element
855 .set(Some(&*element_for_activation));
856 }
857
858 fn unset_active_element(&self) {
859 if let Some(active_element) = self.current_active_element.take() {
860 active_element.set_active_state(false);
861 }
862 }
863
864 fn handle_native_mouse_button_event(
867 &self,
868 cx: &mut JSContext,
869 event: MouseButtonEvent,
870 input_event: &ConstellationInputEvent,
871 ) {
872 let flags = if input_event.primary_button_is_pressed() {
873 HitTestFlags::IncludeDomPosition
874 } else {
875 HitTestFlags::empty()
876 };
877 let Some(hit_test_result) = self.window.hit_test_from_input_event(flags, input_event)
879 else {
880 return;
881 };
882
883 debug!(
884 "{:?}: at {:?}",
885 event.action, hit_test_result.point_in_frame
886 );
887
888 let document = self.window.Document();
891 if event.action == MouseButtonAction::Down {
892 document
893 .focus_handler()
894 .set_sequential_focus_navigation_starting_point(&hit_test_result.node);
895 }
896
897 let Some(element) = hit_test_result
898 .node
899 .inclusive_ancestors(ShadowIncluding::Yes)
900 .find_map(DomRoot::downcast::<Element>)
901 else {
902 return;
903 };
904
905 let node = element.upcast::<Node>();
906 debug!("{:?} on {:?}", event.action, node.debug_str());
907
908 if event.button == MouseButton::Left {
913 if event.action == MouseButtonAction::Down {
914 self.set_active_element(&element);
915 }
916 if event.action == MouseButtonAction::Up {
917 self.unset_active_element();
918 }
919 }
920
921 if element.is_actually_disabled() {
925 return;
926 }
927
928 let mouse_event_type = match event.action {
929 embedder_traits::MouseButtonAction::Up => atom!("mouseup"),
930 embedder_traits::MouseButtonAction::Down => atom!("mousedown"),
931 };
932
933 if event.action == MouseButtonAction::Down {
940 self.click_counting_info
941 .safe_borrow_mut(cx.no_gc())
942 .reset_click_count_if_necessary(event.button, hit_test_result.point_in_frame);
943 }
944
945 let mouse_event = MouseEvent::for_platform_button_event(
946 cx,
947 mouse_event_type,
948 event,
949 input_event.pressed_mouse_buttons,
950 &self.window,
951 &hit_test_result,
952 input_event.active_keyboard_modifiers,
953 self.click_counting_info.borrow().count + 1,
954 );
955
956 match event.action {
957 MouseButtonAction::Down => {
958 self.last_mouse_button_down_point
959 .set(Some(hit_test_result.point_in_frame));
960
961 let mouse_buttons_down = self.mouse_buttons_down.get();
963 let pointer_event_name = if mouse_buttons_down == 0 {
964 "pointerdown".into()
969 } else {
970 "pointermove".into()
976 };
977 let pointer_event = mouse_event.to_pointer_event(cx, pointer_event_name);
978
979 let pointer_id = PointerId::Mouse as i32;
981
982 let released_disconnected =
984 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
985
986 let pointer_target = self
988 .get_pointer_capture_target(pointer_id)
989 .map(DomRoot::upcast::<EventTarget>)
990 .unwrap_or_else(|| DomRoot::from_ref(node.upcast::<EventTarget>()));
991
992 self.mouse_buttons_down.set(mouse_buttons_down + 1);
994
995 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
996
997 if !released_disconnected {
1001 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
1002 }
1003
1004 let result = mouse_event
1006 .upcast::<Event>()
1007 .dispatch(cx, node.upcast(), false);
1008
1009 if result {
1012 document
1016 .focus_handler()
1017 .focus(cx, &node.find_click_focusable_area(cx));
1018 }
1019
1020 if let MouseButton::Right = event.button {
1023 self.maybe_show_context_menu(cx, node.upcast(), &hit_test_result, input_event);
1024 }
1025 },
1026 MouseButtonAction::Up => {
1028 let mouse_buttons_down = self.mouse_buttons_down.get();
1030 let pointer_event_name = if mouse_buttons_down == 1 {
1031 "pointerup".into()
1036 } else {
1037 "pointermove".into()
1043 };
1044 let pointer_event = mouse_event.to_pointer_event(cx, pointer_event_name);
1045
1046 let pointer_id = PointerId::Mouse as i32;
1048
1049 let released_disconnected =
1051 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
1052
1053 let pointer_target = self
1055 .get_pointer_capture_target(pointer_id)
1056 .map(DomRoot::upcast::<EventTarget>)
1057 .unwrap_or_else(|| DomRoot::from_ref(node.upcast::<EventTarget>()));
1058
1059 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1060
1061 self.mouse_buttons_down
1064 .set(mouse_buttons_down.saturating_sub(1));
1065
1066 if !released_disconnected {
1070 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
1071 }
1072
1073 if mouse_buttons_down == 1 {
1075 self.implicit_release_pointer_capture(cx, pointer_id, "mouse", true);
1076 }
1077
1078 mouse_event
1080 .upcast::<Event>()
1081 .dispatch(cx, node.upcast(), false);
1082
1083 self.click_counting_info
1087 .safe_borrow_mut(cx.no_gc())
1088 .increment_click_count(event.button, hit_test_result.point_in_frame);
1089
1090 self.maybe_trigger_click_for_mouse_button_down_event(
1091 cx,
1092 event,
1093 input_event,
1094 &hit_test_result,
1095 &element,
1096 );
1097 },
1098 }
1099 }
1100
1101 fn maybe_trigger_click_for_mouse_button_down_event(
1104 &self,
1105 cx: &mut JSContext,
1106 event: MouseButtonEvent,
1107 input_event: &ConstellationInputEvent,
1108 hit_test_result: &HitTestResult,
1109 element: &Element,
1110 ) {
1111 if event.button != MouseButton::Left {
1112 return;
1113 }
1114
1115 let Some(last_mouse_button_down_point) = self.last_mouse_button_down_point.take() else {
1116 return;
1117 };
1118
1119 let distance = last_mouse_button_down_point.distance_to(hit_test_result.point_in_frame);
1120 let maximum_click_distance = 10.0 * self.window.device_pixel_ratio().get();
1121 if distance > maximum_click_distance {
1122 return;
1123 }
1124
1125 let element = &element.inclusive_ancestor_element_in_non_ua_shadow_root();
1129 self.most_recently_clicked_element.set(Some(element));
1130
1131 let click_count = self.click_counting_info.borrow().count;
1132 element.set_click_in_progress(true);
1133 MouseEvent::for_platform_button_event(
1134 cx,
1135 atom!("click"),
1136 event,
1137 input_event.pressed_mouse_buttons,
1138 &self.window,
1139 hit_test_result,
1140 input_event.active_keyboard_modifiers,
1141 click_count,
1142 )
1143 .upcast::<Event>()
1144 .dispatch(cx, element.upcast(), false);
1145 element.set_click_in_progress(false);
1146
1147 if click_count.is_multiple_of(2) {
1156 MouseEvent::for_platform_button_event(
1157 cx,
1158 Atom::from("dblclick"),
1159 event,
1160 input_event.pressed_mouse_buttons,
1161 &self.window,
1162 hit_test_result,
1163 input_event.active_keyboard_modifiers,
1164 2,
1165 )
1166 .upcast::<Event>()
1167 .dispatch(cx, element.upcast(), false);
1168 }
1169 }
1170
1171 fn maybe_show_context_menu(
1173 &self,
1174 cx: &mut js::context::JSContext,
1175 target: &EventTarget,
1176 hit_test_result: &HitTestResult,
1177 input_event: &ConstellationInputEvent,
1178 ) {
1179 let menu_event = PointerEvent::new(
1181 cx,
1182 &self.window, "contextmenu".into(), EventBubbles::Bubbles, EventCancelable::Cancelable, Some(&self.window), 0, hit_test_result.point_in_frame.to_i32(),
1189 hit_test_result.point_in_frame.to_i32(),
1190 hit_test_result
1191 .point_relative_to_initial_containing_block
1192 .to_i32(),
1193 input_event.active_keyboard_modifiers,
1194 2i16, input_event.pressed_mouse_buttons,
1196 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![], );
1213 menu_event.upcast::<Event>().set_composed(true);
1214
1215 let result = menu_event.upcast::<Event>().fire(cx, target);
1217
1218 if result {
1220 self.window
1221 .Document()
1222 .embedder_controls()
1223 .show_context_menu(hit_test_result);
1224 };
1225 }
1226
1227 fn handle_touch_event(
1228 &self,
1229 cx: &mut JSContext,
1230 event: EmbedderTouchEvent,
1231 input_event: &ConstellationInputEvent,
1232 ) -> InputEventResult {
1233 let flags = HitTestFlags::empty();
1234 let Some(hit_test_result) = self.window.hit_test_from_input_event(flags, input_event)
1236 else {
1237 self.update_active_touch_points_when_early_return(event);
1238 return Default::default();
1239 };
1240
1241 let TouchId(identifier) = event.touch_id;
1242
1243 let Some(element) = hit_test_result
1244 .node
1245 .inclusive_ancestors(ShadowIncluding::Yes)
1246 .find_map(DomRoot::downcast::<Element>)
1247 else {
1248 self.update_active_touch_points_when_early_return(event);
1249 return Default::default();
1250 };
1251
1252 let current_target = DomRoot::upcast::<EventTarget>(element.clone());
1253 let window = &*self.window;
1254
1255 let client_x = Finite::wrap(hit_test_result.point_in_frame.x as f64);
1256 let client_y = Finite::wrap(hit_test_result.point_in_frame.y as f64);
1257 let page_x =
1258 Finite::wrap(hit_test_result.point_in_frame.x as f64 + window.PageXOffset() as f64);
1259 let page_y =
1260 Finite::wrap(hit_test_result.point_in_frame.y as f64 + window.PageYOffset() as f64);
1261
1262 let pointer_touch = Touch::new(
1264 cx,
1265 window,
1266 identifier,
1267 ¤t_target,
1268 client_x,
1269 client_y, client_x,
1271 client_y,
1272 page_x,
1273 page_y,
1274 );
1275
1276 let pointer_event_name = match event.event_type {
1278 TouchEventType::Down => "pointerdown",
1279 TouchEventType::Move => "pointermove",
1280 TouchEventType::Up => "pointerup",
1281 TouchEventType::Cancel => "pointercancel",
1282 };
1283
1284 let pointer_type = match event.pointer_type {
1286 TouchPointerType::Pen => "pen",
1287 TouchPointerType::Touch => "touch",
1288 };
1289
1290 let pointer_id = self.get_or_create_pointer_id_for_touch(identifier);
1292 let is_primary = self.is_primary_pointer(pointer_id);
1293
1294 if matches!(event.event_type, TouchEventType::Down) {
1297 let pointer_over = pointer_touch.to_pointer_event(
1299 cx,
1300 window,
1301 "pointerover",
1302 pointer_id,
1303 is_primary,
1304 pointer_type,
1305 input_event.active_keyboard_modifiers,
1306 true, Some(hit_test_result.point_in_node),
1308 );
1309 pointer_over.upcast::<Event>().fire(cx, ¤t_target);
1310
1311 self.fire_pointer_event_for_touch(
1313 cx,
1314 &element,
1315 &pointer_touch,
1316 pointer_id,
1317 "pointerenter",
1318 is_primary,
1319 pointer_type,
1320 input_event,
1321 &hit_test_result,
1322 );
1323 }
1324
1325 let released_disconnected = if matches!(event.event_type, TouchEventType::Cancel) {
1329 false
1330 } else {
1331 self.release_disconnected_pointer_capture(cx, pointer_id, pointer_type, is_primary)
1332 };
1333
1334 let pointer_target = self
1336 .get_pointer_capture_target(pointer_id)
1337 .map(DomRoot::upcast::<EventTarget>)
1338 .unwrap_or_else(|| current_target.clone());
1339
1340 let pointer_event = pointer_touch.to_pointer_event(
1341 cx,
1342 window,
1343 pointer_event_name,
1344 pointer_id,
1345 is_primary,
1346 pointer_type,
1347 input_event.active_keyboard_modifiers,
1348 event.is_cancelable(),
1349 Some(hit_test_result.point_in_node),
1350 );
1351 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1352
1353 if !released_disconnected && !matches!(event.event_type, TouchEventType::Cancel) {
1359 self.process_pending_pointer_capture(cx, pointer_id, pointer_type, is_primary);
1360 }
1361
1362 if matches!(
1365 event.event_type,
1366 TouchEventType::Up | TouchEventType::Cancel
1367 ) {
1368 self.implicit_release_pointer_capture(cx, pointer_id, pointer_type, is_primary);
1369 }
1370
1371 if matches!(
1374 event.event_type,
1375 TouchEventType::Up | TouchEventType::Cancel
1376 ) {
1377 let pointer_out = pointer_touch.to_pointer_event(
1379 cx,
1380 window,
1381 "pointerout",
1382 pointer_id,
1383 is_primary,
1384 pointer_type,
1385 input_event.active_keyboard_modifiers,
1386 true, Some(hit_test_result.point_in_node),
1388 );
1389 pointer_out.upcast::<Event>().fire(cx, ¤t_target);
1390
1391 self.fire_pointer_event_for_touch(
1393 cx,
1394 &element,
1395 &pointer_touch,
1396 pointer_id,
1397 "pointerleave",
1398 is_primary,
1399 pointer_type,
1400 input_event,
1401 &hit_test_result,
1402 );
1403 }
1404
1405 let (touch_dispatch_target, changed_touch) = match event.event_type {
1406 TouchEventType::Down => {
1407 self.active_touch_points
1409 .safe_borrow_mut(cx.no_gc())
1410 .push(Dom::from_ref(&*pointer_touch));
1411 self.set_active_element(&element);
1412 (current_target, pointer_touch)
1413 },
1414 _ => {
1415 let active_touch_points = self.active_touch_points.borrow();
1421 let Some(index) = active_touch_points
1422 .iter()
1423 .position(|point| point.Identifier() == identifier)
1424 else {
1425 warn!("No active touch point for {:?}", event.event_type);
1426 return Default::default();
1427 };
1428 let original_target = active_touch_points[index].Target();
1430 drop(active_touch_points);
1431
1432 let touch_with_touchstart_target = Touch::new(
1433 cx,
1434 window,
1435 identifier,
1436 &original_target,
1437 client_x,
1438 client_y,
1439 client_x,
1440 client_y,
1441 page_x,
1442 page_y,
1443 );
1444
1445 let mut active_touch_points = self.active_touch_points.safe_borrow_mut(cx.no_gc());
1446 match event.event_type {
1448 TouchEventType::Move => {
1449 active_touch_points[index] = Dom::from_ref(&*touch_with_touchstart_target);
1450 },
1451 TouchEventType::Up | TouchEventType::Cancel => {
1452 active_touch_points.swap_remove(index);
1453 self.remove_pointer_id_for_touch(identifier);
1454 self.unset_active_element();
1455 },
1456 TouchEventType::Down => unreachable!("Should have been handled above"),
1457 }
1458 (original_target, touch_with_touchstart_target)
1459 },
1460 };
1461
1462 rooted_vec!(let mut target_touches);
1463 target_touches.extend(
1464 self.active_touch_points
1465 .borrow()
1466 .iter()
1467 .filter(|touch| touch.Target() == touch_dispatch_target)
1468 .cloned(),
1469 );
1470
1471 let event_name = match event.event_type {
1472 TouchEventType::Down => "touchstart",
1473 TouchEventType::Move => "touchmove",
1474 TouchEventType::Up => "touchend",
1475 TouchEventType::Cancel => "touchcancel",
1476 };
1477
1478 let touches = TouchList::new(cx, window, self.active_touch_points.borrow().r());
1479 let changed_touches = TouchList::new(cx, window, from_ref(&&*changed_touch));
1480 let target_touches = TouchList::new(cx, window, target_touches.r());
1481
1482 let touch_event = TouchEvent::new(
1483 cx,
1484 window,
1485 event_name.into(),
1486 EventBubbles::Bubbles,
1487 EventCancelable::from(event.is_cancelable()),
1488 EventComposed::Composed,
1489 Some(window),
1490 0i32,
1491 &touches,
1492 &changed_touches,
1493 &target_touches,
1494 false,
1496 false,
1497 false,
1498 false,
1499 );
1500 let event = touch_event.upcast::<Event>();
1501 event.fire(cx, &touch_dispatch_target);
1502 event.flags().into()
1503 }
1504
1505 fn update_active_touch_points_when_early_return(&self, event: EmbedderTouchEvent) {
1514 match event.event_type {
1515 TouchEventType::Down | TouchEventType::Move => {},
1516 TouchEventType::Up | TouchEventType::Cancel => {
1517 let mut active_touch_points = self.active_touch_points.borrow_mut();
1518 if let Some(index) = active_touch_points
1519 .iter()
1520 .position(|t| t.Identifier() == event.touch_id.0)
1521 {
1522 active_touch_points.swap_remove(index);
1523 self.remove_pointer_id_for_touch(event.touch_id.0);
1524 } else {
1525 warn!(
1526 "Received {:?} for a non-active touch point {}",
1527 event.event_type, event.touch_id.0
1528 );
1529 }
1530 },
1531 }
1532 }
1533
1534 fn handle_keyboard_event(
1536 &self,
1537 cx: &mut JSContext,
1538 keyboard_event: EmbedderKeyboardEvent,
1539 ) -> InputEventResult {
1540 let target = &self.target_for_events_following_focus();
1541 let keyevent = KeyboardEvent::new_with_platform_keyboard_event(
1542 cx,
1543 &self.window,
1544 keyboard_event.event.state.event_type().into(),
1545 &keyboard_event.event,
1546 );
1547
1548 let event = keyevent.upcast::<Event>();
1549
1550 event.set_composed(true);
1551
1552 event.fire(cx, target);
1553
1554 let mut flags = event.flags();
1555 if flags.contains(EventFlags::Canceled) {
1556 return flags.into();
1557 }
1558
1559 let is_character_value_key = matches!(
1565 keyboard_event.event.key,
1566 Key::Character(_) | Key::Named(NamedKey::Enter)
1567 );
1568 if keyboard_event.event.state == KeyState::Down &&
1569 is_character_value_key &&
1570 !keyboard_event.event.is_composing
1571 {
1572 let keypress_event = KeyboardEvent::new_with_platform_keyboard_event(
1574 cx,
1575 &self.window,
1576 atom!("keypress"),
1577 &keyboard_event.event,
1578 );
1579 keypress_event.upcast::<Event>().set_composed(true);
1580 let event = keypress_event.upcast::<Event>();
1581 event.fire(cx, target);
1582 flags = event.flags();
1583 }
1584
1585 flags.into()
1586 }
1587
1588 fn handle_ime_event(&self, cx: &mut JSContext, event: ImeEvent) -> InputEventResult {
1589 let document = self.window.Document();
1590 let composition_event = match event {
1591 ImeEvent::Dismissed => {
1592 document.focus_handler().focus(cx, &FocusableArea::Viewport);
1593 return Default::default();
1594 },
1595 ImeEvent::Composition(composition_event) => composition_event,
1596 };
1597
1598 let focused_area = document.focus_handler().focused_area();
1603 let Some(focused_element) = focused_area.element() else {
1604 return Default::default();
1606 };
1607
1608 let cancelable = composition_event.state == keyboard_types::CompositionState::Start;
1609 let event = CompositionEvent::new(
1610 cx,
1611 &self.window,
1612 composition_event.state.event_type().into(),
1613 true,
1614 cancelable,
1615 Some(&self.window),
1616 0,
1617 DOMString::from(composition_event.data),
1618 );
1619
1620 let event = event.upcast::<Event>();
1621 event.fire(cx, focused_element.upcast());
1622 event.flags().into()
1623 }
1624
1625 fn handle_wheel_event(
1626 &self,
1627 cx: &mut JSContext,
1628 event: EmbedderWheelEvent,
1629 input_event: &ConstellationInputEvent,
1630 ) -> InputEventResult {
1631 let flags = HitTestFlags::empty();
1633 let Some(hit_test_result) = self.window.hit_test_from_input_event(flags, input_event)
1634 else {
1635 return Default::default();
1636 };
1637
1638 let Some(el) = hit_test_result
1639 .node
1640 .inclusive_ancestors(ShadowIncluding::Yes)
1641 .find_map(DomRoot::downcast::<Element>)
1642 else {
1643 return Default::default();
1644 };
1645
1646 let node = el.upcast::<Node>();
1647 debug!(
1648 "wheel: on {:?} at {:?}",
1649 node.debug_str(),
1650 hit_test_result.point_in_frame
1651 );
1652
1653 let event_type = "wheel".into();
1654
1655 let cancelable = EventCancelable::from(
1656 self.window
1657 .upcast::<EventTarget>()
1658 .has_non_passive_listener(&event_type) ||
1659 node.inclusive_ancestors(ShadowIncluding::Yes)
1660 .any(|target| {
1661 target
1662 .upcast::<EventTarget>()
1663 .has_non_passive_listener(&event_type)
1664 }),
1665 );
1666 let dom_event = WheelEvent::new(
1668 cx,
1669 &self.window,
1670 event_type,
1671 EventBubbles::Bubbles,
1672 cancelable,
1673 Some(&self.window),
1674 0i32,
1675 hit_test_result.point_in_frame.to_i32(),
1676 hit_test_result.point_in_frame.to_i32(),
1677 hit_test_result
1678 .point_relative_to_initial_containing_block
1679 .to_i32(),
1680 input_event.active_keyboard_modifiers,
1681 0i16,
1682 input_event.pressed_mouse_buttons,
1683 None,
1684 None,
1685 Finite::wrap(-event.delta.x),
1690 Finite::wrap(-event.delta.y),
1691 Finite::wrap(-event.delta.z),
1692 event.delta.mode as u32,
1693 );
1694
1695 let dom_event = dom_event.upcast::<Event>();
1696 dom_event.set_trusted(true);
1697 dom_event.set_composed(true);
1698 dom_event.fire(cx, node.upcast());
1699
1700 dom_event.flags().into()
1701 }
1702
1703 #[cfg(feature = "gamepad")]
1704 fn handle_gamepad_event(&self, gamepad_event: EmbedderGamepadEvent) {
1705 match gamepad_event {
1706 EmbedderGamepadEvent::Connected(index, name, bounds, supported_haptic_effects) => {
1707 self.handle_gamepad_connect(
1708 index.0,
1709 name,
1710 bounds.axis_bounds,
1711 bounds.button_bounds,
1712 supported_haptic_effects,
1713 );
1714 },
1715 EmbedderGamepadEvent::Disconnected(index) => {
1716 self.handle_gamepad_disconnect(index.0);
1717 },
1718 EmbedderGamepadEvent::Updated(index, update_type) => {
1719 self.receive_new_gamepad_button_or_axis(index.0, update_type);
1720 },
1721 };
1722 }
1723
1724 #[cfg(feature = "gamepad")]
1726 fn handle_gamepad_connect(
1727 &self,
1728 _index: usize,
1732 name: String,
1733 axis_bounds: (f64, f64),
1734 button_bounds: (f64, f64),
1735 supported_haptic_effects: GamepadSupportedHapticEffects,
1736 ) {
1737 let doc = self.window.Document();
1739
1740 if !doc.allowed_to_use_feature(PermissionName::Gamepad) {
1743 return;
1744 }
1745
1746 let trusted_window = Trusted::new(&*self.window);
1747
1748 self.window
1751 .upcast::<GlobalScope>()
1752 .task_manager()
1753 .gamepad_task_source()
1754 .queue(task!(gamepad_connected: move |cx| {
1755 let window = trusted_window.root();
1756
1757 let navigator = window.Navigator(cx);
1760 let selected_index = navigator.select_gamepad_index();
1761 let gamepad = Gamepad::new(
1762 cx,
1763 &window,
1764 selected_index,
1765 name,
1766 "standard".into(),
1767 axis_bounds,
1768 button_bounds,
1769 supported_haptic_effects,
1770 false,
1771 );
1772
1773 navigator.set_gamepad(selected_index as usize, Some(&gamepad));
1775
1776 if navigator.has_gamepad_gesture() {
1778 gamepad.set_exposed(true);
1780 if window.Document().is_fully_active() {
1785 gamepad.notify_event(cx, GamepadEventType::Connected);
1786 }
1787 }
1788 }));
1789 }
1790
1791 #[cfg(feature = "gamepad")]
1793 fn handle_gamepad_disconnect(&self, index: usize) {
1794 let trusted_window = Trusted::new(&*self.window);
1798 self.window
1799 .upcast::<GlobalScope>()
1800 .task_manager()
1801 .gamepad_task_source()
1802 .queue(task!(gamepad_disconnected: move |cx| {
1803 let window = trusted_window.root();
1804 let navigator = window.Navigator(cx);
1805
1806 if let Some(gamepad) = navigator.get_gamepad(index) {
1807 gamepad.update_connected(false);
1809 if gamepad.exposed() && window.Document().is_fully_active() {
1817 gamepad.notify_event(cx, GamepadEventType::Disconnected);
1818 }
1819 }
1820
1821 navigator.set_gamepad(index, None);
1825 navigator.shrink_gamepads_list();
1828 }));
1829 }
1830
1831 #[cfg(feature = "gamepad")]
1833 fn receive_new_gamepad_button_or_axis(&self, index: usize, update_type: GamepadUpdateType) {
1834 let trusted_window = Trusted::new(&*self.window);
1837
1838 self.window
1841 .upcast::<GlobalScope>()
1842 .task_manager()
1843 .gamepad_task_source()
1844 .queue(task!(update_gamepad_state: move |cx| {
1845 let window = trusted_window.root();
1846 let document = window.Document();
1847 document.event_handler().update_gamepad_state(cx, index, update_type);
1848 }));
1849 }
1850
1851 #[cfg(feature = "gamepad")]
1853 fn update_gamepad_state(
1854 &self,
1855 cx: &mut JSContext,
1856 gamepad_index: usize,
1857 update_type: GamepadUpdateType,
1858 ) {
1859 use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods;
1860 let now = *self.window.Performance(cx).Now();
1863
1864 let navigator = self.window.Navigator(cx);
1867
1868 if let Some(gamepad) = navigator.get_gamepad(gamepad_index) {
1869 gamepad.update_timestamp(now);
1871 match update_type {
1874 GamepadUpdateType::Axis(axis_index, value) => {
1875 gamepad.map_and_normalize_axes(axis_index, value);
1876 },
1877 GamepadUpdateType::Button(button_index, value) => {
1878 gamepad.map_and_normalize_buttons(button_index, value);
1879 },
1880 };
1881 if !navigator.has_gamepad_gesture() && contains_user_gesture(update_type) {
1886 navigator.set_has_gamepad_gesture(true);
1888 navigator
1890 .get_connected_gamepad()
1891 .iter()
1892 .for_each(|connected_gamepad| {
1893 connected_gamepad.set_exposed(true);
1895 connected_gamepad.update_timestamp(now);
1897 let trusted_gamepad = Trusted::new(&**connected_gamepad);
1904 if self.window.Document().is_fully_active() {
1905 self.window
1906 .upcast::<GlobalScope>()
1907 .task_manager()
1908 .gamepad_task_source()
1909 .queue(task!(fire_gamepad_connected: move |cx| {
1910 let gamepad = trusted_gamepad.root();
1911 gamepad.notify_event(cx, GamepadEventType::Connected);
1912 }));
1913 }
1914 });
1915 }
1916 }
1917 }
1918
1919 pub(crate) fn handle_editing_action(
1921 &self,
1922 cx: &mut JSContext,
1923 element: Option<DomRoot<Element>>,
1924 action: EditingActionEvent,
1925 ) -> InputEventResult {
1926 let clipboard_event_type = match action {
1927 EditingActionEvent::Copy => ClipboardEventType::Copy,
1928 EditingActionEvent::Cut => ClipboardEventType::Cut,
1929 EditingActionEvent::Paste => ClipboardEventType::Paste,
1930 };
1931
1932 let script_triggered = false;
1934
1935 let script_may_access_clipboard = false;
1939
1940 if script_triggered && !script_may_access_clipboard {
1942 return InputEventResult::empty();
1943 }
1944
1945 let clipboard_event = self.fire_clipboard_event(cx, element.clone(), clipboard_event_type);
1947
1948 let event = clipboard_event.upcast::<Event>();
1951 if !event.IsTrusted() {
1952 return event.flags().into();
1953 }
1954
1955 if event.DefaultPrevented() {
1957 let event_type = event.Type();
1958 match_domstring_ascii!(event_type,
1959
1960 "copy" => {
1961 if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1964 let drag_data_store =
1965 clipboard_data.data_store().expect("This shouldn't fail");
1966 self.write_content_to_the_clipboard(&drag_data_store);
1967 }
1968 },
1969 "cut" => {
1970 if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1973 let drag_data_store =
1974 clipboard_data.data_store().expect("This shouldn't fail");
1975 self.write_content_to_the_clipboard(&drag_data_store);
1976 }
1977
1978 self.fire_clipboard_event(cx, element, ClipboardEventType::Change);
1980 },
1981 "paste" => (),
1985 _ => (),
1986 )
1987 }
1988
1989 event.flags().into()
1992 }
1993
1994 pub(crate) fn fire_clipboard_event(
1996 &self,
1997 cx: &mut JSContext,
1998 target: Option<DomRoot<Element>>,
1999 clipboard_event_type: ClipboardEventType,
2000 ) -> DomRoot<ClipboardEvent> {
2001 let clipboard_event = ClipboardEvent::new(
2002 cx,
2003 &self.window,
2004 None,
2005 clipboard_event_type.as_str().into(),
2006 EventBubbles::Bubbles,
2007 EventCancelable::Cancelable,
2008 None,
2009 );
2010
2011 let mut drag_data_store = DragDataStore::new();
2014
2015 let trusted = true;
2019
2020 let target = target
2022 .map(DomRoot::upcast)
2023 .unwrap_or_else(|| self.target_for_events_following_focus());
2024
2025 match clipboard_event_type {
2028 ClipboardEventType::Copy | ClipboardEventType::Cut => {
2029 drag_data_store.set_mode(Mode::ReadWrite);
2031 },
2032 ClipboardEventType::Paste => {
2033 let (callback, receiver) =
2034 GenericCallback::new_blocking().expect("Could not create callback");
2035 self.window.send_to_embedder(EmbedderMsg::GetClipboardText(
2036 self.window.webview_id(),
2037 callback,
2038 ));
2039 let text_contents = receiver
2040 .recv()
2041 .map(Result::unwrap_or_default)
2042 .unwrap_or_default();
2043
2044 drag_data_store.set_mode(Mode::ReadOnly);
2046 if trusted {
2048 let data = DOMString::from(text_contents);
2052 let type_ = DOMString::from("text/plain");
2053 let _ = drag_data_store.add(Kind::Text { data, type_ });
2054
2055 }
2061 },
2062 ClipboardEventType::Change => (),
2063 }
2064
2065 let clipboard_event_data = DataTransfer::new(
2067 cx,
2068 &self.window,
2069 Rc::new(RefCell::new(Some(drag_data_store))),
2070 );
2071
2072 clipboard_event.set_clipboard_data(Some(&clipboard_event_data));
2074
2075 let event = clipboard_event.upcast::<Event>();
2077 event.set_trusted(trusted);
2078
2079 event.set_composed(true);
2081
2082 event.dispatch(cx, &target, false);
2084
2085 DomRoot::from(clipboard_event)
2086 }
2087
2088 fn write_content_to_the_clipboard(&self, drag_data_store: &DragDataStore) {
2090 if drag_data_store.list_len() > 0 {
2092 self.window
2094 .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
2095 for item in drag_data_store.iter_item_list() {
2097 match item {
2098 Kind::Text { data, .. } => {
2099 self.window.send_to_embedder(EmbedderMsg::SetClipboardText(
2103 self.window.webview_id(),
2104 data.to_string(),
2105 ));
2106 },
2107 Kind::File { .. } => {
2108 },
2112 }
2113 }
2114 } else {
2115 if drag_data_store.clear_was_called {
2117 self.window
2119 .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
2120 }
2123 }
2124 }
2125
2126 #[expect(unsafe_code)]
2129 pub(crate) fn handle_embedder_scroll_event(&self, scrolled_node: ExternalScrollId) {
2130 let document = self.window.Document();
2132 if scrolled_node.is_root() {
2133 document.handle_viewport_scroll_event();
2134 } else {
2135 let node_id = node_id_from_scroll_id(scrolled_node.0 as usize);
2139 let node = unsafe {
2140 node::from_untrusted_node_address(UntrustedNodeAddress::from_id(node_id))
2141 };
2142 let Some(element) = node
2143 .inclusive_ancestors(ShadowIncluding::Yes)
2144 .find_map(DomRoot::downcast::<Element>)
2145 else {
2146 return;
2147 };
2148
2149 element.handle_scroll_event();
2150 }
2151 }
2152
2153 pub(crate) fn maybe_dispatch_simulated_click(
2159 &self,
2160 cx: &mut JSContext,
2161 node: &Node,
2162 event: &KeyboardEvent,
2163 ) -> bool {
2164 if event.key() != Key::Named(NamedKey::Enter) && event.original_code() != Some(Code::Space)
2165 {
2166 return false;
2167 }
2168
2169 if node
2173 .downcast::<Element>()
2174 .and_then(Element::as_maybe_activatable)
2175 .is_none()
2176 {
2177 return false;
2178 }
2179
2180 node.fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
2181 true
2182 }
2183
2184 pub(crate) fn run_default_keyboard_event_handler(
2185 &self,
2186 cx: &mut js::context::JSContext,
2187 node: &Node,
2188 event: &KeyboardEvent,
2189 ) {
2190 if event.upcast::<Event>().type_() != atom!("keydown") {
2191 return;
2192 }
2193
2194 if self.maybe_dispatch_simulated_click(cx, node, event) {
2195 return;
2196 }
2197
2198 if self.maybe_handle_accesskey(cx, event) {
2199 return;
2200 }
2201
2202 let mut is_space = false;
2203 let scroll = match event.key() {
2204 Key::Named(NamedKey::ArrowDown) => KeyboardScroll::Down,
2205 Key::Named(NamedKey::ArrowLeft) => KeyboardScroll::Left,
2206 Key::Named(NamedKey::ArrowRight) => KeyboardScroll::Right,
2207 Key::Named(NamedKey::ArrowUp) => KeyboardScroll::Up,
2208 Key::Named(NamedKey::End) => KeyboardScroll::End,
2209 Key::Named(NamedKey::Home) => KeyboardScroll::Home,
2210 Key::Named(NamedKey::PageDown) => KeyboardScroll::PageDown,
2211 Key::Named(NamedKey::PageUp) => KeyboardScroll::PageUp,
2212 Key::Character(string) if &string == " " => {
2213 is_space = true;
2214 if event.modifiers().contains(Modifiers::SHIFT) {
2215 KeyboardScroll::PageUp
2216 } else {
2217 KeyboardScroll::PageDown
2218 }
2219 },
2220 Key::Named(NamedKey::Tab) => {
2221 self.window
2227 .Document()
2228 .focus_handler()
2229 .sequential_focus_navigation_via_keyboard_event(cx, event);
2230 return;
2231 },
2232 _ => return,
2233 };
2234
2235 if !event.modifiers().is_empty() && !is_space {
2236 return;
2237 }
2238
2239 self.do_keyboard_scroll(cx, scroll);
2240 }
2241
2242 pub(crate) fn do_keyboard_scroll(&self, cx: &mut JSContext, scroll: KeyboardScroll) {
2243 let scroll_axis = match scroll {
2244 KeyboardScroll::Left | KeyboardScroll::Right => ScrollingBoxAxis::X,
2245 _ => ScrollingBoxAxis::Y,
2246 };
2247
2248 let document = self.window.Document();
2249 let mut scrolling_box = document
2250 .focus_handler()
2251 .focused_area()
2252 .element()
2253 .or(self.most_recently_clicked_element.get().as_deref())
2254 .and_then(|element| element.scrolling_box(ScrollContainerQueryFlags::Inclusive))
2255 .unwrap_or_else(|| {
2256 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2257 });
2258
2259 while !scrolling_box.can_keyboard_scroll_in_axis(cx.no_gc(), scroll_axis) {
2260 if scrolling_box.is_viewport() {
2262 break;
2263 }
2264 let parent = scrolling_box.parent().unwrap_or_else(|| {
2265 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2266 });
2267 scrolling_box = parent;
2268 }
2269
2270 let calculate_current_scroll_offset_and_delta = || {
2271 const LINE_HEIGHT: f32 = 76.0;
2272 const LINE_WIDTH: f32 = 76.0;
2273
2274 let current_scroll_offset = scrolling_box.scroll_position();
2275 (
2276 current_scroll_offset,
2277 match scroll {
2278 KeyboardScroll::Home => Vector2D::new(0.0, -current_scroll_offset.y),
2279 KeyboardScroll::End => Vector2D::new(
2280 0.0,
2281 -current_scroll_offset.y + scrolling_box.content_size().height -
2282 scrolling_box.size(cx.no_gc()).height,
2283 ),
2284 KeyboardScroll::PageDown => Vector2D::new(
2285 0.0,
2286 scrolling_box.size(cx.no_gc()).height - 2.0 * LINE_HEIGHT,
2287 ),
2288 KeyboardScroll::PageUp => Vector2D::new(
2289 0.0,
2290 2.0 * LINE_HEIGHT - scrolling_box.size(cx.no_gc()).height,
2291 ),
2292 KeyboardScroll::Up => Vector2D::new(0.0, -LINE_HEIGHT),
2293 KeyboardScroll::Down => Vector2D::new(0.0, LINE_HEIGHT),
2294 KeyboardScroll::Left => Vector2D::new(-LINE_WIDTH, 0.0),
2295 KeyboardScroll::Right => Vector2D::new(LINE_WIDTH, 0.0),
2296 },
2297 )
2298 };
2299
2300 let parent_pipeline = self.window.parent_info();
2304 if scrolling_box.is_viewport() && parent_pipeline.is_none() {
2305 let (_, delta) = calculate_current_scroll_offset_and_delta();
2306 self.window
2307 .paint_api()
2308 .scroll_viewport_by_delta(self.window.webview_id(), delta);
2309 }
2310
2311 if !scrolling_box.can_keyboard_scroll_in_axis(cx.no_gc(), scroll_axis) {
2314 assert!(scrolling_box.is_viewport());
2315
2316 let window_proxy = document.window().window_proxy();
2317 if let Some(iframe) = window_proxy.frame_element() {
2318 let iframe_window = iframe.owner_window();
2321 let mut realm = enter_auto_realm(cx, &*iframe_window);
2322 let cx = &mut realm;
2323 iframe_window
2324 .Document()
2325 .event_handler()
2326 .do_keyboard_scroll(cx, scroll);
2327 } else if let Some(parent_pipeline) = parent_pipeline {
2328 document.window().send_to_constellation(
2332 ScriptToConstellationMessage::ForwardKeyboardScroll(parent_pipeline, scroll),
2333 );
2334 };
2335 return;
2336 }
2337
2338 let (current_scroll_offset, delta) = calculate_current_scroll_offset_and_delta();
2339 scrolling_box.scroll_to(cx, delta + current_scroll_offset, ScrollBehavior::Auto);
2340 }
2341
2342 fn get_or_create_pointer_id_for_touch(&self, touch_id: i32) -> i32 {
2345 let mut active_pointer_ids = self.active_pointer_ids.borrow_mut();
2346
2347 if let Some(&pointer_id) = active_pointer_ids.get(&touch_id) {
2348 return pointer_id;
2349 }
2350
2351 let pointer_id = self.next_touch_pointer_id.get();
2352 active_pointer_ids.insert(touch_id, pointer_id);
2353 self.next_touch_pointer_id.set(pointer_id + 1);
2354 pointer_id
2355 }
2356
2357 fn remove_pointer_id_for_touch(&self, touch_id: i32) {
2359 self.active_pointer_ids.borrow_mut().remove(&touch_id);
2360 }
2361
2362 fn is_primary_pointer(&self, pointer_id: i32) -> bool {
2365 self.active_pointer_ids
2368 .borrow()
2369 .values()
2370 .min()
2371 .is_some_and(|primary_pointer| *primary_pointer == pointer_id)
2372 }
2373
2374 #[allow(clippy::too_many_arguments)]
2378 fn fire_pointer_event_for_touch(
2379 &self,
2380 cx: &mut js::context::JSContext,
2381 target_element: &Element,
2382 touch: &Touch,
2383 pointer_id: i32,
2384 event_name: &str,
2385 is_primary: bool,
2386 pointer_type: &str,
2387 input_event: &ConstellationInputEvent,
2388 hit_test_result: &HitTestResult,
2389 ) {
2390 let mut targets: Vec<_> = target_element
2391 .upcast::<Node>()
2392 .inclusive_ancestors_in_flat_tree()
2393 .collect();
2394
2395 if event_name == "pointerenter" {
2397 targets.reverse();
2398 }
2399
2400 for target in targets {
2401 let pointer_event = touch.to_pointer_event(
2402 cx,
2403 &self.window,
2404 event_name,
2405 pointer_id,
2406 is_primary,
2407 pointer_type,
2408 input_event.active_keyboard_modifiers,
2409 false,
2410 Some(hit_test_result.point_in_node),
2411 );
2412 pointer_event.upcast::<Event>().fire(cx, target.upcast());
2413 }
2414 }
2415
2416 pub(crate) fn has_assigned_access_key(&self, element: &HTMLElement) -> bool {
2417 self.access_key_handlers
2418 .borrow()
2419 .values()
2420 .any(|value| &**value == element)
2421 }
2422
2423 pub(crate) fn unassign_access_key(&self, element: &HTMLElement) {
2424 self.access_key_handlers
2425 .borrow_mut()
2426 .retain(|_, value| &**value != element)
2427 }
2428
2429 pub(crate) fn assign_access_key(&self, element: &HTMLElement, code: Code) {
2430 let mut access_key_handlers = self.access_key_handlers.borrow_mut();
2431 access_key_handlers
2433 .entry(code.into())
2434 .or_insert(Dom::from_ref(element));
2435 }
2436
2437 fn maybe_handle_accesskey(
2438 &self,
2439 cx: &mut js::context::JSContext,
2440 event: &KeyboardEvent,
2441 ) -> bool {
2442 #[cfg(target_os = "macos")]
2443 let access_key_modifiers = Modifiers::CONTROL | Modifiers::ALT;
2444 #[cfg(not(target_os = "macos"))]
2445 let access_key_modifiers = Modifiers::SHIFT | Modifiers::ALT;
2446
2447 if event.modifiers() != access_key_modifiers {
2448 return false;
2449 }
2450
2451 let Ok(code) = Code::from_str(&event.Code().str()) else {
2452 return false;
2453 };
2454
2455 let Some(html_element) = self
2456 .access_key_handlers
2457 .borrow()
2458 .get(&code.into())
2459 .map(|html_element| html_element.as_rooted())
2460 else {
2461 return false;
2462 };
2463
2464 let Ok(command) = InteractiveElementCommand::try_from(&*html_element) else {
2472 return false;
2473 };
2474
2475 if command.disabled() || command.hidden() {
2476 return false;
2477 }
2478
2479 let node = html_element.upcast::<Node>();
2480 if !node.is_connected() {
2481 return false;
2482 }
2483
2484 for node in node.inclusive_ancestors_unrooted(cx.no_gc(), ShadowIncluding::Yes) {
2485 if node
2486 .downcast::<HTMLElement>()
2487 .is_some_and(|html_element| html_element.Hidden())
2488 {
2489 return false;
2490 }
2491 }
2492
2493 self.focus_and_scroll_to_element_for_key_event(cx, html_element.upcast());
2496 command.perform_action(cx);
2497 true
2498 }
2499
2500 pub(crate) fn focus_and_scroll_to_element_for_key_event(
2501 &self,
2502 cx: &mut JSContext,
2503 element: &Element,
2504 ) {
2505 element.upcast::<Node>().run_the_focusing_steps(cx, None);
2506 let scroll_axis = ScrollAxisState {
2507 position: ScrollLogicalPosition::Center,
2508 requirement: ScrollRequirement::IfNotVisible,
2509 };
2510 element.scroll_into_view_with_options(
2511 cx,
2512 ScrollBehavior::Auto,
2513 scroll_axis,
2514 scroll_axis,
2515 None,
2516 None,
2517 );
2518 }
2519
2520 pub(crate) fn is_active_pointer(&self, pointer_id: i32) -> bool {
2523 if pointer_id == PointerId::Mouse as i32 {
2524 self.mouse_buttons_down.get() > 0
2526 } else {
2527 self.active_pointer_ids
2529 .borrow()
2530 .values()
2531 .any(|&id| id == pointer_id)
2532 }
2533 }
2534
2535 pub(crate) fn set_pending_pointer_capture(&self, pointer_id: i32, element: &Element) {
2538 self.pending_pointer_capture
2539 .borrow_mut()
2540 .insert(pointer_id, Dom::from_ref(element));
2541 }
2542
2543 pub(crate) fn clear_pending_pointer_capture(&self, pointer_id: i32) {
2546 self.pending_pointer_capture
2547 .borrow_mut()
2548 .remove(&pointer_id);
2549 }
2550
2551 pub(crate) fn has_pointer_capture(&self, pointer_id: i32, element: &Element) -> bool {
2554 self.pending_pointer_capture
2555 .borrow()
2556 .get(&pointer_id)
2557 .is_some_and(|el| &**el == element)
2558 }
2559
2560 fn get_pointer_capture_target(&self, pointer_id: i32) -> Option<DomRoot<Element>> {
2565 self.pointer_capture_target
2566 .borrow()
2567 .get(&pointer_id)
2568 .map(|el| DomRoot::from_ref(&**el))
2569 .filter(|el| el.upcast::<Node>().is_connected())
2570 }
2571
2572 fn release_disconnected_pointer_capture(
2578 &self,
2579 cx: &mut JSContext,
2580 pointer_id: i32,
2581 pointer_type: &str,
2582 is_primary: bool,
2583 ) -> bool {
2584 let capture_target = self
2585 .pointer_capture_target
2586 .borrow()
2587 .get(&pointer_id)
2588 .map(|el| DomRoot::from_ref(&**el));
2589 if let Some(capture_element) = capture_target &&
2590 !capture_element.upcast::<Node>().is_connected()
2591 {
2592 let document = self.window.Document();
2594 self.fire_pointer_capture_event_at_target(
2595 cx,
2596 "lostpointercapture",
2597 pointer_id,
2598 pointer_type,
2599 is_primary,
2600 document.upcast::<EventTarget>(),
2601 );
2602 self.pending_pointer_capture
2604 .safe_borrow_mut(cx.no_gc())
2605 .remove(&pointer_id);
2606 self.pointer_capture_target
2607 .safe_borrow_mut(cx.no_gc())
2608 .remove(&pointer_id);
2609 return true;
2610 }
2611 false
2612 }
2613
2614 fn fire_pointer_capture_event(
2617 &self,
2618 cx: &mut JSContext,
2619 event_type: &str,
2620 pointer_id: i32,
2621 pointer_type: &str,
2622 is_primary: bool,
2623 target: &Element,
2624 ) {
2625 self.fire_pointer_capture_event_at_target(
2626 cx,
2627 event_type,
2628 pointer_id,
2629 pointer_type,
2630 is_primary,
2631 target.upcast::<EventTarget>(),
2632 );
2633 }
2634
2635 fn fire_pointer_capture_event_at_target(
2637 &self,
2638 cx: &mut JSContext,
2639 event_type: &str,
2640 pointer_id: i32,
2641 pointer_type: &str,
2642 is_primary: bool,
2643 target: &EventTarget,
2644 ) {
2645 let pointer_event = PointerEvent::new(
2646 cx,
2647 &self.window,
2648 event_type.into(),
2649 EventBubbles::Bubbles,
2650 EventCancelable::NotCancelable,
2651 Some(&self.window),
2652 0,
2653 Point2D::new(0, 0),
2654 Point2D::new(0, 0),
2655 Point2D::new(0, 0),
2656 Modifiers::empty(),
2657 0,
2658 0,
2659 None,
2660 None,
2661 pointer_id,
2662 1,
2663 1,
2664 0.0,
2665 0.0,
2666 0,
2667 0,
2668 0,
2669 PI / 2.0,
2670 0.0,
2671 DOMString::from(pointer_type),
2672 is_primary,
2673 vec![],
2674 vec![],
2675 );
2676 pointer_event.upcast::<Event>().set_composed(true);
2677 pointer_event.upcast::<Event>().fire(cx, target);
2678 }
2679
2680 #[expect(clippy::too_many_arguments)]
2684 fn fire_pointer_boundary_event(
2685 &self,
2686 cx: &mut JSContext,
2687 event_type: &str,
2688 bubbles: EventBubbles,
2689 pointer_id: i32,
2690 pointer_type: &str,
2691 is_primary: bool,
2692 target: &Element,
2693 related_target: Option<&Element>,
2694 ) {
2695 let pointer_event = PointerEvent::new(
2696 cx,
2697 &self.window,
2698 event_type.into(),
2699 bubbles,
2700 EventCancelable::NotCancelable,
2701 Some(&self.window),
2702 0,
2703 Point2D::new(0, 0),
2704 Point2D::new(0, 0),
2705 Point2D::new(0, 0),
2706 Modifiers::empty(),
2707 -1, self.mouse_buttons_down.get() as u16,
2709 related_target.map(|el| el.upcast::<EventTarget>()),
2710 None,
2711 pointer_id,
2712 1,
2713 1,
2714 0.0,
2715 0.0,
2716 0,
2717 0,
2718 0,
2719 PI / 2.0,
2720 0.0,
2721 DOMString::from(pointer_type),
2722 is_primary,
2723 vec![],
2724 vec![],
2725 );
2726 pointer_event.upcast::<Event>().set_composed(true);
2727 pointer_event.upcast::<Event>().fire(cx, target.upcast());
2728 }
2729
2730 fn fire_pointer_capture_boundary_transition(
2735 &self,
2736 cx: &mut JSContext,
2737 pointer_id: i32,
2738 pointer_type: &str,
2739 is_primary: bool,
2740 old_target: &Element,
2741 new_target: &Element,
2742 ) {
2743 if pointer_type != "mouse" {
2744 return;
2745 }
2746 if old_target == new_target {
2747 return;
2748 }
2749 self.fire_pointer_boundary_event(
2750 cx,
2751 "pointerout",
2752 EventBubbles::Bubbles,
2753 pointer_id,
2754 pointer_type,
2755 is_primary,
2756 old_target,
2757 Some(new_target),
2758 );
2759 self.fire_pointer_boundary_event(
2760 cx,
2761 "pointerleave",
2762 EventBubbles::DoesNotBubble,
2763 pointer_id,
2764 pointer_type,
2765 is_primary,
2766 old_target,
2767 Some(new_target),
2768 );
2769 self.fire_pointer_boundary_event(
2770 cx,
2771 "pointerover",
2772 EventBubbles::Bubbles,
2773 pointer_id,
2774 pointer_type,
2775 is_primary,
2776 new_target,
2777 Some(old_target),
2778 );
2779 self.fire_pointer_boundary_event(
2780 cx,
2781 "pointerenter",
2782 EventBubbles::DoesNotBubble,
2783 pointer_id,
2784 pointer_type,
2785 is_primary,
2786 new_target,
2787 Some(old_target),
2788 );
2789 }
2790
2791 fn implicit_release_pointer_capture(
2794 &self,
2795 cx: &mut JSContext,
2796 pointer_id: i32,
2797 pointer_type: &str,
2798 is_primary: bool,
2799 ) {
2800 let capture_element = self
2801 .pointer_capture_target
2802 .borrow()
2803 .get(&pointer_id)
2804 .map(|el| DomRoot::from_ref(&**el));
2805 if let Some(capture_element) = capture_element {
2806 if capture_element.upcast::<Node>().is_connected() {
2807 self.fire_pointer_capture_event(
2808 cx,
2809 "lostpointercapture",
2810 pointer_id,
2811 pointer_type,
2812 is_primary,
2813 &capture_element,
2814 );
2815 if let Some(hover_target) = self.current_hover_target.get() {
2818 self.fire_pointer_capture_boundary_transition(
2819 cx,
2820 pointer_id,
2821 pointer_type,
2822 is_primary,
2823 &capture_element,
2824 &hover_target,
2825 );
2826 }
2827 } else {
2828 let document = self.window.Document();
2829 self.fire_pointer_capture_event_at_target(
2830 cx,
2831 "lostpointercapture",
2832 pointer_id,
2833 pointer_type,
2834 is_primary,
2835 document.upcast::<EventTarget>(),
2836 );
2837 }
2838 }
2839 self.pending_pointer_capture
2840 .safe_borrow_mut(cx.no_gc())
2841 .remove(&pointer_id);
2842 self.pointer_capture_target
2843 .safe_borrow_mut(cx.no_gc())
2844 .remove(&pointer_id);
2845 }
2846
2847 fn process_pending_pointer_capture(
2851 &self,
2852 cx: &mut JSContext,
2853 pointer_id: i32,
2854 pointer_type: &str,
2855 is_primary: bool,
2856 ) {
2857 let pending = self
2858 .pending_pointer_capture
2859 .borrow()
2860 .get(&pointer_id)
2861 .map(|el| DomRoot::from_ref(&**el));
2862 let current = self
2863 .pointer_capture_target
2864 .borrow()
2865 .get(&pointer_id)
2866 .map(|el| DomRoot::from_ref(&**el));
2867
2868 let pending_connected = pending
2871 .as_ref()
2872 .is_some_and(|el| el.upcast::<Node>().is_connected());
2873
2874 let pointer_is_active = self.is_active_pointer(pointer_id);
2877
2878 match (&pending, ¤t) {
2879 (Some(pending_el), None) if pending_connected => {
2880 if pointer_is_active {
2881 if let Some(hover_target) = self.current_hover_target.get() {
2884 self.fire_pointer_capture_boundary_transition(
2885 cx,
2886 pointer_id,
2887 pointer_type,
2888 is_primary,
2889 &hover_target,
2890 pending_el,
2891 );
2892 }
2893 self.fire_pointer_capture_event(
2894 cx,
2895 "gotpointercapture",
2896 pointer_id,
2897 pointer_type,
2898 is_primary,
2899 pending_el,
2900 );
2901 self.pointer_capture_target
2902 .safe_borrow_mut(cx.no_gc())
2903 .insert(pointer_id, Dom::from_ref(pending_el));
2904 } else {
2905 self.pending_pointer_capture
2906 .safe_borrow_mut(cx.no_gc())
2907 .remove(&pointer_id);
2908 }
2909 },
2910 (Some(pending_el), Some(current_el))
2911 if pending_connected && pending_el != current_el =>
2912 {
2913 self.fire_pointer_capture_event(
2914 cx,
2915 "lostpointercapture",
2916 pointer_id,
2917 pointer_type,
2918 is_primary,
2919 current_el,
2920 );
2921 if pointer_is_active {
2922 self.fire_pointer_capture_boundary_transition(
2923 cx,
2924 pointer_id,
2925 pointer_type,
2926 is_primary,
2927 current_el,
2928 pending_el,
2929 );
2930 self.fire_pointer_capture_event(
2931 cx,
2932 "gotpointercapture",
2933 pointer_id,
2934 pointer_type,
2935 is_primary,
2936 pending_el,
2937 );
2938 self.pointer_capture_target
2939 .safe_borrow_mut(cx.no_gc())
2940 .insert(pointer_id, Dom::from_ref(pending_el));
2941 } else {
2942 self.pending_pointer_capture
2943 .safe_borrow_mut(cx.no_gc())
2944 .remove(&pointer_id);
2945 self.pointer_capture_target
2946 .safe_borrow_mut(cx.no_gc())
2947 .remove(&pointer_id);
2948 }
2949 },
2950 (None, Some(current_el)) | (Some(_), Some(current_el)) if !pending_connected => {
2951 self.fire_pointer_capture_event(
2952 cx,
2953 "lostpointercapture",
2954 pointer_id,
2955 pointer_type,
2956 is_primary,
2957 current_el,
2958 );
2959 if let Some(hover_target) = self.current_hover_target.get() {
2962 self.fire_pointer_capture_boundary_transition(
2963 cx,
2964 pointer_id,
2965 pointer_type,
2966 is_primary,
2967 current_el,
2968 &hover_target,
2969 );
2970 }
2971 self.pointer_capture_target
2972 .safe_borrow_mut(cx.no_gc())
2973 .remove(&pointer_id);
2974 if !pending_connected {
2975 self.pending_pointer_capture
2976 .safe_borrow_mut(cx.no_gc())
2977 .remove(&pointer_id);
2978 }
2979 },
2980 _ => {},
2981 }
2982 }
2983}
2984
2985pub(crate) fn character_to_code(character: char) -> Option<Code> {
2986 Some(match character.to_ascii_lowercase() {
2987 '`' => Code::Backquote,
2988 '\\' => Code::Backslash,
2989 '[' | '{' => Code::BracketLeft,
2990 ']' | '}' => Code::BracketRight,
2991 ',' | '<' => Code::Comma,
2992 '0' => Code::Digit0,
2993 '1' => Code::Digit1,
2994 '2' => Code::Digit2,
2995 '3' => Code::Digit3,
2996 '4' => Code::Digit4,
2997 '5' => Code::Digit5,
2998 '6' => Code::Digit6,
2999 '7' => Code::Digit7,
3000 '8' => Code::Digit8,
3001 '9' => Code::Digit9,
3002 '=' => Code::Equal,
3003 'a' => Code::KeyA,
3004 'b' => Code::KeyB,
3005 'c' => Code::KeyC,
3006 'd' => Code::KeyD,
3007 'e' => Code::KeyE,
3008 'f' => Code::KeyF,
3009 'g' => Code::KeyG,
3010 'h' => Code::KeyH,
3011 'i' => Code::KeyI,
3012 'j' => Code::KeyJ,
3013 'k' => Code::KeyK,
3014 'l' => Code::KeyL,
3015 'm' => Code::KeyM,
3016 'n' => Code::KeyN,
3017 'o' => Code::KeyO,
3018 'p' => Code::KeyP,
3019 'q' => Code::KeyQ,
3020 'r' => Code::KeyR,
3021 's' => Code::KeyS,
3022 't' => Code::KeyT,
3023 'u' => Code::KeyU,
3024 'v' => Code::KeyV,
3025 'w' => Code::KeyW,
3026 'x' => Code::KeyX,
3027 'y' => Code::KeyY,
3028 'z' => Code::KeyZ,
3029 '-' => Code::Minus,
3030 '.' => Code::Period,
3031 '\'' | '"' => Code::Quote,
3032 ';' => Code::Semicolon,
3033 '/' => Code::Slash,
3034 ' ' => Code::Space,
3035 _ => return None,
3036 })
3037}
3038
3039impl Element {
3040 fn inclusive_ancestor_element_in_non_ua_shadow_root(&self) -> DomRoot<Element> {
3042 if !self.upcast::<Node>().is_in_ua_widget() {
3043 return DomRoot::from_ref(self);
3044 }
3045 let Some(shadow_root) = self.containing_shadow_root() else {
3046 return DomRoot::from_ref(self);
3047 };
3048 shadow_root
3049 .Host()
3050 .inclusive_ancestor_element_in_non_ua_shadow_root()
3051 }
3052}