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::{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::script_runtime::CanGc;
43use script_bindings::str::DOMString;
44use script_traits::ConstellationInputEvent;
45use servo_base::generic_channel::GenericCallback;
46use servo_config::pref;
47use servo_constellation_traits::{KeyboardScroll, ScriptToConstellationMessage};
48use style::Atom;
49use style_traits::CSSPixel;
50use webrender_api::ExternalScrollId;
51
52#[cfg(feature = "gamepad")]
53use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionName;
54use crate::dom::bindings::inheritance::{ElementTypeId, HTMLElementTypeId, NodeTypeId};
55use crate::dom::bindings::refcounted::Trusted;
56use crate::dom::bindings::root::MutNullableDom;
57use crate::dom::bindings::trace::NoTrace;
58use crate::dom::clipboardevent::ClipboardEventType;
59use crate::dom::document::FireMouseEventType;
60use crate::dom::document::focus::FocusableArea;
61use crate::dom::event::{EventBubbles, EventCancelable, EventComposed, EventFlags};
62#[cfg(feature = "gamepad")]
63use crate::dom::gamepad::gamepad::{Gamepad, contains_user_gesture};
64#[cfg(feature = "gamepad")]
65use crate::dom::gamepad::gamepadevent::GamepadEventType;
66use crate::dom::inputevent::HitTestResult;
67use crate::dom::interactive_element_command::InteractiveElementCommand;
68use crate::dom::iterators::ShadowIncluding;
69use crate::dom::keyboardevent::KeyboardEvent;
70use crate::dom::node::{self, Node, NodeTraits};
71use crate::dom::pointerevent::{PointerEvent, PointerId};
72use crate::dom::scrolling_box::{ScrollAxisState, ScrollRequirement, ScrollingBoxAxis};
73use crate::dom::types::{
74 ClipboardEvent, CompositionEvent, DataTransfer, Element, Event, EventTarget, GlobalScope,
75 HTMLAnchorElement, HTMLElement, HTMLLabelElement, MouseEvent, Touch, TouchEvent, TouchList,
76 WheelEvent, Window,
77};
78use crate::drag_data_store::{DragDataStore, Kind, Mode};
79use crate::realms::{enter_auto_realm, enter_realm};
80
81#[derive(Default, JSTraceable, MallocSizeOf)]
91struct ClickCountingInfo {
92 time: Option<Instant>,
93 #[no_trace]
94 point: Option<Point2D<f32, CSSPixel>>,
95 #[no_trace]
96 button: Option<MouseButton>,
97 count: usize,
98}
99
100impl ClickCountingInfo {
101 fn reset_click_count_if_necessary(
102 &mut self,
103 button: MouseButton,
104 point_in_frame: Point2D<f32, CSSPixel>,
105 ) {
106 let (Some(previous_button), Some(previous_point), Some(previous_time)) =
107 (self.button, self.point, self.time)
108 else {
109 assert_eq!(self.count, 0);
110 return;
111 };
112
113 let double_click_timeout =
114 Duration::from_millis(pref!(dom_document_dblclick_timeout) as u64);
115 let double_click_distance_threshold = pref!(dom_document_dblclick_dist) as u64;
116
117 let line = point_in_frame - previous_point;
119 let distance = (line.dot(line) as f64).sqrt();
120 if previous_button != button ||
121 Instant::now().duration_since(previous_time) > double_click_timeout ||
122 distance > double_click_distance_threshold as f64
123 {
124 self.count = 0;
125 self.time = None;
126 self.point = None;
127 }
128 }
129
130 fn increment_click_count(
131 &mut self,
132 button: MouseButton,
133 point: Point2D<f32, CSSPixel>,
134 ) -> usize {
135 self.time = Some(Instant::now());
136 self.point = Some(point);
137 self.button = Some(button);
138 self.count += 1;
139 self.count
140 }
141}
142
143#[derive(JSTraceable, MallocSizeOf)]
147#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
148pub(crate) struct DocumentEventHandler {
149 window: Dom<Window>,
151 #[no_trace]
153 #[ignore_malloc_size_of = "InputEvent contains data from outside crates"]
154 pending_input_events: DomRefCell<Vec<ConstellationInputEvent>>,
155 mouse_move_event_index: DomRefCell<Option<usize>>,
157 #[no_trace]
159 #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
160 coalesced_mouse_move_event_ids: DomRefCell<Vec<InputEventId>>,
161 wheel_event_index: DomRefCell<Option<usize>>,
166 #[no_trace]
168 #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
169 coalesced_wheel_event_ids: DomRefCell<Vec<InputEventId>>,
170 click_counting_info: DomRefCell<ClickCountingInfo>,
172 #[no_trace]
173 last_mouse_button_down_point: Cell<Option<Point2D<f32, CSSPixel>>>,
174 mouse_buttons_down: Cell<u32>,
178 current_hover_target: MutNullableDom<Element>,
180 current_active_element: MutNullableDom<Element>,
183 most_recently_clicked_element: MutNullableDom<Element>,
185 #[no_trace]
187 most_recent_mousemove_point: Cell<Option<Point2D<f32, CSSPixel>>>,
188 #[no_trace]
191 current_cursor: Cell<Option<Cursor>>,
192 active_touch_points: DomRefCell<Vec<Dom<Touch>>>,
194 #[no_trace]
196 active_keyboard_modifiers: Cell<Modifiers>,
197 active_pointer_ids: DomRefCell<FxHashMap<i32, i32>>,
199 next_touch_pointer_id: Cell<i32>,
201 access_key_handlers: DomRefCell<FxHashMap<NoTrace<Code>, Dom<HTMLElement>>>,
203 pending_pointer_capture: DomRefCell<FxHashMap<i32, Dom<Element>>>,
207 pointer_capture_target: DomRefCell<FxHashMap<i32, Dom<Element>>>,
210}
211
212impl DocumentEventHandler {
213 pub(crate) fn new(window: &Window) -> Self {
214 Self {
215 window: Dom::from_ref(window),
216 pending_input_events: Default::default(),
217 mouse_move_event_index: Default::default(),
218 coalesced_mouse_move_event_ids: Default::default(),
219 wheel_event_index: Default::default(),
220 coalesced_wheel_event_ids: Default::default(),
221 click_counting_info: Default::default(),
222 last_mouse_button_down_point: Default::default(),
223 mouse_buttons_down: Cell::new(0),
224 current_hover_target: Default::default(),
225 current_active_element: Default::default(),
226 most_recently_clicked_element: Default::default(),
227 most_recent_mousemove_point: Default::default(),
228 current_cursor: Default::default(),
229 active_touch_points: Default::default(),
230 active_keyboard_modifiers: Default::default(),
231 active_pointer_ids: Default::default(),
232 next_touch_pointer_id: Cell::new(1),
233 access_key_handlers: Default::default(),
234 pending_pointer_capture: Default::default(),
235 pointer_capture_target: Default::default(),
236 }
237 }
238
239 pub(crate) fn note_pending_input_event(&self, event: ConstellationInputEvent) {
241 let mut pending_input_events = self.pending_input_events.borrow_mut();
242 if matches!(event.event.event, InputEvent::MouseMove(..)) {
243 if let Some(mouse_move_event) = self
245 .mouse_move_event_index
246 .borrow()
247 .and_then(|index| pending_input_events.get_mut(index))
248 {
249 self.coalesced_mouse_move_event_ids
250 .borrow_mut()
251 .push(mouse_move_event.event.id);
252 *mouse_move_event = event;
253 return;
254 }
255
256 *self.mouse_move_event_index.borrow_mut() = Some(pending_input_events.len());
257 }
258
259 if let InputEvent::Wheel(ref new_wheel_event) = event.event.event {
260 if let Some(existing_constellation_wheel_event) = self
262 .wheel_event_index
263 .borrow()
264 .and_then(|index| pending_input_events.get_mut(index)) &&
265 let InputEvent::Wheel(ref mut existing_wheel_event) =
266 existing_constellation_wheel_event.event.event &&
267 existing_wheel_event.delta.mode == new_wheel_event.delta.mode
268 {
269 self.coalesced_wheel_event_ids
270 .borrow_mut()
271 .push(existing_constellation_wheel_event.event.id);
272 existing_wheel_event.delta.x += new_wheel_event.delta.x;
273 existing_wheel_event.delta.y += new_wheel_event.delta.y;
274 existing_wheel_event.delta.z += new_wheel_event.delta.z;
275 existing_wheel_event.point = new_wheel_event.point;
276 existing_constellation_wheel_event.event.id = event.event.id;
277 return;
278 }
279
280 *self.wheel_event_index.borrow_mut() = Some(pending_input_events.len());
281 }
282
283 pending_input_events.push(event);
284 }
285
286 pub(crate) fn has_pending_input_events(&self) -> bool {
289 !self.pending_input_events.borrow().is_empty()
290 }
291
292 pub(crate) fn alternate_action_keyboard_modifier_active(&self) -> bool {
293 #[cfg(target_os = "macos")]
294 {
295 self.active_keyboard_modifiers
296 .get()
297 .contains(Modifiers::META)
298 }
299 #[cfg(not(target_os = "macos"))]
300 {
301 self.active_keyboard_modifiers
302 .get()
303 .contains(Modifiers::CONTROL)
304 }
305 }
306
307 pub(crate) fn handle_pending_input_events(&self, cx: &mut JSContext) {
308 debug_assert!(
309 !self.pending_input_events.borrow().is_empty(),
310 "handle_pending_input_events called with no events"
311 );
312 let _realm = enter_realm(&*self.window);
313
314 *self.mouse_move_event_index.borrow_mut() = None;
316 *self.wheel_event_index.borrow_mut() = None;
317 let pending_input_events = mem::take(&mut *self.pending_input_events.borrow_mut());
318 let mut coalesced_mouse_move_event_ids =
319 mem::take(&mut *self.coalesced_mouse_move_event_ids.borrow_mut());
320 let mut coalesced_wheel_event_ids =
321 mem::take(&mut *self.coalesced_wheel_event_ids.borrow_mut());
322
323 let mut input_event_outcomes = Vec::with_capacity(
324 pending_input_events.len() +
325 coalesced_mouse_move_event_ids.len() +
326 coalesced_wheel_event_ids.len(),
327 );
328 for event in pending_input_events {
334 self.active_keyboard_modifiers
335 .set(event.active_keyboard_modifiers);
336 let result = match event.event.event {
337 InputEvent::MouseButton(mouse_button_event) => {
338 self.handle_native_mouse_button_event(cx, mouse_button_event, &event);
339 InputEventResult::default()
340 },
341 InputEvent::MouseMove(_) => {
342 self.handle_native_mouse_move_event(cx, &event);
343 input_event_outcomes.extend(
344 mem::take(&mut coalesced_mouse_move_event_ids)
345 .into_iter()
346 .map(|id| InputEventOutcome {
347 id,
348 result: InputEventResult::default(),
349 }),
350 );
351 InputEventResult::default()
352 },
353 InputEvent::MouseLeftViewport(mouse_leave_event) => {
354 self.handle_mouse_left_viewport_event(cx, &event, &mouse_leave_event);
355 InputEventResult::default()
356 },
357 InputEvent::Touch(touch_event) => self.handle_touch_event(cx, touch_event, &event),
358 InputEvent::Wheel(wheel_event) => {
359 let result = self.handle_wheel_event(cx, wheel_event, &event);
360 input_event_outcomes.extend(
361 mem::take(&mut coalesced_wheel_event_ids)
362 .into_iter()
363 .map(|id| InputEventOutcome { id, result }),
364 );
365 result
366 },
367 InputEvent::Keyboard(keyboard_event) => {
368 self.handle_keyboard_event(cx, keyboard_event)
369 },
370 InputEvent::Ime(ime_event) => self.handle_ime_event(cx, ime_event),
371 #[cfg(feature = "gamepad")]
372 InputEvent::Gamepad(gamepad_event) => {
373 self.handle_gamepad_event(gamepad_event);
374 InputEventResult::default()
375 },
376 InputEvent::EditingAction(editing_action_event) => {
377 self.handle_editing_action(cx, None, editing_action_event)
378 },
379 };
380
381 input_event_outcomes.push(InputEventOutcome {
382 id: event.event.id,
383 result,
384 });
385 }
386
387 self.notify_embedder_that_events_were_handled(input_event_outcomes);
388 }
389
390 fn notify_embedder_that_events_were_handled(
391 &self,
392 input_event_outcomes: Vec<InputEventOutcome>,
393 ) {
394 let trusted_window = Trusted::new(&*self.window);
397 self.window
398 .as_global_scope()
399 .task_manager()
400 .dom_manipulation_task_source()
401 .queue(task!(notify_webdriver_input_event_completed: move || {
402 let window = trusted_window.root();
403 window.send_to_embedder(
404 EmbedderMsg::InputEventsHandled(window.webview_id(), input_event_outcomes));
405 }));
406 }
407
408 fn target_for_events_following_focus(&self) -> DomRoot<EventTarget> {
412 let document = self.window.Document();
413 match &*document.focus_handler().focused_area() {
414 FocusableArea::Node { node, .. } => DomRoot::from_ref(node.upcast()),
415 FocusableArea::IFrameViewport { iframe_element, .. } => {
416 DomRoot::from_ref(iframe_element.upcast())
417 },
418 FocusableArea::Viewport => document
419 .GetBody()
420 .map(DomRoot::upcast)
421 .unwrap_or_else(|| DomRoot::from_ref(self.window.upcast())),
422 }
423 }
424
425 pub(crate) fn set_cursor(&self, cursor: Option<Cursor>) {
426 if cursor == self.current_cursor.get() {
427 return;
428 }
429 self.current_cursor.set(cursor);
430 self.window.send_to_embedder(EmbedderMsg::SetCursor(
431 self.window.webview_id(),
432 cursor.unwrap_or_default(),
433 ));
434 }
435
436 fn handle_mouse_left_viewport_event(
437 &self,
438 cx: &mut JSContext,
439 input_event: &ConstellationInputEvent,
440 mouse_leave_event: &MouseLeftViewportEvent,
441 ) {
442 if let Some(current_hover_target) = self.current_hover_target.get() {
443 let current_hover_target = current_hover_target.upcast::<Node>();
444 for element in current_hover_target
445 .inclusive_ancestors(ShadowIncluding::Yes)
446 .filter_map(DomRoot::downcast::<Element>)
447 {
448 element.set_hover_state(false);
449 }
450
451 if let Some(hit_test_result) = self
452 .most_recent_mousemove_point
453 .get()
454 .and_then(|point| self.window.hit_test_from_point_in_viewport(point))
455 {
456 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
457 cx,
458 &self.window,
459 FireMouseEventType::Out,
460 &hit_test_result,
461 input_event,
462 );
463
464 mouse_out_event
466 .to_pointer_hover_event("pointerout", CanGc::from_cx(cx))
467 .upcast::<Event>()
468 .fire(cx, current_hover_target.upcast());
469
470 mouse_out_event
471 .upcast::<Event>()
472 .fire(cx, current_hover_target.upcast());
473
474 self.handle_mouse_enter_leave_event(
475 cx,
476 DomRoot::from_ref(current_hover_target),
477 None,
478 FireMouseEventType::Leave,
479 &hit_test_result,
480 input_event,
481 );
482 }
483 }
484
485 if !mouse_leave_event.focus_moving_to_another_iframe {
490 self.window
494 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
495 self.set_cursor(None);
496 } else {
497 self.current_cursor.set(None);
498 }
499
500 self.current_hover_target.set(None);
501 self.most_recent_mousemove_point.set(None);
502 }
503
504 fn handle_mouse_enter_leave_event(
505 &self,
506 cx: &mut JSContext,
507 event_target: DomRoot<Node>,
508 related_target: Option<DomRoot<Node>>,
509 event_type: FireMouseEventType,
510 hit_test_result: &HitTestResult,
511 input_event: &ConstellationInputEvent,
512 ) {
513 assert!(matches!(
514 event_type,
515 FireMouseEventType::Enter | FireMouseEventType::Leave
516 ));
517
518 let common_ancestor = match related_target.as_ref() {
519 Some(related_target) => event_target
520 .common_ancestor_in_flat_tree(related_target)
521 .unwrap_or_else(|| DomRoot::from_ref(&*event_target)),
522 None => DomRoot::from_ref(&*event_target),
523 };
524
525 let mut targets = vec![];
528 let mut current = Some(event_target);
529 while let Some(node) = current {
530 if node == common_ancestor {
531 break;
532 }
533 current = node.parent_in_flat_tree();
534 targets.push(node);
535 }
536
537 if event_type == FireMouseEventType::Enter {
540 targets = targets.into_iter().rev().collect();
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(pointer_event_name, CanGc::from_cx(cx))
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 Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
589 return;
590 };
591
592 let old_mouse_move_point = self
593 .most_recent_mousemove_point
594 .replace(Some(hit_test_result.point_in_frame));
595 if old_mouse_move_point == Some(hit_test_result.point_in_frame) {
596 return;
597 }
598
599 self.set_cursor(Some(hit_test_result.cursor));
601
602 let Some(new_target) = hit_test_result
603 .node
604 .inclusive_ancestors(ShadowIncluding::Yes)
605 .find_map(DomRoot::downcast::<Element>)
606 else {
607 return;
608 };
609
610 let capture_is_active = self.get_pointer_capture_target(pointer_id).is_some();
611 let old_hover_target = self.current_hover_target.get();
612 let target_has_changed = old_hover_target
613 .as_ref()
614 .is_none_or(|old_target| *old_target != new_target);
615
616 if target_has_changed {
619 if let Some(old_target) = self.current_hover_target.get() {
621 let old_target_is_ancestor_of_new_target = old_target
622 .upcast::<Node>()
623 .is_ancestor_of(new_target.upcast::<Node>());
624
625 if !old_target_is_ancestor_of_new_target {
628 for element in old_target
629 .upcast::<Node>()
630 .inclusive_ancestors(ShadowIncluding::Yes)
631 .filter_map(DomRoot::downcast::<Element>)
632 {
633 element.set_hover_state(false);
634 }
635 }
636
637 if !capture_is_active {
638 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
639 cx,
640 &self.window,
641 FireMouseEventType::Out,
642 &hit_test_result,
643 input_event,
644 );
645 mouse_out_event
646 .upcast::<Event>()
647 .set_related_target(Some(new_target.upcast()));
648
649 mouse_out_event
651 .to_pointer_hover_event("pointerout", CanGc::from_cx(cx))
652 .upcast::<Event>()
653 .fire(cx, old_target.upcast());
654
655 mouse_out_event
656 .upcast::<Event>()
657 .fire(cx, old_target.upcast());
658
659 if !old_target_is_ancestor_of_new_target {
660 let event_target = DomRoot::from_ref(old_target.upcast::<Node>());
661 let moving_into = Some(DomRoot::from_ref(new_target.upcast::<Node>()));
662 self.handle_mouse_enter_leave_event(
663 cx,
664 event_target,
665 moving_into,
666 FireMouseEventType::Leave,
667 &hit_test_result,
668 input_event,
669 );
670 }
671 }
672 }
673
674 for element in new_target
676 .upcast::<Node>()
677 .inclusive_ancestors(ShadowIncluding::Yes)
678 .filter_map(DomRoot::downcast::<Element>)
679 {
680 element.set_hover_state(true);
681 }
682
683 if !capture_is_active {
684 let mouse_over_event = MouseEvent::new_for_platform_motion_event(
685 cx,
686 &self.window,
687 FireMouseEventType::Over,
688 &hit_test_result,
689 input_event,
690 );
691 mouse_over_event
692 .upcast::<Event>()
693 .set_related_target(old_hover_target.as_ref().map(|target| target.upcast()));
694
695 mouse_over_event
697 .to_pointer_hover_event("pointerover", CanGc::from_cx(cx))
698 .upcast::<Event>()
699 .dispatch(cx, new_target.upcast(), false);
700
701 mouse_over_event
702 .upcast::<Event>()
703 .dispatch(cx, new_target.upcast(), false);
704
705 let moving_from = old_hover_target
706 .map(|old_target| DomRoot::from_ref(old_target.upcast::<Node>()));
707 let event_target = DomRoot::from_ref(new_target.upcast::<Node>());
708 self.handle_mouse_enter_leave_event(
709 cx,
710 event_target,
711 moving_from,
712 FireMouseEventType::Enter,
713 &hit_test_result,
714 input_event,
715 );
716 }
717 }
718
719 let mouse_event = MouseEvent::new_for_platform_motion_event(
722 cx,
723 &self.window,
724 FireMouseEventType::Move,
725 &hit_test_result,
726 input_event,
727 );
728
729 let pointer_target = self
734 .get_pointer_capture_target(pointer_id)
735 .map(DomRoot::upcast::<EventTarget>)
736 .unwrap_or_else(|| DomRoot::from_ref(new_target.upcast::<EventTarget>()));
737
738 let pointer_event =
739 mouse_event.to_pointer_event(Atom::from("pointermove"), CanGc::from_cx(cx));
740 pointer_event.upcast::<Event>().set_composed(true);
741 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
742
743 if !released_disconnected {
747 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
748 }
749
750 mouse_event.upcast::<Event>().fire(cx, &pointer_target);
752
753 self.update_current_hover_target_and_status(Some(new_target));
754 }
755
756 fn update_current_hover_target_and_status(&self, new_hover_target: Option<DomRoot<Element>>) {
757 let current_hover_target = self.current_hover_target.get();
758 if current_hover_target == new_hover_target {
759 return;
760 }
761
762 let previous_hover_target = self.current_hover_target.get();
763 self.current_hover_target.set(new_hover_target.as_deref());
764
765 if let Some(target) = self.current_hover_target.get() &&
768 let Some(anchor) = target
769 .upcast::<Node>()
770 .inclusive_ancestors(ShadowIncluding::Yes)
771 .find_map(DomRoot::downcast::<HTMLAnchorElement>)
772 {
773 let status = anchor
774 .full_href_url_for_user_interface()
775 .map(|url| url.to_string());
776 self.window
777 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), status));
778 return;
779 }
780
781 if previous_hover_target.is_none_or(|previous_hover_target| {
786 previous_hover_target
787 .upcast::<Node>()
788 .inclusive_ancestors(ShadowIncluding::Yes)
789 .any(|node| node.is::<HTMLAnchorElement>())
790 }) {
791 self.window
792 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
793 }
794 }
795
796 pub(crate) fn handle_refresh_cursor(&self) {
797 let Some(most_recent_mousemove_point) = self.most_recent_mousemove_point.get() else {
798 return;
799 };
800
801 let Some(hit_test_result) = self
802 .window
803 .hit_test_from_point_in_viewport(most_recent_mousemove_point)
804 else {
805 return;
806 };
807
808 self.set_cursor(Some(hit_test_result.cursor));
809 }
810
811 fn set_active_element(&self, original_target: &Element) {
812 let find_element_for_activation = |element: &Element| {
813 let node: &Node = element.upcast();
814 if node.is_in_ua_widget() &&
815 let Some(containing_shadow_root) = node.containing_shadow_root()
816 {
817 return containing_shadow_root.Host();
818 }
819
820 if node.type_id() ==
822 NodeTypeId::Element(ElementTypeId::HTMLElement(
823 HTMLElementTypeId::HTMLLabelElement,
824 ))
825 {
826 let label = element.downcast::<HTMLLabelElement>().unwrap();
827 if let Some(control) = label.GetControl() {
828 return DomRoot::from_ref(control.upcast::<Element>());
829 }
830 }
831
832 DomRoot::from_ref(element)
833 };
834 let element_for_activation = find_element_for_activation(original_target);
835
836 if let Some(currently_active_element) = self.current_active_element.get() {
839 if currently_active_element == element_for_activation {
840 return;
841 }
842 self.unset_active_element();
843 }
844
845 element_for_activation.set_active_state(true);
846 self.current_active_element
847 .set(Some(&*element_for_activation));
848 }
849
850 fn unset_active_element(&self) {
851 if let Some(active_element) = self.current_active_element.take() {
852 active_element.set_active_state(false);
853 }
854 }
855
856 fn handle_native_mouse_button_event(
859 &self,
860 cx: &mut JSContext,
861 event: MouseButtonEvent,
862 input_event: &ConstellationInputEvent,
863 ) {
864 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
866 return;
867 };
868
869 debug!(
870 "{:?}: at {:?}",
871 event.action, hit_test_result.point_in_frame
872 );
873
874 let document = self.window.Document();
877 if event.action == MouseButtonAction::Down {
878 document
879 .focus_handler()
880 .set_sequential_focus_navigation_starting_point(&hit_test_result.node);
881 }
882
883 let Some(element) = hit_test_result
884 .node
885 .inclusive_ancestors(ShadowIncluding::Yes)
886 .find_map(DomRoot::downcast::<Element>)
887 else {
888 return;
889 };
890
891 let node = element.upcast::<Node>();
892 debug!("{:?} on {:?}", event.action, node.debug_str());
893
894 if event.button == MouseButton::Left {
899 if event.action == MouseButtonAction::Down {
900 self.set_active_element(&element);
901 }
902 if event.action == MouseButtonAction::Up {
903 self.unset_active_element();
904 }
905 }
906
907 if element.is_actually_disabled() {
911 return;
912 }
913
914 let mouse_event_type = match event.action {
915 embedder_traits::MouseButtonAction::Up => atom!("mouseup"),
916 embedder_traits::MouseButtonAction::Down => atom!("mousedown"),
917 };
918
919 if event.action == MouseButtonAction::Down {
926 self.click_counting_info
927 .borrow_mut()
928 .reset_click_count_if_necessary(event.button, hit_test_result.point_in_frame);
929 }
930
931 let mouse_event = MouseEvent::for_platform_button_event(
932 cx,
933 mouse_event_type,
934 event,
935 input_event.pressed_mouse_buttons,
936 &self.window,
937 &hit_test_result,
938 input_event.active_keyboard_modifiers,
939 self.click_counting_info.borrow().count + 1,
940 );
941
942 match event.action {
943 MouseButtonAction::Down => {
944 self.last_mouse_button_down_point
945 .set(Some(hit_test_result.point_in_frame));
946
947 let mouse_buttons_down = self.mouse_buttons_down.get();
949 let pointer_event_name = if mouse_buttons_down == 0 {
950 "pointerdown".into()
955 } else {
956 "pointermove".into()
962 };
963 let pointer_event =
964 mouse_event.to_pointer_event(pointer_event_name, CanGc::from_cx(cx));
965
966 let pointer_id = PointerId::Mouse as i32;
968
969 let released_disconnected =
971 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
972
973 let pointer_target = self
975 .get_pointer_capture_target(pointer_id)
976 .map(DomRoot::upcast::<EventTarget>)
977 .unwrap_or_else(|| DomRoot::from_ref(node.upcast::<EventTarget>()));
978
979 self.mouse_buttons_down.set(mouse_buttons_down + 1);
981
982 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
983
984 if !released_disconnected {
988 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
989 }
990
991 let result = mouse_event
993 .upcast::<Event>()
994 .dispatch(cx, node.upcast(), false);
995
996 if result {
999 document
1003 .focus_handler()
1004 .focus(cx, node.find_click_focusable_area());
1005 }
1006
1007 if let MouseButton::Right = event.button {
1010 self.maybe_show_context_menu(cx, node.upcast(), &hit_test_result, input_event);
1011 }
1012 },
1013 MouseButtonAction::Up => {
1015 let mouse_buttons_down = self.mouse_buttons_down.get();
1017 let pointer_event_name = if mouse_buttons_down == 1 {
1018 "pointerup".into()
1023 } else {
1024 "pointermove".into()
1030 };
1031 let pointer_event =
1032 mouse_event.to_pointer_event(pointer_event_name, CanGc::from_cx(cx));
1033
1034 let pointer_id = PointerId::Mouse as i32;
1036
1037 let released_disconnected =
1039 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
1040
1041 let pointer_target = self
1043 .get_pointer_capture_target(pointer_id)
1044 .map(DomRoot::upcast::<EventTarget>)
1045 .unwrap_or_else(|| DomRoot::from_ref(node.upcast::<EventTarget>()));
1046
1047 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1048
1049 self.mouse_buttons_down
1052 .set(mouse_buttons_down.saturating_sub(1));
1053
1054 if !released_disconnected {
1058 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
1059 }
1060
1061 if mouse_buttons_down == 1 {
1063 self.implicit_release_pointer_capture(cx, pointer_id, "mouse", true);
1064 }
1065
1066 mouse_event
1068 .upcast::<Event>()
1069 .dispatch(cx, node.upcast(), false);
1070
1071 self.click_counting_info
1075 .borrow_mut()
1076 .increment_click_count(event.button, hit_test_result.point_in_frame);
1077
1078 self.maybe_trigger_click_for_mouse_button_down_event(
1079 cx,
1080 event,
1081 input_event,
1082 &hit_test_result,
1083 &element,
1084 );
1085 },
1086 }
1087 }
1088
1089 fn maybe_trigger_click_for_mouse_button_down_event(
1092 &self,
1093 cx: &mut JSContext,
1094 event: MouseButtonEvent,
1095 input_event: &ConstellationInputEvent,
1096 hit_test_result: &HitTestResult,
1097 element: &Element,
1098 ) {
1099 if event.button != MouseButton::Left {
1100 return;
1101 }
1102
1103 let Some(last_mouse_button_down_point) = self.last_mouse_button_down_point.take() else {
1104 return;
1105 };
1106
1107 let distance = last_mouse_button_down_point.distance_to(hit_test_result.point_in_frame);
1108 let maximum_click_distance = 10.0 * self.window.device_pixel_ratio().get();
1109 if distance > maximum_click_distance {
1110 return;
1111 }
1112
1113 let element = match hit_test_result.node.find_click_focusable_area() {
1120 FocusableArea::Node { node, .. } => DomRoot::downcast::<Element>(node),
1121 _ => None,
1122 }
1123 .unwrap_or_else(|| DomRoot::from_ref(element));
1124 self.most_recently_clicked_element.set(Some(&*element));
1125
1126 let click_count = self.click_counting_info.borrow().count;
1127 element.set_click_in_progress(true);
1128 MouseEvent::for_platform_button_event(
1129 cx,
1130 atom!("click"),
1131 event,
1132 input_event.pressed_mouse_buttons,
1133 &self.window,
1134 hit_test_result,
1135 input_event.active_keyboard_modifiers,
1136 click_count,
1137 )
1138 .upcast::<Event>()
1139 .dispatch(cx, element.upcast(), false);
1140 element.set_click_in_progress(false);
1141
1142 if click_count.is_multiple_of(2) {
1151 MouseEvent::for_platform_button_event(
1152 cx,
1153 Atom::from("dblclick"),
1154 event,
1155 input_event.pressed_mouse_buttons,
1156 &self.window,
1157 hit_test_result,
1158 input_event.active_keyboard_modifiers,
1159 2,
1160 )
1161 .upcast::<Event>()
1162 .dispatch(cx, element.upcast(), false);
1163 }
1164 }
1165
1166 fn maybe_show_context_menu(
1168 &self,
1169 cx: &mut js::context::JSContext,
1170 target: &EventTarget,
1171 hit_test_result: &HitTestResult,
1172 input_event: &ConstellationInputEvent,
1173 ) {
1174 let menu_event = PointerEvent::new(
1176 &self.window, "contextmenu".into(), EventBubbles::Bubbles, EventCancelable::Cancelable, Some(&self.window), 0, hit_test_result.point_in_frame.to_i32(),
1183 hit_test_result.point_in_frame.to_i32(),
1184 hit_test_result
1185 .point_relative_to_initial_containing_block
1186 .to_i32(),
1187 input_event.active_keyboard_modifiers,
1188 2i16, input_event.pressed_mouse_buttons,
1190 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![], CanGc::from_cx(cx),
1207 );
1208 menu_event.upcast::<Event>().set_composed(true);
1209
1210 let result = menu_event.upcast::<Event>().fire(cx, target);
1212
1213 if result {
1215 self.window
1216 .Document()
1217 .embedder_controls()
1218 .show_context_menu(hit_test_result);
1219 };
1220 }
1221
1222 fn handle_touch_event(
1223 &self,
1224 cx: &mut JSContext,
1225 event: EmbedderTouchEvent,
1226 input_event: &ConstellationInputEvent,
1227 ) -> InputEventResult {
1228 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
1230 self.update_active_touch_points_when_early_return(event);
1231 return Default::default();
1232 };
1233
1234 let TouchId(identifier) = event.touch_id;
1235
1236 let Some(element) = hit_test_result
1237 .node
1238 .inclusive_ancestors(ShadowIncluding::Yes)
1239 .find_map(DomRoot::downcast::<Element>)
1240 else {
1241 self.update_active_touch_points_when_early_return(event);
1242 return Default::default();
1243 };
1244
1245 let current_target = DomRoot::upcast::<EventTarget>(element.clone());
1246 let window = &*self.window;
1247
1248 let client_x = Finite::wrap(hit_test_result.point_in_frame.x as f64);
1249 let client_y = Finite::wrap(hit_test_result.point_in_frame.y as f64);
1250 let page_x =
1251 Finite::wrap(hit_test_result.point_in_frame.x as f64 + window.PageXOffset() as f64);
1252 let page_y =
1253 Finite::wrap(hit_test_result.point_in_frame.y as f64 + window.PageYOffset() as f64);
1254
1255 let pointer_touch = Touch::new(
1257 window,
1258 identifier,
1259 ¤t_target,
1260 client_x,
1261 client_y, client_x,
1263 client_y,
1264 page_x,
1265 page_y,
1266 CanGc::from_cx(cx),
1267 );
1268
1269 let pointer_event_name = match event.event_type {
1271 TouchEventType::Down => "pointerdown",
1272 TouchEventType::Move => "pointermove",
1273 TouchEventType::Up => "pointerup",
1274 TouchEventType::Cancel => "pointercancel",
1275 };
1276
1277 let pointer_type = match event.pointer_type {
1279 TouchPointerType::Pen => "pen",
1280 TouchPointerType::Touch => "touch",
1281 };
1282
1283 let pointer_id = self.get_or_create_pointer_id_for_touch(identifier);
1285 let is_primary = self.is_primary_pointer(pointer_id);
1286
1287 if matches!(event.event_type, TouchEventType::Down) {
1290 let pointer_over = pointer_touch.to_pointer_event(
1292 window,
1293 "pointerover",
1294 pointer_id,
1295 is_primary,
1296 pointer_type,
1297 input_event.active_keyboard_modifiers,
1298 true, Some(hit_test_result.point_in_node),
1300 CanGc::from_cx(cx),
1301 );
1302 pointer_over.upcast::<Event>().fire(cx, ¤t_target);
1303
1304 self.fire_pointer_event_for_touch(
1306 cx,
1307 &element,
1308 &pointer_touch,
1309 pointer_id,
1310 "pointerenter",
1311 is_primary,
1312 pointer_type,
1313 input_event,
1314 &hit_test_result,
1315 );
1316 }
1317
1318 let released_disconnected = if matches!(event.event_type, TouchEventType::Cancel) {
1322 false
1323 } else {
1324 self.release_disconnected_pointer_capture(cx, pointer_id, pointer_type, is_primary)
1325 };
1326
1327 let pointer_target = self
1329 .get_pointer_capture_target(pointer_id)
1330 .map(DomRoot::upcast::<EventTarget>)
1331 .unwrap_or_else(|| current_target.clone());
1332
1333 let pointer_event = pointer_touch.to_pointer_event(
1334 window,
1335 pointer_event_name,
1336 pointer_id,
1337 is_primary,
1338 pointer_type,
1339 input_event.active_keyboard_modifiers,
1340 event.is_cancelable(),
1341 Some(hit_test_result.point_in_node),
1342 CanGc::from_cx(cx),
1343 );
1344 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1345
1346 if !released_disconnected && !matches!(event.event_type, TouchEventType::Cancel) {
1352 self.process_pending_pointer_capture(cx, pointer_id, pointer_type, is_primary);
1353 }
1354
1355 if matches!(
1358 event.event_type,
1359 TouchEventType::Up | TouchEventType::Cancel
1360 ) {
1361 self.implicit_release_pointer_capture(cx, pointer_id, pointer_type, is_primary);
1362 }
1363
1364 if matches!(
1367 event.event_type,
1368 TouchEventType::Up | TouchEventType::Cancel
1369 ) {
1370 let pointer_out = pointer_touch.to_pointer_event(
1372 window,
1373 "pointerout",
1374 pointer_id,
1375 is_primary,
1376 pointer_type,
1377 input_event.active_keyboard_modifiers,
1378 true, Some(hit_test_result.point_in_node),
1380 CanGc::from_cx(cx),
1381 );
1382 pointer_out.upcast::<Event>().fire(cx, ¤t_target);
1383
1384 self.fire_pointer_event_for_touch(
1386 cx,
1387 &element,
1388 &pointer_touch,
1389 pointer_id,
1390 "pointerleave",
1391 is_primary,
1392 pointer_type,
1393 input_event,
1394 &hit_test_result,
1395 );
1396 }
1397
1398 let (touch_dispatch_target, changed_touch) = match event.event_type {
1399 TouchEventType::Down => {
1400 self.active_touch_points
1402 .borrow_mut()
1403 .push(Dom::from_ref(&*pointer_touch));
1404 self.set_active_element(&element);
1405 (current_target, pointer_touch)
1406 },
1407 _ => {
1408 let mut active_touch_points = self.active_touch_points.borrow_mut();
1414 let Some(index) = active_touch_points
1415 .iter()
1416 .position(|point| point.Identifier() == identifier)
1417 else {
1418 warn!("No active touch point for {:?}", event.event_type);
1419 return Default::default();
1420 };
1421 let original_target = active_touch_points[index].Target();
1423
1424 let touch_with_touchstart_target = Touch::new(
1425 window,
1426 identifier,
1427 &original_target,
1428 client_x,
1429 client_y,
1430 client_x,
1431 client_y,
1432 page_x,
1433 page_y,
1434 CanGc::from_cx(cx),
1435 );
1436
1437 match event.event_type {
1439 TouchEventType::Move => {
1440 active_touch_points[index] = Dom::from_ref(&*touch_with_touchstart_target);
1441 },
1442 TouchEventType::Up | TouchEventType::Cancel => {
1443 active_touch_points.swap_remove(index);
1444 self.remove_pointer_id_for_touch(identifier);
1445 self.unset_active_element();
1446 },
1447 TouchEventType::Down => unreachable!("Should have been handled above"),
1448 }
1449 (original_target, touch_with_touchstart_target)
1450 },
1451 };
1452
1453 rooted_vec!(let mut target_touches);
1454 target_touches.extend(
1455 self.active_touch_points
1456 .borrow()
1457 .iter()
1458 .filter(|touch| touch.Target() == touch_dispatch_target)
1459 .cloned(),
1460 );
1461
1462 let event_name = match event.event_type {
1463 TouchEventType::Down => "touchstart",
1464 TouchEventType::Move => "touchmove",
1465 TouchEventType::Up => "touchend",
1466 TouchEventType::Cancel => "touchcancel",
1467 };
1468
1469 let touch_event = TouchEvent::new(
1470 window,
1471 event_name.into(),
1472 EventBubbles::Bubbles,
1473 EventCancelable::from(event.is_cancelable()),
1474 EventComposed::Composed,
1475 Some(window),
1476 0i32,
1477 &TouchList::new(
1478 window,
1479 self.active_touch_points.borrow().r(),
1480 CanGc::from_cx(cx),
1481 ),
1482 &TouchList::new(window, from_ref(&&*changed_touch), CanGc::from_cx(cx)),
1483 &TouchList::new(window, target_touches.r(), CanGc::from_cx(cx)),
1484 false,
1486 false,
1487 false,
1488 false,
1489 CanGc::from_cx(cx),
1490 );
1491 let event = touch_event.upcast::<Event>();
1492 event.fire(cx, &touch_dispatch_target);
1493 event.flags().into()
1494 }
1495
1496 fn update_active_touch_points_when_early_return(&self, event: EmbedderTouchEvent) {
1505 match event.event_type {
1506 TouchEventType::Down | TouchEventType::Move => {},
1507 TouchEventType::Up | TouchEventType::Cancel => {
1508 let mut active_touch_points = self.active_touch_points.borrow_mut();
1509 if let Some(index) = active_touch_points
1510 .iter()
1511 .position(|t| t.Identifier() == event.touch_id.0)
1512 {
1513 active_touch_points.swap_remove(index);
1514 self.remove_pointer_id_for_touch(event.touch_id.0);
1515 } else {
1516 warn!(
1517 "Received {:?} for a non-active touch point {}",
1518 event.event_type, event.touch_id.0
1519 );
1520 }
1521 },
1522 }
1523 }
1524
1525 fn handle_keyboard_event(
1527 &self,
1528 cx: &mut JSContext,
1529 keyboard_event: EmbedderKeyboardEvent,
1530 ) -> InputEventResult {
1531 let target = &self.target_for_events_following_focus();
1532 let keyevent = KeyboardEvent::new_with_platform_keyboard_event(
1533 cx,
1534 &self.window,
1535 keyboard_event.event.state.event_type().into(),
1536 &keyboard_event.event,
1537 );
1538
1539 let event = keyevent.upcast::<Event>();
1540
1541 event.set_composed(true);
1542
1543 event.fire(cx, target);
1544
1545 let mut flags = event.flags();
1546 if flags.contains(EventFlags::Canceled) {
1547 return flags.into();
1548 }
1549
1550 let is_character_value_key = matches!(
1556 keyboard_event.event.key,
1557 Key::Character(_) | Key::Named(NamedKey::Enter)
1558 );
1559 if keyboard_event.event.state == KeyState::Down &&
1560 is_character_value_key &&
1561 !keyboard_event.event.is_composing
1562 {
1563 let keypress_event = KeyboardEvent::new_with_platform_keyboard_event(
1565 cx,
1566 &self.window,
1567 atom!("keypress"),
1568 &keyboard_event.event,
1569 );
1570 keypress_event.upcast::<Event>().set_composed(true);
1571 let event = keypress_event.upcast::<Event>();
1572 event.fire(cx, target);
1573 flags = event.flags();
1574 }
1575
1576 flags.into()
1577 }
1578
1579 fn handle_ime_event(&self, cx: &mut JSContext, event: ImeEvent) -> InputEventResult {
1580 let document = self.window.Document();
1581 let composition_event = match event {
1582 ImeEvent::Dismissed => {
1583 document.focus_handler().focus(cx, FocusableArea::Viewport);
1584 return Default::default();
1585 },
1586 ImeEvent::Composition(composition_event) => composition_event,
1587 };
1588
1589 let focused_area = document.focus_handler().focused_area();
1594 let Some(focused_element) = focused_area.element() else {
1595 return Default::default();
1597 };
1598
1599 let cancelable = composition_event.state == keyboard_types::CompositionState::Start;
1600 let event = CompositionEvent::new(
1601 &self.window,
1602 composition_event.state.event_type().into(),
1603 true,
1604 cancelable,
1605 Some(&self.window),
1606 0,
1607 DOMString::from(composition_event.data),
1608 CanGc::from_cx(cx),
1609 );
1610
1611 let event = event.upcast::<Event>();
1612 event.fire(cx, focused_element.upcast());
1613 event.flags().into()
1614 }
1615
1616 fn handle_wheel_event(
1617 &self,
1618 cx: &mut JSContext,
1619 event: EmbedderWheelEvent,
1620 input_event: &ConstellationInputEvent,
1621 ) -> InputEventResult {
1622 let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
1624 return Default::default();
1625 };
1626
1627 let Some(el) = hit_test_result
1628 .node
1629 .inclusive_ancestors(ShadowIncluding::Yes)
1630 .find_map(DomRoot::downcast::<Element>)
1631 else {
1632 return Default::default();
1633 };
1634
1635 let node = el.upcast::<Node>();
1636 debug!(
1637 "wheel: on {:?} at {:?}",
1638 node.debug_str(),
1639 hit_test_result.point_in_frame
1640 );
1641
1642 let dom_event = WheelEvent::new(
1644 &self.window,
1645 "wheel".into(),
1646 EventBubbles::Bubbles,
1647 EventCancelable::Cancelable,
1648 Some(&self.window),
1649 0i32,
1650 hit_test_result.point_in_frame.to_i32(),
1651 hit_test_result.point_in_frame.to_i32(),
1652 hit_test_result
1653 .point_relative_to_initial_containing_block
1654 .to_i32(),
1655 input_event.active_keyboard_modifiers,
1656 0i16,
1657 input_event.pressed_mouse_buttons,
1658 None,
1659 None,
1660 Finite::wrap(-event.delta.x),
1665 Finite::wrap(-event.delta.y),
1666 Finite::wrap(-event.delta.z),
1667 event.delta.mode as u32,
1668 CanGc::from_cx(cx),
1669 );
1670
1671 let dom_event = dom_event.upcast::<Event>();
1672 dom_event.set_trusted(true);
1673 dom_event.set_composed(true);
1674 dom_event.fire(cx, node.upcast());
1675
1676 dom_event.flags().into()
1677 }
1678
1679 #[cfg(feature = "gamepad")]
1680 fn handle_gamepad_event(&self, gamepad_event: EmbedderGamepadEvent) {
1681 match gamepad_event {
1682 EmbedderGamepadEvent::Connected(index, name, bounds, supported_haptic_effects) => {
1683 self.handle_gamepad_connect(
1684 index.0,
1685 name,
1686 bounds.axis_bounds,
1687 bounds.button_bounds,
1688 supported_haptic_effects,
1689 );
1690 },
1691 EmbedderGamepadEvent::Disconnected(index) => {
1692 self.handle_gamepad_disconnect(index.0);
1693 },
1694 EmbedderGamepadEvent::Updated(index, update_type) => {
1695 self.receive_new_gamepad_button_or_axis(index.0, update_type);
1696 },
1697 };
1698 }
1699
1700 #[cfg(feature = "gamepad")]
1702 fn handle_gamepad_connect(
1703 &self,
1704 _index: usize,
1708 name: String,
1709 axis_bounds: (f64, f64),
1710 button_bounds: (f64, f64),
1711 supported_haptic_effects: GamepadSupportedHapticEffects,
1712 ) {
1713 let doc = self.window.Document();
1715
1716 if !doc.allowed_to_use_feature(PermissionName::Gamepad) {
1719 return;
1720 }
1721
1722 let trusted_window = Trusted::new(&*self.window);
1723
1724 self.window
1727 .upcast::<GlobalScope>()
1728 .task_manager()
1729 .gamepad_task_source()
1730 .queue(task!(gamepad_connected: move |cx| {
1731 let window = trusted_window.root();
1732
1733 let navigator = window.Navigator();
1736 let selected_index = navigator.select_gamepad_index();
1737 let gamepad = Gamepad::new(
1738 cx,
1739 &window,
1740 selected_index,
1741 name,
1742 "standard".into(),
1743 axis_bounds,
1744 button_bounds,
1745 supported_haptic_effects,
1746 false,
1747 );
1748
1749 navigator.set_gamepad(selected_index as usize, Some(&gamepad));
1751
1752 if navigator.has_gamepad_gesture() {
1754 gamepad.set_exposed(true);
1756 if window.Document().is_fully_active() {
1761 gamepad.notify_event(cx, GamepadEventType::Connected);
1762 }
1763 }
1764 }));
1765 }
1766
1767 #[cfg(feature = "gamepad")]
1769 fn handle_gamepad_disconnect(&self, index: usize) {
1770 let trusted_window = Trusted::new(&*self.window);
1774 self.window
1775 .upcast::<GlobalScope>()
1776 .task_manager()
1777 .gamepad_task_source()
1778 .queue(task!(gamepad_disconnected: move |cx| {
1779 let window = trusted_window.root();
1780 let navigator = window.Navigator();
1781
1782 if let Some(gamepad) = navigator.get_gamepad(index) {
1783 gamepad.update_connected(false);
1785 if gamepad.exposed() && window.Document().is_fully_active() {
1793 gamepad.notify_event(cx, GamepadEventType::Disconnected);
1794 }
1795 }
1796
1797 navigator.set_gamepad(index, None);
1801 navigator.shrink_gamepads_list();
1804 }));
1805 }
1806
1807 #[cfg(feature = "gamepad")]
1809 fn receive_new_gamepad_button_or_axis(&self, index: usize, update_type: GamepadUpdateType) {
1810 let trusted_window = Trusted::new(&*self.window);
1813
1814 self.window
1817 .upcast::<GlobalScope>()
1818 .task_manager()
1819 .gamepad_task_source()
1820 .queue(task!(update_gamepad_state: move || {
1821 let window = trusted_window.root();
1822 let document = window.Document();
1823 document.event_handler().update_gamepad_state(index, update_type);
1824 }));
1825 }
1826
1827 #[cfg(feature = "gamepad")]
1829 fn update_gamepad_state(&self, gamepad_index: usize, update_type: GamepadUpdateType) {
1830 use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods;
1831 let now = *self.window.Performance().Now();
1834
1835 let navigator = self.window.Navigator();
1838
1839 if let Some(gamepad) = navigator.get_gamepad(gamepad_index) {
1840 gamepad.update_timestamp(now);
1842 match update_type {
1845 GamepadUpdateType::Axis(axis_index, value) => {
1846 gamepad.map_and_normalize_axes(axis_index, value);
1847 },
1848 GamepadUpdateType::Button(button_index, value) => {
1849 gamepad.map_and_normalize_buttons(button_index, value);
1850 },
1851 };
1852 if !navigator.has_gamepad_gesture() && contains_user_gesture(update_type) {
1857 navigator.set_has_gamepad_gesture(true);
1859 navigator
1861 .get_connected_gamepad()
1862 .iter()
1863 .for_each(|connected_gamepad| {
1864 connected_gamepad.set_exposed(true);
1866 connected_gamepad.update_timestamp(now);
1868 let trusted_gamepad = Trusted::new(&**connected_gamepad);
1875 if self.window.Document().is_fully_active() {
1876 self.window
1877 .upcast::<GlobalScope>()
1878 .task_manager()
1879 .gamepad_task_source()
1880 .queue(task!(fire_gamepad_connected: move |cx| {
1881 let gamepad = trusted_gamepad.root();
1882 gamepad.notify_event(cx, GamepadEventType::Connected);
1883 }));
1884 }
1885 });
1886 }
1887 }
1888 }
1889
1890 pub(crate) fn handle_editing_action(
1892 &self,
1893 cx: &mut JSContext,
1894 element: Option<DomRoot<Element>>,
1895 action: EditingActionEvent,
1896 ) -> InputEventResult {
1897 let clipboard_event_type = match action {
1898 EditingActionEvent::Copy => ClipboardEventType::Copy,
1899 EditingActionEvent::Cut => ClipboardEventType::Cut,
1900 EditingActionEvent::Paste => ClipboardEventType::Paste,
1901 };
1902
1903 let script_triggered = false;
1905
1906 let script_may_access_clipboard = false;
1910
1911 if script_triggered && !script_may_access_clipboard {
1913 return InputEventResult::empty();
1914 }
1915
1916 let clipboard_event = self.fire_clipboard_event(cx, element.clone(), clipboard_event_type);
1918
1919 let event = clipboard_event.upcast::<Event>();
1922 if !event.IsTrusted() {
1923 return event.flags().into();
1924 }
1925
1926 if event.DefaultPrevented() {
1928 let event_type = event.Type();
1929 match_domstring_ascii!(event_type,
1930
1931 "copy" => {
1932 if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1935 let drag_data_store =
1936 clipboard_data.data_store().expect("This shouldn't fail");
1937 self.write_content_to_the_clipboard(&drag_data_store);
1938 }
1939 },
1940 "cut" => {
1941 if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1944 let drag_data_store =
1945 clipboard_data.data_store().expect("This shouldn't fail");
1946 self.write_content_to_the_clipboard(&drag_data_store);
1947 }
1948
1949 self.fire_clipboard_event(cx, element, ClipboardEventType::Change);
1951 },
1952 "paste" => (),
1956 _ => (),
1957 )
1958 }
1959
1960 event.flags().into()
1963 }
1964
1965 pub(crate) fn fire_clipboard_event(
1967 &self,
1968 cx: &mut JSContext,
1969 target: Option<DomRoot<Element>>,
1970 clipboard_event_type: ClipboardEventType,
1971 ) -> DomRoot<ClipboardEvent> {
1972 let clipboard_event = ClipboardEvent::new(
1973 &self.window,
1974 None,
1975 clipboard_event_type.as_str().into(),
1976 EventBubbles::Bubbles,
1977 EventCancelable::Cancelable,
1978 None,
1979 CanGc::from_cx(cx),
1980 );
1981
1982 let mut drag_data_store = DragDataStore::new();
1985
1986 let trusted = true;
1990
1991 let target = target
1993 .map(DomRoot::upcast)
1994 .unwrap_or_else(|| self.target_for_events_following_focus());
1995
1996 match clipboard_event_type {
1999 ClipboardEventType::Copy | ClipboardEventType::Cut => {
2000 drag_data_store.set_mode(Mode::ReadWrite);
2002 },
2003 ClipboardEventType::Paste => {
2004 let (callback, receiver) =
2005 GenericCallback::new_blocking().expect("Could not create callback");
2006 self.window.send_to_embedder(EmbedderMsg::GetClipboardText(
2007 self.window.webview_id(),
2008 callback,
2009 ));
2010 let text_contents = receiver
2011 .recv()
2012 .map(Result::unwrap_or_default)
2013 .unwrap_or_default();
2014
2015 drag_data_store.set_mode(Mode::ReadOnly);
2017 if trusted {
2019 let data = DOMString::from(text_contents);
2023 let type_ = DOMString::from("text/plain");
2024 let _ = drag_data_store.add(Kind::Text { data, type_ });
2025
2026 }
2032 },
2033 ClipboardEventType::Change => (),
2034 }
2035
2036 let clipboard_event_data = DataTransfer::new(
2038 &self.window,
2039 Rc::new(RefCell::new(Some(drag_data_store))),
2040 CanGc::from_cx(cx),
2041 );
2042
2043 clipboard_event.set_clipboard_data(Some(&clipboard_event_data));
2045
2046 let event = clipboard_event.upcast::<Event>();
2048 event.set_trusted(trusted);
2049
2050 event.set_composed(true);
2052
2053 event.dispatch(cx, &target, false);
2055
2056 DomRoot::from(clipboard_event)
2057 }
2058
2059 fn write_content_to_the_clipboard(&self, drag_data_store: &DragDataStore) {
2061 if drag_data_store.list_len() > 0 {
2063 self.window
2065 .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
2066 for item in drag_data_store.iter_item_list() {
2068 match item {
2069 Kind::Text { data, .. } => {
2070 self.window.send_to_embedder(EmbedderMsg::SetClipboardText(
2074 self.window.webview_id(),
2075 data.to_string(),
2076 ));
2077 },
2078 Kind::File { .. } => {
2079 },
2083 }
2084 }
2085 } else {
2086 if drag_data_store.clear_was_called {
2088 self.window
2090 .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
2091 }
2094 }
2095 }
2096
2097 #[expect(unsafe_code)]
2100 pub(crate) fn handle_embedder_scroll_event(&self, scrolled_node: ExternalScrollId) {
2101 let document = self.window.Document();
2103 if scrolled_node.is_root() {
2104 document.handle_viewport_scroll_event();
2105 } else {
2106 let node_id = node_id_from_scroll_id(scrolled_node.0 as usize);
2110 let node = unsafe {
2111 node::from_untrusted_node_address(UntrustedNodeAddress::from_id(node_id))
2112 };
2113 let Some(element) = node
2114 .inclusive_ancestors(ShadowIncluding::Yes)
2115 .find_map(DomRoot::downcast::<Element>)
2116 else {
2117 return;
2118 };
2119
2120 element.handle_scroll_event();
2121 }
2122 }
2123
2124 pub(crate) fn maybe_dispatch_simulated_click(
2130 &self,
2131 cx: &mut JSContext,
2132 node: &Node,
2133 event: &KeyboardEvent,
2134 ) -> bool {
2135 if event.key() != Key::Named(NamedKey::Enter) && event.original_code() != Some(Code::Space)
2136 {
2137 return false;
2138 }
2139
2140 if node
2144 .downcast::<Element>()
2145 .and_then(Element::as_maybe_activatable)
2146 .is_none()
2147 {
2148 return false;
2149 }
2150
2151 node.fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
2152 true
2153 }
2154
2155 pub(crate) fn run_default_keyboard_event_handler(
2156 &self,
2157 cx: &mut js::context::JSContext,
2158 node: &Node,
2159 event: &KeyboardEvent,
2160 ) {
2161 if event.upcast::<Event>().type_() != atom!("keydown") {
2162 return;
2163 }
2164
2165 if self.maybe_dispatch_simulated_click(cx, node, event) {
2166 return;
2167 }
2168
2169 if self.maybe_handle_accesskey(cx, event) {
2170 return;
2171 }
2172
2173 let mut is_space = false;
2174 let scroll = match event.key() {
2175 Key::Named(NamedKey::ArrowDown) => KeyboardScroll::Down,
2176 Key::Named(NamedKey::ArrowLeft) => KeyboardScroll::Left,
2177 Key::Named(NamedKey::ArrowRight) => KeyboardScroll::Right,
2178 Key::Named(NamedKey::ArrowUp) => KeyboardScroll::Up,
2179 Key::Named(NamedKey::End) => KeyboardScroll::End,
2180 Key::Named(NamedKey::Home) => KeyboardScroll::Home,
2181 Key::Named(NamedKey::PageDown) => KeyboardScroll::PageDown,
2182 Key::Named(NamedKey::PageUp) => KeyboardScroll::PageUp,
2183 Key::Character(string) if &string == " " => {
2184 is_space = true;
2185 if event.modifiers().contains(Modifiers::SHIFT) {
2186 KeyboardScroll::PageUp
2187 } else {
2188 KeyboardScroll::PageDown
2189 }
2190 },
2191 Key::Named(NamedKey::Tab) => {
2192 self.window
2198 .Document()
2199 .focus_handler()
2200 .sequential_focus_navigation_via_keyboard_event(cx, event);
2201 return;
2202 },
2203 _ => return,
2204 };
2205
2206 if !event.modifiers().is_empty() && !is_space {
2207 return;
2208 }
2209
2210 self.do_keyboard_scroll(cx, scroll);
2211 }
2212
2213 pub(crate) fn do_keyboard_scroll(&self, cx: &mut JSContext, scroll: KeyboardScroll) {
2214 let scroll_axis = match scroll {
2215 KeyboardScroll::Left | KeyboardScroll::Right => ScrollingBoxAxis::X,
2216 _ => ScrollingBoxAxis::Y,
2217 };
2218
2219 let document = self.window.Document();
2220 let mut scrolling_box = document
2221 .focus_handler()
2222 .focused_area()
2223 .element()
2224 .or(self.most_recently_clicked_element.get().as_deref())
2225 .and_then(|element| element.scrolling_box(ScrollContainerQueryFlags::Inclusive))
2226 .unwrap_or_else(|| {
2227 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2228 });
2229
2230 while !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
2231 if scrolling_box.is_viewport() {
2233 break;
2234 }
2235 let parent = scrolling_box.parent().unwrap_or_else(|| {
2236 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2237 });
2238 scrolling_box = parent;
2239 }
2240
2241 let calculate_current_scroll_offset_and_delta = || {
2242 const LINE_HEIGHT: f32 = 76.0;
2243 const LINE_WIDTH: f32 = 76.0;
2244
2245 let current_scroll_offset = scrolling_box.scroll_position();
2246 (
2247 current_scroll_offset,
2248 match scroll {
2249 KeyboardScroll::Home => Vector2D::new(0.0, -current_scroll_offset.y),
2250 KeyboardScroll::End => Vector2D::new(
2251 0.0,
2252 -current_scroll_offset.y + scrolling_box.content_size().height -
2253 scrolling_box.size().height,
2254 ),
2255 KeyboardScroll::PageDown => {
2256 Vector2D::new(0.0, scrolling_box.size().height - 2.0 * LINE_HEIGHT)
2257 },
2258 KeyboardScroll::PageUp => {
2259 Vector2D::new(0.0, 2.0 * LINE_HEIGHT - scrolling_box.size().height)
2260 },
2261 KeyboardScroll::Up => Vector2D::new(0.0, -LINE_HEIGHT),
2262 KeyboardScroll::Down => Vector2D::new(0.0, LINE_HEIGHT),
2263 KeyboardScroll::Left => Vector2D::new(-LINE_WIDTH, 0.0),
2264 KeyboardScroll::Right => Vector2D::new(LINE_WIDTH, 0.0),
2265 },
2266 )
2267 };
2268
2269 let parent_pipeline = self.window.parent_info();
2273 if scrolling_box.is_viewport() && parent_pipeline.is_none() {
2274 let (_, delta) = calculate_current_scroll_offset_and_delta();
2275 self.window
2276 .paint_api()
2277 .scroll_viewport_by_delta(self.window.webview_id(), delta);
2278 }
2279
2280 if !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
2283 assert!(scrolling_box.is_viewport());
2284
2285 let window_proxy = document.window().window_proxy();
2286 if let Some(iframe) = window_proxy.frame_element() {
2287 let iframe_window = iframe.owner_window();
2290 let mut realm = enter_auto_realm(cx, &*iframe_window);
2291 let cx = &mut realm;
2292 iframe_window
2293 .Document()
2294 .event_handler()
2295 .do_keyboard_scroll(cx, scroll);
2296 } else if let Some(parent_pipeline) = parent_pipeline {
2297 document.window().send_to_constellation(
2301 ScriptToConstellationMessage::ForwardKeyboardScroll(parent_pipeline, scroll),
2302 );
2303 };
2304 return;
2305 }
2306
2307 let (current_scroll_offset, delta) = calculate_current_scroll_offset_and_delta();
2308 scrolling_box.scroll_to(cx, delta + current_scroll_offset, ScrollBehavior::Auto);
2309 }
2310
2311 fn get_or_create_pointer_id_for_touch(&self, touch_id: i32) -> i32 {
2314 let mut active_pointer_ids = self.active_pointer_ids.borrow_mut();
2315
2316 if let Some(&pointer_id) = active_pointer_ids.get(&touch_id) {
2317 return pointer_id;
2318 }
2319
2320 let pointer_id = self.next_touch_pointer_id.get();
2321 active_pointer_ids.insert(touch_id, pointer_id);
2322 self.next_touch_pointer_id.set(pointer_id + 1);
2323 pointer_id
2324 }
2325
2326 fn remove_pointer_id_for_touch(&self, touch_id: i32) {
2328 self.active_pointer_ids.borrow_mut().remove(&touch_id);
2329 }
2330
2331 fn is_primary_pointer(&self, pointer_id: i32) -> bool {
2334 self.active_pointer_ids
2337 .borrow()
2338 .values()
2339 .min()
2340 .is_some_and(|primary_pointer| *primary_pointer == pointer_id)
2341 }
2342
2343 #[allow(clippy::too_many_arguments)]
2347 fn fire_pointer_event_for_touch(
2348 &self,
2349 cx: &mut js::context::JSContext,
2350 target_element: &Element,
2351 touch: &Touch,
2352 pointer_id: i32,
2353 event_name: &str,
2354 is_primary: bool,
2355 pointer_type: &str,
2356 input_event: &ConstellationInputEvent,
2357 hit_test_result: &HitTestResult,
2358 ) {
2359 let mut targets: Vec<DomRoot<Node>> = vec![];
2361 let mut current: Option<DomRoot<Node>> = Some(DomRoot::from_ref(target_element.upcast()));
2362 while let Some(node) = current {
2363 targets.push(DomRoot::from_ref(&*node));
2364 current = node.parent_in_flat_tree();
2365 }
2366
2367 if event_name == "pointerenter" {
2369 targets.reverse();
2370 }
2371
2372 for target in targets {
2373 let pointer_event = touch.to_pointer_event(
2374 &self.window,
2375 event_name,
2376 pointer_id,
2377 is_primary,
2378 pointer_type,
2379 input_event.active_keyboard_modifiers,
2380 false,
2381 Some(hit_test_result.point_in_node),
2382 CanGc::from_cx(cx),
2383 );
2384 pointer_event.upcast::<Event>().fire(cx, target.upcast());
2385 }
2386 }
2387
2388 pub(crate) fn has_assigned_access_key(&self, element: &HTMLElement) -> bool {
2389 self.access_key_handlers
2390 .borrow()
2391 .values()
2392 .any(|value| &**value == element)
2393 }
2394
2395 pub(crate) fn unassign_access_key(&self, element: &HTMLElement) {
2396 self.access_key_handlers
2397 .borrow_mut()
2398 .retain(|_, value| &**value != element)
2399 }
2400
2401 pub(crate) fn assign_access_key(&self, element: &HTMLElement, code: Code) {
2402 let mut access_key_handlers = self.access_key_handlers.borrow_mut();
2403 access_key_handlers
2405 .entry(code.into())
2406 .or_insert(Dom::from_ref(element));
2407 }
2408
2409 fn maybe_handle_accesskey(
2410 &self,
2411 cx: &mut js::context::JSContext,
2412 event: &KeyboardEvent,
2413 ) -> bool {
2414 #[cfg(target_os = "macos")]
2415 let access_key_modifiers = Modifiers::CONTROL | Modifiers::ALT;
2416 #[cfg(not(target_os = "macos"))]
2417 let access_key_modifiers = Modifiers::SHIFT | Modifiers::ALT;
2418
2419 if event.modifiers() != access_key_modifiers {
2420 return false;
2421 }
2422
2423 let Ok(code) = Code::from_str(&event.Code().str()) else {
2424 return false;
2425 };
2426
2427 let Some(html_element) = self
2428 .access_key_handlers
2429 .borrow()
2430 .get(&code.into())
2431 .map(|html_element| html_element.as_rooted())
2432 else {
2433 return false;
2434 };
2435
2436 let Ok(command) = InteractiveElementCommand::try_from(&*html_element) else {
2444 return false;
2445 };
2446
2447 if command.disabled() || command.hidden() {
2448 return false;
2449 }
2450
2451 let node = html_element.upcast::<Node>();
2452 if !node.is_connected() {
2453 return false;
2454 }
2455
2456 for node in node.inclusive_ancestors_unrooted(cx.no_gc(), ShadowIncluding::Yes) {
2457 if node
2458 .downcast::<HTMLElement>()
2459 .is_some_and(|html_element| html_element.Hidden())
2460 {
2461 return false;
2462 }
2463 }
2464
2465 self.focus_and_scroll_to_element_for_key_event(cx, html_element.upcast());
2468 command.perform_action(cx);
2469 true
2470 }
2471
2472 pub(crate) fn focus_and_scroll_to_element_for_key_event(
2473 &self,
2474 cx: &mut JSContext,
2475 element: &Element,
2476 ) {
2477 element.upcast::<Node>().run_the_focusing_steps(cx, None);
2478 let scroll_axis = ScrollAxisState {
2479 position: ScrollLogicalPosition::Center,
2480 requirement: ScrollRequirement::IfNotVisible,
2481 };
2482 element.scroll_into_view_with_options(
2483 cx,
2484 ScrollBehavior::Auto,
2485 scroll_axis,
2486 scroll_axis,
2487 None,
2488 None,
2489 );
2490 }
2491
2492 pub(crate) fn is_active_pointer(&self, pointer_id: i32) -> bool {
2495 if pointer_id == PointerId::Mouse as i32 {
2496 self.mouse_buttons_down.get() > 0
2498 } else {
2499 self.active_pointer_ids
2501 .borrow()
2502 .values()
2503 .any(|&id| id == pointer_id)
2504 }
2505 }
2506
2507 pub(crate) fn set_pending_pointer_capture(&self, pointer_id: i32, element: &Element) {
2510 self.pending_pointer_capture
2511 .borrow_mut()
2512 .insert(pointer_id, Dom::from_ref(element));
2513 }
2514
2515 pub(crate) fn clear_pending_pointer_capture(&self, pointer_id: i32) {
2518 self.pending_pointer_capture
2519 .borrow_mut()
2520 .remove(&pointer_id);
2521 }
2522
2523 pub(crate) fn get_pending_pointer_capture(&self, pointer_id: i32) -> Option<DomRoot<Element>> {
2525 self.pending_pointer_capture
2526 .borrow()
2527 .get(&pointer_id)
2528 .map(|el| DomRoot::from_ref(&**el))
2529 }
2530
2531 pub(crate) fn has_pointer_capture(&self, pointer_id: i32, element: &Element) -> bool {
2534 self.pending_pointer_capture
2535 .borrow()
2536 .get(&pointer_id)
2537 .is_some_and(|el| &**el == element)
2538 }
2539
2540 fn get_pointer_capture_target(&self, pointer_id: i32) -> Option<DomRoot<Element>> {
2545 self.pointer_capture_target
2546 .borrow()
2547 .get(&pointer_id)
2548 .map(|el| DomRoot::from_ref(&**el))
2549 .filter(|el| el.upcast::<Node>().is_connected())
2550 }
2551
2552 fn release_disconnected_pointer_capture(
2558 &self,
2559 cx: &mut JSContext,
2560 pointer_id: i32,
2561 pointer_type: &str,
2562 is_primary: bool,
2563 ) -> bool {
2564 let capture_target = self
2565 .pointer_capture_target
2566 .borrow()
2567 .get(&pointer_id)
2568 .map(|el| DomRoot::from_ref(&**el));
2569 if let Some(capture_element) = capture_target &&
2570 !capture_element.upcast::<Node>().is_connected()
2571 {
2572 let document = self.window.Document();
2574 self.fire_pointer_capture_event_at_target(
2575 cx,
2576 "lostpointercapture",
2577 pointer_id,
2578 pointer_type,
2579 is_primary,
2580 document.upcast::<EventTarget>(),
2581 );
2582 self.pending_pointer_capture
2584 .borrow_mut()
2585 .remove(&pointer_id);
2586 self.pointer_capture_target.borrow_mut().remove(&pointer_id);
2587 return true;
2588 }
2589 false
2590 }
2591
2592 fn fire_pointer_capture_event(
2595 &self,
2596 cx: &mut JSContext,
2597 event_type: &str,
2598 pointer_id: i32,
2599 pointer_type: &str,
2600 is_primary: bool,
2601 target: &Element,
2602 ) {
2603 self.fire_pointer_capture_event_at_target(
2604 cx,
2605 event_type,
2606 pointer_id,
2607 pointer_type,
2608 is_primary,
2609 target.upcast::<EventTarget>(),
2610 );
2611 }
2612
2613 fn fire_pointer_capture_event_at_target(
2615 &self,
2616 cx: &mut JSContext,
2617 event_type: &str,
2618 pointer_id: i32,
2619 pointer_type: &str,
2620 is_primary: bool,
2621 target: &EventTarget,
2622 ) {
2623 let pointer_event = PointerEvent::new(
2624 &self.window,
2625 event_type.into(),
2626 EventBubbles::Bubbles,
2627 EventCancelable::NotCancelable,
2628 Some(&self.window),
2629 0,
2630 Point2D::new(0, 0),
2631 Point2D::new(0, 0),
2632 Point2D::new(0, 0),
2633 Modifiers::empty(),
2634 0,
2635 0,
2636 None,
2637 None,
2638 pointer_id,
2639 1,
2640 1,
2641 0.0,
2642 0.0,
2643 0,
2644 0,
2645 0,
2646 PI / 2.0,
2647 0.0,
2648 DOMString::from(pointer_type),
2649 is_primary,
2650 vec![],
2651 vec![],
2652 CanGc::from_cx(cx),
2653 );
2654 pointer_event.upcast::<Event>().set_composed(true);
2655 pointer_event.upcast::<Event>().fire(cx, target);
2656 }
2657
2658 #[expect(clippy::too_many_arguments)]
2662 fn fire_pointer_boundary_event(
2663 &self,
2664 cx: &mut JSContext,
2665 event_type: &str,
2666 bubbles: EventBubbles,
2667 pointer_id: i32,
2668 pointer_type: &str,
2669 is_primary: bool,
2670 target: &Element,
2671 related_target: Option<&Element>,
2672 ) {
2673 let pointer_event = PointerEvent::new(
2674 &self.window,
2675 event_type.into(),
2676 bubbles,
2677 EventCancelable::NotCancelable,
2678 Some(&self.window),
2679 0,
2680 Point2D::new(0, 0),
2681 Point2D::new(0, 0),
2682 Point2D::new(0, 0),
2683 Modifiers::empty(),
2684 -1, self.mouse_buttons_down.get() as u16,
2686 related_target.map(|el| el.upcast::<EventTarget>()),
2687 None,
2688 pointer_id,
2689 1,
2690 1,
2691 0.0,
2692 0.0,
2693 0,
2694 0,
2695 0,
2696 PI / 2.0,
2697 0.0,
2698 DOMString::from(pointer_type),
2699 is_primary,
2700 vec![],
2701 vec![],
2702 CanGc::from_cx(cx),
2703 );
2704 pointer_event.upcast::<Event>().set_composed(true);
2705 pointer_event.upcast::<Event>().fire(cx, target.upcast());
2706 }
2707
2708 fn fire_pointer_capture_boundary_transition(
2713 &self,
2714 cx: &mut JSContext,
2715 pointer_id: i32,
2716 pointer_type: &str,
2717 is_primary: bool,
2718 old_target: &Element,
2719 new_target: &Element,
2720 ) {
2721 if pointer_type != "mouse" {
2722 return;
2723 }
2724 if old_target == new_target {
2725 return;
2726 }
2727 self.fire_pointer_boundary_event(
2728 cx,
2729 "pointerout",
2730 EventBubbles::Bubbles,
2731 pointer_id,
2732 pointer_type,
2733 is_primary,
2734 old_target,
2735 Some(new_target),
2736 );
2737 self.fire_pointer_boundary_event(
2738 cx,
2739 "pointerleave",
2740 EventBubbles::DoesNotBubble,
2741 pointer_id,
2742 pointer_type,
2743 is_primary,
2744 old_target,
2745 Some(new_target),
2746 );
2747 self.fire_pointer_boundary_event(
2748 cx,
2749 "pointerover",
2750 EventBubbles::Bubbles,
2751 pointer_id,
2752 pointer_type,
2753 is_primary,
2754 new_target,
2755 Some(old_target),
2756 );
2757 self.fire_pointer_boundary_event(
2758 cx,
2759 "pointerenter",
2760 EventBubbles::DoesNotBubble,
2761 pointer_id,
2762 pointer_type,
2763 is_primary,
2764 new_target,
2765 Some(old_target),
2766 );
2767 }
2768
2769 fn implicit_release_pointer_capture(
2772 &self,
2773 cx: &mut JSContext,
2774 pointer_id: i32,
2775 pointer_type: &str,
2776 is_primary: bool,
2777 ) {
2778 let capture_element = self
2779 .pointer_capture_target
2780 .borrow()
2781 .get(&pointer_id)
2782 .map(|el| DomRoot::from_ref(&**el));
2783 if let Some(capture_element) = capture_element {
2784 if capture_element.upcast::<Node>().is_connected() {
2785 self.fire_pointer_capture_event(
2786 cx,
2787 "lostpointercapture",
2788 pointer_id,
2789 pointer_type,
2790 is_primary,
2791 &capture_element,
2792 );
2793 if let Some(hover_target) = self.current_hover_target.get() {
2796 self.fire_pointer_capture_boundary_transition(
2797 cx,
2798 pointer_id,
2799 pointer_type,
2800 is_primary,
2801 &capture_element,
2802 &hover_target,
2803 );
2804 }
2805 } else {
2806 let document = self.window.Document();
2807 self.fire_pointer_capture_event_at_target(
2808 cx,
2809 "lostpointercapture",
2810 pointer_id,
2811 pointer_type,
2812 is_primary,
2813 document.upcast::<EventTarget>(),
2814 );
2815 }
2816 }
2817 self.pending_pointer_capture
2818 .borrow_mut()
2819 .remove(&pointer_id);
2820 self.pointer_capture_target.borrow_mut().remove(&pointer_id);
2821 }
2822
2823 fn process_pending_pointer_capture(
2827 &self,
2828 cx: &mut JSContext,
2829 pointer_id: i32,
2830 pointer_type: &str,
2831 is_primary: bool,
2832 ) {
2833 let pending = self
2834 .pending_pointer_capture
2835 .borrow()
2836 .get(&pointer_id)
2837 .map(|el| DomRoot::from_ref(&**el));
2838 let current = self
2839 .pointer_capture_target
2840 .borrow()
2841 .get(&pointer_id)
2842 .map(|el| DomRoot::from_ref(&**el));
2843
2844 let pending_connected = pending
2847 .as_ref()
2848 .is_some_and(|el| el.upcast::<Node>().is_connected());
2849
2850 let pointer_is_active = self.is_active_pointer(pointer_id);
2853
2854 match (&pending, ¤t) {
2855 (Some(pending_el), None) if pending_connected => {
2856 if pointer_is_active {
2857 if let Some(hover_target) = self.current_hover_target.get() {
2860 self.fire_pointer_capture_boundary_transition(
2861 cx,
2862 pointer_id,
2863 pointer_type,
2864 is_primary,
2865 &hover_target,
2866 pending_el,
2867 );
2868 }
2869 self.fire_pointer_capture_event(
2870 cx,
2871 "gotpointercapture",
2872 pointer_id,
2873 pointer_type,
2874 is_primary,
2875 pending_el,
2876 );
2877 self.pointer_capture_target
2878 .borrow_mut()
2879 .insert(pointer_id, Dom::from_ref(pending_el));
2880 } else {
2881 self.pending_pointer_capture
2882 .borrow_mut()
2883 .remove(&pointer_id);
2884 }
2885 },
2886 (Some(pending_el), Some(current_el))
2887 if pending_connected && pending_el != current_el =>
2888 {
2889 self.fire_pointer_capture_event(
2890 cx,
2891 "lostpointercapture",
2892 pointer_id,
2893 pointer_type,
2894 is_primary,
2895 current_el,
2896 );
2897 if pointer_is_active {
2898 self.fire_pointer_capture_boundary_transition(
2899 cx,
2900 pointer_id,
2901 pointer_type,
2902 is_primary,
2903 current_el,
2904 pending_el,
2905 );
2906 self.fire_pointer_capture_event(
2907 cx,
2908 "gotpointercapture",
2909 pointer_id,
2910 pointer_type,
2911 is_primary,
2912 pending_el,
2913 );
2914 self.pointer_capture_target
2915 .borrow_mut()
2916 .insert(pointer_id, Dom::from_ref(pending_el));
2917 } else {
2918 self.pending_pointer_capture
2919 .borrow_mut()
2920 .remove(&pointer_id);
2921 self.pointer_capture_target.borrow_mut().remove(&pointer_id);
2922 }
2923 },
2924 (None, Some(current_el)) | (Some(_), Some(current_el)) if !pending_connected => {
2925 self.fire_pointer_capture_event(
2926 cx,
2927 "lostpointercapture",
2928 pointer_id,
2929 pointer_type,
2930 is_primary,
2931 current_el,
2932 );
2933 if let Some(hover_target) = self.current_hover_target.get() {
2936 self.fire_pointer_capture_boundary_transition(
2937 cx,
2938 pointer_id,
2939 pointer_type,
2940 is_primary,
2941 current_el,
2942 &hover_target,
2943 );
2944 }
2945 self.pointer_capture_target.borrow_mut().remove(&pointer_id);
2946 if !pending_connected {
2947 self.pending_pointer_capture
2948 .borrow_mut()
2949 .remove(&pointer_id);
2950 }
2951 },
2952 _ => {},
2953 }
2954 }
2955}
2956
2957pub(crate) fn character_to_code(character: char) -> Option<Code> {
2958 Some(match character.to_ascii_lowercase() {
2959 '`' => Code::Backquote,
2960 '\\' => Code::Backslash,
2961 '[' | '{' => Code::BracketLeft,
2962 ']' | '}' => Code::BracketRight,
2963 ',' | '<' => Code::Comma,
2964 '0' => Code::Digit0,
2965 '1' => Code::Digit1,
2966 '2' => Code::Digit2,
2967 '3' => Code::Digit3,
2968 '4' => Code::Digit4,
2969 '5' => Code::Digit5,
2970 '6' => Code::Digit6,
2971 '7' => Code::Digit7,
2972 '8' => Code::Digit8,
2973 '9' => Code::Digit9,
2974 '=' => Code::Equal,
2975 'a' => Code::KeyA,
2976 'b' => Code::KeyB,
2977 'c' => Code::KeyC,
2978 'd' => Code::KeyD,
2979 'e' => Code::KeyE,
2980 'f' => Code::KeyF,
2981 'g' => Code::KeyG,
2982 'h' => Code::KeyH,
2983 'i' => Code::KeyI,
2984 'j' => Code::KeyJ,
2985 'k' => Code::KeyK,
2986 'l' => Code::KeyL,
2987 'm' => Code::KeyM,
2988 'n' => Code::KeyN,
2989 'o' => Code::KeyO,
2990 'p' => Code::KeyP,
2991 'q' => Code::KeyQ,
2992 'r' => Code::KeyR,
2993 's' => Code::KeyS,
2994 't' => Code::KeyT,
2995 'u' => Code::KeyU,
2996 'v' => Code::KeyV,
2997 'w' => Code::KeyW,
2998 'x' => Code::KeyX,
2999 'y' => Code::KeyY,
3000 'z' => Code::KeyZ,
3001 '-' => Code::Minus,
3002 '.' => Code::Period,
3003 '\'' | '"' => Code::Quote,
3004 ';' => Code::Semicolon,
3005 '/' => Code::Slash,
3006 ' ' => Code::Space,
3007 _ => return None,
3008 })
3009}